From 0c3c528798147d3c6eaf8602fd100e55b0799526 Mon Sep 17 00:00:00 2001 From: Fionn Malone Date: Wed, 22 Nov 2023 10:22:17 -0800 Subject: [PATCH] Reformat src/openfermion using black formatter. (#846) * Add black / isort configuration to pyproject.toml. * Fix configuration. * Reformat using black. * Ignore reformat for git blame. * Up max line lenght in pylintrc. --- .git-blame-ignore-revs | 2 + src/openfermion/__init__.py | 10 +- src/openfermion/_compat.py | 7 +- src/openfermion/_compat_test.py | 5 +- src/openfermion/chem/__init__.py | 25 +- src/openfermion/chem/chemical_series.py | 45 +- src/openfermion/chem/chemical_series_test.py | 40 +- src/openfermion/chem/molecular_data.py | 523 +++++--- src/openfermion/chem/molecular_data_test.py | 194 ++- src/openfermion/chem/pubchem.py | 29 +- src/openfermion/chem/pubchem_test.py | 38 +- src/openfermion/chem/reduced_hamiltonian.py | 18 +- .../chem/reduced_hamiltonian_test.py | 29 +- src/openfermion/circuits/__init__.py | 5 +- src/openfermion/circuits/gates/__init__.py | 20 +- .../circuits/gates/common_gates.py | 13 +- .../circuits/gates/common_gates_test.py | 73 +- .../circuits/gates/fermionic_simulation.py | 434 +++---- .../gates/fermionic_simulation_test.py | 402 ++++--- .../circuits/gates/four_qubit_gates.py | 71 +- .../circuits/gates/four_qubit_gates_test.py | 308 +++-- .../circuits/gates/three_qubit_gates.py | 5 +- .../circuits/gates/three_qubit_gates_test.py | 18 +- src/openfermion/circuits/lcu_util.py | 27 +- src/openfermion/circuits/lcu_util_test.py | 101 +- src/openfermion/circuits/low_rank.py | 55 +- src/openfermion/circuits/low_rank_test.py | 152 +-- .../circuits/primitives/__init__.py | 5 +- .../primitives/bogoliubov_transform.py | 126 +- .../primitives/bogoliubov_transform_test.py | 250 ++-- src/openfermion/circuits/primitives/ffft.py | 49 +- .../circuits/primitives/ffft_test.py | 266 ++-- .../optimal_givens_decomposition.py | 62 +- .../optimal_givens_decomposition_test.py | 64 +- .../circuits/primitives/state_preparation.py | 125 +- .../primitives/state_preparation_test.py | 157 ++- .../circuits/primitives/swap_network.py | 18 +- .../circuits/primitives/swap_network_test.py | 53 +- .../circuits/slater_determinants.py | 59 +- .../circuits/slater_determinants_test.py | 113 +- src/openfermion/circuits/trotter/__init__.py | 9 +- .../circuits/trotter/algorithms/__init__.py | 5 +- .../trotter/algorithms/linear_swap_network.py | 168 ++- .../circuits/trotter/algorithms/low_rank.py | 179 +-- .../trotter/algorithms/split_operator.py | 204 ++-- .../trotter/diagonal_coulomb_trotter_error.py | 133 +- .../diagonal_coulomb_trotter_error_test.py | 307 ++--- .../circuits/trotter/hubbard_trotter_error.py | 7 +- .../trotter/hubbard_trotter_error_test.py | 122 +- .../trotter/low_depth_trotter_error.py | 126 +- .../trotter/low_depth_trotter_error_test.py | 222 ++-- .../circuits/trotter/simulate_trotter.py | 124 +- .../circuits/trotter/simulate_trotter_test.py | 501 +++++--- .../circuits/trotter/trotter_algorithm.py | 41 +- .../trotter/trotter_algorithm_test.py | 3 - .../circuits/trotter/trotter_error.py | 37 +- .../circuits/trotter/trotter_error_test.py | 112 +- .../circuits/trotter_exp_to_qgates.py | 107 +- .../circuits/trotter_exp_to_qgates_test.py | 169 ++- src/openfermion/circuits/unitary_cc.py | 80 +- src/openfermion/circuits/unitary_cc_test.py | 282 +++-- src/openfermion/circuits/vpe_circuits.py | 29 +- src/openfermion/circuits/vpe_circuits_test.py | 16 +- .../contrib/representability/_bijections.py | 1 - .../representability/_bijections_test.py | 9 +- .../contrib/representability/_dualbasis.py | 43 +- .../representability/_dualbasis_test.py | 37 +- .../contrib/representability/_higham.py | 14 +- .../contrib/representability/_higham_test.py | 20 +- .../contrib/representability/_multitensor.py | 25 +- .../representability/_multitensor_test.py | 18 +- .../contrib/representability/_namedtensor.py | 42 +- .../representability/_namedtensor_test.py | 17 +- .../spin_orbital_2pos_constraints.py | 72 +- .../spin_orbital_2pos_constraints_test.py | 67 +- src/openfermion/functionals/__init__.py | 8 +- src/openfermion/functionals/contextuality.py | 13 +- .../functionals/contextuality_test.py | 31 +- src/openfermion/functionals/get_one_norm.py | 38 +- .../functionals/get_one_norm_test.py | 28 +- src/openfermion/hamiltonians/__init__.py | 10 +- .../hamiltonians/general_hubbard.py | 112 +- .../hamiltonians/general_hubbard_test.py | 187 +-- src/openfermion/hamiltonians/hartree_fock.py | 276 +++-- .../hamiltonians/hartree_fock_test.py | 193 ++- src/openfermion/hamiltonians/hubbard.py | 207 ++-- src/openfermion/hamiltonians/hubbard_test.py | 190 ++- src/openfermion/hamiltonians/jellium.py | 263 ++-- .../hamiltonians/jellium_hf_state.py | 28 +- .../hamiltonians/jellium_hf_state_test.py | 45 +- src/openfermion/hamiltonians/jellium_test.py | 192 ++- .../hamiltonians/mean_field_dwave.py | 55 +- .../hamiltonians/mean_field_dwave_test.py | 129 +- .../hamiltonians/plane_wave_hamiltonian.py | 106 +- .../plane_wave_hamiltonian_test.py | 66 +- .../hamiltonians/richardson_gaudin.py | 14 +- .../hamiltonians/richardson_gaudin_test.py | 54 +- .../hamiltonians/special_operators.py | 44 +- .../hamiltonians/special_operators_test.py | 112 +- src/openfermion/linalg/__init__.py | 18 +- src/openfermion/linalg/davidson.py | 148 +-- src/openfermion/linalg/davidson_test.py | 319 ++--- src/openfermion/linalg/erpa.py | 38 +- src/openfermion/linalg/erpa_test.py | 51 +- src/openfermion/linalg/givens_rotations.py | 118 +- .../linalg/givens_rotations_test.py | 96 +- .../linalg/linear_qubit_operator.py | 42 +- .../linalg/linear_qubit_operator_test.py | 178 +-- .../linalg/rdm_reconstruction_test.py | 2 +- src/openfermion/linalg/sparse_tools.py | 548 ++++----- src/openfermion/linalg/sparse_tools_test.py | 1071 ++++++++--------- src/openfermion/linalg/wave_fitting.py | 25 +- src/openfermion/linalg/wave_fitting_test.py | 34 +- src/openfermion/linalg/wedge_product.py | 42 +- src/openfermion/linalg/wedge_product_test.py | 75 +- src/openfermion/measurements/__init__.py | 10 +- .../equality_constraint_projection.py | 46 +- .../equality_constraint_projection_test.py | 57 +- .../measurements/fermion_partitioning.py | 84 +- .../measurements/fermion_partitioning_test.py | 45 +- .../measurements/get_interaction_rdm.py | 4 +- .../measurements/get_interaction_rdm_test.py | 2 +- .../measurements/qubit_partitioning.py | 30 +- .../measurements/qubit_partitioning_test.py | 77 +- .../measurements/rdm_equality_constraints.py | 30 +- .../rdm_equality_constraints_test.py | 28 +- .../measurements/vpe_estimators.py | 37 +- .../measurements/vpe_estimators_test.py | 33 +- src/openfermion/ops/operators/__init__.py | 13 +- src/openfermion/ops/operators/binary_code.py | 75 +- .../ops/operators/binary_code_test.py | 62 +- .../ops/operators/binary_polynomial.py | 85 +- .../ops/operators/binary_polynomial_test.py | 15 +- .../ops/operators/boson_operator.py | 8 +- .../ops/operators/boson_operator_test.py | 1 - .../ops/operators/fermion_operator.py | 10 +- .../ops/operators/fermion_operator_test.py | 1 - .../ops/operators/majorana_operator.py | 29 +- .../ops/operators/majorana_operator_test.py | 35 +- .../ops/operators/quad_operator.py | 8 +- .../ops/operators/quad_operator_test.py | 1 - .../ops/operators/qubit_operator.py | 35 +- .../ops/operators/qubit_operator_test.py | 57 +- .../ops/operators/symbolic_operator.py | 113 +- .../ops/operators/symbolic_operator_test.py | 249 ++-- .../ops/representations/__init__.py | 11 +- .../diagonal_coulomb_hamiltonian.py | 10 +- .../diagonal_coulomb_hamiltonian_test.py | 31 +- .../ops/representations/doci_hamiltonian.py | 135 ++- .../representations/doci_hamiltonian_test.py | 142 +-- .../representations/interaction_operator.py | 93 +- .../interaction_operator_test.py | 61 +- .../ops/representations/interaction_rdm.py | 37 +- .../representations/interaction_rdm_test.py | 27 +- .../ops/representations/polynomial_tensor.py | 51 +- .../representations/polynomial_tensor_test.py | 410 +++---- .../representations/quadratic_hamiltonian.py | 220 ++-- .../quadratic_hamiltonian_test.py | 275 ++--- .../resource_estimates/__init__.py | 1 + .../resource_estimates/df/__init__.py | 4 +- .../resource_estimates/df/compute_cost_df.py | 67 +- .../df/compute_cost_df_test.py | 12 +- .../df/compute_lambda_df.py | 11 +- .../df/compute_lambda_df_test.py | 7 +- .../resource_estimates/df/factorize_df.py | 4 +- .../df/generate_costing_table_df.py | 131 +- .../resource_estimates/molecule/__init__.py | 19 +- .../molecule/pyscf_utils.py | 286 +++-- .../molecule/pyscf_utils_test.py | 65 +- .../pbc/df/compute_df_resources.py | 109 +- .../pbc/df/compute_df_resources_test.py | 27 +- .../pbc/df/compute_lambda_df.py | 18 +- .../pbc/df/compute_lambda_df_test.py | 34 +- .../resource_estimates/pbc/df/df_integrals.py | 71 +- .../pbc/df/df_integrals_test.py | 18 +- .../pbc/df/generate_costing_table_df.py | 36 +- .../pbc/df/generate_costing_table_df_test.py | 23 +- .../pbc/hamiltonian/__init__.py | 9 +- .../pbc/hamiltonian/cc_extensions.py | 39 +- .../pbc/hamiltonian/cc_extensions_test.py | 39 +- .../pbc/hamiltonian/hamiltonian.py | 37 +- .../pbc/hamiltonian/hamiltonian_test.py | 21 +- .../pbc/resources/data_types.py | 13 +- .../pbc/resources/data_types_test.py | 29 +- .../resource_estimates/pbc/resources/qrom.py | 8 +- .../pbc/resources/qrom_test.py | 2 +- .../pbc/sf/compute_lambda_sf.py | 23 +- .../pbc/sf/compute_lambda_sf_test.py | 39 +- .../pbc/sf/compute_sf_resources.py | 153 ++- .../pbc/sf/compute_sf_resources_test.py | 20 +- .../pbc/sf/generate_costing_table_sf.py | 36 +- .../pbc/sf/generate_costing_table_sf_test.py | 19 +- .../resource_estimates/pbc/sf/sf_integrals.py | 31 +- .../pbc/sf/sf_integrals_test.py | 15 +- .../pbc/sparse/compute_lambda_sparse.py | 33 +- .../pbc/sparse/compute_lambda_sparse_test.py | 22 +- .../pbc/sparse/compute_sparse_resources.py | 98 +- .../sparse/compute_sparse_resources_test.py | 12 +- .../sparse/generate_costing_table_sparse.py | 36 +- .../generate_costing_table_sparse_test.py | 17 +- .../pbc/sparse/sparse_integrals.py | 39 +- .../pbc/sparse/sparse_integrals_test.py | 44 +- .../pbc/thc/compute_lambda_thc.py | 49 +- .../pbc/thc/compute_lambda_thc_test.py | 28 +- .../pbc/thc/compute_thc_resources.py | 73 +- .../pbc/thc/compute_thc_resources_test.py | 6 +- .../pbc/thc/factorizations/gvec_map_logic.py | 28 +- .../thc/factorizations/gvec_map_logic_test.py | 113 +- .../pbc/thc/factorizations/isdf.py | 338 ++---- .../pbc/thc/factorizations/isdf_test.py | 265 ++-- .../pbc/thc/factorizations/kmeans.py | 28 +- .../pbc/thc/factorizations/kmeans_test.py | 10 +- .../pbc/thc/factorizations/thc_jax.py | 363 +++--- .../pbc/thc/factorizations/thc_jax_test.py | 130 +- .../pbc/thc/generate_costing_table_thc.py | 50 +- .../thc/generate_costing_table_thc_test.py | 11 +- .../pbc/thc/thc_integrals.py | 77 +- .../pbc/thc/thc_integrals_test.py | 26 +- .../resource_estimates/sf/__init__.py | 2 +- .../resource_estimates/sf/compute_cost_sf.py | 92 +- .../sf/compute_cost_sf_test.py | 12 +- .../sf/compute_lambda_sf.py | 14 +- .../sf/compute_lambda_sf_test.py | 7 +- .../resource_estimates/sf/factorize_sf.py | 8 +- .../sf/generate_costing_table_sf.py | 122 +- .../sparse/costing_sparse.py | 46 +- .../sparse/costing_sparse_test.py | 4 +- .../physical_costing.py | 131 +- .../resource_estimates/thc/__init__.py | 2 +- .../thc/compute_cost_thc.py | 35 +- .../thc/compute_cost_thc_test.py | 7 +- .../thc/compute_lambda_thc.py | 43 +- .../thc/compute_lambda_thc_test.py | 11 +- .../resource_estimates/thc/factorize_thc.py | 106 +- .../thc/generate_costing_table_thc.py | 137 ++- .../resource_estimates/thc/spacetime.py | 222 ++-- .../resource_estimates/thc/spacetime_test.py | 517 +++++++- .../resource_estimates/thc/utils/__init__.py | 35 +- .../resource_estimates/thc/utils/adagrad.py | 35 +- .../thc/utils/thc_factorization.py | 179 ++- .../thc/utils/thc_objectives.py | 215 ++-- src/openfermion/resource_estimates/utils.py | 44 +- .../resource_estimates/utils_test.py | 20 +- src/openfermion/testing/__init__.py | 5 +- src/openfermion/testing/circuit_validation.py | 10 +- .../testing/circuit_validation_test.py | 11 +- src/openfermion/testing/examples_test.py | 12 +- .../testing/hydrogen_integration_test.py | 173 ++- .../testing/lih_integration_test.py | 67 +- .../testing/performance_benchmarks.py | 94 +- .../testing/performance_benchmarks_test.py | 3 +- src/openfermion/testing/random.py | 4 +- src/openfermion/testing/random_test.py | 7 +- src/openfermion/testing/testing_utils.py | 103 +- src/openfermion/testing/testing_utils_test.py | 104 +- src/openfermion/testing/wrapped.py | 26 +- .../transforms/opconversions/__init__.py | 37 +- .../opconversions/binary_code_transform.py | 40 +- .../binary_code_transform_test.py | 26 +- .../transforms/opconversions/binary_codes.py | 76 +- .../opconversions/binary_codes_test.py | 48 +- .../transforms/opconversions/bksf.py | 148 ++- .../transforms/opconversions/bksf_test.py | 151 +-- .../transforms/opconversions/bravyi_kitaev.py | 288 ++--- .../opconversions/bravyi_kitaev_test.py | 98 +- .../opconversions/bravyi_kitaev_tree.py | 51 +- .../opconversions/bravyi_kitaev_tree_test.py | 47 +- .../commutator_diagonal_coulomb_operator.py | 131 +- ...mmutator_diagonal_coulomb_operator_test.py | 131 +- .../transforms/opconversions/conversions.py | 57 +- .../opconversions/conversions_test.py | 136 ++- .../transforms/opconversions/fenwick_tree.py | 2 + .../opconversions/fenwick_tree_test.py | 1 - .../transforms/opconversions/jordan_wigner.py | 146 ++- .../opconversions/jordan_wigner_test.py | 175 ++- .../qubitoperator_to_paulisum.py | 14 +- .../qubitoperator_to_paulisum_test.py | 30 +- .../opconversions/remove_symmetry_qubits.py | 105 +- .../remove_symmetry_qubits_test.py | 59 +- .../opconversions/reverse_jordan_wigner.py | 14 +- .../reverse_jordan_wigner_test.py | 43 +- .../opconversions/term_reordering.py | 74 +- .../opconversions/term_reordering_test.py | 84 +- .../opconversions/verstraete_cirac.py | 65 +- .../opconversions/verstraete_cirac_test.py | 40 +- .../transforms/repconversions/__init__.py | 22 +- .../transforms/repconversions/conversions.py | 163 +-- .../repconversions/conversions_test.py | 168 +-- .../repconversions/fourier_transforms.py | 34 +- .../repconversions/fourier_transforms_test.py | 29 +- .../repconversions/operator_tapering.py | 10 +- .../repconversions/operator_tapering_test.py | 8 +- .../qubit_operator_transforms.py | 25 +- .../qubit_operator_transforms_test.py | 39 +- .../qubit_tapering_from_stabilizer.py | 109 +- .../qubit_tapering_from_stabilizer_test.py | 216 ++-- .../repconversions/weyl_ordering.py | 9 +- .../repconversions/weyl_ordering_test.py | 37 +- src/openfermion/utils/__init__.py | 19 +- src/openfermion/utils/bch_expansion.py | 48 +- src/openfermion/utils/bch_expansion_test.py | 34 +- src/openfermion/utils/channel_state.py | 81 +- src/openfermion/utils/channel_state_test.py | 124 +- src/openfermion/utils/commutators.py | 148 +-- src/openfermion/utils/commutators_test.py | 285 +++-- src/openfermion/utils/grid.py | 87 +- src/openfermion/utils/grid_test.py | 68 +- src/openfermion/utils/indexing.py | 4 +- src/openfermion/utils/lattice.py | 111 +- src/openfermion/utils/lattice_test.py | 188 +-- src/openfermion/utils/operator_utils.py | 96 +- src/openfermion/utils/operator_utils_test.py | 219 ++-- .../utils/rdm_mapping_functions.py | 31 +- .../utils/rdm_mapping_functions_test.py | 131 +- 314 files changed, 12874 insertions(+), 13356 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..f70dc11c1 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# migrating to the black formatter +5cd8b96e605039af1496b6ad97ea490ef2fa7b82 diff --git a/src/openfermion/__init__.py b/src/openfermion/__init__.py index bb2f6017e..278d9194a 100644 --- a/src/openfermion/__init__.py +++ b/src/openfermion/__init__.py @@ -34,9 +34,13 @@ make_reduced_hamiltonian, ) -from openfermion.functionals import (contextuality, get_one_norm_mol, - get_one_norm_mol_woconst, get_one_norm_int, - get_one_norm_int_woconst) +from openfermion.functionals import ( + contextuality, + get_one_norm_mol, + get_one_norm_mol_woconst, + get_one_norm_int, + get_one_norm_int_woconst, +) from openfermion.hamiltonians import ( FermiHubbardModel, diff --git a/src/openfermion/_compat.py b/src/openfermion/_compat.py index e1ef41528..bc90c8c98 100644 --- a/src/openfermion/_compat.py +++ b/src/openfermion/_compat.py @@ -14,8 +14,7 @@ import warnings -def wrap_module(module: ModuleType, - deprecated_attributes: Dict[str, Tuple[str, str]]): +def wrap_module(module: ModuleType, deprecated_attributes: Dict[str, Tuple[str, str]]): """Wrap a module with deprecated attributes. Args: @@ -30,7 +29,6 @@ def wrap_module(module: ModuleType, """ class Wrapped(ModuleType): - __dict__ = module.__dict__ def __getattr__(self, name): @@ -42,7 +40,8 @@ def __getattr__(self, name): f'openfermion {version}.\n' f'{fix}\n', DeprecationWarning, - stacklevel=2) + stacklevel=2, + ) return getattr(module, name) return Wrapped(module.__name__, module.__doc__) diff --git a/src/openfermion/_compat_test.py b/src/openfermion/_compat_test.py index 010d7db39..68cdd4db6 100644 --- a/src/openfermion/_compat_test.py +++ b/src/openfermion/_compat_test.py @@ -49,7 +49,6 @@ def f(): def test_deprecated_test(): - @deprecated_test def test(): f() @@ -63,14 +62,12 @@ def test(): def test_wrap_module(): openfermion.deprecated_attribute = None - wrapped_openfermion = wrap_module(openfermion, - {'deprecated_attribute': ('', '')}) + wrapped_openfermion = wrap_module(openfermion, {'deprecated_attribute': ('', '')}) with pytest.deprecated_call(): _ = wrapped_openfermion.deprecated_attribute def test_cirq_deprecations(): - @deprecated(deadline="v0.12", fix="use new_func") def old_func(): pass diff --git a/src/openfermion/chem/__init__.py b/src/openfermion/chem/__init__.py index 367b2b8c9..07a1d8972 100644 --- a/src/openfermion/chem/__init__.py +++ b/src/openfermion/chem/__init__.py @@ -9,17 +9,22 @@ # 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. -from .chemical_series import ( - make_atom, - make_atomic_lattice, - make_atomic_ring, -) +from .chemical_series import make_atom, make_atomic_lattice, make_atomic_ring -from .molecular_data import (angstroms_to_bohr, bohr_to_angstroms, - MolecularData, name_molecule, geometry_from_file, - load_molecular_hamiltonian, periodic_table, - periodic_hash_table, periodic_polarization, - antisymtei, j_mat, k_mat) +from .molecular_data import ( + angstroms_to_bohr, + bohr_to_angstroms, + MolecularData, + name_molecule, + geometry_from_file, + load_molecular_hamiltonian, + periodic_table, + periodic_hash_table, + periodic_polarization, + antisymtei, + j_mat, + k_mat, +) from .pubchem import geometry_from_pubchem diff --git a/src/openfermion/chem/chemical_series.py b/src/openfermion/chem/chemical_series.py index 3bc4c2bc5..24361852b 100644 --- a/src/openfermion/chem/chemical_series.py +++ b/src/openfermion/chem/chemical_series.py @@ -13,8 +13,11 @@ import numpy -from openfermion.chem.molecular_data import (MolecularData, periodic_hash_table, - periodic_polarization) +from openfermion.chem.molecular_data import ( + MolecularData, + periodic_hash_table, + periodic_polarization, +) # Define error objects which inherit from Exception. @@ -22,12 +25,7 @@ class MolecularLatticeError(Exception): pass -def make_atomic_ring(n_atoms, - spacing, - basis, - atom_type='H', - charge=0, - filename=''): +def make_atomic_ring(n_atoms, spacing, basis, atom_type='H', charge=0, filename=''): """Function to create atomic rings with n_atoms. Note that basic geometry suggests that for spacing L between atoms @@ -47,36 +45,30 @@ def make_atomic_ring(n_atoms, """ # Make geometry. geometry = [] - theta = 2. * numpy.pi / float(n_atoms) - radius = spacing / (2. * numpy.cos(numpy.pi / 2. - theta / 2.)) + theta = 2.0 * numpy.pi / float(n_atoms) + radius = spacing / (2.0 * numpy.cos(numpy.pi / 2.0 - theta / 2.0)) for atom in range(n_atoms): x_coord = radius * numpy.cos(atom * theta) y_coord = radius * numpy.sin(atom * theta) - geometry += [(atom_type, (x_coord, y_coord, 0.))] + geometry += [(atom_type, (x_coord, y_coord, 0.0))] # Set multiplicity. n_electrons = n_atoms * periodic_hash_table[atom_type] n_electrons -= charge - if (n_electrons % 2): + if n_electrons % 2: multiplicity = 2 else: multiplicity = 1 # Create molecule and return. description = 'ring_{}'.format(spacing) - molecule = MolecularData(geometry, basis, multiplicity, charge, description, - filename) + molecule = MolecularData(geometry, basis, multiplicity, charge, description, filename) return molecule -def make_atomic_lattice(nx_atoms, - ny_atoms, - nz_atoms, - spacing, - basis, - atom_type='H', - charge=0, - filename=''): +def make_atomic_lattice( + nx_atoms, ny_atoms, nz_atoms, spacing, basis, atom_type='H', charge=0, filename='' +): """Function to create atomic lattice with n_atoms. Args: @@ -110,7 +102,7 @@ def make_atomic_lattice(nx_atoms, n_atoms = nx_atoms * ny_atoms * nz_atoms n_electrons = n_atoms * periodic_hash_table[atom_type] n_electrons -= charge - if (n_electrons % 2): + if n_electrons % 2: multiplicity = 2 else: multiplicity = 1 @@ -127,8 +119,7 @@ def make_atomic_lattice(nx_atoms, raise MolecularLatticeError('Invalid lattice dimensions.') # Create molecule and return. - molecule = MolecularData(geometry, basis, multiplicity, charge, description, - filename) + molecule = MolecularData(geometry, basis, multiplicity, charge, description, filename) return molecule @@ -142,9 +133,9 @@ def make_atom(atom_type, basis, filename=''): Returns: atom: An instance of the MolecularData class. """ - geometry = [(atom_type, (0., 0., 0.))] + geometry = [(atom_type, (0.0, 0.0, 0.0))] atomic_number = periodic_hash_table[atom_type] - spin = periodic_polarization[atomic_number] / 2. + spin = periodic_polarization[atomic_number] / 2.0 multiplicity = int(2 * spin + 1) atom = MolecularData(geometry, basis, multiplicity, filename=filename) return atom diff --git a/src/openfermion/chem/chemical_series_test.py b/src/openfermion/chem/chemical_series_test.py index 343a978f4..ba9d249b3 100644 --- a/src/openfermion/chem/chemical_series_test.py +++ b/src/openfermion/chem/chemical_series_test.py @@ -14,27 +14,28 @@ import unittest import numpy -from openfermion.chem.chemical_series import (MolecularLatticeError, - make_atomic_lattice, make_atom, - make_atomic_ring) -from openfermion.chem.molecular_data import (periodic_table, - periodic_polarization) +from openfermion.chem.chemical_series import ( + MolecularLatticeError, + make_atomic_lattice, + make_atom, + make_atomic_ring, +) +from openfermion.chem.molecular_data import periodic_table, periodic_polarization class ChemicalSeries(unittest.TestCase): - def test_make_atomic_ring(self): - spacing = 1. + spacing = 1.0 basis = 'sto-3g' for n_atoms in range(2, 10): molecule = make_atomic_ring(n_atoms, spacing, basis) # Check that ring is centered. - vector_that_should_sum_to_zero = 0. + vector_that_should_sum_to_zero = 0.0 for atom in molecule.geometry: for coordinate in atom[1]: vector_that_should_sum_to_zero += coordinate - self.assertAlmostEqual(vector_that_should_sum_to_zero, 0.) + self.assertAlmostEqual(vector_that_should_sum_to_zero, 0.0) # Check that the spacing between the atoms is correct. for atom_index in range(n_atoms): @@ -44,9 +45,10 @@ def test_make_atomic_ring(self): atom_a = molecule.geometry[atom_index - 1] coords_a = atom_a[1] observed_spacing = numpy.sqrt( - numpy.square(coords_b[0] - coords_a[0]) + - numpy.square(coords_b[1] - coords_a[1]) + - numpy.square(coords_b[2] - coords_a[2])) + numpy.square(coords_b[0] - coords_a[0]) + + numpy.square(coords_b[1] - coords_a[1]) + + numpy.square(coords_b[2] - coords_a[2]) + ) self.assertAlmostEqual(observed_spacing, spacing) def test_make_atomic_lattice_1d(self): @@ -54,8 +56,7 @@ def test_make_atomic_lattice_1d(self): basis = 'sto-3g' atom_type = 'H' for n_atoms in range(2, 10): - molecule = make_atomic_lattice(n_atoms, 1, 1, spacing, basis, - atom_type) + molecule = make_atomic_lattice(n_atoms, 1, 1, spacing, basis, atom_type) # Check that the spacing between the atoms is correct. for atom_index in range(n_atoms): @@ -73,8 +74,7 @@ def test_make_atomic_lattice_2d(self): basis = 'sto-3g' atom_type = 'H' atom_dim = 7 - molecule = make_atomic_lattice(atom_dim, atom_dim, 1, spacing, basis, - atom_type) + molecule = make_atomic_lattice(atom_dim, atom_dim, 1, spacing, basis, atom_type) # Check that the spacing between the atoms is correct. for atom in range(atom_dim**2): @@ -93,8 +93,7 @@ def test_make_atomic_lattice_3d(self): basis = 'sto-3g' atom_type = 'H' atom_dim = 4 - molecule = make_atomic_lattice(atom_dim, atom_dim, atom_dim, spacing, - basis, atom_type) + molecule = make_atomic_lattice(atom_dim, atom_dim, atom_dim, spacing, basis, atom_type) # Check that the spacing between the atoms is correct. for atom in range(atom_dim**3): @@ -118,8 +117,7 @@ def test_make_atomic_lattice_0d_raise_error(self): atom_type = 'H' atom_dim = 0 with self.assertRaises(MolecularLatticeError): - make_atomic_lattice(atom_dim, atom_dim, atom_dim, spacing, basis, - atom_type) + make_atomic_lattice(atom_dim, atom_dim, atom_dim, spacing, basis, atom_type) def test_make_atom(self): basis = 'sto-3g' @@ -127,6 +125,6 @@ def test_make_atom(self): for n_electrons in range(1, largest_atom): atom_name = periodic_table[n_electrons] atom = make_atom(atom_name, basis) - expected_spin = periodic_polarization[n_electrons] / 2. + expected_spin = periodic_polarization[n_electrons] / 2.0 expected_multiplicity = int(2 * expected_spin + 1) self.assertAlmostEqual(expected_multiplicity, atom.multiplicity) diff --git a/src/openfermion/chem/molecular_data.py b/src/openfermion/chem/molecular_data.py index c0f0d1f11..15787e761 100644 --- a/src/openfermion/chem/molecular_data.py +++ b/src/openfermion/chem/molecular_data.py @@ -19,6 +19,7 @@ from openfermion.config import EQ_TOLERANCE, DATA_DIRECTORY import openfermion.ops.representations as reps + r"""NOTE ON PQRS CONVENTION: The data structures which hold fermionic operators / integrals / coefficients assume a particular convention which depends on how integrals @@ -58,15 +59,110 @@ def angstroms_to_bohr(distance): # The Periodic Table as a python list and dictionary. periodic_table = [ # - '?', 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', - 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', - 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', - 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', - 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', - 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', - 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', - 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', - 'Lr' + '?', + 'H', + 'He', + 'Li', + 'Be', + 'B', + 'C', + 'N', + 'O', + 'F', + 'Ne', + 'Na', + 'Mg', + 'Al', + 'Si', + 'P', + 'S', + 'Cl', + 'Ar', + 'K', + 'Ca', + 'Sc', + 'Ti', + 'V', + 'Cr', + 'Mn', + 'Fe', + 'Co', + 'Ni', + 'Cu', + 'Zn', + 'Ga', + 'Ge', + 'As', + 'Se', + 'Br', + 'Kr', + 'Rb', + 'Sr', + 'Y', + 'Zr', + 'Nb', + 'Mo', + 'Tc', + 'Ru', + 'Rh', + 'Pd', + 'Ag', + 'Cd', + 'In', + 'Sn', + 'Sb', + 'Te', + 'I', + 'Xe', + 'Cs', + 'Ba', + 'La', + 'Ce', + 'Pr', + 'Nd', + 'Pm', + 'Sm', + 'Eu', + 'Gd', + 'Tb', + 'Dy', + 'Ho', + 'Er', + 'Tm', + 'Yb', + 'Lu', + 'Hf', + 'Ta', + 'W', + 'Re', + 'Os', + 'Ir', + 'Pt', + 'Au', + 'Hg', + 'Tl', + 'Pb', + 'Bi', + 'Po', + 'At', + 'Rn', + 'Fr', + 'Ra', + 'Ac', + 'Th', + 'Pa', + 'U', + 'Np', + 'Pu', + 'Am', + 'Cm', + 'Bk', + 'Cf', + 'Es', + 'Fm', + 'Md', + 'No', + 'Lr', ] periodic_hash_table = {} @@ -75,9 +171,61 @@ def angstroms_to_bohr(distance): # Spin polarization of atoms on period table. periodic_polarization = [ - -1, 1, 0, 1, 0, 1, 2, 3, 2, 1, 0, 1, 0, 1, 2, 3, 2, 1, 0, 1, 0, 1, 2, 3, 6, - 5, 4, 3, 2, 1, 0, 1, 2, 3, 2, 1, 0, 1, 0, 1, 2, 5, 6, 5, 8, 9, 0, 1, 0, 1, - 2, 3, 2, 1, 0 + -1, + 1, + 0, + 1, + 0, + 1, + 2, + 3, + 2, + 1, + 0, + 1, + 0, + 1, + 2, + 3, + 2, + 1, + 0, + 1, + 0, + 1, + 2, + 3, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + 1, + 2, + 3, + 2, + 1, + 0, + 1, + 0, + 1, + 2, + 5, + 6, + 5, + 8, + 9, + 0, + 1, + 0, + 1, + 2, + 3, + 2, + 1, + 0, ] @@ -104,8 +252,7 @@ def name_molecule(geometry, basis, multiplicity, charge, description): # Get sorted atom vector. atoms = [item[0] for item in geometry] atom_charge_info = [(atom, atoms.count(atom)) for atom in set(atoms)] - sorted_info = sorted(atom_charge_info, - key=lambda atom: periodic_hash_table[atom[0]]) + sorted_info = sorted(atom_charge_info, key=lambda atom: periodic_hash_table[atom[0]]) # Name molecule. name = '{}{}'.format(sorted_info[0][0], sorted_info[0][1]) @@ -130,9 +277,9 @@ def name_molecule(geometry, basis, multiplicity, charge, description): 9: 'nonet', 10: 'dectet', 11: 'undectet', - 12: 'duodectet' + 12: 'duodectet', } - if (multiplicity not in multiplicity_dict): + if multiplicity not in multiplicity_dict: raise MoleculeNameError('Invalid spin multiplicity provided.') else: name += '_{}'.format(multiplicity_dict[multiplicity]) @@ -199,8 +346,7 @@ def j_mat(two_body_integrals): j_matr : Numpy array of the coulomb integrals J_{p,q} = (pp|qq) (in chemist notation). """ - chem_ordering = numpy.copy(two_body_integrals.transpose(0, 3, 1, 2), - order='C') + chem_ordering = numpy.copy(two_body_integrals.transpose(0, 3, 1, 2), order='C') return numpy.einsum('iijj -> ij', chem_ordering) @@ -214,8 +360,7 @@ def k_mat(two_body_integrals): k_matr : Numpy array of the exchange integrals K_{p,q} = (pq|qp) (in chemist notation). """ - chem_ordering = numpy.copy(two_body_integrals.transpose(0, 3, 1, 2), - order='C') + chem_ordering = numpy.copy(two_body_integrals.transpose(0, 3, 1, 2), order='C') return numpy.einsum('ijji -> ij', chem_ordering) @@ -224,38 +369,35 @@ def spinorb_from_spatial(one_body_integrals, two_body_integrals): # Initialize Hamiltonian coefficients. one_body_coefficients = numpy.zeros((n_qubits, n_qubits)) - two_body_coefficients = numpy.zeros( - (n_qubits, n_qubits, n_qubits, n_qubits)) + two_body_coefficients = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits)) # Loop through integrals. for p in range(n_qubits // 2): for q in range(n_qubits // 2): - # Populate 1-body coefficients. Require p and q have same spin. one_body_coefficients[2 * p, 2 * q] = one_body_integrals[p, q] - one_body_coefficients[2 * p + 1, 2 * q + - 1] = one_body_integrals[p, q] + one_body_coefficients[2 * p + 1, 2 * q + 1] = one_body_integrals[p, q] # Continue looping to prepare 2-body coefficients. for r in range(n_qubits // 2): for s in range(n_qubits // 2): - # Mixed spin - two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * - s] = (two_body_integrals[p, q, r, s]) - two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + - 1] = (two_body_integrals[p, q, r, s]) + two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = two_body_integrals[ + p, q, r, s + ] + two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = two_body_integrals[ + p, q, r, s + ] # Same spin - two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * - s] = (two_body_integrals[p, q, r, s]) - two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + - 1, 2 * s + - 1] = (two_body_integrals[p, q, r, s]) + two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = two_body_integrals[ + p, q, r, s + ] + two_body_coefficients[ + 2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1 + ] = two_body_integrals[p, q, r, s] # Truncate. - one_body_coefficients[ - numpy.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0. - two_body_coefficients[ - numpy.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0. + one_body_coefficients[numpy.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0.0 + two_body_coefficients[numpy.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0.0 return one_body_coefficients, two_body_coefficients @@ -306,14 +448,16 @@ class MolecularData(object): for this system annotated by the key. """ - def __init__(self, - geometry=None, - basis=None, - multiplicity=None, - charge=0, - description="", - filename="", - data_directory=None): + def __init__( + self, + geometry=None, + basis=None, + multiplicity=None, + charge=0, + description="", + filename="", + data_directory=None, + ): """Initialize molecular metadata which defines class. Args: @@ -336,18 +480,19 @@ def __init__(self, data directory specified in config file. """ # Check appropriate data as been provided and autoload if requested. - if ((geometry is None) or (basis is None) or (multiplicity is None)): + if (geometry is None) or (basis is None) or (multiplicity is None): if filename: if filename[-5:] == '.hdf5': - self.filename = filename[:(len(filename) - 5)] + self.filename = filename[: (len(filename) - 5)] else: self.filename = filename self.load() self.init_lazy_properties() return else: - raise ValueError("Geometry, basis, multiplicity must be" - "specified when not loading from file.") + raise ValueError( + "Geometry, basis, multiplicity must be" "specified when not loading from file." + ) # Metadata fields which must be provided. self.geometry = geometry @@ -356,16 +501,15 @@ def __init__(self, # Metadata fields with default values. self.charge = charge - if (not isinstance(description, basestring)): + if not isinstance(description, basestring): raise TypeError("description must be a string.") self.description = description # Name molecule and get associated filename - self.name = name_molecule(geometry, basis, multiplicity, charge, - description) + self.name = name_molecule(geometry, basis, multiplicity, charge, description) if filename: if filename[-5:] == '.hdf5': - filename = filename[:(len(filename) - 5)] + filename = filename[: (len(filename) - 5)] self.filename = filename else: if data_directory is None: @@ -376,8 +520,9 @@ def __init__(self, # Attributes generated automatically by class. if not isinstance(geometry, basestring): self.n_atoms = len(geometry) - self.atoms = sorted([row[0] for row in geometry], - key=lambda atom: periodic_hash_table[atom]) + self.atoms = sorted( + [row[0] for row in geometry], key=lambda atom: periodic_hash_table[atom] + ) self.protons = [periodic_hash_table[atom] for atom in self.atoms] self.n_electrons = sum(self.protons) - charge else: @@ -448,8 +593,7 @@ def init_lazy_properties(self): def canonical_orbitals(self): if self._canonical_orbitals is None: data = self.get_from_file("canonical_orbitals") - self._canonical_orbitals = (data if data is not None and - data.dtype.num != 0 else None) + self._canonical_orbitals = data if data is not None and data.dtype.num != 0 else None return self._canonical_orbitals @canonical_orbitals.setter @@ -460,8 +604,7 @@ def canonical_orbitals(self, value): def overlap_integrals(self): if self._overlap_integrals is None: data = self.get_from_file("overlap_integrals") - self._overlap_integrals = (data if data is not None and - data.dtype.num != 0 else None) + self._overlap_integrals = data if data is not None and data.dtype.num != 0 else None return self._overlap_integrals @overlap_integrals.setter @@ -472,8 +615,7 @@ def overlap_integrals(self, value): def one_body_integrals(self): if self._one_body_integrals is None: data = self.get_from_file("one_body_integrals") - self._one_body_integrals = (data if data is not None and - data.dtype.num != 0 else None) + self._one_body_integrals = data if data is not None and data.dtype.num != 0 else None return self._one_body_integrals @one_body_integrals.setter @@ -484,8 +626,7 @@ def one_body_integrals(self, value): def two_body_integrals(self): if self._two_body_integrals is None: data = self.get_from_file("two_body_integrals") - self._two_body_integrals = (data if data is not None and - data.dtype.num != 0 else None) + self._two_body_integrals = data if data is not None and data.dtype.num != 0 else None return self._two_body_integrals @two_body_integrals.setter @@ -496,8 +637,7 @@ def two_body_integrals(self, value): def cisd_one_rdm(self): if self._cisd_one_rdm is None: data = self.get_from_file("cisd_one_rdm") - self._cisd_one_rdm = (data if data is not None and - data.dtype.num != 0 else None) + self._cisd_one_rdm = data if data is not None and data.dtype.num != 0 else None return self._cisd_one_rdm @cisd_one_rdm.setter @@ -508,8 +648,7 @@ def cisd_one_rdm(self, value): def cisd_two_rdm(self): if self._cisd_two_rdm is None: data = self.get_from_file("cisd_two_rdm") - self._cisd_two_rdm = (data if data is not None and - data.dtype.num != 0 else None) + self._cisd_two_rdm = data if data is not None and data.dtype.num != 0 else None return self._cisd_two_rdm @cisd_two_rdm.setter @@ -520,8 +659,7 @@ def cisd_two_rdm(self, value): def fci_one_rdm(self): if self._fci_one_rdm is None: data = self.get_from_file("fci_one_rdm") - self._fci_one_rdm = (data if data is not None and - data.dtype.num != 0 else None) + self._fci_one_rdm = data if data is not None and data.dtype.num != 0 else None return self._fci_one_rdm @fci_one_rdm.setter @@ -532,8 +670,7 @@ def fci_one_rdm(self, value): def fci_two_rdm(self): if self._fci_two_rdm is None: data = self.get_from_file("fci_two_rdm") - self._fci_two_rdm = (data if data is not None and - data.dtype.num != 0 else None) + self._fci_two_rdm = data if data is not None and data.dtype.num != 0 else None return self._fci_two_rdm @fci_two_rdm.setter @@ -544,8 +681,7 @@ def fci_two_rdm(self, value): def ccsd_single_amps(self): if self._ccsd_single_amps is None: data = self.get_from_file("ccsd_single_amps") - self._ccsd_single_amps = (data if data is not None and - data.dtype.num != 0 else None) + self._ccsd_single_amps = data if data is not None and data.dtype.num != 0 else None return self._ccsd_single_amps @ccsd_single_amps.setter @@ -556,8 +692,7 @@ def ccsd_single_amps(self, value): def ccsd_double_amps(self): if self._ccsd_double_amps is None: data = self.get_from_file("ccsd_double_amps") - self._ccsd_double_amps = (data if data is not None and - data.dtype.num != 0 else None) + self._ccsd_double_amps = data if data is not None and data.dtype.num != 0 else None return self._ccsd_double_amps @ccsd_double_amps.setter @@ -574,16 +709,12 @@ def save(self): d_geom = f.create_group("geometry") if not isinstance(self.geometry, basestring): atoms = [numpy.string_(item[0]) for item in self.geometry] - positions = numpy.array( - [list(item[1]) for item in self.geometry]) + positions = numpy.array([list(item[1]) for item in self.geometry]) else: atoms = numpy.string_(self.geometry) positions = None - d_geom.create_dataset("atoms", - data=(atoms if atoms is not None else False)) - d_geom.create_dataset( - "positions", - data=(positions if positions is not None else False)) + d_geom.create_dataset("atoms", data=(atoms if atoms is not None else False)) + d_geom.create_dataset("positions", data=(positions if positions is not None else False)) # Save basis: f.create_dataset("basis", data=numpy.string_(self.basis)) # Save multiplicity: @@ -591,8 +722,7 @@ def save(self): # Save charge: f.create_dataset("charge", data=self.charge) # Save description: - f.create_dataset("description", - data=numpy.string_(self.description)) + f.create_dataset("description", data=numpy.string_(self.description)) # Save name: f.create_dataset("name", data=numpy.string_(self.name)) # Save n_atoms: @@ -604,107 +734,106 @@ def save(self): # Save n_electrons: f.create_dataset("n_electrons", data=self.n_electrons) # Save generic attributes from calculations: - f.create_dataset("n_orbitals", - data=(self.n_orbitals - if self.n_orbitals is not None else False)) f.create_dataset( - "n_qubits", - data=(self.n_qubits if self.n_qubits is not None else False)) + "n_orbitals", data=(self.n_orbitals if self.n_orbitals is not None else False) + ) + f.create_dataset( + "n_qubits", data=(self.n_qubits if self.n_qubits is not None else False) + ) f.create_dataset( "nuclear_repulsion", - data=(self.nuclear_repulsion - if self.nuclear_repulsion is not None else False)) + data=(self.nuclear_repulsion if self.nuclear_repulsion is not None else False), + ) # Save attributes generated from SCF calculation. f.create_dataset( - "hf_energy", - data=(self.hf_energy if self.hf_energy is not None else False)) + "hf_energy", data=(self.hf_energy if self.hf_energy is not None else False) + ) f.create_dataset( "canonical_orbitals", - data=(self.canonical_orbitals - if self.canonical_orbitals is not None else False), - compression=("gzip" - if self.canonical_orbitals is not None else None)) + data=(self.canonical_orbitals if self.canonical_orbitals is not None else False), + compression=("gzip" if self.canonical_orbitals is not None else None), + ) f.create_dataset( "overlap_integrals", - data=(self.overlap_integrals - if self.overlap_integrals is not None else False), - compression=("gzip" - if self.overlap_integrals is not None else None)) + data=(self.overlap_integrals if self.overlap_integrals is not None else False), + compression=("gzip" if self.overlap_integrals is not None else None), + ) f.create_dataset( "orbital_energies", - data=(self.orbital_energies - if self.orbital_energies is not None else False)) + data=(self.orbital_energies if self.orbital_energies is not None else False), + ) # Save attributes generated from integrals. f.create_dataset( "one_body_integrals", - data=(self.one_body_integrals - if self.one_body_integrals is not None else False), - compression=("gzip" - if self.one_body_integrals is not None else None)) + data=(self.one_body_integrals if self.one_body_integrals is not None else False), + compression=("gzip" if self.one_body_integrals is not None else None), + ) f.create_dataset( "two_body_integrals", - data=(self.two_body_integrals - if self.two_body_integrals is not None else False), - compression=("gzip" - if self.two_body_integrals is not None else None)) + data=(self.two_body_integrals if self.two_body_integrals is not None else False), + compression=("gzip" if self.two_body_integrals is not None else None), + ) # Save attributes generated from MP2 calculation. - f.create_dataset("mp2_energy", - data=(self.mp2_energy - if self.mp2_energy is not None else False)) + f.create_dataset( + "mp2_energy", data=(self.mp2_energy if self.mp2_energy is not None else False) + ) # Save attributes generated from CISD calculation. - f.create_dataset("cisd_energy", - data=(self.cisd_energy - if self.cisd_energy is not None else False)) + f.create_dataset( + "cisd_energy", data=(self.cisd_energy if self.cisd_energy is not None else False) + ) f.create_dataset( "cisd_one_rdm", - data=(self.cisd_one_rdm - if self.cisd_one_rdm is not None else False), - compression=("gzip" if self.cisd_one_rdm is not None else None)) + data=(self.cisd_one_rdm if self.cisd_one_rdm is not None else False), + compression=("gzip" if self.cisd_one_rdm is not None else None), + ) f.create_dataset( "cisd_two_rdm", - data=(self.cisd_two_rdm - if self.cisd_two_rdm is not None else False), - compression=("gzip" if self.cisd_two_rdm is not None else None)) + data=(self.cisd_two_rdm if self.cisd_two_rdm is not None else False), + compression=("gzip" if self.cisd_two_rdm is not None else None), + ) # Save attributes generated from exact diagonalization. - f.create_dataset("fci_energy", - data=(self.fci_energy - if self.fci_energy is not None else False)) + f.create_dataset( + "fci_energy", data=(self.fci_energy if self.fci_energy is not None else False) + ) f.create_dataset( "fci_one_rdm", - data=(self.fci_one_rdm - if self.fci_one_rdm is not None else False), - compression=("gzip" if self.fci_one_rdm is not None else None)) + data=(self.fci_one_rdm if self.fci_one_rdm is not None else False), + compression=("gzip" if self.fci_one_rdm is not None else None), + ) f.create_dataset( "fci_two_rdm", - data=(self.fci_two_rdm - if self.fci_two_rdm is not None else False), - compression=("gzip" if self.fci_two_rdm is not None else None)) + data=(self.fci_two_rdm if self.fci_two_rdm is not None else False), + compression=("gzip" if self.fci_two_rdm is not None else None), + ) # Save attributes generated from CCSD calculation. - f.create_dataset("ccsd_energy", - data=(self.ccsd_energy - if self.ccsd_energy is not None else False)) + f.create_dataset( + "ccsd_energy", data=(self.ccsd_energy if self.ccsd_energy is not None else False) + ) f.create_dataset( "ccsd_single_amps", - data=(self.ccsd_single_amps - if self.ccsd_single_amps is not None else False), - compression=("gzip" - if self.ccsd_single_amps is not None else None)) + data=(self.ccsd_single_amps if self.ccsd_single_amps is not None else False), + compression=("gzip" if self.ccsd_single_amps is not None else None), + ) f.create_dataset( "ccsd_double_amps", - data=(self.ccsd_double_amps - if self.ccsd_double_amps is not None else False), - compression=("gzip" - if self.ccsd_double_amps is not None else None)) + data=(self.ccsd_double_amps if self.ccsd_double_amps is not None else False), + compression=("gzip" if self.ccsd_double_amps is not None else None), + ) # Save general calculation data key_list = list(self.general_calculations.keys()) - f.create_dataset("general_calculations_keys", - data=([numpy.string_(key) for key in key_list] - if len(key_list) > 0 else False)) + f.create_dataset( + "general_calculations_keys", + data=([numpy.string_(key) for key in key_list] if len(key_list) > 0 else False), + ) f.create_dataset( "general_calculations_values", - data=([self.general_calculations[key] for key in key_list] - if len(key_list) > 0 else False)) + data=( + [self.general_calculations[key] for key in key_list] + if len(key_list) > 0 + else False + ), + ) # Remove old file first for compatibility with systems that don't allow # rename replacement. Catching OSError for when file does not exist @@ -723,8 +852,7 @@ def load(self): # Load geometry: data = f["geometry/atoms"] if data.shape != (()): - for atom, pos in zip(f["geometry/atoms"][...], - f["geometry/positions"][...]): + for atom, pos in zip(f["geometry/atoms"][...], f["geometry/positions"][...]): geometry.append((atom.tobytes().decode('utf-8'), list(pos))) self.geometry = geometry else: @@ -736,8 +864,7 @@ def load(self): # Load charge: self.charge = int(f["charge"][...]) # Load description: - self.description = f["description"][...].tobytes().decode( - 'utf-8').rstrip(u'\x00') + self.description = f["description"][...].tobytes().decode('utf-8').rstrip(u'\x00') # Load name: self.name = f["name"][...].tobytes().decode('utf-8') # Load n_atoms: @@ -754,8 +881,7 @@ def load(self): data = f["n_qubits"][...] self.n_qubits = int(data) if data.dtype.num != 0 else None data = f["nuclear_repulsion"][...] - self.nuclear_repulsion = (float(data) - if data.dtype.num != 0 else None) + self.nuclear_repulsion = float(data) if data.dtype.num != 0 else None # Load attributes generated from SCF calculation. data = f["hf_energy"][...] self.hf_energy = data if data.dtype.num != 0 else None @@ -774,8 +900,7 @@ def load(self): data = f["ccsd_energy"][...] self.ccsd_energy = data if data.dtype.num != 0 else None # Load general calculations - if ("general_calculations_keys" in f and - "general_calculations_values" in f): + if "general_calculations_keys" in f and "general_calculations_values" in f: keys = f["general_calculations_keys"] values = f["general_calculations_values"] if keys.shape != (()): @@ -833,12 +958,11 @@ def get_integrals(self): if self.one_body_integrals is None or self.two_body_integrals is None: raise MissingCalculationError( 'Missing integral calculation in {}, run before loading ' - 'integrals.'.format(self.filename)) + 'integrals.'.format(self.filename) + ) return self.one_body_integrals, self.two_body_integrals - def get_active_space_integrals(self, - occupied_indices=None, - active_indices=None): + def get_active_space_integrals(self, occupied_indices=None, active_indices=None): """Restricts a molecule at a spatial orbital level to an active space This active space may be defined by a list of active indices and @@ -866,18 +990,16 @@ def get_active_space_integrals(self, """ # Fix data type for a few edge cases occupied_indices = [] if occupied_indices is None else occupied_indices - if (len(active_indices) < 1): + if len(active_indices) < 1: raise ValueError('Some active indices required for reduction.') # Get integrals. one_body_integrals, two_body_integrals = self.get_integrals() - return reps.get_active_space_integrals(one_body_integrals, - two_body_integrals, - occupied_indices, active_indices) + return reps.get_active_space_integrals( + one_body_integrals, two_body_integrals, occupied_indices, active_indices + ) - def get_molecular_hamiltonian(self, - occupied_indices=None, - active_indices=None): + def get_molecular_hamiltonian(self, occupied_indices=None, active_indices=None): """Output arrays of the second quantized Hamiltonian coefficients. Args: @@ -899,16 +1021,21 @@ def get_molecular_hamiltonian(self, one_body_integrals, two_body_integrals = self.get_integrals() constant = self.nuclear_repulsion else: - core_adjustment, one_body_integrals, two_body_integrals = self. \ - get_active_space_integrals(occupied_indices, active_indices) + ( + core_adjustment, + one_body_integrals, + two_body_integrals, + ) = self.get_active_space_integrals(occupied_indices, active_indices) constant = self.nuclear_repulsion + core_adjustment one_body_coefficients, two_body_coefficients = spinorb_from_spatial( - one_body_integrals, two_body_integrals) + one_body_integrals, two_body_integrals + ) # Cast to InteractionOperator class and return. molecular_hamiltonian = reps.InteractionOperator( - constant, one_body_coefficients, 1 / 2 * two_body_coefficients) + constant, one_body_coefficients, 1 / 2 * two_body_coefficients + ) return molecular_hamiltonian @@ -930,21 +1057,23 @@ def get_molecular_rdm(self, use_fci=False): if use_fci: if self.fci_energy is None: raise MissingCalculationError( - 'Missing FCI RDM in {}'.format(self.filename) + - 'Run FCI calculation before loading FCI RDMs.') + 'Missing FCI RDM in {}'.format(self.filename) + + 'Run FCI calculation before loading FCI RDMs.' + ) one_rdm = self.fci_one_rdm two_rdm = self.fci_two_rdm else: if self.cisd_energy is None: raise MissingCalculationError( - 'Missing CISD RDM in {}'.format(self.filename) + - 'Run CISD calculation before loading CISD RDMs.') + 'Missing CISD RDM in {}'.format(self.filename) + + 'Run CISD calculation before loading CISD RDMs.' + ) one_rdm = self.cisd_one_rdm two_rdm = self.cisd_two_rdm # Truncate. - one_rdm[numpy.absolute(one_rdm) < EQ_TOLERANCE] = 0. - two_rdm[numpy.absolute(two_rdm) < EQ_TOLERANCE] = 0. + one_rdm[numpy.absolute(one_rdm) < EQ_TOLERANCE] = 0.0 + two_rdm[numpy.absolute(two_rdm) < EQ_TOLERANCE] = 0.0 # Cast to InteractionRDM class. rdm = reps.InteractionRDM(one_rdm, two_rdm) @@ -964,7 +1093,8 @@ def get_j(self): if self.two_body_integrals is None: raise MissingCalculationError( 'Missing integral calculation in {}, run before loading ' - 'integrals.'.format(self.filename)) + 'integrals.'.format(self.filename) + ) return j_mat(self.two_body_integrals) def get_k(self): @@ -981,7 +1111,8 @@ def get_k(self): if self.two_body_integrals is None: raise MissingCalculationError( 'Missing integral calculation in {}, run before loading ' - 'integrals.'.format(self.filename)) + 'integrals.'.format(self.filename) + ) return k_mat(self.two_body_integrals) def get_antisym(self): @@ -998,18 +1129,17 @@ def get_antisym(self): if self.two_body_integrals is None: raise MissingCalculationError( 'Missing integral calculation in {}, run before loading ' - 'integrals.'.format(self.filename)) - _, two_body_coefficients = spinorb_from_spatial(self.one_body_integrals, - self.two_body_integrals) + 'integrals.'.format(self.filename) + ) + _, two_body_coefficients = spinorb_from_spatial( + self.one_body_integrals, self.two_body_integrals + ) return antisymtei(two_body_coefficients) -def load_molecular_hamiltonian(geometry, - basis, - multiplicity, - description, - n_active_electrons=None, - n_active_orbitals=None): +def load_molecular_hamiltonian( + geometry, basis, multiplicity, description, n_active_electrons=None, n_active_orbitals=None +): """Attempt to load a molecular Hamiltonian with the given properties. Args: @@ -1030,10 +1160,7 @@ def load_molecular_hamiltonian(geometry, The Hamiltonian as an InteractionOperator. """ - molecule = MolecularData(geometry, - basis, - multiplicity, - description=description) + molecule = MolecularData(geometry, basis, multiplicity, description=description) molecule.load() if n_active_electrons is None: @@ -1046,8 +1173,8 @@ def load_molecular_hamiltonian(geometry, if n_active_orbitals is None: active_indices = None else: - active_indices = list( - range(n_core_orbitals, n_core_orbitals + n_active_orbitals)) + active_indices = list(range(n_core_orbitals, n_core_orbitals + n_active_orbitals)) - return molecule.get_molecular_hamiltonian(occupied_indices=occupied_indices, - active_indices=active_indices) + return molecule.get_molecular_hamiltonian( + occupied_indices=occupied_indices, active_indices=active_indices + ) diff --git a/src/openfermion/chem/molecular_data_test.py b/src/openfermion/chem/molecular_data_test.py index c6776a70a..a6c222d10 100644 --- a/src/openfermion/chem/molecular_data_test.py +++ b/src/openfermion/chem/molecular_data_test.py @@ -20,25 +20,29 @@ from openfermion.hamiltonians import jellium_model from openfermion.chem.chemical_series import make_atom from openfermion.chem.molecular_data import ( - name_molecule, angstroms_to_bohr, bohr_to_angstroms, - load_molecular_hamiltonian, MolecularData, MoleculeNameError, - geometry_from_file, MissingCalculationError, periodic_table) -from openfermion.transforms.repconversions import (get_interaction_operator, - get_molecular_data) + name_molecule, + angstroms_to_bohr, + bohr_to_angstroms, + load_molecular_hamiltonian, + MolecularData, + MoleculeNameError, + geometry_from_file, + MissingCalculationError, + periodic_table, +) +from openfermion.transforms.repconversions import get_interaction_operator, get_molecular_data from openfermion.utils import count_qubits, Grid class MolecularDataTest(unittest.TestCase): - def setUp(self): - self.geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + self.geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] self.basis = 'sto-3g' self.multiplicity = 1 self.filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - filename=self.filename) + self.molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, filename=self.filename + ) self.molecule.load() def testUnitConversion(self): @@ -52,67 +56,61 @@ def testUnitConversion(self): def test_name_molecule(self): charge = 0 correct_name = str('H2_sto-3g_singlet_0.7414') - computed_name = name_molecule(self.geometry, - self.basis, - self.multiplicity, - charge, - description="0.7414") + computed_name = name_molecule( + self.geometry, self.basis, self.multiplicity, charge, description="0.7414" + ) self.assertEqual(correct_name, computed_name) self.assertEqual(correct_name, self.molecule.name) # Check (+) charge charge = 1 correct_name = "H2_sto-3g_singlet_1+_0.7414" - computed_name = name_molecule(self.geometry, - self.basis, - self.multiplicity, - charge, - description="0.7414") + computed_name = name_molecule( + self.geometry, self.basis, self.multiplicity, charge, description="0.7414" + ) self.assertEqual(correct_name, computed_name) # Check > 1 atom type charge = 0 correct_name = "H1-F1_sto-3g_singlet_1.0" test_geometry = [('H', (0, 0, 0)), ('F', (0, 0, 1.0))] - computed_name = name_molecule(test_geometry, - self.basis, - self.multiplicity, - charge, - description="1.0") + computed_name = name_molecule( + test_geometry, self.basis, self.multiplicity, charge, description="1.0" + ) self.assertEqual(correct_name, computed_name) # Check errors in naming with self.assertRaises(TypeError): - test_molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - description=5) + test_molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, description=5 + ) correct_name = str('H2_sto-3g_singlet') test_molecule = self.molecule = MolecularData( - self.geometry, - self.basis, - self.multiplicity, - data_directory=DATA_DIRECTORY) + self.geometry, self.basis, self.multiplicity, data_directory=DATA_DIRECTORY + ) self.assertSequenceEqual(correct_name, test_molecule.name) def test_invalid_multiplicity(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = -1 with self.assertRaises(MoleculeNameError): MolecularData(geometry, basis, multiplicity) def test_geometry_from_file(self): - water_geometry = [('O', (0., 0., 0.)), ('H', (0.757, 0.586, 0.)), - ('H', (-.757, 0.586, 0.))] + water_geometry = [ + ('O', (0.0, 0.0, 0.0)), + ('H', (0.757, 0.586, 0.0)), + ('H', (-0.757, 0.586, 0.0)), + ] filename = os.path.join(DATA_DIRECTORY, 'geometry_example.txt') test_geometry = geometry_from_file(filename) for atom in range(3): - self.assertAlmostEqual(water_geometry[atom][0], - test_geometry[atom][0]) + self.assertAlmostEqual(water_geometry[atom][0], test_geometry[atom][0]) for coordinate in range(3): - self.assertAlmostEqual(water_geometry[atom][1][coordinate], - test_geometry[atom][1][coordinate]) + self.assertAlmostEqual( + water_geometry[atom][1][coordinate], test_geometry[atom][1][coordinate] + ) def test_save_load(self): n_atoms = self.molecule.n_atoms @@ -126,34 +124,32 @@ def test_save_load(self): self.assertTrue(dummy_data is None) def test_dummy_save(self): - # Make fake molecule. filename = os.path.join(DATA_DIRECTORY, 'dummy_molecule') - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = '6-31g*' multiplicity = 7 charge = -1 description = 'openfermion_forever' - molecule = MolecularData(geometry, basis, multiplicity, charge, - description, filename) + molecule = MolecularData(geometry, basis, multiplicity, charge, description, filename) # Make some attributes to save. molecule.n_orbitals = 10 molecule.n_qubits = 10 molecule.nuclear_repulsion = -12.3 - molecule.hf_energy = 99. + molecule.hf_energy = 99.0 molecule.canonical_orbitals = [1, 2, 3, 4] molecule.orbital_energies = [5, 6, 7, 8] molecule.one_body_integrals = [5, 6, 7, 8] molecule.two_body_integrals = [5, 6, 7, 8] - molecule.mp2_energy = -12. - molecule.cisd_energy = 32. + molecule.mp2_energy = -12.0 + molecule.cisd_energy = 32.0 molecule.cisd_one_rdm = numpy.arange(10) molecule.cisd_two_rdm = numpy.arange(10) - molecule.fci_energy = 232. + molecule.fci_energy = 232.0 molecule.fci_one_rdm = numpy.arange(11) molecule.fci_two_rdm = numpy.arange(11) - molecule.ccsd_energy = 88. + molecule.ccsd_energy = 88.0 molecule.ccsd_single_amps = [1, 2, 3] molecule.ccsd_double_amps = [1, 2, 3] molecule.general_calculations['Fake CI'] = 1.2345 @@ -163,7 +159,7 @@ def test_dummy_save(self): molecule.one_body_integrals = None with self.assertRaises(MissingCalculationError): molecule.get_integrals() - molecule.hf_energy = 99. + molecule.hf_energy = 99.0 with self.assertRaises(ValueError): molecule.get_active_space_integrals([], []) @@ -171,12 +167,12 @@ def test_dummy_save(self): molecule.fci_energy = None with self.assertRaises(MissingCalculationError): molecule.get_molecular_rdm(use_fci=True) - molecule.fci_energy = 232. + molecule.fci_energy = 232.0 molecule.cisd_energy = None with self.assertRaises(MissingCalculationError): molecule.get_molecular_rdm(use_fci=False) - molecule.cisd_energy = 232. + molecule.cisd_energy = 232.0 # Save molecule. molecule.save() @@ -194,25 +190,25 @@ def test_dummy_save(self): molecule.save() # Check CCSD energy. - self.assertAlmostEqual(new_molecule.ccsd_energy, - molecule.ccsd_energy) - self.assertAlmostEqual(molecule.ccsd_energy, 88.) + self.assertAlmostEqual(new_molecule.ccsd_energy, molecule.ccsd_energy) + self.assertAlmostEqual(molecule.ccsd_energy, 88.0) finally: os.remove(filename + '.hdf5') def test_file_loads(self): """Test different filename specs""" data_directory = os.path.join(DATA_DIRECTORY) - molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - filename=self.filename) + molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, filename=self.filename + ) test_hf_energy = molecule.hf_energy - molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - filename=self.filename + ".hdf5", - data_directory=data_directory) + molecule = MolecularData( + self.geometry, + self.basis, + self.multiplicity, + filename=self.filename + ".hdf5", + data_directory=data_directory, + ) self.assertAlmostEqual(test_hf_energy, molecule.hf_energy) molecule = MolecularData(filename=self.filename + ".hdf5") @@ -226,16 +222,19 @@ def test_active_space(self): """Test simple active space truncation features""" # Start w/ no truncation - core_const, one_body_integrals, two_body_integrals = ( - self.molecule.get_active_space_integrals(active_indices=[0, 1])) + ( + core_const, + one_body_integrals, + two_body_integrals, + ) = self.molecule.get_active_space_integrals(active_indices=[0, 1]) self.assertAlmostEqual(core_const, 0.0) self.assertAlmostEqual( - scipy.linalg.norm(one_body_integrals - - self.molecule.one_body_integrals), 0.0) + scipy.linalg.norm(one_body_integrals - self.molecule.one_body_integrals), 0.0 + ) self.assertAlmostEqual( - scipy.linalg.norm(two_body_integrals - - self.molecule.two_body_integrals), 0.0) + scipy.linalg.norm(two_body_integrals - self.molecule.two_body_integrals), 0.0 + ) def test_energies(self): self.assertAlmostEqual(self.molecule.hf_energy, -1.1167, places=4) @@ -245,7 +244,6 @@ def test_energies(self): self.assertAlmostEqual(self.molecule.ccsd_energy, -1.1373, places=4) def test_rdm_and_rotation(self): - # Compute total energy from RDM. molecular_hamiltonian = self.molecule.get_molecular_hamiltonian() molecular_rdm = self.molecule.get_molecular_rdm() @@ -254,10 +252,8 @@ def test_rdm_and_rotation(self): # Build random rotation with correction dimension. num_spatial_orbitals = self.molecule.n_orbitals - rotation_generator = numpy.random.randn(num_spatial_orbitals, - num_spatial_orbitals) - rotation_matrix = scipy.linalg.expm(rotation_generator - - rotation_generator.T) + rotation_generator = numpy.random.randn(num_spatial_orbitals, num_spatial_orbitals) + rotation_matrix = scipy.linalg.expm(rotation_generator - rotation_generator.T) # Compute total energy from RDM under some basis set rotation. molecular_rdm.rotate_basis(rotation_matrix) @@ -277,20 +273,19 @@ def test_get_up_down_electrons(self): molecule = make_atom(atom_name, basis) # Test. - self.assertAlmostEqual(molecule.get_n_alpha_electrons(), - correct_alpha[n_electrons]) - self.assertAlmostEqual(molecule.get_n_beta_electrons(), - correct_beta[n_electrons]) + self.assertAlmostEqual(molecule.get_n_alpha_electrons(), correct_alpha[n_electrons]) + self.assertAlmostEqual(molecule.get_n_beta_electrons(), correct_beta[n_electrons]) def test_abstract_molecule(self): """Test an abstract molecule like jellium for saving and loading""" - jellium_interaction = get_interaction_operator( - jellium_model(Grid(2, 2, 1.0))) - jellium_molecule = get_molecular_data(jellium_interaction, - geometry="Jellium", - basis="PlaneWave22", - multiplicity=1, - n_electrons=4) + jellium_interaction = get_interaction_operator(jellium_model(Grid(2, 2, 1.0))) + jellium_molecule = get_molecular_data( + jellium_interaction, + geometry="Jellium", + basis="PlaneWave22", + multiplicity=1, + n_electrons=4, + ) jellium_filename = jellium_molecule.filename jellium_molecule.save() @@ -301,19 +296,21 @@ def test_abstract_molecule(self): def test_load_molecular_hamiltonian(self): bond_length = 1.45 - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., bond_length))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, bond_length))] - lih_hamiltonian = load_molecular_hamiltonian(geometry, 'sto-3g', 1, - format(bond_length), 2, 2) + lih_hamiltonian = load_molecular_hamiltonian( + geometry, 'sto-3g', 1, format(bond_length), 2, 2 + ) self.assertEqual(count_qubits(lih_hamiltonian), 4) - lih_hamiltonian = load_molecular_hamiltonian(geometry, 'sto-3g', 1, - format(bond_length), 2, 3) + lih_hamiltonian = load_molecular_hamiltonian( + geometry, 'sto-3g', 1, format(bond_length), 2, 3 + ) self.assertEqual(count_qubits(lih_hamiltonian), 6) - lih_hamiltonian = load_molecular_hamiltonian(geometry, 'sto-3g', 1, - format(bond_length), None, - None) + lih_hamiltonian = load_molecular_hamiltonian( + geometry, 'sto-3g', 1, format(bond_length), None, None + ) self.assertEqual(count_qubits(lih_hamiltonian), 12) def test_jk_matr(self): @@ -338,13 +335,12 @@ def test_antisymint(self): nocc = h2mol.n_electrons mol_H = h2mol.get_molecular_hamiltonian() E1bdy = np.sum(np.diag(mol_H.one_body_tensor[:nocc, :nocc])) - E2bdy = 1/2.*np.einsum('ijij',\ - antisymm_spin_orb_tei[:nocc,:nocc,:nocc,:nocc]) + E2bdy = 1 / 2.0 * np.einsum('ijij', antisymm_spin_orb_tei[:nocc, :nocc, :nocc, :nocc]) E_test = h2mol.nuclear_repulsion + E1bdy + E2bdy self.assertAlmostEqual(E_test, h2mol.hf_energy) def test_missing_calcs_for_integrals(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.8764))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.8764))] basis = 'sto-3g' multiplicity = 1 molecule = MolecularData(geometry, basis, multiplicity) @@ -353,4 +349,4 @@ def test_missing_calcs_for_integrals(self): with self.assertRaises(MissingCalculationError): molecule.get_k() with self.assertRaises(MissingCalculationError): - molecule.get_antisym() \ No newline at end of file + molecule.get_antisym() diff --git a/src/openfermion/chem/pubchem.py b/src/openfermion/chem/pubchem.py index c0539bd10..ca365c431 100644 --- a/src/openfermion/chem/pubchem.py +++ b/src/openfermion/chem/pubchem.py @@ -33,33 +33,28 @@ def geometry_from_pubchem(name: str, structure: str = None): import pubchempy if structure in ['2d', '3d']: - pubchempy_molecule = pubchempy.get_compounds(name, - 'name', - record_type=structure) + pubchempy_molecule = pubchempy.get_compounds(name, 'name', record_type=structure) elif structure is None: # Ideally get the 3-D geometry if available. - pubchempy_molecule = pubchempy.get_compounds(name, - 'name', - record_type='3d') + pubchempy_molecule = pubchempy.get_compounds(name, 'name', record_type='3d') # If the 3-D geometry isn't available, get the 2-D geometry instead. if not pubchempy_molecule: - pubchempy_molecule = pubchempy.get_compounds(name, - 'name', - record_type='2d') + pubchempy_molecule = pubchempy.get_compounds(name, 'name', record_type='2d') else: - raise ValueError('Incorrect value for the argument structure=%s' % - structure) + raise ValueError('Incorrect value for the argument structure=%s' % structure) # Check if pubchempy_molecule is an empty list or None if not pubchempy_molecule: - print("Unable to find structure info in the PubChem database" - "for the specified molecule %s." % name) + print( + "Unable to find structure info in the PubChem database" + "for the specified molecule %s." % name + ) return None - pubchempy_geometry = \ - pubchempy_molecule[0].to_dict(properties=['atoms'])['atoms'] - geometry = [(atom['element'], (atom['x'], atom['y'], atom.get('z', 0))) - for atom in pubchempy_geometry] + pubchempy_geometry = pubchempy_molecule[0].to_dict(properties=['atoms'])['atoms'] + geometry = [ + (atom['element'], (atom['x'], atom['y'], atom.get('z', 0))) for atom in pubchempy_geometry + ] return geometry diff --git a/src/openfermion/chem/pubchem_test.py b/src/openfermion/chem/pubchem_test.py index fd8dd5345..5c3cbbffb 100644 --- a/src/openfermion/chem/pubchem_test.py +++ b/src/openfermion/chem/pubchem_test.py @@ -18,13 +18,13 @@ from openfermion.chem.pubchem import geometry_from_pubchem from openfermion.testing.testing_utils import module_importable -using_pubchempy = pytest.mark.skipif(module_importable('pubchempy') is False, - reason='Not detecting `pubchempy`.') +using_pubchempy = pytest.mark.skipif( + module_importable('pubchempy') is False, reason='Not detecting `pubchempy`.' +) @using_pubchempy class OpenFermionPubChemTest(unittest.TestCase): - def test_water(self): water_geometry = geometry_from_pubchem('water') self.water_natoms = len(water_geometry) @@ -34,32 +34,33 @@ def test_water(self): water_oxygen_coordinate = numpy.array(water_oxygen[1]) water_hydrogen1_coordinate = numpy.array(water_geometry[0][1]) water_hydrogen2_coordinate = numpy.array(water_geometry[1][1]) - water_oxygen_hydrogen1 = \ - water_hydrogen1_coordinate - water_oxygen_coordinate - water_oxygen_hydrogen2 = \ - water_hydrogen2_coordinate - water_oxygen_coordinate + water_oxygen_hydrogen1 = water_hydrogen1_coordinate - water_oxygen_coordinate + water_oxygen_hydrogen2 = water_hydrogen2_coordinate - water_oxygen_coordinate self.water_bond_length_1 = numpy.linalg.norm(water_oxygen_hydrogen1) self.water_bond_length_2 = numpy.linalg.norm(water_oxygen_hydrogen2) - self.water_bond_angle = \ - numpy.arccos(numpy.dot(water_oxygen_hydrogen1, - water_oxygen_hydrogen2 / - (numpy.linalg.norm(water_oxygen_hydrogen1) * - numpy.linalg.norm(water_oxygen_hydrogen2)))) + self.water_bond_angle = numpy.arccos( + numpy.dot( + water_oxygen_hydrogen1, + water_oxygen_hydrogen2 + / ( + numpy.linalg.norm(water_oxygen_hydrogen1) + * numpy.linalg.norm(water_oxygen_hydrogen2) + ), + ) + ) water_natoms = 3 self.assertEqual(water_natoms, self.water_natoms) - self.assertAlmostEqual(self.water_bond_length_1, - self.water_bond_length_2, - places=4) + self.assertAlmostEqual(self.water_bond_length_1, self.water_bond_length_2, places=4) water_bond_length_low = 0.9 water_bond_length_high = 1.1 self.assertTrue(water_bond_length_low <= self.water_bond_length_1) self.assertTrue(water_bond_length_high >= self.water_bond_length_1) - water_bond_angle_low = 100. / 360 * 2 * numpy.pi - water_bond_angle_high = 110. / 360 * 2 * numpy.pi + water_bond_angle_low = 100.0 / 360 * 2 * numpy.pi + water_bond_angle_high = 110.0 / 360 * 2 * numpy.pi self.assertTrue(water_bond_angle_low <= self.water_bond_angle) self.assertTrue(water_bond_angle_high >= self.water_bond_angle) @@ -88,6 +89,5 @@ def test_water_2d(self): self.assertEqual(z, self.oxygen_z_1) self.assertEqual(z, self.oxygen_z_2) - with pytest.raises(ValueError, - match='Incorrect value for the argument structure'): + with pytest.raises(ValueError, match='Incorrect value for the argument structure'): _ = geometry_from_pubchem('water', structure='foo') diff --git a/src/openfermion/chem/reduced_hamiltonian.py b/src/openfermion/chem/reduced_hamiltonian.py index 5b0362be7..40dedb06c 100644 --- a/src/openfermion/chem/reduced_hamiltonian.py +++ b/src/openfermion/chem/reduced_hamiltonian.py @@ -3,8 +3,9 @@ from openfermion.ops.representations import InteractionOperator -def make_reduced_hamiltonian(molecular_hamiltonian: InteractionOperator, - n_electrons: int) -> InteractionOperator: +def make_reduced_hamiltonian( + molecular_hamiltonian: InteractionOperator, n_electrons: int +) -> InteractionOperator: r""" Construct the reduced Hamiltonian. @@ -39,8 +40,15 @@ def make_reduced_hamiltonian(molecular_hamiltonian: InteractionOperator, k2 = numpy.zeros_like(h2) normalization = 1 / (4 * (n_electrons - 1)) for i, j, k, l in product(range(h1.shape[0]), repeat=4): - k2[i, j, k, l] = normalization * ( - h1[i, l] * delta[j, k] + h1[j, k] * delta[i, l] - - h1[i, k] * delta[j, l] - h1[j, l] * delta[i, k]) + h2[i, j, k, l] + k2[i, j, k, l] = ( + normalization + * ( + h1[i, l] * delta[j, k] + + h1[j, k] * delta[i, l] + - h1[i, k] * delta[j, l] + - h1[j, l] * delta[i, k] + ) + + h2[i, j, k, l] + ) return InteractionOperator(constant, numpy.zeros_like(h1), k2) diff --git a/src/openfermion/chem/reduced_hamiltonian_test.py b/src/openfermion/chem/reduced_hamiltonian_test.py index 3821a7f6a..d6e314a23 100644 --- a/src/openfermion/chem/reduced_hamiltonian_test.py +++ b/src/openfermion/chem/reduced_hamiltonian_test.py @@ -4,16 +4,16 @@ from openfermion.chem import MolecularData from openfermion.ops.representations import InteractionOperator from openfermion.chem.reduced_hamiltonian import make_reduced_hamiltonian -from openfermion.linalg.sparse_tools import ( - get_number_preserving_sparse_operator) +from openfermion.linalg.sparse_tools import get_number_preserving_sparse_operator from openfermion.transforms.opconversions import get_fermion_operator def test_mrd_return_type(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) assert isinstance(reduced_ham, InteractionOperator) @@ -21,8 +21,9 @@ def test_mrd_return_type(): def test_constant_one_body(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) assert numpy.isclose(reduced_ham.constant, molecule.nuclear_repulsion) assert numpy.allclose(reduced_ham.one_body_tensor, 0) @@ -31,26 +32,30 @@ def test_constant_one_body(): def test_fci_energy(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) numpy_ham = get_number_preserving_sparse_operator( get_fermion_operator(reduced_ham), molecule.n_qubits, num_electrons=molecule.n_electrons, - spin_preserving=True) + spin_preserving=True, + ) w, _ = numpy.linalg.eigh(numpy_ham.toarray()) assert numpy.isclose(molecule.fci_energy, w[0]) filename = os.path.join(DATA_DIRECTORY, "H1-Li1_sto-3g_singlet_1.45.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) numpy_ham = get_number_preserving_sparse_operator( get_fermion_operator(reduced_ham), molecule.n_qubits, num_electrons=molecule.n_electrons, - spin_preserving=True) + spin_preserving=True, + ) w, _ = numpy.linalg.eigh(numpy_ham.toarray()) assert numpy.isclose(molecule.fci_energy, w[0]) diff --git a/src/openfermion/circuits/__init__.py b/src/openfermion/circuits/__init__.py index 69a4ab9f5..d596b5c83 100644 --- a/src/openfermion/circuits/__init__.py +++ b/src/openfermion/circuits/__init__.py @@ -32,10 +32,7 @@ CRyxxy, ) -from .lcu_util import ( - preprocess_lcu_coefficients_for_reversible_sampling, - lambda_norm, -) +from .lcu_util import preprocess_lcu_coefficients_for_reversible_sampling, lambda_norm from .low_rank import ( get_chemist_two_body_coefficients, diff --git a/src/openfermion/circuits/gates/__init__.py b/src/openfermion/circuits/gates/__init__.py index 09137196c..f6dbf58bf 100644 --- a/src/openfermion/circuits/gates/__init__.py +++ b/src/openfermion/circuits/gates/__init__.py @@ -11,14 +11,7 @@ # limitations under the License. """Gates useful for simulating fermions.""" -from .common_gates import ( - FSWAP, - FSwapPowGate, - Rxxyy, - Ryxxy, - Rzz, - rot11, -) +from .common_gates import FSWAP, FSwapPowGate, Rxxyy, Ryxxy, Rzz, rot11 from .fermionic_simulation import ( state_swap_eigen_component, @@ -31,13 +24,6 @@ QuarticFermionicSimulationGate, ) -from .four_qubit_gates import ( - DoubleExcitation, - DoubleExcitationGate, -) +from .four_qubit_gates import DoubleExcitation, DoubleExcitationGate -from .three_qubit_gates import ( - rot111, - CRxxyy, - CRyxxy, -) +from .three_qubit_gates import rot111, CRxxyy, CRyxxy diff --git a/src/openfermion/circuits/gates/common_gates.py b/src/openfermion/circuits/gates/common_gates.py index 695432bd8..df399bf32 100644 --- a/src/openfermion/circuits/gates/common_gates.py +++ b/src/openfermion/circuits/gates/common_gates.py @@ -57,10 +57,9 @@ def _eigen_components(self): [0, -0.5, 0.5, 0], [0, 0, 0, 1]])), ] - #yapf: enable + # yapf: enable - def _apply_unitary_(self, - args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: + def _apply_unitary_(self, args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: if self.exponent != 1: return NotImplemented @@ -73,14 +72,12 @@ def _apply_unitary_(self, args.target_tensor[ii] *= -1 return args.target_tensor - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs - ) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: if args.use_unicode_characters: symbols = '×ᶠ', '×ᶠ' else: symbols = 'fswap', 'fswap' - return cirq.CircuitDiagramInfo(wire_symbols=symbols, - exponent=self._diagram_exponent(args)) + return cirq.CircuitDiagramInfo(wire_symbols=symbols, exponent=self._diagram_exponent(args)) def __str__(self) -> str: if self.exponent == 1: @@ -114,7 +111,7 @@ def Rzz(rads: float) -> cirq.ZZPowGate: def rot11(rads: float) -> cirq.CZPowGate: """Phases the |11> state of two qubits by e^{i rads}.""" pi = sympy.pi if isinstance(rads, sympy.Basic) else np.pi - return cirq.CZ**(rads / pi) + return cirq.CZ ** (rads / pi) FSWAP = FSwapPowGate() diff --git a/src/openfermion/circuits/gates/common_gates_test.py b/src/openfermion/circuits/gates/common_gates_test.py index e8826957e..7f7ecc119 100644 --- a/src/openfermion/circuits/gates/common_gates_test.py +++ b/src/openfermion/circuits/gates/common_gates_test.py @@ -49,78 +49,89 @@ def test_fswap_repr(): def test_fswap_matrix(): np.testing.assert_allclose( cirq.unitary(openfermion.FSWAP), - np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, -1]])) + np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, -1]]), + ) np.testing.assert_allclose( cirq.unitary(openfermion.FSWAP**0.5), - np.array([[1, 0, 0, 0], [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], - [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], [0, 0, 0, 1j]])) + np.array( + [ + [1, 0, 0, 0], + [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], + [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], + [0, 0, 0, 1j], + ] + ), + ) cirq.testing.assert_has_consistent_apply_unitary_for_various_exponents( - val=openfermion.FSWAP, exponents=[1, 0.5]) + val=openfermion.FSWAP, exponents=[1, 0.5] + ) -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_compare_ryxxy_to_cirq_equivalent(rads): old_gate = openfermion.Ryxxy(rads=rads) new_gate = cirq.givens(angle_rads=rads) np.testing.assert_allclose(cirq.unitary(old_gate), cirq.unitary(new_gate)) -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_rxxyy_unitary(rads): X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]]) XX = kron(X, X) YY = kron(Y, Y) - np.testing.assert_allclose(cirq.unitary(openfermion.Rxxyy(rads)), - expm(-1j * rads * (XX + YY) / 2), - atol=1e-8) + np.testing.assert_allclose( + cirq.unitary(openfermion.Rxxyy(rads)), expm(-1j * rads * (XX + YY) / 2), atol=1e-8 + ) -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_ryxxy_unitary(rads): X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]]) YX = kron(Y, X) XY = kron(X, Y) - np.testing.assert_allclose(cirq.unitary(openfermion.Ryxxy(rads)), - expm(-1j * rads * (YX - XY) / 2), - atol=1e-8) + np.testing.assert_allclose( + cirq.unitary(openfermion.Ryxxy(rads)), expm(-1j * rads * (YX - XY) / 2), atol=1e-8 + ) -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_rzz_unitary(rads): ZZ = np.diag([1, -1, -1, 1]) - np.testing.assert_allclose(cirq.unitary(openfermion.Rzz(rads)), - expm(-1j * ZZ * rads)) + np.testing.assert_allclose(cirq.unitary(openfermion.Rzz(rads)), expm(-1j * ZZ * rads)) def test_common_gate_text_diagrams(): a = cirq.NamedQubit('a') b = cirq.NamedQubit('b') - circuit = cirq.Circuit(openfermion.FSWAP(a, b), - openfermion.FSWAP(a, b)**0.5) + circuit = cirq.Circuit(openfermion.FSWAP(a, b), openfermion.FSWAP(a, b) ** 0.5) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───×ᶠ───×ᶠ─────── │ │ b: ───×ᶠ───×ᶠ^0.5─── -""") +""", + ) - cirq.testing.assert_has_diagram(circuit, - """ + cirq.testing.assert_has_diagram( + circuit, + """ a: ---fswap---fswap------- | | b: ---fswap---fswap^0.5--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) diff --git a/src/openfermion/circuits/gates/fermionic_simulation.py b/src/openfermion/circuits/gates/fermionic_simulation.py index 3c03e1193..b432ad7ed 100644 --- a/src/openfermion/circuits/gates/fermionic_simulation.py +++ b/src/openfermion/circuits/gates/fermionic_simulation.py @@ -12,7 +12,7 @@ import abc import itertools -from typing import (cast, Dict, Optional, Sequence, Tuple, TYPE_CHECKING, Union) +from typing import cast, Dict, Optional, Sequence, Tuple, TYPE_CHECKING, Union import numpy as np import scipy.linalg as la @@ -42,8 +42,10 @@ def _canonicalize_weight(w): if cirq.is_parameterized(w): return (cirq.PeriodicValue(abs(w), 2 * sympy.pi), sympy.arg(w)) period = 2 * np.pi - return (np.round((w.real % period) if (w == np.real(w)) else - (abs(w) % period) * w / abs(w), 8), 0) + return ( + np.round((w.real % period) if (w == np.real(w)) else (abs(w) % period) * w / abs(w), 8), + 0, + ) def state_swap_eigen_component(x: str, y: str, sign: int = 1, angle: float = 0): @@ -85,18 +87,19 @@ def state_swap_eigen_component(x: str, y: str, sign: int = 1, angle: float = 0): if sign not in (-1, 1): raise ValueError('sign not in (-1, 1)') - dim = 2**len(x) + dim = 2 ** len(x) i, j = int(x, 2), int(y, 2) component = np.zeros((dim, dim), dtype=np.complex128) component[i, i] = component[j, j] = 0.5 - component[j, i] = sign * 0.5 * 1j**(angle * 2 / np.pi) - component[i, j] = sign * 0.5 * 1j**(-angle * 2 / np.pi) + component[j, i] = sign * 0.5 * 1j ** (angle * 2 / np.pi) + component[i, j] = sign * 0.5 * 1j ** (-angle * 2 / np.pi) return component def fermionic_simulation_gates_from_interaction_operator( - operator: 'openfermion.InteractionOperator'): + operator: 'openfermion.InteractionOperator', +): r""" Given $H = \sum_{I \subset [n]} H_I$, returns gates $\left\{G_I\right\} = \left\{e^{i H_I\right\}$. @@ -118,29 +121,32 @@ def fermionic_simulation_gates_from_interaction_operator( for p in range(n_qubits): coeff = operator.one_body_tensor[p, p] if coeff: - gates[(p,)] = cirq.Z**(coeff / np.pi) + gates[(p,)] = cirq.Z ** (coeff / np.pi) for modes in itertools.combinations(range(n_qubits), 2): - gate: Optional[InteractionOperatorFermionicGate] = ( - QuadraticFermionicSimulationGate.from_interaction_operator( - operator=operator, modes=modes)) + gate: Optional[ + InteractionOperatorFermionicGate + ] = QuadraticFermionicSimulationGate.from_interaction_operator( + operator=operator, modes=modes + ) if gate: gates[modes] = gate for modes in itertools.combinations(range(n_qubits), 3): gate = CubicFermionicSimulationGate.from_interaction_operator( - operator=operator, modes=modes) + operator=operator, modes=modes + ) if gate: gates[modes] = gate for modes in itertools.combinations(range(n_qubits), 4): gate = QuarticFermionicSimulationGate.from_interaction_operator( - operator=operator, modes=modes) + operator=operator, modes=modes + ) if gate: gates[modes] = gate return gates def sum_of_interaction_operator_gate_generators( - n_modes: int, - gates: Dict[Tuple[int, ...], Union[float, cirq.Gate]], + n_modes: int, gates: Dict[Tuple[int, ...], Union[float, cirq.Gate]] ) -> 'openfermion.InteractionOperator': """The interaction operator that is the sum of the generators of the specified fermionic simulation gates. @@ -174,8 +180,7 @@ def sum_of_interaction_operator_gate_generators( operator.constant += gate._exponent * gate._global_shift * np.pi operator.one_body_tensor[indices * 2] += coeff elif isinstance(gate, InteractionOperatorFermionicGate): - gate.interaction_operator_generator(operator=operator, - modes=indices) + gate.interaction_operator_generator(operator=operator, modes=indices) else: raise TypeError(f'Gate type {gate} not supported.') @@ -203,11 +208,11 @@ class ParityPreservingFermionicGate(cirq.Gate, metaclass=abc.ABCMeta): """ def __init__( - self, - weights: Optional[Tuple[complex, ...]] = None, - absorb_exponent: bool = False, - exponent: cirq.TParamVal = 1.0, - global_shift: float = 0.0, + self, + weights: Optional[Tuple[complex, ...]] = None, + absorb_exponent: bool = False, + exponent: cirq.TParamVal = 1.0, + global_shift: float = 0.0, ) -> None: """A fermionic interaction. @@ -218,7 +223,7 @@ def __init__( Defaults to `False`. """ if weights is None: - weights = (1.,) * self.num_weights() + weights = (1.0,) * self.num_weights() self.weights = weights self._exponent = exponent @@ -249,23 +254,21 @@ def num_weights(cls) -> int: def qubit_generator_matrix(self) -> np.ndarray: """The matrix G such that the gate's unitary is exp(-i t G) with exponent t.""" - return jordan_wigner_sparse(self.fermion_generator, - self.num_qubits()).toarray() + return jordan_wigner_sparse(self.fermion_generator, self.num_qubits()).toarray() @property def fermion_generator(self) -> 'openfermion.FermionOperator': """The FermionOperator G such that the gate's unitary is exp(-i t G) with exponent t using the Jordan-Wigner transformation.""" - half_generator = sum(( - w * G - for w, G in zip(self.weights, self.fermion_generator_components())), - ops.FermionOperator()) + half_generator = sum( + (w * G for w, G in zip(self.weights, self.fermion_generator_components())), + ops.FermionOperator(), + ) return half_generator + hermitian_conjugated(half_generator) - def _diagram_exponent(self, - args: cirq.CircuitDiagramInfoArgs, - *, - ignore_global_phase: bool = True): + def _diagram_exponent( + self, args: cirq.CircuitDiagramInfoArgs, *, ignore_global_phase: bool = True + ): if not isinstance(self._exponent, (int, float)): return self._exponent result = float(self._exponent) @@ -279,29 +282,23 @@ def wire_symbol(cls, use_unicode: bool): return cls.__name__ def _resolve_parameters_(self, resolver, recursive: bool = True): - resolved_weights = cirq.resolve_parameters(self.weights, - resolver, - recursive=recursive) - resolved_exponent = cirq.resolve_parameters(self._exponent, - resolver, - recursive=recursive) - resolved_global_shift = cirq.resolve_parameters(self._global_shift, - resolver, - recursive=recursive) - return type(self)(resolved_weights, - exponent=resolved_exponent, - global_shift=resolved_global_shift) + resolved_weights = cirq.resolve_parameters(self.weights, resolver, recursive=recursive) + resolved_exponent = cirq.resolve_parameters(self._exponent, resolver, recursive=recursive) + resolved_global_shift = cirq.resolve_parameters( + self._global_shift, resolver, recursive=recursive + ) + return type(self)( + resolved_weights, exponent=resolved_exponent, global_shift=resolved_global_shift + ) def _value_equality_values_(self): return tuple( _canonicalize_weight(w * self.exponent) - for w in list(self.weights) + [self._global_shift]) + for w in list(self.weights) + [self._global_shift] + ) def _is_parameterized_(self) -> bool: - return any( - cirq.is_parameterized(v) - for V in self._value_equality_values_() - for v in V) + return any(cirq.is_parameterized(v) for V in self._value_equality_values_() for v in V) def absorb_exponent_into_weights(self): period = (2 * sympy.pi) if self._is_parameterized_() else 2 * (np.pi) @@ -324,10 +321,10 @@ def permute(self, init_pos: Sequence[int]): raise ValueError(f'{init_pos} is not a permutation of {I}.') curr_pos = list(init_pos) for i in I: - for j in I[i % 2:-1:2]: + for j in I[i % 2 : -1 : 2]: if curr_pos[j] > curr_pos[j + 1]: self.fswap(j) - curr_pos[j:j + 2] = reversed(curr_pos[j:j + 2]) + curr_pos[j : j + 2] = reversed(curr_pos[j : j + 2]) assert curr_pos == list(I) def permuted(self, init_pos: Sequence[int]): @@ -345,18 +342,13 @@ def permuted(self, init_pos: Sequence[int]): return gate def __copy__(self): - return type(self)(self.weights, - exponent=self.exponent, - global_shift=self._global_shift) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs - ) -> cirq.CircuitDiagramInfo: - wire_symbols = [self.wire_symbol(args.use_unicode_characters) - ] * self.num_qubits() + return type(self)(self.weights, exponent=self.exponent, global_shift=self._global_shift) + + def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: + wire_symbols = [self.wire_symbol(args.use_unicode_characters)] * self.num_qubits() wire_symbols[0] += f'{tuple(self.weights)}' exponent = self._diagram_exponent(args) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols, - exponent=exponent) + return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols, exponent=exponent) class InteractionOperatorFermionicGate(ParityPreservingFermionicGate): @@ -370,19 +362,16 @@ class InteractionOperatorFermionicGate(ParityPreservingFermionicGate): @classmethod @abc.abstractmethod def from_interaction_operator( - cls, - *, - operator: 'openfermion.InteractionOperator', - modes: Optional[Sequence[int]] = None, + cls, *, operator: 'openfermion.InteractionOperator', modes: Optional[Sequence[int]] = None ) -> Optional['ParityPreservingFermionicGate']: """Constructs the gate corresponding to the specified term in the Hamiltonian.""" def interaction_operator_generator( - self, - *, - operator: Optional['openfermion.InteractionOperator'] = None, - modes: Optional[Sequence[int]] = None + self, + *, + operator: Optional['openfermion.InteractionOperator'] = None, + modes: Optional[Sequence[int]] = None, ) -> 'openfermion.InteractionOperator': """Constructs the Hamiltonian corresponding to the gate's generator.""" if modes is None: @@ -396,9 +385,9 @@ def interaction_operator_generator( return get_interaction_operator(fermion_operator, n_qubits=n_modes) -class QuadraticFermionicSimulationGate(InteractionOperatorFermionicGate, - cirq.InterchangeableQubitsGate, - cirq.EigenGate): +class QuadraticFermionicSimulationGate( + InteractionOperatorFermionicGate, cirq.InterchangeableQubitsGate, cirq.EigenGate +): r"""``(w0 |10⟩⟨01| + h.c.) + w1 |11⟩⟨11|`` interaction. With weights $(w_0, w_1)$ and exponent $t$, this gate's matrix @@ -441,30 +430,39 @@ def _num_qubits_(self) -> int: def _decompose_(self, qubits): r = 2 * abs(self.weights[0]) / np.pi theta = _arg(self.weights[0]) / np.pi - yield cirq.Z(qubits[0])**-theta + yield cirq.Z(qubits[0]) ** -theta yield cirq.ISwapPowGate(exponent=-r * self.exponent)(*qubits) - yield cirq.Z(qubits[0])**theta - yield cirq.CZPowGate(exponent=-self.weights[1] * self.exponent / - np.pi)(*qubits) + yield cirq.Z(qubits[0]) ** theta + yield cirq.CZPowGate(exponent=-self.weights[1] * self.exponent / np.pi)(*qubits) def _eigen_components(self): - components = [(0, np.diag([1, 0, 0, 0])), - (-self.weights[1] / np.pi, np.diag([0, 0, 0, 1]))] + components = [(0, np.diag([1, 0, 0, 0])), (-self.weights[1] / np.pi, np.diag([0, 0, 0, 1]))] r = abs(self.weights[0]) / np.pi theta = 2 * _arg(self.weights[0]) / np.pi for s in (-1, 1): components.append( - (-s * r, - np.array([[0, 0, 0, 0], [0, 1, s * 1j**(-theta), 0], - [0, s * 1j**(theta), 1, 0], [0, 0, 0, 0]]) / 2)) + ( + -s * r, + np.array( + [ + [0, 0, 0, 0], + [0, 1, s * 1j ** (-theta), 0], + [0, s * 1j ** (theta), 1, 0], + [0, 0, 0, 0], + ] + ) + / 2, + ) + ) return components def __repr__(self): - exponent_str = ('' if self.exponent == 1 else ', exponent=' + - cirq._compat.proper_repr(self.exponent)) - return ('openfermion.QuadraticFermionicSimulationGate(({}){})'.format( - ', '.join(cirq._compat.proper_repr(v) for v in self.weights), - exponent_str)) + exponent_str = ( + '' if self.exponent == 1 else ', exponent=' + cirq._compat.proper_repr(self.exponent) + ) + return 'openfermion.QuadraticFermionicSimulationGate(({}){})'.format( + ', '.join(cirq._compat.proper_repr(v) for v in self.weights), exponent_str + ) @property def qubit_generator_matrix(self): @@ -489,31 +487,29 @@ def wire_symbol(cls, use_unicode: bool): @classmethod def from_interaction_operator( - cls, - *, - operator: 'openfermion.InteractionOperator', - modes: Optional[Sequence[int]] = None, + cls, *, operator: 'openfermion.InteractionOperator', modes: Optional[Sequence[int]] = None ) -> Optional['QuadraticFermionicSimulationGate']: if modes is None: modes = (0, 1) p, q = modes tunneling_coeff = operator.one_body_tensor[p, q] - interaction_coeff = (-operator.two_body_tensor[p, q, p, q] + - operator.two_body_tensor[q, p, p, q] + - operator.two_body_tensor[p, q, q, p] - - operator.two_body_tensor[q, p, q, p]) - weights: Tuple[complex, complex] = (-tunneling_coeff, - -interaction_coeff) + interaction_coeff = ( + -operator.two_body_tensor[p, q, p, q] + + operator.two_body_tensor[q, p, p, q] + + operator.two_body_tensor[p, q, q, p] + - operator.two_body_tensor[q, p, q, p] + ) + weights: Tuple[complex, complex] = (-tunneling_coeff, -interaction_coeff) if any(weights): return cls(weights) return None def interaction_operator_generator( - self, - *, - operator: Optional['openfermion.InteractionOperator'] = None, - modes: Optional[Sequence[int]] = None + self, + *, + operator: Optional['openfermion.InteractionOperator'] = None, + modes: Optional[Sequence[int]] = None, ) -> 'openfermion.InteractionOperator': if modes is None: modes = (0, 1) @@ -534,8 +530,7 @@ def fswap(self, i: int = 0): self.weights = (self.weights[0].conjugate(), self.weights[1]) -class CubicFermionicSimulationGate(InteractionOperatorFermionicGate, - cirq.EigenGate): +class CubicFermionicSimulationGate(InteractionOperatorFermionicGate, cirq.EigenGate): r"""``w0|110⟩⟨101| + w1|110⟩⟨011| + w2|101⟩⟨011|`` + h.c. interaction. With weights $(w_0, w_1, w_2)$ and exponent $t$, this gate's @@ -598,19 +593,28 @@ def _eigen_components(self): exp_factor = -eig_val / np.pi proj = np.zeros((8, 8), dtype=np.complex128) nontrivial_indices = np.array([3, 5, 6], dtype=np.intp) - proj[nontrivial_indices[:, np.newaxis], nontrivial_indices] = ( - np.outer(eig_vec.conjugate(), eig_vec)) + proj[nontrivial_indices[:, np.newaxis], nontrivial_indices] = np.outer( + eig_vec.conjugate(), eig_vec + ) components.append((exp_factor, proj)) return components def __repr__(self): - return ('openfermion.CubicFermionicSimulationGate(' + '({})'.format( - ' ,'.join(cirq._compat.proper_repr(w) for w in self.weights)) + - ('' if self.exponent == 1 else - (', exponent=' + cirq._compat.proper_repr(self.exponent))) + - ('' if self._global_shift == 0 else - (', global_shift=' + - cirq._compat.proper_repr(self._global_shift))) + ')') + return ( + 'openfermion.CubicFermionicSimulationGate(' + + '({})'.format(' ,'.join(cirq._compat.proper_repr(w) for w in self.weights)) + + ( + '' + if self.exponent == 1 + else (', exponent=' + cirq._compat.proper_repr(self.exponent)) + ) + + ( + '' + if self._global_shift == 0 + else (', global_shift=' + cirq._compat.proper_repr(self._global_shift)) + ) + + ')' + ) @property def qubit_generator_matrix(self): @@ -636,30 +640,30 @@ def fermion_generator_components(): @classmethod def from_interaction_operator( - cls, - *, - operator: 'openfermion.InteractionOperator', - modes: Optional[Sequence[int]] = None, + cls, *, operator: 'openfermion.InteractionOperator', modes: Optional[Sequence[int]] = None ) -> Optional['CubicFermionicSimulationGate']: if modes is None: modes = (0, 1, 2) i, j, k = modes weights = tuple( - sgn * (operator.two_body_tensor[p, q, p, r] - - operator.two_body_tensor[p, q, r, p] - - operator.two_body_tensor[q, p, p, r] + - operator.two_body_tensor[q, p, r, p]) - for sgn, (p, q, - r) in zip([1, -1, 1], [(i, j, k), (j, i, k), (k, i, j)])) + sgn + * ( + operator.two_body_tensor[p, q, p, r] + - operator.two_body_tensor[p, q, r, p] + - operator.two_body_tensor[q, p, p, r] + + operator.two_body_tensor[q, p, r, p] + ) + for sgn, (p, q, r) in zip([1, -1, 1], [(i, j, k), (j, i, k), (k, i, j)]) + ) if any(weights): return cls(cast(Tuple[complex, complex, complex], weights)) return None def interaction_operator_generator( - self, - *, - operator: Optional['openfermion.InteractionOperator'] = None, - modes: Optional[Sequence[int]] = None + self, + *, + operator: Optional['openfermion.InteractionOperator'] = None, + modes: Optional[Sequence[int]] = None, ) -> 'openfermion.InteractionOperator': if modes is None: modes = (0, 1, 2) @@ -680,17 +684,14 @@ def interaction_operator_generator( def fswap(self, i: int): if i == 0: - self.weights = (-self.weights[1], -self.weights[0], - self.weights[2].conjugate()) + self.weights = (-self.weights[1], -self.weights[0], self.weights[2].conjugate()) elif i == 1: - self.weights = (self.weights[0].conjugate(), -self.weights[2], - -self.weights[1]) + self.weights = (self.weights[0].conjugate(), -self.weights[2], -self.weights[1]) else: raise ValueError(f'{i} not in (0, 1)') -class QuarticFermionicSimulationGate(InteractionOperatorFermionicGate, - cirq.EigenGate): +class QuarticFermionicSimulationGate(InteractionOperatorFermionicGate, cirq.EigenGate): r"""Rotates Hamming-weight 2 states into their bitwise complements. With weights $(w_0, w_1, w_2)$ and exponent $t$, this gate's @@ -741,22 +742,24 @@ def num_qubits(self): def _eigen_components(self): # projector onto subspace spanned by basis states with # Hamming weight != 2 - zero_component = np.diag( - [int(bin(i).count('1') != 2) for i in range(16)]) + zero_component = np.diag([int(bin(i).count('1') != 2) for i in range(16)]) state_pairs = (('0110', '1001'), ('0101', '1010'), ('0011', '1100')) plus_minus_components = tuple( - (-abs(weight) * sign / np.pi, - state_swap_eigen_component(state_pair[0], state_pair[1], sign, - np.angle(weight))) + ( + -abs(weight) * sign / np.pi, + state_swap_eigen_component(state_pair[0], state_pair[1], sign, np.angle(weight)), + ) for weight, state_pair in zip(self.weights, state_pairs) - for sign in (-1, 1)) + for sign in (-1, 1) + ) return ((0, zero_component),) + plus_minus_components - def _with_exponent(self, exponent: Union[sympy.Symbol, float] - ) -> 'QuarticFermionicSimulationGate': + def _with_exponent( + self, exponent: Union[sympy.Symbol, float] + ) -> 'QuarticFermionicSimulationGate': gate = QuarticFermionicSimulationGate(self.weights) gate._exponent = exponent return gate @@ -795,56 +798,65 @@ def _decompose_(self, qubits): return NotImplemented individual_rotations = [ - la.expm(0.5j * self.exponent * - np.array([[np.real(w), 1j * s * np.imag(w)], - [-1j * s * np.imag(w), -np.real(w)]])) + la.expm( + 0.5j + * self.exponent + * np.array([[np.real(w), 1j * s * np.imag(w)], [-1j * s * np.imag(w), -np.real(w)]]) + ) for s, w in zip([1, -1, -1], self.weights) ] combined_rotations = {} combined_rotations[0] = la.sqrtm( - np.linalg.multi_dot([ - la.inv(individual_rotations[1]), individual_rotations[0], - individual_rotations[2] - ])) + np.linalg.multi_dot( + [la.inv(individual_rotations[1]), individual_rotations[0], individual_rotations[2]] + ) + ) combined_rotations[1] = la.inv(combined_rotations[0]) - combined_rotations[2] = np.linalg.multi_dot([ - la.inv(individual_rotations[0]), individual_rotations[1], - combined_rotations[0] - ]) + combined_rotations[2] = np.linalg.multi_dot( + [la.inv(individual_rotations[0]), individual_rotations[1], combined_rotations[0]] + ) combined_rotations[3] = individual_rotations[0] controlled_rotations = { - i: cirq.ControlledGate( - cirq.MatrixGate(combined_rotations[i], qid_shape=(2,))) + i: cirq.ControlledGate(cirq.MatrixGate(combined_rotations[i], qid_shape=(2,))) for i in range(4) } a, b, c, d = qubits basis_change = list( - cirq.flatten_op_tree([ - cirq.CNOT(b, a), - cirq.CNOT(c, b), - cirq.CNOT(d, c), - cirq.CNOT(c, b), - cirq.CNOT(b, a), - cirq.CNOT(a, b), - cirq.CNOT(b, c), - cirq.CNOT(a, b), - [cirq.X(c), cirq.X(d)], - [cirq.CNOT(c, d), cirq.CNOT(d, c)], - [cirq.X(c), cirq.X(d)], - ])) + cirq.flatten_op_tree( + [ + cirq.CNOT(b, a), + cirq.CNOT(c, b), + cirq.CNOT(d, c), + cirq.CNOT(c, b), + cirq.CNOT(b, a), + cirq.CNOT(a, b), + cirq.CNOT(b, c), + cirq.CNOT(a, b), + [cirq.X(c), cirq.X(d)], + [cirq.CNOT(c, d), cirq.CNOT(d, c)], + [cirq.X(c), cirq.X(d)], + ] + ) + ) controlled_rotations = list( - cirq.flatten_op_tree([ - controlled_rotations[0](b, c), - cirq.CNOT(a, b), controlled_rotations[1](b, c), - cirq.CNOT(b, a), - cirq.CNOT(a, b), controlled_rotations[2](b, c), - cirq.CNOT(a, b), controlled_rotations[3](b, c) - ])) + cirq.flatten_op_tree( + [ + controlled_rotations[0](b, c), + cirq.CNOT(a, b), + controlled_rotations[1](b, c), + cirq.CNOT(b, a), + cirq.CNOT(a, b), + controlled_rotations[2](b, c), + cirq.CNOT(a, b), + controlled_rotations[3](b, c), + ] + ) + ) controlled_swaps = [ [cirq.CNOT(c, d), cirq.H(c)], @@ -861,14 +873,14 @@ def _decompose_(self, qubits): def wire_symbol(cls, use_unicode: bool): return '⇊⇈' if use_unicode else 'a*a*aa' - def _apply_unitary_(self, - args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: + def _apply_unitary_(self, args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: if cirq.is_parameterized(self): return NotImplemented - am, bm, cm = (la.expm(-1j * self.exponent * - np.array([[0, w], [w.conjugate(), 0]])) - for w in self.weights) + am, bm, cm = ( + la.expm(-1j * self.exponent * np.array([[0, w], [w.conjugate(), 0]])) + for w in self.weights + ) a1 = args.subspace_index(0b1001) b1 = args.subspace_index(0b0101) @@ -878,26 +890,25 @@ def _apply_unitary_(self, b2 = args.subspace_index(0b1010) c2 = args.subspace_index(0b1100) - cirq.apply_matrix_to_slices(args.target_tensor, - am, - slices=[a1, a2], - out=args.available_buffer) - cirq.apply_matrix_to_slices(args.available_buffer, - bm, - slices=[b1, b2], - out=args.target_tensor) - return cirq.apply_matrix_to_slices(args.target_tensor, - cm, - slices=[c1, c2], - out=args.available_buffer) + cirq.apply_matrix_to_slices( + args.target_tensor, am, slices=[a1, a2], out=args.available_buffer + ) + cirq.apply_matrix_to_slices( + args.available_buffer, bm, slices=[b1, b2], out=args.target_tensor + ) + return cirq.apply_matrix_to_slices( + args.target_tensor, cm, slices=[c1, c2], out=args.available_buffer + ) def __repr__(self): - return ('openfermion.QuarticFermionicSimulationGate(({}), ' - 'absorb_exponent=False, ' - 'exponent={})'.format( - ', '.join( - cirq._compat.proper_repr(v) for v in self.weights), - cirq._compat.proper_repr(self.exponent))) + return ( + 'openfermion.QuarticFermionicSimulationGate(({}), ' + 'absorb_exponent=False, ' + 'exponent={})'.format( + ', '.join(cirq._compat.proper_repr(v) for v in self.weights), + cirq._compat.proper_repr(self.exponent), + ) + ) @property def qubit_generator_matrix(self): @@ -928,28 +939,28 @@ def fermion_generator_components(): @classmethod def from_interaction_operator( - cls, - *, - operator: 'openfermion.InteractionOperator', - modes: Optional[Sequence[int]] = None, + cls, *, operator: 'openfermion.InteractionOperator', modes: Optional[Sequence[int]] = None ) -> Optional['QuarticFermionicSimulationGate']: if modes is None: modes = (0, 1, 2, 3) i, j, k, l = modes weights = tuple( - (operator.two_body_tensor[p, q, r, s] - - operator.two_body_tensor[p, q, s, r] - - operator.two_body_tensor[q, p, r, s] + - operator.two_body_tensor[q, p, s, r]) - for p, q, r, s in [(i, l, j, k), (i, k, j, l), (i, j, k, l)]) + ( + operator.two_body_tensor[p, q, r, s] + - operator.two_body_tensor[p, q, s, r] + - operator.two_body_tensor[q, p, r, s] + + operator.two_body_tensor[q, p, s, r] + ) + for p, q, r, s in [(i, l, j, k), (i, k, j, l), (i, j, k, l)] + ) if any(weights): return cls(cast(Tuple[complex, complex, complex], weights)) return None def interaction_operator_generator( - self, - operator: Optional['openfermion.InteractionOperator'] = None, - modes: Optional[Sequence[int]] = None + self, + operator: Optional['openfermion.InteractionOperator'] = None, + modes: Optional[Sequence[int]] = None, ) -> 'openfermion.InteractionOperator': if modes is None: modes = (0, 1, 2, 3) @@ -970,8 +981,11 @@ def interaction_operator_generator( def fswap(self, i: int): if i == 0: - self.weights = (self.weights[1].conjugate(), - self.weights[0].conjugate(), -self.weights[2]) + self.weights = ( + self.weights[1].conjugate(), + self.weights[0].conjugate(), + -self.weights[2], + ) elif i == 1: self.weights = (-self.weights[0], self.weights[2], self.weights[1]) elif i == 2: diff --git a/src/openfermion/circuits/gates/fermionic_simulation_test.py b/src/openfermion/circuits/gates/fermionic_simulation_test.py index f6c30f223..c519b3b0c 100644 --- a/src/openfermion/circuits/gates/fermionic_simulation_test.py +++ b/src/openfermion/circuits/gates/fermionic_simulation_test.py @@ -40,31 +40,23 @@ def test_state_swap_eigen_component_args(): state_swap_eigen_component('01', 'ab', 1) -@pytest.mark.parametrize('index_pair,n_qubits', [ - ((0, 1), 2), - ((0, 3), 2), -]) +@pytest.mark.parametrize('index_pair,n_qubits', [((0, 1), 2), ((0, 3), 2)]) def test_state_swap_eigen_component(index_pair, n_qubits): state_pair = tuple(format(i, '0' + str(n_qubits) + 'b') for i in index_pair) i, j = index_pair dim = 2**n_qubits for sign in (-1, 1): - actual_component = state_swap_eigen_component(state_pair[0], - state_pair[1], sign) + actual_component = state_swap_eigen_component(state_pair[0], state_pair[1], sign) expected_component = np.zeros((dim, dim)) expected_component[i, i] = expected_component[j, j] = 0.5 expected_component[i, j] = expected_component[j, i] = sign * 0.5 assert np.allclose(actual_component, expected_component) -@pytest.mark.parametrize('n_modes, seed', - [(7, np.random.randint(1 << 30)) for _ in range(2)]) +@pytest.mark.parametrize('n_modes, seed', [(7, np.random.randint(1 << 30)) for _ in range(2)]) def test_interaction_operator_interconversion(n_modes, seed): - operator = openfermion.random_interaction_operator(n_modes, - real=False, - seed=seed) - gates = openfermion.fermionic_simulation_gates_from_interaction_operator( - operator) + operator = openfermion.random_interaction_operator(n_modes, real=False, seed=seed) + gates = openfermion.fermionic_simulation_gates_from_interaction_operator(operator) other_operator = sum_of_interaction_operator_gate_generators(n_modes, gates) operator = openfermion.normal_ordered(operator) other_operator = openfermion.normal_ordered(other_operator) @@ -89,15 +81,12 @@ def random_fermionic_simulation_gate(order): exponent = random_real() if order == 2: weights = (random_complex(), random_real()) - return openfermion.QuadraticFermionicSimulationGate(weights, - exponent=exponent) + return openfermion.QuadraticFermionicSimulationGate(weights, exponent=exponent) weights = random_complex(3) if order == 3: - return openfermion.CubicFermionicSimulationGate(weights, - exponent=exponent) + return openfermion.CubicFermionicSimulationGate(weights, exponent=exponent) if order == 4: - return openfermion.QuarticFermionicSimulationGate(weights, - exponent=exponent) + return openfermion.QuarticFermionicSimulationGate(weights, exponent=exponent) def assert_symbolic_decomposition_consistent(gate): @@ -119,8 +108,7 @@ def assert_symbolic_decomposition_consistent(gate): def assert_generators_consistent(gate): qubit_generator = gate.qubit_generator_matrix - qubit_generator_from_fermion_generator = (super( - type(gate), gate).qubit_generator_matrix) + qubit_generator_from_fermion_generator = super(type(gate), gate).qubit_generator_matrix assert np.allclose(qubit_generator, qubit_generator_from_fermion_generator) @@ -130,9 +118,9 @@ def assert_resolution_consistent(gate): resolver = dict(zip(weight_names, gate.weights)) resolver['s'] = gate._global_shift resolver['e'] = gate._exponent - symbolic_gate = type(gate)(weight_params, - exponent=sympy.Symbol('e'), - global_shift=sympy.Symbol('s')) + symbolic_gate = type(gate)( + weight_params, exponent=sympy.Symbol('e'), global_shift=sympy.Symbol('s') + ) resolved_gate = cirq.resolve_parameters(symbolic_gate, resolver) assert resolved_gate == gate @@ -141,8 +129,9 @@ def assert_fswap_consistent(gate): gate = gate.__copy__() n_qubits = gate.num_qubits() for i in range(n_qubits - 1): - fswap = cirq.kron(np.eye(1 << i), cirq.unitary(openfermion.FSWAP), - np.eye(1 << (n_qubits - i - 2))) + fswap = cirq.kron( + np.eye(1 << i), cirq.unitary(openfermion.FSWAP), np.eye(1 << (n_qubits - i - 2)) + ) assert fswap.shape == (1 << n_qubits,) * 2 generator = gate.qubit_generator_matrix fswapped_generator = np.linalg.multi_dot([fswap, generator, fswap]) @@ -164,11 +153,13 @@ def assert_permute_consistent(gate): actual_unitary = cirq.unitary(permuted_gate) ops = [ - cca.LinearPermutationGate(n_qubits, dict(zip(range(n_qubits), pos)), - openfermion.FSWAP)(*qubits), + cca.LinearPermutationGate(n_qubits, dict(zip(range(n_qubits), pos)), openfermion.FSWAP)( + *qubits + ), gate(*qubits), - cca.LinearPermutationGate(n_qubits, dict(zip(pos, range(n_qubits))), - openfermion.FSWAP)(*qubits) + cca.LinearPermutationGate(n_qubits, dict(zip(pos, range(n_qubits))), openfermion.FSWAP)( + *qubits + ), ] circuit = cirq.Circuit(ops) expected_unitary = cirq.unitary(circuit) @@ -188,15 +179,12 @@ def assert_interaction_operator_consistent(gate): else: assert cirq.approx_eq(gate, other_gate) interaction_op = openfermion.normal_ordered(interaction_op) - other_interaction_op = openfermion.InteractionOperator.zero( - interaction_op.n_qubits) - super(type(gate), - gate).interaction_operator_generator(operator=other_interaction_op) + other_interaction_op = openfermion.InteractionOperator.zero(interaction_op.n_qubits) + super(type(gate), gate).interaction_operator_generator(operator=other_interaction_op) other_interaction_op = openfermion.normal_ordered(interaction_op) assert interaction_op == other_interaction_op - other_interaction_op = super(type(gate), - gate).interaction_operator_generator() + other_interaction_op = super(type(gate), gate).interaction_operator_generator() other_interaction_op = openfermion.normal_ordered(interaction_op) assert interaction_op == other_interaction_op @@ -207,10 +195,12 @@ def assert_interaction_operator_consistent(gate): for weights in [cast(Tuple[float, float], (1, 1)), (1, 0), (0, 1), (0, 0)] ] quadratic_gates = random_quadratic_gates + manual_quadratic_gates -cubic_gates = ([openfermion.CubicFermionicSimulationGate()] + - [random_fermionic_simulation_gate(3) for _ in range(5)]) -quartic_gates = ([openfermion.QuarticFermionicSimulationGate()] + - [random_fermionic_simulation_gate(4) for _ in range(5)]) +cubic_gates = [openfermion.CubicFermionicSimulationGate()] + [ + random_fermionic_simulation_gate(3) for _ in range(5) +] +quartic_gates = [openfermion.QuarticFermionicSimulationGate()] + [ + random_fermionic_simulation_gate(4) for _ in range(5) +] gates = quadratic_gates + cubic_gates + quartic_gates @@ -237,8 +227,10 @@ def test_weights_and_exponent(weights): exponents = np.linspace(-1, 1, 8) gates = tuple( openfermion.QuarticFermionicSimulationGate( - weights / exponent, exponent=exponent, absorb_exponent=True) - for exponent in exponents) + weights / exponent, exponent=exponent, absorb_exponent=True + ) + for exponent in exponents + ) for g1, g2 in itertools.combinations(gates, 2): assert cirq.approx_eq(g1, g2, atol=1e-100) @@ -252,9 +244,9 @@ def test_weights_and_exponent(weights): def test_zero_weights(): for gate_type in [ - openfermion.QuadraticFermionicSimulationGate, - openfermion.CubicFermionicSimulationGate, - openfermion.QuarticFermionicSimulationGate + openfermion.QuadraticFermionicSimulationGate, + openfermion.CubicFermionicSimulationGate, + openfermion.QuarticFermionicSimulationGate, ]: weights = (0,) * gate_type.num_weights() gate = gate_type(weights) @@ -269,8 +261,14 @@ def test_zero_weights(): @pytest.mark.parametrize( 'weights,exponent', - [((np.random.uniform(-5, 5) + 1j * np.random.uniform(-5, 5), - np.random.uniform(-5, 5)), np.random.uniform(-5, 5)) for _ in range(5)]) + [ + ( + (np.random.uniform(-5, 5) + 1j * np.random.uniform(-5, 5), np.random.uniform(-5, 5)), + np.random.uniform(-5, 5), + ) + for _ in range(5) + ], +) def test_quadratic_fermionic_simulation_gate_unitary(weights, exponent): generator = np.zeros((4, 4), dtype=np.complex128) # w0 |10><01| + h.c. @@ -280,14 +278,14 @@ def test_quadratic_fermionic_simulation_gate_unitary(weights, exponent): generator[3, 3] = weights[1] expected_unitary = la.expm(-1j * exponent * generator) - gate = openfermion.QuadraticFermionicSimulationGate(weights, - exponent=exponent) + gate = openfermion.QuadraticFermionicSimulationGate(weights, exponent=exponent) actual_unitary = cirq.unitary(gate) assert np.allclose(expected_unitary, actual_unitary) - symbolic_gate = (openfermion.QuadraticFermionicSimulationGate( - (sympy.Symbol('w0'), sympy.Symbol('w1')), exponent=sympy.Symbol('t'))) + symbolic_gate = openfermion.QuadraticFermionicSimulationGate( + (sympy.Symbol('w0'), sympy.Symbol('w1')), exponent=sympy.Symbol('t') + ) qubits = cirq.LineQubit.range(2) circuit = cirq.Circuit(symbolic_gate._decompose_(qubits)) resolver = {'w0': weights[0], 'w1': weights[1], 't': exponent} @@ -305,22 +303,18 @@ def test_quadratic_fermionic_simulation_gate_symbolic_decompose(gate): def test_cubic_fermionic_simulation_gate_equality(): eq = cirq.testing.EqualsTester() eq.add_equality_group( - openfermion.CubicFermionicSimulationGate()**0.5, + openfermion.CubicFermionicSimulationGate() ** 0.5, openfermion.CubicFermionicSimulationGate((1,) * 3, exponent=0.5), openfermion.CubicFermionicSimulationGate((0.5,) * 3), ) - eq.add_equality_group(openfermion.CubicFermionicSimulationGate((1j, 0, 0)),) + eq.add_equality_group(openfermion.CubicFermionicSimulationGate((1j, 0, 0))) eq.add_equality_group( - openfermion.CubicFermionicSimulationGate((sympy.Symbol('s'), 0, 0), - exponent=2), - openfermion.CubicFermionicSimulationGate((2 * sympy.Symbol('s'), 0, 0), - exponent=1), + openfermion.CubicFermionicSimulationGate((sympy.Symbol('s'), 0, 0), exponent=2), + openfermion.CubicFermionicSimulationGate((2 * sympy.Symbol('s'), 0, 0), exponent=1), ) eq.add_equality_group( openfermion.CubicFermionicSimulationGate((0, 0.7, 0), global_shift=2), - openfermion.CubicFermionicSimulationGate((0, 0.35, 0), - global_shift=1, - exponent=2), + openfermion.CubicFermionicSimulationGate((0, 0.35, 0), global_shift=1, exponent=2), ) eq.add_equality_group( openfermion.CubicFermionicSimulationGate((1, 1, 1)), @@ -328,30 +322,31 @@ def test_cubic_fermionic_simulation_gate_equality(): ) -@pytest.mark.parametrize('exponent,control', - itertools.product([0, 1, -1, 0.25, -0.5, 0.1], - [0, 1, 2])) +@pytest.mark.parametrize( + 'exponent,control', itertools.product([0, 1, -1, 0.25, -0.5, 0.1], [0, 1, 2]) +) def test_cubic_fermionic_simulation_gate_consistency_special(exponent, control): weights = tuple(np.eye(1, 3, control)[0] * 0.5 * np.pi) - general_gate = openfermion.CubicFermionicSimulationGate(weights, - exponent=exponent) + general_gate = openfermion.CubicFermionicSimulationGate(weights, exponent=exponent) general_unitary = cirq.unitary(general_gate) - indices = np.dot(list(itertools.product((0, 1), repeat=3)), - (2**np.roll(np.arange(3), -control))[::-1]) + indices = np.dot( + list(itertools.product((0, 1), repeat=3)), (2 ** np.roll(np.arange(3), -control))[::-1] + ) special_gate = cirq.ControlledGate(cirq.ISWAP**-exponent) - special_unitary = ( - cirq.unitary(special_gate)[indices[:, np.newaxis], indices]) + special_unitary = cirq.unitary(special_gate)[indices[:, np.newaxis], indices] assert np.allclose(general_unitary, special_unitary) @pytest.mark.parametrize( 'weights,exponent', - [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), - np.random.uniform(-5, 5)) for _ in range(5)]) -def test_cubic_fermionic_simulation_gate_consistency_docstring( - weights, exponent): + [ + (np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), np.random.uniform(-5, 5)) + for _ in range(5) + ], +) +def test_cubic_fermionic_simulation_gate_consistency_docstring(weights, exponent): generator = np.zeros((8, 8), dtype=np.complex128) # w0 |110><101| + h.c. generator[6, 5] = weights[0] @@ -372,88 +367,163 @@ def test_cubic_fermionic_simulation_gate_consistency_docstring( def test_quartic_fermionic_simulation_consistency(): openfermion.testing.assert_implements_consistent_protocols( - openfermion.QuarticFermionicSimulationGate()) + openfermion.QuarticFermionicSimulationGate() + ) quartic_fermionic_simulation_simulator_test_cases = [ - (openfermion.QuarticFermionicSimulationGate( - (0, 0, 0)), 1., np.ones(16) / 4., np.ones(16) / 4., 5e-6), - (openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), 0., - np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - 5e-6), - (openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), 0.3, - np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - np.array([ - 1, -1, -1, -np.exp(0.21j), -1, -np.exp(-0.03j), - np.exp(-0.06j), 1, 1, - np.exp(-0.06j), - np.exp(-0.03j), 1, - np.exp(0.21j), 1, 1, 1 - ]) / 4., 5e-6), - (openfermion.QuarticFermionicSimulationGate((1. / 3, 0, 0)), 1., - np.array([0, 0, 0, 0, 0, 0, 1., 0, 0, 1., 0, 0, 0, 0, 0, 0]) / np.sqrt(2), - np.array([0, 0, 0, 0, 0, 0, 1., 0, 0, 1., 0, 0, 0, 0, 0, 0]) / np.sqrt(2), - 5e-6), - (openfermion.QuarticFermionicSimulationGate((0, np.pi / 3, 0)), 1., - np.array([1., 1., 0, 0, 0, 1., 0, 0, 0, 0., -1., 0, 0, 0, 0, 0]) / 2., - np.array([ - 1., 1., 0, 0, 0, -np.exp(4j * np.pi / 3), 0, 0, 0, 0., - -np.exp(1j * np.pi / 3), 0, 0, 0, 0, 0 - ]) / 2., 5e-6), - (openfermion.QuarticFermionicSimulationGate((0, 0, -np.pi / 2)), 1., - np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1., 0, 0, - 0]), np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0]), 5e-6), - (openfermion.QuarticFermionicSimulationGate((0, 0, -0.25 * np.pi)), 1., - np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), - np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1j, 0, 0, 0]) / np.sqrt(2), - 5e-6), - (openfermion.QuarticFermionicSimulationGate( - (-np.pi / 4, np.pi / 6, -np.pi / 2)), 1., - np.array([0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]) / np.sqrt(3), - np.array([ - 0, 0, 0, 1j, 0, -1j / 2., 1 / np.sqrt(2), 0, 0, 1j / np.sqrt(2), - np.sqrt(3) / 2, 0, 0, 0, 0, 0 - ]) / np.sqrt(3), 5e-6), + ( + openfermion.QuarticFermionicSimulationGate((0, 0, 0)), + 1.0, + np.ones(16) / 4.0, + np.ones(16) / 4.0, + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), + 0.0, + np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), + 0.3, + np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + np.array( + [ + 1, + -1, + -1, + -np.exp(0.21j), + -1, + -np.exp(-0.03j), + np.exp(-0.06j), + 1, + 1, + np.exp(-0.06j), + np.exp(-0.03j), + 1, + np.exp(0.21j), + 1, + 1, + 1, + ] + ) + / 4.0, + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((1.0 / 3, 0, 0)), + 1.0, + np.array([0, 0, 0, 0, 0, 0, 1.0, 0, 0, 1.0, 0, 0, 0, 0, 0, 0]) / np.sqrt(2), + np.array([0, 0, 0, 0, 0, 0, 1.0, 0, 0, 1.0, 0, 0, 0, 0, 0, 0]) / np.sqrt(2), + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((0, np.pi / 3, 0)), + 1.0, + np.array([1.0, 1.0, 0, 0, 0, 1.0, 0, 0, 0, 0.0, -1.0, 0, 0, 0, 0, 0]) / 2.0, + np.array( + [ + 1.0, + 1.0, + 0, + 0, + 0, + -np.exp(4j * np.pi / 3), + 0, + 0, + 0, + 0.0, + -np.exp(1j * np.pi / 3), + 0, + 0, + 0, + 0, + 0, + ] + ) + / 2.0, + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((0, 0, -np.pi / 2)), + 1.0, + np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, 0, 0, 0]), + np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((0, 0, -0.25 * np.pi)), + 1.0, + np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1j, 0, 0, 0]) / np.sqrt(2), + 5e-6, + ), + ( + openfermion.QuarticFermionicSimulationGate((-np.pi / 4, np.pi / 6, -np.pi / 2)), + 1.0, + np.array([0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]) / np.sqrt(3), + np.array( + [ + 0, + 0, + 0, + 1j, + 0, + -1j / 2.0, + 1 / np.sqrt(2), + 0, + 0, + 1j / np.sqrt(2), + np.sqrt(3) / 2, + 0, + 0, + 0, + 0, + 0, + ] + ) + / np.sqrt(3), + 5e-6, + ), ] -@pytest.mark.parametrize('gate, exponent, initial_state, correct_state, atol', - quartic_fermionic_simulation_simulator_test_cases) -def test_quartic_fermionic_simulation_on_simulator(gate, exponent, - initial_state, correct_state, - atol): - +@pytest.mark.parametrize( + 'gate, exponent, initial_state, correct_state, atol', + quartic_fermionic_simulation_simulator_test_cases, +) +def test_quartic_fermionic_simulation_on_simulator( + gate, exponent, initial_state, correct_state, atol +): a, b, c, d = cirq.LineQubit.range(4) - circuit = cirq.Circuit(gate(a, b, c, d)**exponent) + circuit = cirq.Circuit(gate(a, b, c, d) ** exponent) result = circuit.final_state_vector(initial_state=initial_state) - cirq.testing.assert_allclose_up_to_global_phase(result, - correct_state, - atol=atol) + cirq.testing.assert_allclose_up_to_global_phase(result, correct_state, atol=atol) def test_quartic_fermionic_simulation_eq(): eq = cirq.testing.EqualsTester() eq.add_equality_group( - openfermion.QuarticFermionicSimulationGate((1.2, 0.4, -0.4), - exponent=0.5), - openfermion.QuarticFermionicSimulationGate((0.3, 0.1, -0.1), - exponent=2), - openfermion.QuarticFermionicSimulationGate((-0.6, -0.2, 0.2), - exponent=-1), + openfermion.QuarticFermionicSimulationGate((1.2, 0.4, -0.4), exponent=0.5), + openfermion.QuarticFermionicSimulationGate((0.3, 0.1, -0.1), exponent=2), + openfermion.QuarticFermionicSimulationGate((-0.6, -0.2, 0.2), exponent=-1), openfermion.QuarticFermionicSimulationGate((0.6, 0.2, 2 * np.pi - 0.2)), ) eq.add_equality_group( - openfermion.QuarticFermionicSimulationGate((-0.6, 0.0, 0.3), - exponent=0.5)) + openfermion.QuarticFermionicSimulationGate((-0.6, 0.0, 0.3), exponent=0.5) + ) - eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate( - (0.1, -0.3, 0.0), exponent=0.0)) - eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate( - (1., -1., 0.5), exponent=0.75)) + eq.make_equality_group( + lambda: openfermion.QuarticFermionicSimulationGate((0.1, -0.3, 0.0), exponent=0.0) + ) + eq.make_equality_group( + lambda: openfermion.QuarticFermionicSimulationGate((1.0, -1.0, 0.5), exponent=0.75) + ) def test_quadratic_fermionic_simulation_gate_text_diagram(): @@ -462,8 +532,10 @@ def test_quadratic_fermionic_simulation_gate_text_diagram(): circuit = cirq.Circuit([gate(a, b), gate(b, c)]) assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__ - assert (super(type(gate), gate)._diagram_exponent( - cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == gate._exponent) + assert ( + super(type(gate), gate)._diagram_exponent(cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) + == gate._exponent + ) expected_text_diagram = """ 0: ───↓↑(1, 1)────────────── @@ -481,9 +553,7 @@ def test_quadratic_fermionic_simulation_gate_text_diagram(): | 2: ---------------a*a--------- """.strip() - cirq.testing.assert_has_diagram(circuit, - expected_text_diagram, - use_unicode_characters=False) + cirq.testing.assert_has_diagram(circuit, expected_text_diagram, use_unicode_characters=False) def test_cubic_fermionic_simulation_gate_text_diagram(): @@ -492,8 +562,10 @@ def test_cubic_fermionic_simulation_gate_text_diagram(): circuit = cirq.Circuit([gate(*qubits[:3]), gate(*qubits[2:5])]) assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__ - assert (super(type(gate), gate)._diagram_exponent( - cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == gate._exponent) + assert ( + super(type(gate), gate)._diagram_exponent(cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) + == gate._exponent + ) expected_text_diagram = """ 0: ───↕↓↑(1, 1, 1)────────────────── @@ -519,27 +591,28 @@ def test_cubic_fermionic_simulation_gate_text_diagram(): | 4: -------------------na*a------------ """.strip() - cirq.testing.assert_has_diagram(circuit, - expected_text_diagram, - use_unicode_characters=False) + cirq.testing.assert_has_diagram(circuit, expected_text_diagram, use_unicode_characters=False) test_weights = [1.0, 0.5, 0.25, 0.1, 0.0, -0.5] -@pytest.mark.parametrize('weights', - itertools.chain( - itertools.product(test_weights, repeat=3), - np.random.rand(10, 3))) +@pytest.mark.parametrize( + 'weights', itertools.chain(itertools.product(test_weights, repeat=3), np.random.rand(10, 3)) +) def test_quartic_fermionic_simulation_decompose(weights): cirq.testing.assert_decompose_is_consistent_with_unitary( - openfermion.QuarticFermionicSimulationGate(weights)) + openfermion.QuarticFermionicSimulationGate(weights) + ) @pytest.mark.parametrize( 'weights,exponent', - [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), - np.random.uniform(-5, 5)) for _ in range(5)]) + [ + (np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), np.random.uniform(-5, 5)) + for _ in range(5) + ], +) def test_quartic_fermionic_simulation_unitary(weights, exponent): generator = np.zeros((1 << 4,) * 2, dtype=np.complex128) @@ -554,8 +627,7 @@ def test_quartic_fermionic_simulation_unitary(weights, exponent): generator[3, 12] = weights[2].conjugate() expected_unitary = la.expm(-1j * exponent * generator) - gate = openfermion.QuarticFermionicSimulationGate(weights, - exponent=exponent) + gate = openfermion.QuarticFermionicSimulationGate(weights, exponent=exponent) actual_unitary = cirq.unitary(gate) assert np.allclose(expected_unitary, actual_unitary) @@ -568,8 +640,10 @@ def test_quartic_fermionic_simulation_gate_text_diagram(): assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__ for G in (gate, gate._with_exponent('e')): - assert (super(type(G), G)._diagram_exponent( - cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == G._exponent) + assert ( + super(type(G), G)._diagram_exponent(cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) + == G._exponent + ) expected_text_diagram = """ 0: ───⇊⇈(1, 1, 1)───────────────── @@ -599,16 +673,16 @@ def test_quartic_fermionic_simulation_gate_text_diagram(): | 5: ---------------------a*a*aa------------ """.strip() - cirq.testing.assert_has_diagram(circuit, - expected_text_diagram, - use_unicode_characters=False) + cirq.testing.assert_has_diagram(circuit, expected_text_diagram, use_unicode_characters=False) @pytest.mark.parametrize( 'weights,exponent', - [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), - np.random.uniform(-5, 5)) for _ in range(5)]) + [ + (np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3), np.random.uniform(-5, 5)) + for _ in range(5) + ], +) def test_quartic_fermionic_simulation_apply_unitary(weights, exponent): - gate = openfermion.QuarticFermionicSimulationGate(weights, - exponent=exponent) + gate = openfermion.QuarticFermionicSimulationGate(weights, exponent=exponent) cirq.testing.assert_has_consistent_apply_unitary(gate, atol=5e-6) diff --git a/src/openfermion/circuits/gates/four_qubit_gates.py b/src/openfermion/circuits/gates/four_qubit_gates.py index 2eb71ba34..59baddabe 100644 --- a/src/openfermion/circuits/gates/four_qubit_gates.py +++ b/src/openfermion/circuits/gates/four_qubit_gates.py @@ -24,12 +24,13 @@ class DoubleExcitationGate(cirq.EigenGate): """Evolve under ``-|0011⟩⟨1100|`` + h.c. for some time.""" def __init__( - self, - *, # Forces keyword args. - exponent: Optional[Union[sympy.Symbol, float]] = None, - rads: Optional[float] = None, - degs: Optional[float] = None, - duration: Optional[float] = None) -> None: + self, + *, # Forces keyword args. + exponent: Optional[Union[sympy.Symbol, float]] = None, + rads: Optional[float] = None, + degs: Optional[float] = None, + duration: Optional[float] = None, + ) -> None: """Initialize the gate. At most one of exponent, rads, degs, or duration may be specified. @@ -44,17 +45,15 @@ def __init__( duration: The exponent as a duration of time. """ - if len([1 for e in [exponent, rads, degs, duration] if e is not None - ]) > 1: - raise ValueError('Redundant exponent specification. ' - 'Use ONE of exponent, rads, degs, or duration.') + if len([1 for e in [exponent, rads, degs, duration] if e is not None]) > 1: + raise ValueError( + 'Redundant exponent specification. ' 'Use ONE of exponent, rads, degs, or duration.' + ) if duration is not None: exponent = 2 * duration / np.pi else: - exponent = cirq.chosen_angle_to_half_turns(half_turns=exponent, - rads=rads, - degs=degs) + exponent = cirq.chosen_angle_to_half_turns(half_turns=exponent, rads=rads, degs=degs) super().__init__(exponent=exponent) @@ -70,49 +69,44 @@ def _eigen_components(self): plus_one_component[3, 3] = plus_one_component[12, 12] = 0.5 plus_one_component[3, 12] = plus_one_component[12, 3] = 0.5 - return [(0, np.diag([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1])), - (-1, minus_one_component), (1, plus_one_component)] + return [ + (0, np.diag([1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1])), + (-1, minus_one_component), + (1, plus_one_component), + ] - def _apply_unitary_(self, - args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: + def _apply_unitary_(self, args: cirq.ApplyUnitaryArgs) -> Optional[np.ndarray]: if cirq.is_parameterized(self): return None inner_matrix = cirq.unitary(cirq.rx(-2 * np.pi * self.exponent)) a = args.subspace_index(0b0011) b = args.subspace_index(0b1100) - return cirq.apply_matrix_to_slices(args.target_tensor, - inner_matrix, - slices=[a, b], - out=args.available_buffer) + return cirq.apply_matrix_to_slices( + args.target_tensor, inner_matrix, slices=[a, b], out=args.available_buffer + ) - def _with_exponent(self, exponent: Union[sympy.Symbol, float] - ) -> 'DoubleExcitationGate': + def _with_exponent(self, exponent: Union[sympy.Symbol, float]) -> 'DoubleExcitationGate': return DoubleExcitationGate(exponent=exponent) def _decompose_(self, qubits): p, q, r, s = qubits - rq_phase_block = [cirq.Z(q)**0.125, cirq.CNOT(r, q), cirq.Z(q)**-0.125] + rq_phase_block = [cirq.Z(q) ** 0.125, cirq.CNOT(r, q), cirq.Z(q) ** -0.125] - srq_parity_transform = [ - cirq.CNOT(s, r), cirq.CNOT(r, q), - cirq.CNOT(s, r) - ] + srq_parity_transform = [cirq.CNOT(s, r), cirq.CNOT(r, q), cirq.CNOT(s, r)] - phase_parity_block = [[ - rq_phase_block, srq_parity_transform, rq_phase_block - ]] + phase_parity_block = [[rq_phase_block, srq_parity_transform, rq_phase_block]] yield cirq.CNOT(r, s) yield cirq.CNOT(q, p) yield cirq.CNOT(q, r) - yield cirq.X(q)**-self.exponent + yield cirq.X(q) ** -self.exponent yield phase_parity_block yield cirq.CNOT(p, q) yield cirq.X(q) yield phase_parity_block - yield cirq.X(q)**self.exponent + yield cirq.X(q) ** self.exponent yield phase_parity_block yield cirq.CNOT(p, q) yield cirq.X(q) @@ -122,21 +116,20 @@ def _decompose_(self, qubits): yield cirq.CNOT(q, r) yield cirq.CNOT(r, s) - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs - ) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: if args.use_unicode_characters: wire_symbols = ('⇅', '⇅', '⇵', '⇵') else: # pylint: disable=anomalous-backslash-in-string wire_symbols = (r'/\ \/', r'/\ \/', '\/ /\\', '\/ /\\') - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols, - exponent=self._diagram_exponent(args)) + return cirq.CircuitDiagramInfo( + wire_symbols=wire_symbols, exponent=self._diagram_exponent(args) + ) def __repr__(self): if self.exponent == 1: return 'openfermion.DoubleExcitation' - return '(openfermion.DoubleExcitation**{})'.format( - proper_repr(self.exponent)) + return '(openfermion.DoubleExcitation**{})'.format(proper_repr(self.exponent)) DoubleExcitation = DoubleExcitationGate() diff --git a/src/openfermion/circuits/gates/four_qubit_gates_test.py b/src/openfermion/circuits/gates/four_qubit_gates_test.py index 79d84f553..ce771a716 100644 --- a/src/openfermion/circuits/gates/four_qubit_gates_test.py +++ b/src/openfermion/circuits/gates/four_qubit_gates_test.py @@ -20,8 +20,7 @@ def test_double_excitation_init_with_multiple_args_fails(): with pytest.raises(ValueError): - _ = openfermion.DoubleExcitationGate(exponent=1.0, - duration=numpy.pi / 2) + _ = openfermion.DoubleExcitationGate(exponent=1.0, duration=numpy.pi / 2) def test_double_excitation_eq(): @@ -32,87 +31,173 @@ def test_double_excitation_eq(): openfermion.DoubleExcitationGate(exponent=-0.5), openfermion.DoubleExcitationGate(rads=-0.5 * numpy.pi), openfermion.DoubleExcitationGate(degs=-90), - openfermion.DoubleExcitationGate(duration=-0.5 * numpy.pi / 2)) + openfermion.DoubleExcitationGate(duration=-0.5 * numpy.pi / 2), + ) eq.add_equality_group( openfermion.DoubleExcitationGate(exponent=0.5), openfermion.DoubleExcitationGate(exponent=-1.5), openfermion.DoubleExcitationGate(rads=0.5 * numpy.pi), openfermion.DoubleExcitationGate(degs=90), - openfermion.DoubleExcitationGate(duration=-1.5 * numpy.pi / 2)) + openfermion.DoubleExcitationGate(duration=-1.5 * numpy.pi / 2), + ) - eq.make_equality_group(lambda: openfermion.DoubleExcitationGate(exponent=0.0 - )) - eq.make_equality_group(lambda: openfermion.DoubleExcitationGate(exponent= - 0.75)) + eq.make_equality_group(lambda: openfermion.DoubleExcitationGate(exponent=0.0)) + eq.make_equality_group(lambda: openfermion.DoubleExcitationGate(exponent=0.75)) def test_double_excitation_consistency(): - openfermion.testing.assert_implements_consistent_protocols( - openfermion.DoubleExcitation) + openfermion.testing.assert_implements_consistent_protocols(openfermion.DoubleExcitation) double_excitation_simulator_test_cases = [ - (openfermion.DoubleExcitation, 1.0, - numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - numpy.array([1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1]) / 4., - 5e-6), - (openfermion.DoubleExcitation, -1.0, - numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - numpy.array([1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1]) / 4., - 5e-6), - (openfermion.DoubleExcitation, 0.5, - numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]) / - numpy.sqrt(8), - numpy.array([1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1j, 0, 0, 0]) / - numpy.sqrt(8), 5e-6), - (openfermion.DoubleExcitation, -0.5, - numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - numpy.array([1, -1, -1, -1j, -1, -1, 1, 1, 1, 1, 1, 1, 1j, 1, 1, 1]) / 4., - 5e-6), - (openfermion.DoubleExcitation, -1. / 7, - numpy.array( - [1, 1j, -1j, -1, 1, 1j, -1j, -1, 1, 1j, -1j, -1, 1, 1j, -1j, -1]) / 4., - numpy.array([ - 1, 1j, -1j, -numpy.cos(numpy.pi / 7) - 1j * numpy.sin(numpy.pi / 7), 1, - 1j, -1j, -1, 1, 1j, -1j, -1, - numpy.cos(numpy.pi / 7) + 1j * numpy.sin(numpy.pi / 7), 1j, -1j, -1 - ]) / 4., 5e-6), - (openfermion.DoubleExcitation, 7. / 3, - numpy.array([ - 0, 0, 0, 2, (1 + 1j) / numpy.sqrt(2), (1 - 1j) / numpy.sqrt(2), - -(1 + 1j) / numpy.sqrt(2), -1, 1, 1j, -1j, -1, 1, 1j, -1j, -1 - ]) / 4., - numpy.array([ - 0, 0, 0, 1 + 1j * numpy.sqrt(3) / 2, (1 + 1j) / numpy.sqrt(2), - (1 - 1j) / numpy.sqrt(2), -(1 + 1j) / numpy.sqrt(2), -1, 1, 1j, -1j, - -1, 0.5 + 1j * numpy.sqrt(3), 1j, -1j, -1 - ]) / 4., 5e-6), - (openfermion.DoubleExcitation, 0, - numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4., - 5e-6), - (openfermion.DoubleExcitation, 0.25, - numpy.array([1, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 1]) / - numpy.sqrt(15), - numpy.array([ - 1, 0, 0, +3j / numpy.sqrt(2) - numpy.sqrt(2), 0, 0, 0, 0, 0, 0, 0, 0, - 3 / numpy.sqrt(2) - 1j * numpy.sqrt(2), 0, 0, 1 - ]) / numpy.sqrt(15), 5e-6) + ( + openfermion.DoubleExcitation, + 1.0, + numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + numpy.array([1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1]) / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + -1.0, + numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + numpy.array([1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1]) / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + 0.5, + numpy.array([1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]) / numpy.sqrt(8), + numpy.array([1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1j, 0, 0, 0]) / numpy.sqrt(8), + 5e-6, + ), + ( + openfermion.DoubleExcitation, + -0.5, + numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + numpy.array([1, -1, -1, -1j, -1, -1, 1, 1, 1, 1, 1, 1, 1j, 1, 1, 1]) / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + -1.0 / 7, + numpy.array([1, 1j, -1j, -1, 1, 1j, -1j, -1, 1, 1j, -1j, -1, 1, 1j, -1j, -1]) / 4.0, + numpy.array( + [ + 1, + 1j, + -1j, + -numpy.cos(numpy.pi / 7) - 1j * numpy.sin(numpy.pi / 7), + 1, + 1j, + -1j, + -1, + 1, + 1j, + -1j, + -1, + numpy.cos(numpy.pi / 7) + 1j * numpy.sin(numpy.pi / 7), + 1j, + -1j, + -1, + ] + ) + / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + 7.0 / 3, + numpy.array( + [ + 0, + 0, + 0, + 2, + (1 + 1j) / numpy.sqrt(2), + (1 - 1j) / numpy.sqrt(2), + -(1 + 1j) / numpy.sqrt(2), + -1, + 1, + 1j, + -1j, + -1, + 1, + 1j, + -1j, + -1, + ] + ) + / 4.0, + numpy.array( + [ + 0, + 0, + 0, + 1 + 1j * numpy.sqrt(3) / 2, + (1 + 1j) / numpy.sqrt(2), + (1 - 1j) / numpy.sqrt(2), + -(1 + 1j) / numpy.sqrt(2), + -1, + 1, + 1j, + -1j, + -1, + 0.5 + 1j * numpy.sqrt(3), + 1j, + -1j, + -1, + ] + ) + / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + 0, + numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + numpy.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.0, + 5e-6, + ), + ( + openfermion.DoubleExcitation, + 0.25, + numpy.array([1, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 1]) / numpy.sqrt(15), + numpy.array( + [ + 1, + 0, + 0, + +3j / numpy.sqrt(2) - numpy.sqrt(2), + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3 / numpy.sqrt(2) - 1j * numpy.sqrt(2), + 0, + 0, + 1, + ] + ) + / numpy.sqrt(15), + 5e-6, + ), ] -@pytest.mark.parametrize('gate, exponent, initial_state, correct_state, atol', - double_excitation_simulator_test_cases) -def test_four_qubit_rotation_gates_on_simulator(gate, exponent, initial_state, - correct_state, atol): - +@pytest.mark.parametrize( + 'gate, exponent, initial_state, correct_state, atol', double_excitation_simulator_test_cases +) +def test_four_qubit_rotation_gates_on_simulator(gate, exponent, initial_state, correct_state, atol): a, b, c, d = cirq.LineQubit.range(4) - circuit = cirq.Circuit(gate(a, b, c, d)**exponent) + circuit = cirq.Circuit(gate(a, b, c, d) ** exponent) result = circuit.final_state_vector(initial_state=initial_state) - cirq.testing.assert_allclose_up_to_global_phase(result, - correct_state, - atol=atol) + cirq.testing.assert_allclose_up_to_global_phase(result, correct_state, atol=atol) def test_double_excitation_gate_text_diagrams(): @@ -123,7 +208,8 @@ def test_double_excitation_gate_text_diagrams(): circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d)) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───⇅─── │ b: ───⇅─── @@ -131,11 +217,13 @@ def test_double_excitation_gate_text_diagrams(): c: ───⇵─── │ d: ───⇵─── -""") +""", + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d)**-0.5) + circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d) ** -0.5) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───⇅──────── │ b: ───⇅──────── @@ -143,11 +231,13 @@ def test_double_excitation_gate_text_diagrams(): c: ───⇵──────── │ d: ───⇵^-0.5─── -""") +""", + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(a, c, b, d)**0.2) + circuit = cirq.Circuit(openfermion.DoubleExcitation(a, c, b, d) ** 0.2) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───⇅─────── │ b: ───⇵─────── @@ -155,11 +245,13 @@ def test_double_excitation_gate_text_diagrams(): c: ───⇅─────── │ d: ───⇵^0.2─── -""") +""", + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c)**0.7) + circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c) ** 0.7) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───⇵─────── │ b: ───⇅─────── @@ -167,11 +259,13 @@ def test_double_excitation_gate_text_diagrams(): c: ───⇵─────── │ d: ───⇅^0.7─── -""") +""", + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c)**2.3) + circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c) ** 2.3) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ a: ───⇵─────── │ b: ───⇅─────── @@ -179,7 +273,8 @@ def test_double_excitation_gate_text_diagrams(): c: ───⇵─────── │ d: ───⇅^0.3─── -""") +""", + ) def test_double_excitation_gate_text_diagrams_no_unicode(): @@ -190,8 +285,9 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d)) # pylint: disable=anomalous-backslash-in-string - cirq.testing.assert_has_diagram(circuit, - r""" + cirq.testing.assert_has_diagram( + circuit, + r""" a: ---/\ \/--- | b: ---/\ \/--- @@ -200,11 +296,13 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): | d: ---\/ /\--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d)**-0.5) - cirq.testing.assert_has_diagram(circuit, - r""" + circuit = cirq.Circuit(openfermion.DoubleExcitation(a, b, c, d) ** -0.5) + cirq.testing.assert_has_diagram( + circuit, + r""" a: ---/\ \/-------- | b: ---/\ \/-------- @@ -213,11 +311,13 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): | d: ---\/ /\^-0.5--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(a, c, b, d)**0.2) - cirq.testing.assert_has_diagram(circuit, - r""" + circuit = cirq.Circuit(openfermion.DoubleExcitation(a, c, b, d) ** 0.2) + cirq.testing.assert_has_diagram( + circuit, + r""" a: ---/\ \/------- | b: ---\/ /\------- @@ -226,11 +326,13 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): | d: ---\/ /\^0.2--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c)**0.7) - cirq.testing.assert_has_diagram(circuit, - r""" + circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c) ** 0.7) + cirq.testing.assert_has_diagram( + circuit, + r""" a: ---\/ /\------- | b: ---/\ \/------- @@ -239,11 +341,13 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): | d: ---/\ \/^0.7--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) - circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c)**2.3) - cirq.testing.assert_has_diagram(circuit, - r""" + circuit = cirq.Circuit(openfermion.DoubleExcitation(d, b, a, c) ** 2.3) + cirq.testing.assert_has_diagram( + circuit, + r""" a: ---\/ /\------- | b: ---/\ \/------- @@ -252,7 +356,8 @@ def test_double_excitation_gate_text_diagrams_no_unicode(): | d: ---/\ \/^0.3--- """, - use_unicode_characters=False) + use_unicode_characters=False, + ) @pytest.mark.parametrize('exponent', [1.0, 0.5, 0.25, 0.1, 0.0, -0.5]) @@ -263,10 +368,7 @@ def test_double_excitation_matches_fermionic_evolution(exponent): op += openfermion.hermitian_conjugated(op) matrix_op = openfermion.get_sparse_operator(op) - time_evol_op = scipy.sparse.linalg.expm(-1j * matrix_op * exponent * - numpy.pi) + time_evol_op = scipy.sparse.linalg.expm(-1j * matrix_op * exponent * numpy.pi) time_evol_op = time_evol_op.todense() - cirq.testing.assert_allclose_up_to_global_phase(cirq.unitary(gate), - time_evol_op, - atol=1e-7) + cirq.testing.assert_allclose_up_to_global_phase(cirq.unitary(gate), time_evol_op, atol=1e-7) diff --git a/src/openfermion/circuits/gates/three_qubit_gates.py b/src/openfermion/circuits/gates/three_qubit_gates.py index 3fd8cb131..0269222d7 100644 --- a/src/openfermion/circuits/gates/three_qubit_gates.py +++ b/src/openfermion/circuits/gates/three_qubit_gates.py @@ -16,7 +16,7 @@ def rot111(rads: float) -> cirq.CCZPowGate: """Phases the |111> state of three qubits by e^{i rads}.""" - return cirq.CCZ**(rads / np.pi) + return cirq.CCZ ** (rads / np.pi) def CRxxyy(rads: float) -> cirq.ControlledGate: @@ -26,5 +26,4 @@ def CRxxyy(rads: float) -> cirq.ControlledGate: def CRyxxy(rads: float) -> cirq.ControlledGate: """Controlled version of openfermion.Ryxxy""" - return cirq.ControlledGate( - cirq.PhasedISwapPowGate(exponent=2 * rads / np.pi)) + return cirq.ControlledGate(cirq.PhasedISwapPowGate(exponent=2 * rads / np.pi)) diff --git a/src/openfermion/circuits/gates/three_qubit_gates_test.py b/src/openfermion/circuits/gates/three_qubit_gates_test.py index 4d0169c48..6433513d3 100644 --- a/src/openfermion/circuits/gates/three_qubit_gates_test.py +++ b/src/openfermion/circuits/gates/three_qubit_gates_test.py @@ -16,21 +16,23 @@ import openfermion -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_crxxyy_unitary(rads): np.testing.assert_allclose( cirq.unitary(openfermion.CRxxyy(rads)), cirq.unitary(cirq.ControlledGate(openfermion.Rxxyy(rads))), - atol=1e-8) + atol=1e-8, + ) -@pytest.mark.parametrize('rads', [ - 2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi -]) +@pytest.mark.parametrize( + 'rads', [2 * np.pi, np.pi, 0.5 * np.pi, 0.25 * np.pi, 0.1 * np.pi, 0.0, -0.5 * np.pi] +) def test_cryxxy_unitary(rads): np.testing.assert_allclose( cirq.unitary(openfermion.CRyxxy(rads)), cirq.unitary(cirq.ControlledGate(openfermion.Ryxxy(rads))), - atol=1e-8) + atol=1e-8, + ) diff --git a/src/openfermion/circuits/lcu_util.py b/src/openfermion/circuits/lcu_util.py index 83bf3c879..81d4ae3db 100644 --- a/src/openfermion/circuits/lcu_util.py +++ b/src/openfermion/circuits/lcu_util.py @@ -24,19 +24,19 @@ def lambda_norm(diagonal_operator): Returns: lambda_norm: A float giving the lambda norm. """ - lambda_norm = 0. + lambda_norm = 0.0 n_qubits = diagonal_operator.one_body.shape[0] z_vector = numpy.zeros(n_qubits, float) for p in range(n_qubits): for q in range(n_qubits): if p == q: - z_vector[p] -= diagonal_operator.one_body[p, p] / 2. - z_vector[p] -= diagonal_operator.two_body[p, p] / 2. + z_vector[p] -= diagonal_operator.one_body[p, p] / 2.0 + z_vector[p] -= diagonal_operator.two_body[p, p] / 2.0 else: - lambda_norm += abs(diagonal_operator.one_body[p, q]) / 2. - lambda_norm += abs(diagonal_operator.two_body[p, q]) / 4. - z_vector[p] -= diagonal_operator.two_body[p, q] / 4. - z_vector[q] -= diagonal_operator.two_body[p, q] / 4. + lambda_norm += abs(diagonal_operator.one_body[p, q]) / 2.0 + lambda_norm += abs(diagonal_operator.two_body[p, q]) / 4.0 + z_vector[p] -= diagonal_operator.two_body[p, q] / 4.0 + z_vector[q] -= diagonal_operator.two_body[p, q] / 4.0 lambda_norm += numpy.sum(numpy.absolute(z_vector)) return lambda_norm @@ -87,9 +87,7 @@ def _discretize_probability_distribution(unnormalized_probabilities, epsilon): cumulative = list(_partial_sums(unnormalized_probabilities)) total = cumulative[-1] - discretized_cumulative = [ - int(math.floor(c / total * bin_count + 0.5)) for c in cumulative - ] + discretized_cumulative = [int(math.floor(c / total * bin_count + 0.5)) for c in cumulative] discretized = list(_differences(discretized_cumulative)) return discretized, bin_count, sub_bit_precision @@ -168,8 +166,7 @@ def _preprocess_for_efficient_roulette_selection(discretized_probabilities): return alternates, keep_weights -def preprocess_lcu_coefficients_for_reversible_sampling(lcu_coefficients, - epsilon): +def preprocess_lcu_coefficients_for_reversible_sampling(lcu_coefficients, epsilon): """Prepares data used to perform efficient reversible roulette selection. Treats the coefficients of unitaries in the linear combination of @@ -204,8 +201,8 @@ def preprocess_lcu_coefficients_for_reversible_sampling(lcu_coefficients, a probability. The actual denominator is 2**sub_bit_precision. """ numers, denom, sub_bit_precision = _discretize_probability_distribution( - lcu_coefficients, epsilon) + lcu_coefficients, epsilon + ) assert denom == 2**sub_bit_precision * len(numers) - alternates, keep_numers = _preprocess_for_efficient_roulette_selection( - numers) + alternates, keep_numers = _preprocess_for_efficient_roulette_selection(numers) return alternates, keep_numers, sub_bit_precision diff --git a/src/openfermion/circuits/lcu_util_test.py b/src/openfermion/circuits/lcu_util_test.py index 077e59c3e..9a79aaa7a 100644 --- a/src/openfermion/circuits/lcu_util_test.py +++ b/src/openfermion/circuits/lcu_util_test.py @@ -18,15 +18,15 @@ from openfermion.ops.representations import DiagonalCoulombHamiltonian from openfermion.transforms import jordan_wigner from openfermion.circuits.lcu_util import ( - lambda_norm, _discretize_probability_distribution, + lambda_norm, + _discretize_probability_distribution, _preprocess_for_efficient_roulette_selection, - preprocess_lcu_coefficients_for_reversible_sampling) + preprocess_lcu_coefficients_for_reversible_sampling, +) class LambdaNormTest(unittest.TestCase): - def test_random(self): - # Create random DiagonalCoulombHamiltonian. n_qubits = 8 random.seed(n_qubits) @@ -38,8 +38,7 @@ def test_random(self): two_body[p, q] = random.random() one_body += one_body.T two_body += two_body.T - diagonal_operator = DiagonalCoulombHamiltonian(one_body, two_body, - random.random()) + diagonal_operator = DiagonalCoulombHamiltonian(one_body, two_body, random.random()) # Compute the lambda norm using expensive (reliable) method. qubit_operator = jordan_wigner(diagonal_operator) @@ -50,7 +49,7 @@ def test_random(self): test_norm = lambda_norm(diagonal_operator) # Third norm. - third_norm = 0. + third_norm = 0.0 for _, coefficient in qubit_operator.terms.items(): third_norm += abs(coefficient) @@ -60,18 +59,16 @@ def test_random(self): class DiscretizeDistributionTest(unittest.TestCase): - def assertGetDiscretizedDistribution(self, probabilities, epsilon): total_probability = sum(probabilities) - numers, denom, mu = _discretize_probability_distribution( - probabilities, epsilon) + numers, denom, mu = _discretize_probability_distribution(probabilities, epsilon) self.assertEqual(sum(numers), denom) self.assertEqual(len(numers), len(probabilities)) self.assertEqual(len(probabilities) * 2**mu, denom) for i in range(len(numers)): - self.assertAlmostEqual(numers[i] / denom, - probabilities[i] / total_probability, - delta=epsilon) + self.assertAlmostEqual( + numers[i] / denom, probabilities[i] / total_probability, delta=epsilon + ) return numers, denom def test_fuzz(self): @@ -79,46 +76,42 @@ def test_fuzz(self): for _ in range(100): n = random.randint(1, 50) weights = [random.random() for _ in range(n)] - self.assertGetDiscretizedDistribution(weights, - 2**-random.randint(1, 20)) + self.assertGetDiscretizedDistribution(weights, 2 ** -random.randint(1, 20)) def test_known_discretizations(self): - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.25), - ([4], 4)) + self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.25), ([4], 4)) - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.125), - ([8], 8)) + self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.125), ([8], 8)) self.assertEqual( - self.assertGetDiscretizedDistribution([0.1, 0.1, 0.1], 0.25), - ([2, 2, 2], 6)) + self.assertGetDiscretizedDistribution([0.1, 0.1, 0.1], 0.25), ([2, 2, 2], 6) + ) self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.25), - ([2, 2, 2], 6)) + self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.25), ([2, 2, 2], 6) + ) self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.1), - ([4, 4, 4], 12)) + self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.1), ([4, 4, 4], 12) + ) self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.05), - ([7, 9, 8], 24)) + self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.05), ([7, 9, 8], 24) + ) self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.01), - ([58, 70, 64], 192)) + self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.01), ([58, 70, 64], 192) + ) self.assertEqual( self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.00335), - ([115, 141, 128], 384)) + ([115, 141, 128], 384), + ) class PreprocessForEfficientRouletteSelectionTest(unittest.TestCase): - def assertPreprocess(self, weights): - alternates, keep_chances = ( - _preprocess_for_efficient_roulette_selection(weights)) + alternates, keep_chances = _preprocess_for_efficient_roulette_selection(weights) self.assertEqual(len(alternates), len(keep_chances)) @@ -148,36 +141,28 @@ def test_validation(self): def test_already_uniform(self): self.assertEqual(self.assertPreprocess(weights=[1]), ([0], [0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1]), - ([0, 1], [0, 0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1, 1]), - ([0, 1, 2], [0, 0, 0])) - self.assertEqual(self.assertPreprocess(weights=[2, 2, 2]), - ([0, 1, 2], [0, 0, 0])) + self.assertEqual(self.assertPreprocess(weights=[1, 1]), ([0, 1], [0, 0])) + self.assertEqual(self.assertPreprocess(weights=[1, 1, 1]), ([0, 1, 2], [0, 0, 0])) + self.assertEqual(self.assertPreprocess(weights=[2, 2, 2]), ([0, 1, 2], [0, 0, 0])) def test_donation(self): # v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[1, 2, 3]), - ([2, 1, 2], [1, 0, 0])) + self.assertEqual(self.assertPreprocess(weights=[1, 2, 3]), ([2, 1, 2], [1, 0, 0])) # v0 donates 1 to v1. - self.assertEqual(self.assertPreprocess(weights=[3, 1, 2]), - ([0, 0, 2], [0, 1, 0])) + self.assertEqual(self.assertPreprocess(weights=[3, 1, 2]), ([0, 0, 2], [0, 1, 0])) # v0 donates 1 to v1, then 2 to v2. - self.assertEqual(self.assertPreprocess(weights=[5, 1, 0]), - ([0, 0, 0], [0, 1, 0])) + self.assertEqual(self.assertPreprocess(weights=[5, 1, 0]), ([0, 0, 0], [0, 1, 0])) def test_over_donation(self): # v0 donates 2 to v1, leaving v0 needy, then v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[3, 0, 3]), - ([2, 0, 2], [1, 0, 0])) + self.assertEqual(self.assertPreprocess(weights=[3, 0, 3]), ([2, 0, 2], [1, 0, 0])) class PreprocessLCUCoefficientsForReversibleSamplingTest(unittest.TestCase): - def assertPreprocess(self, lcu_coefs, epsilon): - alternates, keep_numers, mu = ( - preprocess_lcu_coefficients_for_reversible_sampling( - lcu_coefs, epsilon)) + alternates, keep_numers, mu = preprocess_lcu_coefficients_for_reversible_sampling( + lcu_coefs, epsilon + ) n = len(lcu_coefs) keep_denom = 2**mu @@ -192,9 +177,7 @@ def assertPreprocess(self, lcu_coefs, epsilon): total = sum(lcu_coefs) for i in range(n): - self.assertAlmostEqual(out_distribution[i], - lcu_coefs[i] / total, - delta=epsilon) + self.assertAlmostEqual(out_distribution[i], lcu_coefs[i] / total, delta=epsilon) return alternates, keep_numers, keep_denom @@ -204,11 +187,11 @@ def test_fuzz(self): n = random.randint(1, 50) weights = [random.randint(0, 100) for _ in range(n)] weights[-1] += n - sum(weights) % n # Ensure multiple of length. - self.assertPreprocess(weights, 2**-random.randint(1, 20)) + self.assertPreprocess(weights, 2 ** -random.randint(1, 20)) def test_known(self): - self.assertEqual(self.assertPreprocess([1, 2], epsilon=0.01), - ([1, 1], [43, 0], 64)) + self.assertEqual(self.assertPreprocess([1, 2], epsilon=0.01), ([1, 1], [43, 0], 64)) - self.assertEqual(self.assertPreprocess([1, 2, 3], epsilon=0.01), - ([2, 1, 2], [32, 0, 0], 64)) + self.assertEqual( + self.assertPreprocess([1, 2, 3], epsilon=0.01), ([2, 1, 2], [32, 0, 0], 64) + ) diff --git a/src/openfermion/circuits/low_rank.py b/src/openfermion/circuits/low_rank.py index b88c2eb80..330c4556d 100644 --- a/src/openfermion/circuits/low_rank.py +++ b/src/openfermion/circuits/low_rank.py @@ -49,8 +49,7 @@ def get_chemist_two_body_coefficients(two_body_coefficients, spin_basis=True): """ # Initialize. n_orbitals = two_body_coefficients.shape[0] - chemist_two_body_coefficients = numpy.transpose(two_body_coefficients, - [0, 3, 1, 2]) + chemist_two_body_coefficients = numpy.transpose(two_body_coefficients, [0, 3, 1, 2]) # If the specification was in spin-orbitals, chop down to spatial orbitals # assuming a spin-symmetric interaction. @@ -58,25 +57,26 @@ def get_chemist_two_body_coefficients(two_body_coefficients, spin_basis=True): n_orbitals = n_orbitals // 2 alpha_indices = list(range(0, n_orbitals * 2, 2)) beta_indices = list(range(1, n_orbitals * 2, 2)) - chemist_two_body_coefficients = chemist_two_body_coefficients[numpy.ix_( - alpha_indices, alpha_indices, beta_indices, beta_indices)] + chemist_two_body_coefficients = chemist_two_body_coefficients[ + numpy.ix_(alpha_indices, alpha_indices, beta_indices, beta_indices) + ] # Determine a one body correction in the spin basis from spatial basis. one_body_correction = numpy.zeros((2 * n_orbitals, 2 * n_orbitals), complex) for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): for sigma, tau in itertools.product(range(2), repeat=2): if (q == r) and (sigma == tau): - one_body_correction[2 * p + sigma, 2 * s + tau] -= ( - chemist_two_body_coefficients[p, q, r, s]) + one_body_correction[2 * p + sigma, 2 * s + tau] -= chemist_two_body_coefficients[ + p, q, r, s + ] # Return. return one_body_correction, chemist_two_body_coefficients -def low_rank_two_body_decomposition(two_body_coefficients, - truncation_threshold=1e-8, - final_rank=None, - spin_basis=True): +def low_rank_two_body_decomposition( + two_body_coefficients, truncation_threshold=1e-8, final_rank=None, spin_basis=True +): r"""Convert two-body operator into sum of squared one-body operators. As in arXiv:1808.02625, this function decomposes @@ -109,16 +109,15 @@ def low_rank_two_body_decomposition(two_body_coefficients, TypeError: Invalid two-body coefficient tensor specification. """ # Initialize N^2 by N^2 interaction array. - one_body_correction, chemist_two_body_coefficients = ( - get_chemist_two_body_coefficients(two_body_coefficients, spin_basis)) + one_body_correction, chemist_two_body_coefficients = get_chemist_two_body_coefficients( + two_body_coefficients, spin_basis + ) n_orbitals = chemist_two_body_coefficients.shape[0] full_rank = n_orbitals**2 - interaction_array = numpy.reshape(chemist_two_body_coefficients, - (full_rank, full_rank)) + interaction_array = numpy.reshape(chemist_two_body_coefficients, (full_rank, full_rank)) # Make sure interaction array is symmetric and real. - asymmetry = numpy.sum( - numpy.absolute(interaction_array - interaction_array.transpose())) + asymmetry = numpy.sum(numpy.absolute(interaction_array - interaction_array.transpose())) imaginary_norm = numpy.sum(numpy.absolute(interaction_array.imag)) if asymmetry > EQ_TOLERANCE or imaginary_norm > EQ_TOLERANCE: raise TypeError('Invalid two-body coefficient tensor specification.') @@ -128,16 +127,14 @@ def low_rank_two_body_decomposition(two_body_coefficients, # Get one-body squares and compute weights. term_weights = numpy.zeros(full_rank) - one_body_squares = numpy.zeros((full_rank, 2 * n_orbitals, 2 * n_orbitals), - complex) + one_body_squares = numpy.zeros((full_rank, 2 * n_orbitals, 2 * n_orbitals), complex) # Reshape and add spin back in. for l in range(full_rank): one_body_squares[l] = numpy.kron( - numpy.reshape(eigenvectors[:, l], (n_orbitals, n_orbitals)), - numpy.eye(2)) - term_weights[l] = abs(eigenvalues[l]) * numpy.sum( - numpy.absolute(one_body_squares[l]))**2 + numpy.reshape(eigenvectors[:, l], (n_orbitals, n_orbitals)), numpy.eye(2) + ) + term_weights[l] = abs(eigenvalues[l]) * numpy.sum(numpy.absolute(one_body_squares[l])) ** 2 # Sort by weight. indices = numpy.argsort(term_weights)[::-1] @@ -155,8 +152,12 @@ def low_rank_two_body_decomposition(two_body_coefficients, else: max_rank = final_rank truncation_value = truncation_errors[max_rank - 1] - return (eigenvalues[:max_rank], one_body_squares[:max_rank], - one_body_correction, truncation_value) + return ( + eigenvalues[:max_rank], + one_body_squares[:max_rank], + one_body_correction, + truncation_value, + ) def prepare_one_body_squared_evolution(one_body_matrix, spin_basis=True): @@ -190,8 +191,7 @@ def prepare_one_body_squared_evolution(one_body_matrix, spin_basis=True): if spin_basis: n_modes = one_body_matrix.shape[0] alpha_indices = list(range(0, n_modes, 2)) - one_body_matrix = one_body_matrix[numpy.ix_(alpha_indices, - alpha_indices)] + one_body_matrix = one_body_matrix[numpy.ix_(alpha_indices, alpha_indices)] # Diagonalize the one-body matrix. if utils.is_hermitian(one_body_matrix): @@ -202,8 +202,7 @@ def prepare_one_body_squared_evolution(one_body_matrix, spin_basis=True): # If the specification was in spin-orbitals, expand back if spin_basis: - basis_transformation_matrix = numpy.kron(basis_transformation_matrix, - numpy.eye(2)) + basis_transformation_matrix = numpy.kron(basis_transformation_matrix, numpy.eye(2)) eigenvalues = numpy.kron(eigenvalues, numpy.ones(2)) # Obtain the diagonal two-body matrix. diff --git a/src/openfermion/circuits/low_rank_test.py b/src/openfermion/circuits/low_rank_test.py index fecd7eed8..64cb2e94c 100644 --- a/src/openfermion/circuits/low_rank_test.py +++ b/src/openfermion/circuits/low_rank_test.py @@ -18,40 +18,37 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData from openfermion.ops.operators import FermionOperator -from openfermion.transforms.opconversions import (get_fermion_operator, - normal_ordered) +from openfermion.transforms.opconversions import get_fermion_operator, normal_ordered from openfermion.linalg import eigenspectrum from openfermion.utils.operator_utils import is_hermitian from openfermion.testing.testing_utils import random_interaction_operator -from openfermion.circuits.low_rank import (get_chemist_two_body_coefficients, - low_rank_two_body_decomposition, - prepare_one_body_squared_evolution) +from openfermion.circuits.low_rank import ( + get_chemist_two_body_coefficients, + low_rank_two_body_decomposition, + prepare_one_body_squared_evolution, +) class ChemistTwoBodyTest(unittest.TestCase): - def test_operator_consistency_w_spin(self): - # Initialize a random InteractionOperator and FermionOperator. n_orbitals = 3 n_qubits = 2 * n_orbitals - random_interaction = random_interaction_operator(n_orbitals, - expand_spin=True, - real=False, - seed=8) + random_interaction = random_interaction_operator( + n_orbitals, expand_spin=True, real=False, seed=8 + ) random_fermion = get_fermion_operator(random_interaction) # Convert to chemist ordered tensor. - one_body_correction, chemist_tensor = \ - get_chemist_two_body_coefficients( - random_interaction.two_body_tensor, spin_basis=True) + one_body_correction, chemist_tensor = get_chemist_two_body_coefficients( + random_interaction.two_body_tensor, spin_basis=True + ) # Convert output to FermionOperator. output_operator = FermionOperator((), random_interaction.constant) # Convert one-body. - one_body_coefficients = (random_interaction.one_body_tensor + - one_body_correction) + one_body_coefficients = random_interaction.one_body_tensor + one_body_correction for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] @@ -72,37 +69,33 @@ def test_operator_consistency_w_spin(self): term = ((2 * p, 1), (2 * q, 0), (2 * r, 1), (2 * s, 0)) output_operator += FermionOperator(term, coefficient) - term = ((2 * p + 1, 1), (2 * q + 1, 0), (2 * r + 1, 1), (2 * s + 1, - 0)) + term = ((2 * p + 1, 1), (2 * q + 1, 0), (2 * r + 1, 1), (2 * s + 1, 0)) output_operator += FermionOperator(term, coefficient) # Check that difference is small. difference = normal_ordered(random_fermion - output_operator) - self.assertAlmostEqual(0., difference.induced_norm()) + self.assertAlmostEqual(0.0, difference.induced_norm()) class LowRankTest(unittest.TestCase): - def test_random_operator_consistency(self): - # Initialize a random InteractionOperator and FermionOperator. n_orbitals = 3 n_qubits = 2 * n_orbitals - random_interaction = random_interaction_operator(n_orbitals, - expand_spin=True, - real=True, - seed=8) + random_interaction = random_interaction_operator( + n_orbitals, expand_spin=True, real=True, seed=8 + ) random_fermion = get_fermion_operator(random_interaction) # Decompose. - eigenvalues, one_body_squares, one_body_correction, error = ( - low_rank_two_body_decomposition(random_interaction.two_body_tensor)) + eigenvalues, one_body_squares, one_body_correction, error = low_rank_two_body_decomposition( + random_interaction.two_body_tensor + ) self.assertFalse(error) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), random_interaction.constant) - one_body_coefficients = (random_interaction.one_body_tensor + - one_body_correction) + one_body_coefficients = random_interaction.one_body_tensor + one_body_correction for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] @@ -119,10 +112,9 @@ def test_random_operator_consistency(self): # Test for consistency. difference = normal_ordered(decomposed_operator - random_fermion) - self.assertAlmostEqual(0., difference.induced_norm()) + self.assertAlmostEqual(0.0, difference.induced_norm()) def test_molecular_operator_consistency(self): - # Initialize H2 InteractionOperator. n_qubits = 4 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') @@ -135,16 +127,19 @@ def test_molecular_operator_consistency(self): two_body_coefficients = molecule_interaction.two_body_tensor # Perform decomposition. - eigenvalues, one_body_squares, one_body_corrections, trunc_error = ( - low_rank_two_body_decomposition(two_body_coefficients)) - self.assertAlmostEqual(trunc_error, 0.) + ( + eigenvalues, + one_body_squares, + one_body_corrections, + trunc_error, + ) = low_rank_two_body_decomposition(two_body_coefficients) + self.assertAlmostEqual(trunc_error, 0.0) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) - coefficient = (one_body_coefficients[p, q] + - one_body_corrections[p, q]) + coefficient = one_body_coefficients[p, q] + one_body_corrections[p, q] decomposed_operator += FermionOperator(term, coefficient) # Build back two-body component. @@ -160,25 +155,27 @@ def test_molecular_operator_consistency(self): # Test for consistency. difference = normal_ordered(decomposed_operator - molecule_operator) - self.assertAlmostEqual(0., difference.induced_norm()) + self.assertAlmostEqual(0.0, difference.induced_norm()) # Decompose with slightly negative operator that must use eigen molecule = MolecularData(filename=filename) molecule.two_body_integrals[0, 0, 0, 0] -= 1 - eigenvalues, one_body_squares, one_body_corrections, trunc_error = ( - low_rank_two_body_decomposition(two_body_coefficients)) - self.assertAlmostEqual(trunc_error, 0.) + ( + eigenvalues, + one_body_squares, + one_body_corrections, + trunc_error, + ) = low_rank_two_body_decomposition(two_body_coefficients) + self.assertAlmostEqual(trunc_error, 0.0) # Check for property errors with self.assertRaises(TypeError): - eigenvalues, one_body_squares, _, trunc_error = ( - low_rank_two_body_decomposition(two_body_coefficients + 0.01j, - truncation_threshold=1., - final_rank=1)) + eigenvalues, one_body_squares, _, trunc_error = low_rank_two_body_decomposition( + two_body_coefficients + 0.01j, truncation_threshold=1.0, final_rank=1 + ) def test_rank_reduction(self): - # Initialize H2 InteractionOperator. n_qubits = 4 n_orbitals = 2 @@ -192,24 +189,26 @@ def test_rank_reduction(self): # Rank reduce with threshold. errors = [] - for truncation_threshold in [1., 0.1, 0.01, 0.001]: - + for truncation_threshold in [1.0, 0.1, 0.01, 0.001]: # Decompose with threshold. - (test_eigenvalues, one_body_squares, one_body_correction, - trunc_error) = low_rank_two_body_decomposition( - two_body_coefficients, - truncation_threshold=truncation_threshold) + ( + test_eigenvalues, + one_body_squares, + one_body_correction, + trunc_error, + ) = low_rank_two_body_decomposition( + two_body_coefficients, truncation_threshold=truncation_threshold + ) # Make sure error is below truncation specification. - self.assertTrue(trunc_error > 0.) + self.assertTrue(trunc_error > 0.0) self.assertTrue(trunc_error < truncation_threshold) self.assertTrue(len(test_eigenvalues) < n_orbitals**2) self.assertTrue(len(one_body_squares) == len(test_eigenvalues)) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) - one_body_coefficients = (molecule_interaction.one_body_tensor + - one_body_correction) + one_body_coefficients = molecule_interaction.one_body_tensor + one_body_correction for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] @@ -222,8 +221,7 @@ def test_rank_reduction(self): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) - decomposed_operator += (test_eigenvalues[l] * - one_body_operator**2) + decomposed_operator += test_eigenvalues[l] * one_body_operator**2 # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_operator) @@ -234,19 +232,20 @@ def test_rank_reduction(self): # Rank reduce by imposing final rank. errors = [] for final_rank in [1, 2, 3, 4]: - # Decompose with threshold. - (test_eigenvalues, one_body_squares, one_body_correction, - trunc_error) = low_rank_two_body_decomposition( - two_body_coefficients, final_rank=final_rank) + ( + test_eigenvalues, + one_body_squares, + one_body_correction, + trunc_error, + ) = low_rank_two_body_decomposition(two_body_coefficients, final_rank=final_rank) # Make sure error is below truncation specification. self.assertTrue(len(test_eigenvalues) == final_rank) # Build back operator constant and one-body components. decomposed_operator = FermionOperator((), constant) - one_body_coefficients = (molecule_interaction.one_body_tensor + - one_body_correction) + one_body_coefficients = molecule_interaction.one_body_tensor + one_body_correction for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (q, 0)) coefficient = one_body_coefficients[p, q] @@ -259,8 +258,7 @@ def test_rank_reduction(self): term = ((p, 1), (q, 0)) coefficient = one_body_squares[l, p, q] one_body_operator += FermionOperator(term, coefficient) - decomposed_operator += (test_eigenvalues[l] * - one_body_operator**2) + decomposed_operator += test_eigenvalues[l] * one_body_operator**2 # Test for consistency. difference = normal_ordered(decomposed_operator - fermion_operator) @@ -268,7 +266,6 @@ def test_rank_reduction(self): self.assertTrue(errors[3] <= errors[2] <= errors[1] <= errors[0]) def test_one_body_square_decomposition(self): - # Initialize H2 InteractionOperator. n_qubits = 4 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') @@ -279,8 +276,9 @@ def test_one_body_square_decomposition(self): two_body_coefficients = molecule_interaction.two_body_tensor # Decompose. - eigenvalues, one_body_squares, _, _ = (low_rank_two_body_decomposition( - two_body_coefficients, truncation_threshold=0)) + eigenvalues, one_body_squares, _, _ = low_rank_two_body_decomposition( + two_body_coefficients, truncation_threshold=0 + ) rank = eigenvalues.size for l in range(rank): one_body_operator = FermionOperator() @@ -296,8 +294,10 @@ def test_one_body_square_decomposition(self): prepare_one_body_squared_evolution(one_body_squares[l]) continue else: - density_density_matrix, basis_transformation_matrix = ( - prepare_one_body_squared_evolution(one_body_squares[l])) + ( + density_density_matrix, + basis_transformation_matrix, + ) = prepare_one_body_squared_evolution(one_body_squares[l]) two_body_operator = FermionOperator() for p, q in itertools.product(range(n_qubits), repeat=2): term = ((p, 1), (p, 0), (q, 1), (q, 0)) @@ -308,23 +308,23 @@ def test_one_body_square_decomposition(self): hopefully_diagonal = basis_transformation_matrix.dot( numpy.dot( one_body_squares[l], - numpy.transpose( - numpy.conjugate(basis_transformation_matrix)))) + numpy.transpose(numpy.conjugate(basis_transformation_matrix)), + ) + ) diagonal = numpy.diag(hopefully_diagonal) difference = hopefully_diagonal - numpy.diag(diagonal) - self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(difference))) density_density_alternative = numpy.outer(diagonal, diagonal) difference = density_density_alternative - density_density_matrix - self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(difference))) # Test spectra. one_body_squared_spectrum = eigenspectrum(one_body_squared) two_body_spectrum = eigenspectrum(two_body_operator) difference = two_body_spectrum - one_body_squared_spectrum - self.assertAlmostEqual(0., numpy.amax(numpy.absolute(difference))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(difference))) def test_one_body_squared_nonhermitian_raises_error(self): one_body_matrix = numpy.array([[0, 1], [0, 0]]) with self.assertRaises(ValueError): - prepare_one_body_squared_evolution(one_body_matrix, - spin_basis=False) + prepare_one_body_squared_evolution(one_body_matrix, spin_basis=False) diff --git a/src/openfermion/circuits/primitives/__init__.py b/src/openfermion/circuits/primitives/__init__.py index 3a56c2c12..16a35c45a 100644 --- a/src/openfermion/circuits/primitives/__init__.py +++ b/src/openfermion/circuits/primitives/__init__.py @@ -17,9 +17,6 @@ from .optimal_givens_decomposition import optimal_givens_decomposition -from .state_preparation import ( - prepare_gaussian_state, - prepare_slater_determinant, -) +from .state_preparation import prepare_gaussian_state, prepare_slater_determinant from .swap_network import swap_network diff --git a/src/openfermion/circuits/primitives/bogoliubov_transform.py b/src/openfermion/circuits/primitives/bogoliubov_transform.py index a46b6ccb2..7b7fcb985 100644 --- a/src/openfermion/circuits/primitives/bogoliubov_transform.py +++ b/src/openfermion/circuits/primitives/bogoliubov_transform.py @@ -11,7 +11,7 @@ # limitations under the License. """The Bogoliubov transformation.""" -from typing import (Iterable, List, Optional, Sequence, Tuple, Union, cast) +from typing import Iterable, List, Optional, Sequence, Tuple, Union, cast import numpy @@ -21,9 +21,9 @@ def bogoliubov_transform( - qubits: Sequence[cirq.Qid], - transformation_matrix: numpy.ndarray, - initial_state: Optional[Union[int, Sequence[int]]] = None + qubits: Sequence[cirq.Qid], + transformation_matrix: numpy.ndarray, + initial_state: Optional[Union[int, Sequence[int]]] = None, ) -> cirq.OP_TREE: r"""Perform a Bogoliubov transformation. @@ -81,10 +81,12 @@ def bogoliubov_transform( n_qubits = len(qubits) shape = transformation_matrix.shape if shape not in [(n_qubits, n_qubits), (n_qubits, 2 * n_qubits)]: - raise ValueError('Bad shape for transformation_matrix. ' - 'Expected {} or {} but got {}.'.format( - (n_qubits, n_qubits), (n_qubits, 2 * n_qubits), - shape)) + raise ValueError( + 'Bad shape for transformation_matrix. ' + 'Expected {} or {} but got {}.'.format( + (n_qubits, n_qubits), (n_qubits, 2 * n_qubits), shape + ) + ) if isinstance(initial_state, int): initial_state = _occupied_orbitals(initial_state, n_qubits) @@ -93,51 +95,40 @@ def bogoliubov_transform( # If the transformation matrix is block diagonal with two blocks, # do each block separately if _is_spin_block_diagonal(transformation_matrix): - up_block = transformation_matrix[:n_qubits // 2, :n_qubits // 2] - up_qubits = qubits[:n_qubits // 2] + up_block = transformation_matrix[: n_qubits // 2, : n_qubits // 2] + up_qubits = qubits[: n_qubits // 2] - down_block = transformation_matrix[n_qubits // 2:, n_qubits // 2:] - down_qubits = qubits[n_qubits // 2:] + down_block = transformation_matrix[n_qubits // 2 :, n_qubits // 2 :] + down_qubits = qubits[n_qubits // 2 :] if initially_occupied_orbitals is None: up_orbitals = None down_orbitals = None else: - up_orbitals = [ - i for i in initially_occupied_orbitals if i < n_qubits // 2 - ] + up_orbitals = [i for i in initially_occupied_orbitals if i < n_qubits // 2] down_orbitals = [ - i - n_qubits // 2 - for i in initially_occupied_orbitals - if i >= n_qubits // 2 + i - n_qubits // 2 for i in initially_occupied_orbitals if i >= n_qubits // 2 ] - yield bogoliubov_transform(up_qubits, - up_block, - initial_state=up_orbitals) - yield bogoliubov_transform(down_qubits, - down_block, - initial_state=down_orbitals) + yield bogoliubov_transform(up_qubits, up_block, initial_state=up_orbitals) + yield bogoliubov_transform(down_qubits, down_block, initial_state=down_orbitals) return if shape == (n_qubits, n_qubits): # We're performing a particle-number conserving "Slater" basis change - yield _slater_basis_change(qubits, transformation_matrix, - initially_occupied_orbitals) + yield _slater_basis_change(qubits, transformation_matrix, initially_occupied_orbitals) else: # We're performing a more general Gaussian unitary - yield _gaussian_basis_change(qubits, transformation_matrix, - initially_occupied_orbitals) + yield _gaussian_basis_change(qubits, transformation_matrix, initially_occupied_orbitals) def _is_spin_block_diagonal(matrix) -> bool: n = matrix.shape[0] if n % 2: return False - max_upper_right = numpy.max(numpy.abs(matrix[:n // 2, n // 2:])) - max_lower_left = numpy.max(numpy.abs(matrix[n // 2:, :n // 2])) - return (numpy.isclose(max_upper_right, 0.0) and - numpy.isclose(max_lower_left, 0.0)) + max_upper_right = numpy.max(numpy.abs(matrix[: n // 2, n // 2 :])) + max_lower_left = numpy.max(numpy.abs(matrix[n // 2 :, : n // 2])) + return numpy.isclose(max_upper_right, 0.0) and numpy.isclose(max_lower_left, 0.0) def _occupied_orbitals(computational_basis_state: int, n_qubits) -> List[int]: @@ -147,42 +138,40 @@ def _occupied_orbitals(computational_basis_state: int, n_qubits) -> List[int]: return [j for j in range(len(bitstring)) if bitstring[j] == '1'] -def _slater_basis_change(qubits: Sequence[cirq.Qid], - transformation_matrix: numpy.ndarray, - initially_occupied_orbitals: Optional[Sequence[int]] - ) -> cirq.OP_TREE: +def _slater_basis_change( + qubits: Sequence[cirq.Qid], + transformation_matrix: numpy.ndarray, + initially_occupied_orbitals: Optional[Sequence[int]], +) -> cirq.OP_TREE: n_qubits = len(qubits) if initially_occupied_orbitals is None: - decomposition, diagonal = linalg.givens_decomposition_square( - transformation_matrix) + decomposition, diagonal = linalg.givens_decomposition_square(transformation_matrix) circuit_description = list(reversed(decomposition)) # The initial state is not a computational basis state so the # phases left on the diagonal in the decomposition matter - yield (cirq.rz(rads=numpy.angle(diagonal[j])).on(qubits[j]) - for j in range(n_qubits)) + yield (cirq.rz(rads=numpy.angle(diagonal[j])).on(qubits[j]) for j in range(n_qubits)) else: - initially_occupied_orbitals = cast(Sequence[int], - initially_occupied_orbitals) - transformation_matrix = transformation_matrix[list( - initially_occupied_orbitals)] + initially_occupied_orbitals = cast(Sequence[int], initially_occupied_orbitals) + transformation_matrix = transformation_matrix[list(initially_occupied_orbitals)] n_occupied = len(initially_occupied_orbitals) # Flip bits so that the first n_occupied are 1 and the rest 0 initially_occupied_orbitals_set = set(initially_occupied_orbitals) - yield (cirq.X(qubits[j]) - for j in range(n_qubits) - if (j < n_occupied) != (j in initially_occupied_orbitals_set)) - circuit_description = circuits.slater_determinant_preparation_circuit( - transformation_matrix) + yield ( + cirq.X(qubits[j]) + for j in range(n_qubits) + if (j < n_occupied) != (j in initially_occupied_orbitals_set) + ) + circuit_description = circuits.slater_determinant_preparation_circuit(transformation_matrix) - yield _ops_from_givens_rotations_circuit_description( - qubits, circuit_description) + yield _ops_from_givens_rotations_circuit_description(qubits, circuit_description) -def _gaussian_basis_change(qubits: Sequence[cirq.Qid], - transformation_matrix: numpy.ndarray, - initially_occupied_orbitals: Optional[Sequence[int]] - ) -> cirq.OP_TREE: +def _gaussian_basis_change( + qubits: Sequence[cirq.Qid], + transformation_matrix: numpy.ndarray, + initially_occupied_orbitals: Optional[Sequence[int]], +) -> cirq.OP_TREE: n_qubits = len(qubits) # Rearrange the transformation matrix because the OpenFermion routine @@ -190,32 +179,31 @@ def _gaussian_basis_change(qubits: Sequence[cirq.Qid], # operators left_block = transformation_matrix[:, :n_qubits] right_block = transformation_matrix[:, n_qubits:] - transformation_matrix = numpy.block( - [numpy.conjugate(right_block), - numpy.conjugate(left_block)]) + transformation_matrix = numpy.block([numpy.conjugate(right_block), numpy.conjugate(left_block)]) - decomposition, left_decomposition, _, left_diagonal = ( - linalg.fermionic_gaussian_decomposition(transformation_matrix)) + decomposition, left_decomposition, _, left_diagonal = linalg.fermionic_gaussian_decomposition( + transformation_matrix + ) - if (initially_occupied_orbitals is not None and - len(initially_occupied_orbitals) == 0): + if initially_occupied_orbitals is not None and len(initially_occupied_orbitals) == 0: # Starting with the vacuum state yields additional symmetry circuit_description = list(reversed(decomposition)) else: if initially_occupied_orbitals is None: # The initial state is not a computational basis state so the # phases left on the diagonal in the Givens decomposition matter - yield (cirq.rz(rads=numpy.angle(left_diagonal[j])).on(qubits[j]) - for j in range(n_qubits)) + yield ( + cirq.rz(rads=numpy.angle(left_diagonal[j])).on(qubits[j]) for j in range(n_qubits) + ) circuit_description = list(reversed(decomposition + left_decomposition)) - yield _ops_from_givens_rotations_circuit_description( - qubits, circuit_description) + yield _ops_from_givens_rotations_circuit_description(qubits, circuit_description) def _ops_from_givens_rotations_circuit_description( - qubits: Sequence[cirq.Qid], circuit_description: Iterable[Iterable[ - Union[str, Tuple[int, int, float, float]]]]) -> cirq.OP_TREE: + qubits: Sequence[cirq.Qid], + circuit_description: Iterable[Iterable[Union[str, Tuple[int, int, float, float]]]], +) -> cirq.OP_TREE: """Yield operations from a Givens rotations circuit obtained from OpenFermion. """ @@ -226,4 +214,4 @@ def _ops_from_givens_rotations_circuit_description( else: i, j, theta, phi = cast(Tuple[int, int, float, float], op) yield circuits.Ryxxy(theta).on(qubits[i], qubits[j]) - yield cirq.Z(qubits[j])**(phi / numpy.pi) + yield cirq.Z(qubits[j]) ** (phi / numpy.pi) diff --git a/src/openfermion/circuits/primitives/bogoliubov_transform_test.py b/src/openfermion/circuits/primitives/bogoliubov_transform_test.py index e54488216..fe6a0a324 100644 --- a/src/openfermion/circuits/primitives/bogoliubov_transform_test.py +++ b/src/openfermion/circuits/primitives/bogoliubov_transform_test.py @@ -19,33 +19,42 @@ import openfermion from openfermion import bogoliubov_transform, get_sparse_operator -from openfermion.testing import (random_quadratic_hamiltonian, - random_unitary_matrix) +from openfermion.testing import random_quadratic_hamiltonian, random_unitary_matrix def fourier_transform_matrix(n_modes): root_of_unity = numpy.exp(2j * numpy.pi / n_modes) - return numpy.array([[root_of_unity**(j * k) - for k in range(n_modes)] - for j in range(n_modes)]) / numpy.sqrt(n_modes) + return numpy.array( + [[root_of_unity ** (j * k) for k in range(n_modes)] for j in range(n_modes)] + ) / numpy.sqrt(n_modes) @pytest.mark.parametrize( - 'transformation_matrix, initial_state, correct_state', [ - (fourier_transform_matrix(3), 4, - numpy.array([0, 1, 1, 0, 1, 0, 0, 0]) / numpy.sqrt(3)), - (fourier_transform_matrix(3), [1, 2], - numpy.array([ - 0, 0, 0, - numpy.exp(2j * numpy.pi / 3) - 1, 0, - 1 - numpy.exp(2j * numpy.pi / 3), - numpy.exp(2j * numpy.pi / 3) - 1, 0 - ]) / 3), - ]) -def test_bogoliubov_transform_fourier_transform(transformation_matrix, - initial_state, - correct_state, - atol=5e-6): + 'transformation_matrix, initial_state, correct_state', + [ + (fourier_transform_matrix(3), 4, numpy.array([0, 1, 1, 0, 1, 0, 0, 0]) / numpy.sqrt(3)), + ( + fourier_transform_matrix(3), + [1, 2], + numpy.array( + [ + 0, + 0, + 0, + numpy.exp(2j * numpy.pi / 3) - 1, + 0, + 1 - numpy.exp(2j * numpy.pi / 3), + numpy.exp(2j * numpy.pi / 3) - 1, + 0, + ] + ) + / 3, + ), + ], +) +def test_bogoliubov_transform_fourier_transform( + transformation_matrix, initial_state, correct_state, atol=5e-6 +): n_qubits = transformation_matrix.shape[0] sim = cirq.Simulator(dtype=numpy.complex128) qubits = LineQubit.range(n_qubits) @@ -53,97 +62,84 @@ def test_bogoliubov_transform_fourier_transform(transformation_matrix, initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) circuit = cirq.Circuit( - bogoliubov_transform(qubits, - transformation_matrix, - initial_state=initial_state)) - state = sim.simulate(circuit, - initial_state=initial_state).final_state_vector - cirq.testing.assert_allclose_up_to_global_phase(state, - correct_state, - atol=atol) - - -@pytest.mark.parametrize('n_spatial_orbitals, conserves_particle_number', - [(4, True), (5, True)]) -def test_spin_symmetric_bogoliubov_transform(n_spatial_orbitals, - conserves_particle_number, - atol=5e-5): + bogoliubov_transform(qubits, transformation_matrix, initial_state=initial_state) + ) + state = sim.simulate(circuit, initial_state=initial_state).final_state_vector + cirq.testing.assert_allclose_up_to_global_phase(state, correct_state, atol=atol) + + +@pytest.mark.parametrize('n_spatial_orbitals, conserves_particle_number', [(4, True), (5, True)]) +def test_spin_symmetric_bogoliubov_transform( + n_spatial_orbitals, conserves_particle_number, atol=5e-5 +): n_qubits = 2 * n_spatial_orbitals qubits = LineQubit.range(n_qubits) sim = cirq.Simulator(dtype=numpy.complex128) # Initialize a random quadratic Hamiltonian - quad_ham = random_quadratic_hamiltonian(n_spatial_orbitals, - conserves_particle_number, - real=True, - expand_spin=True, - seed=28166) + quad_ham = random_quadratic_hamiltonian( + n_spatial_orbitals, conserves_particle_number, real=True, expand_spin=True, seed=28166 + ) # Reorder the Hamiltonian and get sparse matrix quad_ham = openfermion.get_quadratic_hamiltonian( - openfermion.reorder(openfermion.get_fermion_operator(quad_ham), - openfermion.up_then_down)) + openfermion.reorder(openfermion.get_fermion_operator(quad_ham), openfermion.up_then_down) + ) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the orbital energies and transformation_matrix - up_orbital_energies, _, _ = (quad_ham.diagonalizing_bogoliubov_transform( - spin_sector=0)) - down_orbital_energies, _, _ = (quad_ham.diagonalizing_bogoliubov_transform( - spin_sector=1)) - _, transformation_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + up_orbital_energies, _, _ = quad_ham.diagonalizing_bogoliubov_transform(spin_sector=0) + down_orbital_energies, _, _ = quad_ham.diagonalizing_bogoliubov_transform(spin_sector=1) + _, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() # Pick some orbitals to occupy up_orbitals = list(range(2)) down_orbitals = [0, 2, 3] - energy = sum(up_orbital_energies[up_orbitals]) + sum( - down_orbital_energies[down_orbitals]) + quad_ham.constant + energy = ( + sum(up_orbital_energies[up_orbitals]) + + sum(down_orbital_energies[down_orbitals]) + + quad_ham.constant + ) # Construct initial state - initial_state = (sum(2**(n_qubits - 1 - int(i)) for i in up_orbitals) + sum( - 2**(n_qubits - 1 - int(i + n_spatial_orbitals)) for i in down_orbitals)) + initial_state = sum(2 ** (n_qubits - 1 - int(i)) for i in up_orbitals) + sum( + 2 ** (n_qubits - 1 - int(i + n_spatial_orbitals)) for i in down_orbitals + ) # Apply the circuit circuit = cirq.Circuit( - bogoliubov_transform(qubits, - transformation_matrix, - initial_state=initial_state)) - state = sim.simulate(circuit, - initial_state=initial_state).final_state_vector + bogoliubov_transform(qubits, transformation_matrix, initial_state=initial_state) + ) + state = sim.simulate(circuit, initial_state=initial_state).final_state_vector # Check that the result is an eigenstate with the correct eigenvalue - numpy.testing.assert_allclose(quad_ham_sparse.dot(state), - energy * state, - atol=atol) - - -@pytest.mark.parametrize('n_qubits, conserves_particle_number', [(4, True), - (4, False), - (5, True), - (5, False)]) -def test_bogoliubov_transform_quadratic_hamiltonian(n_qubits, - conserves_particle_number, - atol=5e-5): + numpy.testing.assert_allclose(quad_ham_sparse.dot(state), energy * state, atol=atol) + + +@pytest.mark.parametrize( + 'n_qubits, conserves_particle_number', [(4, True), (4, False), (5, True), (5, False)] +) +def test_bogoliubov_transform_quadratic_hamiltonian(n_qubits, conserves_particle_number, atol=5e-5): qubits = LineQubit.range(n_qubits) sim = cirq.Simulator(dtype=numpy.complex128) # Initialize a random quadratic Hamiltonian - quad_ham = random_quadratic_hamiltonian(n_qubits, - conserves_particle_number, - real=False) + quad_ham = random_quadratic_hamiltonian(n_qubits, conserves_particle_number, real=False) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the orbital energies and circuit - orbital_energies, transformation_matrix, constant = ( - quad_ham.diagonalizing_bogoliubov_transform()) + ( + orbital_energies, + transformation_matrix, + constant, + ) = quad_ham.diagonalizing_bogoliubov_transform() circuit = cirq.Circuit(bogoliubov_transform(qubits, transformation_matrix)) # Pick some random eigenstates to prepare, which correspond to random # subsets of [0 ... n_qubits - 1] n_eigenstates = min(2**n_qubits, 5) subsets = [ - numpy.random.choice(range(n_qubits), - numpy.random.randint(1, n_qubits + 1), False) + numpy.random.choice(range(n_qubits), numpy.random.randint(1, n_qubits + 1), False) for _ in range(n_eigenstates) ] # Also test empty subset @@ -151,41 +147,29 @@ def test_bogoliubov_transform_quadratic_hamiltonian(n_qubits, for occupied_orbitals in subsets: # Compute the energy of this eigenstate - energy = (sum(orbital_energies[i] for i in occupied_orbitals) + - constant) + energy = sum(orbital_energies[i] for i in occupied_orbitals) + constant # Construct initial state - initial_state = sum( - 2**(n_qubits - 1 - int(i)) for i in occupied_orbitals) + initial_state = sum(2 ** (n_qubits - 1 - int(i)) for i in occupied_orbitals) # Get the state using a circuit simulation - state1 = sim.simulate(circuit, - initial_state=initial_state).final_state_vector + state1 = sim.simulate(circuit, initial_state=initial_state).final_state_vector # Also test the option to start with a computational basis state special_circuit = cirq.Circuit( - bogoliubov_transform(qubits, - transformation_matrix, - initial_state=initial_state)) - state2 = sim.simulate(special_circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector + bogoliubov_transform(qubits, transformation_matrix, initial_state=initial_state) + ) + state2 = sim.simulate( + special_circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector # Check that the result is an eigenstate with the correct eigenvalue - numpy.testing.assert_allclose(quad_ham_sparse.dot(state1), - energy * state1, - atol=atol) - numpy.testing.assert_allclose(quad_ham_sparse.dot(state2), - energy * state2, - atol=atol) - - -@pytest.mark.parametrize('n_qubits, atol', [ - (4, 1e-7), - (5, 1e-7), -]) -def test_bogoliubov_transform_fourier_transform_inverse_is_dagger( - n_qubits, atol): + numpy.testing.assert_allclose(quad_ham_sparse.dot(state1), energy * state1, atol=atol) + numpy.testing.assert_allclose(quad_ham_sparse.dot(state2), energy * state2, atol=atol) + + +@pytest.mark.parametrize('n_qubits, atol', [(4, 1e-7), (5, 1e-7)]) +def test_bogoliubov_transform_fourier_transform_inverse_is_dagger(n_qubits, atol): u = fourier_transform_matrix(n_qubits) qubits = cirq.LineQubit.range(n_qubits) @@ -194,24 +178,26 @@ def test_bogoliubov_transform_fourier_transform_inverse_is_dagger( circuit2 = cirq.Circuit(bogoliubov_transform(qubits, u.T.conj())) - cirq.testing.assert_allclose_up_to_global_phase(circuit1.unitary(), - circuit2.unitary(), - atol=atol) + cirq.testing.assert_allclose_up_to_global_phase( + circuit1.unitary(), circuit2.unitary(), atol=atol + ) -@pytest.mark.parametrize('n_qubits, real, particle_conserving, atol', [ - (5, True, True, 1e-7), - (5, False, True, 1e-7), - (5, True, False, 1e-7), - (5, False, False, 1e-7), -]) +@pytest.mark.parametrize( + 'n_qubits, real, particle_conserving, atol', + [ + (5, True, True, 1e-7), + (5, False, True, 1e-7), + (5, True, False, 1e-7), + (5, False, False, 1e-7), + ], +) def test_bogoliubov_transform_quadratic_hamiltonian_inverse_is_dagger( - n_qubits, real, particle_conserving, atol): + n_qubits, real, particle_conserving, atol +): quad_ham = random_quadratic_hamiltonian( - n_qubits, - real=real, - conserves_particle_number=particle_conserving, - seed=46533) + n_qubits, real=real, conserves_particle_number=particle_conserving, seed=46533 + ) _, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() qubits = cirq.LineQubit.range(n_qubits) @@ -221,41 +207,33 @@ def test_bogoliubov_transform_quadratic_hamiltonian_inverse_is_dagger( else: left_block = transformation_matrix[:, :n_qubits] right_block = transformation_matrix[:, n_qubits:] - daggered_transformation_matrix = numpy.block( - [left_block.T.conj(), right_block.T]) + daggered_transformation_matrix = numpy.block([left_block.T.conj(), right_block.T]) - circuit1 = cirq.Circuit( - cirq.inverse(bogoliubov_transform(qubits, transformation_matrix))) + circuit1 = cirq.Circuit(cirq.inverse(bogoliubov_transform(qubits, transformation_matrix))) - circuit2 = cirq.Circuit( - bogoliubov_transform(qubits, daggered_transformation_matrix)) + circuit2 = cirq.Circuit(bogoliubov_transform(qubits, daggered_transformation_matrix)) - cirq.testing.assert_allclose_up_to_global_phase(circuit1.unitary(), - circuit2.unitary(), - atol=atol) + cirq.testing.assert_allclose_up_to_global_phase( + circuit1.unitary(), circuit2.unitary(), atol=atol + ) -@pytest.mark.parametrize('n_qubits, atol', [ - (4, 1e-7), - (5, 1e-7), -]) +@pytest.mark.parametrize('n_qubits, atol', [(4, 1e-7), (5, 1e-7)]) def test_bogoliubov_transform_compose(n_qubits, atol): u = random_unitary_matrix(n_qubits, seed=24964) v = random_unitary_matrix(n_qubits, seed=33656) qubits = cirq.LineQubit.range(n_qubits) - circuit1 = cirq.Circuit(bogoliubov_transform(qubits, u), - bogoliubov_transform(qubits, v)) + circuit1 = cirq.Circuit(bogoliubov_transform(qubits, u), bogoliubov_transform(qubits, v)) circuit2 = cirq.Circuit(bogoliubov_transform(qubits, u.dot(v))) - cirq.testing.assert_allclose_up_to_global_phase(circuit1.unitary(), - circuit2.unitary(), - atol=atol) + cirq.testing.assert_allclose_up_to_global_phase( + circuit1.unitary(), circuit2.unitary(), atol=atol + ) def test_bogoliubov_transform_bad_shape_raises_error(): with pytest.raises(ValueError): - _ = next( - bogoliubov_transform(cirq.LineQubit.range(4), numpy.zeros((4, 7)))) + _ = next(bogoliubov_transform(cirq.LineQubit.range(4), numpy.zeros((4, 7)))) diff --git a/src/openfermion/circuits/primitives/ffft.py b/src/openfermion/circuits/primitives/ffft.py index f5d144d4a..0579840e2 100644 --- a/src/openfermion/circuits/primitives/ffft.py +++ b/src/openfermion/circuits/primitives/ffft.py @@ -11,7 +11,7 @@ # limitations under the License. """The fast fermionic Fourier transform.""" -from typing import (Iterable, List, Sequence) +from typing import Iterable, List, Sequence import numpy as np from sympy.ntheory import factorint @@ -67,15 +67,20 @@ class _F0Gate(cirq.MatrixGate): def __init__(self): """Initializes $F_0$ gate.""" - cirq.MatrixGate.__init__(self, - np.array([[1, 0, 0, 0], - [0, -2**(-0.5), 2**(-0.5), 0], - [0, 2**(-0.5), 2**(-0.5), 0], - [0, 0, 0, -1]]), - qid_shape=(2, 2)) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs - ) -> cirq.CircuitDiagramInfo: + cirq.MatrixGate.__init__( + self, + np.array( + [ + [1, 0, 0, 0], + [0, -(2 ** (-0.5)), 2 ** (-0.5), 0], + [0, 2 ** (-0.5), 2 ** (-0.5), 0], + [0, 0, 0, -1], + ] + ), + qid_shape=(2, 2), + ) + + def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: if args.use_unicode_characters: symbols = 'F₀', 'F₀' else: @@ -111,16 +116,15 @@ def __init__(self, k, n): def _num_qubits_(self) -> int: return 1 - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs - ) -> cirq.CircuitDiagramInfo: + def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: if args.use_unicode_characters: - symbols = 'ω^{}_{}'.format(self.k, self.n), + symbols = ('ω^{}_{}'.format(self.k, self.n),) else: - symbols = 'w^{}_{}'.format(self.k, self.n), + symbols = ('w^{}_{}'.format(self.k, self.n),) return cirq.CircuitDiagramInfo(wire_symbols=symbols) def _decompose_(self, qubits: Iterable[cirq.Qid]): - q, = qubits + (q,) = qubits exponent = -2 * self.k / self.n yield cirq.ZPowGate(exponent=exponent, global_shift=0).on(q) @@ -210,11 +214,9 @@ def fourier_transform_matrix(size): def _ffft_prime(qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE: - def fft_matrix(n): unit = np.exp(-2j * np.pi / n) - return np.array([[unit**(j * k) for k in range(n)] for j in range(n) - ]) / np.sqrt(n) + return np.array([[unit ** (j * k) for k in range(n)] for j in range(n)]) / np.sqrt(n) n = len(qubits) @@ -253,7 +255,7 @@ def _ffft(qubits: Sequence[cirq.Qid], factors: List[int]) -> cirq.OP_TREE: # Performs ny recursive FFFTs, each of size nx. for y in range(ny): - operations.append(_ffft(qubits[nx * y:nx * (y + 1)], factors_x)) + operations.append(_ffft(qubits[nx * y : nx * (y + 1)], factors_x)) # The second part is to perform ny FFFTs of size nx on qubits which are # consecutive in the original sequence. To place qubits in a correct order @@ -265,7 +267,7 @@ def _ffft(qubits: Sequence[cirq.Qid], factors: List[int]) -> cirq.OP_TREE: for x in range(nx): for y in range(1, ny): operations.append(_TwiddleGate(x * y, n).on(qubits[ny * x + y])) - operations.append(_ffft(qubits[ny * x:ny * (x + 1)], factors_y)) + operations.append(_ffft(qubits[ny * x : ny * (x + 1)], factors_y)) # The result of performing FFFT on k-th group of nx qubits is a new group # of fermionic operators with indices k, k + ny, k + 2ny, ... of the final @@ -276,8 +278,7 @@ def _ffft(qubits: Sequence[cirq.Qid], factors: List[int]) -> cirq.OP_TREE: return operations -def _permute(qubits: Sequence[cirq.Qid], - permutation: List[int]) -> cirq.OP_TREE: +def _permute(qubits: Sequence[cirq.Qid], permutation: List[int]) -> cirq.OP_TREE: """ Generates a circuit which reorders Fermionic modes. @@ -296,5 +297,5 @@ def _permute(qubits: Sequence[cirq.Qid], Gate that reorders the qubits accordingly. """ return cirq.contrib.acquaintance.permutation.LinearPermutationGate( - len(qubits), {i: permutation[i] for i in range(len(permutation))}, - FSWAP).on(*qubits) + len(qubits), {i: permutation[i] for i in range(len(permutation))}, FSWAP + ).on(*qubits) diff --git a/src/openfermion/circuits/primitives/ffft_test.py b/src/openfermion/circuits/primitives/ffft_test.py index 5258d206d..24e57762f 100644 --- a/src/openfermion/circuits/primitives/ffft_test.py +++ b/src/openfermion/circuits/primitives/ffft_test.py @@ -24,8 +24,7 @@ from typing import Dict -def _fourier_transform_single_fermionic_modes(amplitudes: List[complex] - ) -> List[complex]: +def _fourier_transform_single_fermionic_modes(amplitudes: List[complex]) -> List[complex]: """Fermionic Fourier transform of a list of single Fermionic modes. Args: @@ -44,8 +43,9 @@ def fft(k, n): return [fft(k, n) for k in range(n)] -def _fourier_transform_multi_fermionic_mode(n: int, amplitude: complex, - modes: List[int]) -> np.ndarray: +def _fourier_transform_multi_fermionic_mode( + n: int, amplitude: complex, modes: List[int] +) -> np.ndarray: """Fermionic Fourier transform of a multi Fermionic mode base state. Args: @@ -116,8 +116,7 @@ def _single_fermionic_modes_state(amplitudes: List[complex]) -> np.ndarray: return state / np.linalg.norm(state) -def _multi_fermionic_mode_base_state(n: int, amplitude: complex, - modes: List[int]) -> np.ndarray: +def _multi_fermionic_mode_base_state(n: int, amplitude: complex, modes: List[int]) -> np.ndarray: """Prepares state which is a base vector with list of desired modes. Prepares a state to be one of the basis vectors of an n-dimensional qubits @@ -141,18 +140,17 @@ def _multi_fermionic_mode_base_state(n: int, amplitude: complex, return state -@pytest.mark.parametrize('amplitudes', - [[1, 0], [1j, 0], [0, 1], [0, -1j], [1, 1]]) +@pytest.mark.parametrize('amplitudes', [[1, 0], [1j, 0], [0, 1], [0, -1j], [1, 1]]) def test_F0Gate_transform(amplitudes): qubits = LineQubit.range(2) sim = cirq.Simulator(dtype=np.complex128) initial_state = _single_fermionic_modes_state(amplitudes) expected_state = _single_fermionic_modes_state( - _fourier_transform_single_fermionic_modes(amplitudes)) + _fourier_transform_single_fermionic_modes(amplitudes) + ) circuit = cirq.Circuit(_F0Gate().on(*qubits)) - state = sim.simulate(circuit, - initial_state=initial_state).final_state_vector + state = sim.simulate(circuit, initial_state=initial_state).final_state_vector assert np.allclose(state, expected_state, rtol=0.0) @@ -161,30 +159,39 @@ def test_F0Gate_text_unicode_diagram(): qubits = LineQubit.range(2) circuit = cirq.Circuit(_F0Gate().on(*qubits)) - assert circuit.to_text_diagram().strip() == """ + assert ( + circuit.to_text_diagram().strip() + == """ 0: ───F₀─── │ 1: ───F₀─── """.strip() + ) def test_F0Gate_text_diagram(): qubits = LineQubit.range(2) circuit = cirq.Circuit(_F0Gate().on(*qubits)) - assert circuit.to_text_diagram(use_unicode_characters=False).strip() == """ + assert ( + circuit.to_text_diagram(use_unicode_characters=False).strip() + == """ 0: ---F0--- | 1: ---F0--- """.strip() - - -@pytest.mark.parametrize('k, n, qubit, initial, expected', [ - (0, 2, 0, [1, 0], [1, 0]), - (2, 8, 0, [1, 0], [np.exp(-2 * np.pi * 1j * 2 / 8), 0]), - (4, 8, 1, [0, 1], [0, np.exp(-2 * np.pi * 1j * 4 / 8)]), - (3, 5, 0, [1, 1], [np.exp(-2 * np.pi * 1j * 3 / 5), 1]), -]) + ) + + +@pytest.mark.parametrize( + 'k, n, qubit, initial, expected', + [ + (0, 2, 0, [1, 0], [1, 0]), + (2, 8, 0, [1, 0], [np.exp(-2 * np.pi * 1j * 2 / 8), 0]), + (4, 8, 1, [0, 1], [0, np.exp(-2 * np.pi * 1j * 4 / 8)]), + (3, 5, 0, [1, 1], [np.exp(-2 * np.pi * 1j * 3 / 5), 1]), + ], +) def test_TwiddleGate_transform(k, n, qubit, initial, expected): qubits = LineQubit.range(2) sim = cirq.Simulator(dtype=np.complex128) @@ -192,9 +199,9 @@ def test_TwiddleGate_transform(k, n, qubit, initial, expected): expected_state = _single_fermionic_modes_state(expected) circuit = cirq.Circuit(_TwiddleGate(k, n).on(qubits[qubit])) - state = sim.simulate(circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector + state = sim.simulate( + circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector assert np.allclose(state, expected_state, rtol=0.0) @@ -203,91 +210,116 @@ def test_TwiddleGate_text_unicode_diagram(): qubit = LineQubit.range(1) circuit = cirq.Circuit(_TwiddleGate(2, 8).on(*qubit)) - assert circuit.to_text_diagram().strip() == """ + assert ( + circuit.to_text_diagram().strip() + == """ 0: ───ω^2_8─── """.strip() + ) def test_TwiddleGate_text_diagram(): qubit = LineQubit.range(1) circuit = cirq.Circuit(_TwiddleGate(2, 8).on(*qubit)) - assert circuit.to_text_diagram(use_unicode_characters=False).strip() == """ + assert ( + circuit.to_text_diagram(use_unicode_characters=False).strip() + == """ 0: ---w^2_8--- """.strip() - - -@pytest.mark.parametrize('amplitudes', - [[1], [1, 0], [0, 1], [1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, -1j / np.sqrt(2), 0, 0, 1 / np.sqrt(2), 0, 0]]) + ) + + +@pytest.mark.parametrize( + 'amplitudes', + [ + [1], + [1, 0], + [0, 1], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, -1j / np.sqrt(2), 0, 0, 1 / np.sqrt(2), 0, 0], + ], +) def test_ffft_single_fermionic_modes(amplitudes): sim = cirq.Simulator(dtype=np.complex128) initial_state = _single_fermionic_modes_state(amplitudes) expected_state = _single_fermionic_modes_state( - _fourier_transform_single_fermionic_modes(amplitudes)) + _fourier_transform_single_fermionic_modes(amplitudes) + ) qubits = LineQubit.range(len(amplitudes)) circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) - state = sim.simulate(circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector + state = sim.simulate( + circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector assert np.allclose(state, expected_state, rtol=0.0) -@pytest.mark.parametrize('amplitudes', [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - [0, 0, 0, 1, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], -]) +@pytest.mark.parametrize( + 'amplitudes', + [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [0, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0], + ], +) def test_ffft_single_fermionic_modes_non_power_of_2(amplitudes): sim = cirq.Simulator(dtype=np.complex128) initial_state = _single_fermionic_modes_state(amplitudes) expected_state = _single_fermionic_modes_state( - _fourier_transform_single_fermionic_modes(amplitudes)) + _fourier_transform_single_fermionic_modes(amplitudes) + ) qubits = LineQubit.range(len(amplitudes)) circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) - state = sim.simulate(circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector - - cirq.testing.assert_allclose_up_to_global_phase(state, - expected_state, - atol=1e-8) - - -@pytest.mark.parametrize('n, initial', [ - (2, (1, [0, 1])), - (4, (1, [0, 1])), - (4, (1, [1, 2])), - (4, (1, [2, 3])), - (8, (1, [0, 7])), - (8, (1, [3, 5])), - (8, (1, [0, 3, 5, 7])), - (8, (1, [0, 1, 2, 3])), - (8, (1, [0, 1, 6, 7])), - (8, (1j, [0, 3, 5, 7])), - (8, (-1j, [0, 1, 2, 3])), - (8, (np.sqrt(0.5) + np.sqrt(0.5) * 1j, [0, 1, 6, 7])), - (8, (1, [0, 1, 2, 3, 5, 6, 7])), - (8, (1, [0, 1, 2, 3, 4, 5, 6, 7])), -]) + state = sim.simulate( + circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector + + cirq.testing.assert_allclose_up_to_global_phase(state, expected_state, atol=1e-8) + + +@pytest.mark.parametrize( + 'n, initial', + [ + (2, (1, [0, 1])), + (4, (1, [0, 1])), + (4, (1, [1, 2])), + (4, (1, [2, 3])), + (8, (1, [0, 7])), + (8, (1, [3, 5])), + (8, (1, [0, 3, 5, 7])), + (8, (1, [0, 1, 2, 3])), + (8, (1, [0, 1, 6, 7])), + (8, (1j, [0, 3, 5, 7])), + (8, (-1j, [0, 1, 2, 3])), + (8, (np.sqrt(0.5) + np.sqrt(0.5) * 1j, [0, 1, 6, 7])), + (8, (1, [0, 1, 2, 3, 5, 6, 7])), + (8, (1, [0, 1, 2, 3, 4, 5, 6, 7])), + ], +) def test_ffft_multi_fermionic_mode(n, initial): sim = cirq.Simulator(dtype=np.complex128) initial_state = _multi_fermionic_mode_base_state(n, *initial) @@ -295,21 +327,24 @@ def test_ffft_multi_fermionic_mode(n, initial): qubits = LineQubit.range(n) circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) - state = sim.simulate(circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector + state = sim.simulate( + circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector assert np.allclose(state, expected_state, rtol=0.0) -@pytest.mark.parametrize('n, initial', [ - (3, (1, [0, 1])), - (3, (1, [0, 1])), - (5, (1, [0, 3, 4])), - (6, (1, [0, 1, 2, 3])), - (7, (1, [0, 1, 5, 6])), - (9, (1, [2, 4, 6])), -]) +@pytest.mark.parametrize( + 'n, initial', + [ + (3, (1, [0, 1])), + (3, (1, [0, 1])), + (5, (1, [0, 3, 4])), + (6, (1, [0, 1, 2, 3])), + (7, (1, [0, 1, 5, 6])), + (9, (1, [2, 4, 6])), + ], +) def test_ffft_multi_fermionic_mode_non_power_of_2(n, initial): initial_state = _multi_fermionic_mode_base_state(n, *initial) expected_state = _fourier_transform_multi_fermionic_mode(n, *initial) @@ -317,13 +352,11 @@ def test_ffft_multi_fermionic_mode_non_power_of_2(n, initial): sim = cirq.Simulator(dtype=np.complex128) circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) - state = sim.simulate(circuit, - initial_state=initial_state, - qubit_order=qubits).final_state_vector + state = sim.simulate( + circuit, initial_state=initial_state, qubit_order=qubits + ).final_state_vector - cirq.testing.assert_allclose_up_to_global_phase(state, - expected_state, - atol=1e-8) + cirq.testing.assert_allclose_up_to_global_phase(state, expected_state, atol=1e-8) def test_ffft_text_diagram(): @@ -331,7 +364,9 @@ def test_ffft_text_diagram(): circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) - assert circuit.to_text_diagram(transpose=True) == """ + assert ( + circuit.to_text_diagram(transpose=True) + == """ 0 1 2 3 4 5 6 7 │ │ │ │ │ │ │ │ 0↦0─1↦4───2↦1─3↦5───4↦2─5↦6───6↦3─7↦7 @@ -357,6 +392,7 @@ def test_ffft_text_diagram(): 0↦0─1↦4───2↦1─3↦5───4↦2─5↦6───6↦3─7↦7 │ │ │ │ │ │ │ │ """.strip() + ) def test_ffft_fails_without_qubits(): @@ -366,40 +402,32 @@ def test_ffft_fails_without_qubits(): @pytest.mark.parametrize('size', [1, 2, 3, 4, 5, 6, 7, 8]) def test_ffft_equal_to_bogoliubov(size): - def fourier_transform_matrix(): root_of_unity = np.exp(-2j * np.pi / size) - return np.array([[root_of_unity**(j * k) - for k in range(size)] - for j in range(size)]) / np.sqrt(size) + return np.array( + [[root_of_unity ** (j * k) for k in range(size)] for j in range(size)] + ) / np.sqrt(size) qubits = LineQubit.range(size) - ffft_circuit = cirq.Circuit(ffft(qubits), - strategy=cirq.InsertStrategy.EARLIEST) + ffft_circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) ffft_matrix = ffft_circuit.unitary(qubits_that_should_be_present=qubits) - bogoliubov_circuit = cirq.Circuit(bogoliubov_transform( - qubits, fourier_transform_matrix()), - strategy=cirq.InsertStrategy.EARLIEST) - bogoliubov_matrix = bogoliubov_circuit.unitary( - qubits_that_should_be_present=qubits) + bogoliubov_circuit = cirq.Circuit( + bogoliubov_transform(qubits, fourier_transform_matrix()), + strategy=cirq.InsertStrategy.EARLIEST, + ) + bogoliubov_matrix = bogoliubov_circuit.unitary(qubits_that_should_be_present=qubits) - cirq.testing.assert_allclose_up_to_global_phase(ffft_matrix, - bogoliubov_matrix, - atol=1e-8) + cirq.testing.assert_allclose_up_to_global_phase(ffft_matrix, bogoliubov_matrix, atol=1e-8) @pytest.mark.parametrize('size', [1, 2, 3, 4, 5, 6, 7, 8]) def test_ffft_inverse(size): - qubits = LineQubit.range(size) - ffft_circuit = cirq.Circuit(ffft(qubits), - strategy=cirq.InsertStrategy.EARLIEST) + ffft_circuit = cirq.Circuit(ffft(qubits), strategy=cirq.InsertStrategy.EARLIEST) ffft_circuit.append(cirq.inverse(ffft(qubits))) ffft_matrix = ffft_circuit.unitary(qubits_that_should_be_present=qubits) - cirq.testing.assert_allclose_up_to_global_phase(ffft_matrix, - np.identity(1 << size), - atol=1e-8) + cirq.testing.assert_allclose_up_to_global_phase(ffft_matrix, np.identity(1 << size), atol=1e-8) diff --git a/src/openfermion/circuits/primitives/optimal_givens_decomposition.py b/src/openfermion/circuits/primitives/optimal_givens_decomposition.py index 35a6abd13..6692f9b01 100644 --- a/src/openfermion/circuits/primitives/optimal_givens_decomposition.py +++ b/src/openfermion/circuits/primitives/optimal_givens_decomposition.py @@ -33,9 +33,9 @@ class GivensMatrixError(Exception): pass -def optimal_givens_decomposition(qubits: Sequence[cirq.Qid], - unitary: numpy.ndarray - ) -> Iterable[cirq.Operation]: +def optimal_givens_decomposition( + qubits: Sequence[cirq.Qid], unitary: numpy.ndarray +) -> Iterable[cirq.Operation]: r""" Implement a circuit that provides the unitary that is generated by single-particle fermion generators @@ -60,57 +60,47 @@ def optimal_givens_decomposition(qubits: Sequence[cirq.Qid], # eliminate U[N - j, i - j] by mixing U[N - j, i - j], # U[N - j, i - j - 1] by right multiplication # of a givens rotation matrix in column [i - j, i - j + 1] - gmat = givens_matrix_elements(unitary[N - j - 1, i - j - 1], - unitary[N - j - 1, i - j - 1 + 1], - which='left') + gmat = givens_matrix_elements( + unitary[N - j - 1, i - j - 1], unitary[N - j - 1, i - j - 1 + 1], which='left' + ) right_rotations.append((gmat.T, (i - j - 1, i - j))) - givens_rotate(unitary, - gmat.conj(), - i - j - 1, - i - j, - which='col') + givens_rotate(unitary, gmat.conj(), i - j - 1, i - j, which='col') else: for j in range(1, i + 1): # elimination of U[N + j - i, j] by mixing U[N + j - i, j] and # U[N + j - i - 1, j] by left multiplication # of a givens rotation that rotates row space # [N + j - i - 1, N + j - i - gmat = givens_matrix_elements(unitary[N + j - i - 1 - 1, j - 1], - unitary[N + j - i - 1, j - 1], - which='right') + gmat = givens_matrix_elements( + unitary[N + j - i - 1 - 1, j - 1], unitary[N + j - i - 1, j - 1], which='right' + ) left_rotations.append((gmat, (N + j - i - 2, N + j - i - 1))) - givens_rotate(unitary, - gmat, - N + j - i - 2, - N + j - i - 1, - which='row') + givens_rotate(unitary, gmat, N + j - i - 2, N + j - i - 1, which='row') new_left_rotations = [] - for (left_gmat, (i, j)) in reversed(left_rotations): + for left_gmat, (i, j) in reversed(left_rotations): phase_matrix = numpy.diag([unitary[i, i], unitary[j, j]]) matrix_to_decompose = left_gmat.conj().T.dot(phase_matrix) - new_givens_matrix = givens_matrix_elements(matrix_to_decompose[1, 0], - matrix_to_decompose[1, 1], - which='left') + new_givens_matrix = givens_matrix_elements( + matrix_to_decompose[1, 0], matrix_to_decompose[1, 1], which='left' + ) new_phase_matrix = matrix_to_decompose.dot(new_givens_matrix.T) # check if T_{m,n}^{-1}D = D T. # coverage: ignore - if not numpy.allclose(new_phase_matrix.dot(new_givens_matrix.conj()), - matrix_to_decompose): - raise GivensTranspositionError("Failed to shift the phase matrix " - "from right to left") + if not numpy.allclose(new_phase_matrix.dot(new_givens_matrix.conj()), matrix_to_decompose): + raise GivensTranspositionError("Failed to shift the phase matrix " "from right to left") # coverage: ignore - unitary[i, i], unitary[j, j] = new_phase_matrix[0, 0], new_phase_matrix[ - 1, 1] + unitary[i, i], unitary[j, j] = new_phase_matrix[0, 0], new_phase_matrix[1, 1] new_left_rotations.append((new_givens_matrix.conj(), (i, j))) phases = numpy.diag(unitary) rotations = [] ordered_rotations = [] - for (gmat, (i, j)) in list(reversed(new_left_rotations)) + list( - map(lambda x: (x[0].conj().T, x[1]), reversed(right_rotations))): + for gmat, (i, j) in list(reversed(new_left_rotations)) + list( + map(lambda x: (x[0].conj().T, x[1]), reversed(right_rotations)) + ): ordered_rotations.append((gmat, (i, j))) # if this throws the impossible has happened @@ -118,11 +108,13 @@ def optimal_givens_decomposition(qubits: Sequence[cirq.Qid], if not numpy.isclose(gmat[0, 0].imag, 0.0): raise GivensMatrixError( "Givens matrix does not obey our convention that all elements " - "in the first column are real") + "in the first column are real" + ) if not numpy.isclose(gmat[1, 0].imag, 0.0): raise GivensMatrixError( "Givens matrix does not obey our convention that all elements " - "in the first column are real") + "in the first column are real" + ) # coverage: ignore theta = numpy.arcsin(numpy.real(gmat[1, 0])) @@ -132,9 +124,9 @@ def optimal_givens_decomposition(qubits: Sequence[cirq.Qid], for op in reversed(rotations): i, j, theta, phi = cast(Tuple[int, int, float, float], op) if not numpy.isclose(phi, 0.0): - yield cirq.Z(qubits[j])**(phi / numpy.pi) + yield cirq.Z(qubits[j]) ** (phi / numpy.pi) yield Ryxxy(-theta).on(qubits[i], qubits[j]) for idx, phase in enumerate(phases): - yield cirq.Z(qubits[idx])**(numpy.angle(phase) / numpy.pi) + yield cirq.Z(qubits[idx]) ** (numpy.angle(phase) / numpy.pi) diff --git a/src/openfermion/circuits/primitives/optimal_givens_decomposition_test.py b/src/openfermion/circuits/primitives/optimal_givens_decomposition_test.py index fb3744acd..f386cf2d7 100644 --- a/src/openfermion/circuits/primitives/optimal_givens_decomposition_test.py +++ b/src/openfermion/circuits/primitives/optimal_givens_decomposition_test.py @@ -14,13 +14,13 @@ import scipy import cirq -from openfermion.linalg import (givens_matrix_elements, givens_rotate, - get_sparse_operator) +from openfermion.linalg import givens_matrix_elements, givens_rotate, get_sparse_operator from openfermion.ops import QubitOperator, FermionOperator from openfermion.transforms import jordan_wigner -from openfermion.circuits.primitives.optimal_givens_decomposition import \ - optimal_givens_decomposition +from openfermion.circuits.primitives.optimal_givens_decomposition import ( + optimal_givens_decomposition, +) def test_givens_inverse(): @@ -40,10 +40,8 @@ def test_givens_inverse(): b = numpy.random.random() + 1j * numpy.random.random() ab_rotation = givens_matrix_elements(a, b, which='right') - assert numpy.allclose(ab_rotation.dot(numpy.conj(ab_rotation).T), - numpy.eye(2)) - assert numpy.allclose( - numpy.conj(ab_rotation).T.dot(ab_rotation), numpy.eye(2)) + assert numpy.allclose(ab_rotation.dot(numpy.conj(ab_rotation).T), numpy.eye(2)) + assert numpy.allclose(numpy.conj(ab_rotation).T.dot(ab_rotation), numpy.eye(2)) def test_row_eliminate(): @@ -51,8 +49,7 @@ def test_row_eliminate(): Test elemination of element in U[i, j] by rotating in i-1 and i. """ dim = 3 - u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random( - (dim, dim)) + u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random((dim, dim)) u_generator = u_generator - numpy.conj(u_generator).T # make sure the generator is actually antihermitian @@ -99,8 +96,7 @@ def test_col_eliminate(): inverse givens """ dim = 3 - u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random( - (dim, dim)) + u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random((dim, dim)) u_generator = u_generator - numpy.conj(u_generator).T # make sure the generator is actually antihermitian assert numpy.allclose(-1 * u_generator, numpy.conj(u_generator).T) @@ -182,8 +178,7 @@ def test_front_back_iteration(): def test_circuit_generation_and_accuracy(): for dim in range(2, 10): qubits = cirq.LineQubit.range(dim) - u_generator = numpy.random.random( - (dim, dim)) + 1j * numpy.random.random((dim, dim)) + u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random((dim, dim)) u_generator = u_generator - numpy.conj(u_generator).T assert numpy.allclose(-1 * u_generator, numpy.conj(u_generator).T) @@ -193,17 +188,15 @@ def test_circuit_generation_and_accuracy(): fermion_generator = QubitOperator(()) * 0.0 for i, j in product(range(dim), repeat=2): - fermion_generator += jordan_wigner( - FermionOperator(((i, 1), (j, 0)), u_generator[i, j])) + fermion_generator += jordan_wigner(FermionOperator(((i, 1), (j, 0)), u_generator[i, j])) - true_unitary = scipy.linalg.expm( - get_sparse_operator(fermion_generator).toarray()) - assert numpy.allclose(true_unitary.conj().T.dot(true_unitary), - numpy.eye(2**dim, dtype=complex)) + true_unitary = scipy.linalg.expm(get_sparse_operator(fermion_generator).toarray()) + assert numpy.allclose( + true_unitary.conj().T.dot(true_unitary), numpy.eye(2**dim, dtype=complex) + ) test_unitary = cirq.unitary(circuit) - assert numpy.isclose( - abs(numpy.trace(true_unitary.conj().T.dot(test_unitary))), 2**dim) + assert numpy.isclose(abs(numpy.trace(true_unitary.conj().T.dot(test_unitary))), 2**dim) def test_circuit_generation_state(): @@ -213,21 +206,22 @@ def test_circuit_generation_state(): simulator = cirq.Simulator() circuit = cirq.Circuit() qubits = cirq.LineQubit.range(4) - circuit.append([ - cirq.X(qubits[0]), - cirq.X(qubits[1]), - cirq.X(qubits[1]), - cirq.X(qubits[2]), - cirq.X(qubits[3]), - cirq.X(qubits[3]) - ]) # alpha-spins are first then beta spins + circuit.append( + [ + cirq.X(qubits[0]), + cirq.X(qubits[1]), + cirq.X(qubits[1]), + cirq.X(qubits[2]), + cirq.X(qubits[3]), + cirq.X(qubits[3]), + ] + ) # alpha-spins are first then beta spins wavefunction = numpy.zeros((2**4, 1), dtype=complex) wavefunction[10, 0] = 1.0 dim = 2 - u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random( - (dim, dim)) + u_generator = numpy.random.random((dim, dim)) + 1j * numpy.random.random((dim, dim)) u_generator = u_generator - numpy.conj(u_generator).T unitary = scipy.linalg.expm(u_generator) @@ -235,11 +229,9 @@ def test_circuit_generation_state(): fermion_generator = QubitOperator(()) * 0.0 for i, j in product(range(dim), repeat=2): - fermion_generator += jordan_wigner( - FermionOperator(((i, 1), (j, 0)), u_generator[i, j])) + fermion_generator += jordan_wigner(FermionOperator(((i, 1), (j, 0)), u_generator[i, j])) - test_unitary = scipy.linalg.expm( - get_sparse_operator(fermion_generator, 4).toarray()) + test_unitary = scipy.linalg.expm(get_sparse_operator(fermion_generator, 4).toarray()) test_final_state = test_unitary.dot(wavefunction) cirq_wf = simulator.simulate(circuit).final_state_vector diff --git a/src/openfermion/circuits/primitives/state_preparation.py b/src/openfermion/circuits/primitives/state_preparation.py index 53452f412..06aaf278c 100644 --- a/src/openfermion/circuits/primitives/state_preparation.py +++ b/src/openfermion/circuits/primitives/state_preparation.py @@ -11,8 +11,7 @@ # limitations under the License. """Operations for preparing useful quantum states.""" -from typing import (Iterable, Optional, Sequence, Set, TYPE_CHECKING, Tuple, - Union, cast) +from typing import Iterable, Optional, Sequence, Set, TYPE_CHECKING, Tuple, Union, cast import numpy @@ -20,18 +19,20 @@ from openfermion.circuits.gates import Ryxxy from openfermion.circuits.slater_determinants import ( - gaussian_state_preparation_circuit, slater_determinant_preparation_circuit) + gaussian_state_preparation_circuit, + slater_determinant_preparation_circuit, +) if TYPE_CHECKING: import openfermion def prepare_gaussian_state( - qubits: Sequence[cirq.Qid], - quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', - occupied_orbitals: Optional[ - Union[Sequence[int], Tuple[Sequence[int], Sequence[int]]]] = None, - initial_state: Union[int, Sequence[int]] = 0) -> cirq.OP_TREE: + qubits: Sequence[cirq.Qid], + quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', + occupied_orbitals: Optional[Union[Sequence[int], Tuple[Sequence[int], Sequence[int]]]] = None, + initial_state: Union[int, Sequence[int]] = 0, +) -> cirq.OP_TREE: """Prepare a fermionic Gaussian state from a computational basis state. A fermionic Gaussian state is an eigenstate of a quadratic Hamiltonian. If @@ -69,60 +70,60 @@ def prepare_gaussian_state( if not occupied_orbitals or isinstance(occupied_orbitals[0], int): # Generic occupied_orbitals = cast(Sequence[int], occupied_orbitals) - yield _generic_gaussian_circuit(qubits, quadratic_hamiltonian, - occupied_orbitals, initial_state) + yield _generic_gaussian_circuit( + qubits, quadratic_hamiltonian, occupied_orbitals, initial_state + ) else: # Spin symmetry - occupied_orbitals = cast(Tuple[Sequence[int], Sequence[int]], - occupied_orbitals) - yield _spin_symmetric_gaussian_circuit(qubits, quadratic_hamiltonian, - occupied_orbitals, initial_state) + occupied_orbitals = cast(Tuple[Sequence[int], Sequence[int]], occupied_orbitals) + yield _spin_symmetric_gaussian_circuit( + qubits, quadratic_hamiltonian, occupied_orbitals, initial_state + ) def _generic_gaussian_circuit( - qubits: Sequence[cirq.Qid], - quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', - occupied_orbitals: Optional[Sequence[int]], - initial_state: Union[int, Sequence[int]]) -> cirq.OP_TREE: - + qubits: Sequence[cirq.Qid], + quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', + occupied_orbitals: Optional[Sequence[int]], + initial_state: Union[int, Sequence[int]], +) -> cirq.OP_TREE: n_qubits = len(qubits) - circuit_description, start_orbitals = (gaussian_state_preparation_circuit( - quadratic_hamiltonian, occupied_orbitals)) + circuit_description, start_orbitals = gaussian_state_preparation_circuit( + quadratic_hamiltonian, occupied_orbitals + ) if isinstance(initial_state, int): - initially_occupied_orbitals = _occupied_orbitals( - initial_state, n_qubits) + initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: initially_occupied_orbitals = initial_state # type: ignore # Flip bits so that the correct starting orbitals are occupied - yield (cirq.X(qubits[j]) - for j in range(n_qubits) - if (j in initially_occupied_orbitals) != (j in start_orbitals)) + yield ( + cirq.X(qubits[j]) + for j in range(n_qubits) + if (j in initially_occupied_orbitals) != (j in start_orbitals) + ) - yield _ops_from_givens_rotations_circuit_description( - qubits, circuit_description) + yield _ops_from_givens_rotations_circuit_description(qubits, circuit_description) def _spin_symmetric_gaussian_circuit( - qubits: Sequence[cirq.Qid], - quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', - occupied_orbitals: Tuple[Sequence[int], Sequence[int]], - initial_state: Union[int, Sequence[int]]) -> cirq.OP_TREE: - + qubits: Sequence[cirq.Qid], + quadratic_hamiltonian: 'openfermion.QuadraticHamiltonian', + occupied_orbitals: Tuple[Sequence[int], Sequence[int]], + initial_state: Union[int, Sequence[int]], +) -> cirq.OP_TREE: n_qubits = len(qubits) if isinstance(initial_state, int): - initially_occupied_orbitals = _occupied_orbitals( - initial_state, n_qubits) + initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: initially_occupied_orbitals = initial_state # type: ignore for spin_sector in range(2): - circuit_description, start_orbitals = ( - gaussian_state_preparation_circuit(quadratic_hamiltonian, - occupied_orbitals[spin_sector], - spin_sector=spin_sector)) + circuit_description, start_orbitals = gaussian_state_preparation_circuit( + quadratic_hamiltonian, occupied_orbitals[spin_sector], spin_sector=spin_sector + ) def index_map(i): return i + spin_sector * (n_qubits // 2) @@ -131,19 +132,21 @@ def index_map(i): spin_qubits = [qubits[i] for i in spin_indices] # Flip bits so that the correct starting orbitals are occupied - yield (cirq.X(spin_qubits[j]) - for j in range(n_qubits // 2) - if (index_map(j) in initially_occupied_orbitals) != ( - index_map(j) in [index_map(k) for k in start_orbitals])) + yield ( + cirq.X(spin_qubits[j]) + for j in range(n_qubits // 2) + if (index_map(j) in initially_occupied_orbitals) + != (index_map(j) in [index_map(k) for k in start_orbitals]) + ) - yield _ops_from_givens_rotations_circuit_description( - spin_qubits, circuit_description) + yield _ops_from_givens_rotations_circuit_description(spin_qubits, circuit_description) -def prepare_slater_determinant(qubits: Sequence[cirq.Qid], - slater_determinant_matrix: numpy.ndarray, - initial_state: Union[int, Sequence[int]] = 0 - ) -> cirq.OP_TREE: +def prepare_slater_determinant( + qubits: Sequence[cirq.Qid], + slater_determinant_matrix: numpy.ndarray, + initial_state: Union[int, Sequence[int]] = 0, +) -> cirq.OP_TREE: r"""Prepare a Slater determinant from a computational basis state. A Slater determinant is described by an $\eta \times N$ matrix @@ -181,23 +184,22 @@ def prepare_slater_determinant(qubits: Sequence[cirq.Qid], Default is 0, the all zeros state. """ n_qubits = len(qubits) - circuit_description = slater_determinant_preparation_circuit( - slater_determinant_matrix) + circuit_description = slater_determinant_preparation_circuit(slater_determinant_matrix) n_occupied = slater_determinant_matrix.shape[0] if isinstance(initial_state, int): - initially_occupied_orbitals = _occupied_orbitals( - initial_state, n_qubits) + initially_occupied_orbitals = _occupied_orbitals(initial_state, n_qubits) else: initially_occupied_orbitals = initial_state # type: ignore # Flip bits so that the first n_occupied are 1 and the rest 0 - yield (cirq.X(qubits[j]) - for j in range(n_qubits) - if (j < n_occupied) != (j in initially_occupied_orbitals)) + yield ( + cirq.X(qubits[j]) + for j in range(n_qubits) + if (j < n_occupied) != (j in initially_occupied_orbitals) + ) - yield _ops_from_givens_rotations_circuit_description( - qubits, circuit_description) + yield _ops_from_givens_rotations_circuit_description(qubits, circuit_description) def _occupied_orbitals(computational_basis_state: int, n_qubits) -> Set[int]: @@ -208,8 +210,9 @@ def _occupied_orbitals(computational_basis_state: int, n_qubits) -> Set[int]: def _ops_from_givens_rotations_circuit_description( - qubits: Sequence[cirq.Qid], circuit_description: Iterable[Iterable[ - Union[str, Tuple[int, int, float, float]]]]) -> cirq.OP_TREE: + qubits: Sequence[cirq.Qid], + circuit_description: Iterable[Iterable[Union[str, Tuple[int, int, float, float]]]], +) -> cirq.OP_TREE: """Yield operations from a Givens rotations circuit obtained from OpenFermion. """ @@ -220,4 +223,4 @@ def _ops_from_givens_rotations_circuit_description( else: i, j, theta, phi = cast(Tuple[int, int, float, float], op) yield Ryxxy(theta).on(qubits[i], qubits[j]) - yield cirq.Z(qubits[j])**(phi / numpy.pi) + yield cirq.Z(qubits[j]) ** (phi / numpy.pi) diff --git a/src/openfermion/circuits/primitives/state_preparation_test.py b/src/openfermion/circuits/primitives/state_preparation_test.py index 3254cb8ae..e9bc83a42 100644 --- a/src/openfermion/circuits/primitives/state_preparation_test.py +++ b/src/openfermion/circuits/primitives/state_preparation_test.py @@ -18,143 +18,136 @@ import openfermion from openfermion.linalg import get_sparse_operator from openfermion.circuits.primitives.state_preparation import ( - prepare_slater_determinant, prepare_gaussian_state) + prepare_slater_determinant, + prepare_gaussian_state, +) from openfermion.testing import random_quadratic_hamiltonian @pytest.mark.parametrize( 'n_qubits, conserves_particle_number, occupied_orbitals, initial_state', - [(4, True, None, 0b0010), (4, False, None, 0b1001), (5, True, None, 0), - (5, False, None, 0b10101), (5, True, range(4), 0), - (5, False, (0, 2, 3), [1, 2, 3, 4])]) -def test_prepare_gaussian_state(n_qubits, - conserves_particle_number, - occupied_orbitals, - initial_state, - atol=1e-5): - + [ + (4, True, None, 0b0010), + (4, False, None, 0b1001), + (5, True, None, 0), + (5, False, None, 0b10101), + (5, True, range(4), 0), + (5, False, (0, 2, 3), [1, 2, 3, 4]), + ], +) +def test_prepare_gaussian_state( + n_qubits, conserves_particle_number, occupied_orbitals, initial_state, atol=1e-5 +): qubits = LineQubit.range(n_qubits) # Initialize a random quadratic Hamiltonian - quad_ham = random_quadratic_hamiltonian(n_qubits, - conserves_particle_number, - real=False) + quad_ham = random_quadratic_hamiltonian(n_qubits, conserves_particle_number, real=False) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the energy of the desired state if occupied_orbitals is None: energy = quad_ham.ground_energy() else: - orbital_energies, _, constant = ( - quad_ham.diagonalizing_bogoliubov_transform()) + orbital_energies, _, constant = quad_ham.diagonalizing_bogoliubov_transform() energy = sum(orbital_energies[i] for i in occupied_orbitals) + constant # Get the state using a circuit simulation circuit = cirq.Circuit( - prepare_gaussian_state(qubits, - quad_ham, - occupied_orbitals, - initial_state=initial_state)) + prepare_gaussian_state(qubits, quad_ham, occupied_orbitals, initial_state=initial_state) + ) if isinstance(initial_state, list): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) state = circuit.final_state_vector(initial_state=initial_state) # Check that the result is an eigenstate with the correct eigenvalue - numpy.testing.assert_allclose(quad_ham_sparse.dot(state), - energy * state, - atol=atol) + numpy.testing.assert_allclose(quad_ham_sparse.dot(state), energy * state, atol=atol) @pytest.mark.parametrize( - 'n_spatial_orbitals, conserves_particle_number, occupied_orbitals, ' - 'initial_state', [(4, True, [range(1), range(1)], 0b00100100), - (5, True, [range(2), range(1)], list(range(3))), - (5, True, [[0, 2], [1, 3]], 0)]) -def test_prepare_gaussian_state_with_spin_symmetry(n_spatial_orbitals, - conserves_particle_number, - occupied_orbitals, - initial_state, - atol=1e-5): - + 'n_spatial_orbitals, conserves_particle_number, occupied_orbitals, ' 'initial_state', + [ + (4, True, [range(1), range(1)], 0b00100100), + (5, True, [range(2), range(1)], list(range(3))), + (5, True, [[0, 2], [1, 3]], 0), + ], +) +def test_prepare_gaussian_state_with_spin_symmetry( + n_spatial_orbitals, conserves_particle_number, occupied_orbitals, initial_state, atol=1e-5 +): n_qubits = 2 * n_spatial_orbitals qubits = LineQubit.range(n_qubits) # Initialize a random quadratic Hamiltonian - quad_ham = random_quadratic_hamiltonian(n_spatial_orbitals, - conserves_particle_number, - real=True, - expand_spin=True, - seed=639) + quad_ham = random_quadratic_hamiltonian( + n_spatial_orbitals, conserves_particle_number, real=True, expand_spin=True, seed=639 + ) # Reorder the Hamiltonian and get sparse matrix quad_ham = openfermion.get_quadratic_hamiltonian( - openfermion.reorder(openfermion.get_fermion_operator(quad_ham), - openfermion.up_then_down)) + openfermion.reorder(openfermion.get_fermion_operator(quad_ham), openfermion.up_then_down) + ) quad_ham_sparse = get_sparse_operator(quad_ham) # Compute the energy of the desired state energy = 0.0 for spin_sector in range(2): - orbital_energies, _, _ = (quad_ham.diagonalizing_bogoliubov_transform( - spin_sector=spin_sector)) - energy += sum( - orbital_energies[i] for i in occupied_orbitals[spin_sector]) + orbital_energies, _, _ = quad_ham.diagonalizing_bogoliubov_transform( + spin_sector=spin_sector + ) + energy += sum(orbital_energies[i] for i in occupied_orbitals[spin_sector]) energy += quad_ham.constant # Get the state using a circuit simulation circuit = cirq.Circuit( - prepare_gaussian_state(qubits, - quad_ham, - occupied_orbitals, - initial_state=initial_state)) + prepare_gaussian_state(qubits, quad_ham, occupied_orbitals, initial_state=initial_state) + ) if isinstance(initial_state, list): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) state = circuit.final_state_vector(initial_state=initial_state) # Check that the result is an eigenstate with the correct eigenvalue - numpy.testing.assert_allclose(quad_ham_sparse.dot(state), - energy * state, - atol=atol) + numpy.testing.assert_allclose(quad_ham_sparse.dot(state), energy * state, atol=atol) @pytest.mark.parametrize( - 'slater_determinant_matrix, correct_state, initial_state', [ - (numpy.array([[1, 1]]) / numpy.sqrt(2), - numpy.array([0, 1, 1, 0]) / numpy.sqrt(2), 0), - (numpy.array([[1, 1j]]) / numpy.sqrt(2), - numpy.array([0, 1j, 1, 0]) / numpy.sqrt(2), 0b01), - (numpy.array( - [[1, 1, 1], - [1, numpy.exp(2j * numpy.pi / 3), - numpy.exp(4j * numpy.pi / 3)]]) / numpy.sqrt(3), - numpy.array([ - 0, 0, 0, - numpy.exp(2j * numpy.pi / 3), 0, 1 + numpy.exp(2j * numpy.pi / 3), - 1, 0 - ]) / numpy.sqrt(3), 0), - (numpy.array( - [[1, 1, 1], - [1, numpy.exp(2j * numpy.pi / 3), - numpy.exp(4j * numpy.pi / 3)]]) / numpy.sqrt(3), - numpy.array([ - 0, 0, 0, - numpy.exp(2j * numpy.pi / 3), 0, 1 + numpy.exp(2j * numpy.pi / 3), - 1, 0 - ]) / numpy.sqrt(3), [0, 2]), - ]) -def test_prepare_slater_determinant(slater_determinant_matrix, - correct_state, - initial_state, - atol=1e-7): - + 'slater_determinant_matrix, correct_state, initial_state', + [ + (numpy.array([[1, 1]]) / numpy.sqrt(2), numpy.array([0, 1, 1, 0]) / numpy.sqrt(2), 0), + (numpy.array([[1, 1j]]) / numpy.sqrt(2), numpy.array([0, 1j, 1, 0]) / numpy.sqrt(2), 0b01), + ( + numpy.array( + [[1, 1, 1], [1, numpy.exp(2j * numpy.pi / 3), numpy.exp(4j * numpy.pi / 3)]] + ) + / numpy.sqrt(3), + numpy.array( + [0, 0, 0, numpy.exp(2j * numpy.pi / 3), 0, 1 + numpy.exp(2j * numpy.pi / 3), 1, 0] + ) + / numpy.sqrt(3), + 0, + ), + ( + numpy.array( + [[1, 1, 1], [1, numpy.exp(2j * numpy.pi / 3), numpy.exp(4j * numpy.pi / 3)]] + ) + / numpy.sqrt(3), + numpy.array( + [0, 0, 0, numpy.exp(2j * numpy.pi / 3), 0, 1 + numpy.exp(2j * numpy.pi / 3), 1, 0] + ) + / numpy.sqrt(3), + [0, 2], + ), + ], +) +def test_prepare_slater_determinant( + slater_determinant_matrix, correct_state, initial_state, atol=1e-7 +): n_qubits = slater_determinant_matrix.shape[1] qubits = LineQubit.range(n_qubits) circuit = cirq.Circuit( - prepare_slater_determinant(qubits, - slater_determinant_matrix, - initial_state=initial_state)) + prepare_slater_determinant(qubits, slater_determinant_matrix, initial_state=initial_state) + ) if isinstance(initial_state, list): initial_state = sum(1 << (n_qubits - 1 - i) for i in initial_state) state = circuit.final_state_vector(initial_state=initial_state) diff --git a/src/openfermion/circuits/primitives/swap_network.py b/src/openfermion/circuits/primitives/swap_network.py index 962d45039..05ec2999b 100644 --- a/src/openfermion/circuits/primitives/swap_network.py +++ b/src/openfermion/circuits/primitives/swap_network.py @@ -19,11 +19,13 @@ def swap_network( - qubits: Sequence[cirq.Qid], - operation: Callable[[int, int, cirq.Qid, cirq.Qid], cirq. - OP_TREE] = lambda p, q, p_qubit, q_qubit: (), - fermionic: bool = False, - offset: bool = False) -> List[cirq.Operation]: + qubits: Sequence[cirq.Qid], + operation: Callable[ + [int, int, cirq.Qid, cirq.Qid], cirq.OP_TREE + ] = lambda p, q, p_qubit, q_qubit: (), + fermionic: bool = False, + offset: bool = False, +) -> List[cirq.Operation]: """Apply operations to pairs of qubits or modes using a swap network. This is used for applying operations between arbitrary pairs of qubits or @@ -124,13 +126,11 @@ def swap_network( for layer_num in range(n_qubits): lowest_active_qubit = (layer_num + offset) % 2 - active_pairs = ( - (i, i + 1) for i in range(lowest_active_qubit, n_qubits - 1, 2)) + active_pairs = ((i, i + 1) for i in range(lowest_active_qubit, n_qubits - 1, 2)) for i, j in active_pairs: p, q = order[i], order[j] extra_ops = operation(p, q, qubits[i], qubits[j]) - result.extend( - cast(Iterable[cirq.Operation], cirq.flatten_op_tree(extra_ops))) + result.extend(cast(Iterable[cirq.Operation], cirq.flatten_op_tree(extra_ops))) result.append(swap_gate(qubits[i], qubits[j])) order[i], order[j] = q, p diff --git a/src/openfermion/circuits/primitives/swap_network_test.py b/src/openfermion/circuits/primitives/swap_network_test.py index 168ee6f97..274929373 100644 --- a/src/openfermion/circuits/primitives/swap_network_test.py +++ b/src/openfermion/circuits/primitives/swap_network_test.py @@ -19,10 +19,10 @@ def test_swap_network(): n_qubits = 4 qubits = cirq.LineQubit.range(n_qubits) - circuit = cirq.Circuit( - swap_network(qubits, lambda i, j, q0, q1: cirq.ISWAP(q0, q1)**-1)) + circuit = cirq.Circuit(swap_network(qubits, lambda i, j, q0, q1: cirq.ISWAP(q0, q1) ** -1)) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ 0: ───iSwap──────×──────────────────iSwap──────×────────────────── │ │ │ │ 1: ───iSwap^-1───×───iSwap──────×───iSwap^-1───×───iSwap──────×─── @@ -30,15 +30,17 @@ def test_swap_network(): 2: ───iSwap──────×───iSwap^-1───×───iSwap──────×───iSwap^-1───×─── │ │ │ │ 3: ───iSwap^-1───×──────────────────iSwap^-1───×────────────────── -""") +""", + ) circuit = cirq.Circuit( - swap_network(qubits, - lambda i, j, q0, q1: cirq.ISWAP(q0, q1)**-1, - fermionic=True, - offset=True)) - cirq.testing.assert_has_diagram(circuit, - """ + swap_network( + qubits, lambda i, j, q0, q1: cirq.ISWAP(q0, q1) ** -1, fermionic=True, offset=True + ) + ) + cirq.testing.assert_has_diagram( + circuit, + """ 0 1 2 3 │ │ │ │ │ iSwap────iSwap^-1 │ @@ -58,16 +60,19 @@ def test_swap_network(): ×ᶠ────×ᶠ ×ᶠ───────×ᶠ │ │ │ │ """, - transpose=True) + transpose=True, + ) n_qubits = 5 qubits = cirq.LineQubit.range(n_qubits) - circuit = cirq.Circuit(swap_network( - qubits, lambda i, j, q0, q1: (), fermionic=True), - strategy=cirq.InsertStrategy.EARLIEST) + circuit = cirq.Circuit( + swap_network(qubits, lambda i, j, q0, q1: (), fermionic=True), + strategy=cirq.InsertStrategy.EARLIEST, + ) cirq.testing.assert_has_diagram( - circuit, """ + circuit, + """ 0: ───×ᶠ────────×ᶠ────────×ᶠ─── │ │ │ 1: ───×ᶠ───×ᶠ───×ᶠ───×ᶠ───×ᶠ─── @@ -77,13 +82,16 @@ def test_swap_network(): 3: ───×ᶠ───×ᶠ───×ᶠ───×ᶠ───×ᶠ─── │ │ 4: ────────×ᶠ────────×ᶠ──────── -""") +""", + ) - circuit = cirq.Circuit(swap_network( - qubits, lambda i, j, q0, q1: (), offset=True), - strategy=cirq.InsertStrategy.EARLIEST) - cirq.testing.assert_has_diagram(circuit, - """ + circuit = cirq.Circuit( + swap_network(qubits, lambda i, j, q0, q1: (), offset=True), + strategy=cirq.InsertStrategy.EARLIEST, + ) + cirq.testing.assert_has_diagram( + circuit, + """ 0 1 2 3 4 │ │ │ │ │ │ ×─× ×─× @@ -97,7 +105,8 @@ def test_swap_network(): │ ×─× ×─× │ │ │ │ │ """, - transpose=True) + transpose=True, + ) def test_reusable(): diff --git a/src/openfermion/circuits/slater_determinants.py b/src/openfermion/circuits/slater_determinants.py index 2644606f7..6127d5b57 100644 --- a/src/openfermion/circuits/slater_determinants.py +++ b/src/openfermion/circuits/slater_determinants.py @@ -17,15 +17,19 @@ from openfermion.config import EQ_TOLERANCE from openfermion.ops import QuadraticHamiltonian from openfermion.linalg.givens_rotations import ( - fermionic_gaussian_decomposition, givens_decomposition) + fermionic_gaussian_decomposition, + givens_decomposition, +) from openfermion.linalg.sparse_tools import ( - jw_configuration_state, jw_sparse_givens_rotation, - jw_sparse_particle_hole_transformation_last_mode) + jw_configuration_state, + jw_sparse_givens_rotation, + jw_sparse_particle_hole_transformation_last_mode, +) -def gaussian_state_preparation_circuit(quadratic_hamiltonian, - occupied_orbitals=None, - spin_sector=None): +def gaussian_state_preparation_circuit( + quadratic_hamiltonian, occupied_orbitals=None, spin_sector=None +): r"""Obtain the description of a circuit which prepares a fermionic Gaussian state. @@ -99,9 +103,11 @@ def gaussian_state_preparation_circuit(quadratic_hamiltonian, if not isinstance(quadratic_hamiltonian, QuadraticHamiltonian): raise ValueError('Input must be an instance of QuadraticHamiltonian.') - orbital_energies, transformation_matrix, _ = ( - quadratic_hamiltonian.diagonalizing_bogoliubov_transform( - spin_sector=spin_sector)) + ( + orbital_energies, + transformation_matrix, + _, + ) = quadratic_hamiltonian.diagonalizing_bogoliubov_transform(spin_sector=spin_sector) if quadratic_hamiltonian.conserves_particle_number: if occupied_orbitals is None: @@ -113,8 +119,7 @@ def gaussian_state_preparation_circuit(quadratic_hamiltonian, slater_determinant_matrix = transformation_matrix[occupied_orbitals] # Get the circuit description - circuit_description = slater_determinant_preparation_circuit( - slater_determinant_matrix) + circuit_description = slater_determinant_preparation_circuit(slater_determinant_matrix) start_orbitals = range(len(occupied_orbitals)) else: # TODO implement this @@ -127,14 +132,14 @@ def gaussian_state_preparation_circuit(quadratic_hamiltonian, left_block = transformation_matrix[:, :n_qubits] right_block = transformation_matrix[:, n_qubits:] # Can't use numpy.block because that requires numpy>=1.13.0 - new_transformation_matrix = numpy.empty((n_qubits, 2 * n_qubits), - dtype=complex) + new_transformation_matrix = numpy.empty((n_qubits, 2 * n_qubits), dtype=complex) new_transformation_matrix[:, :n_qubits] = numpy.conjugate(right_block) new_transformation_matrix[:, n_qubits:] = numpy.conjugate(left_block) # Get the circuit description - decomposition, left_decomposition, _, _ = ( - fermionic_gaussian_decomposition(new_transformation_matrix)) + decomposition, left_decomposition, _, _ = fermionic_gaussian_decomposition( + new_transformation_matrix + ) if occupied_orbitals is None: # The ground state is desired, so the circuit should be applied # to the vaccuum state @@ -144,8 +149,7 @@ def gaussian_state_preparation_circuit(quadratic_hamiltonian, start_orbitals = occupied_orbitals # The circuit won't be applied to the ground state, so we need to # use left_decomposition - circuit_description = list( - reversed(decomposition + left_decomposition)) + circuit_description = list(reversed(decomposition + left_decomposition)) return circuit_description, start_orbitals @@ -221,33 +225,30 @@ def jw_get_gaussian_state(quadratic_hamiltonian, occupied_orbitals=None): if occupied_orbitals is None: # The ground energy is desired if quadratic_hamiltonian.conserves_particle_number: - num_negative_energies = numpy.count_nonzero( - orbital_energies < -EQ_TOLERANCE) + num_negative_energies = numpy.count_nonzero(orbital_energies < -EQ_TOLERANCE) occupied_orbitals = range(num_negative_energies) else: occupied_orbitals = [] energy = numpy.sum(orbital_energies[occupied_orbitals]) + constant # Obtain the circuit that prepares the Gaussian state - circuit_description, start_orbitals = \ - gaussian_state_preparation_circuit(quadratic_hamiltonian, - occupied_orbitals) + circuit_description, start_orbitals = gaussian_state_preparation_circuit( + quadratic_hamiltonian, occupied_orbitals + ) # Initialize the starting state state = jw_configuration_state(start_orbitals, n_qubits) # Apply the circuit if not quadratic_hamiltonian.conserves_particle_number: - particle_hole_transformation = ( - jw_sparse_particle_hole_transformation_last_mode(n_qubits)) + particle_hole_transformation = jw_sparse_particle_hole_transformation_last_mode(n_qubits) for parallel_ops in circuit_description: for op in parallel_ops: if op == 'pht': state = particle_hole_transformation.dot(state) else: i, j, theta, phi = op - state = jw_sparse_givens_rotation(i, j, theta, phi, - n_qubits).dot(state) + state = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits).dot(state) return energy, state @@ -274,8 +275,7 @@ def jw_slater_determinant(slater_determinant_matrix): Returns: The Slater determinant as a sparse matrix. """ - circuit_description = slater_determinant_preparation_circuit( - slater_determinant_matrix) + circuit_description = slater_determinant_preparation_circuit(slater_determinant_matrix) start_orbitals = range(slater_determinant_matrix.shape[0]) n_qubits = slater_determinant_matrix.shape[1] @@ -286,7 +286,6 @@ def jw_slater_determinant(slater_determinant_matrix): for parallel_ops in circuit_description: for op in parallel_ops: i, j, theta, phi = op - state = jw_sparse_givens_rotation(i, j, theta, phi, - n_qubits).dot(state) + state = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits).dot(state) return state diff --git a/src/openfermion/circuits/slater_determinants_test.py b/src/openfermion/circuits/slater_determinants_test.py index 4cb17b7c0..56703a1b3 100644 --- a/src/openfermion/circuits/slater_determinants_test.py +++ b/src/openfermion/circuits/slater_determinants_test.py @@ -17,40 +17,44 @@ import numpy from openfermion.linalg.sparse_tools import ( - jw_sparse_givens_rotation, jw_sparse_particle_hole_transformation_last_mode, - get_sparse_operator, get_ground_state, jw_configuration_state) + jw_sparse_givens_rotation, + jw_sparse_particle_hole_transformation_last_mode, + get_sparse_operator, + get_ground_state, + jw_configuration_state, +) from openfermion.testing.testing_utils import random_quadratic_hamiltonian from openfermion.circuits.slater_determinants import ( - gaussian_state_preparation_circuit, jw_get_gaussian_state, - jw_slater_determinant) + gaussian_state_preparation_circuit, + jw_get_gaussian_state, + jw_slater_determinant, +) class JWSlaterDeterminantTest(unittest.TestCase): - def test_hadamard_transform(self): r"""Test creating the states 1 / sqrt(2) (a^\dagger_0 + a^\dagger_1) |vac> and 1 / sqrt(2) (a^\dagger_0 - a^\dagger_1) |vac>. """ - slater_determinant_matrix = numpy.array([[1., 1.]]) / numpy.sqrt(2.) + slater_determinant_matrix = numpy.array([[1.0, 1.0]]) / numpy.sqrt(2.0) slater_determinant = jw_slater_determinant(slater_determinant_matrix) self.assertAlmostEqual(slater_determinant[1], slater_determinant[2]) - self.assertAlmostEqual(abs(slater_determinant[1]), 1. / numpy.sqrt(2.)) - self.assertAlmostEqual(abs(slater_determinant[0]), 0.) - self.assertAlmostEqual(abs(slater_determinant[3]), 0.) + self.assertAlmostEqual(abs(slater_determinant[1]), 1.0 / numpy.sqrt(2.0)) + self.assertAlmostEqual(abs(slater_determinant[0]), 0.0) + self.assertAlmostEqual(abs(slater_determinant[3]), 0.0) - slater_determinant_matrix = numpy.array([[1., -1.]]) / numpy.sqrt(2.) + slater_determinant_matrix = numpy.array([[1.0, -1.0]]) / numpy.sqrt(2.0) slater_determinant = jw_slater_determinant(slater_determinant_matrix) self.assertAlmostEqual(slater_determinant[1], -slater_determinant[2]) - self.assertAlmostEqual(abs(slater_determinant[1]), 1. / numpy.sqrt(2.)) - self.assertAlmostEqual(abs(slater_determinant[0]), 0.) - self.assertAlmostEqual(abs(slater_determinant[3]), 0.) + self.assertAlmostEqual(abs(slater_determinant[1]), 1.0 / numpy.sqrt(2.0)) + self.assertAlmostEqual(abs(slater_determinant[0]), 0.0) + self.assertAlmostEqual(abs(slater_determinant[3]), 0.0) class GaussianStatePreparationCircuitTest(unittest.TestCase): - def setUp(self): self.n_qubits_range = range(3, 6) @@ -60,8 +64,7 @@ def test_ground_state_particle_conserving(self): for n_qubits in self.n_qubits_range: print(n_qubits) # Initialize a particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, True, True) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, True, True) print(quadratic_hamiltonian) # Compute the true ground state @@ -69,8 +72,9 @@ def test_ground_state_particle_conserving(self): ground_energy, _ = get_ground_state(sparse_operator) # Obtain the circuit - circuit_description, start_orbitals = ( - gaussian_state_preparation_circuit(quadratic_hamiltonian)) + circuit_description, start_orbitals = gaussian_state_preparation_circuit( + quadratic_hamiltonian + ) # Initialize the starting state state = jw_configuration_state(start_orbitals, n_qubits) @@ -80,8 +84,7 @@ def test_ground_state_particle_conserving(self): for op in parallel_ops: self.assertTrue(op != 'pht') i, j, theta, phi = op - state = jw_sparse_givens_rotation(i, j, theta, phi, - n_qubits).dot(state) + state = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits).dot(state) # Check that the state obtained using the circuit is a ground state difference = sparse_operator * state - ground_energy * state @@ -93,31 +96,31 @@ def test_ground_state_particle_nonconserving(self): that does not conserve particle number.""" for n_qubits in self.n_qubits_range: # Initialize a particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, False, True) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, False, True) # Compute the true ground state sparse_operator = get_sparse_operator(quadratic_hamiltonian) ground_energy, _ = get_ground_state(sparse_operator) # Obtain the circuit - circuit_description, start_orbitals = ( - gaussian_state_preparation_circuit(quadratic_hamiltonian)) + circuit_description, start_orbitals = gaussian_state_preparation_circuit( + quadratic_hamiltonian + ) # Initialize the starting state state = jw_configuration_state(start_orbitals, n_qubits) # Apply the circuit - particle_hole_transformation = ( - jw_sparse_particle_hole_transformation_last_mode(n_qubits)) + particle_hole_transformation = jw_sparse_particle_hole_transformation_last_mode( + n_qubits + ) for parallel_ops in circuit_description: for op in parallel_ops: if op == 'pht': state = particle_hole_transformation.dot(state) else: i, j, theta, phi = op - state = jw_sparse_givens_rotation( - i, j, theta, phi, n_qubits).dot(state) + state = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits).dot(state) # Check that the state obtained using the circuit is a ground state difference = sparse_operator * state - ground_energy * state @@ -131,7 +134,6 @@ def test_bad_input(self): class JWGetGaussianStateTest(unittest.TestCase): - def setUp(self): self.n_qubits_range = range(2, 10) @@ -147,15 +149,13 @@ def test_ground_state_particle_conserving(self): ground_energy, _ = get_ground_state(sparse_operator) # Compute the ground state using the circuit - circuit_energy, circuit_state = jw_get_gaussian_state( - quadratic_hamiltonian) + circuit_energy, circuit_state = jw_get_gaussian_state(quadratic_hamiltonian) # Check that the energies match self.assertAlmostEqual(ground_energy, circuit_energy) # Check that the state obtained using the circuit is a ground state - difference = (sparse_operator * circuit_state - - ground_energy * circuit_state) + difference = sparse_operator * circuit_state - ground_energy * circuit_state discrepancy = numpy.amax(numpy.abs(difference)) self.assertAlmostEqual(discrepancy, 0) @@ -164,23 +164,20 @@ def test_ground_state_particle_nonconserving(self): conserve particle number.""" for n_qubits in self.n_qubits_range: # Initialize a non-particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, False) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, False) # Compute the true ground state sparse_operator = get_sparse_operator(quadratic_hamiltonian) ground_energy, _ = get_ground_state(sparse_operator) # Compute the ground state using the circuit - circuit_energy, circuit_state = ( - jw_get_gaussian_state(quadratic_hamiltonian)) + circuit_energy, circuit_state = jw_get_gaussian_state(quadratic_hamiltonian) # Check that the energies match self.assertAlmostEqual(ground_energy, circuit_energy) # Check that the state obtained using the circuit is a ground state - difference = (sparse_operator * circuit_state - - ground_energy * circuit_state) + difference = sparse_operator * circuit_state - ground_energy * circuit_state discrepancy = numpy.amax(numpy.abs(difference)) self.assertAlmostEqual(discrepancy, 0) @@ -193,17 +190,15 @@ def test_excited_state_particle_conserving(self): # Pick some orbitals to occupy num_occupied_orbitals = numpy.random.randint(1, n_qubits + 1) - occupied_orbitals = numpy.random.choice(range(n_qubits), - num_occupied_orbitals, - False) + occupied_orbitals = numpy.random.choice(range(n_qubits), num_occupied_orbitals, False) # Compute the Gaussian state circuit_energy, gaussian_state = jw_get_gaussian_state( - quadratic_hamiltonian, occupied_orbitals) + quadratic_hamiltonian, occupied_orbitals + ) # Compute the true energy - orbital_energies, constant = ( - quadratic_hamiltonian.orbital_energies()) + orbital_energies, constant = quadratic_hamiltonian.orbital_energies() energy = numpy.sum(orbital_energies[occupied_orbitals]) + constant # Check that the energies match @@ -212,8 +207,7 @@ def test_excited_state_particle_conserving(self): # Check that the state obtained using the circuit is an eigenstate # with the correct eigenvalue sparse_operator = get_sparse_operator(quadratic_hamiltonian) - difference = (sparse_operator * gaussian_state - - energy * gaussian_state) + difference = sparse_operator * gaussian_state - energy * gaussian_state discrepancy = numpy.amax(numpy.abs(difference)) self.assertAlmostEqual(discrepancy, 0) @@ -222,22 +216,19 @@ def test_excited_state_particle_nonconserving(self): particle number.""" for n_qubits in self.n_qubits_range: # Initialize a non-particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, False) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, False) # Pick some orbitals to occupy num_occupied_orbitals = numpy.random.randint(1, n_qubits + 1) - occupied_orbitals = numpy.random.choice(range(n_qubits), - num_occupied_orbitals, - False) + occupied_orbitals = numpy.random.choice(range(n_qubits), num_occupied_orbitals, False) # Compute the Gaussian state circuit_energy, gaussian_state = jw_get_gaussian_state( - quadratic_hamiltonian, occupied_orbitals) + quadratic_hamiltonian, occupied_orbitals + ) # Compute the true energy - orbital_energies, constant = ( - quadratic_hamiltonian.orbital_energies()) + orbital_energies, constant = quadratic_hamiltonian.orbital_energies() energy = numpy.sum(orbital_energies[occupied_orbitals]) + constant # Check that the energies match @@ -246,8 +237,7 @@ def test_excited_state_particle_nonconserving(self): # Check that the state obtained using the circuit is an eigenstate # with the correct eigenvalue sparse_operator = get_sparse_operator(quadratic_hamiltonian) - difference = (sparse_operator * gaussian_state - - energy * gaussian_state) + difference = sparse_operator * gaussian_state - energy * gaussian_state discrepancy = numpy.amax(numpy.abs(difference)) self.assertAlmostEqual(discrepancy, 0) @@ -263,15 +253,14 @@ def test_not_implemented_spinr_reduced(): msg += "Hamiltonians is not yet supported." for n_qubits in [2, 4, 6]: # Initialize a particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, False, True) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, False, True) # Obtain the circuit with pytest.raises(NotImplementedError): - _ = gaussian_state_preparation_circuit(quadratic_hamiltonian, - spin_sector=1) + _ = gaussian_state_preparation_circuit(quadratic_hamiltonian, spin_sector=1) + # if __name__ == "__main__": # inst = GaussianStatePreparationCircuitTest() # inst.setUp() -# inst.test_ground_state_particle_conserving() \ No newline at end of file +# inst.test_ground_state_particle_conserving() diff --git a/src/openfermion/circuits/trotter/__init__.py b/src/openfermion/circuits/trotter/__init__.py index 2796633c3..bc98ad67e 100644 --- a/src/openfermion/circuits/trotter/__init__.py +++ b/src/openfermion/circuits/trotter/__init__.py @@ -30,8 +30,7 @@ fermionic_swap_trotter_error_operator_diagonal_two_body, ) -from .hubbard_trotter_error import \ - simulation_ordered_grouped_hubbard_terms_with_info +from .hubbard_trotter_error import simulation_ordered_grouped_hubbard_terms_with_info from .low_depth_trotter_error import ( low_depth_second_order_trotter_error_operator, @@ -44,8 +43,4 @@ from .trotter_algorithm import TrotterAlgorithm, TrotterStep -from .trotter_error import ( - error_bound, - error_operator, - trotter_steps_required, -) +from .trotter_error import error_bound, error_operator, trotter_steps_required diff --git a/src/openfermion/circuits/trotter/algorithms/__init__.py b/src/openfermion/circuits/trotter/algorithms/__init__.py index cf3d35b68..b8e1d486b 100644 --- a/src/openfermion/circuits/trotter/algorithms/__init__.py +++ b/src/openfermion/circuits/trotter/algorithms/__init__.py @@ -16,10 +16,7 @@ LinearSwapNetworkTrotterAlgorithm, ) -from openfermion.circuits.trotter.algorithms.low_rank import ( - LOW_RANK, - LowRankTrotterAlgorithm, -) +from openfermion.circuits.trotter.algorithms.low_rank import LOW_RANK, LowRankTrotterAlgorithm from .split_operator import ( SplitOperatorTrotterAlgorithm, diff --git a/src/openfermion/circuits/trotter/algorithms/linear_swap_network.py b/src/openfermion/circuits/trotter/algorithms/linear_swap_network.py index 3d4b606ce..c8dcfe3ef 100644 --- a/src/openfermion/circuits/trotter/algorithms/linear_swap_network.py +++ b/src/openfermion/circuits/trotter/algorithms/linear_swap_network.py @@ -15,13 +15,14 @@ import cirq -from openfermion.circuits.gates import (Ryxxy, Rxxyy, CRyxxy, CRxxyy, rot111, - rot11) +from openfermion.circuits.gates import Ryxxy, Rxxyy, CRyxxy, CRxxyy, rot111, rot11 from openfermion.ops import DiagonalCoulombHamiltonian from openfermion.circuits.primitives import swap_network -from openfermion.circuits.trotter.trotter_algorithm import (Hamiltonian, - TrotterStep, - TrotterAlgorithm) +from openfermion.circuits.trotter.trotter_algorithm import ( + Hamiltonian, + TrotterStep, + TrotterAlgorithm, +) class LinearSwapNetworkTrotterAlgorithm(TrotterAlgorithm): @@ -42,12 +43,10 @@ def symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: def asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return AsymmetricLinearSwapNetworkTrotterStep(hamiltonian) - def controlled_symmetric(self, - hamiltonian: Hamiltonian) -> Optional[TrotterStep]: + def controlled_symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return ControlledSymmetricLinearSwapNetworkTrotterStep(hamiltonian) - def controlled_asymmetric(self, hamiltonian: Hamiltonian - ) -> Optional[TrotterStep]: + def controlled_asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return ControlledAsymmetricLinearSwapNetworkTrotterStep(hamiltonian) @@ -55,51 +54,43 @@ def controlled_asymmetric(self, hamiltonian: Hamiltonian class SymmetricLinearSwapNetworkTrotterStep(TrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) # Apply one- and two-body interactions for half of the full time def one_and_two_body_interaction(p, q, a, b) -> cirq.OP_TREE: - yield Rxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on( - a, b) - yield Ryxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on( - a, b) + yield Rxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on(a, b) + yield Ryxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on(a, b) yield rot11(rads=-self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, one_and_two_body_interaction, fermionic=True) qubits = qubits[::-1] # Apply one-body potential for the full time - yield (cirq.rz(rads=-self.hamiltonian.one_body[i, i].real * time).on( - qubits[i]) for i in range(n_qubits)) + yield ( + cirq.rz(rads=-self.hamiltonian.one_body[i, i].real * time).on(qubits[i]) + for i in range(n_qubits) + ) # Apply one- and two-body interactions for half of the full time # This time, reorder the operations so that the entire Trotter step is # symmetric - def one_and_two_body_interaction_reverse_order(p, q, a, - b) -> cirq.OP_TREE: + def one_and_two_body_interaction_reverse_order(p, q, a, b) -> cirq.OP_TREE: yield rot11(rads=-self.hamiltonian.two_body[p, q] * time).on(a, b) - yield Ryxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on( - a, b) - yield Rxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on( - a, b) + yield Ryxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on(a, b) + yield Rxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on(a, b) - yield swap_network(qubits, - one_and_two_body_interaction_reverse_order, - fermionic=True, - offset=True) + yield swap_network( + qubits, one_and_two_body_interaction_reverse_order, fermionic=True, offset=True + ) class ControlledSymmetricLinearSwapNetworkTrotterStep(TrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): @@ -108,87 +99,89 @@ def trotter_step(self, # Apply one- and two-body interactions for half of the full time def one_and_two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield CRxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield CRyxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield rot111(-self.hamiltonian.two_body[p, q] * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield swap_network(qubits, one_and_two_body_interaction, fermionic=True) qubits = qubits[::-1] # Apply one-body potential for the full time - yield (rot11(rads=-self.hamiltonian.one_body[i, i].real * time).on( - control_qubit, qubits[i]) for i in range(n_qubits)) + yield ( + rot11(rads=-self.hamiltonian.one_body[i, i].real * time).on(control_qubit, qubits[i]) + for i in range(n_qubits) + ) # Apply one- and two-body interactions for half of the full time # This time, reorder the operations so that the entire Trotter step is # symmetric - def one_and_two_body_interaction_reverse_order(p, q, a, - b) -> cirq.OP_TREE: + def one_and_two_body_interaction_reverse_order(p, q, a, b) -> cirq.OP_TREE: yield rot111(-self.hamiltonian.two_body[p, q] * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield CRyxxy(0.5 * self.hamiltonian.one_body[p, q].imag * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield CRxxyy(0.5 * self.hamiltonian.one_body[p, q].real * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) - yield swap_network(qubits, - one_and_two_body_interaction_reverse_order, - fermionic=True, - offset=True) + yield swap_network( + qubits, one_and_two_body_interaction_reverse_order, fermionic=True, offset=True + ) # Apply phase from constant term yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit) class AsymmetricLinearSwapNetworkTrotterStep(TrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) # Apply one- and two-body interactions for the full time def one_and_two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield Rxxyy(self.hamiltonian.one_body[p, q].real * time).on(a, b) yield Ryxxy(self.hamiltonian.one_body[p, q].imag * time).on(a, b) - yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on( - a, b) + yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, one_and_two_body_interaction, fermionic=True) qubits = qubits[::-1] # Apply one-body potential for the full time - yield (cirq.rz(rads=-self.hamiltonian.one_body[i, i].real * time).on( - qubits[i]) for i in range(n_qubits)) + yield ( + cirq.rz(rads=-self.hamiltonian.one_body[i, i].real * time).on(qubits[i]) + for i in range(n_qubits) + ) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], None - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits, fermionic=True) class ControlledAsymmetricLinearSwapNetworkTrotterStep(TrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: - + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): @@ -197,35 +190,40 @@ def trotter_step(self, # Apply one- and two-body interactions for the full time def one_and_two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield CRxxyy(self.hamiltonian.one_body[p, q].real * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield CRyxxy(self.hamiltonian.one_body[p, q].imag * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield swap_network(qubits, one_and_two_body_interaction, fermionic=True) qubits = qubits[::-1] # Apply one-body potential for the full time - yield (rot11(rads=-self.hamiltonian.one_body[i, i].real * time).on( - control_qubit, qubits[i]) for i in range(n_qubits)) + yield ( + rot11(rads=-self.hamiltonian.one_body[i, i].real * time).on(control_qubit, qubits[i]) + for i in range(n_qubits) + ) # Apply phase from constant term yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], control_qubit - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits, fermionic=True) diff --git a/src/openfermion/circuits/trotter/algorithms/low_rank.py b/src/openfermion/circuits/trotter/algorithms/low_rank.py index a23b3f9bc..05337b160 100644 --- a/src/openfermion/circuits/trotter/algorithms/low_rank.py +++ b/src/openfermion/circuits/trotter/algorithms/low_rank.py @@ -18,15 +18,21 @@ import cirq import openfermion.ops as ops + # import openfermion.circuits.gates as gates from openfermion.circuits.gates import rot11, rot111 + # import openfermion.circuits.primitives as primitives from openfermion.circuits.primitives import swap_network, bogoliubov_transform -from openfermion.circuits.low_rank import (low_rank_two_body_decomposition, - prepare_one_body_squared_evolution) -from openfermion.circuits.trotter.trotter_algorithm import (Hamiltonian, - TrotterStep, - TrotterAlgorithm) +from openfermion.circuits.low_rank import ( + low_rank_two_body_decomposition, + prepare_one_body_squared_evolution, +) +from openfermion.circuits.trotter.trotter_algorithm import ( + Hamiltonian, + TrotterStep, + TrotterAlgorithm, +) if TYPE_CHECKING: # pylint: disable=unused-import @@ -62,10 +68,12 @@ class LowRankTrotterAlgorithm(TrotterAlgorithm): supported_types = {ops.InteractionOperator} - def __init__(self, - truncation_threshold: Optional[float] = 1e-8, - final_rank: Optional[int] = None, - spin_basis=True) -> None: + def __init__( + self, + truncation_threshold: Optional[float] = 1e-8, + final_rank: Optional[int] = None, + spin_basis=True, + ) -> None: """ Args: truncation_threshold: The value of x from the docstring of @@ -80,72 +88,75 @@ def __init__(self, self.spin_basis = spin_basis def asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: - return AsymmetricLowRankTrotterStep(hamiltonian, - self.truncation_threshold, - self.final_rank, self.spin_basis) + return AsymmetricLowRankTrotterStep( + hamiltonian, self.truncation_threshold, self.final_rank, self.spin_basis + ) - def controlled_asymmetric(self, hamiltonian: Hamiltonian - ) -> Optional[TrotterStep]: - return ControlledAsymmetricLowRankTrotterStep(hamiltonian, - self.truncation_threshold, - self.final_rank, - self.spin_basis) + def controlled_asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: + return ControlledAsymmetricLowRankTrotterStep( + hamiltonian, self.truncation_threshold, self.final_rank, self.spin_basis + ) LOW_RANK = LowRankTrotterAlgorithm() class LowRankTrotterStep(TrotterStep): - - def __init__(self, - hamiltonian: 'ops.InteractionOperator', - truncation_threshold: Optional[float] = 1e-8, - final_rank: Optional[int] = None, - spin_basis=True) -> None: - + def __init__( + self, + hamiltonian: 'ops.InteractionOperator', + truncation_threshold: Optional[float] = 1e-8, + final_rank: Optional[int] = None, + spin_basis=True, + ) -> None: self.truncation_threshold = truncation_threshold self.final_rank = final_rank # Perform the low rank decomposition of two-body operator. - self.eigenvalues, self.one_body_squares, one_body_correction, _ = ( - low_rank_two_body_decomposition( - hamiltonian.two_body_tensor, - truncation_threshold=self.truncation_threshold, - final_rank=self.final_rank, - spin_basis=spin_basis)) + ( + self.eigenvalues, + self.one_body_squares, + one_body_correction, + _, + ) = low_rank_two_body_decomposition( + hamiltonian.two_body_tensor, + truncation_threshold=self.truncation_threshold, + final_rank=self.final_rank, + spin_basis=spin_basis, + ) # Get scaled density-density terms and basis transformation matrices. self.scaled_density_density_matrices = [] # type: List[numpy.ndarray] self.basis_change_matrices = [] # type: List[numpy.ndarray] for j in range(len(self.eigenvalues)): - density_density_matrix, basis_change_matrix = ( - prepare_one_body_squared_evolution(self.one_body_squares[j])) + density_density_matrix, basis_change_matrix = prepare_one_body_squared_evolution( + self.one_body_squares[j] + ) self.scaled_density_density_matrices.append( - numpy.real(self.eigenvalues[j] * density_density_matrix)) + numpy.real(self.eigenvalues[j] * density_density_matrix) + ) self.basis_change_matrices.append(basis_change_matrix) # Get transformation matrix and orbital energies for one-body terms - one_body_coefficients = (hamiltonian.one_body_tensor + - one_body_correction) + one_body_coefficients = hamiltonian.one_body_tensor + one_body_correction quad_ham = ops.QuadraticHamiltonian(one_body_coefficients) - self.one_body_energies, self.one_body_basis_change_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + ( + self.one_body_energies, + self.one_body_basis_change_matrix, + _, + ) = quad_ham.diagonalizing_bogoliubov_transform() super().__init__(hamiltonian) class AsymmetricLowRankTrotterStep(LowRankTrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: - + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) # Change to the basis in which the one-body term is diagonal - yield bogoliubov_transform(qubits, - self.one_body_basis_change_matrix.T.conj()) + yield bogoliubov_transform(qubits, self.one_body_basis_change_matrix.T.conj()) # Simulate the one-body terms. for p in range(n_qubits): @@ -155,27 +166,25 @@ def trotter_step(self, prior_basis_matrix = self.one_body_basis_change_matrix for j in range(len(self.eigenvalues)): - # Get the two-body coefficients and basis change matrix. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # Merge previous basis change matrix with the inverse of the # current one - merged_basis_change_matrix = numpy.dot(prior_basis_matrix, - basis_change_matrix.T.conj()) + merged_basis_change_matrix = numpy.dot(prior_basis_matrix, basis_change_matrix.T.conj()) yield bogoliubov_transform(qubits, merged_basis_change_matrix) # Simulate the off-diagonal two-body terms. yield swap_network( - qubits, lambda p, q, a, b: rot11( - rads=-2 * two_body_coefficients[p, q] * time).on(a, b)) + qubits, + lambda p, q, a, b: rot11(rads=-2 * two_body_coefficients[p, q] * time).on(a, b), + ) qubits = qubits[::-1] # Simulate the diagonal two-body terms. for p in range(n_qubits): - yield cirq.rz(rads=-two_body_coefficients[p, p] * time).on( - qubits[p]) + yield cirq.rz(rads=-two_body_coefficients[p, p] * time).on(qubits[p]) # Update prior basis change matrix prior_basis_matrix = basis_change_matrix @@ -184,9 +193,7 @@ def trotter_step(self, yield bogoliubov_transform(qubits, prior_basis_matrix) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering when the number of # eigenvalues is odd @@ -195,11 +202,13 @@ def step_qubit_permutation( else: return qubits, None - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: if not omit_final_swaps: # If the number of swap networks was odd, swap the qubits back if n_steps & 1 and len(self.eigenvalues) & 1: @@ -207,50 +216,48 @@ def finish(self, class ControlledAsymmetricLowRankTrotterStep(LowRankTrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: - + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: if not isinstance(control_qubit, cirq.Qid): raise TypeError('Control qudit must be specified.') n_qubits = len(qubits) # Change to the basis in which the one-body term is diagonal - yield bogoliubov_transform(qubits, - self.one_body_basis_change_matrix.T.conj()) + yield bogoliubov_transform(qubits, self.one_body_basis_change_matrix.T.conj()) # Simulate the one-body terms. for p in range(n_qubits): - yield rot11(rads=-self.one_body_energies[p] * time).on( - control_qubit, qubits[p]) + yield rot11(rads=-self.one_body_energies[p] * time).on(control_qubit, qubits[p]) # Simulate each singular vector of the two-body terms. prior_basis_matrix = self.one_body_basis_change_matrix for j in range(len(self.eigenvalues)): - # Get the two-body coefficients and basis change matrix. two_body_coefficients = self.scaled_density_density_matrices[j] basis_change_matrix = self.basis_change_matrices[j] # Merge previous basis change matrix with the inverse of the # current one - merged_basis_change_matrix = numpy.dot(prior_basis_matrix, - basis_change_matrix.T.conj()) + merged_basis_change_matrix = numpy.dot(prior_basis_matrix, basis_change_matrix.T.conj()) yield bogoliubov_transform(qubits, merged_basis_change_matrix) # Simulate the off-diagonal two-body terms. yield swap_network( - qubits, lambda p, q, a, b: rot111(-2 * two_body_coefficients[ - p, q] * time).on(cast(cirq.Qid, control_qubit), a, b)) + qubits, + lambda p, q, a, b: rot111(-2 * two_body_coefficients[p, q] * time).on( + cast(cirq.Qid, control_qubit), a, b + ), + ) qubits = qubits[::-1] # Simulate the diagonal two-body terms. - yield (rot11(rads=-two_body_coefficients[k, k] * time).on( - control_qubit, qubits[k]) for k in range(n_qubits)) + yield ( + rot11(rads=-two_body_coefficients[k, k] * time).on(control_qubit, qubits[k]) + for k in range(n_qubits) + ) # Update prior basis change matrix. prior_basis_matrix = basis_change_matrix @@ -262,9 +269,7 @@ def trotter_step(self, yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering when the number of # eigenvalues is odd @@ -273,11 +278,13 @@ def step_qubit_permutation( else: return qubits, control_qubit - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: if not omit_final_swaps: # If the number of swap networks was odd, swap the qubits back if n_steps & 1 and len(self.eigenvalues) & 1: diff --git a/src/openfermion/circuits/trotter/algorithms/split_operator.py b/src/openfermion/circuits/trotter/algorithms/split_operator.py index 7fe83fa44..3b1c2603b 100644 --- a/src/openfermion/circuits/trotter/algorithms/split_operator.py +++ b/src/openfermion/circuits/trotter/algorithms/split_operator.py @@ -18,11 +18,14 @@ # import openfermion.circuits.gates as gates from openfermion.circuits.gates import rot11, rot111 import openfermion.ops as ops + # import openfermion.circuits.primitives as primitives from openfermion.circuits.primitives import bogoliubov_transform, swap_network -from openfermion.circuits.trotter.trotter_algorithm import (Hamiltonian, - TrotterStep, - TrotterAlgorithm) +from openfermion.circuits.trotter.trotter_algorithm import ( + Hamiltonian, + TrotterStep, + TrotterAlgorithm, +) class SplitOperatorTrotterAlgorithm(TrotterAlgorithm): @@ -36,6 +39,7 @@ class SplitOperatorTrotterAlgorithm(TrotterAlgorithm): This algorithm is described in arXiv:1706.00023. """ + # TODO Maybe use FFFT supported_types = {ops.DiagonalCoulombHamiltonian} @@ -46,12 +50,10 @@ def symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: def asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return AsymmetricSplitOperatorTrotterStep(hamiltonian) - def controlled_symmetric(self, - hamiltonian: Hamiltonian) -> Optional[TrotterStep]: + def controlled_symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return ControlledSymmetricSplitOperatorTrotterStep(hamiltonian) - def controlled_asymmetric(self, hamiltonian: Hamiltonian - ) -> Optional[TrotterStep]: + def controlled_asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return ControlledAsymmetricSplitOperatorTrotterStep(hamiltonian) @@ -59,70 +61,69 @@ def controlled_asymmetric(self, hamiltonian: Hamiltonian class SplitOperatorTrotterStep(TrotterStep): - - def __init__(self, - hamiltonian: 'openfermion.DiagonalCoulombHamiltonian') -> None: + def __init__(self, hamiltonian: 'openfermion.DiagonalCoulombHamiltonian') -> None: quad_ham = ops.QuadraticHamiltonian(hamiltonian.one_body) # Get the basis change matrix that diagonalizes the one-body term # and associated orbital energies - self.orbital_energies, self.basis_change_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + ( + self.orbital_energies, + self.basis_change_matrix, + _, + ) = quad_ham.diagonalizing_bogoliubov_transform() super().__init__(hamiltonian) class SymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): - - def prepare(self, - qubits: Sequence[cirq.Qid], - control_qubits: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def prepare( + self, qubits: Sequence[cirq.Qid], control_qubits: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: # Change to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the one-body terms for half of the full time - yield (cirq.rz(rads=-0.5 * self.orbital_energies[i] * time).on( - qubits[i]) for i in range(n_qubits)) + yield ( + cirq.rz(rads=-0.5 * self.orbital_energies[i] * time).on(qubits[i]) + for i in range(n_qubits) + ) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: - yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on( - a, b) + yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for half of the full time - yield (cirq.rz(rads=-0.5 * self.orbital_energies[i] * time).on( - qubits[i]) for i in range(n_qubits)) + yield ( + cirq.rz(rads=-0.5 * self.orbital_energies[i] * time).on(qubits[i]) + for i in range(n_qubits) + ) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], None - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # If the number of Trotter steps is odd, possibly swap qubits back @@ -131,27 +132,25 @@ def finish(self, class ControlledSymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): - - def prepare(self, - qubits: Sequence[cirq.Qid], - control_qubits: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def prepare( + self, qubits: Sequence[cirq.Qid], control_qubits: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: # Change to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): raise TypeError('Control qudit must be specified.') # Simulate the one-body terms for half of the full time - yield (rot11(rads=-0.5 * self.orbital_energies[i] * time).on( - control_qubit, qubits[i]) for i in range(n_qubits)) + yield ( + rot11(rads=-0.5 * self.orbital_energies[i] * time).on(control_qubit, qubits[i]) + for i in range(n_qubits) + ) # Rotate to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) @@ -159,36 +158,38 @@ def trotter_step(self, # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate back to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for half of the full time - yield (rot11(rads=-0.5 * self.orbital_energies[i] * time).on( - control_qubit, qubits[i]) for i in range(n_qubits)) + yield ( + rot11(rads=-0.5 * self.orbital_energies[i] * time).on(control_qubit, qubits[i]) + for i in range(n_qubits) + ) # Apply phase from constant term yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], control_qubit - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) # If the number of Trotter steps is odd, possibly swap qubits back @@ -197,59 +198,52 @@ def finish(self, class AsymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: - + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: - yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on( - a, b) + yield rot11(rads=-2 * self.hamiltonian.two_body[p, q] * time).on(a, b) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for the full time - yield (cirq.rz(rads=-self.orbital_energies[i] * time).on(qubits[i]) - for i in range(n_qubits)) + yield ( + cirq.rz(rads=-self.orbital_energies[i] * time).on(qubits[i]) for i in range(n_qubits) + ) # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], None - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits) class ControlledAsymmetricSplitOperatorTrotterStep(SplitOperatorTrotterStep): - - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: - + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: n_qubits = len(qubits) if not isinstance(control_qubit, cirq.Qid): @@ -258,19 +252,21 @@ def trotter_step(self, # Simulate the two-body terms for the full time def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield rot111(-2 * self.hamiltonian.two_body[p, q] * time).on( - cast(cirq.Qid, control_qubit), a, b) + cast(cirq.Qid, control_qubit), a, b + ) yield swap_network(qubits, two_body_interaction) # The qubit ordering has been reversed qubits = qubits[::-1] # Rotate to the basis in which the one-body term is diagonal - yield cirq.inverse( - bogoliubov_transform(qubits, self.basis_change_matrix)) + yield cirq.inverse(bogoliubov_transform(qubits, self.basis_change_matrix)) # Simulate the one-body terms for the full time - yield (rot11(rads=-self.orbital_energies[i] * time).on( - control_qubit, qubits[i]) for i in range(n_qubits)) + yield ( + rot11(rads=-self.orbital_energies[i] * time).on(control_qubit, qubits[i]) + for i in range(n_qubits) + ) # Rotate back to the computational basis yield bogoliubov_transform(qubits, self.basis_change_matrix) @@ -279,18 +275,18 @@ def two_body_interaction(p, q, a, b) -> cirq.OP_TREE: yield cirq.rz(rads=-self.hamiltonian.constant * time).on(control_qubit) def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: # A Trotter step reverses the qubit ordering return qubits[::-1], control_qubit - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: # If the number of Trotter steps is odd, possibly swap qubits back if n_steps & 1 and not omit_final_swaps: yield swap_network(qubits) diff --git a/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error.py b/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error.py index 3a87bce1e..630c06944 100644 --- a/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error.py +++ b/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error.py @@ -14,13 +14,14 @@ import numpy from openfermion.ops.operators import FermionOperator -from openfermion.transforms.opconversions import (get_fermion_operator, - normal_ordered) +from openfermion.transforms.opconversions import get_fermion_operator, normal_ordered from openfermion.utils.operator_utils import count_qubits from openfermion.circuits.trotter.low_depth_trotter_error import ( - simulation_ordered_grouped_low_depth_terms_with_info) -from openfermion.transforms.opconversions import \ - commutator_ordered_diagonal_coulomb_with_two_body_operator + simulation_ordered_grouped_low_depth_terms_with_info, +) +from openfermion.transforms.opconversions import ( + commutator_ordered_diagonal_coulomb_with_two_body_operator, +) def diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian): @@ -41,8 +42,9 @@ def diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian): try: hamiltonian = normal_ordered(get_fermion_operator(hamiltonian)) except TypeError: - raise TypeError('hamiltonian must be either a FermionOperator ' - 'or DiagonalCoulombHamiltonian.') + raise TypeError( + 'hamiltonian must be either a FermionOperator ' 'or DiagonalCoulombHamiltonian.' + ) potential = FermionOperator.zero() kinetic = FermionOperator.zero() @@ -54,18 +56,18 @@ def diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian): else: kinetic += FermionOperator(term, coeff) - potential_terms = numpy.array([ - FermionOperator(term, coeff) for term, coeff in potential.terms.items() - ]) + potential_terms = numpy.array( + [FermionOperator(term, coeff) for term, coeff in potential.terms.items()] + ) kinetic_terms = numpy.array( - [FermionOperator(term, coeff) for term, coeff in kinetic.terms.items()]) + [FermionOperator(term, coeff) for term, coeff in kinetic.terms.items()] + ) return (potential_terms, kinetic_terms) -def bit_mask_of_modes_acted_on_by_fermionic_terms(fermion_term_list, - n_qubits=None): +def bit_mask_of_modes_acted_on_by_fermionic_terms(fermion_term_list, n_qubits=None): """Create a mask of which modes of the system are acted on by which terms. Args: @@ -97,8 +99,9 @@ def bit_mask_of_modes_acted_on_by_fermionic_terms(fermion_term_list, try: mask[mode][term_number] = True except IndexError: - raise ValueError('Bad n_qubits: must be greater than ' - 'highest mode in any FermionOperator.') + raise ValueError( + 'Bad n_qubits: must be greater than ' 'highest mode in any FermionOperator.' + ) return mask @@ -128,23 +131,20 @@ def split_operator_trotter_error_operator_diagonal_two_body(hamiltonian, order): """ n_qubits = count_qubits(hamiltonian) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) # Cache halved potential and kinetic terms for the second commutator. halved_potential_terms = potential_terms / 2.0 halved_kinetic_terms = kinetic_terms / 2.0 # Assign the outer term of the second commutator based on the ordering. - outer_potential_terms = (halved_potential_terms - if order == 'T+V' else potential_terms) - outer_kinetic_terms = (halved_kinetic_terms - if order == 'V+T' else kinetic_terms) + outer_potential_terms = halved_potential_terms if order == 'T+V' else potential_terms + outer_kinetic_terms = halved_kinetic_terms if order == 'V+T' else kinetic_terms - potential_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - potential_terms, n_qubits) - kinetic_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - kinetic_terms, n_qubits) + potential_mask = bit_mask_of_modes_acted_on_by_fermionic_terms(potential_terms, n_qubits) + kinetic_mask = bit_mask_of_modes_acted_on_by_fermionic_terms(kinetic_terms, n_qubits) error_operator = FermionOperator.zero() @@ -153,52 +153,49 @@ def split_operator_trotter_error_operator_diagonal_two_body(hamiltonian, order): for potential_term_action in potential_term.terms: modes_acted_on_by_potential_term.update( - set(operator[0] for operator in potential_term_action)) + set(operator[0] for operator in potential_term_action) + ) if not modes_acted_on_by_potential_term: continue potential_term_mode_mask = numpy.logical_or.reduce( - [kinetic_mask[mode] for mode in modes_acted_on_by_potential_term]) + [kinetic_mask[mode] for mode in modes_acted_on_by_potential_term] + ) for kinetic_term in kinetic_terms[potential_term_mode_mask]: - inner_commutator_term = ( - commutator_ordered_diagonal_coulomb_with_two_body_operator( - potential_term, kinetic_term)) + inner_commutator_term = commutator_ordered_diagonal_coulomb_with_two_body_operator( + potential_term, kinetic_term + ) modes_acted_on_by_inner_commutator = set() for inner_commutator_action in inner_commutator_term.terms: modes_acted_on_by_inner_commutator.update( - set(operator[0] for operator in inner_commutator_action)) + set(operator[0] for operator in inner_commutator_action) + ) if not modes_acted_on_by_inner_commutator: continue - inner_commutator_mode_mask = numpy.logical_or.reduce([ - potential_mask[mode] - for mode in modes_acted_on_by_inner_commutator - ]) + inner_commutator_mode_mask = numpy.logical_or.reduce( + [potential_mask[mode] for mode in modes_acted_on_by_inner_commutator] + ) # halved_potential_terms for T+V order, potential_terms for V+T - for outer_potential_term in outer_potential_terms[ - inner_commutator_mode_mask]: + for outer_potential_term in outer_potential_terms[inner_commutator_mode_mask]: commutator_ordered_diagonal_coulomb_with_two_body_operator( - outer_potential_term, - inner_commutator_term, - prior_terms=error_operator) + outer_potential_term, inner_commutator_term, prior_terms=error_operator + ) - inner_commutator_mode_mask = numpy.logical_or.reduce([ - kinetic_mask[qubit] - for qubit in modes_acted_on_by_inner_commutator - ]) + inner_commutator_mode_mask = numpy.logical_or.reduce( + [kinetic_mask[qubit] for qubit in modes_acted_on_by_inner_commutator] + ) # kinetic_terms for T+V order, halved_kinetic_terms for V+T - for outer_kinetic_term in outer_kinetic_terms[ - inner_commutator_mode_mask]: + for outer_kinetic_term in outer_kinetic_terms[inner_commutator_mode_mask]: commutator_ordered_diagonal_coulomb_with_two_body_operator( - outer_kinetic_term, - inner_commutator_term, - prior_terms=error_operator) + outer_kinetic_term, inner_commutator_term, prior_terms=error_operator + ) # Divide by 12 to match the error operator definition. # If order='V+T', also flip the sign to account for inner_commutator_term @@ -212,7 +209,8 @@ def split_operator_trotter_error_operator_diagonal_two_body(hamiltonian, order): def fermionic_swap_trotter_error_operator_diagonal_two_body( - hamiltonian, external_potential_at_end=False): + hamiltonian, external_potential_at_end=False +): """Compute the fermionic swap network Trotter error of a diagonal two-body Hamiltonian. @@ -229,25 +227,27 @@ def fermionic_swap_trotter_error_operator_diagonal_two_body( """ single_terms = numpy.array( simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, - external_potential_at_end=external_potential_at_end)[0]) + hamiltonian, external_potential_at_end=external_potential_at_end + )[0] + ) # Cache the halved terms for use in the second commutator. halved_single_terms = single_terms / 2.0 term_mode_mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - single_terms, count_qubits(hamiltonian)) + single_terms, count_qubits(hamiltonian) + ) error_operator = FermionOperator.zero() for beta, term_beta in enumerate(single_terms): modes_acted_on_by_term_beta = set() for beta_action in term_beta.terms: - modes_acted_on_by_term_beta.update( - set(operator[0] for operator in beta_action)) + modes_acted_on_by_term_beta.update(set(operator[0] for operator in beta_action)) beta_mode_mask = numpy.logical_or.reduce( - [term_mode_mask[mode] for mode in modes_acted_on_by_term_beta]) + [term_mode_mask[mode] for mode in modes_acted_on_by_term_beta] + ) # alpha_prime indices that could have a nonzero commutator, i.e. # there's overlap between the modes the corresponding terms act on. @@ -259,23 +259,23 @@ def fermionic_swap_trotter_error_operator_diagonal_two_body( for alpha_prime in valid_alpha_primes: term_alpha_prime = single_terms[alpha_prime] - inner_commutator_term = ( - commutator_ordered_diagonal_coulomb_with_two_body_operator( - term_beta, term_alpha_prime)) + inner_commutator_term = commutator_ordered_diagonal_coulomb_with_two_body_operator( + term_beta, term_alpha_prime + ) modes_acted_on_by_inner_commutator = set() for inner_commutator_action in inner_commutator_term.terms: modes_acted_on_by_inner_commutator.update( - set(operator[0] for operator in inner_commutator_action)) + set(operator[0] for operator in inner_commutator_action) + ) # If the inner commutator has no action, the commutator is zero. if not modes_acted_on_by_inner_commutator: continue - inner_commutator_mask = numpy.logical_or.reduce([ - term_mode_mask[mode] - for mode in modes_acted_on_by_inner_commutator - ]) + inner_commutator_mask = numpy.logical_or.reduce( + [term_mode_mask[mode] for mode in modes_acted_on_by_inner_commutator] + ) # alpha indices that could have a nonzero commutator. valid_alphas = numpy.where(inner_commutator_mask)[0] @@ -291,9 +291,8 @@ def fermionic_swap_trotter_error_operator_diagonal_two_body( # Add the partial double commutator to the error operator. commutator_ordered_diagonal_coulomb_with_two_body_operator( - outer_term_alpha, - inner_commutator_term, - prior_terms=error_operator) + outer_term_alpha, inner_commutator_term, prior_terms=error_operator + ) # Divide by 12 to match the error operator definition. error_operator /= 12.0 diff --git a/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error_test.py b/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error_test.py index 736b0c272..98a925773 100644 --- a/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error_test.py +++ b/src/openfermion/circuits/trotter/diagonal_coulomb_trotter_error_test.py @@ -23,52 +23,56 @@ from openfermion.utils.grid import Grid from openfermion.hamiltonians import ( - dual_basis_jellium_model, fermi_hubbard, - hypercube_grid_with_given_wigner_seitz_radius_and_filling, jellium_model) + dual_basis_jellium_model, + fermi_hubbard, + hypercube_grid_with_given_wigner_seitz_radius_and_filling, + jellium_model, +) from openfermion.circuits.trotter.low_depth_trotter_error import ( low_depth_second_order_trotter_error_operator, - simulation_ordered_grouped_low_depth_terms_with_info) + simulation_ordered_grouped_low_depth_terms_with_info, +) from openfermion.circuits.trotter.diagonal_coulomb_trotter_error import ( diagonal_coulomb_potential_and_kinetic_terms_as_arrays, bit_mask_of_modes_acted_on_by_fermionic_terms, split_operator_trotter_error_operator_diagonal_two_body, - fermionic_swap_trotter_error_operator_diagonal_two_body) + fermionic_swap_trotter_error_operator_diagonal_two_body, +) class BreakHamiltonianIntoPotentialKineticArraysTest(unittest.TestCase): - def test_simple_hamiltonian(self): - hamiltonian = (FermionOperator('3^ 1^ 3 1') + FermionOperator('1^ 1') - - FermionOperator('1^ 2') - FermionOperator('2^ 1')) + hamiltonian = ( + FermionOperator('3^ 1^ 3 1') + + FermionOperator('1^ 1') + - FermionOperator('1^ 2') + - FermionOperator('2^ 1') + ) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) - self.assertEqual( - potential, (FermionOperator('1^ 1') + FermionOperator('3^ 1^ 3 1'))) - self.assertEqual(kinetic, - (-FermionOperator('1^ 2') - FermionOperator('2^ 1'))) + self.assertEqual(potential, (FermionOperator('1^ 1') + FermionOperator('3^ 1^ 3 1'))) + self.assertEqual(kinetic, (-FermionOperator('1^ 2') - FermionOperator('2^ 1'))) def test_jellium_hamiltonian_correctly_broken_up(self): - grid = Grid(2, 3, 1.) + grid = Grid(2, 3, 1.0) hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) - true_potential = dual_basis_jellium_model(grid, - spinless=True, - kinetic=False) - true_kinetic = dual_basis_jellium_model(grid, - spinless=True, - potential=False) + true_potential = dual_basis_jellium_model(grid, spinless=True, kinetic=False) + true_kinetic = dual_basis_jellium_model(grid, spinless=True, potential=False) for i in range(count_qubits(true_kinetic)): coeff = true_kinetic.terms.get(((i, 1), (i, 0))) if coeff: @@ -79,39 +83,41 @@ def test_jellium_hamiltonian_correctly_broken_up(self): self.assertEqual(kinetic, true_kinetic) def test_identity_recognized_as_potential_term(self): - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays( - FermionOperator.identity())) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + FermionOperator.identity() + ) - self.assertListEqual(list(potential_terms), - [FermionOperator.identity()]) + self.assertListEqual(list(potential_terms), [FermionOperator.identity()]) self.assertListEqual(list(kinetic_terms), []) def test_zero_hamiltonian(self): - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays( - FermionOperator.zero())) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + FermionOperator.zero() + ) self.assertListEqual(list(potential_terms), []) self.assertListEqual(list(kinetic_terms), []) def test_diagonal_coulomb_hamiltonian_class(self): - hamiltonian = DiagonalCoulombHamiltonian(numpy.array([[1, 1], [1, 1]], - dtype=float), - numpy.array([[0, 1], [1, 0]], - dtype=float), - constant=2.3) + hamiltonian = DiagonalCoulombHamiltonian( + numpy.array([[1, 1], [1, 1]], dtype=float), + numpy.array([[0, 1], [1, 0]], dtype=float), + constant=2.3, + ) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) - expected_potential = (2.3 * FermionOperator.identity() + - FermionOperator('0^ 0') + - FermionOperator('1^ 1') - - FermionOperator('1^ 0^ 1 0', 2.0)) + expected_potential = ( + 2.3 * FermionOperator.identity() + + FermionOperator('0^ 0') + + FermionOperator('1^ 1') + - FermionOperator('1^ 0^ 1 0', 2.0) + ) expected_kinetic = FermionOperator('0^ 1') + FermionOperator('1^ 0') self.assertEqual(potential, expected_potential) @@ -123,185 +129,182 @@ def test_type_error_on_bad_input_hamiltonian(self): class BitMaskModesActedOnByFermionTermsTest(unittest.TestCase): - def test_mask_no_terms(self): mask = bit_mask_of_modes_acted_on_by_fermionic_terms([], n_qubits=2) self.assertTrue(numpy.array_equal(mask, numpy.array([[], []]))) def test_identity_masks_no_modes(self): - mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - [FermionOperator.zero()], n_qubits=3) + mask = bit_mask_of_modes_acted_on_by_fermionic_terms([FermionOperator.zero()], n_qubits=3) self.assertTrue(numpy.array_equal(mask, numpy.zeros((3, 1)))) def test_mask_single_term(self): - mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - [FermionOperator('0^ 0')], n_qubits=2) + mask = bit_mask_of_modes_acted_on_by_fermionic_terms([FermionOperator('0^ 0')], n_qubits=2) self.assertTrue(numpy.array_equal(mask, numpy.array([[True], [False]]))) def test_mask_hermitian_terms_results_in_duplicated_row(self): mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - [FermionOperator('2^ 3'), - FermionOperator('3^ 2')], n_qubits=5) + [FermionOperator('2^ 3'), FermionOperator('3^ 2')], n_qubits=5 + ) - expected_mask = numpy.array([[False, False], [False, - False], [True, True], - [True, True], [False, False]]) + expected_mask = numpy.array( + [[False, False], [False, False], [True, True], [True, True], [False, False]] + ) self.assertTrue(numpy.array_equal(mask, expected_mask)) def test_mask_n_qubits_too_small_for_term(self): with self.assertRaises(ValueError): - bit_mask_of_modes_acted_on_by_fermionic_terms( - [FermionOperator('1^ 1')], n_qubits=1) + bit_mask_of_modes_acted_on_by_fermionic_terms([FermionOperator('1^ 1')], n_qubits=1) def test_mask_long_arbitrary_terms(self): operator1 = FermionOperator('6^ 5^ 4^ 3 2 1', 2.3 - 1.7j) - operator2 = FermionOperator('7^ 5^ 1^ 0^', 0.) - - mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - [operator1, operator2], n_qubits=8) - - expected_mask = numpy.array([[False, True], [True, True], [True, False], - [True, False], [True, False], [True, True], - [True, False], [False, True]]) + operator2 = FermionOperator('7^ 5^ 1^ 0^', 0.0) + + mask = bit_mask_of_modes_acted_on_by_fermionic_terms([operator1, operator2], n_qubits=8) + + expected_mask = numpy.array( + [ + [False, True], + [True, True], + [True, False], + [True, False], + [True, False], + [True, True], + [True, False], + [False, True], + ] + ) self.assertTrue(numpy.array_equal(mask, expected_mask)) def test_bit_mask_n_qubits_not_specified(self): mask = bit_mask_of_modes_acted_on_by_fermionic_terms( - [FermionOperator('0^ 0') + FermionOperator('2^ 2')]) + [FermionOperator('0^ 0') + FermionOperator('2^ 2')] + ) - self.assertTrue( - numpy.array_equal(mask, numpy.array([[True], [False], [True]]))) + self.assertTrue(numpy.array_equal(mask, numpy.array([[True], [False], [True]]))) class FermionicSwapNetworkTrotterErrorTest(unittest.TestCase): - def test_1D_jellium_trotter_error_matches_low_depth_trotter_error(self): hamiltonian = normal_ordered( jellium_model( hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 1, 5, wigner_seitz_radius=10., spinless=True), + 1, 5, wigner_seitz_radius=10.0, spinless=True + ), spinless=True, - plane_wave=False)) - - error_operator = ( - fermionic_swap_trotter_error_operator_diagonal_two_body(hamiltonian) + plane_wave=False, + ) ) + + error_operator = fermionic_swap_trotter_error_operator_diagonal_two_body(hamiltonian) error_operator.compress() # Unpack result into terms, indices they act on, and whether # they're hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, indices, is_hopping = result old_error_operator = low_depth_second_order_trotter_error_operator( - terms, indices, is_hopping, jellium_only=True) + terms, indices, is_hopping, jellium_only=True + ) old_error_operator -= error_operator self.assertEqual(old_error_operator, FermionOperator.zero()) def test_hubbard_trotter_error_matches_low_depth_trotter_error(self): - hamiltonian = normal_ordered(fermi_hubbard(3, 3, 1., 2.3)) + hamiltonian = normal_ordered(fermi_hubbard(3, 3, 1.0, 2.3)) - error_operator = ( - fermionic_swap_trotter_error_operator_diagonal_two_body(hamiltonian) - ) + error_operator = fermionic_swap_trotter_error_operator_diagonal_two_body(hamiltonian) error_operator.compress() # Unpack result into terms, indices they act on, and whether # they're hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, indices, is_hopping = result old_error_operator = low_depth_second_order_trotter_error_operator( - terms, indices, is_hopping, jellium_only=True) + terms, indices, is_hopping, jellium_only=True + ) old_error_operator -= error_operator self.assertEqual(old_error_operator, FermionOperator.zero()) class SplitOperatorTrotterErrorTest(unittest.TestCase): - def test_split_operator_error_operator_TV_order_against_definition(self): - hamiltonian = (normal_ordered(fermi_hubbard(3, 3, 1., 4.0)) - - 2.3 * FermionOperator.identity()) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + hamiltonian = ( + normal_ordered(fermi_hubbard(3, 3, 1.0, 4.0)) - 2.3 * FermionOperator.identity() + ) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) - error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='T+V')) + error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='T+V' + ) # T-then-V ordered double commutators: [T, [V, T]] + [V, [V, T]] / 2 inner_commutator = normal_ordered(commutator(potential, kinetic)) - error_operator_definition = normal_ordered( - commutator(kinetic, inner_commutator)) - error_operator_definition += normal_ordered( - commutator(potential, inner_commutator)) / 2.0 + error_operator_definition = normal_ordered(commutator(kinetic, inner_commutator)) + error_operator_definition += normal_ordered(commutator(potential, inner_commutator)) / 2.0 error_operator_definition /= 12.0 self.assertEqual(error_operator, error_operator_definition) def test_split_operator_error_operator_VT_order_against_definition(self): - hamiltonian = (normal_ordered(fermi_hubbard(3, 3, 1., 4.0)) - - 2.3 * FermionOperator.identity()) - potential_terms, kinetic_terms = ( - diagonal_coulomb_potential_and_kinetic_terms_as_arrays(hamiltonian)) + hamiltonian = ( + normal_ordered(fermi_hubbard(3, 3, 1.0, 4.0)) - 2.3 * FermionOperator.identity() + ) + potential_terms, kinetic_terms = diagonal_coulomb_potential_and_kinetic_terms_as_arrays( + hamiltonian + ) potential = sum(potential_terms, FermionOperator.zero()) kinetic = sum(kinetic_terms, FermionOperator.zero()) - error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='V+T')) + error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='V+T' + ) # V-then-T ordered double commutators: [V, [T, V]] + [T, [T, V]] / 2 inner_commutator = normal_ordered(commutator(kinetic, potential)) - error_operator_definition = normal_ordered( - commutator(potential, inner_commutator)) - error_operator_definition += normal_ordered( - commutator(kinetic, inner_commutator)) / 2.0 + error_operator_definition = normal_ordered(commutator(potential, inner_commutator)) + error_operator_definition += normal_ordered(commutator(kinetic, inner_commutator)) / 2.0 error_operator_definition /= 12.0 self.assertEqual(error_operator, error_operator_definition) def test_intermediate_interaction_hubbard_TV_order_has_larger_error(self): - hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1., 4.0)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1.0, 4.0)) - TV_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='T+V')) - TV_error_bound = numpy.sum( - numpy.absolute(list(TV_error_operator.terms.values()))) + TV_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='T+V' + ) + TV_error_bound = numpy.sum(numpy.absolute(list(TV_error_operator.terms.values()))) - VT_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='V+T')) - VT_error_bound = numpy.sum( - numpy.absolute(list(VT_error_operator.terms.values()))) + VT_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='V+T' + ) + VT_error_bound = numpy.sum(numpy.absolute(list(VT_error_operator.terms.values()))) self.assertAlmostEqual(TV_error_bound, 1706.66666666666) self.assertAlmostEqual(VT_error_bound, 1365.33333333333) def test_strong_interaction_hubbard_VT_order_gives_larger_error(self): - hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1., 10.0)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1.0, 10.0)) - TV_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='T+V')) - TV_error_bound = numpy.sum( - numpy.absolute(list(TV_error_operator.terms.values()))) + TV_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='T+V' + ) + TV_error_bound = numpy.sum(numpy.absolute(list(TV_error_operator.terms.values()))) - VT_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='V+T')) - VT_error_bound = numpy.sum( - numpy.absolute(list(VT_error_operator.terms.values()))) + VT_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='V+T' + ) + VT_error_bound = numpy.sum(numpy.absolute(list(VT_error_operator.terms.values()))) self.assertGreater(VT_error_bound, TV_error_bound) @@ -309,21 +312,22 @@ def test_jellium_wigner_seitz_10_VT_order_gives_larger_error(self): hamiltonian = normal_ordered( jellium_model( hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 2, 3, wigner_seitz_radius=10., spinless=True), + 2, 3, wigner_seitz_radius=10.0, spinless=True + ), spinless=True, - plane_wave=False)) + plane_wave=False, + ) + ) - TV_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='T+V')) - TV_error_bound = numpy.sum( - numpy.absolute(list(TV_error_operator.terms.values()))) + TV_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='T+V' + ) + TV_error_bound = numpy.sum(numpy.absolute(list(TV_error_operator.terms.values()))) - VT_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='V+T')) - VT_error_bound = numpy.sum( - numpy.absolute(list(VT_error_operator.terms.values()))) + VT_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='V+T' + ) + VT_error_bound = numpy.sum(numpy.absolute(list(VT_error_operator.terms.values()))) self.assertGreater(VT_error_bound, TV_error_bound) @@ -331,20 +335,21 @@ def test_1d_jellium_wigner_seitz_10_VT_order_gives_larger_error(self): hamiltonian = normal_ordered( jellium_model( hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 1, 5, wigner_seitz_radius=10., spinless=True), + 1, 5, wigner_seitz_radius=10.0, spinless=True + ), spinless=True, - plane_wave=False)) - - TV_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='T+V')) - TV_error_bound = numpy.sum( - numpy.absolute(list(TV_error_operator.terms.values()))) - - VT_error_operator = ( - split_operator_trotter_error_operator_diagonal_two_body( - hamiltonian, order='V+T')) - VT_error_bound = numpy.sum( - numpy.absolute(list(VT_error_operator.terms.values()))) + plane_wave=False, + ) + ) + + TV_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='T+V' + ) + TV_error_bound = numpy.sum(numpy.absolute(list(TV_error_operator.terms.values()))) + + VT_error_operator = split_operator_trotter_error_operator_diagonal_two_body( + hamiltonian, order='V+T' + ) + VT_error_bound = numpy.sum(numpy.absolute(list(VT_error_operator.terms.values()))) self.assertGreater(VT_error_bound, TV_error_bound) diff --git a/src/openfermion/circuits/trotter/hubbard_trotter_error.py b/src/openfermion/circuits/trotter/hubbard_trotter_error.py index cd4509901..144296889 100644 --- a/src/openfermion/circuits/trotter/hubbard_trotter_error.py +++ b/src/openfermion/circuits/trotter/hubbard_trotter_error.py @@ -15,8 +15,7 @@ from openfermion.utils import count_qubits from openfermion.transforms.opconversions import normal_ordered -from openfermion.circuits.trotter.low_depth_trotter_error import \ - stagger_with_info +from openfermion.circuits.trotter.low_depth_trotter_error import stagger_with_info def simulation_ordered_grouped_hubbard_terms_with_info(hubbard_hamiltonian): @@ -55,7 +54,9 @@ def simulation_ordered_grouped_hubbard_terms_with_info(hubbard_hamiltonian): for i in range(0, n_qubits - side_length, 2 * side_length): for j in range(2 * bool(i % (4 * side_length)), 2 * side_length, 4): original_ordering[i + j], original_ordering[i + j + 1] = ( - original_ordering[i + j + 1], original_ordering[i + j]) + original_ordering[i + j + 1], + original_ordering[i + j], + ) input_ordering = list(original_ordering) diff --git a/src/openfermion/circuits/trotter/hubbard_trotter_error_test.py b/src/openfermion/circuits/trotter/hubbard_trotter_error_test.py index 423bbc7c7..7887381e0 100644 --- a/src/openfermion/circuits/trotter/hubbard_trotter_error_test.py +++ b/src/openfermion/circuits/trotter/hubbard_trotter_error_test.py @@ -15,15 +15,16 @@ from openfermion.ops.operators import FermionOperator from openfermion.hamiltonians import fermi_hubbard from openfermion.circuits.trotter.hubbard_trotter_error import ( - simulation_ordered_grouped_hubbard_terms_with_info) + simulation_ordered_grouped_hubbard_terms_with_info, +) from openfermion.circuits.trotter.low_depth_trotter_error import ( low_depth_second_order_trotter_error_bound, - low_depth_second_order_trotter_error_operator) + low_depth_second_order_trotter_error_operator, +) from openfermion.transforms.opconversions import normal_ordered class ErrorOperatorTest(unittest.TestCase): - def test_error_operator(self): FO = FermionOperator @@ -34,62 +35,45 @@ def test_error_operator(self): if i in [0, 2]: terms.append( - normal_ordered( - FO(((i + 1, 1), (i, 1), (i + 1, 0), (i, 0)), - 3.18309886184))) + normal_ordered(FO(((i + 1, 1), (i, 1), (i + 1, 0), (i, 0)), 3.18309886184)) + ) if i < 2: terms.append( normal_ordered( - FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), - 22.2816920329))) + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), 22.2816920329) + ) + ) self.assertAlmostEqual( - low_depth_second_order_trotter_error_operator(terms).terms[((3, 1), - (2, 1), - (1, 1), - (2, 0), - (1, 0), - (0, - 0))], - 0.75) + low_depth_second_order_trotter_error_operator(terms).terms[ + ((3, 1), (2, 1), (1, 1), (2, 0), (1, 0), (0, 0)) + ], + 0.75, + ) class ErrorBoundTest(unittest.TestCase): - def test_error_bound_superset_hubbard(self): FO = FermionOperator terms = [] for i in [0, 2]: - terms.append( - FO(((i, 1), (i + 1, 0)), -0.01) + FO(((i + 1, 1), - (i, 0)), -0.01)) + terms.append(FO(((i, 1), (i + 1, 0)), -0.01) + FO(((i + 1, 1), (i, 0)), -0.01)) for i in [0, 1]: - terms.append( - FO(((i, 1), (i + 2, 0)), -0.03) + FO(((i + 2, 1), - (i, 0)), -0.03)) - terms.append(FO(((i + 2, 1), (i, 1), (i + 2, 0), (i, 0)), 3.)) - - indices = [ - set([0, 1]), - set([2, 3]), - set([0, 2]), - set([0, 2]), - set([1, 3]), - set([1, 3]) - ] + terms.append(FO(((i, 1), (i + 2, 0)), -0.03) + FO(((i + 2, 1), (i, 0)), -0.03)) + terms.append(FO(((i + 2, 1), (i, 1), (i + 2, 0), (i, 0)), 3.0)) + + indices = [set([0, 1]), set([2, 3]), set([0, 2]), set([0, 2]), set([1, 3]), set([1, 3])] is_hopping_operator = [True, True, True, False, True, False] self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(terms, indices, - is_hopping_operator), - 0.0608) + low_depth_second_order_trotter_error_bound(terms, indices, is_hopping_operator), 0.0608 + ) def test_error_bound_using_info_even_side_length(self): # Generate the Hamiltonian. - hamiltonian = normal_ordered( - fermi_hubbard(4, 4, 0.5, 0.2, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 0.5, 0.2, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -98,13 +82,12 @@ def test_error_bound_using_info_even_side_length(self): terms, indices, is_hopping = result self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(terms, indices, - is_hopping), 13.59) + low_depth_second_order_trotter_error_bound(terms, indices, is_hopping), 13.59 + ) def test_error_bound_using_info_odd_side_length_verbose(self): # Generate the Hamiltonian. - hamiltonian = normal_ordered( - fermi_hubbard(5, 5, -0.5, 0.3, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(5, 5, -0.5, 0.3, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -114,17 +97,13 @@ def test_error_bound_using_info_odd_side_length_verbose(self): self.assertAlmostEqual( 32.025, - low_depth_second_order_trotter_error_bound(terms, - indices, - is_hopping, - verbose=True)) + low_depth_second_order_trotter_error_bound(terms, indices, is_hopping, verbose=True), + ) class OrderedHubbardTermsMoreInfoTest(unittest.TestCase): - def test_sum_of_ordered_terms_equals_full_side_length_2_hopping_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(2, 2, 1., 0.0, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(2, 2, 1.0, 0.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -137,30 +116,25 @@ def test_sum_of_ordered_terms_equals_full_side_length_2_hopping_only(self): self.assertTrue(terms_total == hamiltonian) def test_sum_of_ordered_terms_equals_full_hamiltonian_even_side_len(self): - hamiltonian = normal_ordered( - fermi_hubbard(4, 4, 10.0, 0.3, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 10.0, 0.3, periodic=False)) hamiltonian.compress() - terms = simulation_ordered_grouped_hubbard_terms_with_info( - hamiltonian)[0] + terms = simulation_ordered_grouped_hubbard_terms_with_info(hamiltonian)[0] terms_total = sum(terms, FermionOperator.zero()) self.assertTrue(terms_total == hamiltonian) def test_sum_of_ordered_terms_equals_full_hamiltonian_odd_side_len(self): - hamiltonian = normal_ordered( - fermi_hubbard(5, 5, 1.0, -0.3, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(5, 5, 1.0, -0.3, periodic=False)) hamiltonian.compress() - terms = simulation_ordered_grouped_hubbard_terms_with_info( - hamiltonian)[0] + terms = simulation_ordered_grouped_hubbard_terms_with_info(hamiltonian)[0] terms_total = sum(terms, FermionOperator.zero()) self.assertTrue(terms_total == hamiltonian) def test_correct_indices_terms_with_info(self): - hamiltonian = normal_ordered( - fermi_hubbard(5, 5, 1., -1., periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(5, 5, 1.0, -1.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -173,12 +147,12 @@ def test_correct_indices_terms_with_info(self): term_indices = set() for single_term in term: term_indices = term_indices.union( - [single_term[j][0] for j in range(len(single_term))]) + [single_term[j][0] for j in range(len(single_term))] + ) self.assertEqual(term_indices, indices[i]) def test_is_hopping_operator_terms_with_info(self): - hamiltonian = normal_ordered( - fermi_hubbard(5, 5, 1., -1., periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(5, 5, 1.0, -1.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -188,13 +162,11 @@ def test_is_hopping_operator_terms_with_info(self): for i in range(len(terms)): single_term = list(terms[i].terms)[0] - is_hopping_term = not (single_term[1][1] or - single_term[0][0] == single_term[1][0]) + is_hopping_term = not (single_term[1][1] or single_term[0][0] == single_term[1][0]) self.assertEqual(is_hopping_term, is_hopping[i]) def test_total_length_side_length_2_hopping_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(2, 2, 1., 0.0, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(2, 2, 1.0, 0.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -205,8 +177,7 @@ def test_total_length_side_length_2_hopping_only(self): self.assertEqual(len(terms), 8) def test_total_length_odd_side_length_hopping_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(3, 3, 1., 0.0, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(3, 3, 1.0, 0.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -217,8 +188,7 @@ def test_total_length_odd_side_length_hopping_only(self): self.assertEqual(len(terms), 24) def test_total_length_even_side_length_hopping_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(4, 4, 1., 0.0, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 1.0, 0.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -229,8 +199,7 @@ def test_total_length_even_side_length_hopping_only(self): self.assertEqual(len(terms), 48) def test_total_length_side_length_2_onsite_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(2, 2, 0.0, 1., periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(2, 2, 0.0, 1.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -241,8 +210,7 @@ def test_total_length_side_length_2_onsite_only(self): self.assertEqual(len(terms), 4) def test_total_length_odd_side_length_onsite_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(3, 3, 0.0, 1., periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(3, 3, 0.0, 1.0, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -253,8 +221,7 @@ def test_total_length_odd_side_length_onsite_only(self): self.assertEqual(len(terms), 9) def test_total_length_even_side_length_onsite_only(self): - hamiltonian = normal_ordered( - fermi_hubbard(4, 4, 0., -0.3, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(4, 4, 0.0, -0.3, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're @@ -265,8 +232,7 @@ def test_total_length_even_side_length_onsite_only(self): self.assertEqual(len(terms), 16) def test_total_length_odd_side_length_full_hubbard(self): - hamiltonian = normal_ordered( - fermi_hubbard(5, 5, -1., -0.3, periodic=False)) + hamiltonian = normal_ordered(fermi_hubbard(5, 5, -1.0, -0.3, periodic=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're diff --git a/src/openfermion/circuits/trotter/low_depth_trotter_error.py b/src/openfermion/circuits/trotter/low_depth_trotter_error.py index 8206cf356..d5565a9ec 100644 --- a/src/openfermion/circuits/trotter/low_depth_trotter_error.py +++ b/src/openfermion/circuits/trotter/low_depth_trotter_error.py @@ -16,15 +16,15 @@ from openfermion.utils import count_qubits from openfermion.transforms.opconversions import normal_ordered from openfermion.utils.commutators import ( - double_commutator, trivially_double_commutes_dual_basis, - trivially_double_commutes_dual_basis_using_term_info) + double_commutator, + trivially_double_commutes_dual_basis, + trivially_double_commutes_dual_basis_using_term_info, +) -def low_depth_second_order_trotter_error_operator(terms, - indices=None, - is_hopping_operator=None, - jellium_only=False, - verbose=False): +def low_depth_second_order_trotter_error_operator( + terms, indices=None, is_hopping_operator=None, jellium_only=False, verbose=False +): """Determine the difference between the exact generator of unitary evolution and the approximate generator given by the second-order Trotter-Suzuki expansion. @@ -55,30 +55,40 @@ def low_depth_second_order_trotter_error_operator(terms, if verbose: import time + start = time.time() error_operator = FermionOperator.zero() for beta in range(n_terms): if verbose and beta % (n_terms // 30) == 0: - print('%4.3f percent done in' % ((float(beta) / n_terms)**3 * 100), - time.time() - start) + print( + '%4.3f percent done in' % ((float(beta) / n_terms) ** 3 * 100), time.time() - start + ) for alpha in range(beta + 1): for alpha_prime in range(beta): # If we have pre-computed info on indices, use it to determine # trivial double commutation. if more_info: - if (not trivially_double_commutes_dual_basis_using_term_info( # pylint: disable=C - indices[alpha], indices[beta], indices[alpha_prime], - is_hopping_operator[alpha], - is_hopping_operator[beta], - is_hopping_operator[alpha_prime], jellium_only)): + if not trivially_double_commutes_dual_basis_using_term_info( # pylint: disable=C + indices[alpha], + indices[beta], + indices[alpha_prime], + is_hopping_operator[alpha], + is_hopping_operator[beta], + is_hopping_operator[alpha_prime], + jellium_only, + ): # Determine the result of the double commutator. double_com = double_commutator( - terms[alpha], terms[beta], terms[alpha_prime], - indices[beta], indices[alpha_prime], + terms[alpha], + terms[beta], + terms[alpha_prime], + indices[beta], + indices[alpha_prime], is_hopping_operator[beta], - is_hopping_operator[alpha_prime]) + is_hopping_operator[alpha_prime], + ) if alpha == beta: double_com /= 2.0 @@ -87,9 +97,9 @@ def low_depth_second_order_trotter_error_operator(terms, # If we don't have more info, check for trivial double # commutation using the terms directly. elif not trivially_double_commutes_dual_basis( - terms[alpha], terms[beta], terms[alpha_prime]): - double_com = double_commutator(terms[alpha], terms[beta], - terms[alpha_prime]) + terms[alpha], terms[beta], terms[alpha_prime] + ): + double_com = double_commutator(terms[alpha], terms[beta], terms[alpha_prime]) if alpha == beta: double_com /= 2.0 @@ -100,11 +110,9 @@ def low_depth_second_order_trotter_error_operator(terms, return error_operator -def low_depth_second_order_trotter_error_bound(terms, - indices=None, - is_hopping_operator=None, - jellium_only=False, - verbose=False): +def low_depth_second_order_trotter_error_bound( + terms, indices=None, is_hopping_operator=None, jellium_only=False, verbose=False +): """Numerically upper bound the error in the ground state energy for the second-order Trotter-Suzuki expansion. @@ -135,12 +143,16 @@ def low_depth_second_order_trotter_error_bound(terms, numpy.absolute( list( low_depth_second_order_trotter_error_operator( - terms, indices, is_hopping_operator, jellium_only, - verbose).terms.values()))) + terms, indices, is_hopping_operator, jellium_only, verbose + ).terms.values() + ) + ) + ) def simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, input_ordering=None, external_potential_at_end=False): + hamiltonian, input_ordering=None, external_potential_at_end=False +): """Give terms from the dual basis Hamiltonian in simulated order. Uses the simulation ordering, grouping terms into hopping @@ -193,10 +205,8 @@ def simulation_ordered_grouped_low_depth_terms_with_info( # ordering has been reversed. parity = 0 while input_ordering != final_ordering: - results = stagger_with_info(hamiltonian, input_ordering, parity, - external_potential_at_end) - terms_in_layer, indices_in_layer, is_hopping_operator_in_layer = ( - results) + results = stagger_with_info(hamiltonian, input_ordering, parity, external_potential_at_end) + terms_in_layer, indices_in_layer, is_hopping_operator_in_layer = results ordered_terms.extend(terms_in_layer) ordered_indices.extend(indices_in_layer) @@ -215,8 +225,7 @@ def simulation_ordered_grouped_low_depth_terms_with_info( for qubit in range(n_qubits): coeff = hamiltonian.terms.get(((qubit, 1), (qubit, 0)), 0.0) if coeff: - terms_in_final_layer.append( - FermionOperator(((qubit, 1), (qubit, 0)), coeff)) + terms_in_final_layer.append(FermionOperator(((qubit, 1), (qubit, 0)), coeff)) indices_in_final_layer.append(set((qubit,))) is_hopping_operator_in_final_layer.append(False) @@ -227,10 +236,7 @@ def simulation_ordered_grouped_low_depth_terms_with_info( return (ordered_terms, ordered_indices, ordered_is_hopping_operator) -def stagger_with_info(hamiltonian, - input_ordering, - parity, - external_potential_at_end=False): +def stagger_with_info(hamiltonian, input_ordering, parity, external_potential_at_end=False): """Give terms simulated in a single stagger of a Trotter step. Groups terms into hopping (i^ j + j^ i) and number @@ -275,34 +281,34 @@ def stagger_with_info(hamiltonian, # Calculate the hopping operators in the Hamiltonian. left_hopping_operator = FermionOperator( - ((left, 1), (right, 0)), - hamiltonian.terms.get(((left, 1), (right, 0)), 0.0)) + ((left, 1), (right, 0)), hamiltonian.terms.get(((left, 1), (right, 0)), 0.0) + ) right_hopping_operator = FermionOperator( - ((right, 1), (left, 0)), - hamiltonian.terms.get(((right, 1), (left, 0)), 0.0)) + ((right, 1), (left, 0)), hamiltonian.terms.get(((right, 1), (left, 0)), 0.0) + ) # Calculate the two-number operator l^ r^ l r in the Hamiltonian. two_number_operator = FermionOperator( ((left, 1), (right, 1), (left, 0), (right, 0)), - hamiltonian.terms.get( - ((left, 1), (right, 1), (left, 0), (right, 0)), 0.0)) + hamiltonian.terms.get(((left, 1), (right, 1), (left, 0), (right, 0)), 0.0), + ) if not external_potential_at_end: # Calculate the left number operator, left^ left. left_number_operator = FermionOperator( - ((left, 1), (left, 0)), - hamiltonian.terms.get(((left, 1), (left, 0)), 0.0)) + ((left, 1), (left, 0)), hamiltonian.terms.get(((left, 1), (left, 0)), 0.0) + ) # Calculate the right number operator, right^ right. right_number_operator = FermionOperator( - ((right, 1), (right, 0)), - hamiltonian.terms.get(((right, 1), (right, 0)), 0.0)) + ((right, 1), (right, 0)), hamiltonian.terms.get(((right, 1), (right, 0)), 0.0) + ) # Divide single-number terms by n_qubits-1 to avoid over-accounting # for the interspersed rotations. Each qubit is swapped n_qubits-1 # times total. - left_number_operator /= (n_qubits - 1) - right_number_operator /= (n_qubits - 1) + left_number_operator /= n_qubits - 1 + right_number_operator /= n_qubits - 1 else: left_number_operator = FermionOperator.zero() @@ -310,27 +316,27 @@ def stagger_with_info(hamiltonian, # If the overall hopping operator isn't close to zero, append it. # Include the indices it acts on and that it's a hopping operator. - if not (left_hopping_operator + - right_hopping_operator) == FermionOperator.zero(): - terms_in_layer.append(left_hopping_operator + - right_hopping_operator) + if not (left_hopping_operator + right_hopping_operator) == FermionOperator.zero(): + terms_in_layer.append(left_hopping_operator + right_hopping_operator) indices_in_layer.append(set((left, right))) is_hopping_operator_in_layer.append(True) # If the overall number operator isn't close to zero, append it. # Include the indices it acts on and that it's a number operator. - if not (two_number_operator + left_number_operator + - right_number_operator) == FermionOperator.zero(): - terms_in_layer.append(two_number_operator + left_number_operator + - right_number_operator) + if ( + not (two_number_operator + left_number_operator + right_number_operator) + == FermionOperator.zero() + ): + terms_in_layer.append( + two_number_operator + left_number_operator + right_number_operator + ) terms_in_layer[-1].compress() indices_in_layer.append(set((left, right))) is_hopping_operator_in_layer.append(False) # Modify the current Jordan-Wigner canonical ordering in-place. - input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], - input_ordering[i]) + input_ordering[i], input_ordering[i + 1] = (input_ordering[i + 1], input_ordering[i]) return terms_in_layer, indices_in_layer, is_hopping_operator_in_layer diff --git a/src/openfermion/circuits/trotter/low_depth_trotter_error_test.py b/src/openfermion/circuits/trotter/low_depth_trotter_error_test.py index 2836e80d3..1d69bf627 100644 --- a/src/openfermion/circuits/trotter/low_depth_trotter_error_test.py +++ b/src/openfermion/circuits/trotter/low_depth_trotter_error_test.py @@ -13,8 +13,10 @@ import unittest from openfermion.hamiltonians import ( - jellium_model, hypercube_grid_with_given_wigner_seitz_radius_and_filling, - wigner_seitz_length_scale) + jellium_model, + hypercube_grid_with_given_wigner_seitz_radius_and_filling, + wigner_seitz_length_scale, +) from openfermion.circuits.trotter.low_depth_trotter_error import ( FermionOperator, low_depth_second_order_trotter_error_bound, @@ -27,7 +29,6 @@ class ErrorOperatorTest(unittest.TestCase): - def test_error_operator(self): FO = FermionOperator @@ -39,111 +40,108 @@ def test_error_operator(self): terms.append(FO(((i, 1), ((i + 3) % 4, 0)), -0.012337005501361697)) terms.append( normal_ordered( - FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), - 3.1830988618379052))) + FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), 3.1830988618379052) + ) + ) if i // 2: terms.append( normal_ordered( - FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), - 22.281692032865351))) + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), 22.281692032865351) + ) + ) self.assertAlmostEqual( - low_depth_second_order_trotter_error_operator( - terms, jellium_only=True).terms[((3, 1), (2, 1), (1, 1), (2, 0), - (1, 0), (0, 0))], - -0.562500000003) + low_depth_second_order_trotter_error_operator(terms, jellium_only=True).terms[ + ((3, 1), (2, 1), (1, 1), (2, 0), (1, 0), (0, 0)) + ], + -0.562500000003, + ) class ErrorBoundTest(unittest.TestCase): - def setUp(self): FO = FermionOperator self.terms = [] for i in range(4): self.terms.append(FO(((i, 1), (i, 0)), 0.018505508252042547)) - self.terms.append( - FO(((i, 1), ((i + 1) % 4, 0)), -0.012337005501361697)) - self.terms.append( - FO(((i, 1), ((i + 2) % 4, 0)), 0.0061685027506808475)) - self.terms.append( - FO(((i, 1), ((i + 3) % 4, 0)), -0.012337005501361697)) + self.terms.append(FO(((i, 1), ((i + 1) % 4, 0)), -0.012337005501361697)) + self.terms.append(FO(((i, 1), ((i + 2) % 4, 0)), 0.0061685027506808475)) + self.terms.append(FO(((i, 1), ((i + 3) % 4, 0)), -0.012337005501361697)) self.terms.append( normal_ordered( - FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), - 3.1830988618379052))) + FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), 3.1830988618379052) + ) + ) if i // 2: self.terms.append( normal_ordered( - FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), - 22.281692032865351))) + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), 22.281692032865351) + ) + ) def test_error_bound(self): self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(self.terms, - jellium_only=True), - 6.92941899358) + low_depth_second_order_trotter_error_bound(self.terms, jellium_only=True), 6.92941899358 + ) def test_error_bound_using_info_1d(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension=1, grid_length=4, wigner_seitz_radius=10.) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension=1, grid_length=4, wigner_seitz_radius=10.0 + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, indices, is_hopping = result self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(terms, indices, - is_hopping), - 7.4239378440953283) + low_depth_second_order_trotter_error_bound(terms, indices, is_hopping), + 7.4239378440953283, + ) def test_error_bound_using_info_1d_with_input_ordering(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension=1, grid_length=4, wigner_seitz_radius=10.) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension=1, grid_length=4, wigner_seitz_radius=10.0 + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, input_ordering=[0, 1, 2, 3]) + hamiltonian, input_ordering=[0, 1, 2, 3] + ) terms, indices, is_hopping = result self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(terms, indices, - is_hopping), - 7.4239378440953283) + low_depth_second_order_trotter_error_bound(terms, indices, is_hopping), + 7.4239378440953283, + ) def test_error_bound_using_info_2d_verbose(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension=2, grid_length=3, wigner_seitz_radius=10.) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension=2, grid_length=3, wigner_seitz_radius=10.0 + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, indices, is_hopping = result self.assertAlmostEqual( - low_depth_second_order_trotter_error_bound(terms, - indices, - is_hopping, - jellium_only=True, - verbose=True), - 0.052213321121580794) + low_depth_second_order_trotter_error_bound( + terms, indices, is_hopping, jellium_only=True, verbose=True + ), + 0.052213321121580794, + ) class OrderedDualBasisTermsMoreInfoTest(unittest.TestCase): - def test_sum_of_ordered_terms_equals_full_hamiltonian(self): grid_length = 4 dimension = 2 @@ -154,18 +152,15 @@ def test_sum_of_ordered_terms_equals_full_hamiltonian(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() - terms = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian)[0] + terms = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian)[0] terms_total = sum(terms, FermionOperator.zero()) - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) @@ -182,18 +177,17 @@ def test_sum_of_ordered_terms_equals_full_hamiltonian_rots_at_end(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() terms = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, external_potential_at_end=True)[0] + hamiltonian, external_potential_at_end=True + )[0] terms_total = sum(terms, FermionOperator.zero()) - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless=True, plane_wave=False) @@ -209,16 +203,14 @@ def test_correct_indices_terms_with_info(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, indices, _ = result for i in range(len(terms)): @@ -226,7 +218,8 @@ def test_correct_indices_terms_with_info(self): term_indices = set() for single_term in term: term_indices = term_indices.union( - [single_term[j][0] for j in range(len(single_term))]) + [single_term[j][0] for j in range(len(single_term))] + ) self.assertEqual(term_indices, indices[i]) def test_is_hopping_operator_terms_with_info(self): @@ -237,22 +230,19 @@ def test_is_hopping_operator_terms_with_info(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, _, is_hopping = result for i in range(len(terms)): single_term = list(terms[i].terms)[0] - is_hopping_term = not (single_term[1][1] or - single_term[0][0] == single_term[1][0]) + is_hopping_term = not (single_term[1][1] or single_term[0][0] == single_term[1][0]) self.assertEqual(is_hopping_term, is_hopping[i]) def test_correct_indices_terms_with_info_external_pot_at_end(self): @@ -264,16 +254,16 @@ def test_correct_indices_terms_with_info_external_pot_at_end(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, external_potential_at_end=True) + hamiltonian, external_potential_at_end=True + ) terms, indices, _ = result for i in range(len(terms)): @@ -281,7 +271,8 @@ def test_correct_indices_terms_with_info_external_pot_at_end(self): term_indices = set() for single_term in term: term_indices = term_indices.union( - [single_term[j][0] for j in range(len(single_term))]) + [single_term[j][0] for j in range(len(single_term))] + ) self.assertEqual(term_indices, indices[i]) # Last four terms are the rotations @@ -295,22 +286,21 @@ def test_is_hopping_operator_terms_with_info_external_pot_at_end(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian, external_potential_at_end=True) + hamiltonian, external_potential_at_end=True + ) terms, _, is_hopping = result for i in range(len(terms)): single_term = list(terms[i].terms)[0] - is_hopping_term = not (single_term[1][1] or - single_term[0][0] == single_term[1][0]) + is_hopping_term = not (single_term[1][1] or single_term[0][0] == single_term[1][0]) self.assertEqual(is_hopping_term, is_hopping[i]) # Last four terms are the rotations @@ -325,32 +315,29 @@ def test_total_length(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius, - 1. / inverse_filling_fraction) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius, 1.0 / inverse_filling_fraction + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() # Unpack result into terms, indices they act on, and whether they're # hopping operators. - result = simulation_ordered_grouped_low_depth_terms_with_info( - hamiltonian) + result = simulation_ordered_grouped_low_depth_terms_with_info(hamiltonian) terms, _, _ = result self.assertEqual(len(terms), n_qubits * (n_qubits - 1)) class OrderedDualBasisTermsNoInfoTest(unittest.TestCase): - def test_all_terms_in_standardized_dual_basis_jellium_hamiltonian(self): grid_length = 4 dimension = 1 # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius=10.) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius=10.0 + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() terms = ordered_low_depth_terms_no_info(hamiltonian) @@ -359,21 +346,20 @@ def test_all_terms_in_standardized_dual_basis_jellium_hamiltonian(self): expected_terms = [] for i in range(grid_length**dimension): expected_terms.append(FO(((i, 1), (i, 0)), 0.018505508252042547)) - expected_terms.append( - FO(((i, 1), ((i + 1) % 4, 0)), -0.012337005501361697)) - expected_terms.append( - FO(((i, 1), ((i + 2) % 4, 0)), 0.0061685027506808475)) - expected_terms.append( - FO(((i, 1), ((i + 3) % 4, 0)), -0.012337005501361697)) + expected_terms.append(FO(((i, 1), ((i + 1) % 4, 0)), -0.012337005501361697)) + expected_terms.append(FO(((i, 1), ((i + 2) % 4, 0)), 0.0061685027506808475)) + expected_terms.append(FO(((i, 1), ((i + 3) % 4, 0)), -0.012337005501361697)) expected_terms.append( normal_ordered( - FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), - 3.1830988618379052))) + FO(((i, 1), ((i + 1) % 4, 1), (i, 0), ((i + 1) % 4, 0)), 3.1830988618379052) + ) + ) if i // 2: expected_terms.append( normal_ordered( - FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), - 22.281692032865351))) + FO(((i, 1), ((i + 2) % 4, 1), (i, 0), ((i + 2) % 4, 0)), 22.281692032865351) + ) + ) for term in terms: found_in_other = False @@ -397,9 +383,9 @@ def test_sum_of_ordered_terms_equals_full_hamiltonian(self): # Generate the Hamiltonian. grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension, grid_length, wigner_seitz_radius) - hamiltonian = normal_ordered( - jellium_model(grid, spinless=True, plane_wave=False)) + dimension, grid_length, wigner_seitz_radius + ) + hamiltonian = normal_ordered(jellium_model(grid, spinless=True, plane_wave=False)) hamiltonian.compress() terms = ordered_low_depth_terms_no_info(hamiltonian) diff --git a/src/openfermion/circuits/trotter/simulate_trotter.py b/src/openfermion/circuits/trotter/simulate_trotter.py index f5d0fe223..32c58e82f 100644 --- a/src/openfermion/circuits/trotter/simulate_trotter.py +++ b/src/openfermion/circuits/trotter/simulate_trotter.py @@ -16,21 +16,24 @@ import cirq from openfermion import ops -from openfermion.circuits.trotter.trotter_algorithm import (Hamiltonian, - TrotterStep, - TrotterAlgorithm) -from openfermion.circuits.trotter.algorithms import (LINEAR_SWAP_NETWORK, - LOW_RANK) - - -def simulate_trotter(qubits: Sequence[cirq.Qid], - hamiltonian: Hamiltonian, - time: float, - n_steps: int = 1, - order: int = 0, - algorithm: Optional[TrotterAlgorithm] = None, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: +from openfermion.circuits.trotter.trotter_algorithm import ( + Hamiltonian, + TrotterStep, + TrotterAlgorithm, +) +from openfermion.circuits.trotter.algorithms import LINEAR_SWAP_NETWORK, LOW_RANK + + +def simulate_trotter( + qubits: Sequence[cirq.Qid], + hamiltonian: Hamiltonian, + time: float, + n_steps: int = 1, + order: int = 0, + algorithm: Optional[TrotterAlgorithm] = None, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, +) -> cirq.OP_TREE: """Simulate Hamiltonian evolution using a Trotter-Suzuki product formula. The input is a Hamiltonian represented as an InteractionOperator or @@ -86,14 +89,14 @@ def simulate_trotter(qubits: Sequence[cirq.Qid], raise TypeError( 'The input Hamiltonian was a {} but the chosen Trotter step ' 'algorithm only supports Hamiltonians of type {}'.format( - type(hamiltonian).__name__, - {cls.__name__ for cls in algorithm.supported_types})) + type(hamiltonian).__name__, {cls.__name__ for cls in algorithm.supported_types} + ) + ) # Select the Trotter step to use - trotter_step = _select_trotter_step(hamiltonian, - order, - algorithm, - controlled=control_qubit is not None) + trotter_step = _select_trotter_step( + hamiltonian, order, algorithm, controlled=control_qubit is not None + ) # Get ready to perform Trotter steps yield trotter_step.prepare(qubits, control_qubit) @@ -101,42 +104,40 @@ def simulate_trotter(qubits: Sequence[cirq.Qid], # Perform Trotter steps step_time = time / n_steps for _ in range(n_steps): - yield _perform_trotter_step(qubits, step_time, order, trotter_step, - control_qubit) - qubits, control_qubit = trotter_step.step_qubit_permutation( - qubits, control_qubit) + yield _perform_trotter_step(qubits, step_time, order, trotter_step, control_qubit) + qubits, control_qubit = trotter_step.step_qubit_permutation(qubits, control_qubit) # Finish yield trotter_step.finish(qubits, n_steps, control_qubit, omit_final_swaps) -def _perform_trotter_step(qubits: Sequence[cirq.Qid], time: float, order: int, - trotter_step: TrotterStep, - control_qubit: Optional[cirq.Qid]) -> cirq.OP_TREE: +def _perform_trotter_step( + qubits: Sequence[cirq.Qid], + time: float, + order: int, + trotter_step: TrotterStep, + control_qubit: Optional[cirq.Qid], +) -> cirq.OP_TREE: """Perform a Trotter step.""" if order <= 1: yield trotter_step.trotter_step(qubits, time, control_qubit) else: # Recursively split this Trotter step into five smaller steps. # The first two and last two steps use this amount of time - split_time = time / (4 - 4**(1 / (2 * order - 1))) + split_time = time / (4 - 4 ** (1 / (2 * order - 1))) for _ in range(2): - yield _perform_trotter_step(qubits, split_time, order - 1, - trotter_step, control_qubit) - qubits, control_qubit = trotter_step.step_qubit_permutation( - qubits, control_qubit) + yield _perform_trotter_step(qubits, split_time, order - 1, trotter_step, control_qubit) + qubits, control_qubit = trotter_step.step_qubit_permutation(qubits, control_qubit) - yield _perform_trotter_step(qubits, time - 4 * split_time, order - 1, - trotter_step, control_qubit) - qubits, control_qubit = trotter_step.step_qubit_permutation( - qubits, control_qubit) + yield _perform_trotter_step( + qubits, time - 4 * split_time, order - 1, trotter_step, control_qubit + ) + qubits, control_qubit = trotter_step.step_qubit_permutation(qubits, control_qubit) for _ in range(2): - yield _perform_trotter_step(qubits, split_time, order - 1, - trotter_step, control_qubit) - qubits, control_qubit = trotter_step.step_qubit_permutation( - qubits, control_qubit) + yield _perform_trotter_step(qubits, split_time, order - 1, trotter_step, control_qubit) + qubits, control_qubit = trotter_step.step_qubit_permutation(qubits, control_qubit) def _select_trotter_algorithm(hamiltonian: Hamiltonian) -> TrotterAlgorithm: @@ -145,37 +146,46 @@ def _select_trotter_algorithm(hamiltonian: Hamiltonian) -> TrotterAlgorithm: elif isinstance(hamiltonian, ops.InteractionOperator): return LOW_RANK else: - raise TypeError('Failed to select a default Trotter algorithm ' - 'for Hamiltonian of type {}.'.format( - type(hamiltonian).__name__)) + raise TypeError( + 'Failed to select a default Trotter algorithm ' + 'for Hamiltonian of type {}.'.format(type(hamiltonian).__name__) + ) -def _select_trotter_step(hamiltonian: Hamiltonian, order: int, - algorithm: TrotterAlgorithm, - controlled: bool) -> TrotterStep: +def _select_trotter_step( + hamiltonian: Hamiltonian, order: int, algorithm: TrotterAlgorithm, controlled: bool +) -> TrotterStep: """Select a particular Trotter step from a Trotter step algorithm.""" if controlled: if order == 0: trotter_step = algorithm.controlled_asymmetric(hamiltonian) if trotter_step is None: - raise ValueError('The chosen Trotter step algorithm does not ' - 'support the order 0 (asymmetric) formula ' - 'with a control qubit.') + raise ValueError( + 'The chosen Trotter step algorithm does not ' + 'support the order 0 (asymmetric) formula ' + 'with a control qubit.' + ) else: trotter_step = algorithm.controlled_symmetric(hamiltonian) if trotter_step is None: - raise ValueError('The chosen Trotter step algorithm does not ' - 'support higher (> 0) order formulas ' - 'with a control qubit.') + raise ValueError( + 'The chosen Trotter step algorithm does not ' + 'support higher (> 0) order formulas ' + 'with a control qubit.' + ) else: if order == 0: trotter_step = algorithm.asymmetric(hamiltonian) if trotter_step is None: - raise ValueError('The chosen Trotter step algorithm does not ' - 'support the order 0 (asymmetric) formula.') + raise ValueError( + 'The chosen Trotter step algorithm does not ' + 'support the order 0 (asymmetric) formula.' + ) else: trotter_step = algorithm.symmetric(hamiltonian) if trotter_step is None: - raise ValueError('The chosen Trotter step algorithm does not ' - 'support higher (> 0) order formulas.') + raise ValueError( + 'The chosen Trotter step algorithm does not ' + 'support higher (> 0) order formulas.' + ) return trotter_step diff --git a/src/openfermion/circuits/trotter/simulate_trotter_test.py b/src/openfermion/circuits/trotter/simulate_trotter_test.py index 4da8c25ec..ab5365f7b 100644 --- a/src/openfermion/circuits/trotter/simulate_trotter_test.py +++ b/src/openfermion/circuits/trotter/simulate_trotter_test.py @@ -30,13 +30,12 @@ def fidelity(state1, state2): - return abs(numpy.dot(state1, numpy.conjugate(state2)))**2 + return abs(numpy.dot(state1, numpy.conjugate(state2))) ** 2 -def produce_simulation_test_parameters(hamiltonian: Hamiltonian, - time: float, - seed: Optional[int] = None - ) -> Tuple[numpy.ndarray, numpy.ndarray]: +def produce_simulation_test_parameters( + hamiltonian: Hamiltonian, time: float, seed: Optional[int] = None +) -> Tuple[numpy.ndarray, numpy.ndarray]: """Produce objects for testing Hamiltonian simulation. Produces a random initial state and evolves it under the given Hamiltonian @@ -56,11 +55,10 @@ def produce_simulation_test_parameters(hamiltonian: Hamiltonian, # Simulate exact evolution hamiltonian_sparse = openfermion.get_sparse_operator(hamiltonian) - exact_state = scipy.sparse.linalg.expm_multiply( - -1j * time * hamiltonian_sparse, initial_state) + exact_state = scipy.sparse.linalg.expm_multiply(-1j * time * hamiltonian_sparse, initial_state) # Make sure the time is not too small - assert fidelity(exact_state, initial_state) < .95 + assert fidelity(exact_state, initial_state) < 0.95 return initial_state, exact_state @@ -72,84 +70,176 @@ def produce_simulation_test_parameters(hamiltonian: Hamiltonian, short_time = 0.05 # 5-qubit random DiagonalCoulombHamiltonian -diag_coul_hamiltonian = openfermion.random_diagonal_coulomb_hamiltonian( - 5, real=False, seed=65233) -diag_coul_initial_state, diag_coul_exact_state = ( - produce_simulation_test_parameters(diag_coul_hamiltonian, - long_time, - seed=49075)) +diag_coul_hamiltonian = openfermion.random_diagonal_coulomb_hamiltonian(5, real=False, seed=65233) +diag_coul_initial_state, diag_coul_exact_state = produce_simulation_test_parameters( + diag_coul_hamiltonian, long_time, seed=49075 +) # Hubbard model, reordered hubbard_model = openfermion.fermi_hubbard(2, 2, 1.0, 4.0) hubbard_model = openfermion.reorder(hubbard_model, openfermion.up_then_down) -hubbard_hamiltonian = openfermion.get_diagonal_coulomb_hamiltonian( - hubbard_model) -hubbard_initial_state, hubbard_exact_state = ( - produce_simulation_test_parameters(hubbard_hamiltonian, - long_time, - seed=8200)) +hubbard_hamiltonian = openfermion.get_diagonal_coulomb_hamiltonian(hubbard_model) +hubbard_initial_state, hubbard_exact_state = produce_simulation_test_parameters( + hubbard_hamiltonian, long_time, seed=8200 +) # 4-qubit H2 2-2 with bond length 0.7414 bond_length = 0.7414 -geometry = [('H', (0., 0., 0.)), ('H', (0., 0., bond_length))] -h2_hamiltonian = openfermion.load_molecular_hamiltonian(geometry, 'sto-3g', 1, - format(bond_length), 2, - 2) +geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, bond_length))] +h2_hamiltonian = openfermion.load_molecular_hamiltonian( + geometry, 'sto-3g', 1, format(bond_length), 2, 2 +) h2_initial_state, h2_exact_state = produce_simulation_test_parameters( - h2_hamiltonian, longer_time, seed=44303) + h2_hamiltonian, longer_time, seed=44303 +) # 4-qubit LiH 2-2 with bond length 1.45 bond_length = 1.45 -geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., bond_length))] +geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, bond_length))] lih_hamiltonian = openfermion.load_molecular_hamiltonian( - geometry, 'sto-3g', 1, format(bond_length), 2, 2) + geometry, 'sto-3g', 1, format(bond_length), 2, 2 +) lih_initial_state, lih_exact_state = produce_simulation_test_parameters( - lih_hamiltonian, longer_time, seed=54458) + lih_hamiltonian, longer_time, seed=54458 +) @pytest.mark.parametrize( - 'hamiltonian, time, initial_state, exact_state, order, n_steps, ' - 'algorithm, result_fidelity', [ - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 5, None, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 12, None, .999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 1, 1, LINEAR_SWAP_NETWORK, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 2, 1, LINEAR_SWAP_NETWORK, .99999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 3, SPLIT_OPERATOR, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 6, SPLIT_OPERATOR, .999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 1, 1, SPLIT_OPERATOR, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 2, 1, SPLIT_OPERATOR, .99999), - (hubbard_hamiltonian, long_time, hubbard_initial_state, - hubbard_exact_state, 0, 3, SPLIT_OPERATOR, .999), - (hubbard_hamiltonian, long_time, hubbard_initial_state, - hubbard_exact_state, 0, 6, SPLIT_OPERATOR, .9999), - (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 1, - None, .99), - (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 10, - LOW_RANK, .9999), - (lih_hamiltonian, longer_time, lih_initial_state, lih_exact_state, 0, 1, - LowRankTrotterAlgorithm(final_rank=2), .999), - (lih_hamiltonian, longer_time, lih_initial_state, lih_exact_state, 0, - 10, LowRankTrotterAlgorithm(final_rank=2), .9999), - ]) -def test_simulate_trotter_simulate(hamiltonian, time, initial_state, - exact_state, order, n_steps, algorithm, - result_fidelity): - + 'hamiltonian, time, initial_state, exact_state, order, n_steps, ' 'algorithm, result_fidelity', + [ + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 5, + None, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 12, + None, + 0.999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 1, + 1, + LINEAR_SWAP_NETWORK, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 2, + 1, + LINEAR_SWAP_NETWORK, + 0.99999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 3, + SPLIT_OPERATOR, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 6, + SPLIT_OPERATOR, + 0.999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 1, + 1, + SPLIT_OPERATOR, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 2, + 1, + SPLIT_OPERATOR, + 0.99999, + ), + ( + hubbard_hamiltonian, + long_time, + hubbard_initial_state, + hubbard_exact_state, + 0, + 3, + SPLIT_OPERATOR, + 0.999, + ), + ( + hubbard_hamiltonian, + long_time, + hubbard_initial_state, + hubbard_exact_state, + 0, + 6, + SPLIT_OPERATOR, + 0.9999, + ), + (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 1, None, 0.99), + (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 10, LOW_RANK, 0.9999), + ( + lih_hamiltonian, + longer_time, + lih_initial_state, + lih_exact_state, + 0, + 1, + LowRankTrotterAlgorithm(final_rank=2), + 0.999, + ), + ( + lih_hamiltonian, + longer_time, + lih_initial_state, + lih_exact_state, + 0, + 10, + LowRankTrotterAlgorithm(final_rank=2), + 0.9999, + ), + ], +) +def test_simulate_trotter_simulate( + hamiltonian, time, initial_state, exact_state, order, n_steps, algorithm, result_fidelity +): n_qubits = openfermion.count_qubits(hamiltonian) qubits = cirq.LineQubit.range(n_qubits) start_state = initial_state - circuit = cirq.Circuit( - simulate_trotter(qubits, hamiltonian, time, n_steps, order, algorithm)) + circuit = cirq.Circuit(simulate_trotter(qubits, hamiltonian, time, n_steps, order, algorithm)) final_state = circuit.final_state_vector(initial_state=start_state) correct_state = exact_state @@ -159,53 +249,129 @@ def test_simulate_trotter_simulate(hamiltonian, time, initial_state, @pytest.mark.parametrize( - 'hamiltonian, time, initial_state, exact_state, order, n_steps, ' - 'algorithm, result_fidelity', [ - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 5, None, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 12, None, .999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 1, 1, LINEAR_SWAP_NETWORK, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 2, 1, LINEAR_SWAP_NETWORK, .99999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 3, SPLIT_OPERATOR, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 0, 6, SPLIT_OPERATOR, .999), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 1, 1, SPLIT_OPERATOR, .99), - (diag_coul_hamiltonian, long_time, diag_coul_initial_state, - diag_coul_exact_state, 2, 1, SPLIT_OPERATOR, .99999), - (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 1, - LOW_RANK, .99), - (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 10, - LOW_RANK, .9999), - (lih_hamiltonian, longer_time, lih_initial_state, lih_exact_state, 0, 1, - LowRankTrotterAlgorithm(final_rank=2), .999), - (lih_hamiltonian, longer_time, lih_initial_state, lih_exact_state, 0, - 10, LowRankTrotterAlgorithm(final_rank=2), .9999), - ]) -def test_simulate_trotter_simulate_controlled(hamiltonian, time, initial_state, - exact_state, order, n_steps, - algorithm, result_fidelity): - + 'hamiltonian, time, initial_state, exact_state, order, n_steps, ' 'algorithm, result_fidelity', + [ + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 5, + None, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 12, + None, + 0.999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 1, + 1, + LINEAR_SWAP_NETWORK, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 2, + 1, + LINEAR_SWAP_NETWORK, + 0.99999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 3, + SPLIT_OPERATOR, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 0, + 6, + SPLIT_OPERATOR, + 0.999, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 1, + 1, + SPLIT_OPERATOR, + 0.99, + ), + ( + diag_coul_hamiltonian, + long_time, + diag_coul_initial_state, + diag_coul_exact_state, + 2, + 1, + SPLIT_OPERATOR, + 0.99999, + ), + (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 1, LOW_RANK, 0.99), + (h2_hamiltonian, longer_time, h2_initial_state, h2_exact_state, 0, 10, LOW_RANK, 0.9999), + ( + lih_hamiltonian, + longer_time, + lih_initial_state, + lih_exact_state, + 0, + 1, + LowRankTrotterAlgorithm(final_rank=2), + 0.999, + ), + ( + lih_hamiltonian, + longer_time, + lih_initial_state, + lih_exact_state, + 0, + 10, + LowRankTrotterAlgorithm(final_rank=2), + 0.9999, + ), + ], +) +def test_simulate_trotter_simulate_controlled( + hamiltonian, time, initial_state, exact_state, order, n_steps, algorithm, result_fidelity +): n_qubits = openfermion.count_qubits(hamiltonian) qubits = cirq.LineQubit.range(n_qubits) control = cirq.LineQubit(-1) zero = [1, 0] one = [0, 1] - start_state = (numpy.kron(zero, initial_state) + - numpy.kron(one, initial_state)) / numpy.sqrt(2) + start_state = (numpy.kron(zero, initial_state) + numpy.kron(one, initial_state)) / numpy.sqrt(2) circuit = cirq.Circuit( - simulate_trotter(qubits, hamiltonian, time, n_steps, order, algorithm, - control)) + simulate_trotter(qubits, hamiltonian, time, n_steps, order, algorithm, control) + ) final_state = circuit.final_state_vector(initial_state=start_state) - correct_state = (numpy.kron(zero, initial_state) + - numpy.kron(one, exact_state)) / numpy.sqrt(2) + correct_state = (numpy.kron(zero, initial_state) + numpy.kron(one, exact_state)) / numpy.sqrt(2) assert fidelity(final_state, correct_state) > result_fidelity # Make sure the time wasn't too small assert fidelity(final_state, start_state) < 0.95 * result_fidelity @@ -214,61 +380,51 @@ def test_simulate_trotter_simulate_controlled(hamiltonian, time, initial_state, def test_simulate_trotter_omit_final_swaps(): n_qubits = 5 qubits = cirq.LineQubit.range(n_qubits) - hamiltonian = openfermion.DiagonalCoulombHamiltonian(one_body=numpy.ones( - (n_qubits, n_qubits)), - two_body=numpy.ones( - (n_qubits, - n_qubits))) + hamiltonian = openfermion.DiagonalCoulombHamiltonian( + one_body=numpy.ones((n_qubits, n_qubits)), two_body=numpy.ones((n_qubits, n_qubits)) + ) time = 1.0 circuit_with_swaps = cirq.Circuit( - simulate_trotter(qubits, - hamiltonian, - time, - order=0, - algorithm=LINEAR_SWAP_NETWORK)) + simulate_trotter(qubits, hamiltonian, time, order=0, algorithm=LINEAR_SWAP_NETWORK) + ) circuit_without_swaps = cirq.Circuit( - simulate_trotter(qubits, - hamiltonian, - time, - order=0, - algorithm=LINEAR_SWAP_NETWORK, - omit_final_swaps=True)) + simulate_trotter( + qubits, hamiltonian, time, order=0, algorithm=LINEAR_SWAP_NETWORK, omit_final_swaps=True + ) + ) assert len(circuit_without_swaps) < len(circuit_with_swaps) circuit_with_swaps = cirq.Circuit( - simulate_trotter(qubits, - hamiltonian, - time, - order=1, - n_steps=3, - algorithm=SPLIT_OPERATOR), - strategy=cirq.InsertStrategy.NEW) - circuit_without_swaps = cirq.Circuit(simulate_trotter( - qubits, - hamiltonian, - time, - order=1, - n_steps=3, - algorithm=SPLIT_OPERATOR, - omit_final_swaps=True), - strategy=cirq.InsertStrategy.NEW) + simulate_trotter(qubits, hamiltonian, time, order=1, n_steps=3, algorithm=SPLIT_OPERATOR), + strategy=cirq.InsertStrategy.NEW, + ) + circuit_without_swaps = cirq.Circuit( + simulate_trotter( + qubits, + hamiltonian, + time, + order=1, + n_steps=3, + algorithm=SPLIT_OPERATOR, + omit_final_swaps=True, + ), + strategy=cirq.InsertStrategy.NEW, + ) assert len(circuit_without_swaps) < len(circuit_with_swaps) hamiltonian = lih_hamiltonian qubits = cirq.LineQubit.range(4) circuit_with_swaps = cirq.Circuit( - simulate_trotter(qubits, hamiltonian, time, order=0, - algorithm=LOW_RANK)) + simulate_trotter(qubits, hamiltonian, time, order=0, algorithm=LOW_RANK) + ) circuit_without_swaps = cirq.Circuit( - simulate_trotter(qubits, - hamiltonian, - time, - order=0, - algorithm=LOW_RANK, - omit_final_swaps=True)) + simulate_trotter( + qubits, hamiltonian, time, order=0, algorithm=LOW_RANK, omit_final_swaps=True + ) + ) assert len(circuit_without_swaps) < len(circuit_with_swaps) @@ -288,11 +444,7 @@ def test_simulate_trotter_bad_hamiltonian_type_raises_error(): with pytest.raises(TypeError): _ = next(simulate_trotter(qubits, hamiltonian, time, algorithm=None)) with pytest.raises(TypeError): - _ = next( - simulate_trotter(qubits, - hamiltonian, - time, - algorithm=LINEAR_SWAP_NETWORK)) + _ = next(simulate_trotter(qubits, hamiltonian, time, algorithm=LINEAR_SWAP_NETWORK)) def test_simulate_trotter_unsupported_trotter_step_raises_error(): @@ -306,49 +458,38 @@ class EmptyTrotterAlgorithm(TrotterAlgorithm): algorithm = EmptyTrotterAlgorithm() with pytest.raises(ValueError): - _ = next( - simulate_trotter(qubits, - hamiltonian, - time, - order=0, - algorithm=algorithm)) + _ = next(simulate_trotter(qubits, hamiltonian, time, order=0, algorithm=algorithm)) with pytest.raises(ValueError): - _ = next( - simulate_trotter(qubits, - hamiltonian, - time, - order=1, - algorithm=algorithm)) + _ = next(simulate_trotter(qubits, hamiltonian, time, order=1, algorithm=algorithm)) with pytest.raises(ValueError): _ = next( - simulate_trotter(qubits, - hamiltonian, - time, - order=0, - algorithm=algorithm, - control_qubit=control)) + simulate_trotter( + qubits, hamiltonian, time, order=0, algorithm=algorithm, control_qubit=control + ) + ) with pytest.raises(ValueError): _ = next( - simulate_trotter(qubits, - hamiltonian, - time, - order=1, - algorithm=algorithm, - control_qubit=control)) - - -@pytest.mark.parametrize('algorithm_type,hamiltonian', [ - (LINEAR_SWAP_NETWORK, openfermion.random_diagonal_coulomb_hamiltonian(2)), - (LOW_RANK, openfermion.random_interaction_operator(2)), - (SPLIT_OPERATOR, openfermion.random_diagonal_coulomb_hamiltonian(2)), -]) + simulate_trotter( + qubits, hamiltonian, time, order=1, algorithm=algorithm, control_qubit=control + ) + ) + + +@pytest.mark.parametrize( + 'algorithm_type,hamiltonian', + [ + (LINEAR_SWAP_NETWORK, openfermion.random_diagonal_coulomb_hamiltonian(2)), + (LOW_RANK, openfermion.random_interaction_operator(2)), + (SPLIT_OPERATOR, openfermion.random_diagonal_coulomb_hamiltonian(2)), + ], +) def test_trotter_misspecified_control_raises_error(algorithm_type, hamiltonian): qubits = cirq.LineQubit.range(2) - time = 2. + time = 2.0 algorithms = [ algorithm_type.controlled_asymmetric(hamiltonian), - algorithm_type.controlled_symmetric(hamiltonian) + algorithm_type.controlled_symmetric(hamiltonian), ] for algorithm in algorithms: diff --git a/src/openfermion/circuits/trotter/trotter_algorithm.py b/src/openfermion/circuits/trotter/trotter_algorithm.py index 2040aaa1e..730d44754 100644 --- a/src/openfermion/circuits/trotter/trotter_algorithm.py +++ b/src/openfermion/circuits/trotter/trotter_algorithm.py @@ -22,8 +22,9 @@ # pylint: disable=unused-import from typing import Set, Type -Hamiltonian = Union[ops.FermionOperator, ops.QubitOperator, ops. - InteractionOperator, ops.DiagonalCoulombHamiltonian] +Hamiltonian = Union[ + ops.FermionOperator, ops.QubitOperator, ops.InteractionOperator, ops.DiagonalCoulombHamiltonian +] class TrotterStep(metaclass=abc.ABCMeta): @@ -45,9 +46,9 @@ class TrotterStep(metaclass=abc.ABCMeta): def __init__(self, hamiltonian: Hamiltonian) -> None: self.hamiltonian = hamiltonian - def prepare(self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def prepare( + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: """Operations to perform before doing the Trotter steps. Args: @@ -61,10 +62,9 @@ def prepare(self, return () @abc.abstractmethod - def trotter_step(self, - qubits: Sequence[cirq.Qid], - time: float, - control_qubit: Optional[cirq.Qid] = None) -> cirq.OP_TREE: + def trotter_step( + self, qubits: Sequence[cirq.Qid], time: float, control_qubit: Optional[cirq.Qid] = None + ) -> cirq.OP_TREE: """Yield operations to perform a Trotter step. Args: @@ -75,9 +75,7 @@ def trotter_step(self, """ def step_qubit_permutation( - self, - qubits: Sequence[cirq.Qid], - control_qubit: Optional[cirq.Qid] = None + self, qubits: Sequence[cirq.Qid], control_qubit: Optional[cirq.Qid] = None ) -> Tuple[Sequence[cirq.Qid], Optional[cirq.Qid]]: """The qubit permutation induced by a single Trotter step. @@ -88,11 +86,13 @@ def step_qubit_permutation( # Default: identity permutation return qubits, control_qubit - def finish(self, - qubits: Sequence[cirq.Qid], - n_steps: int, - control_qubit: Optional[cirq.Qid] = None, - omit_final_swaps: bool = False) -> cirq.OP_TREE: + def finish( + self, + qubits: Sequence[cirq.Qid], + n_steps: int, + control_qubit: Optional[cirq.Qid] = None, + omit_final_swaps: bool = False, + ) -> cirq.OP_TREE: """Operations to perform after all Trotter steps are done. Args: @@ -121,6 +121,7 @@ class TrotterAlgorithm(metaclass=abc.ABCMeta): that can be simulated using this Trotter step algorithm. For example, {DiagonalCoulombHamiltonian, InteractionOperator}. """ + supported_types = set() # type: Set[Type[Hamiltonian]] def symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: @@ -129,10 +130,8 @@ def symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: def asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return None - def controlled_symmetric(self, - hamiltonian: Hamiltonian) -> Optional[TrotterStep]: + def controlled_symmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return None - def controlled_asymmetric(self, hamiltonian: Hamiltonian - ) -> Optional[TrotterStep]: + def controlled_asymmetric(self, hamiltonian: Hamiltonian) -> Optional[TrotterStep]: return None diff --git a/src/openfermion/circuits/trotter/trotter_algorithm_test.py b/src/openfermion/circuits/trotter/trotter_algorithm_test.py index 95874809c..6d4649db8 100644 --- a/src/openfermion/circuits/trotter/trotter_algorithm_test.py +++ b/src/openfermion/circuits/trotter/trotter_algorithm_test.py @@ -22,7 +22,6 @@ def test_trotter_algorithm_is_abstract_cant_instantiate(): def test_trotter_algorithm_is_abstract_must_implement(): - class Missing(TrotterStep): pass @@ -31,9 +30,7 @@ class Missing(TrotterStep): def test_trotter_algorithm_is_abstract_can_implement(): - class Included(TrotterStep): - def trotter_step(self, qubits, time, control_qubit): pass diff --git a/src/openfermion/circuits/trotter/trotter_error.py b/src/openfermion/circuits/trotter/trotter_error.py index b95989cb0..a19e686e2 100644 --- a/src/openfermion/circuits/trotter/trotter_error.py +++ b/src/openfermion/circuits/trotter/trotter_error.py @@ -24,8 +24,8 @@ def trivially_commutes(term_a, term_b): position_b = 0 commutes = True - term_op_a, = term_a.terms.keys() - term_op_b, = term_b.terms.keys() + (term_op_a,) = term_a.terms.keys() + (term_op_b,) = term_b.terms.keys() while position_a < len(term_op_a) and position_b < len(term_op_b): qubit_a, action_a = term_op_a[position_a] @@ -56,16 +56,17 @@ def trivially_double_commutes(term_a, term_b, term_c): qubits) is empty, then the double commutator is trivially zero. """ # determine the set of qubits each term acts on - term_op_a, = term_a.terms.keys() - term_op_b, = term_b.terms.keys() - term_op_c, = term_c.terms.keys() + (term_op_a,) = term_a.terms.keys() + (term_op_b,) = term_b.terms.keys() + (term_op_c,) = term_c.terms.keys() qubits_a = set([index for index, _ in term_op_a]) qubits_b = set([index for index, _ in term_op_b]) qubits_c = set([index for index, _ in term_op_c]) - return (trivially_commutes(term_b, term_c) or - not qubits_a.intersection(set(qubits_b.union(qubits_c)))) + return trivially_commutes(term_b, term_c) or not qubits_a.intersection( + set(qubits_b.union(qubits_c)) + ) def error_operator(terms, series_order=2): @@ -92,11 +93,10 @@ def error_operator(terms, series_order=2): for beta in range(len(terms)): for alpha in range(beta + 1): for alpha_prime in range(beta): - if not trivially_double_commutes(terms[alpha], terms[beta], - terms[alpha_prime]): + if not trivially_double_commutes(terms[alpha], terms[beta], terms[alpha_prime]): double_com = commutator( - terms[alpha], commutator(terms[beta], - terms[alpha_prime])) + terms[alpha], commutator(terms[beta], terms[alpha_prime]) + ) error_operator += double_com if alpha == beta: error_operator -= double_com / 2.0 @@ -136,22 +136,21 @@ def error_bound(terms, tight=False): if tight: # return the 1-norm of the error operator (upper bound on error) - error = sum( - abs(coefficient) - for coefficient in error_operator(terms).terms.values()) + error = sum(abs(coefficient) for coefficient in error_operator(terms).terms.values()) elif not tight: for alpha in range(len(terms)): term_a = terms[alpha] - coefficient_a, = term_a.terms.values() + (coefficient_a,) = term_a.terms.values() if coefficient_a: - error_a = 0. + error_a = 0.0 for beta in range(alpha + 1, len(terms)): term_b = terms[beta] - coefficient_b, = term_b.terms.values() - if not (trivially_commutes(term_a, term_b) or - commutator(term_a, term_b) == zero): + (coefficient_b,) = term_b.terms.values() + if not ( + trivially_commutes(term_a, term_b) or commutator(term_a, term_b) == zero + ): error_a += abs(coefficient_b) error += 4.0 * abs(coefficient_a) * error_a**2 diff --git a/src/openfermion/circuits/trotter/trotter_error_test.py b/src/openfermion/circuits/trotter/trotter_error_test.py index 0d03d437a..ffe8304cc 100644 --- a/src/openfermion/circuits/trotter/trotter_error_test.py +++ b/src/openfermion/circuits/trotter/trotter_error_test.py @@ -18,16 +18,19 @@ from openfermion.ops.operators import QubitOperator from openfermion.linalg.sparse_tools import get_sparse_operator from openfermion.circuits.trotter.trotter_error import ( - trivially_commutes, trivially_double_commutes, commutator, error_operator, - error_bound, trotter_steps_required) + trivially_commutes, + trivially_double_commutes, + commutator, + error_operator, + error_bound, + trotter_steps_required, +) class CommutatorTest(unittest.TestCase): - def test_commutator_commutes(self): zero = QubitOperator() - self.assertTrue( - commutator(QubitOperator(()), QubitOperator('X3')) == zero) + self.assertTrue(commutator(QubitOperator(()), QubitOperator('X3')) == zero) def test_commutator_single_pauli(self): com = commutator(QubitOperator('X3'), QubitOperator('Y3')) @@ -41,77 +44,71 @@ def test_commutator_multi_pauli(self): class TriviallyCommutesTest(unittest.TestCase): - def test_trivially_commutes_id_id(self): - self.assertTrue( - trivially_commutes(QubitOperator(()), 3 * QubitOperator(()))) + self.assertTrue(trivially_commutes(QubitOperator(()), 3 * QubitOperator(()))) def test_trivially_commutes_id_x(self): - self.assertTrue( - trivially_commutes(QubitOperator(()), QubitOperator('X1'))) + self.assertTrue(trivially_commutes(QubitOperator(()), QubitOperator('X1'))) def test_trivially_commutes_id_xx(self): - self.assertTrue( - trivially_commutes(QubitOperator(()), QubitOperator('X1 X3'))) + self.assertTrue(trivially_commutes(QubitOperator(()), QubitOperator('X1 X3'))) def test_trivially_commutes_nonid_with_id(self): - self.assertTrue( - trivially_commutes(QubitOperator('X1 Z5 Y9 Z11'), QubitOperator( - ()))) + self.assertTrue(trivially_commutes(QubitOperator('X1 Z5 Y9 Z11'), QubitOperator(()))) def test_trivially_commutes_no_intersect(self): - self.assertTrue( - trivially_commutes(QubitOperator('X1 Y3 Z6'), - QubitOperator('Z0 Z2 X4 Y5'))) + self.assertTrue(trivially_commutes(QubitOperator('X1 Y3 Z6'), QubitOperator('Z0 Z2 X4 Y5'))) def test_trivially_commutes_allsame_oddintersect(self): self.assertTrue( - trivially_commutes(QubitOperator('X1 X3 X4 Z6 X8'), - QubitOperator('X1 X3 X4 Z7 Y9'))) + trivially_commutes(QubitOperator('X1 X3 X4 Z6 X8'), QubitOperator('X1 X3 X4 Z7 Y9')) + ) def test_trivially_commutes_even_anti(self): self.assertTrue( - trivially_commutes(QubitOperator('X1 Z2 Z3 X10'), - QubitOperator('Y1 X2 Z3 Y9'))) + trivially_commutes(QubitOperator('X1 Z2 Z3 X10'), QubitOperator('Y1 X2 Z3 Y9')) + ) def test_no_trivial_commute_odd_anti(self): - self.assertFalse( - trivially_commutes(QubitOperator('X1'), QubitOperator('Y1'))) + self.assertFalse(trivially_commutes(QubitOperator('X1'), QubitOperator('Y1'))) def test_no_trivial_commute_triple_anti_intersect(self): self.assertFalse( - trivially_commutes(QubitOperator('X0 Z2 Z4 Z9 Y17'), - QubitOperator('Y0 X2 Y4 Z9 Z16'))) + trivially_commutes(QubitOperator('X0 Z2 Z4 Z9 Y17'), QubitOperator('Y0 X2 Y4 Z9 Z16')) + ) def test_no_trivial_commute_mostly_commuting(self): self.assertFalse( - trivially_commutes(QubitOperator('X0 Y1 Z2 X4 Y5 Y6'), - QubitOperator('X0 Y1 Z2 X4 Z5 Y6'))) + trivially_commutes( + QubitOperator('X0 Y1 Z2 X4 Y5 Y6'), QubitOperator('X0 Y1 Z2 X4 Z5 Y6') + ) + ) class TriviallyDoubleCommutesTest(unittest.TestCase): - def test_trivial_double_commute_no_intersect(self): self.assertTrue( - trivially_double_commutes(QubitOperator('X1 Z2 Y4'), - QubitOperator('Y0 X3 Z6'), - QubitOperator('Y5'))) + trivially_double_commutes( + QubitOperator('X1 Z2 Y4'), QubitOperator('Y0 X3 Z6'), QubitOperator('Y5') + ) + ) def test_trivial_double_commute_no_intersect_a_bc(self): self.assertTrue( - trivially_double_commutes(QubitOperator('X1 Z2 Y4'), - QubitOperator('Y0 X3 Z6'), - QubitOperator('Z3 Y5'))) + trivially_double_commutes( + QubitOperator('X1 Z2 Y4'), QubitOperator('Y0 X3 Z6'), QubitOperator('Z3 Y5') + ) + ) def test_trivial_double_commute_bc_intersect_commute(self): self.assertTrue( - trivially_double_commutes(QubitOperator('X1 Z2 Y4'), - QubitOperator('X0 Z3'), - QubitOperator('Y0 X3'))) + trivially_double_commutes( + QubitOperator('X1 Z2 Y4'), QubitOperator('X0 Z3'), QubitOperator('Y0 X3') + ) + ) class ErrorOperatorTest(unittest.TestCase): - def test_error_operator_bad_order(self): with self.assertRaises(NotImplementedError): error_operator([QubitOperator], 1) @@ -121,54 +118,51 @@ def test_error_operator_all_diagonal(self): QubitOperator(()), QubitOperator('Z0 Z1 Z2'), QubitOperator('Z0 Z3'), - QubitOperator('Z0 Z1 Z2 Z3') + QubitOperator('Z0 Z1 Z2 Z3'), ] zero = QubitOperator() self.assertTrue(zero == error_operator(terms)) class ErrorBoundTest(unittest.TestCase): - def test_error_bound_xyz_tight(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] - expected = sqrt(7. / 12) # 2-norm of [[-2/3, 1/3+i/6], [1/3-i/6, 2/3]] + expected = sqrt(7.0 / 12) # 2-norm of [[-2/3, 1/3+i/6], [1/3-i/6, 2/3]] self.assertLess(expected, error_bound(terms, tight=True)) def test_error_bound_xyz_loose(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] - self.assertTrue( - numpy.isclose(error_bound(terms, tight=False), 4. * (2**2 + 1**2))) + self.assertTrue(numpy.isclose(error_bound(terms, tight=False), 4.0 * (2**2 + 1**2))) def test_error_operator_xyz(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] - expected = numpy.array([[-2. / 3, 1. / 3 + 1.j / 6, 0., 0.], - [1. / 3 - 1.j / 6, 2. / 3, 0., 0.], - [0., 0., -2. / 3, 1. / 3 + 1.j / 6], - [0., 0., 1. / 3 - 1.j / 6, 2. / 3]]) + expected = numpy.array( + [ + [-2.0 / 3, 1.0 / 3 + 1.0j / 6, 0.0, 0.0], + [1.0 / 3 - 1.0j / 6, 2.0 / 3, 0.0, 0.0], + [0.0, 0.0, -2.0 / 3, 1.0 / 3 + 1.0j / 6], + [0.0, 0.0, 1.0 / 3 - 1.0j / 6, 2.0 / 3], + ] + ) sparse_op = get_sparse_operator(error_operator(terms)) matrix = sparse_op.todense() - self.assertTrue(numpy.allclose(matrix, expected), - ("Got " + str(matrix))) + self.assertTrue(numpy.allclose(matrix, expected), ("Got " + str(matrix))) def test_error_bound_qubit_tight_less_than_loose_integration(self): terms = [QubitOperator('X1'), QubitOperator('Y1'), QubitOperator('Z1')] - self.assertLess(error_bound(terms, tight=True), - error_bound(terms, tight=False)) + self.assertLess(error_bound(terms, tight=True), error_bound(terms, tight=False)) class TrotterStepsRequiredTest(unittest.TestCase): - def test_trotter_steps_required(self): self.assertEqual( - trotter_steps_required(trotter_error_bound=0.3, - time=2.5, - energy_precision=0.04), 7) + trotter_steps_required(trotter_error_bound=0.3, time=2.5, energy_precision=0.04), 7 + ) def test_trotter_steps_required_negative_time(self): self.assertEqual( - trotter_steps_required(trotter_error_bound=0.1, - time=3.3, - energy_precision=0.11), 4) + trotter_steps_required(trotter_error_bound=0.1, time=3.3, energy_precision=0.11), 4 + ) def test_return_type(self): self.assertIsInstance(trotter_steps_required(0.1, 0.1, 0.1), int) diff --git a/src/openfermion/circuits/trotter_exp_to_qgates.py b/src/openfermion/circuits/trotter_exp_to_qgates.py index f7ef3b5a7..18f4c5675 100644 --- a/src/openfermion/circuits/trotter_exp_to_qgates.py +++ b/src/openfermion/circuits/trotter_exp_to_qgates.py @@ -16,6 +16,7 @@ import numpy from openfermion.ops.operators import QubitOperator import openfermion.utils as utils + """ Description: Functions for estimating the exponential of an operator @@ -46,11 +47,16 @@ def _third_order_trotter_helper(op_list): list of QubitOperators giving the trotterized hamiltonian """ # Create list to hold return value - ret_val = collections.deque([ - 7.0 / 24.0 * op_list[-2], 2.0 / 3.0 * op_list[-1], - 3.0 / 4.0 * op_list[-2], -2.0 / 3.0 * op_list[-1], - -1.0 / 24.0 * op_list[-2], 1.0 * op_list[-1] - ]) + ret_val = collections.deque( + [ + 7.0 / 24.0 * op_list[-2], + 2.0 / 3.0 * op_list[-1], + 3.0 / 4.0 * op_list[-2], + -2.0 / 3.0 * op_list[-1], + -1.0 / 24.0 * op_list[-2], + 1.0 * op_list[-1], + ] + ) for i in reversed(range(len(op_list) - 2)): temp_ret_val = copy.deepcopy(ret_val) @@ -64,11 +70,9 @@ def _third_order_trotter_helper(op_list): return ret_val -def trotter_operator_grouping(hamiltonian, - trotter_number=1, - trotter_order=1, - term_ordering=None, - k_exp=1.0): +def trotter_operator_grouping( + hamiltonian, trotter_number=1, trotter_order=1, term_ordering=None, k_exp=1.0 +): """Trotter-decomposes operators into groups without exponentiating. Operators are still Hermitian at the end of this method but have been @@ -117,32 +121,31 @@ def trotter_operator_grouping(hamiltonian, if trotter_order == 1: for _ in range(trotter_number): for op in term_ordering: - yield QubitOperator( - op, hamiltonian.terms[op] * k_exp / trotter_number) + yield QubitOperator(op, hamiltonian.terms[op] * k_exp / trotter_number) # Second order trotter elif trotter_order == 2: if len(term_ordering) < 2: - raise ValueError("Not enough terms in the Hamiltonian to do " + - "second order trotterization") + raise ValueError( + "Not enough terms in the Hamiltonian to do " + "second order trotterization" + ) for _ in range(trotter_number): for op in term_ordering[:-1]: - yield QubitOperator( - op, hamiltonian.terms[op] * k_exp / (2.0 * trotter_number)) + yield QubitOperator(op, hamiltonian.terms[op] * k_exp / (2.0 * trotter_number)) yield QubitOperator( - term_ordering[-1], - hamiltonian.terms[term_ordering[-1]] * k_exp / trotter_number) + term_ordering[-1], hamiltonian.terms[term_ordering[-1]] * k_exp / trotter_number + ) for op in reversed(term_ordering[:-1]): - yield QubitOperator( - op, hamiltonian.terms[op] * k_exp / (2.0 * trotter_number)) + yield QubitOperator(op, hamiltonian.terms[op] * k_exp / (2.0 * trotter_number)) # Third order trotter elif trotter_order == 3: if len(term_ordering) < 2: - raise ValueError("Not enough terms in the Hamiltonian to do " + - "third order trotterization") + raise ValueError( + "Not enough terms in the Hamiltonian to do " + "third order trotterization" + ) # Make sure original hamiltonian is not modified ham = hamiltonian * k_exp / float(trotter_number) ham_temp = [] @@ -153,10 +156,7 @@ def trotter_operator_grouping(hamiltonian, yield returned_op -def pauli_exp_to_qasm(qubit_operator_list, - evolution_time=1.0, - qubit_list=None, - ancilla=None): +def pauli_exp_to_qasm(qubit_operator_list, evolution_time=1.0, qubit_list=None, ancilla=None): """Exponentiate a list of QubitOperators to a QASM string generator. Exponentiates a list of QubitOperators, and yields string generators in @@ -180,10 +180,7 @@ def pauli_exp_to_qasm(qubit_operator_list, string """ - num_qubits = max([ - utils.count_qubits(qubit_operator) - for qubit_operator in qubit_operator_list - ]) + num_qubits = max([utils.count_qubits(qubit_operator) for qubit_operator in qubit_operator_list]) if qubit_list is None: qubit_list = list(range(num_qubits)) else: @@ -197,7 +194,6 @@ def pauli_exp_to_qasm(qubit_operator_list, ret_list = [] for term in qubit_operator.terms: - term_coeff = qubit_operator.terms[term] # Force float @@ -220,10 +216,8 @@ def pauli_exp_to_qasm(qubit_operator_list, string_basis_1.append("H {}".format(qid)) # Hadamard string_basis_2.append("H {}".format(qid)) # Hadamard elif pop == 'Y': - string_basis_1.append( - "Rx 1.5707963267948966 {}".format(qid)) - string_basis_2.append( - "Rx -1.5707963267948966 {}".format(qid)) + string_basis_1.append("Rx 1.5707963267948966 {}".format(qid)) + string_basis_2.append("Rx -1.5707963267948966 {}".format(qid)) # Prep for CNOTs cnot_pairs = numpy.vstack((qids[:-1], qids[1:])) @@ -249,22 +243,19 @@ def pauli_exp_to_qasm(qubit_operator_list, if len(qids) > 0: ret_list = ret_list + [ "C-Phase {} {} {}".format( - -2 * term_coeff * evolution_time, ancilla, qids[-1]) + -2 * term_coeff * evolution_time, ancilla, qids[-1] + ) ] ret_list = ret_list + [ - "Rz {} {}".format(1 * term_coeff * evolution_time, - ancilla) + "Rz {} {}".format(1 * term_coeff * evolution_time, ancilla) ] else: ret_list = ret_list + [ - "Rz {} {}".format(1 * term_coeff * evolution_time, - ancilla) + "Rz {} {}".format(1 * term_coeff * evolution_time, ancilla) ] else: if len(qids) > 0: - ret_list = ret_list + [ - "Rz {} {}".format(term_coeff * evolution_time, qids[-1]) - ] + ret_list = ret_list + ["Rz {} {}".format(term_coeff * evolution_time, qids[-1])] # 4. Second set of CNOTs ret_list = ret_list + cnots2 @@ -276,14 +267,16 @@ def pauli_exp_to_qasm(qubit_operator_list, yield gate -def trotterize_exp_qubop_to_qasm(hamiltonian, - evolution_time=1, - trotter_number=1, - trotter_order=1, - term_ordering=None, - k_exp=1.0, - qubit_list=None, - ancilla=None): +def trotterize_exp_qubop_to_qasm( + hamiltonian, + evolution_time=1, + trotter_number=1, + trotter_order=1, + term_ordering=None, + k_exp=1.0, + qubit_list=None, + ancilla=None, +): """Trotterize a Qubit hamiltonian and write it to QASM format. Assumes input hamiltonian is still hermitian and -1.0j has not yet been @@ -313,12 +306,10 @@ def trotterize_exp_qubop_to_qasm(hamiltonian, """ - for trotterized_op in trotter_operator_grouping(hamiltonian, trotter_number, - trotter_order, - term_ordering, k_exp): + for trotterized_op in trotter_operator_grouping( + hamiltonian, trotter_number, trotter_order, term_ordering, k_exp + ): for exponentiated_qasm_string in pauli_exp_to_qasm( - [trotterized_op], - evolution_time=evolution_time, - qubit_list=qubit_list, - ancilla=ancilla): + [trotterized_op], evolution_time=evolution_time, qubit_list=qubit_list, ancilla=ancilla + ): yield exponentiated_qasm_string diff --git a/src/openfermion/circuits/trotter_exp_to_qgates_test.py b/src/openfermion/circuits/trotter_exp_to_qgates_test.py index da1eb5ad6..4bac8c89d 100644 --- a/src/openfermion/circuits/trotter_exp_to_qgates_test.py +++ b/src/openfermion/circuits/trotter_exp_to_qgates_test.py @@ -18,12 +18,14 @@ from openfermion.utils.operator_utils import count_qubits from openfermion.circuits.trotter_exp_to_qgates import ( - trotter_operator_grouping, _third_order_trotter_helper, - trotterize_exp_qubop_to_qasm, pauli_exp_to_qasm) + trotter_operator_grouping, + _third_order_trotter_helper, + trotterize_exp_qubop_to_qasm, + pauli_exp_to_qasm, +) class TrottQasmTest(unittest.TestCase): - def setUp(self): # First qubit operator example self.opA = QubitOperator('X0 Z1 Y3', 0.5) @@ -35,26 +37,16 @@ def test_exceptions(self): # Test exceptions in trotter_operator_grouping() with self.assertRaises(TypeError): - _ = [ - i for i in pauli_exp_to_qasm([QubitOperator()], - qubit_list='qubit') - ] + _ = [i for i in pauli_exp_to_qasm([QubitOperator()], qubit_list='qubit')] with self.assertRaises(TypeError): - _ = [ - i for i in pauli_exp_to_qasm([QubitOperator('X1 Y2')], - qubit_list=['qubit']) - ] + _ = [i for i in pauli_exp_to_qasm([QubitOperator('X1 Y2')], qubit_list=['qubit'])] with self.assertRaises(ValueError): - _ = [ - i for i in trotter_operator_grouping(self.qo1, trotter_order=0) - ] + _ = [i for i in trotter_operator_grouping(self.qo1, trotter_order=0)] with self.assertRaises(ValueError): - _ = [ - i for i in trotter_operator_grouping(self.qo1, trotter_order=4) - ] + _ = [i for i in trotter_operator_grouping(self.qo1, trotter_order=4)] with self.assertRaises(TypeError): _ = [i for i in trotter_operator_grouping(42)] @@ -65,22 +57,15 @@ def test_exceptions(self): emptyTO = [] with self.assertRaises(TypeError): - _ = [ - i for i in trotter_operator_grouping(self.qo1, - term_ordering=emptyTO) - ] + _ = [i for i in trotter_operator_grouping(self.qo1, term_ordering=emptyTO)] # Too few ops for 2nd-order with self.assertRaises(ValueError): - _ = [ - i for i in trotter_operator_grouping(self.opA, trotter_order=2) - ] + _ = [i for i in trotter_operator_grouping(self.opA, trotter_order=2)] # Too few ops for 3rd-order with self.assertRaises(ValueError): - _ = [ - i for i in trotter_operator_grouping(self.opA, trotter_order=3) - ] + _ = [i for i in trotter_operator_grouping(self.opA, trotter_order=3)] def compare_qubop_lists(self, gold, res): # Compare lists of operators. Used in most test functions. @@ -97,12 +82,12 @@ def test_3rd_order_helper_2ops(self): op_b = QubitOperator('Z2', 0.1) gold = [] - gold.append(op_a * (7. / 24)) - gold.append(op_b * (2. / 3)) - gold.append(op_a * (3. / 4)) - gold.append(op_b * (-2. / 3)) - gold.append(op_a * (-1. / 24)) - gold.append(op_b * (1.)) + gold.append(op_a * (7.0 / 24)) + gold.append(op_b * (2.0 / 3)) + gold.append(op_a * (3.0 / 4)) + gold.append(op_b * (-2.0 / 3)) + gold.append(op_a * (-1.0 / 24)) + gold.append(op_b * (1.0)) # Second arg must be in list form res = _third_order_trotter_helper([op_a, op_b]) @@ -112,31 +97,31 @@ def test_3rd_order_helper_2ops(self): def test_3rd_order_helper_3ops(self): # Test 3rd-order helper, H=A+B+C - op_a = QubitOperator('X0', 1.) - op_b = QubitOperator('Z2', 1.) - op_c = QubitOperator('Z3', 1.) + op_a = QubitOperator('X0', 1.0) + op_b = QubitOperator('Z2', 1.0) + op_c = QubitOperator('Z3', 1.0) gold = [] - gold.append(op_a * 7. / 24) - gold.append(op_b * 7. / 36) - gold.append(op_c * 4. / 9) - gold.append(op_b * 1. / 2) - gold.append(op_c * -4. / 9) - gold.append(op_b * -1. / 36) - gold.append(op_c * 2. / 3) - gold.append(op_a * 3. / 4) - gold.append(op_b * -7. / 36) - gold.append(op_c * -4. / 9) - gold.append(op_b * -1. / 2) - gold.append(op_c * 4. / 9) - gold.append(op_b * 1. / 36) - gold.append(op_c * -2. / 3) - gold.append(op_a * -1. / 24) - gold.append(op_b * 7. / 24) - gold.append(op_c * 2. / 3) - gold.append(op_b * 3. / 4) - gold.append(op_c * -2. / 3) - gold.append(op_b * -1. / 24) + gold.append(op_a * 7.0 / 24) + gold.append(op_b * 7.0 / 36) + gold.append(op_c * 4.0 / 9) + gold.append(op_b * 1.0 / 2) + gold.append(op_c * -4.0 / 9) + gold.append(op_b * -1.0 / 36) + gold.append(op_c * 2.0 / 3) + gold.append(op_a * 3.0 / 4) + gold.append(op_b * -7.0 / 36) + gold.append(op_c * -4.0 / 9) + gold.append(op_b * -1.0 / 2) + gold.append(op_c * 4.0 / 9) + gold.append(op_b * 1.0 / 36) + gold.append(op_c * -2.0 / 3) + gold.append(op_a * -1.0 / 24) + gold.append(op_b * 7.0 / 24) + gold.append(op_c * 2.0 / 3) + gold.append(op_b * 3.0 / 4) + gold.append(op_c * -2.0 / 3) + gold.append(op_b * -1.0 / 24) gold.append(op_c * 1.0) # Second arg must be in list form @@ -147,35 +132,35 @@ def test_3rd_order_helper_3ops(self): def test_trott_ordering_3rd_ord(self): # Test 3rd-order Trotterization, H=A+B+C - op_a = QubitOperator('X0', 1.) - op_b = QubitOperator('Z2', 1.) - op_c = QubitOperator('Z3', 1.) + op_a = QubitOperator('X0', 1.0) + op_b = QubitOperator('Z2', 1.0) + op_c = QubitOperator('Z3', 1.0) ham = op_a + op_b + op_c # Result from code res = [op for op in trotter_operator_grouping(ham, trotter_order=3)] gold = [] - gold.append(op_a * 7. / 24) - gold.append(op_b * 7. / 36) - gold.append(op_c * 4. / 9) - gold.append(op_b * 1. / 2) - gold.append(op_c * -4. / 9) - gold.append(op_b * -1. / 36) - gold.append(op_c * 2. / 3) - gold.append(op_a * 3. / 4) - gold.append(op_b * -7. / 36) - gold.append(op_c * -4. / 9) - gold.append(op_b * -1. / 2) - gold.append(op_c * 4. / 9) - gold.append(op_b * 1. / 36) - gold.append(op_c * -2. / 3) - gold.append(op_a * -1. / 24) - gold.append(op_b * 7. / 24) - gold.append(op_c * 2. / 3) - gold.append(op_b * 3. / 4) - gold.append(op_c * -2. / 3) - gold.append(op_b * -1. / 24) + gold.append(op_a * 7.0 / 24) + gold.append(op_b * 7.0 / 36) + gold.append(op_c * 4.0 / 9) + gold.append(op_b * 1.0 / 2) + gold.append(op_c * -4.0 / 9) + gold.append(op_b * -1.0 / 36) + gold.append(op_c * 2.0 / 3) + gold.append(op_a * 3.0 / 4) + gold.append(op_b * -7.0 / 36) + gold.append(op_c * -4.0 / 9) + gold.append(op_b * -1.0 / 2) + gold.append(op_c * 4.0 / 9) + gold.append(op_b * 1.0 / 36) + gold.append(op_c * -2.0 / 3) + gold.append(op_a * -1.0 / 24) + gold.append(op_b * 7.0 / 24) + gold.append(op_c * 2.0 / 3) + gold.append(op_b * 3.0 / 4) + gold.append(op_c * -2.0 / 3) + gold.append(op_b * -1.0 / 24) gold.append(op_c * 1.0) # Assert each term in list of QubitOperators is correct @@ -183,9 +168,9 @@ def test_trott_ordering_3rd_ord(self): def test_trott_ordering_2nd_ord(self): # Test 2nd-order Trotter ordering - op_a = QubitOperator('X0', 1.) - op_b = QubitOperator('Z2', 1.) - op_c = QubitOperator('Z3', 1.) + op_a = QubitOperator('X0', 1.0) + op_b = QubitOperator('Z2', 1.0) + op_c = QubitOperator('Z3', 1.0) ham = op_a + op_b + op_c _ = [op for op in trotter_operator_grouping(ham, trotter_order=2)] gold = [] @@ -198,11 +183,10 @@ def test_trott_ordering_2nd_ord(self): def test_get_trott_qubops(self): # Testing with trotter number of 2 (first-order) res = [ - op for op in trotter_operator_grouping(self.qo1, - trotter_number=2, - trotter_order=1, - term_ordering=None, - k_exp=1.0) + op + for op in trotter_operator_grouping( + self.qo1, trotter_number=2, trotter_order=1, term_ordering=None, k_exp=1.0 + ) ] gold = [] @@ -263,8 +247,7 @@ def test_qasm_string_Controlled_XYZ(self): qasmstr = str(count_qubits(self.opA) + 1) + "\n" # Write each QASM operation - qasmstr += "\n".join( - trotterize_exp_qubop_to_qasm(self.opA, ancilla='ancilla')) + qasmstr += "\n".join(trotterize_exp_qubop_to_qasm(self.opA, ancilla='ancilla')) # Correct string strcorrect = '''5 @@ -292,9 +275,8 @@ def test_qasm_string_Controlled_XYZ_ancilla_list(self): # Write each QASM operation qasmstr += "\n".join( - trotterize_exp_qubop_to_qasm(self.opA, - ancilla='ancilla', - qubit_list=qubit_list)) + trotterize_exp_qubop_to_qasm(self.opA, ancilla='ancilla', qubit_list=qubit_list) + ) # Correct string strcorrect = '''5 @@ -315,8 +297,7 @@ def test_qasm_string_controlled_identity(self): qasmstr = str(count_qubits(self.op_id) + 1) + "\n" # Write each QASM operation - qasmstr += "\n".join( - trotterize_exp_qubop_to_qasm(self.op_id, ancilla='ancilla')) + qasmstr += "\n".join(trotterize_exp_qubop_to_qasm(self.op_id, ancilla='ancilla')) strcorrect = '''1 Rz 1.0 ancilla''' diff --git a/src/openfermion/circuits/unitary_cc.py b/src/openfermion/circuits/unitary_cc.py index bd912dc97..2f45500fa 100644 --- a/src/openfermion/circuits/unitary_cc.py +++ b/src/openfermion/circuits/unitary_cc.py @@ -50,10 +50,10 @@ def uccsd_generator(single_amplitudes, double_amplitudes, anti_hermitian=True): generator = FermionOperator() # Re-format inputs (ndarrays to lists) if necessary - if (isinstance(single_amplitudes, numpy.ndarray) or - isinstance(double_amplitudes, numpy.ndarray)): + if isinstance(single_amplitudes, numpy.ndarray) or isinstance(double_amplitudes, numpy.ndarray): single_amplitudes, double_amplitudes = uccsd_convert_amplitude_format( - single_amplitudes, double_amplitudes) + single_amplitudes, double_amplitudes + ) # Add single excitations for (i, j), t_ij in single_amplitudes: @@ -67,8 +67,7 @@ def uccsd_generator(single_amplitudes, double_amplitudes, anti_hermitian=True): i, j, k, l = int(i), int(j), int(k), int(l) generator += FermionOperator(((i, 1), (j, 0), (k, 1), (l, 0)), t_ijkl) if anti_hermitian: - generator += FermionOperator(((l, 1), (k, 0), (j, 1), (i, 0)), - -t_ijkl) + generator += FermionOperator(((l, 1), (k, 0), (j, 1), (i, 0)), -t_ijkl) return generator @@ -98,8 +97,7 @@ def uccsd_convert_amplitude_format(single_amplitudes, double_amplitudes): single_amplitudes_list.append([[i, j], single_amplitudes[i, j]]) for i, j, k, l in zip(*double_amplitudes.nonzero()): - double_amplitudes_list.append([[i, j, k, l], - double_amplitudes[i, j, k, l]]) + double_amplitudes_list.append([[i, j, k, l], double_amplitudes[i, j, k, l]]) return single_amplitudes_list, double_amplitudes_list @@ -131,8 +129,9 @@ def uccsd_singlet_paramsize(n_qubits, n_electrons): return n_single_amplitudes + n_double_amplitudes -def uccsd_singlet_get_packed_amplitudes(single_amplitudes, double_amplitudes, - n_qubits, n_electrons): +def uccsd_singlet_get_packed_amplitudes( + single_amplitudes, double_amplitudes, n_qubits, n_electrons +): r"""Convert amplitudes for use with singlet UCCSD The output list contains only those amplitudes that are relevant to @@ -180,12 +179,12 @@ def uccsd_singlet_get_packed_amplitudes(single_amplitudes, double_amplitudes, singles.append(single_amplitudes[virtual_up, occupied_up]) # Get doubles amplitude - doubles_1.append(double_amplitudes[virtual_up, occupied_up, - virtual_down, occupied_down]) + doubles_1.append(double_amplitudes[virtual_up, occupied_up, virtual_down, occupied_down]) # Get doubles amplitudes associated with two spatial occupied-virtual pairs for (p, q), (r, s) in itertools.combinations( - itertools.product(range(n_virtual), range(n_occupied)), 2): + itertools.product(range(n_virtual), range(n_occupied)), 2 + ): # Get indices of spatial orbitals virtual_spatial_1 = n_occupied + p occupied_spatial_1 = q @@ -200,16 +199,14 @@ def uccsd_singlet_get_packed_amplitudes(single_amplitudes, double_amplitudes, occupied_2_up = up_index(occupied_spatial_2) # Get amplitude - doubles_2.append(double_amplitudes[virtual_1_up, occupied_1_up, - virtual_2_up, occupied_2_up]) + doubles_2.append( + double_amplitudes[virtual_1_up, occupied_1_up, virtual_2_up, occupied_2_up] + ) return singles + doubles_1 + doubles_2 -def uccsd_singlet_generator(packed_amplitudes, - n_qubits, - n_electrons, - anti_hermitian=True): +def uccsd_singlet_generator(packed_amplitudes, n_qubits, n_electrons, anti_hermitian=True): """Create a singlet UCCSD generator for a system with n_electrons This function generates a FermionOperator for a UCCSD generator designed @@ -244,9 +241,9 @@ def uccsd_singlet_generator(packed_amplitudes, # Single amplitudes t1 = packed_amplitudes[:n_single_amplitudes] # Double amplitudes associated with one spatial occupied-virtual pair - t2_1 = packed_amplitudes[n_single_amplitudes:2 * n_single_amplitudes] + t2_1 = packed_amplitudes[n_single_amplitudes : 2 * n_single_amplitudes] # Double amplitudes associated with two spatial occupied-virtual pairs - t2_2 = packed_amplitudes[2 * n_single_amplitudes:] + t2_2 = packed_amplitudes[2 * n_single_amplitudes :] # Initialize operator generator = FermionOperator() @@ -255,9 +252,7 @@ def uccsd_singlet_generator(packed_amplitudes, spin_index_functions = [up_index, down_index] # Generate all spin-conserving single and double excitations derived # from one spatial occupied-virtual pair - for i, (p, q) in enumerate( - itertools.product(range(n_virtual), range(n_occupied))): - + for i, (p, q) in enumerate(itertools.product(range(n_virtual), range(n_occupied))): # Get indices of spatial orbitals virtual_spatial = n_occupied + p occupied_spatial = q @@ -276,28 +271,32 @@ def uccsd_singlet_generator(packed_amplitudes, # Generate single excitations coeff = t1[i] - generator += FermionOperator( - ((virtual_this, 1), (occupied_this, 0)), coeff) + generator += FermionOperator(((virtual_this, 1), (occupied_this, 0)), coeff) if anti_hermitian: - generator += FermionOperator( - ((occupied_this, 1), (virtual_this, 0)), -coeff) + generator += FermionOperator(((occupied_this, 1), (virtual_this, 0)), -coeff) # Generate double excitation coeff = t2_1[i] generator += FermionOperator( - ((virtual_this, 1), (occupied_this, 0), (virtual_other, 1), - (occupied_other, 0)), coeff) + ((virtual_this, 1), (occupied_this, 0), (virtual_other, 1), (occupied_other, 0)), + coeff, + ) if anti_hermitian: generator += FermionOperator( - ((occupied_other, 1), (virtual_other, 0), - (occupied_this, 1), (virtual_this, 0)), -coeff) + ( + (occupied_other, 1), + (virtual_other, 0), + (occupied_this, 1), + (virtual_this, 0), + ), + -coeff, + ) # Generate all spin-conserving double excitations derived # from two spatial occupied-virtual pairs for i, ((p, q), (r, s)) in enumerate( - itertools.combinations( - itertools.product(range(n_virtual), range(n_occupied)), 2)): - + itertools.combinations(itertools.product(range(n_virtual), range(n_occupied)), 2) + ): # Get indices of spatial orbitals virtual_spatial_1 = n_occupied + p occupied_spatial_1 = q @@ -306,7 +305,7 @@ def uccsd_singlet_generator(packed_amplitudes, # Generate double excitations coeff = t2_2[i] - for (spin_a, spin_b) in itertools.product(range(2), repeat=2): + for spin_a, spin_b in itertools.product(range(2), repeat=2): # Get the functions which map a spatial orbital index to a # spin orbital index index_a = spin_index_functions[spin_a] @@ -323,13 +322,14 @@ def uccsd_singlet_generator(packed_amplitudes, if occupied_1_a == occupied_2_b: continue else: - generator += FermionOperator( - ((virtual_1_a, 1), (occupied_1_a, 0), (virtual_2_b, 1), - (occupied_2_b, 0)), coeff) + ((virtual_1_a, 1), (occupied_1_a, 0), (virtual_2_b, 1), (occupied_2_b, 0)), + coeff, + ) if anti_hermitian: generator += FermionOperator( - ((occupied_2_b, 1), (virtual_2_b, 0), (occupied_1_a, 1), - (virtual_1_a, 0)), -coeff) + ((occupied_2_b, 1), (virtual_2_b, 0), (occupied_1_a, 1), (virtual_1_a, 0)), + -coeff, + ) return generator diff --git a/src/openfermion/circuits/unitary_cc_test.py b/src/openfermion/circuits/unitary_cc_test.py index d13e1664f..c726d7462 100644 --- a/src/openfermion/circuits/unitary_cc_test.py +++ b/src/openfermion/circuits/unitary_cc_test.py @@ -20,23 +20,26 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData from openfermion.ops.operators import FermionOperator -from openfermion.transforms.opconversions import (get_fermion_operator, - jordan_wigner, normal_ordered) +from openfermion.transforms.opconversions import get_fermion_operator, jordan_wigner, normal_ordered -from openfermion.hamiltonians.special_operators import (s_squared_operator, - sz_operator) -from openfermion.linalg.sparse_tools import (expectation, jordan_wigner_sparse, - jw_hartree_fock_state, - get_sparse_operator) +from openfermion.hamiltonians.special_operators import s_squared_operator, sz_operator +from openfermion.linalg.sparse_tools import ( + expectation, + jordan_wigner_sparse, + jw_hartree_fock_state, + get_sparse_operator, +) -from openfermion.utils import (commutator, count_qubits, hermitian_conjugated) +from openfermion.utils import commutator, count_qubits, hermitian_conjugated from openfermion.circuits.unitary_cc import ( - uccsd_generator, uccsd_singlet_generator, - uccsd_singlet_get_packed_amplitudes, uccsd_singlet_paramsize) + uccsd_generator, + uccsd_singlet_generator, + uccsd_singlet_get_packed_amplitudes, + uccsd_singlet_paramsize, +) class UnitaryCC(unittest.TestCase): - def test_uccsd_anti_hermitian(self): """Test operators are anti-Hermitian independent of inputs""" test_orbitals = 4 @@ -47,35 +50,31 @@ def test_uccsd_anti_hermitian(self): generator = uccsd_generator(single_amplitudes, double_amplitudes) conj_generator = hermitian_conjugated(generator) - self.assertEqual(generator, -1. * conj_generator) + self.assertEqual(generator, -1.0 * conj_generator) def test_uccsd_singlet_anti_hermitian(self): """Test that the singlet version is anti-Hermitian""" test_orbitals = 8 test_electrons = 4 - packed_amplitude_size = uccsd_singlet_paramsize(test_orbitals, - test_electrons) + packed_amplitude_size = uccsd_singlet_paramsize(test_orbitals, test_electrons) packed_amplitudes = randn(int(packed_amplitude_size)) - generator = uccsd_singlet_generator(packed_amplitudes, test_orbitals, - test_electrons) + generator = uccsd_singlet_generator(packed_amplitudes, test_orbitals, test_electrons) conj_generator = hermitian_conjugated(generator) - self.assertEqual(generator, -1. * conj_generator) + self.assertEqual(generator, -1.0 * conj_generator) def test_uccsd_singlet_symmetries(self): """Test that the singlet generator has the correct symmetries.""" test_orbitals = 8 test_electrons = 4 - packed_amplitude_size = uccsd_singlet_paramsize(test_orbitals, - test_electrons) + packed_amplitude_size = uccsd_singlet_paramsize(test_orbitals, test_electrons) packed_amplitudes = randn(int(packed_amplitude_size)) - generator = uccsd_singlet_generator(packed_amplitudes, test_orbitals, - test_electrons) + generator = uccsd_singlet_generator(packed_amplitudes, test_orbitals, test_electrons) # Construct symmetry operators sz = sz_operator(test_orbitals) @@ -97,20 +96,20 @@ def test_uccsd_singlet_builds(self): n_params = uccsd_singlet_paramsize(n_orbitals, n_electrons) self.assertEqual(n_params, 2) - initial_amplitudes = [1., 2.] + initial_amplitudes = [1.0, 2.0] - generator = uccsd_singlet_generator(initial_amplitudes, n_orbitals, - n_electrons) + generator = uccsd_singlet_generator(initial_amplitudes, n_orbitals, n_electrons) - test_generator = (FermionOperator("2^ 0", 1.) + - FermionOperator("0^ 2", -1.) + - FermionOperator("3^ 1", 1.) + - FermionOperator("1^ 3", -1.) + - FermionOperator("2^ 0 3^ 1", 4.) + - FermionOperator("1^ 3 0^ 2", -4.)) + test_generator = ( + FermionOperator("2^ 0", 1.0) + + FermionOperator("0^ 2", -1.0) + + FermionOperator("3^ 1", 1.0) + + FermionOperator("1^ 3", -1.0) + + FermionOperator("2^ 0 3^ 1", 4.0) + + FermionOperator("1^ 3 0^ 2", -4.0) + ) - self.assertEqual(normal_ordered(test_generator), - normal_ordered(generator)) + self.assertEqual(normal_ordered(test_generator), normal_ordered(generator)) # Build 2 n_orbitals = 6 @@ -120,36 +119,40 @@ def test_uccsd_singlet_builds(self): self.assertEqual(n_params, 5) initial_amplitudes = numpy.arange(1, n_params + 1, dtype=float) - generator = uccsd_singlet_generator(initial_amplitudes, n_orbitals, - n_electrons) + generator = uccsd_singlet_generator(initial_amplitudes, n_orbitals, n_electrons) test_generator = ( - FermionOperator("2^ 0", 1.) + FermionOperator("0^ 2", -1) + - FermionOperator("3^ 1", 1.) + FermionOperator("1^ 3", -1.) + - FermionOperator("4^ 0", 2.) + FermionOperator("0^ 4", -2) + - FermionOperator("5^ 1", 2.) + FermionOperator("1^ 5", -2.) + - FermionOperator("2^ 0 3^ 1", 6.) + - FermionOperator("1^ 3 0^ 2", -6.) + - FermionOperator("4^ 0 5^ 1", 8.) + - FermionOperator("1^ 5 0^ 4", -8.) + - FermionOperator("2^ 0 5^ 1", 5.) + - FermionOperator("1^ 5 0^ 2", -5.) + - FermionOperator("4^ 0 3^ 1", 5.) + - FermionOperator("1^ 3 0^ 4", -5.) + - FermionOperator("2^ 0 4^ 0", 5.) + - FermionOperator("0^ 4 0^ 2", -5.) + - FermionOperator("3^ 1 5^ 1", 5.) + - FermionOperator("1^ 5 1^ 3", -5.)) - - self.assertEqual(normal_ordered(test_generator), - normal_ordered(generator)) + FermionOperator("2^ 0", 1.0) + + FermionOperator("0^ 2", -1) + + FermionOperator("3^ 1", 1.0) + + FermionOperator("1^ 3", -1.0) + + FermionOperator("4^ 0", 2.0) + + FermionOperator("0^ 4", -2) + + FermionOperator("5^ 1", 2.0) + + FermionOperator("1^ 5", -2.0) + + FermionOperator("2^ 0 3^ 1", 6.0) + + FermionOperator("1^ 3 0^ 2", -6.0) + + FermionOperator("4^ 0 5^ 1", 8.0) + + FermionOperator("1^ 5 0^ 4", -8.0) + + FermionOperator("2^ 0 5^ 1", 5.0) + + FermionOperator("1^ 5 0^ 2", -5.0) + + FermionOperator("4^ 0 3^ 1", 5.0) + + FermionOperator("1^ 3 0^ 4", -5.0) + + FermionOperator("2^ 0 4^ 0", 5.0) + + FermionOperator("0^ 4 0^ 2", -5.0) + + FermionOperator("3^ 1 5^ 1", 5.0) + + FermionOperator("1^ 5 1^ 3", -5.0) + ) + + self.assertEqual(normal_ordered(test_generator), normal_ordered(generator)) def test_sparse_uccsd_generator_numpy_inputs(self): """Test numpy ndarray inputs to uccsd_generator that are sparse""" test_orbitals = 30 sparse_single_amplitudes = numpy.zeros((test_orbitals, test_orbitals)) sparse_double_amplitudes = numpy.zeros( - (test_orbitals, test_orbitals, test_orbitals, test_orbitals)) + (test_orbitals, test_orbitals, test_orbitals, test_orbitals) + ) sparse_single_amplitudes[3, 5] = 0.12345 sparse_single_amplitudes[12, 4] = 0.44313 @@ -157,36 +160,37 @@ def test_sparse_uccsd_generator_numpy_inputs(self): sparse_double_amplitudes[0, 12, 6, 2] = 0.3434 sparse_double_amplitudes[1, 4, 6, 13] = -0.23423 - generator = uccsd_generator(sparse_single_amplitudes, - sparse_double_amplitudes) - - test_generator = (0.12345 * FermionOperator("3^ 5") + - (-0.12345) * FermionOperator("5^ 3") + - 0.44313 * FermionOperator("12^ 4") + - (-0.44313) * FermionOperator("4^ 12") + - 0.3434 * FermionOperator("0^ 12 6^ 2") + - (-0.3434) * FermionOperator("2^ 6 12^ 0") + - (-0.23423) * FermionOperator("1^ 4 6^ 13") + - 0.23423 * FermionOperator("13^ 6 4^ 1")) + generator = uccsd_generator(sparse_single_amplitudes, sparse_double_amplitudes) + + test_generator = ( + 0.12345 * FermionOperator("3^ 5") + + (-0.12345) * FermionOperator("5^ 3") + + 0.44313 * FermionOperator("12^ 4") + + (-0.44313) * FermionOperator("4^ 12") + + 0.3434 * FermionOperator("0^ 12 6^ 2") + + (-0.3434) * FermionOperator("2^ 6 12^ 0") + + (-0.23423) * FermionOperator("1^ 4 6^ 13") + + 0.23423 * FermionOperator("13^ 6 4^ 1") + ) self.assertEqual(test_generator, generator) def test_sparse_uccsd_generator_list_inputs(self): """Test list inputs to uccsd_generator that are sparse""" sparse_single_amplitudes = [[[3, 5], 0.12345], [[12, 4], 0.44313]] - sparse_double_amplitudes = [[[0, 12, 6, 2], 0.3434], - [[1, 4, 6, 13], -0.23423]] - - generator = uccsd_generator(sparse_single_amplitudes, - sparse_double_amplitudes) - - test_generator = (0.12345 * FermionOperator("3^ 5") + - (-0.12345) * FermionOperator("5^ 3") + - 0.44313 * FermionOperator("12^ 4") + - (-0.44313) * FermionOperator("4^ 12") + - 0.3434 * FermionOperator("0^ 12 6^ 2") + - (-0.3434) * FermionOperator("2^ 6 12^ 0") + - (-0.23423) * FermionOperator("1^ 4 6^ 13") + - 0.23423 * FermionOperator("13^ 6 4^ 1")) + sparse_double_amplitudes = [[[0, 12, 6, 2], 0.3434], [[1, 4, 6, 13], -0.23423]] + + generator = uccsd_generator(sparse_single_amplitudes, sparse_double_amplitudes) + + test_generator = ( + 0.12345 * FermionOperator("3^ 5") + + (-0.12345) * FermionOperator("5^ 3") + + 0.44313 * FermionOperator("12^ 4") + + (-0.44313) * FermionOperator("4^ 12") + + 0.3434 * FermionOperator("0^ 12 6^ 2") + + (-0.3434) * FermionOperator("2^ 6 12^ 0") + + (-0.23423) * FermionOperator("1^ 4 6^ 13") + + 0.23423 * FermionOperator("13^ 6 4^ 1") + ) self.assertEqual(test_generator, generator) def test_uccsd_singlet_get_packed_amplitudes(self): @@ -194,7 +198,8 @@ def test_uccsd_singlet_get_packed_amplitudes(self): test_electrons = 2 sparse_single_amplitudes = numpy.zeros((test_orbitals, test_orbitals)) sparse_double_amplitudes = numpy.zeros( - (test_orbitals, test_orbitals, test_orbitals, test_orbitals)) + (test_orbitals, test_orbitals, test_orbitals, test_orbitals) + ) sparse_single_amplitudes[2, 0] = 0.12345 sparse_single_amplitudes[3, 1] = 0.12345 @@ -205,24 +210,21 @@ def test_uccsd_singlet_get_packed_amplitudes(self): sparse_double_amplitudes[5, 1, 3, 1] = 0.3434 packed_amplitudes = uccsd_singlet_get_packed_amplitudes( - sparse_single_amplitudes, sparse_double_amplitudes, test_orbitals, - test_electrons) + sparse_single_amplitudes, sparse_double_amplitudes, test_orbitals, test_electrons + ) self.assertEqual(len(packed_amplitudes), 5) self.assertTrue( - numpy.allclose(packed_amplitudes, - numpy.array([0.12345, 0., 0.9, 0., 0.3434]))) + numpy.allclose(packed_amplitudes, numpy.array([0.12345, 0.0, 0.9, 0.0, 0.3434])) + ) def test_ucc_h2(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. @@ -237,56 +239,48 @@ def test_ucc_h2(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. - self.fermion_hamiltonian = normal_ordered( - get_fermion_operator(self.molecular_hamiltonian)) + self.fermion_hamiltonian = normal_ordered(get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. - self.hamiltonian_matrix = get_sparse_operator( - self.molecular_hamiltonian) + self.hamiltonian_matrix = get_sparse_operator(self.molecular_hamiltonian) # Test UCCSD for accuracy against FCI using loaded t amplitudes. - ucc_operator = uccsd_generator(self.molecule.ccsd_single_amps, - self.molecule.ccsd_double_amps) + ucc_operator = uccsd_generator( + self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps + ) - hf_state = jw_hartree_fock_state(self.molecule.n_electrons, - count_qubits(self.qubit_hamiltonian)) + hf_state = jw_hartree_fock_state( + self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian) + ) uccsd_sparse = jordan_wigner_sparse(ucc_operator) uccsd_state = scipy.sparse.linalg.expm_multiply(uccsd_sparse, hf_state) - expected_uccsd_energy = expectation(self.hamiltonian_matrix, - uccsd_state) - self.assertAlmostEqual(expected_uccsd_energy, - self.molecule.fci_energy, - places=4) + expected_uccsd_energy = expectation(self.hamiltonian_matrix, uccsd_state) + self.assertAlmostEqual(expected_uccsd_energy, self.molecule.fci_energy, places=4) print("UCCSD ENERGY: {}".format(expected_uccsd_energy)) # Test CCSD for precise match against FCI using loaded t amplitudes. - ccsd_operator = uccsd_generator(self.molecule.ccsd_single_amps, - self.molecule.ccsd_double_amps, - anti_hermitian=False) + ccsd_operator = uccsd_generator( + self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps, anti_hermitian=False + ) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) - ccsd_sparse_l = jordan_wigner_sparse( - -hermitian_conjugated(ccsd_operator)) + ccsd_sparse_l = jordan_wigner_sparse(-hermitian_conjugated(ccsd_operator)) - ccsd_state_r = scipy.sparse.linalg.expm_multiply( - ccsd_sparse_r, hf_state) - ccsd_state_l = scipy.sparse.linalg.expm_multiply( - ccsd_sparse_l, hf_state) + ccsd_state_r = scipy.sparse.linalg.expm_multiply(ccsd_sparse_r, hf_state) + ccsd_state_l = scipy.sparse.linalg.expm_multiply(ccsd_sparse_l, hf_state) expected_ccsd_energy = ccsd_state_l.conjugate().dot( - self.hamiltonian_matrix.dot(ccsd_state_r)) + self.hamiltonian_matrix.dot(ccsd_state_r) + ) self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy) def test_ucc_h2_singlet(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. @@ -301,50 +295,50 @@ def test_ucc_h2_singlet(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. - self.fermion_hamiltonian = normal_ordered( - get_fermion_operator(self.molecular_hamiltonian)) + self.fermion_hamiltonian = normal_ordered(get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. - self.hamiltonian_matrix = get_sparse_operator( - self.molecular_hamiltonian) + self.hamiltonian_matrix = get_sparse_operator(self.molecular_hamiltonian) # Test UCCSD for accuracy against FCI using loaded t amplitudes. - ucc_operator = uccsd_generator(self.molecule.ccsd_single_amps, - self.molecule.ccsd_double_amps) + ucc_operator = uccsd_generator( + self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps + ) - hf_state = jw_hartree_fock_state(self.molecule.n_electrons, - count_qubits(self.qubit_hamiltonian)) + hf_state = jw_hartree_fock_state( + self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian) + ) uccsd_sparse = jordan_wigner_sparse(ucc_operator) uccsd_state = scipy.sparse.linalg.expm_multiply(uccsd_sparse, hf_state) - expected_uccsd_energy = expectation(self.hamiltonian_matrix, - uccsd_state) - self.assertAlmostEqual(expected_uccsd_energy, - self.molecule.fci_energy, - places=4) + expected_uccsd_energy = expectation(self.hamiltonian_matrix, uccsd_state) + self.assertAlmostEqual(expected_uccsd_energy, self.molecule.fci_energy, places=4) print("UCCSD ENERGY: {}".format(expected_uccsd_energy)) # Test CCSD singlet for precise match against FCI using loaded t # amplitudes packed_amplitudes = uccsd_singlet_get_packed_amplitudes( - self.molecule.ccsd_single_amps, self.molecule.ccsd_double_amps, - self.molecule.n_qubits, self.molecule.n_electrons) - ccsd_operator = uccsd_singlet_generator(packed_amplitudes, - self.molecule.n_qubits, - self.molecule.n_electrons, - anti_hermitian=False) + self.molecule.ccsd_single_amps, + self.molecule.ccsd_double_amps, + self.molecule.n_qubits, + self.molecule.n_electrons, + ) + ccsd_operator = uccsd_singlet_generator( + packed_amplitudes, + self.molecule.n_qubits, + self.molecule.n_electrons, + anti_hermitian=False, + ) ccsd_sparse_r = jordan_wigner_sparse(ccsd_operator) - ccsd_sparse_l = jordan_wigner_sparse( - -hermitian_conjugated(ccsd_operator)) + ccsd_sparse_l = jordan_wigner_sparse(-hermitian_conjugated(ccsd_operator)) - ccsd_state_r = scipy.sparse.linalg.expm_multiply( - ccsd_sparse_r, hf_state) - ccsd_state_l = scipy.sparse.linalg.expm_multiply( - ccsd_sparse_l, hf_state) + ccsd_state_r = scipy.sparse.linalg.expm_multiply(ccsd_sparse_r, hf_state) + ccsd_state_l = scipy.sparse.linalg.expm_multiply(ccsd_sparse_l, hf_state) expected_ccsd_energy = ccsd_state_l.conjugate().dot( - self.hamiltonian_matrix.dot(ccsd_state_r)) + self.hamiltonian_matrix.dot(ccsd_state_r) + ) self.assertAlmostEqual(expected_ccsd_energy, self.molecule.fci_energy) def test_value_error_for_odd_n_qubits(self): @@ -354,4 +348,4 @@ def test_value_error_for_odd_n_qubits(self): def test_value_error_bad_amplitudes(self): with self.assertRaises(ValueError): - _ = uccsd_singlet_generator([1.], 3, 4) + _ = uccsd_singlet_generator([1.0], 3, 4) diff --git a/src/openfermion/circuits/vpe_circuits.py b/src/openfermion/circuits/vpe_circuits.py index e675519b4..bf2a322a1 100644 --- a/src/openfermion/circuits/vpe_circuits.py +++ b/src/openfermion/circuits/vpe_circuits.py @@ -16,9 +16,13 @@ import cirq -def vpe_single_circuit(qubits: Sequence[cirq.Qid], prep: cirq.Circuit, - evolve: cirq.Circuit, initial_rotation: cirq.Gate, - final_rotation: cirq.Gate) -> cirq.Circuit: +def vpe_single_circuit( + qubits: Sequence[cirq.Qid], + prep: cirq.Circuit, + evolve: cirq.Circuit, + initial_rotation: cirq.Gate, + final_rotation: cirq.Gate, +) -> cirq.Circuit: """ Combines the different parts that make up a VPE circuit @@ -63,12 +67,13 @@ def vpe_single_circuit(qubits: Sequence[cirq.Qid], prep: cirq.Circuit, # yapf: enable -def vpe_circuits_single_timestep(qubits: Sequence[cirq.Qid], - prep: cirq.Circuit, - evolve: cirq.Circuit, - target_qubit: cirq.Qid, - rotation_set: Optional[Sequence] = None - ) -> Sequence[cirq.Circuit]: +def vpe_circuits_single_timestep( + qubits: Sequence[cirq.Qid], + prep: cirq.Circuit, + evolve: cirq.Circuit, + target_qubit: cirq.Qid, + rotation_set: Optional[Sequence] = None, +) -> Sequence[cirq.Circuit]: """Prepares the circuits to perform VPE at a fixed time Puts together the set of pre- and post-rotations to implement @@ -95,7 +100,9 @@ def vpe_circuits_single_timestep(qubits: Sequence[cirq.Qid], if rotation_set is None: rotation_set = standard_vpe_rotation_set circuits = [ - vpe_single_circuit(qubits, prep, evolve, rdata[1].on(target_qubit), - rdata[2].on(target_qubit)) for rdata in rotation_set + vpe_single_circuit( + qubits, prep, evolve, rdata[1].on(target_qubit), rdata[2].on(target_qubit) + ) + for rdata in rotation_set ] return circuits diff --git a/src/openfermion/circuits/vpe_circuits_test.py b/src/openfermion/circuits/vpe_circuits_test.py index b8cb9aa67..95b04a747 100644 --- a/src/openfermion/circuits/vpe_circuits_test.py +++ b/src/openfermion/circuits/vpe_circuits_test.py @@ -16,10 +16,7 @@ from openfermion.measurements import get_phase_function -from .vpe_circuits import ( - vpe_single_circuit, - vpe_circuits_single_timestep, -) +from .vpe_circuits import vpe_single_circuit, vpe_circuits_single_timestep def test_single_circuit(): @@ -27,13 +24,10 @@ def test_single_circuit(): q1 = cirq.GridQubit(0, 1) qubits = reversed([q0, q1]) prep = cirq.Circuit([cirq.FSimGate(theta=numpy.pi / 4, phi=0).on(q0, q1)]) - evolve = cirq.Circuit( - [cirq.rz(numpy.pi / 2).on(q0), - cirq.rz(numpy.pi / 2).on(q1)]) + evolve = cirq.Circuit([cirq.rz(numpy.pi / 2).on(q0), cirq.rz(numpy.pi / 2).on(q1)]) initial_rotation = cirq.ry(numpy.pi / 2).on(q0) final_rotation = cirq.rx(-numpy.pi / 2).on(q0) - circuit = vpe_single_circuit(qubits, prep, evolve, initial_rotation, - final_rotation) + circuit = vpe_single_circuit(qubits, prep, evolve, initial_rotation, final_rotation) assert len(circuit) == 6 simulator = cirq.Simulator() result = simulator.run(circuit, repetitions=100) @@ -46,9 +40,7 @@ def test_single_timestep(): q1 = cirq.GridQubit(0, 1) qubits = [q0, q1] prep = cirq.Circuit([cirq.FSimGate(theta=numpy.pi / 4, phi=0).on(q0, q1)]) - evolve = cirq.Circuit( - [cirq.rz(numpy.pi / 2).on(q0), - cirq.rz(numpy.pi / 2).on(q1)]) + evolve = cirq.Circuit([cirq.rz(numpy.pi / 2).on(q0), cirq.rz(numpy.pi / 2).on(q1)]) target_qubit = q0 circuits = vpe_circuits_single_timestep(qubits, prep, evolve, target_qubit) results = [] diff --git a/src/openfermion/contrib/representability/_bijections.py b/src/openfermion/contrib/representability/_bijections.py index d1e23f350..150126b83 100644 --- a/src/openfermion/contrib/representability/_bijections.py +++ b/src/openfermion/contrib/representability/_bijections.py @@ -2,7 +2,6 @@ class Bijection: - def __init__(self, fwd: Callable, rev: Callable, sizes: Callable): """ Bijection holds forward maps and backwards maps diff --git a/src/openfermion/contrib/representability/_bijections_test.py b/src/openfermion/contrib/representability/_bijections_test.py index b544b4596..71de7a506 100644 --- a/src/openfermion/contrib/representability/_bijections_test.py +++ b/src/openfermion/contrib/representability/_bijections_test.py @@ -1,6 +1,9 @@ from itertools import product -from openfermion.contrib.representability._bijections import Bijection, \ - index_index_basis, index_tuple_basis +from openfermion.contrib.representability._bijections import ( + Bijection, + index_index_basis, + index_tuple_basis, +) def test_bijection(): @@ -28,4 +31,4 @@ def test_geminal_basis(): assert b.fwd(4) == (0, 4) assert b.rev((0, 4)) == 4 assert b.rev(b.fwd(4)) == 4 - assert b.domain_element_sizes() == (1, 2) \ No newline at end of file + assert b.domain_element_sizes() == (1, 2) diff --git a/src/openfermion/contrib/representability/_dualbasis.py b/src/openfermion/contrib/representability/_dualbasis.py index f957ec321..1375ef88d 100644 --- a/src/openfermion/contrib/representability/_dualbasis.py +++ b/src/openfermion/contrib/representability/_dualbasis.py @@ -18,14 +18,15 @@ class DualBasisElement(object): for all i in [1, dim(`M')]. """ - def __init__(self, - *, - tensor_names: Optional[Union[None, List[str]]] = None, - tensor_elements: Optional[ - Union[None, List[Tuple[int, ...]]]] = None, - tensor_coeffs: Optional[Union[None, List[float]]] = None, - bias: Optional[int] = 0, - scalar: Optional[int] = 0): + def __init__( + self, + *, + tensor_names: Optional[Union[None, List[str]]] = None, + tensor_elements: Optional[Union[None, List[Tuple[int, ...]]]] = None, + tensor_coeffs: Optional[Union[None, List[float]]] = None, + bias: Optional[int] = 0, + scalar: Optional[int] = 0, + ): """ Define a linear operator on a tensor `A', a bias `b', and a result `c' satisfying @@ -106,9 +107,9 @@ def simplify(self): Mutate the DualBasisElement so that non-unique terms get summed together """ id_dict = {} - for tname, telement, tcoeff in zip(self.primal_tensors_names, - self.primal_elements, - self.primal_coeffs): + for tname, telement, tcoeff in zip( + self.primal_tensors_names, self.primal_elements, self.primal_coeffs + ): id_str = tname + ".".join([str(x) for x in telement]) if id_str not in id_dict: id_dict[id_str] = (tname, telement, tcoeff) @@ -134,15 +135,14 @@ def id(self): Get the unique string identifier for the dual basis element """ id_str = "" - for name, element in zip(self.primal_tensors_names, - self.primal_elements): + for name, element in zip(self.primal_tensors_names, self.primal_elements): id_str += name + "(" + ",".join([repr(x) for x in element]) + ")\t" return id_str def __iter__(self): - for t_label, velement, coeff in zip(self.primal_tensors_names, - self.primal_elements, - self.primal_coeffs): + for t_label, velement, coeff in zip( + self.primal_tensors_names, self.primal_elements, self.primal_coeffs + ): yield t_label, velement, coeff def __add__(self, other): @@ -151,15 +151,11 @@ def __add__(self, other): elif isinstance(other, DualBasis): return other + self else: - raise TypeError( - "DualBasisElement can be added to same type or DualBasis") + raise TypeError("DualBasisElement can be added to same type or DualBasis") class DualBasis(object): - - def __init__( - self, - elements: Optional[Union[None, List[DualBasisElement]]] = None): + def __init__(self, elements: Optional[Union[None, List[DualBasisElement]]] = None): """ A collection of DualBasisElements @@ -196,5 +192,4 @@ def __add__(self, other): return DualBasis(elements=new_elements) else: - raise TypeError( - "DualBasis adds DualBasisElements or DualBasis only") + raise TypeError("DualBasis adds DualBasisElements or DualBasis only") diff --git a/src/openfermion/contrib/representability/_dualbasis_test.py b/src/openfermion/contrib/representability/_dualbasis_test.py index 828577d4b..e0767a948 100644 --- a/src/openfermion/contrib/representability/_dualbasis_test.py +++ b/src/openfermion/contrib/representability/_dualbasis_test.py @@ -1,16 +1,15 @@ import pytest import numpy as np -from openfermion.contrib.representability._dualbasis import \ - DualBasisElement, DualBasis +from openfermion.contrib.representability._dualbasis import DualBasisElement, DualBasis def test_dualbasis_element_init(): dbe = DualBasisElement() assert isinstance(dbe, DualBasisElement) - dbe = DualBasisElement(tensor_names=['A'] * 5, - tensor_coeffs=[1] * 5, - tensor_elements=[(i, i) for i in range(5)]) + dbe = DualBasisElement( + tensor_names=['A'] * 5, tensor_coeffs=[1] * 5, tensor_elements=[(i, i) for i in range(5)] + ) assert dbe.primal_tensors_names == ['A'] * 5 assert dbe.primal_coeffs == [1] * 5 @@ -22,9 +21,9 @@ def test_dualbasis_element_init(): def test_daulbasis_init(): db = DualBasis() assert isinstance(db, DualBasis) - dbe = DualBasisElement(tensor_names=['A'] * 5, - tensor_coeffs=[1] * 5, - tensor_elements=[(i, i) for i in range(5)]) + dbe = DualBasisElement( + tensor_names=['A'] * 5, tensor_coeffs=[1] * 5, tensor_elements=[(i, i) for i in range(5)] + ) db = DualBasis(elements=[dbe]) assert db[0] == dbe assert len(db) == 1 @@ -35,9 +34,9 @@ def test_daulbasis_init(): assert isinstance(db2, DualBasis) assert len(db2) == 2 - tdbe = DualBasisElement(tensor_names=['B'] * 5, - tensor_coeffs=[1] * 5, - tensor_elements=[(i, i) for i in range(5)]) + tdbe = DualBasisElement( + tensor_names=['B'] * 5, tensor_coeffs=[1] * 5, tensor_elements=[(i, i) for i in range(5)] + ) tdb = DualBasis(elements=[tdbe]) db3 = db + tdb assert isinstance(db3, DualBasis) @@ -69,17 +68,17 @@ def test_dbe_element_add(): def test_dbe_string(): - dbe = DualBasisElement(tensor_names=['A'] * 5, - tensor_coeffs=[1] * 5, - tensor_elements=[(i, i) for i in range(5)]) + dbe = DualBasisElement( + tensor_names=['A'] * 5, tensor_coeffs=[1] * 5, tensor_elements=[(i, i) for i in range(5)] + ) assert dbe.id() == "A(0,0) A(1,1) A(2,2) A(3,3) A(4,4)\t" def test_dbe_iterator(): - dbe = DualBasisElement(tensor_names=['A'] * 5, - tensor_coeffs=[1] * 5, - tensor_elements=[(i, i) for i in range(5)]) + dbe = DualBasisElement( + tensor_names=['A'] * 5, tensor_coeffs=[1] * 5, tensor_elements=[(i, i) for i in range(5)] + ) for idx, (t, v, c) in enumerate(dbe): assert t == 'A' assert v == (idx, idx) @@ -131,9 +130,7 @@ def test_simplify(): names = ['opdm'] * 3 + ['oqdm'] elements = [(i, j), (i, j), (i, l), (l, k)] coeffs = [1, 1, 0.25, 0.3] - dbe = DualBasisElement(tensor_names=names, - tensor_elements=elements, - tensor_coeffs=coeffs) + dbe = DualBasisElement(tensor_names=names, tensor_elements=elements, tensor_coeffs=coeffs) dbe.simplify() assert len(dbe.primal_tensors_names) == 3 assert set(dbe.primal_coeffs) == {2, 0.25, 0.3} diff --git a/src/openfermion/contrib/representability/_higham.py b/src/openfermion/contrib/representability/_higham.py index 35a25f1f4..eef85c7fe 100644 --- a/src/openfermion/contrib/representability/_higham.py +++ b/src/openfermion/contrib/representability/_higham.py @@ -63,7 +63,7 @@ def higham_polynomial(eigenvalues, shift): return heaviside_indicator.T.dot(eigenvalues - shift) -def higham_root(eigenvalues, target_trace, epsilon=1.0E-15): +def higham_root(eigenvalues, target_trace, epsilon=1.0e-15): """ Find the root of f(sigma) = sum_{j}Theta(l_{i} - sigma)(l_{i} - sigma) = T @@ -99,8 +99,7 @@ def higham_root(eigenvalues, target_trace, epsilon=1.0E-15): def map_to_matrix(mat): if mat.ndim != 4: - raise TypeError( - "I only map rank-4 tensors to matices with symmetric support") + raise TypeError("I only map rank-4 tensors to matices with symmetric support") dim = mat.shape[0] matform = np.zeros((dim**2, dim**2)) for p, q, r, s in product(range(dim), repeat=4): @@ -111,8 +110,7 @@ def map_to_matrix(mat): def map_to_tensor(mat): if mat.ndim != 2: - raise TypeError( - "I only map matrices to rank-4 tensors with symmetric support") + raise TypeError("I only map matrices to rank-4 tensors with symmetric support") dim = int(np.sqrt(mat.shape[0])) tensor_form = np.zeros((dim, dim, dim, dim)) for p, q, r, s in product(range(dim), repeat=4): @@ -140,16 +138,14 @@ def fixed_trace_positive_projection(bmat, target_trace): bmat = 0.5 * (bmat + bmat.conj().T) w, v = np.linalg.eigh(bmat) - if np.all(w >= -1.0 * float(1.0E-14)) and np.isclose( - np.sum(w), target_trace): + if np.all(w >= -1.0 * float(1.0e-14)) and np.isclose(np.sum(w), target_trace): purified_matrix = bmat else: sigma = higham_root(w, target_trace) shifted_eigs = np.multiply(heaviside(w - sigma), (w - sigma)) purified_matrix = np.zeros_like(bmat) for i in range(w.shape[0]): - purified_matrix += shifted_eigs[i] * \ - v[:, [i]].dot(v[:, [i]].conj().T) + purified_matrix += shifted_eigs[i] * v[:, [i]].dot(v[:, [i]].conj().T) if map_to_four_tensor: purified_matrix = map_to_tensor(purified_matrix) diff --git a/src/openfermion/contrib/representability/_higham_test.py b/src/openfermion/contrib/representability/_higham_test.py index 89d800570..eafa490aa 100644 --- a/src/openfermion/contrib/representability/_higham_test.py +++ b/src/openfermion/contrib/representability/_higham_test.py @@ -32,8 +32,13 @@ import numpy as np import pytest from openfermion.contrib.representability._higham import ( - heaviside, higham_polynomial, higham_root, map_to_tensor, map_to_matrix, - fixed_trace_positive_projection) + heaviside, + higham_polynomial, + higham_root, + map_to_tensor, + map_to_matrix, + fixed_trace_positive_projection, +) def test_heaviside(): @@ -102,27 +107,24 @@ def test_reconstruction(): test_mat = fixed_trace_positive_projection(mat, np.trace(mat)) assert np.isclose(np.trace(test_mat), np.trace(mat)) w, v = np.linalg.eigh(test_mat) - assert np.all(w >= -(float(4.0E-15))) + assert np.all(w >= -(float(4.0e-15))) mat = np.arange(16).reshape((4, 4)) mat = 0.5 * (mat + mat.T) mat_tensor = map_to_tensor(mat) trace_mat = np.trace(mat) true_mat = fixed_trace_positive_projection(mat, trace_mat) - test_mat = map_to_matrix( - fixed_trace_positive_projection(mat_tensor, trace_mat)) + test_mat = map_to_matrix(fixed_trace_positive_projection(mat_tensor, trace_mat)) assert np.allclose(true_mat, test_mat) - assert np.allclose(true_mat, - fixed_trace_positive_projection(true_mat, trace_mat)) + assert np.allclose(true_mat, fixed_trace_positive_projection(true_mat, trace_mat)) def test_mlme(): """ Test from fig 1 of maximum likelihood minimum effort! """ - eigs = np.array( - list(reversed([3.0 / 5, 1.0 / 2, 7.0 / 20, 1.0 / 10, -11.0 / 20]))) + eigs = np.array(list(reversed([3.0 / 5, 1.0 / 2, 7.0 / 20, 1.0 / 10, -11.0 / 20]))) target_trace = 1.0 sigma = higham_root(eigs, target_trace) shifted_eigs = np.multiply(heaviside(eigs - sigma), (eigs - sigma)) diff --git a/src/openfermion/contrib/representability/_multitensor.py b/src/openfermion/contrib/representability/_multitensor.py index 07197ce14..b6c23dbfa 100644 --- a/src/openfermion/contrib/representability/_multitensor.py +++ b/src/openfermion/contrib/representability/_multitensor.py @@ -1,11 +1,9 @@ import numpy as np from scipy.sparse import csr_matrix -from openfermion.contrib.representability._dualbasis import \ - DualBasisElement, DualBasis +from openfermion.contrib.representability._dualbasis import DualBasisElement, DualBasis class TMap(object): - def __init__(self, tensors): """ provide a map of tensor name to tensors @@ -25,7 +23,6 @@ def __iter__(self): class MultiTensor(object): - def __init__(self, tensors, dual_basis=DualBasis()): """ A collection of tensor objects with maps from name to tensor @@ -82,8 +79,7 @@ def add_dual_elements(self, dual_element): Add a dual element to the dual basis """ if not isinstance(dual_element, DualBasisElement): - raise TypeError( - "dual_element variable needs to be a DualBasisElement type") + raise TypeError("dual_element variable needs to be a DualBasisElement type") # we should extend TMap to add self.dual_basis.elements.extend(dual_element) @@ -114,16 +110,16 @@ def synthesize_dual_basis(self): inner_prod_data_values.append(float(dual_element.dual_scalar)) bias_data_values.append(dual_element.constant_bias) sparse_dual_operator = csr_matrix( - (dual_data_values, (dual_row_indices, dual_col_indices)), - [index + 1, self.vec_dim]) + (dual_data_values, (dual_row_indices, dual_col_indices)), [index + 1, self.vec_dim] + ) sparse_bias_vector = csr_matrix( - (bias_data_values, (range(index + 1), [0] * (index + 1))), - [index + 1, 1]) + (bias_data_values, (range(index + 1), [0] * (index + 1))), [index + 1, 1] + ) sparse_innerp_vector = csr_matrix( - (inner_prod_data_values, (range(index + 1), [0] * (index + 1))), - [index + 1, 1]) + (inner_prod_data_values, (range(index + 1), [0] * (index + 1))), [index + 1, 1] + ) return sparse_dual_operator, sparse_bias_vector, sparse_innerp_vector @@ -136,8 +132,9 @@ def synthesize_element(self, element): col_idx = [] data_vals = [] for tlabel, velement, coeff in element: - col_idx.append(self.off_set_map[tlabel] + - self.tensors[tlabel].index_vectorized(*velement)) + col_idx.append( + self.off_set_map[tlabel] + self.tensors[tlabel].index_vectorized(*velement) + ) data_vals.append(coeff) return col_idx, data_vals diff --git a/src/openfermion/contrib/representability/_multitensor_test.py b/src/openfermion/contrib/representability/_multitensor_test.py index 54f2d31fd..620fa07e3 100644 --- a/src/openfermion/contrib/representability/_multitensor_test.py +++ b/src/openfermion/contrib/representability/_multitensor_test.py @@ -2,10 +2,8 @@ import pytest import scipy as sp from openfermion.contrib.representability._namedtensor import Tensor -from openfermion.contrib.representability._multitensor import MultiTensor, \ - TMap -from openfermion.contrib.representability._dualbasis import DualBasis, \ - DualBasisElement +from openfermion.contrib.representability._multitensor import MultiTensor, TMap +from openfermion.contrib.representability._dualbasis import DualBasis, DualBasisElement def test_tmap(): @@ -158,11 +156,13 @@ def test_dual_basis_element(): rdm = MultiTensor([opdm]) def generate_dual_basis_element(i, j): - element = DualBasisElement(tensor_names=["opdm"], - tensor_elements=[(i, j)], - tensor_coeffs=[-1.0], - bias=1 if i == j else 0, - scalar=0) + element = DualBasisElement( + tensor_names=["opdm"], + tensor_elements=[(i, j)], + tensor_coeffs=[-1.0], + bias=1 if i == j else 0, + scalar=0, + ) return element opdm_to_oqdm_map = DualBasis() diff --git a/src/openfermion/contrib/representability/_namedtensor.py b/src/openfermion/contrib/representability/_namedtensor.py index 428528c97..ab4db7da3 100644 --- a/src/openfermion/contrib/representability/_namedtensor.py +++ b/src/openfermion/contrib/representability/_namedtensor.py @@ -1,8 +1,7 @@ from typing import Iterable, Generator, Optional, Union from itertools import zip_longest import numpy as np -from openfermion.contrib.representability._bijections import Bijection, \ - index_index_basis +from openfermion.contrib.representability._bijections import Bijection, index_index_basis class Tensor(object): @@ -10,11 +9,13 @@ class Tensor(object): Instantiation of named tensor """ - def __init__(self, - *, - tensor: Optional[Union[None, np.ndarray]] = None, - basis: Optional[Union[None, Bijection]] = None, - name: Optional[Union[None, str]] = None): + def __init__( + self, + *, + tensor: Optional[Union[None, np.ndarray]] = None, + basis: Optional[Union[None, Bijection]] = None, + name: Optional[Union[None, str]] = None, + ): """ Named Tensor that allows one to label elements with different indices @@ -99,8 +100,7 @@ def index_vectorized(self, *indices): Note: the start returns a tuple of n-indices. That includes 1 """ - return self.index_bijection(self.index_transform(indices), self.ndim, - self.dim) + return self.index_bijection(self.index_transform(indices), self.ndim, self.dim) def index_transform(self, indices): """ @@ -112,8 +112,7 @@ def index_transform(self, indices): codomain_element_size = self.basis.domain_element_sizes()[1] index_set = [] for idx_set in grouper(indices, codomain_element_size): - index_set.append( - self.basis.rev(idx_set[0] if len(idx_set) == 1 else idx_set)) + index_set.append(self.basis.rev(idx_set[0] if len(idx_set) == 1 else idx_set)) return tuple(index_set) @@ -123,14 +122,13 @@ def index_bijection(indices, ndim, dim): calculate the bijection with tensor dim counting """ if len(indices) != ndim: - raise TypeError( - "indices are inappriopriate length for the given ndim") + raise TypeError("indices are inappriopriate length for the given ndim") # C-order canonical vectorization--i.e. right most index in indices # changes with the highest frequency bijection = 0 for n in range(ndim): - bijection += indices[n] * dim**(ndim - n - 1) + bijection += indices[n] * dim ** (ndim - n - 1) return bijection def utri_iterator(self) -> Generator: @@ -159,17 +157,15 @@ def _iterator(self, ultri: str) -> Generator: Iterate over the a data store yielding the upper/lower/all values """ if ultri not in ['upper', 'lower', 'all']: - raise TypeError( - "iteration type {} is not 'upper', 'lower', or 'all'".format( - ultri)) + raise TypeError("iteration type {} is not 'upper', 'lower', or 'all'".format(ultri)) it = np.nditer(self.data, flags=['multi_index']) while not it.finished: indices = it.multi_index - left_idx_set = self.index_bijection(indices[:self.ndim // 2], - self.ndim // 2, self.dim) - right_idx_set = self.index_bijection(indices[self.ndim // 2:], - self.ndim // 2, self.dim) + left_idx_set = self.index_bijection(indices[: self.ndim // 2], self.ndim // 2, self.dim) + right_idx_set = self.index_bijection( + indices[self.ndim // 2 :], self.ndim // 2, self.dim + ) if ultri == 'upper' and left_idx_set <= right_idx_set: yield it[0], map(lambda x: self.basis.fwd(x), it.multi_index) @@ -190,9 +186,7 @@ def vectorize(self, order: Optional[str] = 'C') -> np.ndarray: # from standard library itertools recipe book -def grouper(iterable: Iterable, - n: int, - fillvalue: Optional[Union[None, str]] = None): +def grouper(iterable: Iterable, n: int, fillvalue: Optional[Union[None, str]] = None): """Collect data into fixed-length chunks or blocks""" # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n diff --git a/src/openfermion/contrib/representability/_namedtensor_test.py b/src/openfermion/contrib/representability/_namedtensor_test.py index 009bd5e14..0e5947962 100644 --- a/src/openfermion/contrib/representability/_namedtensor_test.py +++ b/src/openfermion/contrib/representability/_namedtensor_test.py @@ -1,8 +1,11 @@ from itertools import product import numpy as np import pytest -from openfermion.contrib.representability._bijections import Bijection, \ - index_index_basis, index_tuple_basis +from openfermion.contrib.representability._bijections import ( + Bijection, + index_index_basis, + index_tuple_basis, +) from openfermion.contrib.representability._namedtensor import Tensor @@ -18,9 +21,7 @@ def test_namedtensor_initialization(): assert np.allclose(td.data, data) with pytest.raises(TypeError): - _ = Tensor(name='opdm', - tensor=data, - basis=dict(zip(range(4), range(4)))) + _ = Tensor(name='opdm', tensor=data, basis=dict(zip(range(4), range(4)))) td = Tensor(name='opdm', tensor=data) assert isinstance(td.basis, Bijection) @@ -66,14 +67,12 @@ def test_namedtensor_call(): assert test_tensor(0, 1, 0, 1) == rand_mat[0, 0] assert test_tensor(0, 1, 0, 1) == rand_mat[0, 0] assert test_tensor(1, 2, 0, 1) == rand_mat[rev_bas[(1, 2)], rev_bas[(0, 1)]] - assert test_tensor.index_vectorized(1, 2, 0, 1) == rev_bas[(1, 2)] * dim + \ - rev_bas[(0, 1)] + assert test_tensor.index_vectorized(1, 2, 0, 1) == rev_bas[(1, 2)] * dim + rev_bas[(0, 1)] # testing iteration over the upper triangle for iter_vals in test_tensor.utri_iterator(): val, [i, j] = iter_vals - assert val == rand_mat[test_tensor.basis.rev(i), - test_tensor.basis.rev(j)] + assert val == rand_mat[test_tensor.basis.rev(i), test_tensor.basis.rev(j)] def test_tensor_index(): diff --git a/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints.py b/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints.py index 0165bd579..add6fd4b0 100644 --- a/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints.py +++ b/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints.py @@ -1,7 +1,6 @@ from typing import List, Optional, Union from itertools import product -from openfermion.contrib.representability._dualbasis import \ - DualBasisElement, DualBasis +from openfermion.contrib.representability._dualbasis import DualBasisElement, DualBasis from openfermion.utils.rdm_mapping_functions import kronecker_delta @@ -20,11 +19,13 @@ def tpdm_trace_constraint(dim: int, normalization: float) -> DualBasisElement: tensor_names = ['cckk'] * (dim**2) tensor_coeffs = [1.0] * (dim**2) bias = 0 - return DualBasisElement(tensor_names=tensor_names, - tensor_elements=tensor_elements, - tensor_coeffs=tensor_coeffs, - bias=bias, - scalar=normalization) + return DualBasisElement( + tensor_names=tensor_names, + tensor_elements=tensor_elements, + tensor_coeffs=tensor_coeffs, + bias=bias, + scalar=normalization, + ) def tpdm_antisymmetry_constraint(dim: int) -> DualBasis: @@ -40,14 +41,11 @@ def tpdm_antisymmetry_constraint(dim: int) -> DualBasis: for p, q, r, s in product(range(dim), repeat=4): if p * dim + q <= r * dim + s: if p < q and r < s: - tensor_elements = [ - tuple(indices) for indices in _coord_generator(p, q, r, s) - ] + tensor_elements = [tuple(indices) for indices in _coord_generator(p, q, r, s)] tensor_names = ['cckk'] * len(tensor_elements) tensor_coeffs = [0.5] * len(tensor_elements) dbe = DualBasisElement() - for n, e, c in zip(tensor_names, tensor_elements, - tensor_coeffs): + for n, e, c in zip(tensor_names, tensor_elements, tensor_coeffs): dbe.add_element(n, e, c) # dual_basis += dbe @@ -56,8 +54,7 @@ def tpdm_antisymmetry_constraint(dim: int) -> DualBasis: return DualBasis(elements=dbe_list) -def tpdm_to_opdm_mapping(dim: int, - normalization: Union[float, int]) -> DualBasis: +def tpdm_to_opdm_mapping(dim: int, normalization: Union[float, int]) -> DualBasis: """ Construct the DualBasis for mapping of the tpdm to the opdm @@ -190,8 +187,7 @@ def tpdm_to_thdm_mapping(dim: int) -> DualBasis: """ dbe_list = [] - def d2q2element(p: int, q: int, r: int, s: int, factor: Union[float, int])\ - -> DualBasisElement: + def d2q2element(p: int, q: int, r: int, s: int, factor: Union[float, int]) -> DualBasisElement: """ Build the dual basis element for symmetric form of 2-marginal @@ -210,13 +206,14 @@ def d2q2element(p: int, q: int, r: int, s: int, factor: Union[float, int])\ if p == r: dbe.add_element('ck', (q, s), factor) if q == r: - dbe.add_element('ck', (p, s), -1. * factor) + dbe.add_element('ck', (p, s), -1.0 * factor) if p == s: - dbe.add_element('ck', (q, r), -1. * factor) + dbe.add_element('ck', (q, r), -1.0 * factor) dbe.dual_scalar = ( - kronecker_delta(q, s) * kronecker_delta(p, r) - - kronecker_delta(q, r) * kronecker_delta(p, s)) * factor + kronecker_delta(q, s) * kronecker_delta(p, r) + - kronecker_delta(q, r) * kronecker_delta(p, s) + ) * factor return dbe for i, j, k, l in product(range(dim), repeat=4): @@ -239,11 +236,9 @@ def tpdm_to_phdm_mapping(dim: int) -> DualBasis: """ dbe_list = [] - def g2d2map(p: int, - q: int, - r: int, - s: int, - factor: Optional[Union[float, int]] = 1) -> DualBasisElement: + def g2d2map( + p: int, q: int, r: int, s: int, factor: Optional[Union[float, int]] = 1 + ) -> DualBasisElement: """ Build the dual basis element for a symmetric 2-marginal @@ -256,7 +251,7 @@ def g2d2map(p: int, """ dbe = DualBasisElement() if q == s: - dbe.add_element('ck', (p, r), -1. * factor) + dbe.add_element('ck', (p, r), -1.0 * factor) dbe.add_element('ckck', (p, s, r, q), 1.0 * factor) dbe.add_element('cckk', (p, q, r, s), 1.0 * factor) dbe.dual_scalar = 0 @@ -272,12 +267,13 @@ def g2d2map(p: int, return DualBasis(elements=dbe_list) -def spin_orbital_linear_constraints(dim: int, - num_alpha: Union[int, float], - num_beta: Union[int, float], - constraint_list: List[str], - sz: Optional[Union[None, float, int]] = None - ) -> DualBasis: +def spin_orbital_linear_constraints( + dim: int, + num_alpha: Union[int, float], + num_beta: Union[int, float], + constraint_list: List[str], + sz: Optional[Union[None, float, int]] = None, +) -> DualBasis: """ Construct dual basis constraints for 2-positivity in a spin-orbital basis @@ -346,7 +342,15 @@ def _coord_generator(i, j, k, l): l, k, i, j l, k, j, i """ - unique_set = {(i, j, k, l), (j, i, k, l), (i, j, l, k), (j, i, l, k), - (k, l, i, j), (k, l, j, i), (l, k, i, j), (l, k, j, i)} + unique_set = { + (i, j, k, l), + (j, i, k, l), + (i, j, l, k), + (j, i, l, k), + (k, l, i, j), + (k, l, j, i), + (l, k, i, j), + (l, k, j, i), + } for index_element in unique_set: yield index_element diff --git a/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints_test.py b/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints_test.py index d728e9e6d..520562b0b 100644 --- a/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints_test.py +++ b/src/openfermion/contrib/representability/constraints/spin_orbital_2pos_constraints_test.py @@ -2,40 +2,60 @@ import os import numpy as np from openfermion.contrib.representability.constraints.spin_orbital_2pos_constraints import ( # pylint: disable=line-too-long - tpdm_antisymmetry_constraint, tpdm_trace_constraint, _coord_generator, - tpdm_to_opdm_mapping, opdm_to_ohdm_mapping, sz_constraint, na_constraint, - nb_constraint, tpdm_to_thdm_mapping, tpdm_to_phdm_mapping, - spin_orbital_linear_constraints) -from openfermion.contrib.representability._dualbasis import \ - DualBasisElement, DualBasis + tpdm_antisymmetry_constraint, + tpdm_trace_constraint, + _coord_generator, + tpdm_to_opdm_mapping, + opdm_to_ohdm_mapping, + sz_constraint, + na_constraint, + nb_constraint, + tpdm_to_thdm_mapping, + tpdm_to_phdm_mapping, + spin_orbital_linear_constraints, +) +from openfermion.contrib.representability._dualbasis import DualBasisElement, DualBasis from openfermion.contrib.representability._namedtensor import Tensor from openfermion.contrib.representability._multitensor import MultiTensor from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData -from openfermion.utils import map_two_pdm_to_two_hole_dm, \ - map_two_pdm_to_particle_hole_dm +from openfermion.utils import map_two_pdm_to_two_hole_dm, map_two_pdm_to_particle_hole_dm def test_trace_constraint(): dbe = tpdm_trace_constraint(4, 10) assert dbe.primal_tensors_names == ['cckk'] * 4**2 - assert dbe.primal_elements == [ - (i, j, i, j) for i, j in product(range(4), repeat=2) - ] + assert dbe.primal_elements == [(i, j, i, j) for i, j in product(range(4), repeat=2)] assert np.isclose(dbe.constant_bias, 0) assert np.isclose(dbe.dual_scalar, 10) def test_coord_generator(): i, j, k, l = 0, 1, 2, 3 - true_set = {(i, j, k, l), (j, i, k, l), (i, j, l, k), (j, i, l, k), - (k, l, i, j), (k, l, j, i), (l, k, i, j), (l, k, j, i)} + true_set = { + (i, j, k, l), + (j, i, k, l), + (i, j, l, k), + (j, i, l, k), + (k, l, i, j), + (k, l, j, i), + (l, k, i, j), + (l, k, j, i), + } assert true_set == set(_coord_generator(i, j, k, l)) i, j, k, l = 1, 1, 2, 3 - true_set = {(i, j, k, l), (j, i, k, l), (i, j, l, k), (j, i, l, k), - (k, l, i, j), (k, l, j, i), (l, k, i, j), (l, k, j, i)} + true_set = { + (i, j, k, l), + (j, i, k, l), + (i, j, l, k), + (j, i, l, k), + (k, l, i, j), + (k, l, j, i), + (l, k, i, j), + (l, k, j, i), + } assert true_set == set(_coord_generator(i, j, k, l)) @@ -43,8 +63,7 @@ def test_d2_antisymm(): db = tpdm_antisymmetry_constraint(4) for dbe in db: i, j, k, l = dbe.primal_elements[0] - assert len(list(_coord_generator(i, j, k, - l))) == len(dbe.primal_elements) + assert len(list(_coord_generator(i, j, k, l))) == len(dbe.primal_elements) assert np.isclose(dbe.constant_bias, 0) assert np.isclose(dbe.dual_scalar, 0) assert np.unique(dbe.primal_coeffs) @@ -69,10 +88,9 @@ def test_tpdm_opdm_mapping(): assert dbe.primal_coeffs elif len(element) == 2: gem_idx = [len(x) for x in dbe.primal_elements].index(4) - assert sorted(element) == sorted([ - dbe.primal_elements[gem_idx][0], - dbe.primal_elements[gem_idx][2] - ]) + assert sorted(element) == sorted( + [dbe.primal_elements[gem_idx][0], dbe.primal_elements[gem_idx][2]] + ) def test_opdm_to_ohdm_mapping(): @@ -159,9 +177,6 @@ def test_d2_to_g2(): def test_spin_orbital_dual_basis_construction(): db = spin_orbital_linear_constraints( - dim=6, - num_alpha=3, - num_beta=3, - constraint_list=['ck', 'kc', 'cckk', 'kkcc', 'ckck'], - sz=0) + dim=6, num_alpha=3, num_beta=3, constraint_list=['ck', 'kc', 'cckk', 'kkcc', 'ckck'], sz=0 + ) assert isinstance(db, DualBasis) diff --git a/src/openfermion/functionals/__init__.py b/src/openfermion/functionals/__init__.py index ad11c2d4c..d7fd9bd1a 100644 --- a/src/openfermion/functionals/__init__.py +++ b/src/openfermion/functionals/__init__.py @@ -12,5 +12,9 @@ from .contextuality import is_contextual -from .get_one_norm import (get_one_norm_mol, get_one_norm_mol_woconst, - get_one_norm_int, get_one_norm_int_woconst) +from .get_one_norm import ( + get_one_norm_mol, + get_one_norm_mol_woconst, + get_one_norm_int, + get_one_norm_int_woconst, +) diff --git a/src/openfermion/functionals/contextuality.py b/src/openfermion/functionals/contextuality.py index 720edc646..229eaaf11 100644 --- a/src/openfermion/functionals/contextuality.py +++ b/src/openfermion/functionals/contextuality.py @@ -19,8 +19,7 @@ def _commutes(operator1: QubitOperator, operator2: QubitOperator) -> bool: return operator1 * operator2 == operator2 * operator1 -def _non_fully_commuting_terms(hamiltonian: QubitOperator - ) -> List[QubitOperator]: +def _non_fully_commuting_terms(hamiltonian: QubitOperator) -> List[QubitOperator]: terms = list([QubitOperator(key) for key in hamiltonian.terms.keys()]) T = [] # will contain the subset of terms that do not # commute universally in terms @@ -51,8 +50,12 @@ def is_contextual(hamiltonian: QubitOperator) -> bool: # commutes with both others. for j in range(len(T)): for k in range(j + 1, len(T)): # Ordering of j, k does not matter. - if i!=j and i!=k and _commutes(T[i],T[j]) \ - and _commutes(T[i],T[k]) and \ - not _commutes(T[j],T[k]): + if ( + i != j + and i != k + and _commutes(T[i], T[j]) + and _commutes(T[i], T[k]) + and not _commutes(T[j], T[k]) + ): return True return False diff --git a/src/openfermion/functionals/contextuality_test.py b/src/openfermion/functionals/contextuality_test.py index 207e5ab58..863beaad9 100644 --- a/src/openfermion/functionals/contextuality_test.py +++ b/src/openfermion/functionals/contextuality_test.py @@ -17,16 +17,15 @@ class IsContextualTest(unittest.TestCase): - def setUp(self): - self.x1 = QubitOperator('X1', 1.) - self.x2 = QubitOperator('X2', 1.) - self.x3 = QubitOperator('X3', 1.) - self.x4 = QubitOperator('X4', 1.) - self.z1 = QubitOperator('Z1', 1.) - self.z2 = QubitOperator('Z2', 1.) - self.x1x2 = QubitOperator('X1 X2', 1.) - self.y1y2 = QubitOperator('Y1 Y2', 1.) + self.x1 = QubitOperator('X1', 1.0) + self.x2 = QubitOperator('X2', 1.0) + self.x3 = QubitOperator('X3', 1.0) + self.x4 = QubitOperator('X4', 1.0) + self.z1 = QubitOperator('Z1', 1.0) + self.z2 = QubitOperator('Z2', 1.0) + self.x1x2 = QubitOperator('X1 X2', 1.0) + self.y1y2 = QubitOperator('Y1 Y2', 1.0) def test_raises_exception(self): with self.assertRaises(TypeError): @@ -46,15 +45,9 @@ def test_contextual_two_qubit_hamiltonians(self): self.assertTrue(is_contextual(self.x1 + self.y1y2 + self.z1 + self.z2)) def test_contextual_hamiltonians_with_extra_terms(self): - self.assertTrue( - is_contextual(self.x1 + self.x2 + self.z1 + self.z2 + self.x3 + - self.x4)) - self.assertTrue( - is_contextual(self.x1 + self.x1x2 + self.z1 + self.z2 + self.x3 + - self.x4)) - self.assertTrue( - is_contextual(self.x1 + self.y1y2 + self.z1 + self.z2 + self.x3 + - self.x4)) + self.assertTrue(is_contextual(self.x1 + self.x2 + self.z1 + self.z2 + self.x3 + self.x4)) + self.assertTrue(is_contextual(self.x1 + self.x1x2 + self.z1 + self.z2 + self.x3 + self.x4)) + self.assertTrue(is_contextual(self.x1 + self.y1y2 + self.z1 + self.z2 + self.x3 + self.x4)) def test_commuting_hamiltonian(self): - self.assertFalse(is_contextual(self.x1 + self.x2 + self.x3 + self.x4)) \ No newline at end of file + self.assertFalse(is_contextual(self.x1 + self.x2 + self.x3 + self.x4)) diff --git a/src/openfermion/functionals/get_one_norm.py b/src/openfermion/functionals/get_one_norm.py index 7eadef57d..771823d9b 100755 --- a/src/openfermion/functionals/get_one_norm.py +++ b/src/openfermion/functionals/get_one_norm.py @@ -34,9 +34,9 @@ def get_one_norm_mol(molecule: MolecularData): ------- one_norm : 1-Norm of the qubit Hamiltonian """ - return get_one_norm_int(molecule.nuclear_repulsion, - molecule.one_body_integrals, - molecule.two_body_integrals) + return get_one_norm_int( + molecule.nuclear_repulsion, molecule.one_body_integrals, molecule.two_body_integrals + ) def get_one_norm_mol_woconst(molecule: MolecularData): @@ -53,12 +53,12 @@ def get_one_norm_mol_woconst(molecule: MolecularData): ------- one_norm : 1-Norm of the qubit Hamiltonian """ - return get_one_norm_int_woconst(molecule.one_body_integrals, - molecule.two_body_integrals) + return get_one_norm_int_woconst(molecule.one_body_integrals, molecule.two_body_integrals) -def get_one_norm_int(constant: float, one_body_integrals: np.ndarray, - two_body_integrals: np.ndarray): +def get_one_norm_int( + constant: float, one_body_integrals: np.ndarray, two_body_integrals: np.ndarray +): """ Returns the 1-Norm of a RHF or ROHF Hamiltonian described in https://arxiv.org/abs/2103.14753 after a fermion-to-qubit @@ -84,21 +84,22 @@ def get_one_norm_int(constant: float, one_body_integrals: np.ndarray, for p in range(n_orb): htilde += one_body_integrals[p, p] for q in range(n_orb): - htilde += ((1 / 2 * two_body_integrals[p, q, q, p]) - - (1 / 4 * two_body_integrals[p, q, p, q])) + htilde += (1 / 2 * two_body_integrals[p, q, q, p]) - ( + 1 / 4 * two_body_integrals[p, q, p, q] + ) htildepq = np.zeros(one_body_integrals.shape) for p in range(n_orb): for q in range(n_orb): htildepq[p, q] = one_body_integrals[p, q] for r in range(n_orb): - htildepq[p, q] += ((two_body_integrals[p, r, r, q]) - - (1 / 2 * two_body_integrals[p, r, q, r])) + htildepq[p, q] += (two_body_integrals[p, r, r, q]) - ( + 1 / 2 * two_body_integrals[p, r, q, r] + ) one_norm = abs(htilde) + np.sum(np.absolute(htildepq)) - anti_sym_integrals = two_body_integrals - np.transpose( - two_body_integrals, (0, 1, 3, 2)) + anti_sym_integrals = two_body_integrals - np.transpose(two_body_integrals, (0, 1, 3, 2)) one_norm += 1 / 8 * np.sum(np.absolute(anti_sym_integrals)) one_norm += 1 / 4 * np.sum(np.absolute(two_body_integrals)) @@ -106,8 +107,7 @@ def get_one_norm_int(constant: float, one_body_integrals: np.ndarray, return one_norm -def get_one_norm_int_woconst(one_body_integrals: np.ndarray, - two_body_integrals: np.ndarray): +def get_one_norm_int_woconst(one_body_integrals: np.ndarray, two_body_integrals: np.ndarray): """ Returns 1-norm, emitting the constant term in the qubit Hamiltonian. See get_one_norm_int. @@ -130,13 +130,13 @@ def get_one_norm_int_woconst(one_body_integrals: np.ndarray, for q in range(n_orb): htildepq[p, q] = one_body_integrals[p, q] for r in range(n_orb): - htildepq[p, q] += ((two_body_integrals[p, r, r, q]) - - (1 / 2 * two_body_integrals[p, r, q, r])) + htildepq[p, q] += (two_body_integrals[p, r, r, q]) - ( + 1 / 2 * two_body_integrals[p, r, q, r] + ) one_norm = np.sum(np.absolute(htildepq)) - anti_sym_integrals = two_body_integrals - np.transpose( - two_body_integrals, (0, 1, 3, 2)) + anti_sym_integrals = two_body_integrals - np.transpose(two_body_integrals, (0, 1, 3, 2)) one_norm += 1 / 8 * np.sum(np.absolute(anti_sym_integrals)) one_norm += 1 / 4 * np.sum(np.absolute(two_body_integrals)) diff --git a/src/openfermion/functionals/get_one_norm_test.py b/src/openfermion/functionals/get_one_norm_test.py index a6727cbb8..349bc8fd6 100644 --- a/src/openfermion/functionals/get_one_norm_test.py +++ b/src/openfermion/functionals/get_one_norm_test.py @@ -13,9 +13,14 @@ import os import pytest -from openfermion import (get_one_norm_mol, get_one_norm_mol_woconst, - get_one_norm_int, get_one_norm_int_woconst, - MolecularData, jordan_wigner) +from openfermion import ( + get_one_norm_mol, + get_one_norm_mol_woconst, + get_one_norm_int, + get_one_norm_int_woconst, + MolecularData, + jordan_wigner, +) from openfermion.config import DATA_DIRECTORY filename = os.path.join(DATA_DIRECTORY, "H1-Li1_sto-3g_singlet_1.45.hdf5") @@ -25,23 +30,20 @@ def test_one_norm_from_molecule(): - assert qubit_hamiltonian.induced_norm() == pytest.approx( - get_one_norm_mol(molecule)) + assert qubit_hamiltonian.induced_norm() == pytest.approx(get_one_norm_mol(molecule)) def test_one_norm_from_ints(): assert qubit_hamiltonian.induced_norm() == pytest.approx( get_one_norm_int( - molecule.nuclear_repulsion, - molecule.one_body_integrals, - molecule.two_body_integrals, - )) + molecule.nuclear_repulsion, molecule.one_body_integrals, molecule.two_body_integrals + ) + ) def test_one_norm_woconst(): - one_norm_woconst = (qubit_hamiltonian.induced_norm() - - abs(qubit_hamiltonian.constant)) + one_norm_woconst = qubit_hamiltonian.induced_norm() - abs(qubit_hamiltonian.constant) assert one_norm_woconst == pytest.approx(get_one_norm_mol_woconst(molecule)) assert one_norm_woconst == pytest.approx( - get_one_norm_int_woconst(molecule.one_body_integrals, - molecule.two_body_integrals)) + get_one_norm_int_woconst(molecule.one_body_integrals, molecule.two_body_integrals) + ) diff --git a/src/openfermion/hamiltonians/__init__.py b/src/openfermion/hamiltonians/__init__.py index c877deaf9..5ad591c44 100644 --- a/src/openfermion/hamiltonians/__init__.py +++ b/src/openfermion/hamiltonians/__init__.py @@ -27,10 +27,7 @@ generate_hamiltonian, ) -from .hubbard import ( - bose_hubbard, - fermi_hubbard, -) +from .hubbard import bose_hubbard, fermi_hubbard from .jellium import ( dual_basis_kinetic, @@ -44,10 +41,7 @@ wigner_seitz_length_scale, ) -from .jellium_hf_state import ( - hartree_fock_state_jellium, - lowest_single_particle_energy_states, -) +from .jellium_hf_state import hartree_fock_state_jellium, lowest_single_particle_energy_states from .mean_field_dwave import mean_field_dwave diff --git a/src/openfermion/hamiltonians/general_hubbard.py b/src/openfermion/hamiltonians/general_hubbard.py index d4f9b1847..37d195231 100644 --- a/src/openfermion/hamiltonians/general_hubbard.py +++ b/src/openfermion/hamiltonians/general_hubbard.py @@ -15,34 +15,35 @@ from collections import namedtuple from openfermion.ops.operators import FermionOperator -from openfermion.utils import (SpinPairs, Spin) +from openfermion.utils import SpinPairs, Spin -TunnelingParameter = namedtuple('TunnelingParameter', - ('edge_type', 'dofs', 'coefficient')) +TunnelingParameter = namedtuple('TunnelingParameter', ('edge_type', 'dofs', 'coefficient')) InteractionParameter = namedtuple( - 'InteractionParameter', ('edge_type', 'dofs', 'coefficient', 'spin_pairs')) + 'InteractionParameter', ('edge_type', 'dofs', 'coefficient', 'spin_pairs') +) PotentialParameter = namedtuple('PotentialParameter', ('dof', 'coefficient')) -def number_operator(i, coefficient=1., particle_hole_symmetry=False): +def number_operator(i, coefficient=1.0, particle_hole_symmetry=False): op = FermionOperator(((i, 1), (i, 0)), coefficient) if particle_hole_symmetry: op -= FermionOperator((), 0.5) return op -def interaction_operator(i, j, coefficient=1., particle_hole_symmetry=False): - return (number_operator( - i, coefficient, particle_hole_symmetry=particle_hole_symmetry) * - number_operator(j, particle_hole_symmetry=particle_hole_symmetry)) +def interaction_operator(i, j, coefficient=1.0, particle_hole_symmetry=False): + return number_operator( + i, coefficient, particle_hole_symmetry=particle_hole_symmetry + ) * number_operator(j, particle_hole_symmetry=particle_hole_symmetry) -def tunneling_operator(i, j, coefficient=1.): - return (FermionOperator(((i, 1), (j, 0)), coefficient) + FermionOperator( - ((j, 1), (i, 0)), coefficient.conjugate())) +def tunneling_operator(i, j, coefficient=1.0): + return FermionOperator(((i, 1), (j, 0)), coefficient) + FermionOperator( + ((j, 1), (i, 0)), coefficient.conjugate() + ) -def number_difference_operator(i, j, coefficient=1.): +def number_difference_operator(i, j, coefficient=1.0): return number_operator(i, coefficient) - number_operator(j, coefficient) @@ -160,13 +161,15 @@ class FermiHubbardModel: \end{align} """ - def __init__(self, - lattice, - tunneling_parameters=None, - interaction_parameters=None, - potential_parameters=None, - magnetic_field=0., - particle_hole_symmetry=False): + def __init__( + self, + lattice, + tunneling_parameters=None, + interaction_parameters=None, + potential_parameters=None, + magnetic_field=0.0, + particle_hole_symmetry=False, + ): r"""A Hubbard model defined on a lattice. Args: @@ -258,12 +261,9 @@ def __init__(self, self.lattice = lattice - self.tunneling_parameters = self.parse_tunneling_parameters( - tunneling_parameters) - self.interaction_parameters = self.parse_interaction_parameters( - interaction_parameters) - self.potential_parameters = self.parse_potential_parameters( - potential_parameters) + self.tunneling_parameters = self.parse_tunneling_parameters(tunneling_parameters) + self.interaction_parameters = self.parse_interaction_parameters(interaction_parameters) + self.potential_parameters = self.parse_potential_parameters(potential_parameters) self.magnetic_field = magnetic_field self.particle_hole_symmetry = particle_hole_symmetry @@ -275,10 +275,13 @@ def parse_tunneling_parameters(self, parameters): parameter = TunnelingParameter(*parameter) self.lattice.validate_edge_type(parameter.edge_type) self.lattice.validate_dofs(parameter.dofs, 2) - if ((parameter.edge_type in self.lattice.onsite_edge_types) and - (len(set(parameter.dofs)) == 1)): - raise ValueError('Invalid onsite tunneling parameter between ' - 'same dof {}.'.format(parameter.dofs)) + if (parameter.edge_type in self.lattice.onsite_edge_types) and ( + len(set(parameter.dofs)) == 1 + ): + raise ValueError( + 'Invalid onsite tunneling parameter between ' + 'same dof {}.'.format(parameter.dofs) + ) parsed_parameters.append(parameter) return parsed_parameters @@ -289,18 +292,19 @@ def parse_interaction_parameters(self, parameters): for parameter in parameters: if len(parameter) not in (3, 4): raise ValueError('len(parameter) not in (3, 4)') - spin_pairs = (SpinPairs.ALL - if len(parameter) < 4 else parameter[-1]) - parameter = InteractionParameter(*parameter[:3], - spin_pairs=spin_pairs) + spin_pairs = SpinPairs.ALL if len(parameter) < 4 else parameter[-1] + parameter = InteractionParameter(*parameter[:3], spin_pairs=spin_pairs) self.lattice.validate_edge_type(parameter.edge_type) self.lattice.validate_dofs(parameter.dofs, 2) - if ((len(set(parameter.dofs)) == 1) and - (parameter.edge_type in self.lattice.onsite_edge_types) and - (parameter.spin_pairs == SpinPairs.SAME)): + if ( + (len(set(parameter.dofs)) == 1) + and (parameter.edge_type in self.lattice.onsite_edge_types) + and (parameter.spin_pairs == SpinPairs.SAME) + ): raise ValueError( - 'Parameter {} specifies '.format(parameter) + - 'invalid interaction between spin orbital and itself.') + 'Parameter {} specifies '.format(parameter) + + 'invalid interaction between spin orbital and itself.' + ) parsed_parameters.append(parameter) return parsed_parameters @@ -333,15 +337,14 @@ def interaction_terms(self): for r, rr in self.lattice.site_pairs_iter(param.edge_type, a != aa): same_spatial_orbital = (a, r) == (aa, rr) for s, ss in self.lattice.spin_pairs_iter( - SpinPairs.DIFF if same_spatial_orbital else - param.spin_pairs, not same_spatial_orbital): + SpinPairs.DIFF if same_spatial_orbital else param.spin_pairs, + not same_spatial_orbital, + ): i = self.lattice.to_spin_orbital_index(r, a, s) j = self.lattice.to_spin_orbital_index(rr, aa, ss) terms += interaction_operator( - i, - j, - param.coefficient, - particle_hole_symmetry=self.particle_hole_symmetry) + i, j, param.coefficient, particle_hole_symmetry=self.particle_hole_symmetry + ) return terms def potential_terms(self): @@ -349,12 +352,10 @@ def potential_terms(self): for param in self.potential_parameters: for site_index in self.lattice.site_indices: for spin_index in self.lattice.spin_indices: - i = self.lattice.to_spin_orbital_index( - site_index, param.dof, spin_index) + i = self.lattice.to_spin_orbital_index(site_index, param.dof, spin_index) terms += number_operator( - i, - -param.coefficient, - particle_hole_symmetry=self.particle_hole_symmetry) + i, -param.coefficient, particle_hole_symmetry=self.particle_hole_symmetry + ) return terms def field_terms(self): @@ -364,11 +365,14 @@ def field_terms(self): for site_index in self.lattice.site_indices: for dof in self.lattice.dof_indices: i = self.lattice.to_spin_orbital_index(site_index, dof, Spin.UP) - j = self.lattice.to_spin_orbital_index(site_index, dof, - Spin.DOWN) + j = self.lattice.to_spin_orbital_index(site_index, dof, Spin.DOWN) terms += number_difference_operator(i, j, -self.magnetic_field) return terms def hamiltonian(self): - return (self.tunneling_terms() + self.interaction_terms() + - self.potential_terms() + self.field_terms()) + return ( + self.tunneling_terms() + + self.interaction_terms() + + self.potential_terms() + + self.field_terms() + ) diff --git a/src/openfermion/hamiltonians/general_hubbard_test.py b/src/openfermion/hamiltonians/general_hubbard_test.py index 6a51b4e77..69e42dee0 100644 --- a/src/openfermion/hamiltonians/general_hubbard_test.py +++ b/src/openfermion/hamiltonians/general_hubbard_test.py @@ -18,52 +18,67 @@ from openfermion.hamiltonians import fermi_hubbard, FermiHubbardModel from openfermion.utils import HubbardSquareLattice, SpinPairs -from openfermion.hamiltonians.general_hubbard import (InteractionParameter, - number_operator) +from openfermion.hamiltonians.general_hubbard import InteractionParameter, number_operator from openfermion.ops import FermionOperator -def fermi_hubbard_from_general(x_dimension, - y_dimension, - tunneling, - coulomb, - chemical_potential=0., - periodic=True, - spinless=False, - magnetic_field=0): - lattice = HubbardSquareLattice(x_dimension, - y_dimension, - periodic=periodic, - spinless=spinless) +def fermi_hubbard_from_general( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential=0.0, + periodic=True, + spinless=False, + magnetic_field=0, +): + lattice = HubbardSquareLattice(x_dimension, y_dimension, periodic=periodic, spinless=spinless) interaction_edge_type = 'neighbor' if spinless else 'onsite' - model = FermiHubbardModel(lattice, - tunneling_parameters=(('neighbor', (0, 0), - tunneling),), - interaction_parameters=((interaction_edge_type, - (0, 0), coulomb),), - potential_parameters=((0, chemical_potential),), - magnetic_field=magnetic_field) + model = FermiHubbardModel( + lattice, + tunneling_parameters=(('neighbor', (0, 0), tunneling),), + interaction_parameters=((interaction_edge_type, (0, 0), coulomb),), + potential_parameters=((0, chemical_potential),), + magnetic_field=magnetic_field, + ) return model.hamiltonian() @pytest.mark.parametrize( - 'x_dimension,y_dimension,tunneling,coulomb,' + - 'chemical_potential,spinless,periodic,magnetic_field', - itertools.product(range(1, 4), range(1, 4), (random.uniform(0, 2.),), - (random.uniform(0, 2.),), (random.uniform(0, 2.),), - (True, False), (True, False), (random.uniform(-1, 1),))) + 'x_dimension,y_dimension,tunneling,coulomb,' + + 'chemical_potential,spinless,periodic,magnetic_field', + itertools.product( + range(1, 4), + range(1, 4), + (random.uniform(0, 2.0),), + (random.uniform(0, 2.0),), + (random.uniform(0, 2.0),), + (True, False), + (True, False), + (random.uniform(-1, 1),), + ), +) def test_fermi_hubbard_square_special_general_equivalence( - x_dimension, y_dimension, tunneling, coulomb, chemical_potential, - spinless, periodic, magnetic_field): - hubbard_model_special = fermi_hubbard(x_dimension, - y_dimension, - tunneling, - coulomb, - chemical_potential=chemical_potential, - spinless=spinless, - periodic=periodic, - magnetic_field=magnetic_field) + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential, + spinless, + periodic, + magnetic_field, +): + hubbard_model_special = fermi_hubbard( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential=chemical_potential, + spinless=spinless, + periodic=periodic, + magnetic_field=magnetic_field, + ) hubbard_model_general = fermi_hubbard_from_general( x_dimension, y_dimension, @@ -72,15 +87,18 @@ def test_fermi_hubbard_square_special_general_equivalence( chemical_potential=chemical_potential, spinless=spinless, periodic=periodic, - magnetic_field=magnetic_field) + magnetic_field=magnetic_field, + ) assert hubbard_model_special == hubbard_model_general def random_parameters(lattice, probability=0.5, distinguish_edges=False): parameters = {} - edge_types = (('onsite', 'horizontal_neighbor', - 'vertical_neighbor') if distinguish_edges else - ('onsite', 'neighbor')) + edge_types = ( + ('onsite', 'horizontal_neighbor', 'vertical_neighbor') + if distinguish_edges + else ('onsite', 'neighbor') + ) parameters['tunneling_parameters'] = [ (edge_type, dofs, random.uniform(-1, 1)) @@ -89,19 +107,22 @@ def random_parameters(lattice, probability=0.5, distinguish_edges=False): if random.random() <= probability ] - possible_spin_pairs = (SpinPairs.ALL,) if lattice.spinless else ( - SpinPairs.SAME, SpinPairs.DIFF) + possible_spin_pairs = (SpinPairs.ALL,) if lattice.spinless else (SpinPairs.SAME, SpinPairs.DIFF) parameters['interaction_parameters'] = [ (edge_type, dofs, random.uniform(-1, 1), spin_pairs) - for edge_type in edge_types for spin_pairs in possible_spin_pairs - for dofs in lattice.dof_pairs_iter(edge_type == 'onsite' and spin_pairs - in (SpinPairs.ALL, SpinPairs.SAME)) + for edge_type in edge_types + for spin_pairs in possible_spin_pairs + for dofs in lattice.dof_pairs_iter( + edge_type == 'onsite' and spin_pairs in (SpinPairs.ALL, SpinPairs.SAME) + ) if random.random() <= probability ] - parameters['potential_parameters'] = [(dof, random.uniform(-1, 1)) - for dof in lattice.dof_indices - if random.random() <= probability] + parameters['potential_parameters'] = [ + (dof, random.uniform(-1, 1)) + for dof in lattice.dof_indices + if random.random() <= probability + ] if random.random() <= probability: parameters['magnetic_field'] = random.uniform(-1, 1) @@ -130,44 +151,45 @@ def test_fermi_hubbard_bad_parameters(): FermiHubbardModel(lattice, interaction_parameters=[(0,) * 5]) with pytest.raises(ValueError): interaction_parameters = [('onsite', (0, 0), 1, SpinPairs.SAME)] - FermiHubbardModel(lattice, - interaction_parameters=interaction_parameters) + FermiHubbardModel(lattice, interaction_parameters=interaction_parameters) lattices = [ - HubbardSquareLattice(random.randrange(3, 10), - random.randrange(3, 10), - periodic=periodic, - spinless=spinless) for periodic in (False, True) - for spinless in (False, True) for _ in range(2) + HubbardSquareLattice( + random.randrange(3, 10), random.randrange(3, 10), periodic=periodic, spinless=spinless + ) + for periodic in (False, True) + for spinless in (False, True) + for _ in range(2) ] @pytest.mark.parametrize( 'lattice,parameters,distinguish_edges', - [(lattice, random_parameters( - lattice, distinguish_edges=distinguish_edges), distinguish_edges) - for lattice in lattices for _ in range(2) - for distinguish_edges in (True, False)]) -def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, - distinguish_edges): + [ + ( + lattice, + random_parameters(lattice, distinguish_edges=distinguish_edges), + distinguish_edges, + ) + for lattice in lattices + for _ in range(2) + for distinguish_edges in (True, False) + ], +) +def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, distinguish_edges): model = FermiHubbardModel(lattice, **parameters) hamiltonian = model.hamiltonian() terms_per_parameter = defaultdict(int) for term, coefficient in hamiltonian.terms.items(): spin_orbitals = set(i for i, _ in term) if len(spin_orbitals) == 2: - (i, a, s), (ii, aa, ss) = ( - lattice.from_spin_orbital_index(i) for i in spin_orbitals) - edge_type = ({ - (0, 0): 'onsite', - (0, 1): 'vertical_neighbor', - (1, 0): 'horizontal_neighbor' - } if distinguish_edges else { - (0, 0): 'onsite', - (0, 1): 'neighbor', - (1, 0): 'neighbor' - })[lattice.delta_mag(i, ii, True)] + (i, a, s), (ii, aa, ss) = (lattice.from_spin_orbital_index(i) for i in spin_orbitals) + edge_type = ( + {(0, 0): 'onsite', (0, 1): 'vertical_neighbor', (1, 0): 'horizontal_neighbor'} + if distinguish_edges + else {(0, 0): 'onsite', (0, 1): 'neighbor', (1, 0): 'neighbor'} + )[lattice.delta_mag(i, ii, True)] dofs = tuple(sorted((a, aa))) if len(term) == 2: parameter = (edge_type, dofs, -coefficient) @@ -175,8 +197,11 @@ def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, terms_per_parameter['tunneling', parameter] += 1 else: assert len(term) == 4 - spin_pairs = (SpinPairs.ALL if lattice.spinless else - (SpinPairs.SAME if s == ss else SpinPairs.DIFF)) + spin_pairs = ( + SpinPairs.ALL + if lattice.spinless + else (SpinPairs.SAME if s == ss else SpinPairs.DIFF) + ) parameter = (edge_type, dofs, coefficient, spin_pairs) assert parameter in parameters['interaction_parameters'] terms_per_parameter['interaction', parameter] += 1 @@ -187,9 +212,8 @@ def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, _, dof, spin_index = lattice.from_spin_orbital_index(spin_orbital) potential_coefficient = -coefficient if not lattice.spinless: - (-1)**spin_index - potential_coefficient -= (((-1)**spin_index) * - parameters.get('magnetic_field', 0)) + (-1) ** spin_index + potential_coefficient -= ((-1) ** spin_index) * parameters.get('magnetic_field', 0) if not potential_coefficient: continue parameter = (dof, potential_coefficient) @@ -199,7 +223,7 @@ def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, 'onsite': lattice.n_sites, 'neighbor': lattice.n_neighbor_pairs(False), 'vertical_neighbor': lattice.n_vertical_neighbor_pairs(False), - 'horizontal_neighbor': lattice.n_horizontal_neighbor_pairs(False) + 'horizontal_neighbor': lattice.n_horizontal_neighbor_pairs(False), } for (term_type, parameter), n_terms in terms_per_parameter.items(): if term_type == 'potential': @@ -216,8 +240,7 @@ def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, expected_n_terms *= len(set(parameter.dofs)) if not lattice.spinless: assert parameter.spin_pairs != SpinPairs.ALL - if (parameter.edge_type == 'onsite' and - parameter.spin_pairs == SpinPairs.DIFF): + if parameter.edge_type == 'onsite' and parameter.spin_pairs == SpinPairs.DIFF: expected_n_terms *= len(set(parameter.dofs)) else: expected_n_terms *= 2 @@ -225,9 +248,9 @@ def test_fermi_hubbard_square_lattice_random_parameters(lattice, parameters, def test_number_op(): - nop = FermionOperator(((0, 1), (0, 0)), coefficient=1.) + nop = FermionOperator(((0, 1), (0, 0)), coefficient=1.0) test_op = number_operator(0) assert test_op == nop test_op = number_operator(0, particle_hole_symmetry=True) - assert test_op == nop - FermionOperator((), coefficient=0.5) \ No newline at end of file + assert test_op == nop - FermionOperator((), coefficient=0.5) diff --git a/src/openfermion/hamiltonians/hartree_fock.py b/src/openfermion/hamiltonians/hartree_fock.py index 4ac0730dc..87840ffe3 100644 --- a/src/openfermion/hamiltonians/hartree_fock.py +++ b/src/openfermion/hamiltonians/hartree_fock.py @@ -9,9 +9,11 @@ import numpy as np import scipy as sp from scipy.optimize import OptimizeResult -from openfermion.ops.representations import (InteractionOperator, - InteractionRDM, - general_basis_change) +from openfermion.ops.representations import ( + InteractionOperator, + InteractionRDM, + general_basis_change, +) import openfermion.linalg as linalg @@ -30,9 +32,7 @@ def get_matrix_of_eigs(w: np.ndarray) -> np.ndarray: if np.isclose(abs(w[i] - w[j]), 0): transform_eigs[i, j] = 1 else: - transform_eigs[i, j] = (np.exp(1j * - (w[i] - w[j])) - 1) / (1j * - (w[i] - w[j])) + transform_eigs[i, j] = (np.exp(1j * (w[i] - w[j])) - 1) / (1j * (w[i] - w[j])) return transform_eigs @@ -40,7 +40,7 @@ class InputError(Exception): pass -class HartreeFockFunctional(): +class HartreeFockFunctional: """ Implementation of the objective function code for Restricted Hartree-Fock @@ -49,15 +49,17 @@ class HartreeFockFunctional(): knowledge of each type. """ - def __init__(self, - *, - one_body_integrals: np.ndarray, - two_body_integrals: np.ndarray, - overlap: np.ndarray, - n_electrons: int, - model='rhf', - nuclear_repulsion: Optional[float] = 0., - initial_orbitals: Optional[Union[None, Callable]] = None): + def __init__( + self, + *, + one_body_integrals: np.ndarray, + two_body_integrals: np.ndarray, + overlap: np.ndarray, + n_electrons: int, + model='rhf', + nuclear_repulsion: Optional[float] = 0.0, + initial_orbitals: Optional[Union[None, Callable]] = None, + ): """ Initialize functional @@ -109,15 +111,13 @@ def __init__(self, _, core_orbs = sp.linalg.eigh(one_body_integrals, b=overlap) molecular_hamiltonian = generate_hamiltonian( - one_body_integrals=general_basis_change(self.obi, core_orbs, - (1, 0)), - two_body_integrals=general_basis_change(self.tbi, core_orbs, - (1, 1, 0, 0)), - constant=self.constant_offset) + one_body_integrals=general_basis_change(self.obi, core_orbs, (1, 0)), + two_body_integrals=general_basis_change(self.tbi, core_orbs, (1, 1, 0, 0)), + constant=self.constant_offset, + ) self.hamiltonian = molecular_hamiltonian else: - self.hamiltonian = initial_orbitals(self.obi, self.tbi, - self.num_electrons) + self.hamiltonian = initial_orbitals(self.obi, self.tbi, self.num_electrons) def rdms_from_rhf_opdm(self, opdm_aa: np.ndarray) -> InteractionRDM: """ @@ -130,8 +130,7 @@ def rdms_from_rhf_opdm(self, opdm_aa: np.ndarray) -> InteractionRDM: Returns: InteractionRDM object for full spin-orbital 1-RDM and 2-RDM """ - opdm = np.zeros((2 * self.num_orbitals, 2 * self.num_orbitals), - dtype=np.complex128) + opdm = np.zeros((2 * self.num_orbitals, 2 * self.num_orbitals), dtype=np.complex128) opdm[::2, ::2] = opdm_aa opdm[1::2, 1::2] = opdm_aa tpdm = linalg.wedge(opdm, opdm, (1, 1), (1, 1)) @@ -162,19 +161,17 @@ def rhf_global_gradient(self, params: np.ndarray, alpha_opdm: np.ndarray): Returns: gradient vector the same size as the input `params' """ - opdm = np.zeros((2 * self.num_orbitals, 2 * self.num_orbitals), - dtype=np.complex128) + opdm = np.zeros((2 * self.num_orbitals, 2 * self.num_orbitals), dtype=np.complex128) opdm[::2, ::2] = alpha_opdm opdm[1::2, 1::2] = alpha_opdm tpdm = 2 * linalg.wedge(opdm, opdm, (1, 1), (1, 1)) # now go through and generate all the necessary Z, Y, Y_kl matrices - kappa_matrix = rhf_params_to_matrix(params, - len(self.occ) + len(self.virt), - self.occ, self.virt) + kappa_matrix = rhf_params_to_matrix( + params, len(self.occ) + len(self.virt), self.occ, self.virt + ) kappa_matrix_full = np.kron(kappa_matrix, np.eye(2)) - w_full, v_full = np.linalg.eigh( - -1j * kappa_matrix_full) # so that kappa = i U lambda U^ + w_full, v_full = np.linalg.eigh(-1j * kappa_matrix_full) # so that kappa = i U lambda U^ eigs_scaled_full = get_matrix_of_eigs(w_full) grad = np.zeros(self.nocc * self.nvirt, dtype=np.complex128) @@ -184,69 +181,93 @@ def rhf_global_gradient(self, params: np.ndarray, alpha_opdm: np.ndarray): for p in range(self.nocc * self.nvirt): grad_params = np.zeros_like(params) grad_params[p] = 1 - Y = rhf_params_to_matrix(grad_params, - len(self.occ) + len(self.virt), self.occ, - self.virt) + Y = rhf_params_to_matrix( + grad_params, len(self.occ) + len(self.virt), self.occ, self.virt + ) Y_full = np.kron(Y, np.eye(2)) # Now rotate Y int othe basis that diagonalizes Z Y_kl_full = v_full.conj().T.dot(Y_full).dot(v_full) # now rotate Y_{kl} * (exp(i(l_{k} - l_{l})) - 1) / (i(l_{k} - l_{l})) # into the original basis - pre_matrix_full = v_full.dot(eigs_scaled_full * Y_kl_full).dot( - v_full.conj().T) - - grad_expectation = -1.0 * np.einsum( - 'ab,pa,pb', - self.hamiltonian.one_body_tensor, - pre_matrix_full, - opdm, - optimize='optimal').real - - grad_expectation += 1.0 * np.einsum( - 'ab,bq,aq', - self.hamiltonian.one_body_tensor, - pre_matrix_full, - opdm, - optimize='optimal').real - - grad_expectation += 1.0 * np.einsum( - 'ijkl,pi,jpkl', - self.hamiltonian.two_body_tensor, - pre_matrix_full, - tpdm, - optimize='optimal').real - - grad_expectation += -1.0 * np.einsum( - 'ijkl,pj,ipkl', - self.hamiltonian.two_body_tensor, - pre_matrix_full, - tpdm, - optimize='optimal').real - - grad_expectation += -1.0 * np.einsum( - 'ijkl,kq,ijlq', - self.hamiltonian.two_body_tensor, - pre_matrix_full, - tpdm, - optimize='optimal').real - - grad_expectation += 1.0 * np.einsum( - 'ijkl,lq,ijkq', - self.hamiltonian.two_body_tensor, - pre_matrix_full, - tpdm, - optimize='optimal').real + pre_matrix_full = v_full.dot(eigs_scaled_full * Y_kl_full).dot(v_full.conj().T) + + grad_expectation = ( + -1.0 + * np.einsum( + 'ab,pa,pb', + self.hamiltonian.one_body_tensor, + pre_matrix_full, + opdm, + optimize='optimal', + ).real + ) + + grad_expectation += ( + 1.0 + * np.einsum( + 'ab,bq,aq', + self.hamiltonian.one_body_tensor, + pre_matrix_full, + opdm, + optimize='optimal', + ).real + ) + + grad_expectation += ( + 1.0 + * np.einsum( + 'ijkl,pi,jpkl', + self.hamiltonian.two_body_tensor, + pre_matrix_full, + tpdm, + optimize='optimal', + ).real + ) + + grad_expectation += ( + -1.0 + * np.einsum( + 'ijkl,pj,ipkl', + self.hamiltonian.two_body_tensor, + pre_matrix_full, + tpdm, + optimize='optimal', + ).real + ) + + grad_expectation += ( + -1.0 + * np.einsum( + 'ijkl,kq,ijlq', + self.hamiltonian.two_body_tensor, + pre_matrix_full, + tpdm, + optimize='optimal', + ).real + ) + + grad_expectation += ( + 1.0 + * np.einsum( + 'ijkl,lq,ijkq', + self.hamiltonian.two_body_tensor, + pre_matrix_full, + tpdm, + optimize='optimal', + ).real + ) grad[p] = grad_expectation return grad -def generate_hamiltonian(one_body_integrals: np.ndarray, - two_body_integrals: np.ndarray, - constant: float, - EQ_TOLERANCE: Optional[float] = 1.0E-12 - ) -> InteractionOperator: +def generate_hamiltonian( + one_body_integrals: np.ndarray, + two_body_integrals: np.ndarray, + constant: float, + EQ_TOLERANCE: Optional[float] = 1.0e-12, +) -> InteractionOperator: n_qubits = 2 * one_body_integrals.shape[0] # Initialize Hamiltonian coefficients. one_body_coefficients = np.zeros((n_qubits, n_qubits)) @@ -254,48 +275,45 @@ def generate_hamiltonian(one_body_integrals: np.ndarray, # Loop through integrals. for p in range(n_qubits // 2): for q in range(n_qubits // 2): - # Populate 1-body coefficients. Require p and q have same spin. one_body_coefficients[2 * p, 2 * q] = one_body_integrals[p, q] - one_body_coefficients[2 * p + 1, 2 * q + - 1] = one_body_integrals[p, q] + one_body_coefficients[2 * p + 1, 2 * q + 1] = one_body_integrals[p, q] # Continue looping to prepare 2-body coefficients. for r in range(n_qubits // 2): for s in range(n_qubits // 2): # Mixed spin - two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * - s] = (two_body_integrals[p, q, r, s] / - 2.) - two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + - 1] = (two_body_integrals[p, q, r, s] / - 2.) + two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) + two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) # Same spin - two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * - s] = (two_body_integrals[p, q, r, s] / - 2.) - two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + - 1, 2 * s + - 1] = (two_body_integrals[p, q, r, s] / - 2.) + two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) + two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) # Truncate. - one_body_coefficients[ - np.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0. - two_body_coefficients[ - np.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0. + one_body_coefficients[np.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0.0 + two_body_coefficients[np.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0.0 # Cast to InteractionOperator class and return. - molecular_hamiltonian = InteractionOperator(constant, one_body_coefficients, - two_body_coefficients) + molecular_hamiltonian = InteractionOperator( + constant, one_body_coefficients, two_body_coefficients + ) return molecular_hamiltonian -def rhf_params_to_matrix(parameters: np.ndarray, - num_orbitals: int, - occ: Optional[Union[None, List[int]]] = None, - virt: Optional[Union[None, List[int]]] = None - ) -> np.ndarray: +def rhf_params_to_matrix( + parameters: np.ndarray, + num_orbitals: int, + occ: Optional[Union[None, List[int]]] = None, + virt: Optional[Union[None, List[int]]] = None, +) -> np.ndarray: """ For restricted Hartree-Fock we have nocc * nvirt parameters. These are provided as a list that is ordered by (virtuals) \times (occupied). @@ -343,11 +361,11 @@ def rhf_params_to_matrix(parameters: np.ndarray, return kappa -def rhf_func_generator(rhf_func: HartreeFockFunctional, - init_occ_vec: Optional[Union[None, np.ndarray]] = None, - get_opdm_func: Optional[bool] = False - ) -> Union[Tuple[Callable, Callable, Callable], - Tuple[Callable, Callable, Callable, Callable]]: +def rhf_func_generator( + rhf_func: HartreeFockFunctional, + init_occ_vec: Optional[Union[None, np.ndarray]] = None, + get_opdm_func: Optional[bool] = False, +) -> Union[Tuple[Callable, Callable, Callable], Tuple[Callable, Callable, Callable, Callable]]: """ Generate the energy, gradient, and unitary functions @@ -376,8 +394,9 @@ def gradient(params): return rhf_func.rhf_global_gradient(params, final_opdm_aa).real def unitary(params): - kappa = rhf_params_to_matrix(params, rhf_func.nocc + rhf_func.nvirt, - rhf_func.occ, rhf_func.virt) + kappa = rhf_params_to_matrix( + params, rhf_func.nocc + rhf_func.nvirt, rhf_func.occ, rhf_func.virt + ) return sp.linalg.expm(kappa) def get_opdm(params): @@ -389,12 +408,13 @@ def get_opdm(params): return unitary, energy, gradient -def rhf_minimization(rhf_object: HartreeFockFunctional, - method: Optional[str] = 'CG', - initial_guess: Optional[Union[None, np.ndarray]] = None, - verbose: Optional[bool] = True, - sp_options: Optional[Union[None, Dict]] = None - ) -> OptimizeResult: +def rhf_minimization( + rhf_object: HartreeFockFunctional, + method: Optional[str] = 'CG', + initial_guess: Optional[Union[None, np.ndarray]] = None, + verbose: Optional[bool] = True, + sp_options: Optional[Union[None, Dict]] = None, +) -> OptimizeResult: """ Perform Hartree-Fock energy minimization @@ -418,8 +438,6 @@ def rhf_minimization(rhf_object: HartreeFockFunctional, if sp_options is not None: sp_optimizer_options.update(sp_options) - return sp.optimize.minimize(energy, - init_guess, - jac=gradient, - method=method, - options=sp_optimizer_options) + return sp.optimize.minimize( + energy, init_guess, jac=gradient, method=method, options=sp_optimizer_options + ) diff --git a/src/openfermion/hamiltonians/hartree_fock_test.py b/src/openfermion/hamiltonians/hartree_fock_test.py index 2313e36df..3274f5f79 100644 --- a/src/openfermion/hamiltonians/hartree_fock_test.py +++ b/src/openfermion/hamiltonians/hartree_fock_test.py @@ -7,24 +7,29 @@ from scipy.optimize import OptimizeResult from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData -from openfermion.ops.representations import (general_basis_change, - InteractionOperator) +from openfermion.ops.representations import general_basis_change, InteractionOperator from openfermion.hamiltonians.hartree_fock import ( - get_matrix_of_eigs, HartreeFockFunctional, InputError, rhf_params_to_matrix, - rhf_func_generator, generate_hamiltonian, rhf_minimization) + get_matrix_of_eigs, + HartreeFockFunctional, + InputError, + rhf_params_to_matrix, + rhf_func_generator, + generate_hamiltonian, + rhf_minimization, +) def test_get_matrix_of_eigs(): lam_vals = np.random.randn(4) + 1j * np.random.randn(4) lam_vals[0] = lam_vals[1] - mat_eigs = np.zeros((lam_vals.shape[0], lam_vals.shape[0]), - dtype=np.complex128) + mat_eigs = np.zeros((lam_vals.shape[0], lam_vals.shape[0]), dtype=np.complex128) for i, j in product(range(lam_vals.shape[0]), repeat=2): if np.isclose(abs(lam_vals[i] - lam_vals[j]), 0): mat_eigs[i, j] = 1 else: - mat_eigs[i, j] = (np.exp(1j * (lam_vals[i] - lam_vals[j])) - - 1) / (1j * (lam_vals[i] - lam_vals[j])) + mat_eigs[i, j] = (np.exp(1j * (lam_vals[i] - lam_vals[j])) - 1) / ( + 1j * (lam_vals[i] - lam_vals[j]) + ) test_mat_eigs = get_matrix_of_eigs(lam_vals) assert np.allclose(test_mat_eigs, mat_eigs) @@ -37,46 +42,54 @@ def test_hffunctional_setup(): def fake_orbital_func(x, y, z): return None - hff = HartreeFockFunctional(one_body_integrals=fake_obi, - two_body_integrals=fake_tbi, - overlap=fake_obi, - n_electrons=6, - model='rhf', - initial_orbitals=fake_orbital_func) + hff = HartreeFockFunctional( + one_body_integrals=fake_obi, + two_body_integrals=fake_tbi, + overlap=fake_obi, + n_electrons=6, + model='rhf', + initial_orbitals=fake_orbital_func, + ) assert hff.occ == list(range(3)) assert hff.virt == list(range(3, 8)) assert hff.nocc == 3 assert hff.nvirt == 5 - hff = HartreeFockFunctional(one_body_integrals=fake_obi, - two_body_integrals=fake_tbi, - overlap=fake_obi, - n_electrons=6, - model='uhf', - initial_orbitals=fake_orbital_func) + hff = HartreeFockFunctional( + one_body_integrals=fake_obi, + two_body_integrals=fake_tbi, + overlap=fake_obi, + n_electrons=6, + model='uhf', + initial_orbitals=fake_orbital_func, + ) assert hff.occ == list(range(6)) assert hff.virt == list(range(6, 16)) assert hff.nocc == 6 assert hff.nvirt == 10 - hff = HartreeFockFunctional(one_body_integrals=fake_obi, - two_body_integrals=fake_tbi, - overlap=fake_obi, - n_electrons=6, - model='ghf', - initial_orbitals=fake_orbital_func) + hff = HartreeFockFunctional( + one_body_integrals=fake_obi, + two_body_integrals=fake_tbi, + overlap=fake_obi, + n_electrons=6, + model='ghf', + initial_orbitals=fake_orbital_func, + ) assert hff.occ == list(range(6)) assert hff.virt == list(range(6, 16)) assert hff.nocc == 6 assert hff.nvirt == 10 with pytest.raises(InputError): - hff = HartreeFockFunctional(one_body_integrals=fake_obi, - two_body_integrals=fake_tbi, - overlap=fake_obi, - n_electrons=6, - model='abc', - initial_orbitals=fake_orbital_func) + hff = HartreeFockFunctional( + one_body_integrals=fake_obi, + two_body_integrals=fake_tbi, + overlap=fake_obi, + n_electrons=6, + model='abc', + initial_orbitals=fake_orbital_func, + ) def test_gradient(): @@ -89,19 +102,17 @@ def test_gradient(): rotation_mat = molecule.canonical_orbitals.T.dot(overlap) obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) - hff = HartreeFockFunctional(one_body_integrals=obi, - two_body_integrals=tbi, - overlap=overlap, - n_electrons=molecule.n_electrons, - model='rhf', - nuclear_repulsion=molecule.nuclear_repulsion) + hff = HartreeFockFunctional( + one_body_integrals=obi, + two_body_integrals=tbi, + overlap=overlap, + n_electrons=molecule.n_electrons, + model='rhf', + nuclear_repulsion=molecule.nuclear_repulsion, + ) params = np.random.randn(hff.nocc * hff.nvirt) - u = sp.linalg.expm( - rhf_params_to_matrix(params, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + u = sp.linalg.expm(rhf_params_to_matrix(params, hff.num_orbitals, occ=hff.occ, virt=hff.virt)) initial_opdm = np.diag([1] * hff.nocc + [0] * hff.nvirt) final_opdm = u.dot(initial_opdm).dot(u.conj().T) grad = hff.rhf_global_gradient(params, final_opdm) @@ -114,24 +125,19 @@ def test_gradient(): params_epsilon = params.copy() params_epsilon[i] += epsilon u = sp.linalg.expm( - rhf_params_to_matrix(params_epsilon, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt) + ) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_plus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) params_epsilon[i] -= 2 * epsilon u = sp.linalg.expm( - rhf_params_to_matrix(params_epsilon, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt) + ) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_minus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) - finite_diff_grad[i] = (energy_plus_epsilon - - energy_minus_epsilon) / (2 * epsilon) + finite_diff_grad[i] = (energy_plus_epsilon - energy_minus_epsilon) / (2 * epsilon) assert np.allclose(finite_diff_grad, grad, atol=epsilon) @@ -147,19 +153,17 @@ def test_gradient_lih(): obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) - hff = HartreeFockFunctional(one_body_integrals=obi, - two_body_integrals=tbi, - overlap=overlap, - n_electrons=molecule.n_electrons, - model='rhf', - nuclear_repulsion=molecule.nuclear_repulsion) + hff = HartreeFockFunctional( + one_body_integrals=obi, + two_body_integrals=tbi, + overlap=overlap, + n_electrons=molecule.n_electrons, + model='rhf', + nuclear_repulsion=molecule.nuclear_repulsion, + ) params = np.random.randn(hff.nocc * hff.nvirt) - u = sp.linalg.expm( - rhf_params_to_matrix(params, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + u = sp.linalg.expm(rhf_params_to_matrix(params, hff.num_orbitals, occ=hff.occ, virt=hff.virt)) grad_dim = hff.nocc * hff.nvirt initial_opdm = np.diag([1] * hff.nocc + [0] * hff.nvirt) final_opdm = u.dot(initial_opdm).dot(u.conj().T) @@ -172,24 +176,19 @@ def test_gradient_lih(): params_epsilon = params.copy() params_epsilon[i] += epsilon u = sp.linalg.expm( - rhf_params_to_matrix(params_epsilon, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt) + ) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_plus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) params_epsilon[i] -= 2 * epsilon u = sp.linalg.expm( - rhf_params_to_matrix(params_epsilon, - hff.num_orbitals, - occ=hff.occ, - virt=hff.virt)) + rhf_params_to_matrix(params_epsilon, hff.num_orbitals, occ=hff.occ, virt=hff.virt) + ) tfinal_opdm = u.dot(initial_opdm).dot(u.conj().T) energy_minus_epsilon = hff.energy_from_rhf_opdm(tfinal_opdm) - finite_diff_grad[i] = (energy_plus_epsilon - - energy_minus_epsilon) / (2 * epsilon) + finite_diff_grad[i] = (energy_plus_epsilon - energy_minus_epsilon) / (2 * epsilon) assert np.allclose(finite_diff_grad, grad, atol=epsilon) @@ -205,12 +204,14 @@ def test_rhf_func_generator(): obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) - hff = HartreeFockFunctional(one_body_integrals=obi, - two_body_integrals=tbi, - overlap=overlap, - n_electrons=molecule.n_electrons, - model='rhf', - nuclear_repulsion=molecule.nuclear_repulsion) + hff = HartreeFockFunctional( + one_body_integrals=obi, + two_body_integrals=tbi, + overlap=overlap, + n_electrons=molecule.n_electrons, + model='rhf', + nuclear_repulsion=molecule.nuclear_repulsion, + ) unitary, energy, gradient = rhf_func_generator(hff) assert isinstance(unitary, Callable) assert isinstance(energy, Callable) @@ -227,8 +228,7 @@ def test_rhf_func_generator(): assert isinstance(opdm_func(params), np.ndarray) assert np.isclose(opdm_func(params).shape[0], hff.num_orbitals) - _, energy, _ = rhf_func_generator(hff, - init_occ_vec=np.array([1, 1, 1, 1, 0, 0])) + _, energy, _ = rhf_func_generator(hff, init_occ_vec=np.array([1, 1, 1, 1, 0, 0])) assert isinstance(energy(params), float) @@ -261,10 +261,8 @@ def test_generate_hamiltonian(): assert isinstance(mol_ham, InteractionOperator) assert np.allclose(mol_ham.one_body_tensor[::2, ::2], mo_obi) assert np.allclose(mol_ham.one_body_tensor[1::2, 1::2], mo_obi) - assert np.allclose(mol_ham.two_body_tensor[::2, ::2, ::2, ::2], - 0.5 * mo_tbi) - assert np.allclose(mol_ham.two_body_tensor[1::2, 1::2, 1::2, 1::2], - 0.5 * mo_tbi) + assert np.allclose(mol_ham.two_body_tensor[::2, ::2, ::2, ::2], 0.5 * mo_tbi) + assert np.allclose(mol_ham.two_body_tensor[1::2, 1::2, 1::2, 1::2], 0.5 * mo_tbi) def test_rhf_min(): @@ -277,20 +275,19 @@ def test_rhf_min(): rotation_mat = molecule.canonical_orbitals.T.dot(overlap) obi = general_basis_change(mo_obi, rotation_mat, (1, 0)) tbi = general_basis_change(mo_tbi, rotation_mat, (1, 1, 0, 0)) - hff = HartreeFockFunctional(one_body_integrals=obi, - two_body_integrals=tbi, - overlap=overlap, - n_electrons=molecule.n_electrons, - model='rhf', - nuclear_repulsion=molecule.nuclear_repulsion) + hff = HartreeFockFunctional( + one_body_integrals=obi, + two_body_integrals=tbi, + overlap=overlap, + n_electrons=molecule.n_electrons, + model='rhf', + nuclear_repulsion=molecule.nuclear_repulsion, + ) result = rhf_minimization(hff) assert isinstance(result, OptimizeResult) - result2 = rhf_minimization(hff, - initial_guess=np.array([0]), - sp_options={ - 'maxiter': 100, - 'disp': False - }) + result2 = rhf_minimization( + hff, initial_guess=np.array([0]), sp_options={'maxiter': 100, 'disp': False} + ) assert isinstance(result2, OptimizeResult) assert np.isclose(result2.fun, result.fun) diff --git a/src/openfermion/hamiltonians/hubbard.py b/src/openfermion/hamiltonians/hubbard.py index d1ec839a7..a549ef4b6 100644 --- a/src/openfermion/hamiltonians/hubbard.py +++ b/src/openfermion/hamiltonians/hubbard.py @@ -17,15 +17,17 @@ from openfermion.hamiltonians.special_operators import number_operator -def fermi_hubbard(x_dimension, - y_dimension, - tunneling, - coulomb, - chemical_potential=0., - magnetic_field=0., - periodic=True, - spinless=False, - particle_hole_symmetry=False): +def fermi_hubbard( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential=0.0, + magnetic_field=0.0, + periodic=True, + spinless=False, + particle_hole_symmetry=False, +): r"""Return symbolic representation of a Fermi-Hubbard Hamiltonian. The idea of this model is that some fermions move around on a grid and the @@ -110,21 +112,39 @@ def fermi_hubbard(x_dimension, hubbard_model: An instance of the FermionOperator class. """ if spinless: - return _spinless_fermi_hubbard_model(x_dimension, y_dimension, - tunneling, coulomb, - chemical_potential, magnetic_field, - periodic, particle_hole_symmetry) + return _spinless_fermi_hubbard_model( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential, + magnetic_field, + periodic, + particle_hole_symmetry, + ) else: - return _spinful_fermi_hubbard_model(x_dimension, y_dimension, tunneling, - coulomb, chemical_potential, - magnetic_field, periodic, - particle_hole_symmetry) - - -def _spinful_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, - chemical_potential, magnetic_field, periodic, - particle_hole_symmetry): - + return _spinful_fermi_hubbard_model( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential, + magnetic_field, + periodic, + particle_hole_symmetry, + ) + + +def _spinful_fermi_hubbard_model( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential, + magnetic_field, + periodic, + particle_hole_symmetry, +): # Initialize operator. n_sites = x_dimension * y_dimension n_spin_orbitals = 2 * n_sites @@ -132,12 +152,9 @@ def _spinful_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, # Loop through sites and add terms. for site in range(n_sites): - # Get indices of right and bottom neighbors - right_neighbor = _right_neighbor(site, x_dimension, y_dimension, - periodic) - bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, - periodic) + right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) + bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic @@ -148,50 +165,49 @@ def _spinful_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, # Add hopping terms with neighbors to the right and bottom. if right_neighbor is not None: - hubbard_model += _hopping_term(up_index(site), - up_index(right_neighbor), -tunneling) - hubbard_model += _hopping_term(down_index(site), - down_index(right_neighbor), - -tunneling) + hubbard_model += _hopping_term(up_index(site), up_index(right_neighbor), -tunneling) + hubbard_model += _hopping_term(down_index(site), down_index(right_neighbor), -tunneling) if bottom_neighbor is not None: - hubbard_model += _hopping_term(up_index(site), - up_index(bottom_neighbor), - -tunneling) - hubbard_model += _hopping_term(down_index(site), - down_index(bottom_neighbor), - -tunneling) + hubbard_model += _hopping_term(up_index(site), up_index(bottom_neighbor), -tunneling) + hubbard_model += _hopping_term( + down_index(site), down_index(bottom_neighbor), -tunneling + ) # Add local pair Coulomb interaction terms. - hubbard_model += _coulomb_interaction_term(n_spin_orbitals, - up_index(site), - down_index(site), coulomb, - particle_hole_symmetry) + hubbard_model += _coulomb_interaction_term( + n_spin_orbitals, up_index(site), down_index(site), coulomb, particle_hole_symmetry + ) # Add chemical potential and magnetic field terms. - hubbard_model += number_operator(n_spin_orbitals, up_index(site), - -chemical_potential - magnetic_field) - hubbard_model += number_operator(n_spin_orbitals, down_index(site), - -chemical_potential + magnetic_field) + hubbard_model += number_operator( + n_spin_orbitals, up_index(site), -chemical_potential - magnetic_field + ) + hubbard_model += number_operator( + n_spin_orbitals, down_index(site), -chemical_potential + magnetic_field + ) return hubbard_model -def _spinless_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, - chemical_potential, magnetic_field, periodic, - particle_hole_symmetry): - +def _spinless_fermi_hubbard_model( + x_dimension, + y_dimension, + tunneling, + coulomb, + chemical_potential, + magnetic_field, + periodic, + particle_hole_symmetry, +): # Initialize operator. n_sites = x_dimension * y_dimension hubbard_model = FermionOperator() # Loop through sites and add terms. for site in range(n_sites): - # Get indices of right and bottom neighbors - right_neighbor = _right_neighbor(site, x_dimension, y_dimension, - periodic) - bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, - periodic) + right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) + bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic @@ -205,16 +221,16 @@ def _spinless_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, # Add hopping term hubbard_model += _hopping_term(site, right_neighbor, -tunneling) # Add local Coulomb interaction term - hubbard_model += _coulomb_interaction_term(n_sites, site, - right_neighbor, coulomb, - particle_hole_symmetry) + hubbard_model += _coulomb_interaction_term( + n_sites, site, right_neighbor, coulomb, particle_hole_symmetry + ) if bottom_neighbor is not None: # Add hopping term hubbard_model += _hopping_term(site, bottom_neighbor, -tunneling) # Add local Coulomb interaction term - hubbard_model += _coulomb_interaction_term(n_sites, site, - bottom_neighbor, coulomb, - particle_hole_symmetry) + hubbard_model += _coulomb_interaction_term( + n_sites, site, bottom_neighbor, coulomb, particle_hole_symmetry + ) # Add chemical potential. The magnetic field doesn't contribute. hubbard_model += number_operator(n_sites, site, -chemical_potential) @@ -222,13 +238,15 @@ def _spinless_fermi_hubbard_model(x_dimension, y_dimension, tunneling, coulomb, return hubbard_model -def bose_hubbard(x_dimension, - y_dimension, - tunneling, - interaction, - chemical_potential=0., - dipole=0., - periodic=True): +def bose_hubbard( + x_dimension, + y_dimension, + tunneling, + interaction, + chemical_potential=0.0, + dipole=0.0, + periodic=True, +): r"""Return symbolic representation of a Bose-Hubbard Hamiltonian. In this model, bosons move around on a lattice, and the @@ -279,12 +297,9 @@ def bose_hubbard(x_dimension, # Loop through sites and add terms. for site in range(n_sites): - # Get indices of right and bottom neighbors - right_neighbor = _right_neighbor(site, x_dimension, y_dimension, - periodic) - bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, - periodic) + right_neighbor = _right_neighbor(site, x_dimension, y_dimension, periodic) + bottom_neighbor = _bottom_neighbor(site, x_dimension, y_dimension, periodic) # Avoid double-counting edges when one of the dimensions is 2 # and the system is periodic @@ -296,43 +311,26 @@ def bose_hubbard(x_dimension, # Add terms that couple with neighbors to the right and bottom. if right_neighbor is not None: # Add hopping term - hubbard_model += _hopping_term(site, - right_neighbor, - -tunneling, - bosonic=True) + hubbard_model += _hopping_term(site, right_neighbor, -tunneling, bosonic=True) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term( - n_sites, - site, - right_neighbor, - dipole, - particle_hole_symmetry=False, - bosonic=True) + n_sites, site, right_neighbor, dipole, particle_hole_symmetry=False, bosonic=True + ) if bottom_neighbor is not None: # Add hopping term - hubbard_model += _hopping_term(site, - bottom_neighbor, - -tunneling, - bosonic=True) + hubbard_model += _hopping_term(site, bottom_neighbor, -tunneling, bosonic=True) # Add local Coulomb interaction term hubbard_model += _coulomb_interaction_term( - n_sites, - site, - bottom_neighbor, - dipole, - particle_hole_symmetry=False, - bosonic=True) + n_sites, site, bottom_neighbor, dipole, particle_hole_symmetry=False, bosonic=True + ) # Add on-site interaction. - hubbard_model += ( - number_operator(n_sites, site, 0.5 * interaction, parity=1) * - (number_operator(n_sites, site, parity=1) - BosonOperator(()))) + hubbard_model += number_operator(n_sites, site, 0.5 * interaction, parity=1) * ( + number_operator(n_sites, site, parity=1) - BosonOperator(()) + ) # Add chemical potential. - hubbard_model += number_operator(n_sites, - site, - -chemical_potential, - parity=1) + hubbard_model += number_operator(n_sites, site, -chemical_potential, parity=1) return hubbard_model @@ -344,12 +342,7 @@ def _hopping_term(i, j, coefficient, bosonic=False): return hopping_term -def _coulomb_interaction_term(n_sites, - i, - j, - coefficient, - particle_hole_symmetry, - bosonic=False): +def _coulomb_interaction_term(n_sites, i, j, coefficient, particle_hole_symmetry, bosonic=False): op_class = BosonOperator if bosonic else FermionOperator number_operator_i = number_operator(n_sites, i, parity=2 * bosonic - 1) number_operator_j = number_operator(n_sites, j, parity=2 * bosonic - 1) diff --git a/src/openfermion/hamiltonians/hubbard_test.py b/src/openfermion/hamiltonians/hubbard_test.py index 2b850fcea..ee35c5429 100644 --- a/src/openfermion/hamiltonians/hubbard_test.py +++ b/src/openfermion/hamiltonians/hubbard_test.py @@ -15,13 +15,10 @@ def test_fermi_hubbard_1x3_spinless(): - hubbard_model = fermi_hubbard(1, - 3, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(1, 3, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + -1.0 [0^ 1] + @@ -35,16 +32,14 @@ def test_fermi_hubbard_1x3_spinless(): -0.5 [2^ 2] + 4.0 [2^ 2 0^ 0] """.strip() + ) def test_fermi_hubbard_3x1_spinless(): - hubbard_model = fermi_hubbard(3, - 1, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(3, 1, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + -1.0 [0^ 1] + @@ -58,16 +53,14 @@ def test_fermi_hubbard_3x1_spinless(): -0.5 [2^ 2] + 4.0 [2^ 2 0^ 0] """.strip() + ) def test_fermi_hubbard_2x2_spinless(): - hubbard_model = fermi_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(2, 2, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + 4.0 [0^ 0 2^ 2] + @@ -85,16 +78,14 @@ def test_fermi_hubbard_2x2_spinless(): -1.0 [3^ 2] + -0.5 [3^ 3] """.strip() + ) def test_fermi_hubbard_2x3_spinless(): - hubbard_model = fermi_hubbard(2, - 3, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(2, 3, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + 4.0 [0^ 0 2^ 2] + @@ -129,16 +120,14 @@ def test_fermi_hubbard_2x3_spinless(): -0.5 [5^ 5] + 4.0 [5^ 5 1^ 1] """.strip() + ) def test_fermi_hubbard_3x2_spinless(): - hubbard_model = fermi_hubbard(3, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(3, 2, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + 4.0 [0^ 0 3^ 3] + @@ -173,16 +162,14 @@ def test_fermi_hubbard_3x2_spinless(): -0.5 [5^ 5] + 4.0 [5^ 5 3^ 3] """.strip() + ) def test_fermi_hubbard_3x3_spinless(): - hubbard_model = fermi_hubbard(3, - 3, - 1.0, - 4.0, - chemical_potential=0.5, - spinless=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard(3, 3, 1.0, 4.0, chemical_potential=0.5, spinless=True) + assert ( + str(hubbard_model).strip() + == """ -0.5 [0^ 0] + 4.0 [0^ 0 1^ 1] + 4.0 [0^ 0 3^ 3] + @@ -247,17 +234,16 @@ def test_fermi_hubbard_3x3_spinless(): 4.0 [8^ 8 2^ 2] + 4.0 [8^ 8 6^ 6] """.strip() + ) def test_fermi_hubbard_2x2_spinful(): - hubbard_model = fermi_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - magnetic_field=0.3, - spinless=False) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard( + 2, 2, 1.0, 4.0, chemical_potential=0.5, magnetic_field=0.3, spinless=False + ) + assert ( + str(hubbard_model).strip() + == """ -0.8 [0^ 0] + 4.0 [0^ 0 1^ 1] + -1.0 [0^ 2] + @@ -287,17 +273,16 @@ def test_fermi_hubbard_2x2_spinful(): -1.0 [7^ 5] + -0.2 [7^ 7] """.strip() + ) def test_fermi_hubbard_2x3_spinful(): - hubbard_model = fermi_hubbard(2, - 3, - 1.0, - 4.0, - chemical_potential=0.5, - magnetic_field=0.3, - spinless=False) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard( + 2, 3, 1.0, 4.0, chemical_potential=0.5, magnetic_field=0.3, spinless=False + ) + assert ( + str(hubbard_model).strip() + == """ -0.8 [0^ 0] + 4.0 [0^ 0 1^ 1] + -1.0 [0^ 2] + @@ -353,18 +338,23 @@ def test_fermi_hubbard_2x3_spinful(): -1.0 [11^ 9] + -0.2 [11^ 11] """.strip() + ) def test_fermi_hubbard_2x2_spinful_phs(): - hubbard_model = fermi_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - magnetic_field=0.3, - spinless=False, - particle_hole_symmetry=True) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard( + 2, + 2, + 1.0, + 4.0, + chemical_potential=0.5, + magnetic_field=0.3, + spinless=False, + particle_hole_symmetry=True, + ) + assert ( + str(hubbard_model).strip() + == """ 4.0 [] + -2.8 [0^ 0] + 4.0 [0^ 0 1^ 1] + @@ -395,18 +385,16 @@ def test_fermi_hubbard_2x2_spinful_phs(): -1.0 [7^ 5] + -2.2 [7^ 7] """.strip() + ) def test_fermi_hubbard_2x2_spinful_aperiodic(): - hubbard_model = fermi_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - magnetic_field=0.3, - spinless=False, - periodic=False) - assert str(hubbard_model).strip() == """ + hubbard_model = fermi_hubbard( + 2, 2, 1.0, 4.0, chemical_potential=0.5, magnetic_field=0.3, spinless=False, periodic=False + ) + assert ( + str(hubbard_model).strip() + == """ -0.8 [0^ 0] + 4.0 [0^ 0 1^ 1] + -1.0 [0^ 2] + @@ -436,16 +424,14 @@ def test_fermi_hubbard_2x2_spinful_aperiodic(): -1.0 [7^ 5] + -0.2 [7^ 7] """.strip() + ) def test_bose_hubbard_2x2(): - hubbard_model = bose_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - dipole=0.3) - assert str(hubbard_model).strip() == """ + hubbard_model = bose_hubbard(2, 2, 1.0, 4.0, chemical_potential=0.5, dipole=0.3) + assert ( + str(hubbard_model).strip() + == """ -1.0 [0 1^] + -1.0 [0 2^] + -2.5 [0^ 0] + @@ -467,16 +453,14 @@ def test_bose_hubbard_2x2(): -2.5 [3^ 3] + 2.0 [3^ 3 3^ 3] """.strip() + ) def test_bose_hubbard_2x3(): - hubbard_model = bose_hubbard(2, - 3, - 1.0, - 4.0, - chemical_potential=0.5, - dipole=0.3) - assert str(hubbard_model).strip() == """ + hubbard_model = bose_hubbard(2, 3, 1.0, 4.0, chemical_potential=0.5, dipole=0.3) + assert ( + str(hubbard_model).strip() + == """ -1.0 [0 1^] + -1.0 [0 2^] + -1.0 [0 4^] + @@ -517,16 +501,14 @@ def test_bose_hubbard_2x3(): -2.5 [5^ 5] + 2.0 [5^ 5 5^ 5] """.strip() + ) def test_bose_hubbard_3x2(): - hubbard_model = bose_hubbard(3, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - dipole=0.3) - assert str(hubbard_model).strip() == """ + hubbard_model = bose_hubbard(3, 2, 1.0, 4.0, chemical_potential=0.5, dipole=0.3) + assert ( + str(hubbard_model).strip() + == """ -1.0 [0 1^] + -1.0 [0 2^] + -1.0 [0 3^] + @@ -567,17 +549,14 @@ def test_bose_hubbard_3x2(): -2.5 [5^ 5] + 2.0 [5^ 5 5^ 5] """.strip() + ) def test_bose_hubbard_2x2_aperiodic(): - hubbard_model = bose_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=0.5, - dipole=0.3, - periodic=False) - assert str(hubbard_model).strip() == """ + hubbard_model = bose_hubbard(2, 2, 1.0, 4.0, chemical_potential=0.5, dipole=0.3, periodic=False) + assert ( + str(hubbard_model).strip() + == """ -1.0 [0 1^] + -1.0 [0 2^] + -2.5 [0^ 0] + @@ -599,3 +578,4 @@ def test_bose_hubbard_2x2_aperiodic(): -2.5 [3^ 3] + 2.0 [3^ 3 3^ 3] """.strip() + ) diff --git a/src/openfermion/hamiltonians/jellium.py b/src/openfermion/hamiltonians/jellium.py index 9cd2e68d1..c54c33a33 100644 --- a/src/openfermion/hamiltonians/jellium.py +++ b/src/openfermion/hamiltonians/jellium.py @@ -19,8 +19,9 @@ from openfermion.utils.grid import Grid -def wigner_seitz_length_scale(wigner_seitz_radius: float, n_particles: int, - dimension: int) -> float: +def wigner_seitz_length_scale( + wigner_seitz_radius: float, n_particles: int, dimension: int +) -> float: """Function to give length_scale associated with Wigner-Seitz radius. Args: @@ -39,24 +40,29 @@ def wigner_seitz_length_scale(wigner_seitz_radius: float, n_particles: int, half_dimension = dimension // 2 if dimension % 2: - volume_per_particle = (2 * numpy.math.factorial(half_dimension) * - (4 * numpy.pi)**half_dimension / - numpy.math.factorial(dimension) * - wigner_seitz_radius**dimension) + volume_per_particle = ( + 2 + * numpy.math.factorial(half_dimension) + * (4 * numpy.pi) ** half_dimension + / numpy.math.factorial(dimension) + * wigner_seitz_radius**dimension + ) else: - volume_per_particle = (numpy.pi**half_dimension / - numpy.math.factorial(half_dimension) * - wigner_seitz_radius**dimension) + volume_per_particle = ( + numpy.pi**half_dimension + / numpy.math.factorial(half_dimension) + * wigner_seitz_radius**dimension + ) volume = volume_per_particle * n_particles - length_scale = volume**(1. / dimension) + length_scale = volume ** (1.0 / dimension) return length_scale -def plane_wave_kinetic(grid: Grid, - spinless: bool = False, - e_cutoff: Optional[float] = None) -> FermionOperator: +def plane_wave_kinetic( + grid: Grid, spinless: bool = False, e_cutoff: Optional[float] = None +) -> FermionOperator: """Return the kinetic energy operator in the plane wave basis. Args: @@ -74,7 +80,7 @@ def plane_wave_kinetic(grid: Grid, # Loop once through all plane waves. for momenta_indices in grid.all_points_indices(): momenta = grid.momentum_vector(momenta_indices) - coefficient = momenta.dot(momenta) / 2. + coefficient = momenta.dot(momenta) / 2.0 # Energy cutoff. if e_cutoff is not None and coefficient > e_cutoff: @@ -91,12 +97,13 @@ def plane_wave_kinetic(grid: Grid, return operator -def plane_wave_potential(grid: Grid, - spinless: bool = False, - e_cutoff: float = None, - non_periodic: bool = False, - period_cutoff: Optional[float] = None - ) -> FermionOperator: +def plane_wave_potential( + grid: Grid, + spinless: bool = False, + e_cutoff: float = None, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return the e-e potential operator in the plane wave basis. Args: @@ -111,11 +118,11 @@ def plane_wave_potential(grid: Grid, operator (FermionOperator) """ # Initialize. - prefactor = 2. * numpy.pi / grid.volume_scale() + prefactor = 2.0 * numpy.pi / grid.volume_scale() operator = FermionOperator((), 0.0) spins = [None] if spinless else [0, 1] if non_periodic and period_cutoff is None: - period_cutoff = grid.volume_scale()**(1. / grid.dimensions) + period_cutoff = grid.volume_scale() ** (1.0 / grid.dimensions) # Pre-Computations. shifted_omega_indices_dict = {} @@ -123,21 +130,23 @@ def plane_wave_potential(grid: Grid, shifted_indices_plus_dict = {} orbital_ids = {} for indices_a in grid.all_points_indices(): - shifted_omega_indices = [ - j - grid.length[i] // 2 for i, j in enumerate(indices_a) - ] + shifted_omega_indices = [j - grid.length[i] // 2 for i, j in enumerate(indices_a)] shifted_omega_indices_dict[indices_a] = shifted_omega_indices shifted_indices_minus_dict[indices_a] = {} shifted_indices_plus_dict[indices_a] = {} for indices_b in grid.all_points_indices(): - shifted_indices_minus_dict[indices_a][indices_b] = tuple([ - (indices_b[i] - shifted_omega_indices[i]) % grid.length[i] - for i in range(grid.dimensions) - ]) - shifted_indices_plus_dict[indices_a][indices_b] = tuple([ - (indices_b[i] + shifted_omega_indices[i]) % grid.length[i] - for i in range(grid.dimensions) - ]) + shifted_indices_minus_dict[indices_a][indices_b] = tuple( + [ + (indices_b[i] - shifted_omega_indices[i]) % grid.length[i] + for i in range(grid.dimensions) + ] + ) + shifted_indices_plus_dict[indices_a][indices_b] = tuple( + [ + (indices_b[i] + shifted_omega_indices[i]) % grid.length[i] + for i in range(grid.dimensions) + ] + ) orbital_ids[indices_a] = {} for spin in spins: orbital_ids[indices_a][spin] = grid.orbital_id(indices_a, spin) @@ -155,21 +164,18 @@ def plane_wave_potential(grid: Grid, continue # Energy cutoff. - if e_cutoff is not None and momenta_squared / 2. > e_cutoff: + if e_cutoff is not None and momenta_squared / 2.0 > e_cutoff: continue # Compute coefficient. coefficient = prefactor / momenta_squared if non_periodic: - coefficient *= 1.0 - numpy.cos( - period_cutoff * numpy.sqrt(momenta_squared)) + coefficient *= 1.0 - numpy.cos(period_cutoff * numpy.sqrt(momenta_squared)) for grid_indices_a in grid.all_points_indices(): - shifted_indices_d = ( - shifted_indices_minus_dict[omega_indices][grid_indices_a]) + shifted_indices_d = shifted_indices_minus_dict[omega_indices][grid_indices_a] for grid_indices_b in grid.all_points_indices(): - shifted_indices_c = ( - shifted_indices_plus_dict[omega_indices][grid_indices_b]) + shifted_indices_c = shifted_indices_plus_dict[omega_indices][grid_indices_b] # Loop over spins. for spin_a in spins: @@ -180,24 +186,28 @@ def plane_wave_potential(grid: Grid, orbital_c = orbital_ids[shifted_indices_c][spin_b] # Add interaction term. - if ((orbital_a != orbital_b) and - (orbital_c != orbital_d)): - operators = ((orbital_a, 1), (orbital_b, 1), - (orbital_c, 0), (orbital_d, 0)) + if (orbital_a != orbital_b) and (orbital_c != orbital_d): + operators = ( + (orbital_a, 1), + (orbital_b, 1), + (orbital_c, 0), + (orbital_d, 0), + ) operator += FermionOperator(operators, coefficient) # Return. return operator -def dual_basis_jellium_model(grid: Grid, - spinless: bool = False, - kinetic: bool = True, - potential: bool = True, - include_constant: bool = False, - non_periodic: bool = False, - period_cutoff: Optional[float] = None - ) -> FermionOperator: +def dual_basis_jellium_model( + grid: Grid, + spinless: bool = False, + kinetic: bool = True, + potential: bool = True, + include_constant: bool = False, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return jellium Hamiltonian in the dual basis of arXiv:1706.00023 Args: @@ -221,7 +231,7 @@ def dual_basis_jellium_model(grid: Grid, operator = FermionOperator() spins = [None] if spinless else [0, 1] if potential and non_periodic and period_cutoff is None: - period_cutoff = grid.volume_scale()**(1.0 / grid.dimensions) + period_cutoff = grid.volume_scale() ** (1.0 / grid.dimensions) # Pre-Computations. position_vectors = {} @@ -245,8 +255,8 @@ def dual_basis_jellium_model(grid: Grid, differences = coordinates_b - coordinates_origin # Compute coefficients. - kinetic_coefficient = 0. - potential_coefficient = 0. + kinetic_coefficient = 0.0 + potential_coefficient = 0.0 for momenta_indices in grid.all_points_indices(): momenta = momentum_vectors[momenta_indices] momenta_squared = momenta_squared_dict[momenta_indices] @@ -255,23 +265,25 @@ def dual_basis_jellium_model(grid: Grid, cos_difference = numpy.cos(momenta.dot(differences)) if kinetic: - kinetic_coefficient += (cos_difference * momenta_squared / - (2. * float(n_points))) + kinetic_coefficient += cos_difference * momenta_squared / (2.0 * float(n_points)) if potential: - potential_coefficient += (position_prefactor * cos_difference / - momenta_squared) + potential_coefficient += position_prefactor * cos_difference / momenta_squared for grid_indices_shift in grid.all_points_indices(): # Loop over spins and identify interacting orbitals. orbital_a = {} orbital_b = {} - shifted_index_1 = tuple([ - (grid_origin[i] + grid_indices_shift[i]) % grid.length[i] - for i in range(grid.dimensions) - ]) - shifted_index_2 = tuple([ - (grid_indices_b[i] + grid_indices_shift[i]) % grid.length[i] - for i in range(grid.dimensions) - ]) + shifted_index_1 = tuple( + [ + (grid_origin[i] + grid_indices_shift[i]) % grid.length[i] + for i in range(grid.dimensions) + ] + ) + shifted_index_2 = tuple( + [ + (grid_indices_b[i] + grid_indices_shift[i]) % grid.length[i] + for i in range(grid.dimensions) + ] + ) for spin in spins: orbital_a[spin] = orbital_ids[shifted_index_1][spin] @@ -285,16 +297,20 @@ def dual_basis_jellium_model(grid: Grid, for sb in spins: if orbital_a[sa] == orbital_b[sb]: continue - operators = ((orbital_a[sa], 1), (orbital_a[sa], 0), - (orbital_b[sb], 1), (orbital_b[sb], 0)) - operator += FermionOperator(operators, - potential_coefficient) + operators = ( + (orbital_a[sa], 1), + (orbital_a[sa], 0), + (orbital_b[sb], 1), + (orbital_b[sb], 0), + ) + operator += FermionOperator(operators, potential_coefficient) # Include the Madelung constant if requested. if include_constant: # TODO: Check for other unit cell shapes - operator += (FermionOperator.identity() * - (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) + operator += FermionOperator.identity() * ( + 2.8372 / grid.volume_scale() ** (1.0 / grid.dimensions) + ) # Return. return operator @@ -313,11 +329,12 @@ def dual_basis_kinetic(grid: Grid, spinless: bool = False) -> FermionOperator: return dual_basis_jellium_model(grid, spinless, True, False) -def dual_basis_potential(grid: Grid, - spinless: bool = False, - non_periodic: bool = False, - period_cutoff: Optional[float] = None - ) -> FermionOperator: +def dual_basis_potential( + grid: Grid, + spinless: bool = False, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return the potential operator in the dual basis of arXiv:1706.00023 Args: @@ -330,17 +347,18 @@ def dual_basis_potential(grid: Grid, Returns: operator (FermionOperator) """ - return dual_basis_jellium_model(grid, spinless, False, True, False, - non_periodic, period_cutoff) - - -def jellium_model(grid: Grid, - spinless: bool = False, - plane_wave: bool = True, - include_constant: bool = False, - e_cutoff: float = None, - non_periodic: bool = False, - period_cutoff: Optional[float] = None) -> FermionOperator: + return dual_basis_jellium_model(grid, spinless, False, True, False, non_periodic, period_cutoff) + + +def jellium_model( + grid: Grid, + spinless: bool = False, + plane_wave: bool = True, + include_constant: bool = False, + e_cutoff: float = None, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return jellium Hamiltonian as FermionOperator class. Args: @@ -361,24 +379,23 @@ def jellium_model(grid: Grid, """ if plane_wave: hamiltonian = plane_wave_kinetic(grid, spinless, e_cutoff) - hamiltonian += plane_wave_potential(grid, spinless, e_cutoff, - non_periodic, period_cutoff) + hamiltonian += plane_wave_potential(grid, spinless, e_cutoff, non_periodic, period_cutoff) else: - hamiltonian = dual_basis_jellium_model(grid, spinless, True, True, - include_constant, non_periodic, - period_cutoff) + hamiltonian = dual_basis_jellium_model( + grid, spinless, True, True, include_constant, non_periodic, period_cutoff + ) # Include the Madelung constant if requested. if include_constant: # TODO: Check for other unit cell shapes - hamiltonian += (FermionOperator.identity() * - (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) + hamiltonian += FermionOperator.identity() * ( + 2.8372 / grid.volume_scale() ** (1.0 / grid.dimensions) + ) return hamiltonian -def jordan_wigner_dual_basis_jellium(grid: Grid, - spinless: bool = False, - include_constant: bool = False - ) -> QubitOperator: +def jordan_wigner_dual_basis_jellium( + grid: Grid, spinless: bool = False, include_constant: bool = False +) -> QubitOperator: """Return the jellium Hamiltonian as QubitOperator in the dual basis. Args: @@ -409,21 +426,20 @@ def jordan_wigner_dual_basis_jellium(grid: Grid, momenta_squared_dict[indices] = momenta.dot(momenta) # Compute the identity coefficient and the coefficient of local Z terms. - identity_coefficient = 0. - z_coefficient = 0. + identity_coefficient = 0.0 + z_coefficient = 0.0 for k_indices in grid.all_points_indices(): momenta = momentum_vectors[k_indices] momenta_squared = momenta.dot(momenta) if momenta_squared == 0: continue - identity_coefficient += momenta_squared / 2. - identity_coefficient -= (numpy.pi * float(n_orbitals) / - (momenta_squared * volume)) + identity_coefficient += momenta_squared / 2.0 + identity_coefficient -= numpy.pi * float(n_orbitals) / (momenta_squared * volume) z_coefficient += numpy.pi / (momenta_squared * volume) - z_coefficient -= momenta_squared / (4. * float(n_orbitals)) + z_coefficient -= momenta_squared / (4.0 * float(n_orbitals)) if spinless: - identity_coefficient /= 2. + identity_coefficient /= 2.0 # Add identity term. identity_term = QubitOperator((), identity_coefficient) @@ -436,7 +452,7 @@ def jordan_wigner_dual_basis_jellium(grid: Grid, # Add ZZ terms and XZX + YZY terms. zz_prefactor = numpy.pi / volume - xzx_yzy_prefactor = .25 / float(n_orbitals) + xzx_yzy_prefactor = 0.25 / float(n_orbitals) for p in range(n_qubits): index_p = grid.grid_indices(p, spinless) position_p = grid.position_vector(index_p) @@ -449,8 +465,8 @@ def jordan_wigner_dual_basis_jellium(grid: Grid, skip_xzx_yzy = not spinless and (p + q) % 2 # Loop through momenta. - zpzq_coefficient = 0. - term_coefficient = 0. + zpzq_coefficient = 0.0 + term_coefficient = 0.0 for k_indices in grid.all_points_indices(): momenta = momentum_vectors[k_indices] momenta_squared = momenta_squared_dict[k_indices] @@ -459,13 +475,11 @@ def jordan_wigner_dual_basis_jellium(grid: Grid, cos_difference = numpy.cos(momenta.dot(difference)) - zpzq_coefficient += (zz_prefactor * cos_difference / - momenta_squared) + zpzq_coefficient += zz_prefactor * cos_difference / momenta_squared if skip_xzx_yzy: continue - term_coefficient += (xzx_yzy_prefactor * cos_difference * - momenta_squared) + term_coefficient += xzx_yzy_prefactor * cos_difference * momenta_squared # Add ZZ term. qubit_term = QubitOperator(((p, 'Z'), (q, 'Z')), zpzq_coefficient) @@ -483,19 +497,19 @@ def jordan_wigner_dual_basis_jellium(grid: Grid, # Include the Madelung constant if requested. if include_constant: # TODO Generalize to other cells - hamiltonian += (QubitOperator( - (),) * (2.8372 / grid.volume_scale()**(1. / grid.dimensions))) + hamiltonian += QubitOperator(()) * (2.8372 / grid.volume_scale() ** (1.0 / grid.dimensions)) # Return Hamiltonian. return hamiltonian def hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dimension: int, - grid_length: int, - wigner_seitz_radius: float, - filling_fraction: float = 0.5, - spinless: bool = True) -> Grid: + dimension: int, + grid_length: int, + wigner_seitz_radius: float, + filling_fraction: float = 0.5, + spinless: bool = True, +) -> Grid: """Return a Grid with the same number of orbitals along each dimension with the specified Wigner-Seitz radius. @@ -519,11 +533,10 @@ def hypercube_grid_with_given_wigner_seitz_radius_and_filling( if not n_particles: raise ValueError( - "filling_fraction too low for number of orbitals specified by " - "other parameters.") + "filling_fraction too low for number of orbitals specified by " "other parameters." + ) # Compute appropriate length scale. - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, - dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) return Grid(dimension, grid_length, length_scale) diff --git a/src/openfermion/hamiltonians/jellium_hf_state.py b/src/openfermion/hamiltonians/jellium_hf_state.py index c566ce419..8a235421e 100644 --- a/src/openfermion/hamiltonians/jellium_hf_state.py +++ b/src/openfermion/hamiltonians/jellium_hf_state.py @@ -30,11 +30,9 @@ def lowest_single_particle_energy_states(hamiltonian, n_states): n_single_particle_states = count_qubits(hamiltonian) # Compute the energies for each of the single-particle states. - single_particle_energies = numpy.zeros(n_single_particle_states, - dtype=float) + single_particle_energies = numpy.zeros(n_single_particle_states, dtype=float) for i in range(n_single_particle_states): - single_particle_energies[i] = hamiltonian.terms.get(((i, 1), (i, 0)), - 0.0) + single_particle_energies[i] = hamiltonian.terms.get(((i, 1), (i, 0)), 0.0) # Find the n_states lowest states. occupied_states = single_particle_energies.argsort()[:n_states] @@ -42,10 +40,7 @@ def lowest_single_particle_energy_states(hamiltonian, n_states): return list(occupied_states) -def hartree_fock_state_jellium(grid, - n_electrons, - spinless=True, - plane_wave=False): +def hartree_fock_state_jellium(grid, n_electrons, spinless=True, plane_wave=False): """Give the Hartree-Fock state of jellium. Args: @@ -70,15 +65,13 @@ def hartree_fock_state_jellium(grid, # The number of occupied single-particle states is the number of electrons. # Those states with the lowest single-particle energies are occupied first. - occupied_states = lowest_single_particle_energy_states( - hamiltonian, n_electrons) + occupied_states = lowest_single_particle_energy_states(hamiltonian, n_electrons) occupied_states = numpy.array(occupied_states) if plane_wave: # In the plane wave basis the HF state is a single determinant. hartree_fock_state_index = numpy.sum(2**occupied_states) - hartree_fock_state = numpy.zeros(2**count_qubits(hamiltonian), - dtype=complex) + hartree_fock_state = numpy.zeros(2 ** count_qubits(hamiltonian), dtype=complex) hartree_fock_state[hartree_fock_state_index] = 1.0 else: @@ -86,22 +79,21 @@ def hartree_fock_state_jellium(grid, # to the dual basis state, then use that to get the dual basis state. hartree_fock_state_creation_operator = FermionOperator.identity() for state in occupied_states[::-1]: - hartree_fock_state_creation_operator *= (FermionOperator( - ((int(state), 1),))) + hartree_fock_state_creation_operator *= FermionOperator(((int(state), 1),)) dual_basis_hf_creation_operator = inverse_fourier_transform( - hartree_fock_state_creation_operator, grid, spinless) + hartree_fock_state_creation_operator, grid, spinless + ) dual_basis_hf_creation = normal_ordered(dual_basis_hf_creation_operator) # Initialize the HF state. - hartree_fock_state = numpy.zeros(2**count_qubits(hamiltonian), - dtype=complex) + hartree_fock_state = numpy.zeros(2 ** count_qubits(hamiltonian), dtype=complex) # Populate the elements of the HF state in the dual basis. for term in dual_basis_hf_creation.terms: index = 0 for operator in term: - index += 2**operator[0] + index += 2 ** operator[0] hartree_fock_state[index] = dual_basis_hf_creation.terms[term] return hartree_fock_state diff --git a/src/openfermion/hamiltonians/jellium_hf_state_test.py b/src/openfermion/hamiltonians/jellium_hf_state_test.py index 149c79724..5432c68a6 100644 --- a/src/openfermion/hamiltonians/jellium_hf_state_test.py +++ b/src/openfermion/hamiltonians/jellium_hf_state_test.py @@ -5,14 +5,17 @@ import numpy from openfermion.hamiltonians import jellium_model, wigner_seitz_length_scale -from openfermion.linalg import (get_sparse_operator, get_ground_state, - expectation, jw_number_restrict_operator) +from openfermion.linalg import ( + get_sparse_operator, + get_ground_state, + expectation, + jw_number_restrict_operator, +) from openfermion.utils.grid import Grid from openfermion.hamiltonians.jellium_hf_state import hartree_fock_state_jellium class JelliumHartreeFockStateTest(unittest.TestCase): - def test_hf_state_energy_close_to_ground_energy_at_high_density(self): grid_length = 8 dimension = 1 @@ -26,13 +29,9 @@ def test_hf_state_energy_close_to_ground_energy_at_high_density(self): hamiltonian = jellium_model(grid, spinless) hamiltonian_sparse = get_sparse_operator(hamiltonian) - hf_state = hartree_fock_state_jellium(grid, - n_particles, - spinless, - plane_wave=True) + hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) - restricted_hamiltonian = jw_number_restrict_operator( - hamiltonian_sparse, n_particles) + restricted_hamiltonian = jw_number_restrict_operator(hamiltonian_sparse, n_particles) E_g = get_ground_state(restricted_hamiltonian)[0] E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) @@ -50,8 +49,7 @@ def test_hf_state_energy_same_in_plane_wave_and_dual_basis(self): n_orbitals *= 2 n_particles = n_orbitals // 2 - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, dimension) grid = Grid(dimension, grid_length, length_scale) hamiltonian = jellium_model(grid, spinless) @@ -61,14 +59,8 @@ def test_hf_state_energy_same_in_plane_wave_and_dual_basis(self): hamiltonian_sparse = get_sparse_operator(hamiltonian) hamiltonian_dual_sparse = get_sparse_operator(hamiltonian_dual_basis) - hf_state = hartree_fock_state_jellium(grid, - n_particles, - spinless, - plane_wave=True) - hf_state_dual = hartree_fock_state_jellium(grid, - n_particles, - spinless, - plane_wave=False) + hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) + hf_state_dual = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=False) E_HF_plane_wave = expectation(hamiltonian_sparse, hf_state) E_HF_dual = expectation(hamiltonian_dual_sparse, hf_state_dual) @@ -86,18 +78,15 @@ def test_hf_state_plane_wave_basis_lowest_single_determinant_state(self): hamiltonian = jellium_model(grid, spinless) hamiltonian_sparse = get_sparse_operator(hamiltonian) - hf_state = hartree_fock_state_jellium(grid, - n_particles, - spinless, - plane_wave=True) + hf_state = hartree_fock_state_jellium(grid, n_particles, spinless, plane_wave=True) HF_energy = expectation(hamiltonian_sparse, hf_state) - for occupied_orbitals in permutations([1] * n_particles + [0] * - (grid_length - n_particles)): - state_index = numpy.sum(2**numpy.array(occupied_orbitals)) + for occupied_orbitals in permutations( + [1] * n_particles + [0] * (grid_length - n_particles) + ): + state_index = numpy.sum(2 ** numpy.array(occupied_orbitals)) HF_competitor = numpy.zeros(2**grid_length) HF_competitor[state_index] = 1.0 - self.assertLessEqual(HF_energy, - expectation(hamiltonian_sparse, HF_competitor)) + self.assertLessEqual(HF_energy, expectation(hamiltonian_sparse, HF_competitor)) diff --git a/src/openfermion/hamiltonians/jellium_test.py b/src/openfermion/hamiltonians/jellium_test.py index 94667c95e..56d6078d8 100644 --- a/src/openfermion/hamiltonians/jellium_test.py +++ b/src/openfermion/hamiltonians/jellium_test.py @@ -32,40 +32,33 @@ class WignerSeitzRadiusTest(unittest.TestCase): - def test_wigner_seitz_radius_1d(self): wigner_seitz_radius = 3.17 n_particles = 20 - one_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, - 1) - self.assertAlmostEqual(one_d_test, - n_particles * 2. * wigner_seitz_radius) + one_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, 1) + self.assertAlmostEqual(one_d_test, n_particles * 2.0 * wigner_seitz_radius) def test_wigner_seitz_radius_2d(self): wigner_seitz_radius = 0.5 n_particles = 3 - two_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, - 2)**2. - self.assertAlmostEqual(two_d_test, - n_particles * numpy.pi * wigner_seitz_radius**2.) + two_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, 2) ** 2.0 + self.assertAlmostEqual(two_d_test, n_particles * numpy.pi * wigner_seitz_radius**2.0) def test_wigner_seitz_radius_3d(self): wigner_seitz_radius = 4.6 n_particles = 37 - three_d_test = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles, 3)**3. + three_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, 3) ** 3.0 self.assertAlmostEqual( - three_d_test, - n_particles * (4. * numpy.pi / 3. * wigner_seitz_radius**3.)) + three_d_test, n_particles * (4.0 * numpy.pi / 3.0 * wigner_seitz_radius**3.0) + ) def test_wigner_seitz_radius_6d(self): - wigner_seitz_radius = 5. + wigner_seitz_radius = 5.0 n_particles = 42 - six_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, - 6)**6 + six_d_test = wigner_seitz_length_scale(wigner_seitz_radius, n_particles, 6) ** 6 self.assertAlmostEqual( - six_d_test, - n_particles * (numpy.pi**3 / 6 * wigner_seitz_radius**6)) + six_d_test, n_particles * (numpy.pi**3 / 6 * wigner_seitz_radius**6) + ) def test_wigner_seitz_radius_bad_dimension_not_integer(self): with self.assertRaises(ValueError): @@ -77,14 +70,14 @@ def test_wigner_seitz_radius_bad_dimension_not_positive(self): class HypercubeGridTest(unittest.TestCase): - def test_1d_generation(self): dim = 1 orbitals = 4 - wigner_seitz_radius = 7. + wigner_seitz_radius = 7.0 grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dim, orbitals, wigner_seitz_radius) + dim, orbitals, wigner_seitz_radius + ) self.assertEqual(grid.dimensions, 1) self.assertEqual(grid.length, (4,)) self.assertEqual(grid.volume_scale(), orbitals * wigner_seitz_radius) @@ -92,56 +85,60 @@ def test_1d_generation(self): def test_generation_away_from_half_filling(self): dim = 1 orbitals = 100 - wigner_seitz_radius = 7. + wigner_seitz_radius = 7.0 filling = 0.2 grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dim, orbitals, wigner_seitz_radius, filling_fraction=filling) + dim, orbitals, wigner_seitz_radius, filling_fraction=filling + ) self.assertEqual(grid.dimensions, 1) self.assertEqual(grid.length, (100,)) - self.assertAlmostEqual(grid.volume_scale(), - orbitals * wigner_seitz_radius / 2.5) + self.assertAlmostEqual(grid.volume_scale(), orbitals * wigner_seitz_radius / 2.5) def test_generation_with_spin(self): dim = 2 orbitals = 4 - wigner_seitz_radius = 10. + wigner_seitz_radius = 10.0 spinless = False grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - dim, orbitals, wigner_seitz_radius, spinless=spinless) + dim, orbitals, wigner_seitz_radius, spinless=spinless + ) self.assertEqual(grid.dimensions, 2) self.assertEqual(grid.length, (4, 4)) - self.assertAlmostEqual(grid.volume_scale(), numpy.pi * 16 * 100.) + self.assertAlmostEqual(grid.volume_scale(), numpy.pi * 16 * 100.0) def test_3d_generation_with_rounding(self): filling = 0.42 grid = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 3, 5, 1., filling_fraction=filling) + 3, 5, 1.0, filling_fraction=filling + ) self.assertEqual(grid.dimensions, 3) self.assertEqual(grid.length, (5, 5, 5)) # There are floor(125 * .42) = 52 particles. # The volume scale should be 4/3 pi r^3 * the "true" filling fraction. - self.assertAlmostEqual(grid.volume_scale(), - (4. / 3.) * numpy.pi * (5.**3) * (52. / 125)) + self.assertAlmostEqual( + grid.volume_scale(), (4.0 / 3.0) * numpy.pi * (5.0**3) * (52.0 / 125) + ) def test_raise_ValueError_filling_fraction_too_low(self): with self.assertRaises(ValueError): _ = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 3, 5, wigner_seitz_radius=10., filling_fraction=0.005) + 3, 5, wigner_seitz_radius=10.0, filling_fraction=0.005 + ) def test_raise_ValueError_filling_fraction_too_high(self): with self.assertRaises(ValueError): _ = hypercube_grid_with_given_wigner_seitz_radius_and_filling( - 1, 4, wigner_seitz_radius=1., filling_fraction=2.) + 1, 4, wigner_seitz_radius=1.0, filling_fraction=2.0 + ) class JelliumTest(unittest.TestCase): - def test_kinetic_integration(self): # Compute kinetic energy operator in both momentum and position space. - grid = Grid(dimensions=2, length=2, scale=3.) + grid = Grid(dimensions=2, length=2, scale=3.0) spinless = False momentum_kinetic = plane_wave_kinetic(grid, spinless) position_kinetic = dual_basis_kinetic(grid, spinless) @@ -175,25 +172,22 @@ def test_kinetic_integration(self): position_spectrum = eigenspectrum(jw_position, 2 * length) # Confirm spectra are the same. - difference = numpy.amax( - numpy.absolute(momentum_spectrum - position_spectrum)) - self.assertAlmostEqual(difference, 0.) + difference = numpy.amax(numpy.absolute(momentum_spectrum - position_spectrum)) + self.assertAlmostEqual(difference, 0.0) def test_potential_integration(self): # Compute potential energy operator in momentum and position space. for length in [2, 3]: - grid = Grid(dimensions=2, length=length, scale=2.) + grid = Grid(dimensions=2, length=length, scale=2.0) spinless = True momentum_potential = plane_wave_potential(grid, spinless) position_potential = dual_basis_potential(grid, spinless) # Confirm they are Hermitian - momentum_potential_operator = ( - get_sparse_operator(momentum_potential)) + momentum_potential_operator = get_sparse_operator(momentum_potential) self.assertTrue(is_hermitian(momentum_potential_operator)) - position_potential_operator = ( - get_sparse_operator(position_potential)) + position_potential_operator = get_sparse_operator(position_potential) self.assertTrue(is_hermitian(position_potential_operator)) # Diagonalize and confirm the same energy. @@ -203,9 +197,8 @@ def test_potential_integration(self): position_spectrum = eigenspectrum(jw_position) # Confirm spectra are the same. - difference = numpy.amax( - numpy.absolute(momentum_spectrum - position_spectrum)) - self.assertAlmostEqual(difference, 0.) + difference = numpy.amax(numpy.absolute(momentum_spectrum - position_spectrum)) + self.assertAlmostEqual(difference, 0.0) def test_model_integration(self): # Compute Hamiltonian in both momentum and position space. @@ -216,12 +209,10 @@ def test_model_integration(self): position_hamiltonian = jellium_model(grid, spinless, False) # Confirm they are Hermitian - momentum_hamiltonian_operator = ( - get_sparse_operator(momentum_hamiltonian)) + momentum_hamiltonian_operator = get_sparse_operator(momentum_hamiltonian) self.assertTrue(is_hermitian(momentum_hamiltonian_operator)) - position_hamiltonian_operator = ( - get_sparse_operator(position_hamiltonian)) + position_hamiltonian_operator = get_sparse_operator(position_hamiltonian) self.assertTrue(is_hermitian(position_hamiltonian_operator)) # Diagonalize and confirm the same energy. @@ -231,9 +222,8 @@ def test_model_integration(self): position_spectrum = eigenspectrum(jw_position) # Confirm spectra are the same. - difference = numpy.amax( - numpy.absolute(momentum_spectrum - position_spectrum)) - self.assertAlmostEqual(difference, 0.) + difference = numpy.amax(numpy.absolute(momentum_spectrum - position_spectrum)) + self.assertAlmostEqual(difference, 0.0) def test_model_integration_with_constant(self): # Compute Hamiltonian in both momentum and position space. @@ -244,19 +234,14 @@ def test_model_integration_with_constant(self): # Include Madelung constant in the momentum but not the position # Hamiltonian. - momentum_hamiltonian = jellium_model(grid, - spinless, - True, - include_constant=True) + momentum_hamiltonian = jellium_model(grid, spinless, True, include_constant=True) position_hamiltonian = jellium_model(grid, spinless, False) # Confirm they are Hermitian - momentum_hamiltonian_operator = ( - get_sparse_operator(momentum_hamiltonian)) + momentum_hamiltonian_operator = get_sparse_operator(momentum_hamiltonian) self.assertTrue(is_hermitian(momentum_hamiltonian_operator)) - position_hamiltonian_operator = ( - get_sparse_operator(position_hamiltonian)) + position_hamiltonian_operator = get_sparse_operator(position_hamiltonian) self.assertTrue(is_hermitian(position_hamiltonian_operator)) # Diagonalize and confirm the same energy. @@ -273,10 +258,10 @@ def test_model_integration_with_constant(self): def test_coefficients(self): # Test that the coefficients post-JW transform are as claimed in paper. - grid = Grid(dimensions=2, length=3, scale=2.) + grid = Grid(dimensions=2, length=3, scale=2.0) spinless = 1 n_orbitals = grid.num_points - n_qubits = (2**(1 - spinless)) * n_orbitals + n_qubits = (2 ** (1 - spinless)) * n_orbitals volume = grid.volume_scale() # Kinetic operator. @@ -292,21 +277,22 @@ def test_coefficients(self): kinetic_coefficient = qubit_kinetic.terms[identity] potential_coefficient = qubit_potential.terms[identity] - paper_kinetic_coefficient = 0. - paper_potential_coefficient = 0. + paper_kinetic_coefficient = 0.0 + paper_potential_coefficient = 0.0 for indices in grid.all_points_indices(): momenta = grid.momentum_vector(indices) - paper_kinetic_coefficient += float(n_qubits) * momenta.dot( - momenta) / float(4. * n_orbitals) + paper_kinetic_coefficient += ( + float(n_qubits) * momenta.dot(momenta) / float(4.0 * n_orbitals) + ) if momenta.any(): - potential_contribution = -numpy.pi * float(n_qubits) / float( - 2. * momenta.dot(momenta) * volume) + potential_contribution = ( + -numpy.pi * float(n_qubits) / float(2.0 * momenta.dot(momenta) * volume) + ) paper_potential_coefficient += potential_contribution self.assertAlmostEqual(kinetic_coefficient, paper_kinetic_coefficient) - self.assertAlmostEqual(potential_coefficient, - paper_potential_coefficient) + self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient) # Check Zp. for p in range(n_qubits): @@ -314,22 +300,18 @@ def test_coefficients(self): kinetic_coefficient = qubit_kinetic.terms[zp] potential_coefficient = qubit_potential.terms[zp] - paper_kinetic_coefficient = 0. - paper_potential_coefficient = 0. + paper_kinetic_coefficient = 0.0 + paper_potential_coefficient = 0.0 for indices in grid.all_points_indices(): momenta = grid.momentum_vector(indices) - paper_kinetic_coefficient -= momenta.dot(momenta) / float( - 4. * n_orbitals) + paper_kinetic_coefficient -= momenta.dot(momenta) / float(4.0 * n_orbitals) if momenta.any(): - potential_contribution = numpy.pi / float( - momenta.dot(momenta) * volume) + potential_contribution = numpy.pi / float(momenta.dot(momenta) * volume) paper_potential_coefficient += potential_contribution - self.assertAlmostEqual(kinetic_coefficient, - paper_kinetic_coefficient) - self.assertAlmostEqual(potential_coefficient, - paper_potential_coefficient) + self.assertAlmostEqual(kinetic_coefficient, paper_kinetic_coefficient) + self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient) # Check Zp Zq. if spinless: @@ -337,10 +319,9 @@ def test_coefficients(self): for indices_a in grid.all_points_indices(): for indices_b in grid.all_points_indices(): - - potential_coefficient = 0. - paper_kinetic_coefficient = 0. - paper_potential_coefficient = 0. + potential_coefficient = 0.0 + paper_kinetic_coefficient = 0.0 + paper_potential_coefficient = 0.0 position_a = grid.position_vector(indices_a) position_b = grid.position_vector(indices_b) @@ -348,7 +329,6 @@ def test_coefficients(self): for spin_a in spins: for spin_b in spins: - p = grid.orbital_id(indices_a, spin_a) q = grid.orbital_id(indices_b, spin_b) @@ -363,24 +343,22 @@ def test_coefficients(self): momenta = grid.momentum_vector(indices_c) if momenta.any(): - potential_contribution = numpy.pi * numpy.cos( - differences.dot(momenta)) / float( - momenta.dot(momenta) * volume) - paper_potential_coefficient += ( - potential_contribution) + potential_contribution = ( + numpy.pi + * numpy.cos(differences.dot(momenta)) + / float(momenta.dot(momenta) * volume) + ) + paper_potential_coefficient += potential_contribution - self.assertAlmostEqual(potential_coefficient, - paper_potential_coefficient) + self.assertAlmostEqual(potential_coefficient, paper_potential_coefficient) def test_jordan_wigner_dual_basis_jellium(self): # Parameters. - grid = Grid(dimensions=2, length=3, scale=1.) + grid = Grid(dimensions=2, length=3, scale=1.0) spinless = True # Compute fermionic Hamiltonian. Include then subtract constant. - fermion_hamiltonian = dual_basis_jellium_model(grid, - spinless, - include_constant=True) + fermion_hamiltonian = dual_basis_jellium_model(grid, spinless, include_constant=True) qubit_hamiltonian = jordan_wigner(fermion_hamiltonian) qubit_hamiltonian -= QubitOperator((), 2.8372) @@ -393,10 +371,9 @@ def test_jordan_wigner_dual_basis_jellium(self): # Check number of terms. n_qubits = count_qubits(qubit_hamiltonian) if spinless: - paper_n_terms = 1 - .5 * n_qubits + 1.5 * (n_qubits**2) + paper_n_terms = 1 - 0.5 * n_qubits + 1.5 * (n_qubits**2) - num_nonzeros = sum( - 1 for coeff in qubit_hamiltonian.terms.values() if coeff != 0.0) + num_nonzeros = sum(1 for coeff in qubit_hamiltonian.terms.values() if coeff != 0.0) self.assertTrue(num_nonzeros <= paper_n_terms) def test_jordan_wigner_dual_basis_jellium_constant_shift(self): @@ -405,9 +382,11 @@ def test_jordan_wigner_dual_basis_jellium_constant_shift(self): spinless = True hamiltonian_without_constant = jordan_wigner_dual_basis_jellium( - grid, spinless, include_constant=False) + grid, spinless, include_constant=False + ) hamiltonian_with_constant = jordan_wigner_dual_basis_jellium( - grid, spinless, include_constant=True) + grid, spinless, include_constant=True + ) difference = hamiltonian_with_constant - hamiltonian_without_constant expected = QubitOperator('') * (2.8372 / length_scale) @@ -428,7 +407,7 @@ def test_plane_wave_energy_cutoff(self): spectrum_2 = eigenspectrum(jw_2) max_diff = numpy.amax(numpy.absolute(spectrum_1 - spectrum_2)) - self.assertGreater(max_diff, 0.) + self.assertGreater(max_diff, 0.0) def test_plane_wave_period_cutoff(self): # TODO: After figuring out the correct formula for period cutoff for @@ -437,19 +416,18 @@ def test_plane_wave_period_cutoff(self): grid = Grid(dimensions=2, length=2, scale=1.0) spinless = True - period_cutoff = 0. + period_cutoff = 0.0 hamiltonian_1 = FermionOperator() jw_1 = jordan_wigner(hamiltonian_1) spectrum_1 = eigenspectrum(jw_1) - hamiltonian_2 = jellium_model(grid, spinless, True, False, None, True, - period_cutoff) + hamiltonian_2 = jellium_model(grid, spinless, True, False, None, True, period_cutoff) jw_2 = jordan_wigner(hamiltonian_2) spectrum_2 = eigenspectrum(jw_2) max_diff = numpy.amax(numpy.absolute(spectrum_1 - spectrum_2)) - self.assertGreater(max_diff, 0.) + self.assertGreater(max_diff, 0.0) # TODO: This is only for code coverage. Remove after having real # integration test. diff --git a/src/openfermion/hamiltonians/mean_field_dwave.py b/src/openfermion/hamiltonians/mean_field_dwave.py index 49bfecd82..d65cfe41e 100644 --- a/src/openfermion/hamiltonians/mean_field_dwave.py +++ b/src/openfermion/hamiltonians/mean_field_dwave.py @@ -20,12 +20,14 @@ import openfermion.utils as op_utils -def mean_field_dwave(x_dimension: int, - y_dimension: int, - tunneling: float, - sc_gap: float, - chemical_potential: Optional[float] = 0., - periodic: bool = True) -> FermionOperator: +def mean_field_dwave( + x_dimension: int, + y_dimension: int, + tunneling: float, + sc_gap: float, + chemical_potential: Optional[float] = 0.0, + periodic: bool = True, +) -> FermionOperator: r"""Return symbolic representation of a BCS mean-field d-wave Hamiltonian. The Hamiltonians of this model live on a grid of dimensions @@ -85,12 +87,12 @@ def mean_field_dwave(x_dimension: int, # Loop through sites and add terms. for site in range(n_sites): # Add chemical potential - mean_field_dwave_model += number_operator(n_spin_orbitals, - up_index(site), - -chemical_potential) - mean_field_dwave_model += number_operator(n_spin_orbitals, - down_index(site), - -chemical_potential) + mean_field_dwave_model += number_operator( + n_spin_orbitals, up_index(site), -chemical_potential + ) + mean_field_dwave_model += number_operator( + n_spin_orbitals, down_index(site), -chemical_potential + ) # Index coupled orbitals. right_neighbor = site + 1 @@ -108,23 +110,20 @@ def mean_field_dwave(x_dimension: int, operators = ((up_index(site), 1), (up_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term - mean_field_dwave_model += op_utils.hermitian_conjugated( - hopping_term) + mean_field_dwave_model += op_utils.hermitian_conjugated(hopping_term) # Add spin-down hopping term operators = ((down_index(site), 1), (down_index(right_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term - mean_field_dwave_model += op_utils.hermitian_conjugated( - hopping_term) + mean_field_dwave_model += op_utils.hermitian_conjugated(hopping_term) # Add pairing term operators = ((up_index(site), 1), (down_index(right_neighbor), 1)) - pairing_term = FermionOperator(operators, sc_gap / 2.) + pairing_term = FermionOperator(operators, sc_gap / 2.0) operators = ((down_index(site), 1), (up_index(right_neighbor), 1)) - pairing_term += FermionOperator(operators, -sc_gap / 2.) + pairing_term += FermionOperator(operators, -sc_gap / 2.0) mean_field_dwave_model -= pairing_term - mean_field_dwave_model -= op_utils.hermitian_conjugated( - pairing_term) + mean_field_dwave_model -= op_utils.hermitian_conjugated(pairing_term) # Add transition to neighbor below. if site + x_dimension + 1 <= n_sites or (periodic and y_dimension > 2): @@ -132,23 +131,19 @@ def mean_field_dwave(x_dimension: int, operators = ((up_index(site), 1), (up_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term - mean_field_dwave_model += op_utils.hermitian_conjugated( - hopping_term) + mean_field_dwave_model += op_utils.hermitian_conjugated(hopping_term) # Add spin-down hopping term - operators = ((down_index(site), 1), (down_index(bottom_neighbor), - 0)) + operators = ((down_index(site), 1), (down_index(bottom_neighbor), 0)) hopping_term = FermionOperator(operators, -tunneling) mean_field_dwave_model += hopping_term - mean_field_dwave_model += op_utils.hermitian_conjugated( - hopping_term) + mean_field_dwave_model += op_utils.hermitian_conjugated(hopping_term) # Add pairing term operators = ((up_index(site), 1), (down_index(bottom_neighbor), 1)) - pairing_term = FermionOperator(operators, -sc_gap / 2.) + pairing_term = FermionOperator(operators, -sc_gap / 2.0) operators = ((down_index(site), 1), (up_index(bottom_neighbor), 1)) - pairing_term += FermionOperator(operators, sc_gap / 2.) + pairing_term += FermionOperator(operators, sc_gap / 2.0) mean_field_dwave_model -= pairing_term - mean_field_dwave_model -= op_utils.hermitian_conjugated( - pairing_term) + mean_field_dwave_model -= op_utils.hermitian_conjugated(pairing_term) # Return. return mean_field_dwave_model diff --git a/src/openfermion/hamiltonians/mean_field_dwave_test.py b/src/openfermion/hamiltonians/mean_field_dwave_test.py index 0a3703ed5..3a6c95aa3 100644 --- a/src/openfermion/hamiltonians/mean_field_dwave_test.py +++ b/src/openfermion/hamiltonians/mean_field_dwave_test.py @@ -17,114 +17,77 @@ class MeanfieldDwaveTest(unittest.TestCase): - def setUp(self): - self.tunneling = 2. - self.sc_gap = 2. - self.chemical_potential = 2. + self.tunneling = 2.0 + self.sc_gap = 2.0 + self.chemical_potential = 2.0 def test_two_by_two(self): # Test the 2 by 2 model. # Initialize the Hamiltonian. - mean_field_dwave_model = mean_field_dwave(2, 2, self.tunneling, - self.sc_gap, - self.chemical_potential) + mean_field_dwave_model = mean_field_dwave( + 2, 2, self.tunneling, self.sc_gap, self.chemical_potential + ) # Check on-site terms. for site in range(8): self.assertAlmostEqual( - mean_field_dwave_model.terms[((site, 1), (site, 0))], - -self.chemical_potential) + mean_field_dwave_model.terms[((site, 1), (site, 0))], -self.chemical_potential + ) # Check up right/left hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (2, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (0, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (6, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 1), (4, 0))], - -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (2, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (0, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (6, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 1), (4, 0))], -self.tunneling) # Check up top/bottom hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (4, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (0, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (6, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 1), (2, 0))], - -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (4, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (0, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (6, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 1), (2, 0))], -self.tunneling) # Check down right/left hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (3, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (1, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (7, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 1), (5, 0))], - -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (3, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (1, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (7, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 1), (5, 0))], -self.tunneling) # Check down top/bottom hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (5, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (1, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (7, 0))], - -self.tunneling) - self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 1), (3, 0))], - -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (5, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (1, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (7, 0))], -self.tunneling) + self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 1), (3, 0))], -self.tunneling) # Check right/left pairing terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (3, 1))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (2, 1))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 0), (0, 0))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 0), (1, 0))], - self.sc_gap / 2) - - self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (7, 1))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (6, 1))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 0), (4, 0))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 0), (5, 0))], - self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (3, 1))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (2, 1))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 0), (0, 0))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 0), (1, 0))], self.sc_gap / 2) + + self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (7, 1))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 1), (6, 1))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 0), (4, 0))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 0), (5, 0))], self.sc_gap / 2) # Check top/bottom pairing terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (5, 1))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (4, 1))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 0), (0, 0))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 0), (1, 0))], - -self.sc_gap / 2) - - self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (7, 1))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (6, 1))], - -self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 0), (2, 0))], - self.sc_gap / 2) - self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 0), (3, 0))], - -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((0, 1), (5, 1))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((1, 1), (4, 1))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((5, 0), (0, 0))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 0), (1, 0))], -self.sc_gap / 2) + + self.assertAlmostEqual(mean_field_dwave_model.terms[((2, 1), (7, 1))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((3, 1), (6, 1))], -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((7, 0), (2, 0))], self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((6, 0), (3, 0))], -self.sc_gap / 2) def test_two_by_three_spinless_periodic_rudimentary(self): - mean_field_dwave_model = mean_field_dwave(2, 3, self.tunneling, - self.sc_gap) + mean_field_dwave_model = mean_field_dwave(2, 3, self.tunneling, self.sc_gap) # Check top/bottom hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((8, 1), (1, 1))], - self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((8, 1), (1, 1))], self.sc_gap / 2) def test_three_by_two_spinless_periodic_rudimentary(self): - mean_field_dwave_model = mean_field_dwave(3, 2, self.tunneling, - self.sc_gap) + mean_field_dwave_model = mean_field_dwave(3, 2, self.tunneling, self.sc_gap) # Check right/left hopping terms. - self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (1, 1))], - -self.sc_gap / 2) + self.assertAlmostEqual(mean_field_dwave_model.terms[((4, 1), (1, 1))], -self.sc_gap / 2) diff --git a/src/openfermion/hamiltonians/plane_wave_hamiltonian.py b/src/openfermion/hamiltonians/plane_wave_hamiltonian.py index c15a9873e..8cdf13bd2 100644 --- a/src/openfermion/hamiltonians/plane_wave_hamiltonian.py +++ b/src/openfermion/hamiltonians/plane_wave_hamiltonian.py @@ -14,8 +14,7 @@ import numpy as np -from openfermion.hamiltonians.jellium import (jellium_model, - jordan_wigner_dual_basis_jellium) +from openfermion.hamiltonians.jellium import jellium_model, jordan_wigner_dual_basis_jellium from openfermion.ops.operators import FermionOperator, QubitOperator from openfermion.transforms.repconversions import inverse_fourier_transform from openfermion.utils.grid import Grid @@ -24,12 +23,12 @@ def dual_basis_external_potential( - grid: Grid, - geometry: List[Tuple[str, Tuple[Union[int, float], Union[int, float], - Union[int, float]]]], - spinless: bool, - non_periodic: bool = False, - period_cutoff: Optional[float] = None) -> FermionOperator: + grid: Grid, + geometry: List[Tuple[str, Tuple[Union[int, float], Union[int, float], Union[int, float]]]], + spinless: bool, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return the external potential in the dual basis of arXiv:1706.00023. The external potential resulting from electrons interacting with nuclei @@ -54,7 +53,7 @@ def dual_basis_external_potential( """ prefactor = -4.0 * np.pi / grid.volume_scale() if non_periodic and period_cutoff is None: - period_cutoff = grid.volume_scale()**(1. / grid.dimensions) + period_cutoff = grid.volume_scale() ** (1.0 / grid.dimensions) operator = None if spinless: spins = [None] @@ -71,9 +70,12 @@ def dual_basis_external_potential( continue cos_index = momenta.dot(coordinate_j - coordinate_p) - coefficient = (prefactor / momenta_squared * - md.periodic_hash_table[nuclear_term[0]] * - np.cos(cos_index)) + coefficient = ( + prefactor + / momenta_squared + * md.periodic_hash_table[nuclear_term[0]] + * np.cos(cos_index) + ) for spin_p in spins: orbital_p = grid.orbital_id(pos_indices, spin_p) @@ -86,13 +88,13 @@ def dual_basis_external_potential( def plane_wave_external_potential( - grid: Grid, - geometry: List[Tuple[str, Tuple[Union[int, float], Union[int, float], - Union[int, float]]]], - spinless: bool, - e_cutoff: Optional[float] = None, - non_periodic: bool = False, - period_cutoff: Optional[float] = None) -> FermionOperator: + grid: Grid, + geometry: List[Tuple[str, Tuple[Union[int, float], Union[int, float], Union[int, float]]]], + spinless: bool, + e_cutoff: Optional[float] = None, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Return the external potential operator in plane wave basis. The external potential resulting from electrons interacting with nuclei. @@ -115,24 +117,26 @@ def plane_wave_external_potential( Returns: FermionOperator: The plane wave operator. """ - dual_basis_operator = dual_basis_external_potential(grid, geometry, - spinless, non_periodic, - period_cutoff) + dual_basis_operator = dual_basis_external_potential( + grid, geometry, spinless, non_periodic, period_cutoff + ) operator = inverse_fourier_transform(dual_basis_operator, grid, spinless) return operator def plane_wave_hamiltonian( - grid: Grid, - geometry: Optional[List[Tuple[str, Tuple[ - Union[int, float], Union[int, float], Union[int, float]]]]] = None, - spinless: bool = False, - plane_wave: bool = True, - include_constant: bool = False, - e_cutoff: Optional[float] = None, - non_periodic: bool = False, - period_cutoff: Optional[float] = None) -> FermionOperator: + grid: Grid, + geometry: Optional[ + List[Tuple[str, Tuple[Union[int, float], Union[int, float], Union[int, float]]]] + ] = None, + spinless: bool = False, + plane_wave: bool = True, + include_constant: bool = False, + e_cutoff: Optional[float] = None, + non_periodic: bool = False, + period_cutoff: Optional[float] = None, +) -> FermionOperator: """Returns Hamiltonian as FermionOperator class. Args: @@ -155,8 +159,9 @@ def plane_wave_hamiltonian( if (geometry is not None) and (include_constant is True): raise ValueError('Constant term unsupported for non-uniform systems') - jellium_op = jellium_model(grid, spinless, plane_wave, include_constant, - e_cutoff, non_periodic, period_cutoff) + jellium_op = jellium_model( + grid, spinless, plane_wave, include_constant, e_cutoff, non_periodic, period_cutoff + ) if geometry is None: return jellium_op @@ -169,20 +174,24 @@ def plane_wave_hamiltonian( if plane_wave: external_potential = plane_wave_external_potential( - grid, geometry, spinless, e_cutoff, non_periodic, period_cutoff) + grid, geometry, spinless, e_cutoff, non_periodic, period_cutoff + ) else: external_potential = dual_basis_external_potential( - grid, geometry, spinless, non_periodic, period_cutoff) + grid, geometry, spinless, non_periodic, period_cutoff + ) return jellium_op + external_potential def jordan_wigner_dual_basis_hamiltonian( - grid: Grid, - geometry: Optional[List[Tuple[str, Tuple[ - Union[int, float], Union[int, float], Union[int, float]]]]] = None, - spinless: bool = False, - include_constant: bool = False) -> QubitOperator: + grid: Grid, + geometry: Optional[ + List[Tuple[str, Tuple[Union[int, float], Union[int, float], Union[int, float]]]] + ] = None, + spinless: bool = False, + include_constant: bool = False, +) -> QubitOperator: """Return the dual basis Hamiltonian as QubitOperator. Args: @@ -199,8 +208,7 @@ def jordan_wigner_dual_basis_hamiltonian( if (geometry is not None) and (include_constant is True): raise ValueError('Constant term unsupported for non-uniform systems') - jellium_op = jordan_wigner_dual_basis_jellium(grid, spinless, - include_constant) + jellium_op = jordan_wigner_dual_basis_jellium(grid, spinless, include_constant) if geometry is None: return jellium_op @@ -234,10 +242,14 @@ def jordan_wigner_dual_basis_hamiltonian( coordinate_j = np.array(nuclear_term[1], float) cos_index = momenta.dot(coordinate_j - coordinate_p) - coefficient = (prefactor / momenta_squared * - md.periodic_hash_table[nuclear_term[0]] * - np.cos(cos_index)) - external_potential += (QubitOperator( - (), coefficient) - QubitOperator(((p, 'Z'),), coefficient)) + coefficient = ( + prefactor + / momenta_squared + * md.periodic_hash_table[nuclear_term[0]] + * np.cos(cos_index) + ) + external_potential += QubitOperator((), coefficient) - QubitOperator( + ((p, 'Z'),), coefficient + ) return jellium_op + external_potential diff --git a/src/openfermion/hamiltonians/plane_wave_hamiltonian_test.py b/src/openfermion/hamiltonians/plane_wave_hamiltonian_test.py index 1e446bbbd..210c02a67 100644 --- a/src/openfermion/hamiltonians/plane_wave_hamiltonian_test.py +++ b/src/openfermion/hamiltonians/plane_wave_hamiltonian_test.py @@ -15,27 +15,30 @@ import numpy as np from openfermion.hamiltonians.plane_wave_hamiltonian import ( - jellium_model, jordan_wigner_dual_basis_hamiltonian, plane_wave_hamiltonian) + jellium_model, + jordan_wigner_dual_basis_hamiltonian, + plane_wave_hamiltonian, +) from openfermion.transforms.opconversions import jordan_wigner from openfermion.linalg import eigenspectrum, get_sparse_operator from openfermion.utils import Grid, is_hermitian class PlaneWaveHamiltonianTest(unittest.TestCase): - def test_plane_wave_hamiltonian_integration(self): length_set = [2, 3, 4] spinless_set = [True, False] length_scale = 1.1 - for geometry in [[('H', (0,)), ('H', (0.8,))], [('H', (0.1,))], - [('H', (0.1,))]]: + for geometry in [[('H', (0,)), ('H', (0.8,))], [('H', (0.1,))], [('H', (0.1,))]]: for l in length_set: for spinless in spinless_set: grid = Grid(dimensions=1, scale=length_scale, length=l) h_plane_wave = plane_wave_hamiltonian( - grid, geometry, spinless, True, include_constant=False) + grid, geometry, spinless, True, include_constant=False + ) h_dual_basis = plane_wave_hamiltonian( - grid, geometry, spinless, False, include_constant=False) + grid, geometry, spinless, False, include_constant=False + ) # Test for Hermiticity plane_wave_operator = get_sparse_operator(h_plane_wave) @@ -48,10 +51,8 @@ def test_plane_wave_hamiltonian_integration(self): h_plane_wave_spectrum = eigenspectrum(jw_h_plane_wave) h_dual_basis_spectrum = eigenspectrum(jw_h_dual_basis) - max_diff = np.amax(h_plane_wave_spectrum - - h_dual_basis_spectrum) - min_diff = np.amin(h_plane_wave_spectrum - - h_dual_basis_spectrum) + max_diff = np.amax(h_plane_wave_spectrum - h_dual_basis_spectrum) + min_diff = np.amin(h_plane_wave_spectrum - h_dual_basis_spectrum) self.assertAlmostEqual(max_diff, 0) self.assertAlmostEqual(min_diff, 0) @@ -65,9 +66,7 @@ def test_plane_wave_hamiltonian_bad_geometry(self): plane_wave_hamiltonian(grid, geometry=[('H', (0, 0, 0))]) with self.assertRaises(ValueError): - plane_wave_hamiltonian(grid, - geometry=[('H', (0, 0, 0))], - include_constant=True) + plane_wave_hamiltonian(grid, geometry=[('H', (0, 0, 0))], include_constant=True) def test_plane_wave_hamiltonian_bad_element(self): grid = Grid(dimensions=3, scale=1.0, length=4) @@ -75,44 +74,41 @@ def test_plane_wave_hamiltonian_bad_element(self): plane_wave_hamiltonian(grid, geometry=[('Unobtainium', (0, 0, 0))]) def test_jordan_wigner_dual_basis_hamiltonian(self): - grid = Grid(dimensions=2, length=3, scale=1.) + grid = Grid(dimensions=2, length=3, scale=1.0) spinless_set = [True, False] geometry = [('H', (0, 0)), ('H', (0.5, 0.8))] for spinless in spinless_set: - fermion_hamiltonian = plane_wave_hamiltonian(grid, - geometry, - spinless, - False, - include_constant=False) + fermion_hamiltonian = plane_wave_hamiltonian( + grid, geometry, spinless, False, include_constant=False + ) qubit_hamiltonian = jordan_wigner(fermion_hamiltonian) test_hamiltonian = jordan_wigner_dual_basis_hamiltonian( - grid, geometry, spinless, include_constant=False) + grid, geometry, spinless, include_constant=False + ) self.assertTrue(test_hamiltonian == qubit_hamiltonian) def test_jordan_wigner_dual_basis_hamiltonian_default_to_jellium(self): grid = Grid(dimensions=1, scale=1.0, length=4) self.assertTrue( - jordan_wigner_dual_basis_hamiltonian(grid) == jordan_wigner( - jellium_model(grid, plane_wave=False))) + jordan_wigner_dual_basis_hamiltonian(grid) + == jordan_wigner(jellium_model(grid, plane_wave=False)) + ) def test_jordan_wigner_dual_basis_hamiltonian_bad_geometry(self): grid = Grid(dimensions=1, scale=1.0, length=4) with self.assertRaises(ValueError): - jordan_wigner_dual_basis_hamiltonian(grid, - geometry=[('H', (0, 0, 0))]) + jordan_wigner_dual_basis_hamiltonian(grid, geometry=[('H', (0, 0, 0))]) with self.assertRaises(ValueError): - jordan_wigner_dual_basis_hamiltonian(grid, - geometry=[('H', (0, 0, 0))], - include_constant=True) + jordan_wigner_dual_basis_hamiltonian( + grid, geometry=[('H', (0, 0, 0))], include_constant=True + ) def test_jordan_wigner_dual_basis_hamiltonian_bad_element(self): grid = Grid(dimensions=3, scale=1.0, length=4) with self.assertRaises(ValueError): - jordan_wigner_dual_basis_hamiltonian(grid, - geometry=[('Unobtainium', - (0, 0, 0))]) + jordan_wigner_dual_basis_hamiltonian(grid, geometry=[('Unobtainium', (0, 0, 0))]) def test_plane_wave_energy_cutoff(self): geometry = [('H', (0,)), ('H', (0.8,))] @@ -123,13 +119,12 @@ def test_plane_wave_energy_cutoff(self): jw_1 = jordan_wigner(h_1) spectrum_1 = eigenspectrum(jw_1) - h_2 = plane_wave_hamiltonian(grid, geometry, True, True, False, - e_cutoff) + h_2 = plane_wave_hamiltonian(grid, geometry, True, True, False, e_cutoff) jw_2 = jordan_wigner(h_2) spectrum_2 = eigenspectrum(jw_2) max_diff = np.amax(np.absolute(spectrum_1 - spectrum_2)) - self.assertGreater(max_diff, 0.) + self.assertGreater(max_diff, 0.0) def test_plane_wave_period_cutoff(self): # TODO: After figuring out the correct formula for period cutoff for @@ -144,13 +139,12 @@ def test_plane_wave_period_cutoff(self): jw_1 = jordan_wigner(h_1) spectrum_1 = eigenspectrum(jw_1) - h_2 = plane_wave_hamiltonian(grid, geometry, True, True, False, None, - True, period_cutoff) + h_2 = plane_wave_hamiltonian(grid, geometry, True, True, False, None, True, period_cutoff) jw_2 = jordan_wigner(h_2) spectrum_2 = eigenspectrum(jw_2) max_diff = np.amax(np.absolute(spectrum_1 - spectrum_2)) - self.assertGreater(max_diff, 0.) + self.assertGreater(max_diff, 0.0) # TODO: This is only for code coverage. Remove after having real # integration test. diff --git a/src/openfermion/hamiltonians/richardson_gaudin.py b/src/openfermion/hamiltonians/richardson_gaudin.py index 1529f0001..4de5e6a72 100644 --- a/src/openfermion/hamiltonians/richardson_gaudin.py +++ b/src/openfermion/hamiltonians/richardson_gaudin.py @@ -13,8 +13,7 @@ """ from itertools import chain, product import numpy -from openfermion.ops.representations import (PolynomialTensor, - get_tensors_from_integrals) +from openfermion.ops.representations import PolynomialTensor, get_tensors_from_integrals from openfermion.ops.representations import DOCIHamiltonian from openfermion.ops import QubitOperator @@ -68,16 +67,19 @@ def __init__(self, g, n_qubits): @DOCIHamiltonian.constant.setter def constant(self, value): - raise TypeError('Raw edits of the constant of a RichardsonGaudin model' - 'is not allowed. Either adjust the g parameter ' - 'or cast to another PolynomialTensor class.') + raise TypeError( + 'Raw edits of the constant of a RichardsonGaudin model' + 'is not allowed. Either adjust the g parameter ' + 'or cast to another PolynomialTensor class.' + ) @DOCIHamiltonian.n_body_tensors.setter def n_body_tensors(self, value): raise TypeError( 'Raw edits of the n_body_tensors of a RichardsonGaudin model' 'is not allowed. Either adjust the g parameter ' - 'or cast to another PolynomialTensor class.') + 'or cast to another PolynomialTensor class.' + ) def get_antisymmetrized_tensors(self): r"""Antisymmetrized Tensors diff --git a/src/openfermion/hamiltonians/richardson_gaudin_test.py b/src/openfermion/hamiltonians/richardson_gaudin_test.py index a30561af2..e098ed1f2 100644 --- a/src/openfermion/hamiltonians/richardson_gaudin_test.py +++ b/src/openfermion/hamiltonians/richardson_gaudin_test.py @@ -19,19 +19,31 @@ from openfermion.transforms import get_fermion_operator from openfermion.transforms import jordan_wigner from openfermion.linalg import get_sparse_operator -from openfermion import(get_fermion_operator, InteractionOperator, \ - normal_ordered) +from openfermion import get_fermion_operator, InteractionOperator, normal_ordered -@pytest.mark.parametrize('g, n_qubits, expected', [ - (0.3, 2, - QubitOperator('3.0 [] + 0.15 [X0 X1] + \ -0.15 [Y0 Y1] - 1.0 [Z0] - 2.0 [Z1]')), - (-0.1, 3, - QubitOperator('6.0 [] - 0.05 [X0 X1] - 0.05 [X0 X2] - \ +@pytest.mark.parametrize( + 'g, n_qubits, expected', + [ + ( + 0.3, + 2, + QubitOperator( + '3.0 [] + 0.15 [X0 X1] + \ +0.15 [Y0 Y1] - 1.0 [Z0] - 2.0 [Z1]' + ), + ), + ( + -0.1, + 3, + QubitOperator( + '6.0 [] - 0.05 [X0 X1] - 0.05 [X0 X2] - \ 0.05 [Y0 Y1] - 0.05 [Y0 Y2] - 1.0 [Z0] - 0.05 [X1 X2] - \ -0.05 [Y1 Y2] - 2.0 [Z1] - 3.0 [Z2]')), -]) +0.05 [Y1 Y2] - 2.0 [Z1] - 3.0 [Z2]' + ), + ), + ], +) def test_richardson_gaudin_hamiltonian(g, n_qubits, expected): rg = RichardsonGaudin(g, n_qubits) rg_qubit = rg.qubit_operator @@ -39,7 +51,8 @@ def test_richardson_gaudin_hamiltonian(g, n_qubits, expected): assert np.array_equal( np.sort(np.unique(get_sparse_operator(rg_qubit).diagonal())), - 2 * np.array(list(range((n_qubits + 1) * n_qubits // 2 + 1)))) + 2 * np.array(list(range((n_qubits + 1) * n_qubits // 2 + 1))), + ) def test_n_body_tensor_errors(): @@ -53,7 +66,7 @@ def test_n_body_tensor_errors(): @pytest.mark.parametrize("g, n_qubits", [(0.2, 4), (-0.2, 4)]) def test_fermionic_hamiltonian_from_integrals(g, n_qubits): rg = RichardsonGaudin(g, n_qubits) - #hc, hr1, hr2 = rg.hc, rg.hr1, rg.hr2 + # hc, hr1, hr2 = rg.hc, rg.hr1, rg.hr2 doci = rg constant = doci.constant reference_constant = 0 @@ -66,27 +79,30 @@ def test_fermionic_hamiltonian_from_integrals(g, n_qubits): one_body_tensors, two_body_tensors = tensors[(1, 0)], tensors[(1, 1, 0, 0)] fermion_op = get_fermion_operator( - InteractionOperator(constant, one_body_tensors, 0.5 * two_body_tensors)) + InteractionOperator(constant, one_body_tensors, 0.5 * two_body_tensors) + ) fermion_op = normal_ordered(fermion_op) fermion_mat = get_sparse_operator(fermion_op).toarray() fermion_eigvals = np.linalg.eigh(fermion_mat)[0] one_body_tensors2, two_body_tensors2 = rg.get_antisymmetrized_tensors() fermion_op2 = get_fermion_operator( - InteractionOperator(reference_constant, one_body_tensors2, - 0.5 * two_body_tensors2)) + InteractionOperator(reference_constant, one_body_tensors2, 0.5 * two_body_tensors2) + ) fermion_op2 = normal_ordered(fermion_op2) fermion_mat2 = get_sparse_operator(fermion_op2).toarray() fermion_eigvals2 = np.linalg.eigh(fermion_mat2)[0] for eigval in doci_eigvals: - assert any(abs(fermion_eigvals - - eigval) < 1e-6), "The DOCI spectrum should have \ + assert any( + abs(fermion_eigvals - eigval) < 1e-6 + ), "The DOCI spectrum should have \ been contained in the spectrum of the fermionic operator constructed via the \ DOCIHamiltonian class" for eigval in doci_eigvals: - assert any(abs(fermion_eigvals2 - - eigval) < 1e-6), "The DOCI spectrum should have \ + assert any( + abs(fermion_eigvals2 - eigval) < 1e-6 + ), "The DOCI spectrum should have \ been contained in the spectrum of the fermionic operators constructed via the anti \ symmetrized tensors" diff --git a/src/openfermion/hamiltonians/special_operators.py b/src/openfermion/hamiltonians/special_operators.py index d1c8b2774..d4b4ffd72 100644 --- a/src/openfermion/hamiltonians/special_operators.py +++ b/src/openfermion/hamiltonians/special_operators.py @@ -104,10 +104,8 @@ def sx_operator(n_spatial_orbitals: int) -> FermionOperator: operator = FermionOperator() for ni in range(n_spatial_orbitals): - operator += FermionOperator(((up_index(ni), 1), (down_index(ni), 0)), - .5) - operator += FermionOperator(((down_index(ni), 1), (up_index(ni), 0)), - .5) + operator += FermionOperator(((up_index(ni), 1), (down_index(ni), 0)), 0.5) + operator += FermionOperator(((down_index(ni), 1), (up_index(ni), 0)), 0.5) return operator @@ -138,10 +136,8 @@ def sy_operator(n_spatial_orbitals: int) -> FermionOperator: operator = FermionOperator() for ni in range(n_spatial_orbitals): - operator += FermionOperator(((up_index(ni), 1), (down_index(ni), 0)), - -.5j) - operator += FermionOperator(((down_index(ni), 1), (up_index(ni), 0)), - .5j) + operator += FermionOperator(((up_index(ni), 1), (down_index(ni), 0)), -0.5j) + operator += FermionOperator(((down_index(ni), 1), (up_index(ni), 0)), 0.5j) return operator @@ -173,8 +169,9 @@ def sz_operator(n_spatial_orbitals: int) -> FermionOperator: operator = FermionOperator() n_spinless_orbitals = 2 * n_spatial_orbitals for ni in range(n_spatial_orbitals): - operator += (number_operator(n_spinless_orbitals, up_index(ni), 0.5) + - number_operator(n_spinless_orbitals, down_index(ni), -0.5)) + operator += number_operator(n_spinless_orbitals, up_index(ni), 0.5) + number_operator( + n_spinless_orbitals, down_index(ni), -0.5 + ) return operator @@ -204,15 +201,16 @@ def s_squared_operator(n_spatial_orbitals: int) -> FermionOperator: raise TypeError("n_orbitals must be specified as an integer") fermion_identity = FermionOperator(()) - operator = (s_minus_operator(n_spatial_orbitals) * - s_plus_operator(n_spatial_orbitals)) - operator += (sz_operator(n_spatial_orbitals) * - (sz_operator(n_spatial_orbitals) + fermion_identity)) + operator = s_minus_operator(n_spatial_orbitals) * s_plus_operator(n_spatial_orbitals) + operator += sz_operator(n_spatial_orbitals) * ( + sz_operator(n_spatial_orbitals) + fermion_identity + ) return operator -def majorana_operator(term: Optional[Union[Tuple[int, int], str]] = None, - coefficient=1.) -> FermionOperator: +def majorana_operator( + term: Optional[Union[Tuple[int, int], str]] = None, coefficient=1.0 +) -> FermionOperator: r"""Initialize a Majorana operator. Args: @@ -266,11 +264,10 @@ def majorana_operator(term: Optional[Union[Tuple[int, int], str]] = None, majorana_op = FermionOperator(((mode, 1),), coefficient) majorana_op += FermionOperator(((mode, 0),), coefficient) elif operator_type == 1: - majorana_op = FermionOperator(((mode, 1),), 1.j * coefficient) - majorana_op -= FermionOperator(((mode, 0),), 1.j * coefficient) + majorana_op = FermionOperator(((mode, 1),), 1.0j * coefficient) + majorana_op -= FermionOperator(((mode, 0),), 1.0j * coefficient) else: - raise ValueError('Invalid operator type: {}'.format( - str(operator_type))) + raise ValueError('Invalid operator type: {}'.format(str(operator_type))) return majorana_op @@ -279,10 +276,9 @@ def majorana_operator(term: Optional[Union[Tuple[int, int], str]] = None, raise ValueError('Operator specified incorrectly.') -def number_operator(n_modes: int, - mode: Optional[int] = None, - coefficient=1., - parity: int = -1) -> Union[BosonOperator, FermionOperator]: +def number_operator( + n_modes: int, mode: Optional[int] = None, coefficient=1.0, parity: int = -1 +) -> Union[BosonOperator, FermionOperator]: """Return a fermionic or bosonic number operator. Args: diff --git a/src/openfermion/hamiltonians/special_operators_test.py b/src/openfermion/hamiltonians/special_operators_test.py index 185bdccab..94b6bd7dd 100644 --- a/src/openfermion/hamiltonians/special_operators_test.py +++ b/src/openfermion/hamiltonians/special_operators_test.py @@ -15,13 +15,20 @@ from openfermion.utils import commutator from openfermion.transforms.opconversions import normal_ordered from openfermion.hamiltonians.special_operators import ( - majorana_operator, number_operator, s_minus_operator, s_plus_operator, - s_squared_operator, sx_operator, sy_operator, sz_operator, up_index, - down_index) + majorana_operator, + number_operator, + s_minus_operator, + s_plus_operator, + s_squared_operator, + sx_operator, + sy_operator, + sz_operator, + up_index, + down_index, +) class FermionSpinOperatorsTest(unittest.TestCase): - def test_up_index(self): self.assertEqual(up_index(2), 4) self.assertEqual(up_index(5), 10) @@ -32,47 +39,58 @@ def test_up_down(self): def test_s_plus_operator(self): op = s_plus_operator(2) - expected = (FermionOperator(((0, 1), (1, 0))) + FermionOperator( - ((2, 1), (3, 0)))) + expected = FermionOperator(((0, 1), (1, 0))) + FermionOperator(((2, 1), (3, 0))) self.assertEqual(op, expected) def test_s_minus_operator(self): op = s_minus_operator(3) - expected = (FermionOperator(((1, 1), (0, 0))) + FermionOperator( - ((3, 1), (2, 0))) + FermionOperator(((5, 1), (4, 0)))) + expected = ( + FermionOperator(((1, 1), (0, 0))) + + FermionOperator(((3, 1), (2, 0))) + + FermionOperator(((5, 1), (4, 0))) + ) self.assertEqual(op, expected) def test_sx_operator(self): op = sx_operator(2) - expected = (FermionOperator(((0, 1), (1, 0)), 0.5) + FermionOperator( - ((1, 1), (0, 0)), 0.5) + FermionOperator( - ((2, 1), (3, 0)), 0.5) + FermionOperator(((3, 1), (2, 0)), 0.5)) + expected = ( + FermionOperator(((0, 1), (1, 0)), 0.5) + + FermionOperator(((1, 1), (0, 0)), 0.5) + + FermionOperator(((2, 1), (3, 0)), 0.5) + + FermionOperator(((3, 1), (2, 0)), 0.5) + ) self.assertEqual(op, expected) def test_sy_operator(self): op = sy_operator(2) - expected = (FermionOperator(((0, 1), (1, 0)), -0.5j) - FermionOperator( - ((1, 1), (0, 0)), -0.5j) + FermionOperator( - ((2, 1), (3, 0)), -0.5j) - FermionOperator( - ((3, 1), (2, 0)), -0.5j)) + expected = ( + FermionOperator(((0, 1), (1, 0)), -0.5j) + - FermionOperator(((1, 1), (0, 0)), -0.5j) + + FermionOperator(((2, 1), (3, 0)), -0.5j) + - FermionOperator(((3, 1), (2, 0)), -0.5j) + ) self.assertEqual(op, expected) def test_sz_operator(self): op = sz_operator(2) - expected = (FermionOperator(((0, 1), (0, 0)), 0.5) - FermionOperator( - ((1, 1), (1, 0)), 0.5) + FermionOperator( - ((2, 1), (2, 0)), 0.5) - FermionOperator(((3, 1), (3, 0)), 0.5)) + expected = ( + FermionOperator(((0, 1), (0, 0)), 0.5) + - FermionOperator(((1, 1), (1, 0)), 0.5) + + FermionOperator(((2, 1), (2, 0)), 0.5) + - FermionOperator(((3, 1), (3, 0)), 0.5) + ) self.assertEqual(op, expected) def test_s_squared_operator(self): op = s_squared_operator(2) - s_minus = (FermionOperator(((1, 1), (0, 0))) + FermionOperator( - ((3, 1), (2, 0)))) - s_plus = (FermionOperator(((0, 1), (1, 0))) + FermionOperator( - ((2, 1), (3, 0)))) - s_z = (FermionOperator(((0, 1), (0, 0)), 0.5) - FermionOperator( - ((1, 1), (1, 0)), 0.5) + FermionOperator( - ((2, 1), (2, 0)), 0.5) - FermionOperator(((3, 1), (3, 0)), 0.5)) + s_minus = FermionOperator(((1, 1), (0, 0))) + FermionOperator(((3, 1), (2, 0))) + s_plus = FermionOperator(((0, 1), (1, 0))) + FermionOperator(((2, 1), (3, 0))) + s_z = ( + FermionOperator(((0, 1), (0, 0)), 0.5) + - FermionOperator(((1, 1), (1, 0)), 0.5) + + FermionOperator(((2, 1), (2, 0)), 0.5) + - FermionOperator(((3, 1), (3, 0)), 0.5) + ) expected = s_minus * s_plus + s_z * s_z + s_z self.assertEqual(op, expected) @@ -88,22 +106,16 @@ def test_relations(self): identity = FermionOperator(()) - self.assertEqual(normal_ordered(sx), - normal_ordered(.5 * (s_plus + s_minus))) - self.assertEqual(normal_ordered(sy), - normal_ordered((.5 / 1.j) * (s_plus - s_minus))) - self.assertEqual(normal_ordered(s_squared), - normal_ordered(sx**2 + sy**2 + sz**2)) + self.assertEqual(normal_ordered(sx), normal_ordered(0.5 * (s_plus + s_minus))) + self.assertEqual(normal_ordered(sy), normal_ordered((0.5 / 1.0j) * (s_plus - s_minus))) + self.assertEqual(normal_ordered(s_squared), normal_ordered(sx**2 + sy**2 + sz**2)) self.assertEqual( - normal_ordered(s_squared), - normal_ordered(s_plus * s_minus + sz * (sz - identity))) - self.assertEqual(normal_ordered(commutator(s_plus, s_minus)), - normal_ordered(2 * sz)) - self.assertEqual(normal_ordered(commutator(sx, sy)), - normal_ordered(1.j * sz)) + normal_ordered(s_squared), normal_ordered(s_plus * s_minus + sz * (sz - identity)) + ) + self.assertEqual(normal_ordered(commutator(s_plus, s_minus)), normal_ordered(2 * sz)) + self.assertEqual(normal_ordered(commutator(sx, sy)), normal_ordered(1.0j * sz)) def test_invalid_input(self): - with self.assertRaises(TypeError): s_minus_operator('a') @@ -124,7 +136,6 @@ def test_invalid_input(self): class NumberOperatorTest(unittest.TestCase): - def test_fermion_number_operator_site(self): op = number_operator(3, 2, 1j, -1) self.assertEqual(op, FermionOperator(((2, 1), (2, 0))) * 1j) @@ -134,15 +145,21 @@ def test_fermion_number_operator_site(self): def test_number_operator_nosite(self): op = number_operator(4, parity=-1) - expected = (FermionOperator(((0, 1), (0, 0))) + FermionOperator( - ((1, 1), (1, 0))) + FermionOperator( - ((2, 1), (2, 0))) + FermionOperator(((3, 1), (3, 0)))) + expected = ( + FermionOperator(((0, 1), (0, 0))) + + FermionOperator(((1, 1), (1, 0))) + + FermionOperator(((2, 1), (2, 0))) + + FermionOperator(((3, 1), (3, 0))) + ) self.assertEqual(op, expected) op = number_operator(4, parity=1) - expected = (BosonOperator(((0, 1), (0, 0))) + BosonOperator( - ((1, 1), (1, 0))) + BosonOperator(((2, 1), (2, 0))) + BosonOperator( - ((3, 1), (3, 0)))) + expected = ( + BosonOperator(((0, 1), (0, 0))) + + BosonOperator(((1, 1), (1, 0))) + + BosonOperator(((2, 1), (2, 0))) + + BosonOperator(((3, 1), (3, 0))) + ) self.assertTrue(op == expected) def test_bad_parity(self): @@ -151,7 +168,6 @@ def test_bad_parity(self): class MajoranaOperatorTest(unittest.TestCase): - def test_init(self): # Test 'c' operator op1 = majorana_operator((2, 0)) @@ -165,7 +181,7 @@ def test_init(self): # Test 'd' operator op1 = majorana_operator((3, 1)) op2 = majorana_operator('d3') - correct = FermionOperator('3^', 1.j) - FermionOperator('3', 1.j) + correct = FermionOperator('3^', 1.0j) - FermionOperator('3', 1.0j) self.assertEqual(op1, op2) self.assertEqual(op1, correct) @@ -186,4 +202,4 @@ def test_bad_term(self): with self.assertRaises(ValueError): majorana_operator('a') with self.assertRaises(ValueError): - majorana_operator(2) \ No newline at end of file + majorana_operator(2) diff --git a/src/openfermion/linalg/__init__.py b/src/openfermion/linalg/__init__.py index ba0700d6a..4513ac6f6 100644 --- a/src/openfermion/linalg/__init__.py +++ b/src/openfermion/linalg/__init__.py @@ -20,10 +20,7 @@ orthonormalize, ) -from .erpa import ( - erpa_eom_hamiltonian, - singlet_erpa, -) +from .erpa import erpa_eom_hamiltonian, singlet_erpa from .givens_rotations import ( givens_decomposition, @@ -43,8 +40,7 @@ ParallelLinearQubitOperator, ) -from .rdm_reconstruction import ( - valdemoro_reconstruction,) +from .rdm_reconstruction import valdemoro_reconstruction from .sparse_tools import ( wrapped_kronecker, @@ -84,12 +80,6 @@ get_number_preserving_sparse_operator, ) -from .wave_fitting import ( - fit_known_frequencies, - prony, -) +from .wave_fitting import fit_known_frequencies, prony -from .wedge_product import ( - generate_parity_permutations, - wedge, -) +from .wedge_product import generate_parity_permutations, wedge diff --git a/src/openfermion/linalg/davidson.py b/src/openfermion/linalg/davidson.py index 9c404688d..a0f910139 100644 --- a/src/openfermion/linalg/davidson.py +++ b/src/openfermion/linalg/davidson.py @@ -22,23 +22,19 @@ import scipy.sparse.linalg from openfermion.linalg.sparse_tools import get_linear_qubit_operator_diagonal -from openfermion.linalg.linear_qubit_operator import \ - generate_linear_qubit_operator +from openfermion.linalg.linear_qubit_operator import generate_linear_qubit_operator class DavidsonError(Exception): """Exceptions.""" + pass class DavidsonOptions(object): """Davidson algorithm iteration options.""" - def __init__(self, - max_subspace=100, - max_iterations=300, - eps=1e-6, - real_only=False): + def __init__(self, max_subspace=100, max_iterations=300, eps=1e-6, real_only=False): """ Args: max_subspace(int): Max number of vectors in the auxiliary subspace. @@ -51,9 +47,10 @@ def __init__(self, complex. """ if max_subspace <= 2 or max_iterations <= 0 or eps <= 0: - raise ValueError('Invalid values for max_subspace, max_iterations ' - 'and/ or eps: ({}, {}, {}).'.format( - max_subspace, max_iterations, eps)) + raise ValueError( + 'Invalid values for max_subspace, max_iterations ' + 'and/ or eps: ({}, {}, {}).'.format(max_subspace, max_iterations, eps) + ) self.max_subspace = max_subspace self.max_iterations = max_iterations @@ -87,11 +84,11 @@ def __init__(self, linear_operator, linear_operator_diagonal, options=None): options = DavidsonOptions() if not isinstance( - linear_operator, - (scipy.sparse.linalg.LinearOperator, scipy.sparse.spmatrix)): + linear_operator, (scipy.sparse.linalg.LinearOperator, scipy.sparse.spmatrix) + ): raise ValueError( - 'linear_operator is not a LinearOperator: {}.'.format( - type(linear_operator))) + 'linear_operator is not a LinearOperator: {}.'.format(type(linear_operator)) + ) self.linear_operator = linear_operator self.linear_operator_diagonal = linear_operator_diagonal @@ -125,21 +122,25 @@ def get_lowest_n(self, n_lowest=1, initial_guess=None, max_iterations=None): # 1. Checks for number of states desired, should be in the range of # [0, max_subspace). if n_lowest <= 0 or n_lowest >= self.options.max_subspace: - raise ValueError('n_lowest {} is supposed to be in [1, {}).'.format( - n_lowest, self.options.max_subspace)) + raise ValueError( + 'n_lowest {} is supposed to be in [1, {}).'.format( + n_lowest, self.options.max_subspace + ) + ) # 2. Checks for initial guess vectors' dimension is the same to that of # the operator. if initial_guess is None: initial_guess = generate_random_vectors( - len(self.linear_operator_diagonal), - n_lowest, - real_only=self.options.real_only) + len(self.linear_operator_diagonal), n_lowest, real_only=self.options.real_only + ) if initial_guess.shape[0] != len(self.linear_operator_diagonal): raise ValueError( 'Guess vectors have a different dimension with ' 'linear opearator diagonal elements: {} != {}.'.format( - initial_guess.shape[1], len(self.linear_operator_diagonal))) + initial_guess.shape[1], len(self.linear_operator_diagonal) + ) + ) # 3. Makes sure real guess vector if real_only is specified. if self.options.real_only: @@ -149,27 +150,35 @@ def get_lowest_n(self, n_lowest=1, initial_guess=None, max_iterations=None): # 4. Checks for non-trivial (non-zero) initial guesses. if numpy.max(numpy.abs(initial_guess)) < self.options.eps: - raise ValueError('Guess vectors are all zero! {}'.format( - initial_guess.shape)) + raise ValueError('Guess vectors are all zero! {}'.format(initial_guess.shape)) initial_guess = scipy.linalg.orth(initial_guess) # 5. Makes sure number of initial guess vector is at least n_lowest. if initial_guess.shape[1] < n_lowest: initial_guess = append_random_vectors( - initial_guess, - n_lowest - initial_guess.shape[1], - real_only=self.options.real_only) + initial_guess, n_lowest - initial_guess.shape[1], real_only=self.options.real_only + ) success = False num_iterations = 0 guess_v = initial_guess guess_mv = None max_iterations = max_iterations or self.options.max_iterations - while (num_iterations < max_iterations and not success): - (eigen_values, eigen_vectors, mat_eigen_vectors, max_trial_error, - guess_v, guess_mv) = self._iterate(n_lowest, guess_v, guess_mv) - logging.info("Eigenvalues for iteration %d: %s, error is %f.", - num_iterations, eigen_values, max_trial_error) + while num_iterations < max_iterations and not success: + ( + eigen_values, + eigen_vectors, + mat_eigen_vectors, + max_trial_error, + guess_v, + guess_mv, + ) = self._iterate(n_lowest, guess_v, guess_mv) + logging.info( + "Eigenvalues for iteration %d: %s, error is %f.", + num_iterations, + eigen_values, + max_trial_error, + ) if max_trial_error < self.options.eps: success = True @@ -185,8 +194,7 @@ def get_lowest_n(self, n_lowest=1, initial_guess=None, max_iterations=None): count_mvs = guess_mv.shape[1] guess_v = orthonormalize(guess_v, count_mvs, self.options.eps) if guess_v.shape[1] <= count_mvs: - guess_v = append_random_vectors( - guess_v, n_lowest, real_only=self.options.real_only) + guess_v = append_random_vectors(guess_v, n_lowest, real_only=self.options.real_only) # Limits number of vectors to self.options.max_subspace, in this # case, keep the following: @@ -196,27 +204,25 @@ def get_lowest_n(self, n_lowest=1, initial_guess=None, max_iterations=None): # 3) new search directions which will be used for improvement for # the next iteration. if guess_v.shape[1] >= self.options.max_subspace: - guess_v = numpy.hstack([ - eigen_vectors, - guess_v[:, count_mvs:], - ]) + guess_v = numpy.hstack([eigen_vectors, guess_v[:, count_mvs:]]) guess_mv = mat_eigen_vectors if self.options.real_only: - if (not numpy.allclose(numpy.real(guess_v), guess_v) or - not numpy.allclose(numpy.real(guess_mv), guess_mv)): + if not numpy.allclose(numpy.real(guess_v), guess_v) or not numpy.allclose( + numpy.real(guess_mv), guess_mv + ): # Forces recalculation for matrix multiplication with # vectors. guess_mv = None num_iterations += 1 - if (self.options.real_only and - not numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)): + if self.options.real_only and not numpy.allclose(numpy.real(eigen_vectors), eigen_vectors): warnings.warn( 'Unable to get real only eigenvectors, return ' - 'complex vectors instead with success state {}.'.format( - success), RuntimeWarning) + 'complex vectors instead with success state {}.'.format(success), + RuntimeWarning, + ) return success, eigen_values, eigen_vectors def _iterate(self, n_lowest, guess_v, guess_mv=None): @@ -250,11 +256,9 @@ def _iterate(self, n_lowest, guess_v, guess_mv=None): # Note that getting guess_mv is the most expensive step. if guess_mv.shape[1] < dimension: - guess_mv = numpy.hstack([ - guess_mv, - self.linear_operator.dot( - guess_v[:, guess_mv.shape[1]:dimension]) - ]) + guess_mv = numpy.hstack( + [guess_mv, self.linear_operator.dot(guess_v[:, guess_mv.shape[1] : dimension])] + ) guess_vmv = numpy.dot(guess_v.conj().T, guess_mv) # Gets new set of eigenvalues and eigenvectors in the vmv space, with a @@ -280,11 +284,11 @@ def _iterate(self, n_lowest, guess_v, guess_mv=None): trial_error = trial_mv - trial_v * trial_lambda new_directions, max_trial_error = self._get_new_directions( - trial_error, trial_lambda, trial_v) + trial_error, trial_lambda, trial_v + ) if new_directions: guess_v = numpy.hstack([guess_v, numpy.stack(new_directions).T]) - return (trial_lambda, trial_v, trial_mv, max_trial_error, guess_v, - guess_mv) + return (trial_lambda, trial_v, trial_mv, max_trial_error, guess_v, guess_mv) def _get_new_directions(self, error_v, trial_lambda, trial_v): """Gets new directions from error vectors. @@ -319,8 +323,7 @@ def _get_new_directions(self, error_v, trial_lambda, trial_v): # search for new directions. continue - max_trial_error = max(max_trial_error, - numpy.linalg.norm(current_error_v)) + max_trial_error = max(max_trial_error, numpy.linalg.norm(current_error_v)) diagonal_inverse = numpy.ones(origonal_dimension) for j in range(origonal_dimension): # Makes sure error vectors are bounded. @@ -331,9 +334,11 @@ def _get_new_directions(self, error_v, trial_lambda, trial_v): diagonal_inverse[j] /= self.options.eps diagonal_inverse_error = diagonal_inverse * current_error_v diagonal_inverse_trial = diagonal_inverse * trial_v[:, i] - new_direction = -current_error_v + (trial_v[:, i] * numpy.dot( - trial_v[:, i].conj(), diagonal_inverse_error) / numpy.dot( - trial_v[:, i].conj(), diagonal_inverse_trial)) + new_direction = -current_error_v + ( + trial_v[:, i] + * numpy.dot(trial_v[:, i].conj(), diagonal_inverse_error) + / numpy.dot(trial_v[:, i].conj(), diagonal_inverse_trial) + ) new_directions.append(new_direction) return new_directions, max_trial_error @@ -353,7 +358,8 @@ def __init__(self, qubit_operator, n_qubits=None, options=None): super(QubitDavidson, self).__init__( generate_linear_qubit_operator(qubit_operator, n_qubits, options), get_linear_qubit_operator_diagonal(qubit_operator, n_qubits), - options=options) + options=options, + ) class SparseDavidson(Davidson): @@ -365,9 +371,9 @@ def __init__(self, sparse_matrix, options=None): sparse_matrix(scipy.sparse.spmatrix): A sparse matrix in scipy. options(DavidsonOptions): Iteration options. """ - super(SparseDavidson, self).__init__(sparse_matrix, - sparse_matrix.diagonal(), - options=options) + super(SparseDavidson, self).__init__( + sparse_matrix, sparse_matrix.diagonal(), options=options + ) def generate_random_vectors(row, col, real_only=False): @@ -412,11 +418,14 @@ def append_random_vectors(vectors, col, max_trial=3, real_only=False): while vector_columns < total_columns: num_trial += 1 - vectors = numpy.hstack([ - vectors, - generate_random_vectors(vectors.shape[0], - total_columns - vector_columns, real_only) - ]) + vectors = numpy.hstack( + [ + vectors, + generate_random_vectors( + vectors.shape[0], total_columns - vector_columns, real_only + ), + ] + ) vectors = orthonormalize(vectors, vector_columns) # Checks whether there are any new vectors added successfully. @@ -424,8 +433,9 @@ def append_random_vectors(vectors, col, max_trial=3, real_only=False): if num_trial > max_trial: warnings.warn( 'Unable to generate specified number of random ' - 'vectors {}: returning {} in total.'.format( - col, vector_columns), RuntimeWarning) + 'vectors {}: returning {} in total.'.format(col, vector_columns), + RuntimeWarning, + ) break else: num_trial = 1 @@ -456,13 +466,11 @@ def orthonormalize(vectors, num_orthonormals=1, eps=1e-6): vector_i = vectors[:, i] # Makes sure vector_i is orthogonal to all processed vectors. for j in range(i): - vector_i -= ortho_normals[:, j] * numpy.dot( - ortho_normals[:, j].conj(), vector_i) + vector_i -= ortho_normals[:, j] * numpy.dot(ortho_normals[:, j].conj(), vector_i) # Makes sure vector_i is normalized. if numpy.max(numpy.abs(vector_i)) < eps: continue - ortho_normals[:, count_orthonormals] = (vector_i / - numpy.linalg.norm(vector_i)) + ortho_normals[:, count_orthonormals] = vector_i / numpy.linalg.norm(vector_i) count_orthonormals += 1 return ortho_normals[:, :count_orthonormals] diff --git a/src/openfermion/linalg/davidson_test.py b/src/openfermion/linalg/davidson_test.py index 0d83f55eb..18135442d 100644 --- a/src/openfermion/linalg/davidson_test.py +++ b/src/openfermion/linalg/davidson_test.py @@ -21,9 +21,14 @@ import scipy.sparse.linalg from openfermion.ops.operators import QubitOperator -from openfermion.linalg.davidson import (Davidson, DavidsonOptions, - QubitDavidson, SparseDavidson, - append_random_vectors, orthonormalize) +from openfermion.linalg.davidson import ( + Davidson, + DavidsonOptions, + QubitDavidson, + SparseDavidson, + append_random_vectors, + orthonormalize, +) def generate_matrix(dimension): @@ -58,27 +63,23 @@ def generate_sparse_matrix(dimension, diagonal_factor=30): def get_difference(linear_operator, eigen_values, eigen_vectors): """Get difference of M * v - lambda v.""" - return numpy.max( - numpy.abs( - linear_operator.dot(eigen_vectors) - eigen_vectors * eigen_values)) + return numpy.max(numpy.abs(linear_operator.dot(eigen_vectors) - eigen_vectors * eigen_values)) class DavidsonOptionsTest(unittest.TestCase): - """"Tests for DavidsonOptions class.""" + """ "Tests for DavidsonOptions class.""" def setUp(self): """Sets up all variables needed for DavidsonOptions class.""" self.max_subspace = 10 self.max_iterations = 100 self.eps = 1e-7 - self.davidson_options = DavidsonOptions(self.max_subspace, - self.max_iterations, self.eps) + self.davidson_options = DavidsonOptions(self.max_subspace, self.max_iterations, self.eps) def test_init(self): """Tests vars in __init__().""" self.assertEqual(self.davidson_options.max_subspace, self.max_subspace) - self.assertEqual(self.davidson_options.max_iterations, - self.max_iterations) + self.assertEqual(self.davidson_options.max_iterations, self.max_iterations) self.assertAlmostEqual(self.davidson_options.eps, self.eps, places=8) self.assertFalse(self.davidson_options.real_only) @@ -90,7 +91,7 @@ def test_set_dimension_small(self): def test_set_dimension_large(self): """Tests set_dimension() with a large dimension not affecting - max_subspace.""" + max_subspace.""" self.davidson_options.set_dimension(60) self.assertEqual(self.davidson_options.max_subspace, self.max_subspace) @@ -116,7 +117,7 @@ def test_invalid_dimension(self): class DavidsonTest(unittest.TestCase): - """"Tests for Davidson class with a real matrix.""" + """ "Tests for Davidson class with a real matrix.""" def setUp(self): """Sets up all variables needed for Davidson class.""" @@ -128,38 +129,40 @@ def mat_vec(vec): return numpy.dot(matrix, vec) self.linear_operator = scipy.sparse.linalg.LinearOperator( - (dimension, dimension), matvec=mat_vec) + (dimension, dimension), matvec=mat_vec + ) self.diagonal = numpy.diag(matrix) - self.davidson = Davidson(linear_operator=self.linear_operator, - linear_operator_diagonal=self.diagonal) + self.davidson = Davidson( + linear_operator=self.linear_operator, linear_operator_diagonal=self.diagonal + ) self.matrix = matrix self.initial_guess = numpy.eye(self.matrix.shape[0], 10) - self.eigen_values = numpy.array([ - 1.15675714, - 1.59132505, - 2.62268014, - 4.44533793, - 5.3722743, - 5.54393114, - 7.73652405, - 8.50089897, - 9.4229309, - 15.54405993, - ]) + self.eigen_values = numpy.array( + [ + 1.15675714, + 1.59132505, + 2.62268014, + 4.44533793, + 5.3722743, + 5.54393114, + 7.73652405, + 8.50089897, + 9.4229309, + 15.54405993, + ] + ) def test_init(self): """Test for __init__().""" davidson = self.davidson - self.assertAlmostEqual( - numpy.max(numpy.abs(self.matrix - self.matrix.T)), 0) + self.assertAlmostEqual(numpy.max(numpy.abs(self.matrix - self.matrix.T)), 0) self.assertTrue(davidson.linear_operator) - self.assertTrue( - numpy.allclose(davidson.linear_operator_diagonal, self.diagonal)) + self.assertTrue(numpy.allclose(davidson.linear_operator_diagonal, self.diagonal)) # Options default values except max_subspace. self.assertEqual(davidson.options.max_subspace, 11) @@ -175,8 +178,8 @@ def test_with_built_in(self): # Checks for eigh() function. eigen_values, eigen_vectors = numpy.linalg.eigh(self.matrix) self.assertAlmostEqual( - get_difference(self.davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(self.davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) def test_lowest_invalid_operator(self): """Test for get_lowest_n() with invalid linear operator.""" @@ -191,23 +194,21 @@ def test_lowest_zero_n(self): def test_lowest_invalid_shape(self): """Test for get_lowest_n() with invalid dimension for initial guess.""" with self.assertRaises(ValueError): - self.davidson.get_lowest_n( - 1, numpy.ones((self.matrix.shape[0] * 2, 1), dtype=complex)) + self.davidson.get_lowest_n(1, numpy.ones((self.matrix.shape[0] * 2, 1), dtype=complex)) def test_get_lowest_n_trivial_guess(self): """Test for get_lowest_n() with trivial initial guess.""" with self.assertRaises(ValueError): - self.davidson.get_lowest_n( - 1, numpy.zeros((self.matrix.shape[0], 1), dtype=complex)) + self.davidson.get_lowest_n(1, numpy.zeros((self.matrix.shape[0], 1), dtype=complex)) def test_get_lowest_fail(self): """Test for get_lowest_n() with n_lowest = 1.""" n_lowest = 1 initial_guess = self.initial_guess[:, :n_lowest] - success, eigen_values, _ = self.davidson.get_lowest_n(n_lowest, - initial_guess, - max_iterations=2) + success, eigen_values, _ = self.davidson.get_lowest_n( + n_lowest, initial_guess, max_iterations=2 + ) self.assertTrue(not success) self.assertTrue(numpy.allclose(eigen_values, numpy.array([1.41556103]))) @@ -225,13 +226,12 @@ def test_get_lowest_one(self): n_lowest = 1 initial_guess = self.initial_guess[:, :n_lowest] - success, eigen_values, _ = self.davidson.get_lowest_n(n_lowest, - initial_guess, - max_iterations=10) + success, eigen_values, _ = self.davidson.get_lowest_n( + n_lowest, initial_guess, max_iterations=10 + ) self.assertTrue(success) - self.assertTrue( - numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) + self.assertTrue(numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) def test_get_lowest_two(self): """Test for get_lowest_n() with n_lowest = 2. @@ -247,14 +247,16 @@ def test_get_lowest_two(self): initial_guess = self.initial_guess[:, :n_lowest] success, eigen_values, eigen_vectors = self.davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=5) + n_lowest, initial_guess, max_iterations=5 + ) self.assertTrue(success) + self.assertTrue(numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) self.assertTrue( - numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) - self.assertTrue( - numpy.allclose(self.davidson.linear_operator * eigen_vectors, - eigen_vectors * eigen_values)) + numpy.allclose( + self.davidson.linear_operator * eigen_vectors, eigen_vectors * eigen_values + ) + ) def test_get_lowest_two_subspace(self): """Test for get_lowest_n() with n_lowest = 2. @@ -274,41 +276,42 @@ def test_get_lowest_two_subspace(self): initial_guess = self.initial_guess[:, :n_lowest] success, eigen_values, eigen_vectors = self.davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=5) + n_lowest, initial_guess, max_iterations=5 + ) self.assertTrue(not success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertFalse( - numpy.allclose(self.davidson.linear_operator * eigen_vectors, - eigen_vectors * eigen_values)) + numpy.allclose( + self.davidson.linear_operator * eigen_vectors, eigen_vectors * eigen_values + ) + ) def test_get_lowest_six(self): """Test for get_lowest_n() with n_lowest = 6.""" n_lowest = 6 initial_guess = self.initial_guess[:, :n_lowest] - success, eigen_values, _ = self.davidson.get_lowest_n(n_lowest, - initial_guess, - max_iterations=2) + success, eigen_values, _ = self.davidson.get_lowest_n( + n_lowest, initial_guess, max_iterations=2 + ) self.assertTrue(success) - self.assertTrue( - numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) + self.assertTrue(numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) def test_get_lowest_all(self): """Test for get_lowest_n() with n_lowest = 10.""" n_lowest = 10 initial_guess = self.initial_guess[:, :n_lowest] - success, eigen_values, _ = self.davidson.get_lowest_n(n_lowest, - initial_guess, - max_iterations=1) + success, eigen_values, _ = self.davidson.get_lowest_n( + n_lowest, initial_guess, max_iterations=1 + ) self.assertTrue(success) - self.assertTrue( - numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) + self.assertTrue(numpy.allclose(eigen_values, self.eigen_values[:n_lowest])) class QubitDavidsonTest(unittest.TestCase): - """"Tests for QubitDavidson class with a QubitOperator.""" + """ "Tests for QubitDavidson class with a QubitOperator.""" def setUp(self): """Sets up all variables needed for QubitDavidson class.""" @@ -321,8 +324,7 @@ def test_get_lowest_n(self): qubit_operator = QubitOperator.zero() for i in range(min(self.n_qubits, 4)): numpy.random.seed(dimension + i) - qubit_operator += QubitOperator(((i, 'Z'),), - numpy.random.rand(1)[0]) + qubit_operator += QubitOperator(((i, 'Z'),), numpy.random.rand(1)[0]) qubit_operator *= self.coefficient davidson = QubitDavidson(qubit_operator, self.n_qubits) @@ -330,15 +332,16 @@ def test_get_lowest_n(self): numpy.random.seed(dimension) initial_guess = numpy.random.rand(dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=20) + n_lowest, initial_guess, max_iterations=20 + ) expected_eigen_values = -3.80376934 * numpy.ones(n_lowest) self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) def test_get_lowest_zzx(self): """Test for get_lowest_n() for one term only within 10 iterations. @@ -351,7 +354,8 @@ def test_get_lowest_zzx(self): numpy.random.seed(dimension) initial_guess = numpy.random.rand(dimension, n_lowest // 2) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=10) + n_lowest, initial_guess, max_iterations=10 + ) # one half of the eigenvalues is -1 and the other half is +1, together # with the coefficient. @@ -360,8 +364,8 @@ def test_get_lowest_zzx(self): self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) def test_get_lowest_xyz(self): """Test for get_lowest_n() for one term only within 10 iterations.""" @@ -376,7 +380,8 @@ def test_get_lowest_xyz(self): numpy.random.seed(dimension * 2) initial_guess += numpy.random.rand(dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=10) + n_lowest, initial_guess, max_iterations=10 + ) # one half of the eigenvalues is -1 and the other half is +1, together # with the coefficient. @@ -385,8 +390,8 @@ def test_get_lowest_xyz(self): self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) def test_get_lowest_z_real(self): """Test for get_lowest_n() for z with real eigenvectors only.""" @@ -402,7 +407,8 @@ def test_get_lowest_z_real(self): numpy.random.seed(dimension * 2) initial_guess += numpy.random.rand(dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=10) + n_lowest, initial_guess, max_iterations=10 + ) # one half of the eigenvalues is -1 and the other half is +1, together # with the coefficient. @@ -411,11 +417,10 @@ def test_get_lowest_z_real(self): self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) # Real components only. - self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), - eigen_vectors)) + self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) def test_get_lowest_y_real_fail(self): """Test for get_lowest_n() for y with real eigenvectors only.""" @@ -431,15 +436,14 @@ def test_get_lowest_y_real_fail(self): initial_guess = 1.0j * numpy.random.rand(dimension, n_lowest) numpy.random.seed(dimension * 2) initial_guess += numpy.random.rand(dimension, n_lowest) - success, _, eigen_vectors = davidson.get_lowest_n(n_lowest, - initial_guess, - max_iterations=10) + success, _, eigen_vectors = davidson.get_lowest_n( + n_lowest, initial_guess, max_iterations=10 + ) self.assertFalse(success) # Not real components only. - self.assertFalse( - numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) + self.assertFalse(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) def test_get_lowest_y_real(self): """Test for get_lowest_n() for y with real eigenvectors only.""" @@ -455,7 +459,8 @@ def test_get_lowest_y_real(self): numpy.random.seed(dimension * 2) initial_guess += numpy.random.rand(dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=10) + n_lowest, initial_guess, max_iterations=10 + ) # one half of the eigenvalues is -1 and the other half is +1, together # with the coefficient. @@ -464,11 +469,10 @@ def test_get_lowest_y_real(self): self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) # Not real components only. - self.assertFalse( - numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) + self.assertFalse(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) def test_get_lowest_y_complex(self): """Test for get_lowest_n() for y with complex eigenvectors.""" @@ -484,7 +488,8 @@ def test_get_lowest_y_complex(self): numpy.random.seed(dimension * 2) initial_guess += numpy.random.rand(dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=10) + n_lowest, initial_guess, max_iterations=10 + ) # one half of the eigenvalues is -1 and the other half is +1, together # with the coefficient. @@ -493,86 +498,83 @@ def test_get_lowest_y_complex(self): self.assertTrue(success) self.assertTrue(numpy.allclose(eigen_values, expected_eigen_values)) self.assertAlmostEqual( - get_difference(davidson.linear_operator, eigen_values, - eigen_vectors), 0) + get_difference(davidson.linear_operator, eigen_values, eigen_vectors), 0 + ) class SparseDavidsonTest(unittest.TestCase): - """"Tests for SparseDavidson class with sparse matrices.""" + """ "Tests for SparseDavidson class with sparse matrices.""" def setUp(self): """Sets up all variables needed for SparseDavidson class.""" logging.basicConfig(level=logging.INFO) self.dimension = 1000 self.sparse_matrix = generate_sparse_matrix(self.dimension) - self.davidson_options = DavidsonOptions(max_subspace=100, - max_iterations=50, - real_only=True) + self.davidson_options = DavidsonOptions(max_subspace=100, max_iterations=50, real_only=True) # Checks for built-in eigh() function. - self.eigen_values, self.eigen_vectors = numpy.linalg.eigh( - self.sparse_matrix) + self.eigen_values, self.eigen_vectors = numpy.linalg.eigh(self.sparse_matrix) self.assertAlmostEqual( - get_difference(self.sparse_matrix, self.eigen_values, - self.eigen_vectors), 0) + get_difference(self.sparse_matrix, self.eigen_values, self.eigen_vectors), 0 + ) # Makes sure eigenvalues are sorted. self.eigen_values = sorted(self.eigen_values) def test_hermitain(self): """Test matrix used is Hermitian.""" - self.assertTrue( - numpy.allclose(self.sparse_matrix, - self.sparse_matrix.conj().T)) + self.assertTrue(numpy.allclose(self.sparse_matrix, self.sparse_matrix.conj().T)) def test_get_lowest_n_coo(self): """Test for get_lowest_n() as a coo_matrix.""" - davidson = SparseDavidson(scipy.sparse.coo_matrix(self.sparse_matrix), - self.davidson_options) + davidson = SparseDavidson( + scipy.sparse.coo_matrix(self.sparse_matrix), self.davidson_options + ) n_lowest = 2 initial_guess = numpy.eye(self.dimension, n_lowest) - success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess) + success, eigen_values, eigen_vectors = davidson.get_lowest_n(n_lowest, initial_guess) expected_eigen_values = self.eigen_values[:n_lowest] self.assertTrue(success) self.assertLess( - numpy.max(numpy.abs(eigen_values - expected_eigen_values)), - self.davidson_options.eps) + numpy.max(numpy.abs(eigen_values - expected_eigen_values)), self.davidson_options.eps + ) self.assertLess( get_difference(self.sparse_matrix, eigen_values, eigen_vectors), - self.davidson_options.eps) + self.davidson_options.eps, + ) # Real components only. - self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), - eigen_vectors)) + self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) def test_get_lowest_n_coo_complex(self): """Test for get_lowest_n() as a coo_matrix with real_only=False.""" self.davidson_options.real_only = False - davidson = SparseDavidson(scipy.sparse.coo_matrix(self.sparse_matrix), - self.davidson_options) + davidson = SparseDavidson( + scipy.sparse.coo_matrix(self.sparse_matrix), self.davidson_options + ) n_lowest = 2 initial_guess = numpy.eye(self.dimension, n_lowest) success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess, max_iterations=30) + n_lowest, initial_guess, max_iterations=30 + ) expected_eigen_values = self.eigen_values[:n_lowest] self.assertTrue(success) self.assertLess( - numpy.max(numpy.abs(eigen_values - expected_eigen_values)), - self.davidson_options.eps) + numpy.max(numpy.abs(eigen_values - expected_eigen_values)), self.davidson_options.eps + ) self.assertLess( get_difference(self.sparse_matrix, eigen_values, eigen_vectors), - self.davidson_options.eps) + self.davidson_options.eps, + ) # Real components only. - self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), - eigen_vectors)) + self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) def test_get_lowest_n(self): """Test for get_lowest_n() as a other sparse formats.""" @@ -581,38 +583,37 @@ def test_get_lowest_n(self): initial_guess = numpy.eye(self.dimension, n_lowest) for run_matrix in [ - scipy.sparse.bsr_matrix(self.sparse_matrix), - scipy.sparse.csc_matrix(self.sparse_matrix), - scipy.sparse.csr_matrix(self.sparse_matrix), - scipy.sparse.dia_matrix(self.sparse_matrix), - scipy.sparse.dok_matrix(self.sparse_matrix), - scipy.sparse.lil_matrix(self.sparse_matrix), + scipy.sparse.bsr_matrix(self.sparse_matrix), + scipy.sparse.csc_matrix(self.sparse_matrix), + scipy.sparse.csr_matrix(self.sparse_matrix), + scipy.sparse.dia_matrix(self.sparse_matrix), + scipy.sparse.dok_matrix(self.sparse_matrix), + scipy.sparse.lil_matrix(self.sparse_matrix), ]: davidson = SparseDavidson(run_matrix, self.davidson_options) - success, eigen_values, eigen_vectors = davidson.get_lowest_n( - n_lowest, initial_guess) + success, eigen_values, eigen_vectors = davidson.get_lowest_n(n_lowest, initial_guess) self.assertTrue(success) self.assertLess( numpy.max(numpy.abs(eigen_values - expected_eigen_values)), - self.davidson_options.eps) + self.davidson_options.eps, + ) self.assertLess( get_difference(self.sparse_matrix, eigen_values, eigen_vectors), - self.davidson_options.eps) + self.davidson_options.eps, + ) # Real components only. - self.assertTrue( - numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) + self.assertTrue(numpy.allclose(numpy.real(eigen_vectors), eigen_vectors)) class DavidsonUtilityTest(unittest.TestCase): - """"Tests for utility functions.""" + """ "Tests for utility functions.""" def test_append_random_vectors_0(self): """Test append_random_vectors() with too few columns.""" vectors = numpy.zeros((10, 2), dtype=complex) - self.assertTrue( - numpy.allclose(append_random_vectors(vectors, 0), vectors)) + self.assertTrue(numpy.allclose(append_random_vectors(vectors, 0), vectors)) def test_append_random_vectors(self): """Test append_random_vectors().""" @@ -627,8 +628,10 @@ def test_append_random_vectors(self): # Orthonormal. self.assertTrue( - numpy.allclose(numpy.dot(new_vectors.conj().T, new_vectors), - numpy.eye(col + add, col + add))) + numpy.allclose( + numpy.dot(new_vectors.conj().T, new_vectors), numpy.eye(col + add, col + add) + ) + ) def test_append_random_vectors_real(self): """Test append_random_vectors().""" @@ -643,8 +646,10 @@ def test_append_random_vectors_real(self): # Orthonormal. self.assertTrue( - numpy.allclose(numpy.dot(new_vectors.conj().T, new_vectors), - numpy.eye(col + add, col + add))) + numpy.allclose( + numpy.dot(new_vectors.conj().T, new_vectors), numpy.eye(col + add, col + add) + ) + ) # Real. self.assertTrue(numpy.allclose(numpy.real(new_vectors), new_vectors)) @@ -660,14 +665,11 @@ def test_append_vectors_big_col(self): def test_orthonormalize(self): """Test for orthonormalization with removing non-independent vectors.""" sqrt_half = numpy.sqrt(0.5) - expected_array = numpy.array([ - [sqrt_half, sqrt_half, 0], - [sqrt_half, -sqrt_half, 0], - [0, 0, 1], - ]) - - array = numpy.array([[1, 1, 10, 1], [1, -1, 10, 1], [0, 0, 2, 1]], - dtype=float) + expected_array = numpy.array( + [[sqrt_half, sqrt_half, 0], [sqrt_half, -sqrt_half, 0], [0, 0, 1]] + ) + + array = numpy.array([[1, 1, 10, 1], [1, -1, 10, 1], [0, 0, 2, 1]], dtype=float) array[:, 0] *= sqrt_half array = orthonormalize(array, 1) self.assertTrue(numpy.allclose(array, expected_array)) @@ -675,15 +677,16 @@ def test_orthonormalize(self): def test_orthonormalize_complex(self): """Test for orthonormalization with complex matrix.""" sqrt_half = numpy.sqrt(0.5) - expected_array = numpy.array([ - [sqrt_half * 1.0j, sqrt_half * 1.0j, 0], - [sqrt_half * 1.0j, -sqrt_half * 1.0j, 0], - [0, 0, 1], - ], - dtype=complex) - - array = numpy.array([[1.j, 1.j, 10], [1.j, -1.j, 10], [0, 0, 2]], - dtype=complex) + expected_array = numpy.array( + [ + [sqrt_half * 1.0j, sqrt_half * 1.0j, 0], + [sqrt_half * 1.0j, -sqrt_half * 1.0j, 0], + [0, 0, 1], + ], + dtype=complex, + ) + + array = numpy.array([[1.0j, 1.0j, 10], [1.0j, -1.0j, 10], [0, 0, 2]], dtype=complex) array[:, 0] *= sqrt_half array = orthonormalize(array, 1) self.assertTrue(numpy.allclose(array, expected_array)) diff --git a/src/openfermion/linalg/erpa.py b/src/openfermion/linalg/erpa.py index b0936a90e..33f89f83a 100644 --- a/src/openfermion/linalg/erpa.py +++ b/src/openfermion/linalg/erpa.py @@ -8,8 +8,9 @@ from openfermion.utils.rdm_mapping_functions import map_two_pdm_to_one_pdm -def erpa_eom_hamiltonian(h_ijkl: numpy.ndarray, tpdm: numpy.ndarray, p: int, - q: int, r: int, s: int) -> Union[float, complex]: +def erpa_eom_hamiltonian( + h_ijkl: numpy.ndarray, tpdm: numpy.ndarray, p: int, q: int, r: int, s: int +) -> Union[float, complex]: """ Evaluate sum_{a,b,c,d}h_{a, b, d, c} @@ -77,8 +78,9 @@ def erpa_eom_hamiltonian(h_ijkl: numpy.ndarray, tpdm: numpy.ndarray, p: int, return h_mat -def singlet_erpa(tpdm: numpy.ndarray, h_ijkl: numpy.ndarray) \ - -> Tuple[numpy.ndarray, numpy.ndarray, Dict]: +def singlet_erpa( + tpdm: numpy.ndarray, h_ijkl: numpy.ndarray +) -> Tuple[numpy.ndarray, numpy.ndarray, Dict]: """ Generate the singlet ERPA equations @@ -113,19 +115,24 @@ def singlet_erpa(tpdm: numpy.ndarray, h_ijkl: numpy.ndarray) \ for ckey, cidx in full_basis.items(): r, s = ckey for sigma, tau in product([0, 1], repeat=2): - erpa_mat[ridx, cidx] += 0.5 * erpa_eom_hamiltonian( - permuted_hijkl, tpdm, 2 * q + sigma, 2 * p + sigma, - 2 * r + tau, 2 * s + tau).real - metric_mat[ridx, cidx] += 0.5 * ( - opdm[2 * q + sigma, 2 * s + tau] * - kdelta(2 * r + tau, 2 * p + sigma) - - opdm[2 * p + sigma, 2 * r + tau] * - kdelta(2 * q + sigma, 2 * s + tau)).real + erpa_mat[ridx, cidx] += ( + 0.5 + * erpa_eom_hamiltonian( + permuted_hijkl, tpdm, 2 * q + sigma, 2 * p + sigma, 2 * r + tau, 2 * s + tau + ).real + ) + metric_mat[ridx, cidx] += ( + 0.5 + * ( + opdm[2 * q + sigma, 2 * s + tau] * kdelta(2 * r + tau, 2 * p + sigma) + - opdm[2 * p + sigma, 2 * r + tau] * kdelta(2 * q + sigma, 2 * s + tau) + ).real + ) # The metric is hermetian and can be diagonalized # this allows us to project into the non-zero eigenvalue space ws, vs = numpy.linalg.eigh(metric_mat) - non_zero_idx = numpy.where(numpy.abs(ws) > 1.0E-8)[0] + non_zero_idx = numpy.where(numpy.abs(ws) > 1.0e-8)[0] left_mat = vs[:, non_zero_idx].T @ erpa_mat @ vs[:, non_zero_idx] right_mat = vs[:, non_zero_idx].T @ metric_mat @ vs[:, non_zero_idx] @@ -134,11 +141,10 @@ def singlet_erpa(tpdm: numpy.ndarray, h_ijkl: numpy.ndarray) \ # the spectrum is symmetric (-w, w) # find the positive spectrum eigensystem and return - real_eig_idx = numpy.where(numpy.abs(w.imag) < 1.0E-8)[0] + real_eig_idx = numpy.where(numpy.abs(w.imag) < 1.0e-8)[0] real_eigs = w[real_eig_idx] real_eig_vecs = v[:, real_eig_idx] reverse_projected_eig_vecs = vs[:, non_zero_idx] @ real_eig_vecs pos_indices = numpy.where(real_eigs > 0)[0] pos_indices = pos_indices[numpy.argsort(real_eigs[pos_indices])] - return real_eigs[pos_indices], reverse_projected_eig_vecs[:, pos_indices], \ - full_basis + return real_eigs[pos_indices], reverse_projected_eig_vecs[:, pos_indices], full_basis diff --git a/src/openfermion/linalg/erpa_test.py b/src/openfermion/linalg/erpa_test.py index c2624920d..490cc298f 100644 --- a/src/openfermion/linalg/erpa_test.py +++ b/src/openfermion/linalg/erpa_test.py @@ -16,14 +16,13 @@ def test_h2_rpa(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) - hf_opdm = np.diag([1] * molecule.n_electrons + [0] * - (molecule.n_qubits - molecule.n_electrons)) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) + hf_opdm = np.diag([1] * molecule.n_electrons + [0] * (molecule.n_qubits - molecule.n_electrons)) hf_tpdm = 2 * wedge(hf_opdm, hf_opdm, (1, 1), (1, 1)) - pos_spectrum, xy_eigvects, basis = singlet_erpa(hf_tpdm, - reduced_ham.two_body_tensor) + pos_spectrum, xy_eigvects, basis = singlet_erpa(hf_tpdm, reduced_ham.two_body_tensor) assert np.isclose(pos_spectrum, 0.92926444) # pyscf-rpa value assert isinstance(xy_eigvects, np.ndarray) assert isinstance(basis, dict) @@ -32,12 +31,12 @@ def test_h2_rpa(): def test_erpa_eom_ham_h2(): filename = os.path.join(DATA_DIRECTORY, "H2_sto-3g_singlet_0.7414.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) rha_fermion = get_fermion_operator(reduced_ham) permuted_hijkl = np.einsum('ijlk', reduced_ham.two_body_tensor) - opdm = np.diag([1] * molecule.n_electrons + [0] * - (molecule.n_qubits - molecule.n_electrons)) + opdm = np.diag([1] * molecule.n_electrons + [0] * (molecule.n_qubits - molecule.n_electrons)) tpdm = 2 * wedge(opdm, opdm, (1, 1), (1, 1)) rdms = InteractionRDM(opdm, tpdm) dim = reduced_ham.one_body_tensor.shape[0] // 2 @@ -53,14 +52,12 @@ def test_erpa_eom_ham_h2(): for ckey in full_basis.keys(): r, s = ckey for sigma, tau in product([0, 1], repeat=2): - test = erpa_eom_hamiltonian(permuted_hijkl, tpdm, 2 * q + sigma, - 2 * p + sigma, 2 * r + tau, - 2 * s + tau).real - qp_op = FermionOperator( - ((2 * q + sigma, 1), (2 * p + sigma, 0))) + test = erpa_eom_hamiltonian( + permuted_hijkl, tpdm, 2 * q + sigma, 2 * p + sigma, 2 * r + tau, 2 * s + tau + ).real + qp_op = FermionOperator(((2 * q + sigma, 1), (2 * p + sigma, 0))) rs_op = FermionOperator(((2 * r + tau, 1), (2 * s + tau, 0))) - erpa_op = normal_ordered( - commutator(qp_op, commutator(rha_fermion, rs_op))) + erpa_op = normal_ordered(commutator(qp_op, commutator(rha_fermion, rs_op))) true = rdms.expectation(get_interaction_operator(erpa_op)) assert np.isclose(true, test) @@ -68,12 +65,12 @@ def test_erpa_eom_ham_h2(): def test_erpa_eom_ham_lih(): filename = os.path.join(DATA_DIRECTORY, "H1-Li1_sto-3g_singlet_1.45.hdf5") molecule = MolecularData(filename=filename) - reduced_ham = make_reduced_hamiltonian(molecule.get_molecular_hamiltonian(), - molecule.n_electrons) + reduced_ham = make_reduced_hamiltonian( + molecule.get_molecular_hamiltonian(), molecule.n_electrons + ) rha_fermion = get_fermion_operator(reduced_ham) permuted_hijkl = np.einsum('ijlk', reduced_ham.two_body_tensor) - opdm = np.diag([1] * molecule.n_electrons + [0] * - (molecule.n_qubits - molecule.n_electrons)) + opdm = np.diag([1] * molecule.n_electrons + [0] * (molecule.n_qubits - molecule.n_electrons)) tpdm = 2 * wedge(opdm, opdm, (1, 1), (1, 1)) rdms = InteractionRDM(opdm, tpdm) dim = 3 # so we don't do the full basis. This would make the test long @@ -90,13 +87,11 @@ def test_erpa_eom_ham_lih(): for ckey in full_basis.keys(): r, s = ckey for sigma, tau in product([0, 1], repeat=2): - test = erpa_eom_hamiltonian(permuted_hijkl, tpdm, 2 * q + sigma, - 2 * p + sigma, 2 * r + tau, - 2 * s + tau).real - qp_op = FermionOperator( - ((2 * q + sigma, 1), (2 * p + sigma, 0))) + test = erpa_eom_hamiltonian( + permuted_hijkl, tpdm, 2 * q + sigma, 2 * p + sigma, 2 * r + tau, 2 * s + tau + ).real + qp_op = FermionOperator(((2 * q + sigma, 1), (2 * p + sigma, 0))) rs_op = FermionOperator(((2 * r + tau, 1), (2 * s + tau, 0))) - erpa_op = normal_ordered( - commutator(qp_op, commutator(rha_fermion, rs_op))) + erpa_op = normal_ordered(commutator(qp_op, commutator(rha_fermion, rs_op))) true = rdms.expectation(get_interaction_operator(erpa_op)) assert np.isclose(true, test) diff --git a/src/openfermion/linalg/givens_rotations.py b/src/openfermion/linalg/givens_rotations.py index 74ce0e1a6..645f2e92a 100644 --- a/src/openfermion/linalg/givens_rotations.py +++ b/src/openfermion/linalg/givens_rotations.py @@ -41,17 +41,17 @@ def givens_matrix_elements(a, b, which='left'): """ # Handle case that a is zero if abs(a) < EQ_TOLERANCE: - cosine = 1. - sine = 0. - phase = 1. + cosine = 1.0 + sine = 0.0 + phase = 1.0 # Handle case that b is zero and a is nonzero elif abs(b) < EQ_TOLERANCE: - cosine = 0. - sine = 1. - phase = 1. + cosine = 0.0 + sine = 1.0 + phase = 1.0 # Handle case that a and b are both nonzero else: - denominator = numpy.sqrt(abs(a)**2 + abs(b)**2) + denominator = numpy.sqrt(abs(a) ** 2 + abs(b) ** 2) cosine = abs(b) / denominator sine = abs(a) / denominator sign_b = b / abs(b) @@ -64,24 +64,18 @@ def givens_matrix_elements(a, b, which='left'): # Construct matrix and return if which == 'left': # We want to zero out a - if (abs(numpy.imag(a)) < EQ_TOLERANCE and - abs(numpy.imag(b)) < EQ_TOLERANCE): + if abs(numpy.imag(a)) < EQ_TOLERANCE and abs(numpy.imag(b)) < EQ_TOLERANCE: # a and b are real, so return a standard rotation matrix - givens_rotation = numpy.array([[cosine, -phase * sine], - [phase * sine, cosine]]) + givens_rotation = numpy.array([[cosine, -phase * sine], [phase * sine, cosine]]) else: - givens_rotation = numpy.array([[cosine, -phase * sine], - [sine, phase * cosine]]) + givens_rotation = numpy.array([[cosine, -phase * sine], [sine, phase * cosine]]) elif which == 'right': # We want to zero out b - if (abs(numpy.imag(a)) < EQ_TOLERANCE and - abs(numpy.imag(b)) < EQ_TOLERANCE): + if abs(numpy.imag(a)) < EQ_TOLERANCE and abs(numpy.imag(b)) < EQ_TOLERANCE: # a and b are real, so return a standard rotation matrix - givens_rotation = numpy.array([[sine, phase * cosine], - [-phase * cosine, sine]]) + givens_rotation = numpy.array([[sine, phase * cosine], [-phase * cosine, sine]]) else: - givens_rotation = numpy.array([[sine, phase * cosine], - [cosine, -phase * sine]]) + givens_rotation = numpy.array([[sine, phase * cosine], [cosine, -phase * sine]]) else: raise ValueError('"which" must be equal to "left" or "right".') return givens_rotation @@ -93,18 +87,14 @@ def givens_rotate(operator, givens_rotation, i, j, which='row'): # Rotate rows i and j row_i = operator[i].copy() row_j = operator[j].copy() - operator[i] = (givens_rotation[0, 0] * row_i + - givens_rotation[0, 1] * row_j) - operator[j] = (givens_rotation[1, 0] * row_i + - givens_rotation[1, 1] * row_j) + operator[i] = givens_rotation[0, 0] * row_i + givens_rotation[0, 1] * row_j + operator[j] = givens_rotation[1, 0] * row_i + givens_rotation[1, 1] * row_j elif which == 'col': # Rotate columns i and j col_i = operator[:, i].copy() col_j = operator[:, j].copy() - operator[:, i] = (givens_rotation[0, 0] * col_i + - givens_rotation[0, 1].conj() * col_j) - operator[:, j] = (givens_rotation[1, 0] * col_i + - givens_rotation[1, 1].conj() * col_j) + operator[:, i] = givens_rotation[0, 0] * col_i + givens_rotation[0, 1].conj() * col_j + operator[:, j] = givens_rotation[1, 0] * col_i + givens_rotation[1, 1].conj() * col_j else: raise ValueError('"which" must be equal to "row" or "col".') @@ -120,8 +110,9 @@ def double_givens_rotate(operator, givens_rotation, i, j, which='row'): if which == 'row': if m % 2 != 0: - raise ValueError('To apply a double Givens rotation on rows, ' - 'the number of rows must be even.') + raise ValueError( + 'To apply a double Givens rotation on rows, ' 'the number of rows must be even.' + ) n = m // 2 # Rotate rows i and j givens_rotate(operator[:n], givens_rotation, i, j, which='row') @@ -129,17 +120,15 @@ def double_givens_rotate(operator, givens_rotation, i, j, which='row'): givens_rotate(operator[n:], givens_rotation.conj(), i, j, which='row') elif which == 'col': if p % 2 != 0: - raise ValueError('To apply a double Givens rotation on columns, ' - 'the number of columns must be even.') + raise ValueError( + 'To apply a double Givens rotation on columns, ' + 'the number of columns must be even.' + ) n = p // 2 # Rotate columns i and j givens_rotate(operator[:, :n], givens_rotation, i, j, which='col') # Rotate cols n + i and n + j - givens_rotate(operator[:, n:], - givens_rotation.conj(), - i, - j, - which='col') + givens_rotate(operator[:, n:], givens_rotation.conj(), i, j, which='col') else: raise ValueError('"which" must be equal to "row" or "col".') @@ -219,9 +208,7 @@ def givens_decomposition_square(unitary_matrix, always_insert=False): if always_insert or abs(right_element) > EQ_TOLERANCE: # We actually need to perform a Givens rotation left_element = current_matrix[i, j - 1].conj() - givens_rotation = givens_matrix_elements(left_element, - right_element, - which='right') + givens_rotation = givens_matrix_elements(left_element, right_element, which='right') # Add the parameters to the list theta = numpy.arcsin(numpy.real(givens_rotation[1, 0])) @@ -229,11 +216,7 @@ def givens_decomposition_square(unitary_matrix, always_insert=False): parallel_ops.append((j - 1, j, theta, phi)) # Update the matrix - givens_rotate(current_matrix, - givens_rotation, - j - 1, - j, - which='col') + givens_rotate(current_matrix, givens_rotation, j - 1, j, which='col') # If the current list of parallel operations is not empty, # append it to the list, @@ -314,7 +297,8 @@ def givens_decomposition(unitary_rows, always_insert=False): # Zero out entry in row l if needed if abs(current_matrix[l, k]) > EQ_TOLERANCE: givens_rotation = givens_matrix_elements( - current_matrix[l, k], current_matrix[l + 1, k]) + current_matrix[l, k], current_matrix[l + 1, k] + ) # Apply Givens rotation givens_rotate(current_matrix, givens_rotation, l, l + 1) givens_rotate(left_unitary, givens_rotation, l, l + 1) @@ -368,9 +352,9 @@ def givens_decomposition(unitary_rows, always_insert=False): if always_insert or abs(right_element) > EQ_TOLERANCE: # We actually need to perform a Givens rotation left_element = current_matrix[i, j - 1].conj() - givens_rotation = givens_matrix_elements(left_element, - right_element, - which='right') + givens_rotation = givens_matrix_elements( + left_element, right_element, which='right' + ) # Add the parameters to the list theta = numpy.arcsin(numpy.real(givens_rotation[1, 0])) @@ -378,11 +362,7 @@ def givens_decomposition(unitary_rows, always_insert=False): parallel_rotations.append((j - 1, j, theta, phi)) # Update the matrix - givens_rotate(current_matrix, - givens_rotation, - j - 1, - j, - which='col') + givens_rotate(current_matrix, givens_rotation, j - 1, j, which='col') # If the current list of parallel operations is not empty, # append it to the list, @@ -469,24 +449,23 @@ def fermionic_gaussian_decomposition(unitary_rows): # Check that p = 2 * n if p != 2 * n: - raise ValueError('The input matrix must have twice as many columns ' - 'as rows.') + raise ValueError('The input matrix must have twice as many columns ' 'as rows.') # Check that left and right parts of unitary_rows satisfy the constraints # necessary for the transformed fermionic operators to satisfy # the fermionic anticommutation relations left_part = unitary_rows[:, :n] right_part = unitary_rows[:, n:] - constraint_matrix_1 = (left_part.dot(left_part.T.conj()) + - right_part.dot(right_part.T.conj())) - constraint_matrix_2 = (left_part.dot(right_part.T) + - right_part.dot(left_part.T)) + constraint_matrix_1 = left_part.dot(left_part.T.conj()) + right_part.dot(right_part.T.conj()) + constraint_matrix_2 = left_part.dot(right_part.T) + right_part.dot(left_part.T) discrepancy_1 = numpy.amax(abs(constraint_matrix_1 - numpy.eye(n))) discrepancy_2 = numpy.amax(abs(constraint_matrix_2)) if discrepancy_1 > EQ_TOLERANCE or discrepancy_2 > EQ_TOLERANCE: - raise ValueError('The input matrix does not satisfy the constraints ' - 'necessary for a proper transformation of the ' - 'fermionic ladder operators.') + raise ValueError( + 'The input matrix does not satisfy the constraints ' + 'necessary for a proper transformation of the ' + 'fermionic ladder operators.' + ) # Compute left_unitary using Givens rotations left_unitary = numpy.eye(n, dtype=complex) @@ -496,7 +475,8 @@ def fermionic_gaussian_decomposition(unitary_rows): # Zero out entry in row l if needed if abs(current_matrix[l, k]) > EQ_TOLERANCE: givens_rotation = givens_matrix_elements( - current_matrix[l, k], current_matrix[l + 1, k]) + current_matrix[l, k], current_matrix[l + 1, k] + ) # Apply Givens rotation givens_rotate(current_matrix, givens_rotation, l, l + 1) givens_rotate(left_unitary, givens_rotation, l, l + 1) @@ -532,8 +512,7 @@ def fermionic_gaussian_decomposition(unitary_rows): if abs(left_element) > EQ_TOLERANCE: # We actually need to perform a Givens rotation right_element = current_matrix[i, j + 1].conj() - givens_rotation = givens_matrix_elements( - left_element, right_element) + givens_rotation = givens_matrix_elements(left_element, right_element) # Add the parameters to the list theta = numpy.arcsin(numpy.real(givens_rotation[1, 0])) @@ -541,11 +520,7 @@ def fermionic_gaussian_decomposition(unitary_rows): parallel_ops.append((j, j + 1, theta, phi)) # Update the matrix - double_givens_rotate(current_matrix, - givens_rotation, - j, - j + 1, - which='col') + double_givens_rotate(current_matrix, givens_rotation, j, j + 1, which='col') # If the current list of parallel operations is not empty, # append it to the list, @@ -560,8 +535,7 @@ def fermionic_gaussian_decomposition(unitary_rows): for k in range(n): current_matrix[:, k] *= diagonal[k].conj() - left_decomposition, left_diagonal = givens_decomposition_square( - current_matrix) + left_decomposition, left_diagonal = givens_decomposition_square(current_matrix) return decomposition, left_decomposition, diagonal, left_diagonal diff --git a/src/openfermion/linalg/givens_rotations_test.py b/src/openfermion/linalg/givens_rotations_test.py index c8825f1b4..d6e7b77df 100644 --- a/src/openfermion/linalg/givens_rotations_test.py +++ b/src/openfermion/linalg/givens_rotations_test.py @@ -13,17 +13,21 @@ import numpy -from openfermion.testing.testing_utils import (random_quadratic_hamiltonian, - random_unitary_matrix) +from openfermion.testing.testing_utils import random_quadratic_hamiltonian, random_unitary_matrix from openfermion.linalg.givens_rotations import ( - double_givens_rotate, fermionic_gaussian_decomposition, swap_columns, - givens_matrix_elements, givens_rotate, givens_decomposition, swap_rows, - givens_decomposition_square) + double_givens_rotate, + fermionic_gaussian_decomposition, + swap_columns, + givens_matrix_elements, + givens_rotate, + givens_decomposition, + swap_rows, + givens_decomposition_square, +) class SwapTests(unittest.TestCase): - def test_swap_rows(self): A = numpy.array([0, 1]) swap_rows(A, 0, 1) @@ -50,20 +54,19 @@ def test_swap_columns(self): class GivensMatrixElementsTest(unittest.TestCase): - def setUp(self): self.num_test_repetitions = 5 def test_already_zero(self): """Test when some entries are already zero.""" # Test when left entry is zero - v = numpy.array([0., numpy.random.randn()]) + v = numpy.array([0.0, numpy.random.randn()]) G = givens_matrix_elements(v[0], v[1]) - self.assertAlmostEqual(G.dot(v)[0], 0.) + self.assertAlmostEqual(G.dot(v)[0], 0.0) # Test when right entry is zero - v = numpy.array([numpy.random.randn(), 0.]) + v = numpy.array([numpy.random.randn(), 0.0]) G = givens_matrix_elements(v[0], v[1]) - self.assertAlmostEqual(G.dot(v)[0], 0.) + self.assertAlmostEqual(G.dot(v)[0], 0.0) def test_real(self): """Test that the procedure works for real numbers.""" @@ -71,19 +74,19 @@ def test_real(self): v = numpy.random.randn(2) G_left = givens_matrix_elements(v[0], v[1], which='left') G_right = givens_matrix_elements(v[0], v[1], which='right') - self.assertAlmostEqual(G_left.dot(v)[0], 0.) - self.assertAlmostEqual(G_right.dot(v)[1], 0.) + self.assertAlmostEqual(G_left.dot(v)[0], 0.0) + self.assertAlmostEqual(G_right.dot(v)[1], 0.0) def test_approximately_real(self): """Test that the procedure throws out small imaginary components.""" for _ in range(self.num_test_repetitions): - v = numpy.random.randn(2) + 1.j * 1e-14 + v = numpy.random.randn(2) + 1.0j * 1e-14 G_left = givens_matrix_elements(v[0], v[1], which='left') G_right = givens_matrix_elements(v[0], v[1], which='right') self.assertAlmostEqual(G_left[0, 0], G_left[1, 1]) self.assertAlmostEqual(G_right[0, 0], G_right[1, 1]) - self.assertAlmostEqual(G_left.dot(v)[0], 0.) - self.assertAlmostEqual(G_right.dot(v)[1], 0.) + self.assertAlmostEqual(G_left.dot(v)[0], 0.0) + self.assertAlmostEqual(G_right.dot(v)[1], 0.0) def test_bad_input(self): """Test bad input.""" @@ -93,7 +96,6 @@ def test_bad_input(self): class GivensRotateTest(unittest.TestCase): - def test_bad_input(self): """Test bad input.""" with self.assertRaises(ValueError): @@ -103,7 +105,6 @@ def test_bad_input(self): class DoubleGivensRotateTest(unittest.TestCase): - def test_odd_dimension(self): """Test that it raises an error for odd-dimensional input.""" A = numpy.random.randn(3, 3) @@ -124,10 +125,18 @@ def test_bad_input(self): class GivensDecompositionTest(unittest.TestCase): - def setUp(self): - self.test_dimensions = [(3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), - (4, 7), (4, 8), (4, 9)] + self.test_dimensions = [ + (3, 4), + (3, 5), + (3, 6), + (3, 7), + (3, 8), + (3, 9), + (4, 7), + (4, 8), + (4, 9), + ] def test_forced_insertion(self): Q = numpy.zeros([2, 4]) @@ -156,9 +165,8 @@ def test_main_procedure(self): for i, j, theta, phi in reversed(parallel_set): c = numpy.cos(theta) s = numpy.sin(theta) - phase = numpy.exp(1.j * phi) - G = numpy.array([[c, -phase * s], [s, phase * c]], - dtype=complex) + phase = numpy.exp(1.0j * phi) + G = numpy.array([[c, -phase * s], [s, phase * c]], dtype=complex) givens_rotate(combined_givens, G, i, j) U = combined_givens.dot(U) @@ -190,9 +198,8 @@ def test_real_numbers(self): for i, j, theta, phi in reversed(parallel_set): c = numpy.cos(theta) s = numpy.sin(theta) - phase = numpy.exp(1.j * phi) - G = numpy.array([[c, -phase * s], [s, phase * c]], - dtype=complex) + phase = numpy.exp(1.0j * phi) + G = numpy.array([[c, -phase * s], [s, phase * c]], dtype=complex) givens_rotate(combined_givens, G, i, j) U = combined_givens.dot(U) @@ -235,14 +242,14 @@ def test_identity(self): # The diagonal should be ones for d in diagonal: - self.assertAlmostEqual(d, 1.) + self.assertAlmostEqual(d, 1.0) def test_antidiagonal(self): m, n = (3, 3) Q = numpy.zeros((m, n), dtype=complex) - Q[0, 2] = 1. - Q[1, 1] = 1. - Q[2, 0] = 1. + Q[0, 2] = 1.0 + Q[1, 1] = 1.0 + Q[2, 0] = 1.0 givens_rotations, V, diagonal = givens_decomposition(Q) # There should be no Givens rotations @@ -284,7 +291,6 @@ def test_square(self): class FermionicGaussianDecompositionTest(unittest.TestCase): - def setUp(self): self.test_dimensions = [3, 4, 5, 6, 7, 8, 9] @@ -294,8 +300,7 @@ def test_main_procedure(self): quadratic_hamiltonian = random_quadratic_hamiltonian(n) # Get the diagonalizing transformation - _, transformation_matrix, _ = ( - quadratic_hamiltonian.diagonalizing_bogoliubov_transform()) + _, transformation_matrix, _ = quadratic_hamiltonian.diagonalizing_bogoliubov_transform() left_block = transformation_matrix[:, :n] right_block = transformation_matrix[:, n:] lower_unitary = numpy.empty((n, 2 * n), dtype=complex) @@ -303,8 +308,12 @@ def test_main_procedure(self): lower_unitary[:, n:] = numpy.conjugate(left_block) # Get fermionic Gaussian decomposition of lower_unitary - decomposition, left_decomposition, diagonal, left_diagonal = ( - fermionic_gaussian_decomposition(lower_unitary)) + ( + decomposition, + left_decomposition, + diagonal, + left_diagonal, + ) = fermionic_gaussian_decomposition(lower_unitary) # Compute left_unitary left_unitary = numpy.eye(n, dtype=complex) @@ -314,9 +323,8 @@ def test_main_procedure(self): i, j, theta, phi = op c = numpy.cos(theta) s = numpy.sin(theta) - phase = numpy.exp(1.j * phi) - givens_rotation = numpy.array( - [[c, -phase * s], [s, phase * c]], dtype=complex) + phase = numpy.exp(1.0j * phi) + givens_rotation = numpy.array([[c, -phase * s], [s, phase * c]], dtype=complex) givens_rotate(combined_op, givens_rotation, i, j) left_unitary = combined_op.dot(left_unitary) for i in range(n): @@ -330,7 +338,7 @@ def test_main_procedure(self): product = left_unitary.dot(lower_unitary) for i in range(n - 1): for j in range(n - 1 - i): - self.assertAlmostEqual(product[i, j], 0.) + self.assertAlmostEqual(product[i, j], 0.0) # Compute right_unitary right_unitary = numpy.eye(2 * n, dtype=complex) @@ -343,15 +351,15 @@ def test_main_procedure(self): i, j, theta, phi = op c = numpy.cos(theta) s = numpy.sin(theta) - phase = numpy.exp(1.j * phi) + phase = numpy.exp(1.0j * phi) givens_rotation = numpy.array( - [[c, -phase * s], [s, phase * c]], dtype=complex) + [[c, -phase * s], [s, phase * c]], dtype=complex + ) double_givens_rotate(combined_op, givens_rotation, i, j) right_unitary = combined_op.dot(right_unitary) # Compute left_unitary * lower_unitary * right_unitary^\dagger - product = left_unitary.dot(lower_unitary.dot( - right_unitary.T.conj())) + product = left_unitary.dot(lower_unitary.dot(right_unitary.T.conj())) # Construct the diagonal matrix diag = numpy.zeros((n, 2 * n), dtype=complex) diff --git a/src/openfermion/linalg/linear_qubit_operator.py b/src/openfermion/linalg/linear_qubit_operator.py index 95ef2aea5..a5b326c27 100644 --- a/src/openfermion/linalg/linear_qubit_operator.py +++ b/src/openfermion/linalg/linear_qubit_operator.py @@ -33,9 +33,7 @@ def __init__(self, processes=10, pool=None): pool(multiprocessing.Pool): A pool of workers. """ if processes <= 0: - raise ValueError( - 'Invalid number of processors specified {} <= 0'.format( - processes)) + raise ValueError('Invalid number of processors specified {} <= 0'.format(processes)) self.processes = min(processes, multiprocessing.cpu_count()) self.pool = pool @@ -90,12 +88,13 @@ def __init__(self, qubit_operator, n_qubits=None): if n_qubits is None: n_qubits = calculated_n_qubits elif n_qubits < calculated_n_qubits: - raise ValueError('Invalid number of qubits specified ' - '{} < {}.'.format(n_qubits, calculated_n_qubits)) + raise ValueError( + 'Invalid number of qubits specified ' + '{} < {}.'.format(n_qubits, calculated_n_qubits) + ) n_hilbert = 2**n_qubits - super(LinearQubitOperator, self).__init__(shape=(n_hilbert, n_hilbert), - dtype=complex) + super(LinearQubitOperator, self).__init__(shape=(n_hilbert, n_hilbert), dtype=complex) self.qubit_operator = qubit_operator self.n_qubits = n_qubits @@ -119,8 +118,9 @@ def _matvec(self, x): # Split vector by half and half for each bit. if pauli_operator[0] > tensor_factor: vecs = [ - v for iter_v in vecs for v in numpy.split( - iter_v, 2**(pauli_operator[0] - tensor_factor)) + v + for iter_v in vecs + for v in numpy.split(iter_v, 2 ** (pauli_operator[0] - tensor_factor)) ] # Note that this is to make sure that XYZ operations always work @@ -133,9 +133,7 @@ def _matvec(self, x): 'Y': lambda vps: [[-1j * vp[1], 1j * vp[0]] for vp in vps], 'Z': lambda vps: [[vp[0], -vp[1]] for vp in vps], } - vecs = [ - v for vp in xyz[pauli_operator[1]](vec_pairs) for v in vp - ] + vecs = [v for vp in xyz[pauli_operator[1]](vec_pairs) for v in vp] tensor_factor = pauli_operator[0] + 1 # No need to check tensor_factor, i.e. to deal with bits left. @@ -156,18 +154,19 @@ def __init__(self, qubit_operator, n_qubits=None, options=None): """ n_qubits = n_qubits or count_qubits(qubit_operator) n_hilbert = 2**n_qubits - super(ParallelLinearQubitOperator, - self).__init__(shape=(n_hilbert, n_hilbert), dtype=complex) + super(ParallelLinearQubitOperator, self).__init__( + shape=(n_hilbert, n_hilbert), dtype=complex + ) self.qubit_operator = qubit_operator self.n_qubits = n_qubits self.options = options or LinearQubitOperatorOptions() self.qubit_operator_groups = list( - qubit_operator.get_operator_groups(self.options.processes)) + qubit_operator.get_operator_groups(self.options.processes) + ) self.linear_operators = [ - LinearQubitOperator(operator, n_qubits) - for operator in self.qubit_operator_groups + LinearQubitOperator(operator, n_qubits) for operator in self.qubit_operator_groups ] def _matvec(self, x): @@ -184,8 +183,8 @@ def _matvec(self, x): pool = self.options.get_pool(len(self.linear_operators)) vecs = pool.imap_unordered( - apply_operator, - [(operator, x) for operator in self.linear_operators]) + apply_operator, [(operator, x) for operator in self.linear_operators] + ) pool.close() pool.join() return functools.reduce(numpy.add, vecs) @@ -198,7 +197,7 @@ def apply_operator(args): def generate_linear_qubit_operator(qubit_operator, n_qubits=None, options=None): - """ Generates a LinearOperator from a QubitOperator. + """Generates a LinearOperator from a QubitOperator. Args: qubit_operator(QubitOperator): A qubit operator to be applied on @@ -212,6 +211,5 @@ def generate_linear_qubit_operator(qubit_operator, n_qubits=None, options=None): if options is None: linear_operator = LinearQubitOperator(qubit_operator, n_qubits) else: - linear_operator = ParallelLinearQubitOperator(qubit_operator, n_qubits, - options) + linear_operator = ParallelLinearQubitOperator(qubit_operator, n_qubits, options) return linear_operator diff --git a/src/openfermion/linalg/linear_qubit_operator_test.py b/src/openfermion/linalg/linear_qubit_operator_test.py index 0a8819546..8fb35f95a 100644 --- a/src/openfermion/linalg/linear_qubit_operator_test.py +++ b/src/openfermion/linalg/linear_qubit_operator_test.py @@ -48,8 +48,7 @@ def test_get_processes_small(self): def test_get_processes_large(self): """Tests get_processes() with a large num.""" - self.assertEqual(self.options.get_processes(2 * self.processes), - self.processes) + self.assertEqual(self.options.get_processes(2 * self.processes), self.processes) def test_invalid_processes(self): """Tests with invalid processes since it's not positive.""" @@ -84,8 +83,7 @@ def test_init(self): self.assertEqual(linear_operator.n_qubits, n_qubits) # Checks type. - self.assertTrue( - isinstance(linear_operator, scipy.sparse.linalg.LinearOperator)) + self.assertTrue(isinstance(linear_operator, scipy.sparse.linalg.LinearOperator)) def test_matvec_wrong_n(self): """Testing with wrong n_qubits.""" @@ -105,8 +103,8 @@ def test_matvec_0(self): matvec_expected = numpy.zeros(vec.shape) self.assertTrue( - numpy.allclose( - LinearQubitOperator(qubit_operator, 3) * vec, matvec_expected)) + numpy.allclose(LinearQubitOperator(qubit_operator, 3) * vec, matvec_expected) + ) def test_matvec_x(self): """Testing product with X.""" @@ -114,9 +112,8 @@ def test_matvec_x(self): matvec_expected = numpy.array([2, 1, 4, 3]) self.assertTrue( - numpy.allclose( - LinearQubitOperator(QubitOperator('X1')) * vec, - matvec_expected)) + numpy.allclose(LinearQubitOperator(QubitOperator('X1')) * vec, matvec_expected) + ) def test_matvec_y(self): """Testing product with Y.""" @@ -124,9 +121,8 @@ def test_matvec_y(self): matvec_expected = 1.0j * numpy.array([-2, 1, -4, 3], dtype=complex) self.assertTrue( - numpy.allclose( - LinearQubitOperator(QubitOperator('Y1')) * vec, - matvec_expected)) + numpy.allclose(LinearQubitOperator(QubitOperator('Y1')) * vec, matvec_expected) + ) def test_matvec_z(self): """Testing product with Z.""" @@ -134,21 +130,19 @@ def test_matvec_z(self): matvec_expected = numpy.array([1, 2, -3, -4]) self.assertTrue( - numpy.allclose( - LinearQubitOperator(QubitOperator('Z0'), 2) * vec, - matvec_expected)) + numpy.allclose(LinearQubitOperator(QubitOperator('Z0'), 2) * vec, matvec_expected) + ) def test_matvec_z3(self): """Testing product with Z^n.""" - vec = numpy.array( - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) + vec = numpy.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) matvec_expected = numpy.array( - [1, -2, 3, -4, 5, -6, 7, -8, 9, -10, 11, -12, 13, -14, 15, -16]) + [1, -2, 3, -4, 5, -6, 7, -8, 9, -10, 11, -12, 13, -14, 15, -16] + ) self.assertTrue( - numpy.allclose( - LinearQubitOperator(QubitOperator('Z3')) * vec, - matvec_expected)) + numpy.allclose(LinearQubitOperator(QubitOperator('Z3')) * vec, matvec_expected) + ) def test_matvec_zx(self): """Testing with multiple factors.""" @@ -156,22 +150,25 @@ def test_matvec_zx(self): matvec_expected = numpy.array([2, 1, -4, -3]) self.assertTrue( - numpy.allclose( - LinearQubitOperator(QubitOperator('Z0 X1')) * vec, - matvec_expected)) + numpy.allclose(LinearQubitOperator(QubitOperator('Z0 X1')) * vec, matvec_expected) + ) def test_matvec_multiple_terms(self): """Testing with multiple terms.""" - qubit_operator = (QubitOperator.identity() + 2 * QubitOperator('Y2') + - QubitOperator(((0, 'Z'), (1, 'X')), 10.0)) + qubit_operator = ( + QubitOperator.identity() + + 2 * QubitOperator('Y2') + + QubitOperator(((0, 'Z'), (1, 'X')), 10.0) + ) vec = numpy.array([1, 2, 3, 4, 5, 6, 7, 8]) - matvec_expected = (10 * numpy.array([3, 4, 1, 2, -7, -8, -5, -6]) + - 2j * numpy.array([-2, 1, -4, 3, -6, 5, -8, 7]) + vec) + matvec_expected = ( + 10 * numpy.array([3, 4, 1, 2, -7, -8, -5, -6]) + + 2j * numpy.array([-2, 1, -4, 3, -6, 5, -8, 7]) + + vec + ) - self.assertTrue( - numpy.allclose( - LinearQubitOperator(qubit_operator) * vec, matvec_expected)) + self.assertTrue(numpy.allclose(LinearQubitOperator(qubit_operator) * vec, matvec_expected)) def test_matvec_compare(self): """Compare LinearQubitOperator with qubit_operator_sparse.""" @@ -181,10 +178,13 @@ def test_matvec_compare(self): self.assertTrue( numpy.allclose( numpy.transpose( - numpy.array([ - LinearQubitOperator(qubit_operator) * v - for v in numpy.identity(16) - ])), mat_expected.A)) + numpy.array( + [LinearQubitOperator(qubit_operator) * v for v in numpy.identity(16)] + ) + ), + mat_expected.A, + ) + ) class ParallelLinearQubitOperatorTest(unittest.TestCase): @@ -192,106 +192,53 @@ class ParallelLinearQubitOperatorTest(unittest.TestCase): def setUp(self): """ParallelLinearQubitOperator test set up.""" - self.qubit_operator = (QubitOperator('Z3') + QubitOperator('Y0') + - QubitOperator('X1')) + self.qubit_operator = QubitOperator('Z3') + QubitOperator('Y0') + QubitOperator('X1') self.n_qubits = 4 self.linear_operator = ParallelLinearQubitOperator(self.qubit_operator) # Vectors for calculations. self.vec = numpy.array(range(2**self.n_qubits)) - expected_matvec = numpy.array([ - 0, - -1, - 2, - -3, - 4, - -5, - 6, - -7, - 8, - -9, - 10, - -11, - 12, - -13, - 14, - -15, - ]) - expected_matvec = expected_matvec + numpy.array([ - -8j, - -9j, - -10j, - -11j, - -12j, - -13j, - -14j, - -15j, - 0j, - 1j, - 2j, - 3j, - 4j, - 5j, - 6j, - 7j, - ]) - expected_matvec += numpy.array([ - 4, - 5, - 6, - 7, - 0, - 1, - 2, - 3, - 12, - 13, - 14, - 15, - 8, - 9, - 10, - 11, - ]) + expected_matvec = numpy.array( + [0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15] + ) + expected_matvec = expected_matvec + numpy.array( + [-8j, -9j, -10j, -11j, -12j, -13j, -14j, -15j, 0j, 1j, 2j, 3j, 4j, 5j, 6j, 7j] + ) + expected_matvec += numpy.array([4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11]) self.expected_matvec = expected_matvec def test_init(self): """Tests __init__().""" - self.assertEqual(self.linear_operator.qubit_operator, - self.qubit_operator) + self.assertEqual(self.linear_operator.qubit_operator, self.qubit_operator) self.assertEqual(self.linear_operator.n_qubits, self.n_qubits) self.assertIsNone(self.linear_operator.options.pool) cpu_count = multiprocessing.cpu_count() default_processes = min(cpu_count, 10) - self.assertEqual(self.linear_operator.options.processes, - default_processes) + self.assertEqual(self.linear_operator.options.processes, default_processes) # Generated variables. - self.assertEqual(len(self.linear_operator.qubit_operator_groups), - min(multiprocessing.cpu_count(), 3)) self.assertEqual( - QubitOperator.accumulate( - self.linear_operator.qubit_operator_groups), - self.qubit_operator) + len(self.linear_operator.qubit_operator_groups), min(multiprocessing.cpu_count(), 3) + ) + self.assertEqual( + QubitOperator.accumulate(self.linear_operator.qubit_operator_groups), + self.qubit_operator, + ) for linear_operator in self.linear_operator.linear_operators: self.assertEqual(linear_operator.n_qubits, self.n_qubits) self.assertTrue(isinstance(linear_operator, LinearQubitOperator)) # Checks type. - self.assertTrue( - isinstance(self.linear_operator, - scipy.sparse.linalg.LinearOperator)) + self.assertTrue(isinstance(self.linear_operator, scipy.sparse.linalg.LinearOperator)) def test_matvec(self): """Tests _matvec() for matrix multiplication with a vector.""" self.assertIsNone(self.linear_operator.options.pool) - self.assertTrue( - numpy.allclose(self.linear_operator * self.vec, - self.expected_matvec)) + self.assertTrue(numpy.allclose(self.linear_operator * self.vec, self.expected_matvec)) def test_matvec_0(self): """Testing with zero term.""" @@ -301,15 +248,15 @@ def test_matvec_0(self): matvec_expected = numpy.zeros(vec.shape) self.assertTrue( - numpy.allclose( - ParallelLinearQubitOperator(qubit_operator, 3) * vec, - matvec_expected)) + numpy.allclose(ParallelLinearQubitOperator(qubit_operator, 3) * vec, matvec_expected) + ) self.assertIsNone(self.linear_operator.options.pool) def test_closed_workers_not_reused(self): qubit_operator = QubitOperator('X0') parallel_qubit_op = ParallelLinearQubitOperator( - qubit_operator, 1, options=LinearQubitOperatorOptions(processes=2)) + qubit_operator, 1, options=LinearQubitOperatorOptions(processes=2) + ) state = [1.0, 0.0] parallel_qubit_op.dot(state) parallel_qubit_op.dot(state) @@ -326,13 +273,13 @@ def test_apply_operator(self): self.assertTrue( numpy.allclose( - apply_operator((LinearQubitOperator(QubitOperator('X1')), vec)), - matvec_expected)) + apply_operator((LinearQubitOperator(QubitOperator('X1')), vec)), matvec_expected + ) + ) def test_generate_linear_operator(self): """Tests generate_linear_qubit_operator().""" - qubit_operator = (QubitOperator('Z3') + QubitOperator('X1') + - QubitOperator('Y0')) + qubit_operator = QubitOperator('Z3') + QubitOperator('X1') + QubitOperator('Y0') n_qubits = 6 # Checks types. @@ -341,7 +288,8 @@ def test_generate_linear_operator(self): self.assertFalse(isinstance(operator, ParallelLinearQubitOperator)) operator_again = generate_linear_qubit_operator( - qubit_operator, n_qubits, options=LinearQubitOperatorOptions(2)) + qubit_operator, n_qubits, options=LinearQubitOperatorOptions(2) + ) self.assertTrue(isinstance(operator_again, ParallelLinearQubitOperator)) self.assertFalse(isinstance(operator_again, LinearQubitOperator)) diff --git a/src/openfermion/linalg/rdm_reconstruction_test.py b/src/openfermion/linalg/rdm_reconstruction_test.py index d1a4f18a5..0561eb378 100644 --- a/src/openfermion/linalg/rdm_reconstruction_test.py +++ b/src/openfermion/linalg/rdm_reconstruction_test.py @@ -23,4 +23,4 @@ def test_correct_trace(): tpdm = wedge(opdm, opdm, (1, 1), (1, 1)) assert np.isclose(np.einsum('ijji', tpdm), 5 * (5 - 1) / 2) d3v = valdemoro_reconstruction(tpdm, 5) - assert np.isclose(np.einsum('ijkkji', d3v), 5 * (5 - 1) * (5 - 2) / 6) \ No newline at end of file + assert np.isclose(np.einsum('ijkkji', d3v), 5 * (5 - 1) * (5 - 2) / 6) diff --git a/src/openfermion/linalg/sparse_tools.py b/src/openfermion/linalg/sparse_tools.py index 59a47ebdc..453db13d3 100644 --- a/src/openfermion/linalg/sparse_tools.py +++ b/src/openfermion/linalg/sparse_tools.py @@ -19,27 +19,20 @@ import scipy.sparse import scipy.sparse.linalg -from openfermion.ops.operators import (FermionOperator, QubitOperator, - BosonOperator, QuadOperator) -from openfermion.ops.representations import (DiagonalCoulombHamiltonian, - PolynomialTensor) +from openfermion.ops.operators import FermionOperator, QubitOperator, BosonOperator, QuadOperator +from openfermion.ops.representations import DiagonalCoulombHamiltonian, PolynomialTensor from openfermion.transforms.opconversions import normal_ordered from openfermion.utils.indexing import up_index, down_index from openfermion.utils.operator_utils import count_qubits, is_hermitian # Make global definitions. identity_csc = scipy.sparse.identity(2, format='csc', dtype=complex) -pauli_x_csc = scipy.sparse.csc_matrix([[0., 1.], [1., 0.]], dtype=complex) -pauli_y_csc = scipy.sparse.csc_matrix([[0., -1.j], [1.j, 0.]], dtype=complex) -pauli_z_csc = scipy.sparse.csc_matrix([[1., 0.], [0., -1.]], dtype=complex) -q_raise_csc = (pauli_x_csc - 1.j * pauli_y_csc) / 2. -q_lower_csc = (pauli_x_csc + 1.j * pauli_y_csc) / 2. -pauli_matrix_map = { - 'I': identity_csc, - 'X': pauli_x_csc, - 'Y': pauli_y_csc, - 'Z': pauli_z_csc -} +pauli_x_csc = scipy.sparse.csc_matrix([[0.0, 1.0], [1.0, 0.0]], dtype=complex) +pauli_y_csc = scipy.sparse.csc_matrix([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) +pauli_z_csc = scipy.sparse.csc_matrix([[1.0, 0.0], [0.0, -1.0]], dtype=complex) +q_raise_csc = (pauli_x_csc - 1.0j * pauli_y_csc) / 2.0 +q_lower_csc = (pauli_x_csc + 1.0j * pauli_y_csc) / 2.0 +pauli_matrix_map = {'I': identity_csc, 'X': pauli_x_csc, 'Y': pauli_y_csc, 'Z': pauli_z_csc} def wrapped_kronecker(operator_1, operator_2): @@ -69,9 +62,7 @@ def jordan_wigner_ladder_sparse(n_qubits, tensor_factor, ladder_type): """ parities = tensor_factor * [pauli_z_csc] identities = [ - scipy.sparse.identity(2**(n_qubits - tensor_factor - 1), - dtype=complex, - format='csc') + scipy.sparse.identity(2 ** (n_qubits - tensor_factor - 1), dtype=complex, format='csc') ] if ladder_type: operator = kronecker_operators(parities + [q_raise_csc] + identities) @@ -101,10 +92,12 @@ def jordan_wigner_sparse(fermion_operator, n_qubits=None): # Create a list of raising and lowering operators for each orbital. jw_operators = [] for tensor_factor in range(n_qubits): - jw_operators += [(jordan_wigner_ladder_sparse(n_qubits, tensor_factor, - 0), - jordan_wigner_ladder_sparse(n_qubits, tensor_factor, - 1))] + jw_operators += [ + ( + jordan_wigner_ladder_sparse(n_qubits, tensor_factor, 0), + jordan_wigner_ladder_sparse(n_qubits, tensor_factor, 1), + ) + ] # Construct the Scipy sparse matrix. n_hilbert = 2**n_qubits @@ -114,10 +107,10 @@ def jordan_wigner_sparse(fermion_operator, n_qubits=None): for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] sparse_matrix = coefficient * scipy.sparse.identity( - 2**n_qubits, dtype=complex, format='csc') + 2**n_qubits, dtype=complex, format='csc' + ) for ladder_operator in term: - sparse_matrix = sparse_matrix * jw_operators[ladder_operator[0]][ - ladder_operator[1]] + sparse_matrix = sparse_matrix * jw_operators[ladder_operator[0]][ladder_operator[1]] if coefficient: # Extract triplets from sparse_term. @@ -131,8 +124,8 @@ def jordan_wigner_sparse(fermion_operator, n_qubits=None): row_list = numpy.concatenate(row_list) column_list = numpy.concatenate(column_list) sparse_operator = scipy.sparse.coo_matrix( - (values_list, (row_list, column_list)), - shape=(n_hilbert, n_hilbert)).tocsc(copy=False) + (values_list, (row_list, column_list)), shape=(n_hilbert, n_hilbert) + ).tocsc(copy=False) sparse_operator.eliminate_zeros() return sparse_operator @@ -164,13 +157,10 @@ def qubit_operator_sparse(qubit_operator, n_qubits=None): coefficient = qubit_operator.terms[qubit_term] sparse_operators = [coefficient] for pauli_operator in qubit_term: - # Grow space for missing identity operators. if pauli_operator[0] > tensor_factor: identity_qubits = pauli_operator[0] - tensor_factor - identity = scipy.sparse.identity(2**identity_qubits, - dtype=complex, - format='csc') + identity = scipy.sparse.identity(2**identity_qubits, dtype=complex, format='csc') sparse_operators += [identity] # Add actual operator to the list. @@ -180,9 +170,7 @@ def qubit_operator_sparse(qubit_operator, n_qubits=None): # Grow space at end of string unless operator acted on final qubit. if tensor_factor < n_qubits or not qubit_term: identity_qubits = n_qubits - tensor_factor - identity = scipy.sparse.identity(2**identity_qubits, - dtype=complex, - format='csc') + identity = scipy.sparse.identity(2**identity_qubits, dtype=complex, format='csc') sparse_operators += [identity] # Extract triplets from sparse_term. @@ -197,14 +185,14 @@ def qubit_operator_sparse(qubit_operator, n_qubits=None): row_list = numpy.concatenate(row_list) column_list = numpy.concatenate(column_list) sparse_operator = scipy.sparse.coo_matrix( - (values_list, (row_list, column_list)), - shape=(n_hilbert, n_hilbert)).tocsc(copy=False) + (values_list, (row_list, column_list)), shape=(n_hilbert, n_hilbert) + ).tocsc(copy=False) sparse_operator.eliminate_zeros() return sparse_operator def get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=None): - """ Return a linear operator's diagonal elements. + """Return a linear operator's diagonal elements. The main motivation is to use it for Davidson's algorithm, to find out the lowest n eigenvalues and associated eigenvectors. @@ -243,17 +231,16 @@ def get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=None): # Split vector by half and half for each bit. if pauli_operator[0] > tensor_factor: vecs = [ - v for iter_v in vecs - for v in numpy.split(iter_v, 2**(pauli_operator[0] - - tensor_factor)) + v + for iter_v in vecs + for v in numpy.split(iter_v, 2 ** (pauli_operator[0] - tensor_factor)) ] vec_pairs = [numpy.split(v, 2) for v in vecs] vecs = [v for vp in vec_pairs for v in (vp[0], -vp[1])] tensor_factor = pauli_operator[0] + 1 if not is_zero: - linear_operator_diagonal += (qubit_operator.terms[qubit_term] * - numpy.concatenate(vecs)) + linear_operator_diagonal += qubit_operator.terms[qubit_term] * numpy.concatenate(vecs) return linear_operator_diagonal @@ -268,7 +255,7 @@ def jw_configuration_state(occupied_orbitals, n_qubits): Returns: basis_vector(sparse): The basis state as a sparse matrix """ - one_index = sum(2**(n_qubits - 1 - i) for i in occupied_orbitals) + one_index = sum(2 ** (n_qubits - 1 - i) for i in occupied_orbitals) basis_vector = numpy.zeros(2**n_qubits, dtype=float) basis_vector[one_index] = 1 return basis_vector @@ -302,11 +289,7 @@ def jw_number_indices(n_electrons, n_qubits): return indices -def jw_sz_indices(sz_value, - n_qubits, - n_electrons=None, - up_index=up_index, - down_index=down_index): +def jw_sz_indices(sz_value, n_qubits, n_electrons=None, up_index=up_index, down_index=down_index): r"""Return the indices of basis vectors with fixed Sz under JW encoding. The returned indices label computational basis vectors which lie within @@ -335,35 +318,30 @@ def jw_sz_indices(sz_value, if n_qubits % 2 != 0: raise ValueError('Number of qubits must be even') - if not (2. * sz_value).is_integer(): + if not (2.0 * sz_value).is_integer(): raise ValueError('Sz value must be an integer or half-integer') n_sites = n_qubits // 2 - sz_integer = int(2. * sz_value) + sz_integer = int(2.0 * sz_value) indices = [] if n_electrons is not None: # Particle number is fixed, so the number of spin-up electrons # (as well as the number of spin-down electrons) is fixed - if ((n_electrons + sz_integer) % 2 != 0 or - n_electrons < abs(sz_integer)): - raise ValueError('The specified particle number and sz value are ' - 'incompatible.') + if (n_electrons + sz_integer) % 2 != 0 or n_electrons < abs(sz_integer): + raise ValueError('The specified particle number and sz value are ' 'incompatible.') num_up = (n_electrons + sz_integer) // 2 num_down = n_electrons - num_up up_occupations = itertools.combinations(range(n_sites), num_up) - down_occupations = list(itertools.combinations(range(n_sites), - num_down)) + down_occupations = list(itertools.combinations(range(n_sites), num_down)) # Each arrangement of up spins can be paired with an arrangement # of down spins for up_occupation in up_occupations: up_occupation = [up_index(index) for index in up_occupation] for down_occupation in down_occupations: - down_occupation = [ - down_index(index) for index in down_occupation - ] + down_occupation = [down_index(index) for index in down_occupation] occupation = up_occupation + down_occupation - indices.append(sum(2**(n_qubits - 1 - k) for k in occupation)) + indices.append(sum(2 ** (n_qubits - 1 - k) for k in occupation)) else: # Particle number is not fixed if sz_integer < 0: @@ -378,19 +356,15 @@ def jw_sz_indices(sz_value, # Choose n of the 'more' spin and n - abs(sz_integer) of the # 'less' spin more_occupations = itertools.combinations(range(n_sites), n) - less_occupations = list( - itertools.combinations(range(n_sites), n - abs(sz_integer))) + less_occupations = list(itertools.combinations(range(n_sites), n - abs(sz_integer))) # Each arrangement of the 'more' spins can be paired with an # arrangement of the 'less' spin for more_occupation in more_occupations: more_occupation = [more_map(index) for index in more_occupation] for less_occupation in less_occupations: - less_occupation = [ - less_map(index) for index in less_occupation - ] + less_occupation = [less_map(index) for index in less_occupation] occupation = more_occupation + less_occupation - indices.append( - sum(2**(n_qubits - 1 - k) for k in occupation)) + indices.append(sum(2 ** (n_qubits - 1 - k) for k in occupation)) return indices @@ -415,12 +389,9 @@ def jw_number_restrict_operator(operator, n_electrons, n_qubits=None): return operator[numpy.ix_(select_indices, select_indices)] -def jw_sz_restrict_operator(operator, - sz_value, - n_electrons=None, - n_qubits=None, - up_index=up_index, - down_index=down_index): +def jw_sz_restrict_operator( + operator, sz_value, n_electrons=None, n_qubits=None, up_index=up_index, down_index=down_index +): """Restrict a Jordan-Wigner encoded operator to a given Sz value Args: @@ -443,11 +414,9 @@ def jw_sz_restrict_operator(operator, if n_qubits is None: n_qubits = int(numpy.log2(operator.shape[0])) - select_indices = jw_sz_indices(sz_value, - n_qubits, - n_electrons=n_electrons, - up_index=up_index, - down_index=down_index) + select_indices = jw_sz_indices( + sz_value, n_qubits, n_electrons=n_electrons, up_index=up_index, down_index=down_index + ) return operator[numpy.ix_(select_indices, select_indices)] @@ -471,12 +440,9 @@ def jw_number_restrict_state(state, n_electrons, n_qubits=None): return state[select_indices] -def jw_sz_restrict_state(state, - sz_value, - n_electrons=None, - n_qubits=None, - up_index=up_index, - down_index=down_index): +def jw_sz_restrict_state( + state, sz_value, n_electrons=None, n_qubits=None, up_index=up_index, down_index=down_index +): """Restrict a Jordan-Wigner encoded state to a given Sz value Args: @@ -499,11 +465,9 @@ def jw_sz_restrict_state(state, if n_qubits is None: n_qubits = int(numpy.log2(state.shape[0])) - select_indices = jw_sz_indices(sz_value, - n_qubits, - n_electrons=n_electrons, - up_index=up_index, - down_index=down_index) + select_indices = jw_sz_indices( + sz_value, n_qubits, n_electrons=n_electrons, up_index=up_index, down_index=down_index + ) return state[select_indices] @@ -528,8 +492,7 @@ def jw_get_ground_state_at_particle_number(sparse_operator, particle_number): n_qubits = int(numpy.log2(sparse_operator.shape[0])) # Get the operator restricted to the subspace of the desired particle number - restricted_operator = jw_number_restrict_operator(sparse_operator, - particle_number, n_qubits) + restricted_operator = jw_number_restrict_operator(sparse_operator, particle_number, n_qubits) # Compute eigenvalues and eigenvectors if restricted_operator.shape[0] - 1 <= 1: @@ -537,9 +500,7 @@ def jw_get_ground_state_at_particle_number(sparse_operator, particle_number): dense_restricted_operator = restricted_operator.toarray() eigvals, eigvecs = numpy.linalg.eigh(dense_restricted_operator) else: - eigvals, eigvecs = scipy.sparse.linalg.eigsh(restricted_operator, - k=1, - which='SA') + eigvals, eigvecs = scipy.sparse.linalg.eigsh(restricted_operator, k=1, which='SA') # Expand the state state = eigvecs[:, 0] @@ -559,18 +520,21 @@ def jw_sparse_givens_rotation(i, j, theta, phi, n_qubits): cosine = numpy.cos(theta) sine = numpy.sin(theta) - phase = numpy.exp(1.j * phi) + phase = numpy.exp(1.0j * phi) # Create the two-qubit rotation matrix rotation_matrix = scipy.sparse.csc_matrix( - ([1., phase * cosine, -phase * sine, sine, cosine, phase], - ((0, 1, 1, 2, 2, 3), (0, 1, 2, 1, 2, 3))), + ( + [1.0, phase * cosine, -phase * sine, sine, cosine, phase], + ((0, 1, 1, 2, 2, 3), (0, 1, 2, 1, 2, 3)), + ), shape=(4, 4), - dtype=numpy.complex128) + dtype=numpy.complex128, + ) # Initialize identity operators left_eye = scipy.sparse.eye(2**i, format='csc') - right_eye = scipy.sparse.eye(2**(n_qubits - 1 - j), format='csc') + right_eye = scipy.sparse.eye(2 ** (n_qubits - 1 - j), format='csc') # Construct the matrix and return givens_matrix = kronecker_operators([left_eye, rotation_matrix, right_eye]) @@ -583,14 +547,13 @@ def jw_sparse_particle_hole_transformation_last_mode(n_qubits): particle-hole transformation on the last mode in the Jordan-Wigner encoding. """ - left_eye = scipy.sparse.eye(2**(n_qubits - 1), format='csc') + left_eye = scipy.sparse.eye(2 ** (n_qubits - 1), format='csc') return kronecker_operators([left_eye, pauli_matrix_map['X']]) def get_density_matrix(states, probabilities): n_qubits = states[0].shape[0] - density_matrix = scipy.sparse.csc_matrix((n_qubits, n_qubits), - dtype=complex) + density_matrix = scipy.sparse.csc_matrix((n_qubits, n_qubits), dtype=complex) for state, probability in zip(states, probabilities): state = scipy.sparse.csc_matrix(state.reshape((len(state), 1))) density_matrix = density_matrix + probability * state * state.getH() @@ -612,11 +575,9 @@ def get_ground_state(sparse_operator, initial_guess=None): eigenstate: The lowest eigenstate in scipy.sparse csc format. """ - values, vectors = scipy.sparse.linalg.eigsh(sparse_operator, - k=1, - v0=initial_guess, - which='SA', - maxiter=1e7) + values, vectors = scipy.sparse.linalg.eigsh( + sparse_operator, k=1, v0=initial_guess, which='SA', maxiter=1e7 + ) order = numpy.argsort(values) values = values[order] @@ -686,8 +647,10 @@ def expectation(operator, state): if isinstance(state, scipy.sparse.spmatrix): # Handle density matrix. if isinstance(operator, scipy.sparse.linalg.LinearOperator): - raise ValueError('Taking the expectation of a LinearOperator with ' - 'a density matrix is not supported.') + raise ValueError( + 'Taking the expectation of a LinearOperator with ' + 'a density matrix is not supported.' + ) product = state * operator expectation = numpy.sum(product.diagonal()) @@ -698,13 +661,11 @@ def expectation(operator, state): expectation = numpy.dot(numpy.conjugate(state), operator * state) else: # Column vector - expectation = numpy.dot(numpy.conjugate(state.T), - operator * state)[0, 0] + expectation = numpy.dot(numpy.conjugate(state.T), operator * state)[0, 0] else: # Handle exception. - raise ValueError( - 'Input state must be a numpy array or a sparse matrix.') + raise ValueError('Input state must be a numpy array or a sparse matrix.') # Return. return expectation @@ -726,7 +687,7 @@ def variance(operator, state): Raises: ValueError: Input state has invalid format. """ - return (expectation(operator**2, state) - expectation(operator, state)**2) + return expectation(operator**2, state) - expectation(operator, state) ** 2 def expectation_computational_basis_state(operator, computational_basis_state): @@ -754,11 +715,11 @@ def expectation_computational_basis_state(operator, computational_basis_state): occupied_orbitals = computational_basis_state if not isinstance(occupied_orbitals, list): - computational_basis_state_index = (occupied_orbitals.nonzero()[0][0]) + computational_basis_state_index = occupied_orbitals.nonzero()[0][0] - occupied_orbitals = [ - digit == '1' for digit in bin(computational_basis_state_index)[2:] - ][::-1] + occupied_orbitals = [digit == '1' for digit in bin(computational_basis_state_index)[2:]][ + ::-1 + ] expectation_value = operator.terms.get((), 0.0) @@ -767,16 +728,14 @@ def expectation_computational_basis_state(operator, computational_basis_state): expectation_value += operator.terms.get(((i, 1), (i, 0)), 0.0) for j in range(i + 1, len(occupied_orbitals)): - expectation_value -= operator.terms.get( - ((j, 1), (i, 1), (j, 0), (i, 0)), 0.0) + expectation_value -= operator.terms.get(((j, 1), (i, 1), (j, 0), (i, 0)), 0.0) return expectation_value -def expectation_db_operator_with_pw_basis_state(operator, - plane_wave_occ_orbitals, - n_spatial_orbitals, grid, - spinless): +def expectation_db_operator_with_pw_basis_state( + operator, plane_wave_occ_orbitals, n_spatial_orbitals, grid, spinless +): """Compute expectation value of a dual basis operator with a plane wave computational basis state. @@ -797,26 +756,33 @@ def expectation_db_operator_with_pw_basis_state(operator, if len(single_action) == 2: expectation_value += coefficient * ( expectation_one_body_db_operator_computational_basis_state( - single_action, plane_wave_occ_orbitals, grid, spinless) / - n_spatial_orbitals) + single_action, plane_wave_occ_orbitals, grid, spinless + ) + / n_spatial_orbitals + ) elif len(single_action) == 4: expectation_value += coefficient * ( expectation_two_body_db_operator_computational_basis_state( - single_action, plane_wave_occ_orbitals, grid, spinless) / - n_spatial_orbitals**2) + single_action, plane_wave_occ_orbitals, grid, spinless + ) + / n_spatial_orbitals**2 + ) elif len(single_action) == 6: expectation_value += coefficient * ( expectation_three_body_db_operator_computational_basis_state( - single_action, plane_wave_occ_orbitals, grid, spinless) / - n_spatial_orbitals**3) + single_action, plane_wave_occ_orbitals, grid, spinless + ) + / n_spatial_orbitals**3 + ) return expectation_value def expectation_one_body_db_operator_computational_basis_state( - dual_basis_action, plane_wave_occ_orbitals, grid, spinless): + dual_basis_action, plane_wave_occ_orbitals, grid, spinless +): """Compute expectation value of a 1-body dual-basis operator with a plane wave computational basis state. @@ -832,10 +798,8 @@ def expectation_one_body_db_operator_computational_basis_state( """ expectation_value = 0.0 - r_p = grid.position_vector( - grid.grid_indices(dual_basis_action[0][0], spinless)) - r_q = grid.position_vector( - grid.grid_indices(dual_basis_action[1][0], spinless)) + r_p = grid.position_vector(grid.grid_indices(dual_basis_action[0][0], spinless)) + r_q = grid.position_vector(grid.grid_indices(dual_basis_action[1][0], spinless)) for orbital in plane_wave_occ_orbitals: # If there's spin, p and q have to have the same parity (spin), @@ -843,15 +807,15 @@ def expectation_one_body_db_operator_computational_basis_state( k_orbital = grid.momentum_vector(grid.grid_indices(orbital, spinless)) # The Fourier transform is spin-conserving. This means that p, q, # and the new orbital all have to have the same spin (parity). - if spinless or (dual_basis_action[0][0] % 2 == - dual_basis_action[1][0] % 2 == orbital % 2): + if spinless or (dual_basis_action[0][0] % 2 == dual_basis_action[1][0] % 2 == orbital % 2): expectation_value += numpy.exp(-1j * k_orbital.dot(r_p - r_q)) return expectation_value def expectation_two_body_db_operator_computational_basis_state( - dual_basis_action, plane_wave_occ_orbitals, grid, spinless): + dual_basis_action, plane_wave_occ_orbitals, grid, spinless +): """Compute expectation value of a 2-body dual-basis operator with a plane wave computational basis state. @@ -869,8 +833,7 @@ def expectation_two_body_db_operator_computational_basis_state( r = {} for i in range(4): - r[i] = grid.position_vector( - grid.grid_indices(dual_basis_action[i][0], spinless)) + r[i] = grid.position_vector(grid.grid_indices(dual_basis_action[i][0], spinless)) rr = {} k_map = {} @@ -901,10 +864,9 @@ def expectation_two_body_db_operator_computational_basis_state( # the parity of the orbitals involved in the transition must # be the same. if spinless or ( - (dual_basis_action[0][0] % 2 == dual_basis_action[3][0] % 2 - == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == dual_basis_action[2][0] % 2 - == orbital2 % 2)): + (dual_basis_action[0][0] % 2 == dual_basis_action[3][0] % 2 == orbital1 % 2) + and (dual_basis_action[1][0] % 2 == dual_basis_action[2][0] % 2 == orbital2 % 2) + ): value = numpy.exp(-1j * (k1ad + k2bc)) # Add because it came from two anti-commutations. @@ -914,10 +876,9 @@ def expectation_two_body_db_operator_computational_basis_state( # the parity of the orbitals involved in the transition must # be the same. if spinless or ( - (dual_basis_action[0][0] % 2 == dual_basis_action[2][0] % 2 - == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == dual_basis_action[3][0] % 2 - == orbital2 % 2)): + (dual_basis_action[0][0] % 2 == dual_basis_action[2][0] % 2 == orbital1 % 2) + and (dual_basis_action[1][0] % 2 == dual_basis_action[3][0] % 2 == orbital2 % 2) + ): value = numpy.exp(-1j * (k1ac + k2bd)) # Subtract because it came from a single anti-commutation. @@ -927,7 +888,8 @@ def expectation_two_body_db_operator_computational_basis_state( def expectation_three_body_db_operator_computational_basis_state( - dual_basis_action, plane_wave_occ_orbitals, grid, spinless): + dual_basis_action, plane_wave_occ_orbitals, grid, spinless +): """Compute expectation value of a 3-body dual-basis operator with a plane wave computational basis state. @@ -945,8 +907,7 @@ def expectation_three_body_db_operator_computational_basis_state( r = {} for i in range(6): - r[i] = grid.position_vector( - grid.grid_indices(dual_basis_action[i][0], spinless)) + r[i] = grid.position_vector(grid.grid_indices(dual_basis_action[i][0], spinless)) rr = {} k_map = {} @@ -984,74 +945,128 @@ def expectation_three_body_db_operator_computational_basis_state( # Handle \delta_{ad} \delta_{bf} \delta_{ce} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[3][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[5][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[4][0] % 2 == orbital3 % 2)): - expectation_value += numpy.exp(-1j * - (k1ad + k2bf + k3ce)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value += numpy.exp(-1j * (k1ad + k2bf + k3ce)) # Handle -\delta_{ad} \delta_{be} \delta_{cf} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[3][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[4][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[5][0] % 2 == orbital3 % 2)): - expectation_value -= numpy.exp(-1j * - (k1ad + k2be + k3cf)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value -= numpy.exp(-1j * (k1ad + k2be + k3cf)) # Handle -\delta_{ae} \delta_{bf} \delta_{cd} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[4][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[5][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[3][0] % 2 == orbital3 % 2)): - expectation_value -= numpy.exp(-1j * - (k1ae + k2bf + k3cd)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value -= numpy.exp(-1j * (k1ae + k2bf + k3cd)) # Handle \delta_{ae} \delta_{bd} \delta_{cf} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[4][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[3][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[5][0] % 2 == orbital3 % 2)): - expectation_value += numpy.exp(-1j * - (k1ae + k2bd + k3cf)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value += numpy.exp(-1j * (k1ae + k2bd + k3cf)) # Handle \delta_{af} \delta_{be} \delta_{cd} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[5][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[4][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[3][0] % 2 == orbital3 % 2)): - expectation_value += numpy.exp(-1j * - (k1af + k2be + k3cd)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value += numpy.exp(-1j * (k1af + k2be + k3cd)) # Handle -\delta_{af} \delta_{bd} \delta_{ce} after FT. # The Fourier transform is spin-conserving. if spinless or ( - (dual_basis_action[0][0] % 2 == - dual_basis_action[5][0] % 2 == orbital1 % 2) and - (dual_basis_action[1][0] % 2 == - dual_basis_action[3][0] % 2 == orbital2 % 2) and - (dual_basis_action[2][0] % 2 == - dual_basis_action[4][0] % 2 == orbital3 % 2)): - expectation_value -= numpy.exp(-1j * - (k1af + k2bd + k3ce)) + ( + dual_basis_action[0][0] % 2 + == dual_basis_action[5][0] % 2 + == orbital1 % 2 + ) + and ( + dual_basis_action[1][0] % 2 + == dual_basis_action[3][0] % 2 + == orbital2 % 2 + ) + and ( + dual_basis_action[2][0] % 2 + == dual_basis_action[4][0] % 2 + == orbital3 % 2 + ) + ): + expectation_value -= numpy.exp(-1j * (k1af + k2bd + k3ce)) return expectation_value @@ -1068,11 +1083,9 @@ def get_gap(sparse_operator, initial_guess=None): if not is_hermitian(sparse_operator): raise ValueError('sparse_operator must be Hermitian.') - values, _ = scipy.sparse.linalg.eigsh(sparse_operator, - k=2, - v0=initial_guess, - which='SA', - maxiter=1e7) + values, _ = scipy.sparse.linalg.eigsh( + sparse_operator, k=2, v0=initial_guess, which='SA', maxiter=1e7 + ) gap = abs(values[1] - values[0]) return gap @@ -1106,17 +1119,9 @@ def boson_ladder_sparse(n_modes, mode, ladder_type, trunc): raise ValueError("Fock space truncation must be a positive integer.") if ladder_type: - lop = scipy.sparse.spdiags(numpy.sqrt(range(1, trunc)), - -1, - trunc, - trunc, - format='csc') + lop = scipy.sparse.spdiags(numpy.sqrt(range(1, trunc)), -1, trunc, trunc, format='csc') else: - lop = scipy.sparse.spdiags(numpy.sqrt(range(trunc)), - 1, - trunc, - trunc, - format='csc') + lop = scipy.sparse.spdiags(numpy.sqrt(range(trunc)), 1, trunc, trunc, format='csc') Id = [scipy.sparse.identity(trunc, format='csc', dtype=complex)] operator_list = Id * mode + [lop] + Id * (n_modes - mode - 1) @@ -1163,7 +1168,7 @@ def single_quad_op_sparse(n_modes, mode, quadrature, hbar, trunc): return operator -def boson_operator_sparse(operator, trunc, hbar=1.): +def boson_operator_sparse(operator, trunc, hbar=1.0): r"""Initialize a Scipy sparse matrix in the Fock space from a bosonic operator. @@ -1185,6 +1190,7 @@ def boson_operator_sparse(operator, trunc, hbar=1.): """ if isinstance(operator, QuadOperator): from openfermion.transforms.opconversions import get_boson_operator + boson_operator = get_boson_operator(operator, hbar) elif isinstance(operator, BosonOperator): boson_operator = operator @@ -1210,8 +1216,7 @@ def boson_operator_sparse(operator, trunc, hbar=1.): # Loop through the terms. for term in boson_operator.terms: coefficient = boson_operator.terms[term] - term_operator = coefficient * scipy.sparse.identity( - n_hilbert, dtype=complex, format='csc') + term_operator = coefficient * scipy.sparse.identity(n_hilbert, dtype=complex, format='csc') for ladder_op in term: # Add actual operator to the list. @@ -1229,13 +1234,13 @@ def boson_operator_sparse(operator, trunc, hbar=1.): row_list = numpy.concatenate(row_list) column_list = numpy.concatenate(column_list) sparse_operator = scipy.sparse.coo_matrix( - (values_list, (row_list, column_list)), - shape=(n_hilbert, n_hilbert)).tocsc(copy=False) + (values_list, (row_list, column_list)), shape=(n_hilbert, n_hilbert) + ).tocsc(copy=False) sparse_operator.eliminate_zeros() return sparse_operator -def get_sparse_operator(operator, n_qubits=None, trunc=None, hbar=1.): +def get_sparse_operator(operator, n_qubits=None, trunc=None, hbar=1.0): r"""Map an operator to a sparse matrix. If the input is not a QubitOperator, the Jordan-Wigner Transform is used. @@ -1253,6 +1258,7 @@ def get_sparse_operator(operator, n_qubits=None, trunc=None, hbar=1.): Applicable only to the QuadOperator. """ from openfermion.transforms.opconversions import get_fermion_operator + if isinstance(operator, (DiagonalCoulombHamiltonian, PolynomialTensor)): return jordan_wigner_sparse(get_fermion_operator(operator)) elif isinstance(operator, FermionOperator): @@ -1262,16 +1268,19 @@ def get_sparse_operator(operator, n_qubits=None, trunc=None, hbar=1.): elif isinstance(operator, (BosonOperator, QuadOperator)): return boson_operator_sparse(operator, trunc, hbar) else: - raise TypeError('Failed to convert a {} to a sparse matrix.'.format( - type(operator).__name__)) - - -def get_number_preserving_sparse_operator(fermion_op, - num_qubits, - num_electrons, - spin_preserving=False, - reference_determinant=None, - excitation_level=None): + raise TypeError( + 'Failed to convert a {} to a sparse matrix.'.format(type(operator).__name__) + ) + + +def get_number_preserving_sparse_operator( + fermion_op, + num_qubits, + num_electrons, + spin_preserving=False, + reference_determinant=None, + excitation_level=None, +): """Initialize a Scipy sparse matrix in a specific symmetry sector. This method initializes a Scipy sparse matrix from a FermionOperator, @@ -1319,8 +1328,7 @@ def get_number_preserving_sparse_operator(fermion_op, # We use the Hartree-Fock determinant as a reference if none is provided. if reference_determinant is None: - reference_determinant = numpy.array( - [i < num_electrons for i in range(num_qubits)]) + reference_determinant = numpy.array([i < num_electrons for i in range(num_qubits)]) else: reference_determinant = numpy.asarray(reference_determinant) @@ -1328,13 +1336,11 @@ def get_number_preserving_sparse_operator(fermion_op, excitation_level = num_electrons state_array = numpy.asarray( - list( - _iterate_basis_(reference_determinant, excitation_level, - spin_preserving))) + list(_iterate_basis_(reference_determinant, excitation_level, spin_preserving)) + ) # Create a 1d array with each determinant encoded # as an integer for sorting purposes. - int_state_array = state_array.dot( - 1 << numpy.arange(state_array.shape[1])[::-1]) + int_state_array = state_array.dot(1 << numpy.arange(state_array.shape[1])[::-1]) sorting_indices = numpy.argsort(int_state_array) space_size = state_array.shape[0] @@ -1345,14 +1351,12 @@ def get_number_preserving_sparse_operator(fermion_op, for term, coefficient in fermion_op.terms.items(): if len(term) == 0: - constant = coefficient * scipy.sparse.identity( - space_size, dtype=float, format='csc') + constant = coefficient * scipy.sparse.identity(space_size, dtype=float, format='csc') sparse_op += constant else: - term_op = _build_term_op_(term, state_array, int_state_array, - sorting_indices) + term_op = _build_term_op_(term, state_array, int_state_array, sorting_indices) sparse_op += coefficient * term_op @@ -1380,24 +1384,22 @@ def _iterate_basis_(reference_determinant, excitation_level, spin_preserving): """ if not spin_preserving: for order in range(excitation_level + 1): - for determinant in _iterate_basis_order_(reference_determinant, - order): + for determinant in _iterate_basis_order_(reference_determinant, order): yield determinant else: - alpha_excitation_level = min( - (numpy.sum(reference_determinant[::2]), excitation_level)) - beta_excitation_level = min( - (numpy.sum(reference_determinant[1::2]), excitation_level)) + alpha_excitation_level = min((numpy.sum(reference_determinant[::2]), excitation_level)) + beta_excitation_level = min((numpy.sum(reference_determinant[1::2]), excitation_level)) for order in range(excitation_level + 1): for alpha_order in range(alpha_excitation_level + 1): beta_order = order - alpha_order - if (beta_order < 0 or beta_order > beta_excitation_level): + if beta_order < 0 or beta_order > beta_excitation_level: continue for determinant in _iterate_basis_spin_order_( - reference_determinant, alpha_order, beta_order): + reference_determinant, alpha_order, beta_order + ): yield determinant @@ -1413,13 +1415,14 @@ def _iterate_basis_order_(reference_determinant, order): Yields: Lists of bools which indicate which orbitals are occupied and which are unoccupied in the current determinant. - """ + """ occupied_indices = numpy.where(reference_determinant)[0] unoccupied_indices = numpy.where(numpy.invert(reference_determinant))[0] for occ_ind, unocc_ind in itertools.product( - itertools.combinations(occupied_indices, order), - itertools.combinations(unoccupied_indices, order)): + itertools.combinations(occupied_indices, order), + itertools.combinations(unoccupied_indices, order), + ): basis_state = reference_determinant.copy() occ_ind = list(occ_ind) @@ -1448,20 +1451,18 @@ def _iterate_basis_spin_order_(reference_determinant, alpha_order, beta_order): Yields: Lists of bools which indicate which orbitals are occupied and which are unoccupied in the current determinant. - """ + """ occupied_alpha_indices = numpy.where(reference_determinant[::2])[0] * 2 - unoccupied_alpha_indices = numpy.where( - numpy.invert(reference_determinant[::2]))[0] * 2 + unoccupied_alpha_indices = numpy.where(numpy.invert(reference_determinant[::2]))[0] * 2 occupied_beta_indices = numpy.where(reference_determinant[1::2])[0] * 2 + 1 - unoccupied_beta_indices = numpy.where( - numpy.invert(reference_determinant[1::2]))[0] * 2 + 1 - - for (alpha_occ_ind, alpha_unocc_ind, beta_occ_ind, - beta_unocc_ind) in itertools.product( - itertools.combinations(occupied_alpha_indices, alpha_order), - itertools.combinations(unoccupied_alpha_indices, alpha_order), - itertools.combinations(occupied_beta_indices, beta_order), - itertools.combinations(unoccupied_beta_indices, beta_order)): + unoccupied_beta_indices = numpy.where(numpy.invert(reference_determinant[1::2]))[0] * 2 + 1 + + for alpha_occ_ind, alpha_unocc_ind, beta_occ_ind, beta_unocc_ind in itertools.product( + itertools.combinations(occupied_alpha_indices, alpha_order), + itertools.combinations(unoccupied_alpha_indices, alpha_order), + itertools.combinations(occupied_beta_indices, beta_order), + itertools.combinations(unoccupied_beta_indices, beta_order), + ): basis_state = reference_determinant.copy() alpha_occ_ind = list(alpha_occ_ind) @@ -1529,8 +1530,7 @@ def _build_term_op_(term, state_array, int_state_array, sorting_indices): delta += 1 if delta != 0: - raise ValueError( - "The supplied operator doesn't preserve particle number") + raise ValueError("The supplied operator doesn't preserve particle number") # We search for every state which has the necessary orbitals occupied and # unoccupied in order to not be immediately zeroed out based on the @@ -1538,8 +1538,9 @@ def _build_term_op_(term, state_array, int_state_array, sorting_indices): maybe_valid_states = numpy.where( numpy.logical_and( numpy.all(state_array[:, needs_to_be_occupied], axis=1), - numpy.logical_not( - numpy.any(state_array[:, needs_to_be_unoccupied], axis=1))))[0] + numpy.logical_not(numpy.any(state_array[:, needs_to_be_unoccupied], axis=1)), + ) + )[0] data = [] row_ind = [] @@ -1563,16 +1564,15 @@ def _build_term_op_(term, state_array, int_state_array, sorting_indices): parity = 1 for i, _ in reversed(term): area_to_check = target_determinant[0:i] - parity *= (-1)**numpy.sum(area_to_check) + parity *= (-1) ** numpy.sum(area_to_check) target_determinant[i] = not target_determinant[i] - int_encoding = target_determinant.dot( - 1 << numpy.arange(target_determinant.size)[::-1]) + int_encoding = target_determinant.dot(1 << numpy.arange(target_determinant.size)[::-1]) - target_state_index_sorted = numpy.searchsorted(int_state_array, - int_encoding, - sorter=sorting_indices) + target_state_index_sorted = numpy.searchsorted( + int_state_array, int_encoding, sorter=sorting_indices + ) target_state = sorting_indices[target_state_index_sorted] diff --git a/src/openfermion/linalg/sparse_tools_test.py b/src/openfermion/linalg/sparse_tools_test.py index 0b10a4e8b..99ad58c69 100644 --- a/src/openfermion/linalg/sparse_tools_test.py +++ b/src/openfermion/linalg/sparse_tools_test.py @@ -26,64 +26,82 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData -from openfermion.hamiltonians import (fermi_hubbard, jellium_model, - wigner_seitz_length_scale, - number_operator) -from openfermion.ops.operators import (FermionOperator, BosonOperator, - QuadOperator, QubitOperator, - MajoranaOperator) +from openfermion.hamiltonians import ( + fermi_hubbard, + jellium_model, + wigner_seitz_length_scale, + number_operator, +) +from openfermion.ops.operators import ( + FermionOperator, + BosonOperator, + QuadOperator, + QubitOperator, + MajoranaOperator, +) from openfermion.ops.representations import DiagonalCoulombHamiltonian -from openfermion.transforms.opconversions import (jordan_wigner, - get_fermion_operator, - normal_ordered) -from openfermion.transforms.repconversions import (fourier_transform, - get_interaction_operator) +from openfermion.transforms.opconversions import jordan_wigner, get_fermion_operator, normal_ordered +from openfermion.transforms.repconversions import fourier_transform, get_interaction_operator from openfermion.testing.testing_utils import random_hermitian_matrix from openfermion.linalg.sparse_tools import ( - get_sparse_operator, get_ground_state, eigenspectrum, expectation, - jw_number_restrict_state, inner_product, jw_sz_restrict_state, - jw_get_ground_state_at_particle_number, jw_sparse_givens_rotation, - pauli_matrix_map, sparse_eigenspectrum, jordan_wigner_sparse, - kronecker_operators, identity_csc, pauli_x_csc, qubit_operator_sparse, - get_linear_qubit_operator_diagonal, jw_number_indices, - get_number_preserving_sparse_operator, jw_configuration_state, - jw_hartree_fock_state, jw_sz_restrict_operator, jw_sz_indices, - jw_number_restrict_operator, variance, + get_sparse_operator, + get_ground_state, + eigenspectrum, + expectation, + jw_number_restrict_state, + inner_product, + jw_sz_restrict_state, + jw_get_ground_state_at_particle_number, + jw_sparse_givens_rotation, + pauli_matrix_map, + sparse_eigenspectrum, + jordan_wigner_sparse, + kronecker_operators, + identity_csc, + pauli_x_csc, + qubit_operator_sparse, + get_linear_qubit_operator_diagonal, + jw_number_indices, + get_number_preserving_sparse_operator, + jw_configuration_state, + jw_hartree_fock_state, + jw_sz_restrict_operator, + jw_sz_indices, + jw_number_restrict_operator, + variance, expectation_computational_basis_state, - expectation_db_operator_with_pw_basis_state, get_gap, boson_ladder_sparse, - boson_operator_sparse, single_quad_op_sparse, _iterate_basis_) - -from openfermion.utils.operator_utils import (is_hermitian, count_qubits, - hermitian_conjugated) + expectation_db_operator_with_pw_basis_state, + get_gap, + boson_ladder_sparse, + boson_operator_sparse, + single_quad_op_sparse, + _iterate_basis_, +) + +from openfermion.utils.operator_utils import is_hermitian, count_qubits, hermitian_conjugated from openfermion.utils.indexing import up_index, down_index from openfermion.utils.grid import Grid -from openfermion.hamiltonians.jellium_hf_state import ( - lowest_single_particle_energy_states) +from openfermion.hamiltonians.jellium_hf_state import lowest_single_particle_energy_states from openfermion.linalg.linear_qubit_operator import LinearQubitOperator class EigenSpectrumTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 self.majorana_operator = MajoranaOperator((1, 4, 9)) self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) - self.fermion_operator = self.fermion_term + hermitian_conjugated( - self.fermion_term) + self.fermion_operator = self.fermion_term + hermitian_conjugated(self.fermion_term) self.qubit_operator = jordan_wigner(self.fermion_operator) - self.interaction_operator = get_interaction_operator( - self.fermion_operator) + self.interaction_operator = get_interaction_operator(self.fermion_operator) def test_eigenspectrum(self): fermion_eigenspectrum = eigenspectrum(self.fermion_operator) qubit_eigenspectrum = eigenspectrum(self.qubit_operator) interaction_eigenspectrum = eigenspectrum(self.interaction_operator) for i in range(2**self.n_qubits): - self.assertAlmostEqual(fermion_eigenspectrum[i], - qubit_eigenspectrum[i]) - self.assertAlmostEqual(fermion_eigenspectrum[i], - interaction_eigenspectrum[i]) + self.assertAlmostEqual(fermion_eigenspectrum[i], qubit_eigenspectrum[i]) + self.assertAlmostEqual(fermion_eigenspectrum[i], interaction_eigenspectrum[i]) with self.assertRaises(TypeError): _ = eigenspectrum(BosonOperator()) @@ -93,21 +111,20 @@ def test_eigenspectrum(self): class SparseOperatorTest(unittest.TestCase): - def test_kronecker_operators(self): - self.assertAlmostEqual( 0, numpy.amax( numpy.absolute( - kronecker_operators(3 * [identity_csc]) - - kronecker_operators(3 * [pauli_x_csc])**2))) + kronecker_operators(3 * [identity_csc]) + - kronecker_operators(3 * [pauli_x_csc]) ** 2 + ) + ), + ) def test_qubit_jw_fermion_integration(self): - # Initialize a random fermionic operator. - fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), - -4.3) + fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), -4.3) fermion_operator += FermionOperator(((3, 1), (1, 0)), 8.17) fermion_operator += 3.2 * FermionOperator() @@ -117,60 +134,47 @@ def test_qubit_jw_fermion_integration(self): qubit_spectrum = sparse_eigenspectrum(qubit_sparse) fermion_sparse = jordan_wigner_sparse(fermion_operator) fermion_spectrum = sparse_eigenspectrum(fermion_sparse) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(fermion_spectrum - qubit_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(fermion_spectrum - qubit_spectrum))) class JordanWignerSparseTest(unittest.TestCase): - def test_jw_sparse_0create(self): expected = csc_matrix(([1], ([1], [0])), shape=(2, 2)) - self.assertTrue( - numpy.allclose( - jordan_wigner_sparse(FermionOperator('0^')).A, expected.A)) + self.assertTrue(numpy.allclose(jordan_wigner_sparse(FermionOperator('0^')).A, expected.A)) def test_jw_sparse_1annihilate(self): expected = csc_matrix(([1, -1], ([0, 2], [1, 3])), shape=(4, 4)) - self.assertTrue( - numpy.allclose( - jordan_wigner_sparse(FermionOperator('1')).A, expected.A)) + self.assertTrue(numpy.allclose(jordan_wigner_sparse(FermionOperator('1')).A, expected.A)) def test_jw_sparse_0create_2annihilate(self): - expected = csc_matrix(([-1j, 1j], ([4, 6], [1, 3])), - shape=(8, 8), - dtype=numpy.complex128) + expected = csc_matrix(([-1j, 1j], ([4, 6], [1, 3])), shape=(8, 8), dtype=numpy.complex128) self.assertTrue( - numpy.allclose( - jordan_wigner_sparse(FermionOperator('0^ 2', -1j)).A, - expected.A)) + numpy.allclose(jordan_wigner_sparse(FermionOperator('0^ 2', -1j)).A, expected.A) + ) def test_jw_sparse_0create_3annihilate(self): expected = csc_matrix( ([-1j, 1j, 1j, -1j], ([8, 10, 12, 14], [1, 3, 5, 7])), shape=(16, 16), - dtype=numpy.complex128) + dtype=numpy.complex128, + ) self.assertTrue( - numpy.allclose( - jordan_wigner_sparse(FermionOperator('0^ 3', -1j)).A, - expected.A)) + numpy.allclose(jordan_wigner_sparse(FermionOperator('0^ 3', -1j)).A, expected.A) + ) def test_jw_sparse_twobody(self): expected = csc_matrix(([1, 1], ([6, 14], [5, 13])), shape=(16, 16)) self.assertTrue( - numpy.allclose( - jordan_wigner_sparse(FermionOperator('2^ 1^ 1 3')).A, - expected.A)) + numpy.allclose(jordan_wigner_sparse(FermionOperator('2^ 1^ 1 3')).A, expected.A) + ) def test_qubit_operator_sparse_n_qubits_too_small(self): with self.assertRaises(ValueError): qubit_operator_sparse(QubitOperator('X3'), 1) def test_qubit_operator_sparse_n_qubits_not_specified(self): - expected = csc_matrix(([1, 1, 1, 1], ([1, 0, 3, 2], [0, 1, 2, 3])), - shape=(4, 4)) - self.assertTrue( - numpy.allclose( - qubit_operator_sparse(QubitOperator('X1')).A, expected.A)) + expected = csc_matrix(([1, 1, 1, 1], ([1, 0, 3, 2], [0, 1, 2, 3])), shape=(4, 4)) + self.assertTrue(numpy.allclose(qubit_operator_sparse(QubitOperator('X1')).A, expected.A)) def test_get_linear_qubit_operator_diagonal_wrong_n(self): """Testing with wrong n_qubits.""" @@ -183,9 +187,8 @@ def test_get_linear_qubit_operator_diagonal_0(self): vec_expected = numpy.zeros(8) self.assertTrue( - numpy.allclose( - get_linear_qubit_operator_diagonal(qubit_operator, 3), - vec_expected)) + numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator, 3), vec_expected) + ) def test_get_linear_qubit_operator_diagonal_zero(self): """Get zero diagonals from get_linear_qubit_operator_diagonal.""" @@ -193,8 +196,8 @@ def test_get_linear_qubit_operator_diagonal_zero(self): vec_expected = numpy.zeros(4) self.assertTrue( - numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), - vec_expected)) + numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), vec_expected) + ) def test_get_linear_qubit_operator_diagonal_non_zero(self): """Get non zero diagonals from get_linear_qubit_operator_diagonal.""" @@ -202,50 +205,45 @@ def test_get_linear_qubit_operator_diagonal_non_zero(self): vec_expected = numpy.array([1, -1, 1, -1, -1, 1, -1, 1]) self.assertTrue( - numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), - vec_expected)) + numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), vec_expected) + ) def test_get_linear_qubit_operator_diagonal_cmp_zero(self): """Compare get_linear_qubit_operator_diagonal with - get_linear_qubit_operator.""" + get_linear_qubit_operator.""" qubit_operator = QubitOperator('Z1 X2 Y5') - vec_expected = numpy.diag( - LinearQubitOperator(qubit_operator) * numpy.eye(2**6)) + vec_expected = numpy.diag(LinearQubitOperator(qubit_operator) * numpy.eye(2**6)) self.assertTrue( - numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), - vec_expected)) + numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), vec_expected) + ) def test_get_linear_qubit_operator_diagonal_cmp_non_zero(self): """Compare get_linear_qubit_operator_diagonal with - get_linear_qubit_operator.""" + get_linear_qubit_operator.""" qubit_operator = QubitOperator('Z1 Z2 Z5') - vec_expected = numpy.diag( - LinearQubitOperator(qubit_operator) * numpy.eye(2**6)) + vec_expected = numpy.diag(LinearQubitOperator(qubit_operator) * numpy.eye(2**6)) self.assertTrue( - numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), - vec_expected)) + numpy.allclose(get_linear_qubit_operator_diagonal(qubit_operator), vec_expected) + ) class ComputationalBasisStateTest(unittest.TestCase): - def test_computational_basis_state(self): comp_basis_state = jw_configuration_state([0, 2, 5], 7) - self.assertAlmostEqual(comp_basis_state[82], 1.) - self.assertAlmostEqual(sum(comp_basis_state), 1.) + self.assertAlmostEqual(comp_basis_state[82], 1.0) + self.assertAlmostEqual(sum(comp_basis_state), 1.0) class JWHartreeFockStateTest(unittest.TestCase): - def test_jw_hartree_fock_state(self): hartree_fock_state = jw_hartree_fock_state(3, 7) - self.assertAlmostEqual(hartree_fock_state[112], 1.) - self.assertAlmostEqual(sum(hartree_fock_state), 1.) + self.assertAlmostEqual(hartree_fock_state[112], 1.0) + self.assertAlmostEqual(sum(hartree_fock_state), 1.0) class JWNumberIndicesTest(unittest.TestCase): - def test_jw_sparse_index(self): """Test the indexing scheme for selecting specific particle numbers""" expected = [1, 2] @@ -272,7 +270,6 @@ def test_jw_number_indices(self): class JWSzIndicesTest(unittest.TestCase): - def test_jw_sz_indices(self): """Test the indexing scheme for selecting specific sz value""" @@ -281,14 +278,8 @@ def sz_integer(bitstring): minus the total number of occupied down sites.""" n_sites = len(bitstring) // 2 - n_up = len([ - site for site in range(n_sites) - if bitstring[up_index(site)] == '1' - ]) - n_down = len([ - site for site in range(n_sites) - if bitstring[down_index(site)] == '1' - ]) + n_up = len([site for site in range(n_sites) if bitstring[up_index(site)] == '1']) + n_down = len([site for site in range(n_sites) if bitstring[down_index(site)] == '1']) return n_up - n_down @@ -296,7 +287,7 @@ def jw_sz_indices_brute_force(sz_value, n_qubits): """Computes the correct indices by brute force.""" indices = [] for bitstring in itertools.product(['0', '1'], repeat=n_qubits): - if (sz_integer(bitstring) == int(2 * sz_value)): + if sz_integer(bitstring) == int(2 * sz_value): indices.append(int(''.join(bitstring), 2)) return indices @@ -304,9 +295,8 @@ def jw_sz_indices_brute_force(sz_value, n_qubits): # General test n_sites = numpy.random.randint(1, 10) n_qubits = 2 * n_sites - sz_int = ((-1)**numpy.random.randint(2) * - numpy.random.randint(n_sites + 1)) - sz_value = sz_int / 2. + sz_int = (-1) ** numpy.random.randint(2) * numpy.random.randint(n_sites + 1) + sz_value = sz_int / 2.0 correct_indices = jw_sz_indices_brute_force(sz_value, n_qubits) subspace_dimension = len(correct_indices) @@ -323,14 +313,11 @@ def jw_sz_indices_brute_force(sz_value, n_qubits): n_particles = abs(sz_int) correct_indices = [ - index for index in correct_indices - if bin(index)[2:].count('1') == n_particles + index for index in correct_indices if bin(index)[2:].count('1') == n_particles ] subspace_dimension = len(correct_indices) - calculated_indices = jw_sz_indices(sz_value, - n_qubits, - n_electrons=n_particles) + calculated_indices = jw_sz_indices(sz_value, n_qubits, n_electrons=n_particles) self.assertEqual(len(calculated_indices), subspace_dimension) @@ -354,26 +341,29 @@ def jw_sz_indices_brute_force(sz_value, n_qubits): class JWNumberRestrictOperatorTest(unittest.TestCase): - def test_jw_restrict_operator(self): """Test the scheme for restricting JW encoded operators to number""" # Make a Hamiltonian that cares mostly about number of electrons n_qubits = 4 target_electrons = 2 - penalty_const = 10. + penalty_const = 10.0 number_sparse = jordan_wigner_sparse(number_operator(n_qubits)) bias_sparse = jordan_wigner_sparse( - sum([ - FermionOperator(((i, 1), (i, 0)), 1.0) for i in range(n_qubits) - ], FermionOperator())) - hamiltonian_sparse = penalty_const * ( - number_sparse - - target_electrons * scipy.sparse.identity(2**n_qubits) - ).dot(number_sparse - target_electrons * - scipy.sparse.identity(2**n_qubits)) + bias_sparse + sum( + [FermionOperator(((i, 1), (i, 0)), 1.0) for i in range(n_qubits)], FermionOperator() + ) + ) + hamiltonian_sparse = ( + penalty_const + * (number_sparse - target_electrons * scipy.sparse.identity(2**n_qubits)).dot( + number_sparse - target_electrons * scipy.sparse.identity(2**n_qubits) + ) + + bias_sparse + ) restricted_hamiltonian = jw_number_restrict_operator( - hamiltonian_sparse, target_electrons, n_qubits) + hamiltonian_sparse, target_electrons, n_qubits + ) true_eigvals, _ = eigh(hamiltonian_sparse.A) test_eigvals, _ = eigh(restricted_hamiltonian.A) @@ -390,20 +380,15 @@ def test_jw_restrict_operator_hopping_to_1_particle(self): def test_jw_restrict_operator_interaction_to_1_particle(self): interaction = FermionOperator('3^ 2^ 4 1') interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) - interaction_restrict = jw_number_restrict_operator(interaction_sparse, - 1, - n_qubits=6) + interaction_restrict = jw_number_restrict_operator(interaction_sparse, 1, n_qubits=6) expected = csc_matrix(([], ([], [])), shape=(6, 6)) self.assertTrue(numpy.allclose(interaction_restrict.A, expected.A)) def test_jw_restrict_operator_interaction_to_2_particles(self): - interaction = (FermionOperator('3^ 2^ 4 1') + - FermionOperator('4^ 1^ 3 2')) + interaction = FermionOperator('3^ 2^ 4 1') + FermionOperator('4^ 1^ 3 2') interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) - interaction_restrict = jw_number_restrict_operator(interaction_sparse, - 2, - n_qubits=6) + interaction_restrict = jw_number_restrict_operator(interaction_sparse, 2, n_qubits=6) dim = 6 * 5 // 2 # shape of new sparse array @@ -414,12 +399,10 @@ def test_jw_restrict_operator_interaction_to_2_particles(self): self.assertTrue(numpy.allclose(interaction_restrict.A, expected.A)) def test_jw_restrict_operator_hopping_to_1_particle_default_nqubits(self): - interaction = (FermionOperator('3^ 2^ 4 1') + - FermionOperator('4^ 1^ 3 2')) + interaction = FermionOperator('3^ 2^ 4 1') + FermionOperator('4^ 1^ 3 2') interaction_sparse = jordan_wigner_sparse(interaction, n_qubits=6) # n_qubits should default to 6 - interaction_restrict = jw_number_restrict_operator( - interaction_sparse, 2) + interaction_restrict = jw_number_restrict_operator(interaction_sparse, 2) dim = 6 * 5 // 2 # shape of new sparse array @@ -432,15 +415,13 @@ def test_jw_restrict_operator_hopping_to_1_particle_default_nqubits(self): def test_jw_restrict_jellium_ground_state_integration(self): n_qubits = 4 grid = Grid(dimensions=1, length=n_qubits, scale=1.0) - jellium_hamiltonian = jordan_wigner_sparse( - jellium_model(grid, spinless=False)) + jellium_hamiltonian = jordan_wigner_sparse(jellium_model(grid, spinless=False)) # 2 * n_qubits because of spin number_sparse = jordan_wigner_sparse(number_operator(2 * n_qubits)) restricted_number = jw_number_restrict_operator(number_sparse, 2) - restricted_jellium_hamiltonian = jw_number_restrict_operator( - jellium_hamiltonian, 2) + restricted_jellium_hamiltonian = jw_number_restrict_operator(jellium_hamiltonian, 2) _, ground_state = get_ground_state(restricted_jellium_hamiltonian) @@ -449,27 +430,25 @@ def test_jw_restrict_jellium_ground_state_integration(self): class JWSzRestrictOperatorTest(unittest.TestCase): - def test_restrict_interaction_hamiltonian(self): """Test restricting a coulomb repulsion Hamiltonian to a specified Sz manifold.""" x_dim = 3 y_dim = 2 - interaction_term = fermi_hubbard(x_dim, y_dim, 0., 1.) + interaction_term = fermi_hubbard(x_dim, y_dim, 0.0, 1.0) interaction_sparse = get_sparse_operator(interaction_term) sz_value = 2 - interaction_restricted = jw_sz_restrict_operator( - interaction_sparse, sz_value) + interaction_restricted = jw_sz_restrict_operator(interaction_sparse, sz_value) restricted_interaction_values = set( - [int(value.real) for value in interaction_restricted.diagonal()]) + [int(value.real) for value in interaction_restricted.diagonal()] + ) # Originally the eigenvalues run from 0 to 6 but after restricting, # they should run from 0 to 2 self.assertEqual(restricted_interaction_values, {0, 1, 2}) class JWNumberRestrictStateTest(unittest.TestCase): - def test_jw_number_restrict_state(self): n_qubits = numpy.random.randint(1, 12) n_particles = numpy.random.randint(0, n_qubits) @@ -490,17 +469,15 @@ def test_jw_number_restrict_state(self): # Check that it has the same norm as the original vector self.assertAlmostEqual( - inner_product(vector, vector), - inner_product(restricted_vector, restricted_vector)) + inner_product(vector, vector), inner_product(restricted_vector, restricted_vector) + ) class JWSzRestrictStateTest(unittest.TestCase): - def test_jw_sz_restrict_state(self): n_sites = numpy.random.randint(1, 10) n_qubits = 2 * n_sites - sz_int = ((-1)**numpy.random.randint(2) * - numpy.random.randint(n_sites + 1)) + sz_int = (-1) ** numpy.random.randint(2) * numpy.random.randint(n_sites + 1) sz_value = sz_int / 2 sz_indices = jw_sz_indices(sz_value, n_qubits) @@ -519,17 +496,21 @@ def test_jw_sz_restrict_state(self): # Check that it has the same norm as the original vector self.assertAlmostEqual( - inner_product(vector, vector), - inner_product(restricted_vector, restricted_vector)) + inner_product(vector, vector), inner_product(restricted_vector, restricted_vector) + ) class JWGetGroundStatesByParticleNumberTest(unittest.TestCase): - def test_jw_get_ground_state_at_particle_number_herm_conserving(self): # Initialize a particle-number-conserving Hermitian operator - ferm_op = FermionOperator('0^ 1') + FermionOperator('1^ 0') + \ - FermionOperator('1^ 2') + FermionOperator('2^ 1') + \ - FermionOperator('1^ 3', -.4) + FermionOperator('3^ 1', -.4) + ferm_op = ( + FermionOperator('0^ 1') + + FermionOperator('1^ 0') + + FermionOperator('1^ 2') + + FermionOperator('2^ 1') + + FermionOperator('1^ 3', -0.4) + + FermionOperator('3^ 1', -0.4) + ) jw_hamiltonian = jordan_wigner(ferm_op) sparse_operator = get_sparse_operator(jw_hamiltonian) n_qubits = 4 @@ -539,19 +520,16 @@ def test_jw_get_ground_state_at_particle_number_herm_conserving(self): # Test each possible particle number for particle_number in range(n_qubits): # Get the ground energy and ground state at this particle number - energy, state = jw_get_ground_state_at_particle_number( - sparse_operator, particle_number) + energy, state = jw_get_ground_state_at_particle_number(sparse_operator, particle_number) # Check that it's an eigenvector with the correct eigenvalue - self.assertTrue( - numpy.allclose(sparse_operator.dot(state), energy * state)) + self.assertTrue(numpy.allclose(sparse_operator.dot(state), energy * state)) # Check that it has the correct particle number num = expectation(num_op, state) self.assertAlmostEqual(num, particle_number) def test_jw_get_ground_state_at_particle_number_hubbard(self): - model = fermi_hubbard(2, 2, 1.0, 4.0) sparse_operator = get_sparse_operator(model) n_qubits = count_qubits(model) @@ -560,19 +538,16 @@ def test_jw_get_ground_state_at_particle_number_hubbard(self): # Test each possible particle number for particle_number in range(n_qubits): # Get the ground energy and ground state at this particle number - energy, state = jw_get_ground_state_at_particle_number( - sparse_operator, particle_number) + energy, state = jw_get_ground_state_at_particle_number(sparse_operator, particle_number) # Check that it's an eigenvector with the correct eigenvalue - self.assertTrue( - numpy.allclose(sparse_operator.dot(state), energy * state)) + self.assertTrue(numpy.allclose(sparse_operator.dot(state), energy * state)) # Check that it has the correct particle number num = expectation(num_op, state) self.assertAlmostEqual(num, particle_number) def test_jw_get_ground_state_at_particle_number_jellium(self): - grid = Grid(2, 2, 1.0) model = jellium_model(grid, spinless=True, plane_wave=False) sparse_operator = get_sparse_operator(model) @@ -582,12 +557,10 @@ def test_jw_get_ground_state_at_particle_number_jellium(self): # Test each possible particle number for particle_number in range(n_qubits): # Get the ground energy and ground state at this particle number - energy, state = jw_get_ground_state_at_particle_number( - sparse_operator, particle_number) + energy, state = jw_get_ground_state_at_particle_number(sparse_operator, particle_number) # Check that it's an eigenvector with the correct eigenvalue - self.assertTrue( - numpy.allclose(sparse_operator.dot(state), energy * state)) + self.assertTrue(numpy.allclose(sparse_operator.dot(state), energy * state)) # Check that it has the correct particle number num = expectation(num_op, state) @@ -595,49 +568,44 @@ def test_jw_get_ground_state_at_particle_number_jellium(self): class JWSparseGivensRotationTest(unittest.TestCase): - def test_bad_input(self): with self.assertRaises(ValueError): - jw_sparse_givens_rotation(0, 2, 1., 1., 5) + jw_sparse_givens_rotation(0, 2, 1.0, 1.0, 5) with self.assertRaises(ValueError): - jw_sparse_givens_rotation(4, 5, 1., 1., 5) + jw_sparse_givens_rotation(4, 5, 1.0, 1.0, 5) class GroundStateTest(unittest.TestCase): - def test_get_ground_state_hermitian(self): ground = get_ground_state( - get_sparse_operator( - QubitOperator('Y0 X1') + QubitOperator('Z0 Z1'))) - expected_state = csc_matrix(([1j, 1], ([1, 2], [0, 0])), - shape=(4, 1), - dtype=numpy.complex128).A + get_sparse_operator(QubitOperator('Y0 X1') + QubitOperator('Z0 Z1')) + ) + expected_state = csc_matrix( + ([1j, 1], ([1, 2], [0, 0])), shape=(4, 1), dtype=numpy.complex128 + ).A expected_state /= numpy.sqrt(2.0) self.assertAlmostEqual(ground[0], -2) - self.assertAlmostEqual( - numpy.absolute(expected_state.T.conj().dot(ground[1]))[0], 1.) + self.assertAlmostEqual(numpy.absolute(expected_state.T.conj().dot(ground[1]))[0], 1.0) class ExpectationTest(unittest.TestCase): - def test_expectation_correct_sparse_matrix(self): operator = get_sparse_operator(QubitOperator('X0'), n_qubits=2) - vector = numpy.array([0., 1.j, 0., 1.j]) + vector = numpy.array([0.0, 1.0j, 0.0, 1.0j]) self.assertAlmostEqual(expectation(operator, vector), 2.0) - density_matrix = scipy.sparse.csc_matrix( - numpy.outer(vector, numpy.conjugate(vector))) + density_matrix = scipy.sparse.csc_matrix(numpy.outer(vector, numpy.conjugate(vector))) self.assertAlmostEqual(expectation(operator, density_matrix), 2.0) def test_expectation_correct_linear_operator(self): operator = LinearQubitOperator(QubitOperator('X0'), n_qubits=2) - vector = numpy.array([0., 1.j, 0., 1.j]) + vector = numpy.array([0.0, 1.0j, 0.0, 1.0j]) self.assertAlmostEqual(expectation(operator, vector), 2.0) def test_expectation_handles_column_vector(self): operator = get_sparse_operator(QubitOperator('X0'), n_qubits=2) - vector = numpy.array([[0.], [1.j], [0.], [1.j]]) + vector = numpy.array([[0.0], [1.0j], [0.0], [1.0j]]) self.assertAlmostEqual(expectation(operator, vector), 2.0) def test_expectation_correct_zero(self): @@ -655,105 +623,102 @@ def test_execptions(self): class VarianceTest(unittest.TestCase): - def test_variance_row_vector(self): - X = pauli_matrix_map['X'] Z = pauli_matrix_map['Z'] - zero = numpy.array([1., 0.]) - plus = numpy.array([1., 1.]) / numpy.sqrt(2) - minus = numpy.array([1., -1.]) / numpy.sqrt(2) + zero = numpy.array([1.0, 0.0]) + plus = numpy.array([1.0, 1.0]) / numpy.sqrt(2) + minus = numpy.array([1.0, -1.0]) / numpy.sqrt(2) - self.assertAlmostEqual(variance(Z, zero), 0.) - self.assertAlmostEqual(variance(X, zero), 1.) + self.assertAlmostEqual(variance(Z, zero), 0.0) + self.assertAlmostEqual(variance(X, zero), 1.0) - self.assertAlmostEqual(variance(Z, plus), 1.) - self.assertAlmostEqual(variance(X, plus), 0.) + self.assertAlmostEqual(variance(Z, plus), 1.0) + self.assertAlmostEqual(variance(X, plus), 0.0) - self.assertAlmostEqual(variance(Z, minus), 1.) - self.assertAlmostEqual(variance(X, minus), 0.) + self.assertAlmostEqual(variance(Z, minus), 1.0) + self.assertAlmostEqual(variance(X, minus), 0.0) def test_variance_column_vector(self): - X = pauli_matrix_map['X'] Z = pauli_matrix_map['Z'] - zero = numpy.array([[1.], [0.]]) - plus = numpy.array([[1.], [1.]]) / numpy.sqrt(2) - minus = numpy.array([[1.], [-1.]]) / numpy.sqrt(2) + zero = numpy.array([[1.0], [0.0]]) + plus = numpy.array([[1.0], [1.0]]) / numpy.sqrt(2) + minus = numpy.array([[1.0], [-1.0]]) / numpy.sqrt(2) - self.assertAlmostEqual(variance(Z, zero), 0.) - self.assertAlmostEqual(variance(X, zero), 1.) + self.assertAlmostEqual(variance(Z, zero), 0.0) + self.assertAlmostEqual(variance(X, zero), 1.0) - self.assertAlmostEqual(variance(Z, plus), 1.) - self.assertAlmostEqual(variance(X, plus), 0.) + self.assertAlmostEqual(variance(Z, plus), 1.0) + self.assertAlmostEqual(variance(X, plus), 0.0) - self.assertAlmostEqual(variance(Z, minus), 1.) - self.assertAlmostEqual(variance(X, minus), 0.) + self.assertAlmostEqual(variance(Z, minus), 1.0) + self.assertAlmostEqual(variance(X, minus), 0.0) class ExpectationComputationalBasisStateTest(unittest.TestCase): - def test_expectation_fermion_operator_single_number_terms(self): operator = FermionOperator('3^ 3', 1.9) + FermionOperator('2^ 1') state = csc_matrix(([1], ([15], [0])), shape=(16, 1)) - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 1.9) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 1.9) def test_expectation_fermion_operator_two_number_terms(self): - operator = (FermionOperator('2^ 2', 1.9) + FermionOperator('2^ 1') + - FermionOperator('2^ 1^ 2 1', -1.7)) + operator = ( + FermionOperator('2^ 2', 1.9) + + FermionOperator('2^ 1') + + FermionOperator('2^ 1^ 2 1', -1.7) + ) state = csc_matrix(([1], ([6], [0])), shape=(16, 1)) - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 3.6) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 3.6) def test_expectation_identity_fermion_operator(self): operator = FermionOperator.identity() * 1.1 state = csc_matrix(([1], ([6], [0])), shape=(16, 1)) - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 1.1) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 1.1) def test_expectation_state_is_list_single_number_terms(self): operator = FermionOperator('3^ 3', 1.9) + FermionOperator('2^ 1') state = [1, 1, 1, 1] - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 1.9) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 1.9) def test_expectation_state_is_list_fermion_operator_two_number_terms(self): - operator = (FermionOperator('2^ 2', 1.9) + FermionOperator('2^ 1') + - FermionOperator('2^ 1^ 2 1', -1.7)) + operator = ( + FermionOperator('2^ 2', 1.9) + + FermionOperator('2^ 1') + + FermionOperator('2^ 1^ 2 1', -1.7) + ) state = [0, 1, 1] - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 3.6) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 3.6) def test_expectation_state_is_list_identity_fermion_operator(self): operator = FermionOperator.identity() * 1.1 state = [0, 1, 1] - self.assertAlmostEqual( - expectation_computational_basis_state(operator, state), 1.1) + self.assertAlmostEqual(expectation_computational_basis_state(operator, state), 1.1) def test_expectation_bad_operator_type(self): with self.assertRaises(TypeError): expectation_computational_basis_state( - 'never', csc_matrix(([1], ([6], [0])), shape=(16, 1))) + 'never', csc_matrix(([1], ([6], [0])), shape=(16, 1)) + ) def test_expectation_qubit_operator_not_implemented(self): with self.assertRaises(NotImplementedError): expectation_computational_basis_state( - QubitOperator(), csc_matrix(([1], ([6], [0])), shape=(16, 1))) + QubitOperator(), csc_matrix(([1], ([6], [0])), shape=(16, 1)) + ) class ExpectationDualBasisOperatorWithPlaneWaveBasisState(unittest.TestCase): - def setUp(self): grid_length = 4 dimension = 1 - wigner_seitz_radius = 10. + wigner_seitz_radius = 10.0 self.spinless = True self.n_spatial_orbitals = grid_length**dimension @@ -761,8 +726,7 @@ def setUp(self): self.n_particles = 3 # Compute appropriate length scale and the corresponding grid. - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - self.n_particles, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, self.n_particles, dimension) self.grid1 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. @@ -771,105 +735,128 @@ def setUp(self): hamiltonian.compress() occupied_states = numpy.array( - lowest_single_particle_energy_states(hamiltonian, self.n_particles)) + lowest_single_particle_energy_states(hamiltonian, self.n_particles) + ) self.hf_state_index1 = numpy.sum(2**occupied_states) self.hf_state1 = numpy.zeros(2**n_qubits) self.hf_state1[self.hf_state_index1] = 1.0 - self.orbital_occupations1 = [ - digit == '1' for digit in bin(self.hf_state_index1)[2:] - ][::-1] + self.orbital_occupations1 = [digit == '1' for digit in bin(self.hf_state_index1)[2:]][::-1] self.occupied_orbitals1 = [ - index for index, occupied in enumerate(self.orbital_occupations1) - if occupied + index for index, occupied in enumerate(self.orbital_occupations1) if occupied ] self.reversed_occupied_orbitals1 = list(self.occupied_orbitals1) for i in range(len(self.reversed_occupied_orbitals1)): - self.reversed_occupied_orbitals1[i] = -1 + int( - numpy.log2(self.hf_state1.shape[0]) - ) - self.reversed_occupied_orbitals1[i] + self.reversed_occupied_orbitals1[i] = ( + -1 + int(numpy.log2(self.hf_state1.shape[0])) - self.reversed_occupied_orbitals1[i] + ) self.reversed_hf_state_index1 = sum( - 2**index for index in self.reversed_occupied_orbitals1) + 2**index for index in self.reversed_occupied_orbitals1 + ) def test_1body_hopping_operator_1D(self): operator = FermionOperator('2^ 0') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_1body_number_operator_1D(self): operator = FermionOperator('2^ 2') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_2body_partial_number_operator_high_1D(self): operator = FermionOperator('2^ 1^ 2 0') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_2body_partial_number_operator_mid_1D(self): operator = FermionOperator('1^ 0^ 1 2') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_3body_double_number_operator_1D(self): operator = FermionOperator('3^ 2^ 1^ 3 1 0') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_2body_adjacent_number_operator_1D(self): operator = FermionOperator('3^ 2^ 2 1') operator = normal_ordered(operator) transformed_operator = normal_ordered( - fourier_transform(operator, self.grid1, self.spinless)) + fourier_transform(operator, self.grid1, self.spinless) + ) - expected = expectation(get_sparse_operator(transformed_operator), - self.hf_state1) + expected = expectation(get_sparse_operator(transformed_operator), self.hf_state1) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals1, self.n_spatial_orbitals, - self.grid1, self.spinless) + operator, + self.reversed_occupied_orbitals1, + self.n_spatial_orbitals, + self.grid1, + self.spinless, + ) self.assertAlmostEqual(expected, actual) def test_1d5_with_spin_10particles(self): @@ -884,8 +871,7 @@ def test_1d5_with_spin_10particles(self): n_qubits *= 2 n_particles_big = 10 - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles_big, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles_big, dimension) self.grid3 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. @@ -894,32 +880,35 @@ def test_1d5_with_spin_10particles(self): hamiltonian.compress() occupied_states = numpy.array( - lowest_single_particle_energy_states(hamiltonian, n_particles_big)) + lowest_single_particle_energy_states(hamiltonian, n_particles_big) + ) self.hf_state_index3 = numpy.sum(2**occupied_states) - self.hf_state3 = csc_matrix(([1.0], ([self.hf_state_index3], [0])), - shape=(2**n_qubits, 1)) + self.hf_state3 = csc_matrix( + ([1.0], ([self.hf_state_index3], [0])), shape=(2**n_qubits, 1) + ) - self.orbital_occupations3 = [ - digit == '1' for digit in bin(self.hf_state_index3)[2:] - ][::-1] + self.orbital_occupations3 = [digit == '1' for digit in bin(self.hf_state_index3)[2:]][::-1] self.occupied_orbitals3 = [ - index for index, occupied in enumerate(self.orbital_occupations3) - if occupied + index for index, occupied in enumerate(self.orbital_occupations3) if occupied ] self.reversed_occupied_orbitals3 = list(self.occupied_orbitals3) for i in range(len(self.reversed_occupied_orbitals3)): - self.reversed_occupied_orbitals3[i] = -1 + int( - numpy.log2(self.hf_state3.shape[0]) - ) - self.reversed_occupied_orbitals3[i] + self.reversed_occupied_orbitals3[i] = ( + -1 + int(numpy.log2(self.hf_state3.shape[0])) - self.reversed_occupied_orbitals3[i] + ) self.reversed_hf_state_index3 = sum( - 2**index for index in self.reversed_occupied_orbitals3) - - operator = (FermionOperator('6^ 0^ 1^ 3 5 4', 2) + - FermionOperator('7^ 6^ 5 4', -3.7j) + - FermionOperator('3^ 3', 2.1) + FermionOperator('3^ 2', 1.7)) + 2**index for index in self.reversed_occupied_orbitals3 + ) + + operator = ( + FermionOperator('6^ 0^ 1^ 3 5 4', 2) + + FermionOperator('7^ 6^ 5 4', -3.7j) + + FermionOperator('3^ 3', 2.1) + + FermionOperator('3^ 2', 1.7) + ) operator = normal_ordered(operator) normal_ordered(fourier_transform(operator, self.grid3, spinless)) @@ -927,8 +916,8 @@ def test_1d5_with_spin_10particles(self): # Calculated from expectation(get_sparse_operator( # transformed_operator), self.hf_state3) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, - self.grid3, spinless) + operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, self.grid3, spinless + ) self.assertAlmostEqual(expected, actual) @@ -944,8 +933,7 @@ def test_1d5_with_spin_7particles(self): n_qubits *= 2 n_particles_big = 7 - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles_big, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles_big, dimension) self.grid3 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. @@ -954,33 +942,35 @@ def test_1d5_with_spin_7particles(self): hamiltonian.compress() occupied_states = numpy.array( - lowest_single_particle_energy_states(hamiltonian, n_particles_big)) + lowest_single_particle_energy_states(hamiltonian, n_particles_big) + ) self.hf_state_index3 = numpy.sum(2**occupied_states) - self.hf_state3 = csc_matrix(([1.0], ([self.hf_state_index3], [0])), - shape=(2**n_qubits, 1)) + self.hf_state3 = csc_matrix( + ([1.0], ([self.hf_state_index3], [0])), shape=(2**n_qubits, 1) + ) - self.orbital_occupations3 = [ - digit == '1' for digit in bin(self.hf_state_index3)[2:] - ][::-1] + self.orbital_occupations3 = [digit == '1' for digit in bin(self.hf_state_index3)[2:]][::-1] self.occupied_orbitals3 = [ - index for index, occupied in enumerate(self.orbital_occupations3) - if occupied + index for index, occupied in enumerate(self.orbital_occupations3) if occupied ] self.reversed_occupied_orbitals3 = list(self.occupied_orbitals3) for i in range(len(self.reversed_occupied_orbitals3)): - self.reversed_occupied_orbitals3[i] = -1 + int( - numpy.log2(self.hf_state3.shape[0]) - ) - self.reversed_occupied_orbitals3[i] + self.reversed_occupied_orbitals3[i] = ( + -1 + int(numpy.log2(self.hf_state3.shape[0])) - self.reversed_occupied_orbitals3[i] + ) self.reversed_hf_state_index3 = sum( - 2**index for index in self.reversed_occupied_orbitals3) - - operator = (FermionOperator('6^ 0^ 1^ 3 5 4', 2) + - FermionOperator('7^ 2^ 4 1') + - FermionOperator('3^ 3', 2.1) + - FermionOperator('5^ 3^ 1 0', 7.3)) + 2**index for index in self.reversed_occupied_orbitals3 + ) + + operator = ( + FermionOperator('6^ 0^ 1^ 3 5 4', 2) + + FermionOperator('7^ 2^ 4 1') + + FermionOperator('3^ 3', 2.1) + + FermionOperator('5^ 3^ 1 0', 7.3) + ) operator = normal_ordered(operator) normal_ordered(fourier_transform(operator, self.grid3, spinless)) @@ -988,8 +978,8 @@ def test_1d5_with_spin_7particles(self): # Calculated with expected = expectation(get_sparse_operator( # transformed_operator), self.hf_state3) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, - self.grid3, spinless) + operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, self.grid3, spinless + ) self.assertAlmostEqual(expected, actual) @@ -1003,8 +993,7 @@ def test_3d2_spinless(self): n_qubits = n_spatial_orbitals n_particles_big = 5 - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles_big, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles_big, dimension) self.grid3 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. @@ -1013,40 +1002,42 @@ def test_3d2_spinless(self): hamiltonian.compress() occupied_states = numpy.array( - lowest_single_particle_energy_states(hamiltonian, n_particles_big)) + lowest_single_particle_energy_states(hamiltonian, n_particles_big) + ) self.hf_state_index3 = numpy.sum(2**occupied_states) - self.hf_state3 = csc_matrix(([1.0], ([self.hf_state_index3], [0])), - shape=(2**n_qubits, 1)) + self.hf_state3 = csc_matrix( + ([1.0], ([self.hf_state_index3], [0])), shape=(2**n_qubits, 1) + ) - self.orbital_occupations3 = [ - digit == '1' for digit in bin(self.hf_state_index3)[2:] - ][::-1] + self.orbital_occupations3 = [digit == '1' for digit in bin(self.hf_state_index3)[2:]][::-1] self.occupied_orbitals3 = [ - index for index, occupied in enumerate(self.orbital_occupations3) - if occupied + index for index, occupied in enumerate(self.orbital_occupations3) if occupied ] self.reversed_occupied_orbitals3 = list(self.occupied_orbitals3) for i in range(len(self.reversed_occupied_orbitals3)): - self.reversed_occupied_orbitals3[i] = -1 + int( - numpy.log2(self.hf_state3.shape[0]) - ) - self.reversed_occupied_orbitals3[i] + self.reversed_occupied_orbitals3[i] = ( + -1 + int(numpy.log2(self.hf_state3.shape[0])) - self.reversed_occupied_orbitals3[i] + ) self.reversed_hf_state_index3 = sum( - 2**index for index in self.reversed_occupied_orbitals3) - - operator = (FermionOperator('4^ 2^ 3^ 5 5 4', 2) + - FermionOperator('7^ 6^ 7 4', -3.7j) + - FermionOperator('3^ 7', 2.1)) + 2**index for index in self.reversed_occupied_orbitals3 + ) + + operator = ( + FermionOperator('4^ 2^ 3^ 5 5 4', 2) + + FermionOperator('7^ 6^ 7 4', -3.7j) + + FermionOperator('3^ 7', 2.1) + ) operator = normal_ordered(operator) normal_ordered(fourier_transform(operator, self.grid3, spinless)) expected = -0.2625 - 0.4625j # Calculated with expectation(get_sparse_operator( # transformed_operator), self.hf_state3) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, - self.grid3, spinless) + operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, self.grid3, spinless + ) self.assertAlmostEqual(expected, actual) @@ -1062,8 +1053,7 @@ def test_3d2_with_spin(self): n_qubits *= 2 n_particles_big = 9 - length_scale = wigner_seitz_length_scale(wigner_seitz_radius, - n_particles_big, dimension) + length_scale = wigner_seitz_length_scale(wigner_seitz_radius, n_particles_big, dimension) self.grid3 = Grid(dimension, grid_length, length_scale) # Get the occupied orbitals of the plane-wave basis Hartree-Fock state. @@ -1072,32 +1062,34 @@ def test_3d2_with_spin(self): hamiltonian.compress() occupied_states = numpy.array( - lowest_single_particle_energy_states(hamiltonian, n_particles_big)) + lowest_single_particle_energy_states(hamiltonian, n_particles_big) + ) self.hf_state_index3 = numpy.sum(2**occupied_states) - self.hf_state3 = csc_matrix(([1.0], ([self.hf_state_index3], [0])), - shape=(2**n_qubits, 1)) + self.hf_state3 = csc_matrix( + ([1.0], ([self.hf_state_index3], [0])), shape=(2**n_qubits, 1) + ) - self.orbital_occupations3 = [ - digit == '1' for digit in bin(self.hf_state_index3)[2:] - ][::-1] + self.orbital_occupations3 = [digit == '1' for digit in bin(self.hf_state_index3)[2:]][::-1] self.occupied_orbitals3 = [ - index for index, occupied in enumerate(self.orbital_occupations3) - if occupied + index for index, occupied in enumerate(self.orbital_occupations3) if occupied ] self.reversed_occupied_orbitals3 = list(self.occupied_orbitals3) for i in range(len(self.reversed_occupied_orbitals3)): - self.reversed_occupied_orbitals3[i] = -1 + int( - numpy.log2(self.hf_state3.shape[0]) - ) - self.reversed_occupied_orbitals3[i] + self.reversed_occupied_orbitals3[i] = ( + -1 + int(numpy.log2(self.hf_state3.shape[0])) - self.reversed_occupied_orbitals3[i] + ) self.reversed_hf_state_index3 = sum( - 2**index for index in self.reversed_occupied_orbitals3) - - operator = (FermionOperator('4^ 2^ 3^ 5 5 4', 2) + - FermionOperator('7^ 6^ 7 4', -3.7j) + - FermionOperator('3^ 7', 2.1)) + 2**index for index in self.reversed_occupied_orbitals3 + ) + + operator = ( + FermionOperator('4^ 2^ 3^ 5 5 4', 2) + + FermionOperator('7^ 6^ 7 4', -3.7j) + + FermionOperator('3^ 7', 2.1) + ) operator = normal_ordered(operator) normal_ordered(fourier_transform(operator, self.grid3, spinless)) @@ -1105,39 +1097,37 @@ def test_3d2_with_spin(self): # Calculated from expected = expectation(get_sparse_operator( # transformed_operator), self.hf_state3) actual = expectation_db_operator_with_pw_basis_state( - operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, - self.grid3, spinless) + operator, self.reversed_occupied_orbitals3, n_spatial_orbitals, self.grid3, spinless + ) self.assertAlmostEqual(expected, actual) class GetGapTest(unittest.TestCase): - def test_get_gap(self): operator = QubitOperator('Y0 X1') + QubitOperator('Z0 Z1') self.assertAlmostEqual(get_gap(get_sparse_operator(operator)), 2.0) def test_get_gap_nonhermitian_error(self): - operator = (QubitOperator('X0 Y1', 1 + 1j) + - QubitOperator('Z0 Z1', 1j) + QubitOperator((), 2 + 1j)) + operator = ( + QubitOperator('X0 Y1', 1 + 1j) + QubitOperator('Z0 Z1', 1j) + QubitOperator((), 2 + 1j) + ) with self.assertRaises(ValueError): get_gap(get_sparse_operator(operator)) class InnerProductTest(unittest.TestCase): - def test_inner_product(self): - state_1 = numpy.array([1., 1.j]) - state_2 = numpy.array([1., -1.j]) + state_1 = numpy.array([1.0, 1.0j]) + state_2 = numpy.array([1.0, -1.0j]) - self.assertAlmostEqual(inner_product(state_1, state_1), 2.) - self.assertAlmostEqual(inner_product(state_1, state_2), 0.) + self.assertAlmostEqual(inner_product(state_1, state_1), 2.0) + self.assertAlmostEqual(inner_product(state_1, state_2), 0.0) class BosonSparseTest(unittest.TestCase): - def setUp(self): - self.hbar = 1. + self.hbar = 1.0 self.d = 5 self.b = numpy.diag(numpy.sqrt(numpy.arange(1, self.d)), 1) self.bd = self.b.conj().T @@ -1271,8 +1261,7 @@ def test_boson_operator_sparse_multi_mode(self): expected = numpy.identity(self.d**2) for term in op.terms: for i, j in term: - expected = expected.dot( - single_quad_op_sparse(2, i, j, self.hbar, self.d).toarray()) + expected = expected.dot(single_quad_op_sparse(2, i, j, self.hbar, self.d).toarray()) self.assertTrue(numpy.allclose(res, expected)) def test_boson_operator_sparse_addition(self): @@ -1288,29 +1277,21 @@ def test_boson_operator_sparse_addition(self): class GetNumberPreservingSparseOperatorIntegrationTestLiH(unittest.TestCase): - def setUp(self): # Set up molecule. - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H1-Li1_sto-3g_singlet_1.45') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. self.molecular_hamiltonian = self.molecule.get_molecular_hamiltonian() - self.hubbard_hamiltonian = fermi_hubbard(2, - 2, - 1.0, - 4.0, - chemical_potential=.2, - magnetic_field=0.0, - spinless=False) + self.hubbard_hamiltonian = fermi_hubbard( + 2, 2, 1.0, 4.0, chemical_potential=0.2, magnetic_field=0.0, spinless=False + ) def test_exceptions(self): op = FermionOperator('1') @@ -1320,10 +1301,8 @@ def test_exceptions(self): def test_number_on_reference(self): sum_n_op = FermionOperator() sum_sparse_n_op = get_number_preserving_sparse_operator( - sum_n_op, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=False) + sum_n_op, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=False + ) space_size = sum_sparse_n_op.shape[0] reference = numpy.zeros((space_size)) @@ -1334,10 +1313,8 @@ def test_number_on_reference(self): sum_n_op += n_op sparse_n_op = get_number_preserving_sparse_operator( - n_op, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=False) + n_op, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=False + ) sum_sparse_n_op += sparse_n_op @@ -1349,25 +1326,19 @@ def test_number_on_reference(self): assert expectation == 0.0 convert_after_adding = get_number_preserving_sparse_operator( - sum_n_op, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=False) + sum_n_op, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=False + ) - assert scipy.sparse.linalg.norm(convert_after_adding - - sum_sparse_n_op) < 1E-9 + assert scipy.sparse.linalg.norm(convert_after_adding - sum_sparse_n_op) < 1e-9 - assert reference.dot(sum_sparse_n_op.dot(reference)) - \ - self.molecule.n_electrons < 1E-9 + assert reference.dot(sum_sparse_n_op.dot(reference)) - self.molecule.n_electrons < 1e-9 def test_space_size_correct(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) sparse_ham = get_number_preserving_sparse_operator( - hamiltonian_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) space_size = sparse_ham.shape[0] @@ -1378,10 +1349,8 @@ def test_hf_energy(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) sparse_ham = get_number_preserving_sparse_operator( - hamiltonian_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) space_size = sparse_ham.shape[0] reference = numpy.zeros((space_size)) @@ -1389,13 +1358,11 @@ def test_hf_energy(self): sparse_hf_energy = reference.dot(sparse_ham.dot(reference)) - assert numpy.linalg.norm(sparse_hf_energy - - self.molecule.hf_energy) < 1E-9 + assert numpy.linalg.norm(sparse_hf_energy - self.molecule.hf_energy) < 1e-9 def test_one_body_hf_energy(self): one_body_part = self.molecular_hamiltonian - one_body_part.two_body_tensor = numpy.zeros_like( - one_body_part.two_body_tensor) + one_body_part.two_body_tensor = numpy.zeros_like(one_body_part.two_body_tensor) one_body_fop = get_fermion_operator(one_body_part) one_body_regular_sparse_op = get_sparse_operator(one_body_fop) @@ -1407,14 +1374,11 @@ def test_one_body_hf_energy(self): hf_state[0] = 1.0 hf_state = make_hf_sparse_op.dot(hf_state) - regular_sparse_hf_energy = \ - (hf_state.dot(one_body_regular_sparse_op.dot(hf_state))).real + regular_sparse_hf_energy = (hf_state.dot(one_body_regular_sparse_op.dot(hf_state))).real one_body_sparse_op = get_number_preserving_sparse_operator( - one_body_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + one_body_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) space_size = one_body_sparse_op.shape[0] reference = numpy.zeros((space_size)) @@ -1422,49 +1386,44 @@ def test_one_body_hf_energy(self): sparse_hf_energy = reference.dot(one_body_sparse_op.dot(reference)) - assert numpy.linalg.norm(sparse_hf_energy - - regular_sparse_hf_energy) < 1E-9 + assert numpy.linalg.norm(sparse_hf_energy - regular_sparse_hf_energy) < 1e-9 def test_ground_state_energy(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) sparse_ham = get_number_preserving_sparse_operator( - hamiltonian_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) eig_val, _ = scipy.sparse.linalg.eigsh(sparse_ham, k=1, which='SA') - assert numpy.abs(eig_val[0] - self.molecule.fci_energy) < 1E-9 + assert numpy.abs(eig_val[0] - self.molecule.fci_energy) < 1e-9 def test_doubles_are_subset(self): - reference_determinants = [[ - True, True, True, True, False, False, False, False, False, False, - False, False - ], - [ - True, True, False, False, False, False, - True, True, False, False, False, False - ]] + reference_determinants = [ + [True, True, True, True, False, False, False, False, False, False, False, False], + [True, True, False, False, False, False, True, True, False, False, False, False], + ] for reference_determinant in reference_determinants: reference_determinant = numpy.asarray(reference_determinant) doubles_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=2, - spin_preserving=True))) + _iterate_basis_(reference_determinant, excitation_level=2, spin_preserving=True) + ) + ) doubles_int_state_array = doubles_state_array.dot( - 1 << numpy.arange(doubles_state_array.shape[1])[::-1]) + 1 << numpy.arange(doubles_state_array.shape[1])[::-1] + ) all_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=4, - spin_preserving=True))) + _iterate_basis_(reference_determinant, excitation_level=4, spin_preserving=True) + ) + ) all_int_state_array = all_state_array.dot( - 1 << numpy.arange(all_state_array.shape[1])[::-1]) + 1 << numpy.arange(all_state_array.shape[1])[::-1] + ) for item in doubles_int_state_array: assert item in all_int_state_array @@ -1473,19 +1432,23 @@ def test_doubles_are_subset(self): reference_determinant = numpy.asarray(reference_determinant) doubles_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=2, - spin_preserving=True))) + _iterate_basis_(reference_determinant, excitation_level=2, spin_preserving=True) + ) + ) doubles_int_state_array = doubles_state_array.dot( - 1 << numpy.arange(doubles_state_array.shape[1])[::-1]) + 1 << numpy.arange(doubles_state_array.shape[1])[::-1] + ) all_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=4, - spin_preserving=False))) + _iterate_basis_( + reference_determinant, excitation_level=4, spin_preserving=False + ) + ) + ) all_int_state_array = all_state_array.dot( - 1 << numpy.arange(all_state_array.shape[1])[::-1]) + 1 << numpy.arange(all_state_array.shape[1])[::-1] + ) for item in doubles_int_state_array: assert item in all_int_state_array @@ -1494,19 +1457,25 @@ def test_doubles_are_subset(self): reference_determinant = numpy.asarray(reference_determinant) doubles_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=2, - spin_preserving=False))) + _iterate_basis_( + reference_determinant, excitation_level=2, spin_preserving=False + ) + ) + ) doubles_int_state_array = doubles_state_array.dot( - 1 << numpy.arange(doubles_state_array.shape[1])[::-1]) + 1 << numpy.arange(doubles_state_array.shape[1])[::-1] + ) all_state_array = numpy.asarray( list( - _iterate_basis_(reference_determinant, - excitation_level=4, - spin_preserving=False))) + _iterate_basis_( + reference_determinant, excitation_level=4, spin_preserving=False + ) + ) + ) all_int_state_array = all_state_array.dot( - 1 << numpy.arange(all_state_array.shape[1])[::-1]) + 1 << numpy.arange(all_state_array.shape[1])[::-1] + ) for item in doubles_int_state_array: assert item in all_int_state_array @@ -1515,12 +1484,10 @@ def test_full_ham_hermitian(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) sparse_ham = get_number_preserving_sparse_operator( - hamiltonian_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) - assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1e-9 def test_full_ham_hermitian_non_spin_preserving(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1529,9 +1496,10 @@ def test_full_ham_hermitian_non_spin_preserving(self): hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, - spin_preserving=False) + spin_preserving=False, + ) - assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1e-9 def test_singles_simple_one_body_term_hermitian(self): fop = FermionOperator(((3, 1), (1, 0))) @@ -1542,17 +1510,18 @@ def test_singles_simple_one_body_term_hermitian(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) sparse_op_conj = get_number_preserving_sparse_operator( fop_conj, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) - assert scipy.sparse.linalg.norm(sparse_op - - sparse_op_conj.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_op - sparse_op_conj.getH()) < 1e-9 def test_singles_simple_two_body_term_hermitian(self): fop = FermionOperator(((3, 1), (8, 1), (1, 0), (4, 0))) @@ -1563,17 +1532,18 @@ def test_singles_simple_two_body_term_hermitian(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) sparse_op_conj = get_number_preserving_sparse_operator( fop_conj, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) - assert scipy.sparse.linalg.norm(sparse_op - - sparse_op_conj.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_op - sparse_op_conj.getH()) < 1e-9 def test_singles_repeating_two_body_term_hermitian(self): fop = FermionOperator(((3, 1), (1, 1), (5, 0), (1, 0))) @@ -1584,17 +1554,18 @@ def test_singles_repeating_two_body_term_hermitian(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) sparse_op_conj = get_number_preserving_sparse_operator( fop_conj, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) - assert scipy.sparse.linalg.norm(sparse_op - - sparse_op_conj.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_op - sparse_op_conj.getH()) < 1e-9 def test_singles_ham_hermitian(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1604,9 +1575,10 @@ def test_singles_ham_hermitian(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=1) + excitation_level=1, + ) - assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1e-9 def test_singles_ham_hermitian_non_spin_preserving(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1616,9 +1588,10 @@ def test_singles_ham_hermitian_non_spin_preserving(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=False, - excitation_level=1) + excitation_level=1, + ) - assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1E-9 + assert scipy.sparse.linalg.norm(sparse_ham - sparse_ham.getH()) < 1e-9 def test_cisd_energy(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1628,11 +1601,12 @@ def test_cisd_energy(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True, - excitation_level=2) + excitation_level=2, + ) eig_val, _ = scipy.sparse.linalg.eigsh(sparse_ham, k=1, which='SA') - assert numpy.abs(eig_val[0] - self.molecule.cisd_energy) < 1E-9 + assert numpy.abs(eig_val[0] - self.molecule.cisd_energy) < 1e-9 def test_cisd_energy_non_spin_preserving(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1642,28 +1616,26 @@ def test_cisd_energy_non_spin_preserving(self): self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=False, - excitation_level=2) + excitation_level=2, + ) eig_val, _ = scipy.sparse.linalg.eigsh(sparse_ham, k=1, which='SA') - assert numpy.abs(eig_val[0] - self.molecule.cisd_energy) < 1E-9 + assert numpy.abs(eig_val[0] - self.molecule.cisd_energy) < 1e-9 def test_cisd_matches_fci_energy_two_electron_hubbard(self): hamiltonian_fop = self.hubbard_hamiltonian sparse_ham_cisd = get_number_preserving_sparse_operator( - hamiltonian_fop, 8, 2, spin_preserving=True, excitation_level=2) + hamiltonian_fop, 8, 2, spin_preserving=True, excitation_level=2 + ) sparse_ham_fci = get_sparse_operator(hamiltonian_fop, n_qubits=8) - eig_val_cisd, _ = scipy.sparse.linalg.eigsh(sparse_ham_cisd, - k=1, - which='SA') - eig_val_fci, _ = scipy.sparse.linalg.eigsh(sparse_ham_fci, - k=1, - which='SA') + eig_val_cisd, _ = scipy.sparse.linalg.eigsh(sparse_ham_cisd, k=1, which='SA') + eig_val_fci, _ = scipy.sparse.linalg.eigsh(sparse_ham_fci, k=1, which='SA') - assert numpy.abs(eig_val_cisd[0] - eig_val_fci[0]) < 1E-9 + assert numpy.abs(eig_val_cisd[0] - eig_val_fci[0]) < 1e-9 def test_weird_determinant_matches_fci_energy_two_electron_hubbard(self): hamiltonian_fop = self.hubbard_hamiltonian @@ -1675,18 +1647,16 @@ def test_weird_determinant_matches_fci_energy_two_electron_hubbard(self): spin_preserving=True, excitation_level=2, reference_determinant=numpy.asarray( - [False, False, True, True, False, False, False, False])) + [False, False, True, True, False, False, False, False] + ), + ) sparse_ham_fci = get_sparse_operator(hamiltonian_fop, n_qubits=8) - eig_val_cisd, _ = scipy.sparse.linalg.eigsh(sparse_ham_cisd, - k=1, - which='SA') - eig_val_fci, _ = scipy.sparse.linalg.eigsh(sparse_ham_fci, - k=1, - which='SA') + eig_val_cisd, _ = scipy.sparse.linalg.eigsh(sparse_ham_cisd, k=1, which='SA') + eig_val_fci, _ = scipy.sparse.linalg.eigsh(sparse_ham_fci, k=1, which='SA') - assert numpy.abs(eig_val_cisd[0] - eig_val_fci[0]) < 1E-9 + assert numpy.abs(eig_val_cisd[0] - eig_val_fci[0]) < 1e-9 def test_number_restricted_spectra_match_molecule(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) @@ -1695,103 +1665,100 @@ def test_number_restricted_spectra_match_molecule(self): hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, - spin_preserving=False) + spin_preserving=False, + ) - sparse_ham = get_sparse_operator(hamiltonian_fop, - self.molecule.n_qubits) + sparse_ham = get_sparse_operator(hamiltonian_fop, self.molecule.n_qubits) sparse_ham_restricted_number_preserving = jw_number_restrict_operator( - sparse_ham, - n_electrons=self.molecule.n_electrons, - n_qubits=self.molecule.n_qubits) + sparse_ham, n_electrons=self.molecule.n_electrons, n_qubits=self.molecule.n_qubits + ) - spectrum_from_new_sparse_method = sparse_eigenspectrum( - sparse_ham_number_preserving) + spectrum_from_new_sparse_method = sparse_eigenspectrum(sparse_ham_number_preserving) spectrum_from_old_sparse_method = sparse_eigenspectrum( - sparse_ham_restricted_number_preserving) + sparse_ham_restricted_number_preserving + ) spectral_deviation = numpy.amax( - numpy.absolute(spectrum_from_new_sparse_method - - spectrum_from_old_sparse_method)) - self.assertAlmostEqual(spectral_deviation, 0.) + numpy.absolute(spectrum_from_new_sparse_method - spectrum_from_old_sparse_method) + ) + self.assertAlmostEqual(spectral_deviation, 0.0) def test_number_restricted_spectra_match_hubbard(self): hamiltonian_fop = self.hubbard_hamiltonian sparse_ham_number_preserving = get_number_preserving_sparse_operator( - hamiltonian_fop, 8, 4, spin_preserving=False) + hamiltonian_fop, 8, 4, spin_preserving=False + ) sparse_ham = get_sparse_operator(hamiltonian_fop, 8) sparse_ham_restricted_number_preserving = jw_number_restrict_operator( - sparse_ham, n_electrons=4, n_qubits=8) + sparse_ham, n_electrons=4, n_qubits=8 + ) - spectrum_from_new_sparse_method = sparse_eigenspectrum( - sparse_ham_number_preserving) + spectrum_from_new_sparse_method = sparse_eigenspectrum(sparse_ham_number_preserving) spectrum_from_old_sparse_method = sparse_eigenspectrum( - sparse_ham_restricted_number_preserving) + sparse_ham_restricted_number_preserving + ) spectral_deviation = numpy.amax( - numpy.absolute(spectrum_from_new_sparse_method - - spectrum_from_old_sparse_method)) - self.assertAlmostEqual(spectral_deviation, 0.) + numpy.absolute(spectrum_from_new_sparse_method - spectrum_from_old_sparse_method) + ) + self.assertAlmostEqual(spectral_deviation, 0.0) def test_number_sz_restricted_spectra_match_molecule(self): hamiltonian_fop = get_fermion_operator(self.molecular_hamiltonian) sparse_ham_number_sz_preserving = get_number_preserving_sparse_operator( - hamiltonian_fop, - self.molecule.n_qubits, - self.molecule.n_electrons, - spin_preserving=True) + hamiltonian_fop, self.molecule.n_qubits, self.molecule.n_electrons, spin_preserving=True + ) - sparse_ham = get_sparse_operator(hamiltonian_fop, - self.molecule.n_qubits) + sparse_ham = get_sparse_operator(hamiltonian_fop, self.molecule.n_qubits) sparse_ham_restricted_number_sz_preserving = jw_sz_restrict_operator( - sparse_ham, - 0, - n_electrons=self.molecule.n_electrons, - n_qubits=self.molecule.n_qubits) + sparse_ham, 0, n_electrons=self.molecule.n_electrons, n_qubits=self.molecule.n_qubits + ) - spectrum_from_new_sparse_method = sparse_eigenspectrum( - sparse_ham_number_sz_preserving) + spectrum_from_new_sparse_method = sparse_eigenspectrum(sparse_ham_number_sz_preserving) spectrum_from_old_sparse_method = sparse_eigenspectrum( - sparse_ham_restricted_number_sz_preserving) + sparse_ham_restricted_number_sz_preserving + ) spectral_deviation = numpy.amax( - numpy.absolute(spectrum_from_new_sparse_method - - spectrum_from_old_sparse_method)) - self.assertAlmostEqual(spectral_deviation, 0.) + numpy.absolute(spectrum_from_new_sparse_method - spectrum_from_old_sparse_method) + ) + self.assertAlmostEqual(spectral_deviation, 0.0) def test_number_sz_restricted_spectra_match_hubbard(self): hamiltonian_fop = self.hubbard_hamiltonian sparse_ham_number_sz_preserving = get_number_preserving_sparse_operator( - hamiltonian_fop, 8, 4, spin_preserving=True) + hamiltonian_fop, 8, 4, spin_preserving=True + ) sparse_ham = get_sparse_operator(hamiltonian_fop, 8) sparse_ham_restricted_number_sz_preserving = jw_sz_restrict_operator( - sparse_ham, 0, n_electrons=4, n_qubits=8) + sparse_ham, 0, n_electrons=4, n_qubits=8 + ) - spectrum_from_new_sparse_method = sparse_eigenspectrum( - sparse_ham_number_sz_preserving) + spectrum_from_new_sparse_method = sparse_eigenspectrum(sparse_ham_number_sz_preserving) spectrum_from_old_sparse_method = sparse_eigenspectrum( - sparse_ham_restricted_number_sz_preserving) + sparse_ham_restricted_number_sz_preserving + ) spectral_deviation = numpy.amax( - numpy.absolute(spectrum_from_new_sparse_method - - spectrum_from_old_sparse_method)) - self.assertAlmostEqual(spectral_deviation, 0.) + numpy.absolute(spectrum_from_new_sparse_method - spectrum_from_old_sparse_method) + ) + self.assertAlmostEqual(spectral_deviation, 0.0) class GetSparseOperatorQubitTest(unittest.TestCase): - def test_sparse_matrix_Y(self): term = QubitOperator(((0, 'Y'),)) sparse_operator = get_sparse_operator(term) @@ -1800,11 +1767,11 @@ def test_sparse_matrix_Y(self): self.assertTrue(is_hermitian(sparse_operator)) def test_sparse_matrix_ZX(self): - coefficient = 2. + coefficient = 2.0 operators = ((0, 'Z'), (1, 'X')) term = QubitOperator(operators, coefficient) sparse_operator = get_sparse_operator(term) - self.assertEqual(list(sparse_operator.data), [2., 2., -2., -2.]) + self.assertEqual(list(sparse_operator.data), [2.0, 2.0, -2.0, -2.0]) self.assertEqual(list(sparse_operator.indices), [1, 0, 3, 2]) self.assertTrue(is_hermitian(sparse_operator)) @@ -1812,21 +1779,20 @@ def test_sparse_matrix_ZIZ(self): operators = ((0, 'Z'), (2, 'Z')) term = QubitOperator(operators) sparse_operator = get_sparse_operator(term) - self.assertEqual(list(sparse_operator.data), - [1, -1, 1, -1, -1, 1, -1, 1]) + self.assertEqual(list(sparse_operator.data), [1, -1, 1, -1, -1, 1, -1, 1]) self.assertEqual(list(sparse_operator.indices), list(range(8))) self.assertTrue(is_hermitian(sparse_operator)) def test_sparse_matrix_combo(self): - qop = (QubitOperator(((0, 'Y'), (1, 'X')), -0.1j) + QubitOperator( - ((0, 'X'), (1, 'Z')), 3. + 2.j)) + qop = QubitOperator(((0, 'Y'), (1, 'X')), -0.1j) + QubitOperator( + ((0, 'X'), (1, 'Z')), 3.0 + 2.0j + ) sparse_operator = get_sparse_operator(qop) self.assertEqual( - list(sparse_operator.data), - [3 + 2j, 0.1, 0.1, -3 - 2j, 3 + 2j, -0.1, -0.1, -3 - 2j]) - self.assertEqual(list(sparse_operator.indices), - [2, 3, 2, 3, 0, 1, 0, 1]) + list(sparse_operator.data), [3 + 2j, 0.1, 0.1, -3 - 2j, 3 + 2j, -0.1, -0.1, -3 - 2j] + ) + self.assertEqual(list(sparse_operator.indices), [2, 3, 2, 3, 0, 1, 0, 1]) def test_sparse_matrix_zero_1qubit(self): sparse_operator = get_sparse_operator(QubitOperator((), 0.0), 1) @@ -1864,7 +1830,6 @@ def test_sparse_matrix_linearity(self): class GetSparseOperatorFermionTest(unittest.TestCase): - def test_sparse_matrix_zero_n_qubit(self): sparse_operator = get_sparse_operator(FermionOperator.zero(), 4) sparse_operator.eliminate_zeros() @@ -1873,9 +1838,8 @@ def test_sparse_matrix_zero_n_qubit(self): class GetSparseOperatorBosonTest(unittest.TestCase): - def setUp(self): - self.hbar = 1. + self.hbar = 1.0 self.d = 4 self.b = numpy.diag(numpy.sqrt(numpy.arange(1, self.d)), 1) self.bd = self.b.conj().T @@ -1897,7 +1861,6 @@ def test_sparse_matrix_error(self): class GetSparseOperatorDiagonalCoulombHamiltonianTest(unittest.TestCase): - def test_diagonal_coulomb_hamiltonian(self): n_qubits = 5 one_body = random_hermitian_matrix(n_qubits, real=False) @@ -1908,7 +1871,7 @@ def test_diagonal_coulomb_hamiltonian(self): op1 = get_sparse_operator(op) op2 = get_sparse_operator(jordan_wigner(get_fermion_operator(op))) diff = op1 - op2 - discrepancy = 0. + discrepancy = 0.0 if diff.nnz: discrepancy = max(abs(diff.data)) - self.assertAlmostEqual(discrepancy, 0.) + self.assertAlmostEqual(discrepancy, 0.0) diff --git a/src/openfermion/linalg/wave_fitting.py b/src/openfermion/linalg/wave_fitting.py index d09720ae3..2f186647c 100644 --- a/src/openfermion/linalg/wave_fitting.py +++ b/src/openfermion/linalg/wave_fitting.py @@ -16,8 +16,9 @@ import scipy -def fit_known_frequencies(signal: numpy.ndarray, times: numpy.ndarray, - frequencies: numpy.ndarray) -> numpy.ndarray: +def fit_known_frequencies( + signal: numpy.ndarray, times: numpy.ndarray, frequencies: numpy.ndarray +) -> numpy.ndarray: """Fits a set of known exponential components to a dataset Decomposes a function g(t) as g(t)=sum_jA_jexp(iw_jt), where the frequencies @@ -31,9 +32,9 @@ def fit_known_frequencies(signal: numpy.ndarray, times: numpy.ndarray, Returns: amplitudes {numpy.ndarray} -- the found amplitudes A_j """ - generation_matrix = numpy.array([ - [numpy.exp(1j * time * freq) for freq in frequencies] for time in times - ]) + generation_matrix = numpy.array( + [[numpy.exp(1j * time * freq) for freq in frequencies] for time in times] + ) amplitudes = scipy.linalg.lstsq(generation_matrix, signal)[0] return amplitudes @@ -59,18 +60,16 @@ def prony(signal: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]: """ num_freqs = len(signal) // 2 - hankel0 = scipy.linalg.hankel(c=signal[:num_freqs], - r=signal[num_freqs - 1:-1]) - hankel1 = scipy.linalg.hankel(c=signal[1:num_freqs + 1], - r=signal[num_freqs:]) + hankel0 = scipy.linalg.hankel(c=signal[:num_freqs], r=signal[num_freqs - 1 : -1]) + hankel1 = scipy.linalg.hankel(c=signal[1 : num_freqs + 1], r=signal[num_freqs:]) shift_matrix = scipy.linalg.lstsq(hankel0.T, hankel1.T)[0] phases = numpy.linalg.eigvals(shift_matrix.T) - generation_matrix = numpy.array( - [[phase**k for phase in phases] for k in range(len(signal))]) + generation_matrix = numpy.array([[phase**k for phase in phases] for k in range(len(signal))]) amplitudes = scipy.linalg.lstsq(generation_matrix, signal)[0] - amplitudes, phases = zip(*sorted( - zip(amplitudes, phases), key=lambda x: numpy.abs(x[0]), reverse=True)) + amplitudes, phases = zip( + *sorted(zip(amplitudes, phases), key=lambda x: numpy.abs(x[0]), reverse=True) + ) return numpy.array(amplitudes), numpy.array(phases) diff --git a/src/openfermion/linalg/wave_fitting_test.py b/src/openfermion/linalg/wave_fitting_test.py index cba01fabb..e9b1cf657 100644 --- a/src/openfermion/linalg/wave_fitting_test.py +++ b/src/openfermion/linalg/wave_fitting_test.py @@ -18,8 +18,8 @@ def test_prony_zeros(): signal = numpy.zeros(10) amplitudes, phases = prony(signal) - assert (len(amplitudes) == 5) - assert (len(phases) == 5) + assert len(amplitudes) == 5 + assert len(phases) == 5 for j in range(5): numpy.testing.assert_allclose(amplitudes[j], 0) numpy.testing.assert_allclose(phases[j], 0) @@ -27,14 +27,17 @@ def test_prony_zeros(): def test_prony_signal(): x_vec = numpy.linspace(0, 1, 11) - y_vec = (0.5 * numpy.exp(1j * x_vec * 3) + 0.3 * numpy.exp(1j * x_vec * 5) + - 0.15 * numpy.exp(1j * x_vec * 1.5) + - 0.1 * numpy.exp(1j * x_vec * 4) + - 0.05 * numpy.exp(1j * x_vec * 1.2)) + y_vec = ( + 0.5 * numpy.exp(1j * x_vec * 3) + + 0.3 * numpy.exp(1j * x_vec * 5) + + 0.15 * numpy.exp(1j * x_vec * 1.5) + + 0.1 * numpy.exp(1j * x_vec * 4) + + 0.05 * numpy.exp(1j * x_vec * 1.2) + ) print(y_vec) amplitudes, phases = prony(y_vec) - assert (len(amplitudes) == 5) - assert (len(phases) == 5) + assert len(amplitudes) == 5 + assert len(phases) == 5 for a, p in zip(amplitudes, phases): print(a, numpy.angle(p)) numpy.testing.assert_allclose(numpy.abs(amplitudes[0]), 0.5, atol=1e-4) @@ -53,13 +56,14 @@ def test_fitting_signal(): frequencies = numpy.array([0.4, 0.5, 0.8]) amplitudes = numpy.array([0.2, 0.4, 0.4]) times = numpy.linspace(0, 10, 21) - signal = numpy.array([ - numpy.sum([ - amp * numpy.exp(1j * time * freq) - for freq, amp in zip(frequencies, amplitudes) - ]) - for time in times - ]) + signal = numpy.array( + [ + numpy.sum( + [amp * numpy.exp(1j * time * freq) for freq, amp in zip(frequencies, amplitudes)] + ) + for time in times + ] + ) amplitudes_guess = fit_known_frequencies(signal, times, frequencies) assert len(amplitudes_guess == 3) for index in range(3): diff --git a/src/openfermion/linalg/wedge_product.py b/src/openfermion/linalg/wedge_product.py index 29c618343..ab4d2f55b 100644 --- a/src/openfermion/linalg/wedge_product.py +++ b/src/openfermion/linalg/wedge_product.py @@ -59,8 +59,7 @@ def generate_parity_permutations(seq): # insert new object starting at end of the list new_index_list.insert(len(perm[0]) - put_index, index_to_inject) - new_permutations.append( - (new_index_list, perm[1] * (-1)**(put_index))) + new_permutations.append((new_index_list, perm[1] * (-1) ** (put_index))) permutations = new_permutations @@ -103,41 +102,42 @@ def wedge(left_tensor, right_tensor, left_index_ranks, right_index_ranks): right_tensor """ if left_tensor.ndim != sum(left_index_ranks): - raise IndexError( - "n_tensor shape is not consistent with the input n_index rank") + raise IndexError("n_tensor shape is not consistent with the input n_index rank") if right_tensor.ndim != sum(right_index_ranks): - raise IndexError( - "n_tensor shape is not consistent with the input n_index rank") + raise IndexError("n_tensor shape is not consistent with the input n_index rank") # assign upper and lower indices for n_tensor total_upper = left_index_ranks[0] + right_index_ranks[0] total_lower = left_index_ranks[1] + right_index_ranks[1] upper_characters = EINSUM_CHARS[:total_upper] - lower_characters = EINSUM_CHARS[total_upper:total_upper + total_lower] - new_tensor = numpy.zeros(left_tensor.shape + right_tensor.shape, - dtype=complex) + lower_characters = EINSUM_CHARS[total_upper : total_upper + total_lower] + new_tensor = numpy.zeros(left_tensor.shape + right_tensor.shape, dtype=complex) ordered_einsum_string = upper_characters + lower_characters for upper_order_parities, lower_order_parities in product( - generate_parity_permutations(upper_characters), - generate_parity_permutations(lower_characters[::-1])): + generate_parity_permutations(upper_characters), + generate_parity_permutations(lower_characters[::-1]), + ): # we reverse the order in the lower_chars so because # = D_{dc}^{ab} in this code. - n_upper_einsum_chars = upper_order_parities[0][:left_index_ranks[0]] - m_upper_einsum_chars = upper_order_parities[0][left_index_ranks[0]:] - n_lower_einsum_chars = lower_order_parities[0] \ - [:left_index_ranks[1]][::-1] - m_lower_einsum_chars = lower_order_parities[0] \ - [left_index_ranks[1]:][::-1] + n_upper_einsum_chars = upper_order_parities[0][: left_index_ranks[0]] + m_upper_einsum_chars = upper_order_parities[0][left_index_ranks[0] :] + n_lower_einsum_chars = lower_order_parities[0][: left_index_ranks[1]][::-1] + m_lower_einsum_chars = lower_order_parities[0][left_index_ranks[1] :][::-1] n_string = "".join(n_upper_einsum_chars + n_lower_einsum_chars) m_string = "".join(m_upper_einsum_chars + m_lower_einsum_chars) # we are doing lots of extra += operations but with the benefit of not # having to write a python loop over the entire new_tensor object. - new_tensor += upper_order_parities[1] * lower_order_parities[1] * \ - numpy.einsum('{},{}->{}'.format(n_string, m_string, - ordered_einsum_string), - left_tensor, right_tensor) + new_tensor += ( + upper_order_parities[1] + * lower_order_parities[1] + * numpy.einsum( + '{},{}->{}'.format(n_string, m_string, ordered_einsum_string), + left_tensor, + right_tensor, + ) + ) new_tensor /= factorial(total_upper) * factorial(total_lower) diff --git a/src/openfermion/linalg/wedge_product_test.py b/src/openfermion/linalg/wedge_product_test.py index ee961ad42..e55dda778 100644 --- a/src/openfermion/linalg/wedge_product_test.py +++ b/src/openfermion/linalg/wedge_product_test.py @@ -2,8 +2,7 @@ import pytest import numpy -from openfermion.linalg.wedge_product import (wedge, - generate_parity_permutations) +from openfermion.linalg.wedge_product import wedge, generate_parity_permutations def test_upper_lower_index_size_check(): @@ -29,10 +28,12 @@ def test_eye_wedge(): m_tensor = numpy.eye(2) true_eye2 = numpy.zeros((2, 2, 2, 2)) for p, q, r, s in product(range(2), repeat=4): - true_eye2[p, q, r, s] += (n_tensor[p, s] * m_tensor[q, r] - - n_tensor[q, s] * m_tensor[p, r] - - n_tensor[p, r] * m_tensor[q, s] + - n_tensor[q, r] * m_tensor[p, s]) + true_eye2[p, q, r, s] += ( + n_tensor[p, s] * m_tensor[q, r] + - n_tensor[q, s] * m_tensor[p, r] + - n_tensor[p, r] * m_tensor[q, s] + + n_tensor[q, r] * m_tensor[p, s] + ) true_eye2 /= 2**2 test_eye2 = wedge(n_tensor, m_tensor, (1, 1), (1, 1)) assert numpy.allclose(test_eye2, true_eye2) @@ -49,10 +50,12 @@ def test_random_wedge(): m_tensor = numpy.random.random((dim, dim)) true_eye2 = numpy.zeros(tuple([dim] * 4)) for p, q, r, s in product(range(dim), repeat=4): - true_eye2[p, q, r, s] += (n_tensor[p, s] * m_tensor[q, r] - - n_tensor[q, s] * m_tensor[p, r] - - n_tensor[p, r] * m_tensor[q, s] + - n_tensor[q, r] * m_tensor[p, s]) + true_eye2[p, q, r, s] += ( + n_tensor[p, s] * m_tensor[q, r] + - n_tensor[q, s] * m_tensor[p, r] + - n_tensor[p, r] * m_tensor[q, s] + + n_tensor[q, r] * m_tensor[p, s] + ) true_eye2 /= 2**2 test_eye2 = wedge(n_tensor, m_tensor, (1, 1), (1, 1)) assert numpy.allclose(test_eye2, true_eye2) @@ -72,9 +75,12 @@ def test_random_two_wedge(): for a, b, c, d, e, f in product(range(dim), repeat=6): for u_perm, u_phase in generate_parity_permutations([a, b, c]): for l_perm, l_phase in generate_parity_permutations([f, e, d]): - true_tensor[a, b, c, d, e, f] += u_phase * l_phase * n_tensor[ - u_perm[0], u_perm[1], l_perm[1], - l_perm[0]] * m_tensor[u_perm[2], l_perm[2]] + true_tensor[a, b, c, d, e, f] += ( + u_phase + * l_phase + * n_tensor[u_perm[0], u_perm[1], l_perm[1], l_perm[0]] + * m_tensor[u_perm[2], l_perm[2]] + ) true_tensor /= 6**2 test_tensor = wedge(n_tensor, m_tensor, (2, 2), (1, 1)) @@ -97,15 +103,14 @@ def test_random_two_wedge_two(): for l_perm, l_phase in generate_parity_permutations([h, g, f, e]): # D_{hgfe}^{abcd} = C [D_{hg}^{ab}D_{fe}^{cd} + # -1 * D_{hg}^{ab}D_{ef}^{cd} + ...] - true_tensor[a, b, c, d, e, f, g, h] += u_phase * l_phase * \ - n_tensor[ - u_perm[0], u_perm[1], - l_perm[1], l_perm[ - 0]] * m_tensor[ - u_perm[2], u_perm[3], - l_perm[3], l_perm[2]] - - true_tensor /= (4 * 3 * 2)**2 + true_tensor[a, b, c, d, e, f, g, h] += ( + u_phase + * l_phase + * n_tensor[u_perm[0], u_perm[1], l_perm[1], l_perm[0]] + * m_tensor[u_perm[2], u_perm[3], l_perm[3], l_perm[2]] + ) + + true_tensor /= (4 * 3 * 2) ** 2 test_tensor = wedge(n_tensor, m_tensor, (2, 2), (2, 2)) assert numpy.allclose(test_tensor, true_tensor) @@ -120,11 +125,14 @@ def test_random_1_2_wedge_1_1(): for l_perm, l_phase in generate_parity_permutations([e, d, c]): # D_{edc}^{ab} = C[D_{ed}^{a}D_{c}^{b} - D_{ed}^{b}D_{c}^{a} - # D_{ec}^{a}D_{d}^{b} +...] - true_tensor[a, b, c, d, e] += u_phase * l_phase * n_tensor[ - u_perm[0], l_perm[2], l_perm[1]] * m_tensor[u_perm[1], - l_perm[0]] - - true_tensor /= (2 * 3 * 2) + true_tensor[a, b, c, d, e] += ( + u_phase + * l_phase + * n_tensor[u_perm[0], l_perm[2], l_perm[1]] + * m_tensor[u_perm[1], l_perm[0]] + ) + + true_tensor /= 2 * 3 * 2 test_tensor = wedge(n_tensor, m_tensor, (1, 2), (1, 1)) assert numpy.allclose(test_tensor, true_tensor) @@ -137,10 +145,13 @@ def test_random_2_1_wedge_1_1(): for a, b, c, d, e in product(range(dim), repeat=5): for u_perm, u_phase in generate_parity_permutations([a, b, c]): for l_perm, l_phase in generate_parity_permutations([e, d]): - true_tensor[a, b, c, d, e] += u_phase * l_phase * n_tensor[ - u_perm[0], u_perm[1], l_perm[0]] * m_tensor[u_perm[2], - l_perm[1]] - - true_tensor /= (2 * 3 * 2) + true_tensor[a, b, c, d, e] += ( + u_phase + * l_phase + * n_tensor[u_perm[0], u_perm[1], l_perm[0]] + * m_tensor[u_perm[2], l_perm[1]] + ) + + true_tensor /= 2 * 3 * 2 test_tensor = wedge(n_tensor, m_tensor, (2, 1), (1, 1)) assert numpy.allclose(test_tensor, true_tensor) diff --git a/src/openfermion/measurements/__init__.py b/src/openfermion/measurements/__init__.py index fdedb9e71..73fd8c2b7 100644 --- a/src/openfermion/measurements/__init__.py +++ b/src/openfermion/measurements/__init__.py @@ -36,12 +36,6 @@ from .get_interaction_rdm import get_interaction_rdm -from .rdm_equality_constraints import ( - one_body_fermion_constraints, - two_body_fermion_constraints, -) +from .rdm_equality_constraints import one_body_fermion_constraints, two_body_fermion_constraints -from .vpe_estimators import ( - PhaseFitEstimator, - get_phase_function, -) +from .vpe_estimators import PhaseFitEstimator, get_phase_function diff --git a/src/openfermion/measurements/equality_constraint_projection.py b/src/openfermion/measurements/equality_constraint_projection.py index 4bb55392f..2ba9d6e6c 100644 --- a/src/openfermion/measurements/equality_constraint_projection.py +++ b/src/openfermion/measurements/equality_constraint_projection.py @@ -50,8 +50,7 @@ def linearize_term(term, n_orbitals): q = term[1][0] r = term[2][0] s = term[3][0] - return (1 + n_orbitals**2 + p + q * n_orbitals + r * n_orbitals**2 + - s * n_orbitals**3) + return 1 + n_orbitals**2 + p + q * n_orbitals + r * n_orbitals**2 + s * n_orbitals**3 def unlinearize_term(index, n_orbitals): @@ -66,8 +65,8 @@ def unlinearize_term(index, n_orbitals): """ # Handle identity term. if not index: - return (()) - elif (0 < index < 1 + n_orbitals**2): + return () + elif 0 < index < 1 + n_orbitals**2: # Handle one-body terms. shift = 1 new_index = index - shift @@ -82,9 +81,8 @@ def unlinearize_term(index, n_orbitals): s = new_index // n_orbitals**3 r = (new_index - s * n_orbitals**3) // n_orbitals**2 q = (new_index - s * n_orbitals**3 - r * n_orbitals**2) // n_orbitals - p = (new_index - q * n_orbitals - r * n_orbitals**2 - s * n_orbitals**3) - assert index == (shift + p + q * n_orbitals + r * n_orbitals**2 + - s * n_orbitals**3) + p = new_index - q * n_orbitals - r * n_orbitals**2 - s * n_orbitals**3 + assert index == (shift + p + q * n_orbitals + r * n_orbitals**2 + s * n_orbitals**3) return ((p, 1), (q, 1), (r, 0), (s, 0)) @@ -172,13 +170,13 @@ def apply_constraints(operator, n_fermions): # Get vectorized operator. vectorized_operator = operator_to_vector(operator) - initial_bound = numpy.sum(numpy.absolute(vectorized_operator[1::]))**2 + initial_bound = numpy.sum(numpy.absolute(vectorized_operator[1::])) ** 2 print('Initial bound on measurements is %f.' % initial_bound) # Get linear programming coefficient vector. n_variables = n_constraints + n_terms lp_vector = numpy.zeros(n_variables, float) - lp_vector[-n_terms:] = 1. + lp_vector[-n_terms:] = 1.0 # Get linear programming constraint matrix. lp_constraint_matrix = scipy.sparse.dok_matrix((2 * n_terms, n_variables)) @@ -187,8 +185,8 @@ def apply_constraints(operator, n_fermions): lp_constraint_matrix[j, i] = value lp_constraint_matrix[n_terms + j, i] = -value for i in range(n_terms): - lp_constraint_matrix[i, n_constraints + i] = -1. - lp_constraint_matrix[n_terms + i, n_constraints + i] = -1. + lp_constraint_matrix[i, n_constraints + i] = -1.0 + lp_constraint_matrix[n_terms + i, n_constraints + i] = -1.0 # Get linear programming constraint vector. lp_constraint_vector = numpy.zeros(2 * n_terms, float) @@ -199,22 +197,24 @@ def apply_constraints(operator, n_fermions): print('Starting linear programming.') options = {'maxiter': int(1e6)} bound = n_constraints * [(None, None)] + n_terms * [(0, None)] - solution = scipy.optimize.linprog(c=lp_vector, - A_ub=lp_constraint_matrix.toarray(), - b_ub=lp_constraint_vector, - bounds=bound, - options=options) + solution = scipy.optimize.linprog( + c=lp_vector, + A_ub=lp_constraint_matrix.toarray(), + b_ub=lp_constraint_vector, + bounds=bound, + options=options, + ) # Analyze results. print(solution['message']) assert solution['success'] solution_vector = solution['x'] - solution['fun']**2 + solution['fun'] ** 2 print('Program terminated after %i iterations.' % solution['nit']) # Alternative bound. residuals = solution_vector[-n_terms:] - alternative_bound = numpy.sum(numpy.absolute(residuals[1::]))**2 + alternative_bound = numpy.sum(numpy.absolute(residuals[1::])) ** 2 print('Bound implied by solution vector is %f.' % alternative_bound) # Make sure residuals are positive. @@ -223,12 +223,10 @@ def apply_constraints(operator, n_fermions): # Get bound on updated Hamiltonian. weights = solution_vector[:n_constraints] - final_vectorized_operator = (vectorized_operator - - constraints.transpose() * weights) - final_bound = numpy.sum(numpy.absolute(final_vectorized_operator[1::]))**2 + final_vectorized_operator = vectorized_operator - constraints.transpose() * weights + final_bound = numpy.sum(numpy.absolute(final_vectorized_operator[1::])) ** 2 print('Actual bound determined is %f.' % final_bound) # Return modified operator. - modified_operator = vector_to_operator(final_vectorized_operator, - n_orbitals) - return (modified_operator + hermitian_conjugated(modified_operator)) / 2. + modified_operator = vector_to_operator(final_vectorized_operator, n_orbitals) + return (modified_operator + hermitian_conjugated(modified_operator)) / 2.0 diff --git a/src/openfermion/measurements/equality_constraint_projection_test.py b/src/openfermion/measurements/equality_constraint_projection_test.py index 8bd9f0630..d59f6a9f0 100644 --- a/src/openfermion/measurements/equality_constraint_projection_test.py +++ b/src/openfermion/measurements/equality_constraint_projection_test.py @@ -17,30 +17,32 @@ from openfermion.chem import MolecularData from openfermion.config import DATA_DIRECTORY from openfermion.transforms.opconversions import get_fermion_operator -from openfermion.linalg import (get_sparse_operator, get_ground_state, - jw_number_restrict_operator, - sparse_eigenspectrum, expectation) - -from .equality_constraint_projection import (apply_constraints, - constraint_matrix, linearize_term, - operator_to_vector, - unlinearize_term, - vector_to_operator) +from openfermion.linalg import ( + get_sparse_operator, + get_ground_state, + jw_number_restrict_operator, + sparse_eigenspectrum, + expectation, +) + +from .equality_constraint_projection import ( + apply_constraints, + constraint_matrix, + linearize_term, + operator_to_vector, + unlinearize_term, + vector_to_operator, +) class EqualityConstraintProjectionTest(unittest.TestCase): - def setUp(self): - # Set up molecule. - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + molecule = MolecularData(geometry, basis, multiplicity, filename=filename) molecule.load() self.n_fermions = molecule.n_electrons self.n_orbitals = molecule.n_qubits @@ -70,7 +72,6 @@ def test_operator_to_vector_consistency(self): self.assertEqual(len(difference.terms), 0) def test_constraint_matrix(self): - # Randomly project operator with constraints. numpy.random.seed(8) constraints = constraint_matrix(self.n_orbitals, self.n_fermions) @@ -80,8 +81,7 @@ def test_constraint_matrix(self): vectorized_operator = operator_to_vector(self.fermion_hamiltonian) modification_vector = constraints.transpose() * random_weights new_operator_vector = vectorized_operator + modification_vector - modified_operator = vector_to_operator(new_operator_vector, - self.n_orbitals) + modified_operator = vector_to_operator(new_operator_vector, self.n_orbitals) # Map both to sparse matrix under Jordan-Wigner. sparse_original = get_sparse_operator(self.fermion_hamiltonian) @@ -93,20 +93,18 @@ def test_constraint_matrix(self): self.assertAlmostEqual(modified_energy, energy) def test_apply_constraints(self): - # Get norm of original operator. - original_norm = 0. + original_norm = 0.0 for term, coefficient in self.fermion_hamiltonian.terms.items(): if term != (): original_norm += abs(coefficient) # Get modified operator. - modified_operator = apply_constraints(self.fermion_hamiltonian, - self.n_fermions) + modified_operator = apply_constraints(self.fermion_hamiltonian, self.n_fermions) modified_operator.compress() # Get norm of modified operator. - modified_norm = 0. + modified_norm = 0.0 for term, coefficient in modified_operator.terms.items(): if term != (): modified_norm += abs(coefficient) @@ -117,15 +115,12 @@ def test_apply_constraints(self): sparse_modified = get_sparse_operator(modified_operator) # Check spectra. - sparse_original = jw_number_restrict_operator(sparse_original, - self.n_fermions) - sparse_modified = jw_number_restrict_operator(sparse_modified, - self.n_fermions) + sparse_original = jw_number_restrict_operator(sparse_original, self.n_fermions) + sparse_modified = jw_number_restrict_operator(sparse_modified, self.n_fermions) original_spectrum = sparse_eigenspectrum(sparse_original) modified_spectrum = sparse_eigenspectrum(sparse_modified) - spectral_deviation = numpy.amax( - numpy.absolute(original_spectrum - modified_spectrum)) - self.assertAlmostEqual(spectral_deviation, 0.) + spectral_deviation = numpy.amax(numpy.absolute(original_spectrum - modified_spectrum)) + self.assertAlmostEqual(spectral_deviation, 0.0) # Check expectation value. energy, wavefunction = get_ground_state(sparse_original) diff --git a/src/openfermion/measurements/fermion_partitioning.py b/src/openfermion/measurements/fermion_partitioning.py index f12bc1d17..62e514bff 100644 --- a/src/openfermion/measurements/fermion_partitioning.py +++ b/src/openfermion/measurements/fermion_partitioning.py @@ -60,18 +60,14 @@ def pair_within(labels: list) -> list: if len(labels) % 4 == 1: frag1.append(None) - for (pairing1, pairing2) in zip(pair_within(frag1), pair_within(frag2)): - + for pairing1, pairing2 in zip(pair_within(frag1), pair_within(frag2)): if len(labels) % 4 == 1: if pairing1[-1] is None: yield pairing1[:-1] + pairing2 else: extra_pair = ((pairing1[-1], pairing2[-1]),) - zero_index, = [ - pair[0] for pair in pairing1[:-1] if pair[1] is None - ] - pairing1 = tuple( - pair for pair in pairing1[:-1] if pair[1] is not None) + (zero_index,) = [pair[0] for pair in pairing1[:-1] if pair[1] is None] + pairing1 = tuple(pair for pair in pairing1[:-1] if pair[1] is not None) yield pairing1 + pairing2[:-1] + extra_pair + (zero_index,) elif len(labels) % 4 == 2: @@ -119,22 +115,25 @@ def pair_between(frag1: list, frag2: list, start_offset: int = 0) -> tuple: num_pairs = min(len(frag1), len(frag2)) for index_offset in range(start_offset, num_iter): - if len(frag1) > len(frag2): pairing = tuple( (frag1[(index + index_offset) % len(frag1)], frag2[index]) - for index in range(num_pairs)) - pairing += tuple(frag1[index % len(frag1)] for index in range( - len(frag2) + index_offset, - len(frag1) + index_offset)) + for index in range(num_pairs) + ) + pairing += tuple( + frag1[index % len(frag1)] + for index in range(len(frag2) + index_offset, len(frag1) + index_offset) + ) else: pairing = tuple( (frag1[index], frag2[(index + index_offset) % len(frag2)]) - for index in range(num_pairs)) + for index in range(num_pairs) + ) if len(frag2) > len(frag1): - pairing += tuple(frag2[index % len(frag2)] for index in range( - len(frag1) + index_offset, - len(frag2) + index_offset)) + pairing += tuple( + frag2[index % len(frag2)] + for index in range(len(frag1) + index_offset, len(frag2) + index_offset) + ) yield pairing @@ -149,8 +148,7 @@ def _loop_iterator(func, *params): looped = True num_loops += 1 if num_loops > MAX_LOOPS: - raise ValueError( - 'Number of loops exceeded maximum allowed.') # pragma: no cover + raise ValueError('Number of loops exceeded maximum allowed.') # pragma: no cover generator = func(*params) @@ -164,23 +162,23 @@ def _gen_partitions(labels, min_size=4): if len(labels) == 1: yield (labels,) return - partitions = (labels[:len(labels) // 2], labels[len(labels) // 2:]) + partitions = (labels[: len(labels) // 2], labels[len(labels) // 2 :]) while True: yield partitions if len(partitions[-1]) < min_size: return new_partitions = [] for part in partitions: - new_partitions.append(part[:len(part) // 2]) - new_partitions.append(part[len(part) // 2:]) + new_partitions.append(part[: len(part) // 2]) + new_partitions.append(part[len(part) // 2 :]) partitions = new_partitions def _gen_pairings_between_partitions(parta, partb): if len(parta + partb) < 5: yield (tuple(parta), tuple(partb)) - splita = [parta[:len(parta) // 2], parta[len(parta) // 2:]] - splitb = [partb[:len(partb) // 2], partb[len(partb) // 2:]] + splita = [parta[: len(parta) // 2], parta[len(parta) // 2 :]] + splitb = [partb[: len(partb) // 2], partb[len(partb) // 2 :]] for a, b in ((0, 0), (0, 1), (1, 0), (1, 1)): if max(len(splita[a]), len(splitb[b])) < 2: continue @@ -189,8 +187,8 @@ def _gen_pairings_between_partitions(parta, partb): gen_a = _loop_iterator(pair_within, splita[a]) gen_b = _loop_iterator(pair_within, splitb[b]) num_iter = max( - len(splitb[b]) - 1 + len(splitb[b]) % 2, - len(splita[a]) - 1 + len(splita[a]) % 2) + len(splitb[b]) - 1 + len(splitb[b]) % 2, len(splita[a]) - 1 + len(splita[a]) % 2 + ) for _ in range(num_iter): pair_a, _ = next(gen_a) pair_b, _ = next(gen_b) @@ -230,16 +228,12 @@ def pair_within_simultaneously(labels: list) -> tuple: return for partition in _gen_partitions(labels): - generator_list = [ - _loop_iterator(pair_within, partition[j]) - for j in range(len(partition)) - ] + generator_list = [_loop_iterator(pair_within, partition[j]) for j in range(len(partition))] for dummy1 in range(len(partition[-2]) - 1 + len(partition[-2]) % 2): pairing = tuple() for generator in generator_list[::2]: pairing = pairing + next(generator)[0] - for dummy2 in range( - len(partition[-1]) - 1 + len(partition[-1]) % 2): + for dummy2 in range(len(partition[-1]) - 1 + len(partition[-1]) % 2): pairing2 = tuple(pairing) for generator in generator_list[1::2]: pairing2 = pairing2 + next(generator)[0] @@ -302,9 +296,7 @@ def _asynchronous_iter(iterators, flatten=False): # Edge cases if list_size == 1: - next_res = [ - iterator[0] if iterator else None for iterator in iterator_lists - ] + next_res = [iterator[0] if iterator else None for iterator in iterator_lists] if flatten: next_res = [x for result in next_res if result for x in result] yield tuple(next_res) @@ -320,10 +312,7 @@ def _asynchronous_iter(iterators, flatten=False): for j in range(new_size): for l in range(new_size): - next_res = [ - iterator_lists[k][(j * k + l) % new_size] - for k in range(num_lists - 1) - ] + next_res = [iterator_lists[k][(j * k + l) % new_size] for k in range(num_lists - 1)] next_res.append(iterator_lists[-1][j]) if flatten: next_res = [x for result in next_res if result for x in result] @@ -344,8 +333,8 @@ def _asynchronous_iter_small_lists(iterator_lists, flatten=False): """ for partitions in partition_iterator(iterator_lists, 2): for res in _asynchronous_iter( - [_parallel_iter(partition, flatten) for partition in partitions], - flatten): + [_parallel_iter(partition, flatten) for partition in partitions], flatten + ): yield res @@ -431,14 +420,13 @@ def pair_within_simultaneously_binned(binned_majoranas: list) -> tuple: for bin_index in range(num_bins): if bin_index < bin_index ^ bin_gap: iterators.append( - pair_between(binned_majoranas[bin_index], - binned_majoranas[bin_index ^ bin_gap])) + pair_between(binned_majoranas[bin_index], binned_majoranas[bin_index ^ bin_gap]) + ) for pairing in _asynchronous_iter(iterators, flatten=True): yield pairing -def pair_within_simultaneously_symmetric(num_fermions: int, - num_symmetries: int) -> tuple: +def pair_within_simultaneously_symmetric(num_fermions: int, num_symmetries: int) -> tuple: """Generates symmetry-respecting pairings between four-elements in a list A pairing of a list is a set of pairs of list elements. E.g. a pairing of @@ -469,12 +457,10 @@ def pair_within_simultaneously_symmetric(num_fermions: int, number of Majoranas generated will be twice this size) num_symmetries (int): the number of symmetries to be respectd. """ - binned_majoranas = [[ - index - for index in range(2 * num_fermions) - if index % 2**num_symmetries == bin_index + binned_majoranas = [ + [index for index in range(2 * num_fermions) if index % 2**num_symmetries == bin_index] + for bin_index in range(2**num_symmetries) ] - for bin_index in range(2**num_symmetries)] for pairing in pair_within_simultaneously_binned(binned_majoranas): yield pairing diff --git a/src/openfermion/measurements/fermion_partitioning_test.py b/src/openfermion/measurements/fermion_partitioning_test.py index 895c48592..352cd86c6 100644 --- a/src/openfermion/measurements/fermion_partitioning_test.py +++ b/src/openfermion/measurements/fermion_partitioning_test.py @@ -12,15 +12,19 @@ """Tests for _qubit_partitioning.py""" import unittest import numpy -from .fermion_partitioning import (pair_within, pair_within_simultaneously, - pair_within_simultaneously_binned, - pair_within_simultaneously_symmetric, - _gen_partitions, _get_padding, - _parallel_iter, _asynchronous_iter) +from .fermion_partitioning import ( + pair_within, + pair_within_simultaneously, + pair_within_simultaneously_binned, + pair_within_simultaneously_symmetric, + _gen_partitions, + _get_padding, + _parallel_iter, + _asynchronous_iter, +) class TestPairWithin(unittest.TestCase): - def test_zero(self): count = 0 for _ in pair_within([]): @@ -61,9 +65,7 @@ def test_many(self): self.assertEqual(len(pairings_list), num_indices) for pairing in pairings_list: print(pairing) - all_pairs = [(i, j) - for i in range(num_indices) - for j in range(i + 1, num_indices)] + all_pairs = [(i, j) for i in range(num_indices) for j in range(i + 1, num_indices)] checksum = [0 for _ in all_pairs] for pairing in pairings_list: for j, pair in enumerate(all_pairs): @@ -75,16 +77,18 @@ def test_many(self): class TestPairWithinSimultaneously(unittest.TestCase): - def test_small(self): for num_indices in [4, 5, 7, 10, 15]: print() print('Trying with num_indices = {}'.format(num_indices)) labels = [j for j in range(num_indices)] - all_quads = [((i, j, k, l)) for i in range(num_indices) - for j in range(i + 1, num_indices) - for k in range(j + 1, num_indices) - for l in range(k + 1, num_indices)] + all_quads = [ + ((i, j, k, l)) + for i in range(num_indices) + for j in range(i + 1, num_indices) + for k in range(j + 1, num_indices) + for l in range(k + 1, num_indices) + ] checksum = [0 for pp2 in all_quads] for pairing in pair_within_simultaneously(labels): print(pairing) @@ -103,7 +107,6 @@ def test_small(self): class TestPadding(unittest.TestCase): - def test_primes(self): bin_size = 11 for num_bins in range(10): @@ -116,12 +119,9 @@ def test_composite(self): class TestIterators(unittest.TestCase): - def test_asynchronous_three(self): lists = [[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]] - test_matrices = [ - [numpy.zeros((3, 3)) for j in range(4)] for k in range(4) - ] + test_matrices = [[numpy.zeros((3, 3)) for j in range(4)] for k in range(4)] iterator = _asynchronous_iter(lists) count = 0 for next_tuple in iterator: @@ -147,7 +147,6 @@ def test_asynchronous_four(self): self.assertEqual(count, 25) def test_parallel(self): - def iter1(): for j in range(4): yield j @@ -163,7 +162,6 @@ def iter2(): self.assertEqual(count, 5) def test_parallel_flatten(self): - def iter1(): for j in range(4): yield [j] @@ -181,7 +179,6 @@ def iter2(): class TestPairingWithSymmetries(unittest.TestCase): - def test_two_fermions(self): bins = [[1], [2], [3], [4]] count = 0 @@ -243,9 +240,7 @@ def test_four_symmetries(self): def test_gen_partitions_1input(): - labels = [ - 0, - ] + labels = [0] count = 0 for _ in _gen_partitions(labels): count += 1 diff --git a/src/openfermion/measurements/get_interaction_rdm.py b/src/openfermion/measurements/get_interaction_rdm.py index a4baa3be5..7d15a8d8e 100644 --- a/src/openfermion/measurements/get_interaction_rdm.py +++ b/src/openfermion/measurements/get_interaction_rdm.py @@ -18,6 +18,7 @@ def get_interaction_rdm(qubit_operator, n_qubits=None): # Avoid circular import. from openfermion.transforms import jordan_wigner + if n_qubits is None: n_qubits = count_qubits(qubit_operator) one_rdm = numpy.zeros((n_qubits,) * 2, dtype=complex) @@ -32,8 +33,7 @@ def get_interaction_rdm(qubit_operator, n_qubits=None): # Two-RDM. for i, j, k, l in itertools.product(range(n_qubits), repeat=4): - transformed_operator = jordan_wigner( - FermionOperator(((i, 1), (j, 1), (k, 0), (l, 0)))) + transformed_operator = jordan_wigner(FermionOperator(((i, 1), (j, 1), (k, 0), (l, 0)))) for term, coefficient in transformed_operator.terms.items(): if term in qubit_operator.terms: two_rdm[i, j, k, l] += coefficient * qubit_operator.terms[term] diff --git a/src/openfermion/measurements/get_interaction_rdm_test.py b/src/openfermion/measurements/get_interaction_rdm_test.py index dd3b777ed..5c3c4872a 100644 --- a/src/openfermion/measurements/get_interaction_rdm_test.py +++ b/src/openfermion/measurements/get_interaction_rdm_test.py @@ -1,3 +1,3 @@ """ Oddly I can't find a test for this function. -""" \ No newline at end of file +""" diff --git a/src/openfermion/measurements/qubit_partitioning.py b/src/openfermion/measurements/qubit_partitioning.py index 96c3ce15b..e308bf06a 100644 --- a/src/openfermion/measurements/qubit_partitioning.py +++ b/src/openfermion/measurements/qubit_partitioning.py @@ -99,10 +99,8 @@ def partition_iterator(qubit_list, partition_size, num_iterations=None): num_iterations = int(numpy.ceil(numpy.log2(num_qubits))) # First iterate over the outer binary partition - outer_iterator = binary_partition_iterator(qubit_list, - num_iterations=num_iterations) + outer_iterator = binary_partition_iterator(qubit_list, num_iterations=num_iterations) for set1, set2 in outer_iterator: - # Each new partition needs to be subdivided fewer times # to prevent an additional k! factor in the scaling. num_iterations -= 1 @@ -110,18 +108,18 @@ def partition_iterator(qubit_list, partition_size, num_iterations=None): # Iterate over all possibilities of partitioning the first # set into l parts and the second set into k - l parts. for inner_partition_size in range(1, partition_size): - if inner_partition_size > len(set1) or\ - partition_size - inner_partition_size > len(set2): + if inner_partition_size > len(set1) or partition_size - inner_partition_size > len( + set2 + ): continue # subdivide the first partition - inner_iterator1 = partition_iterator(set1, inner_partition_size, - num_iterations) + inner_iterator1 = partition_iterator(set1, inner_partition_size, num_iterations) for inner_partition1 in inner_iterator1: - # subdivide the second partition inner_iterator2 = partition_iterator( - set2, partition_size - inner_partition_size, num_iterations) + set2, partition_size - inner_partition_size, num_iterations + ) for inner_partition2 in inner_iterator2: yield inner_partition1 + inner_partition2 @@ -160,9 +158,7 @@ def pauli_string_iterator(num_qubits, max_word_size=2): def _find_compatible_basis(term, bases): for basis in bases: basis_qubits = {op[0] for op in basis} - conflicts = ((i, P) - for (i, P) in term - if i in basis_qubits and (i, P) not in basis) + conflicts = ((i, P) for (i, P) in term if i in basis_qubits and (i, P) not in basis) if any(conflicts): continue return basis @@ -212,9 +208,10 @@ def group_into_tensor_product_basis_sets(operator, seed=None): TypeError: Operator of invalid type. """ if not isinstance(operator, QubitOperator): - raise TypeError('Can only split QubitOperator into tensor product' - ' basis sets. {} is not supported.'.format( - type(operator).__name__)) + raise TypeError( + 'Can only split QubitOperator into tensor product' + ' basis sets. {} is not supported.'.format(type(operator).__name__) + ) sub_operators = {} r = numpy.random.RandomState(seed) @@ -228,8 +225,7 @@ def group_into_tensor_product_basis_sets(operator, seed=None): sub_operator = sub_operators.pop(basis) sub_operator += QubitOperator(term, coefficient) additions = tuple(op for op in term if op not in basis) - basis = tuple( - sorted(basis + additions, key=lambda factor: factor[0])) + basis = tuple(sorted(basis + additions, key=lambda factor: factor[0])) sub_operators[basis] = sub_operator return sub_operators diff --git a/src/openfermion/measurements/qubit_partitioning_test.py b/src/openfermion/measurements/qubit_partitioning_test.py index dcc9ab464..31fa6d82c 100644 --- a/src/openfermion/measurements/qubit_partitioning_test.py +++ b/src/openfermion/measurements/qubit_partitioning_test.py @@ -12,16 +12,17 @@ """Tests for qubit_partitioning.py""" import unittest -from openfermion.ops.operators import (QubitOperator, FermionOperator, - BosonOperator) +from openfermion.ops.operators import QubitOperator, FermionOperator, BosonOperator -from .qubit_partitioning import (binary_partition_iterator, partition_iterator, - pauli_string_iterator, - group_into_tensor_product_basis_sets) +from .qubit_partitioning import ( + binary_partition_iterator, + partition_iterator, + pauli_string_iterator, + group_into_tensor_product_basis_sets, +) class BinaryPartitionIteratorTest(unittest.TestCase): - def test_num_partitions(self): qubit_list = range(6) bpi = binary_partition_iterator(qubit_list) @@ -94,11 +95,10 @@ def test_zero_counting(self): class PartitionIteratorTest(unittest.TestCase): - def test_unary_case(self): qubit_list = list(range(6)) bpi = partition_iterator(qubit_list, 1) - for p1, in bpi: + for (p1,) in bpi: self.assertEqual(p1, qubit_list) def test_binary_case(self): @@ -146,15 +146,10 @@ def test_partition_three(self): pi = partition_iterator(qubit_list, 3) count = 0 for p1, p2, p3 in pi: - self.assertEqual( - len(p1) + len(p2) + len(p3), len(qubit_list)) + self.assertEqual(len(p1) + len(p2) + len(p3), len(qubit_list)) self.assertEqual(set(p1 + p2 + p3), set(qubit_list)) print('Partition obtained: ', p1, p2, p3) - if max( - sum(1 - for x in p - if x in [i, j, k]) - for p in [p1, p2, p3]) == 1: + if max(sum(1 for x in p if x in [i, j, k]) for p in [p1, p2, p3]) == 1: count += 1 print('count = {}'.format(count)) self.assertTrue(count > 0) @@ -162,7 +157,6 @@ def test_partition_three(self): class PauliStringIteratorTest(unittest.TestCase): - def test_eightpartition_three(self): for i1 in range(8): for i2 in range(i1 + 1, 8): @@ -173,9 +167,11 @@ def test_eightpartition_three(self): psg = pauli_string_iterator(8, 3) count = 0 for pauli_string in psg: - if (pauli_string[i1] == l1 and - pauli_string[i2] == l2 and - pauli_string[i3] == l3): + if ( + pauli_string[i1] == l1 + and pauli_string[i2] == l2 + and pauli_string[i3] == l3 + ): count += 1 self.assertTrue(count > 0) @@ -196,45 +192,50 @@ def test_small_run_cases(self): class GroupTensorProductBasisTest(unittest.TestCase): - def test_demo_qubit_operator(self): for seed in [None, 0, 10000]: - op = QubitOperator('X0 Y1', 2.) + QubitOperator('X1 Y2', 3.j) + op = QubitOperator('X0 Y1', 2.0) + QubitOperator('X1 Y2', 3.0j) sub_operators = group_into_tensor_product_basis_sets(op, seed=seed) expected = { - ((0, 'X'), (1, 'Y')): QubitOperator('X0 Y1', 2.), - ((1, 'X'), (2, 'Y')): QubitOperator('X1 Y2', 3.j) + ((0, 'X'), (1, 'Y')): QubitOperator('X0 Y1', 2.0), + ((1, 'X'), (2, 'Y')): QubitOperator('X1 Y2', 3.0j), } self.assertEqual(sub_operators, expected) - op = QubitOperator('X0 Y1', 2.) + QubitOperator('Y1 Y2', 3.j) + op = QubitOperator('X0 Y1', 2.0) + QubitOperator('Y1 Y2', 3.0j) sub_operators = group_into_tensor_product_basis_sets(op, seed=seed) expected = {((0, 'X'), (1, 'Y'), (2, 'Y')): op} self.assertEqual(sub_operators, expected) - op = QubitOperator('', 4.) + QubitOperator('X1', 2.j) + op = QubitOperator('', 4.0) + QubitOperator('X1', 2.0j) sub_operators = group_into_tensor_product_basis_sets(op, seed=seed) expected = {((1, 'X'),): op} self.assertEqual(sub_operators, expected) - op = (QubitOperator('X0 X1', 0.1) + QubitOperator('X1 X2', 2.j) + - QubitOperator('Y2 Z3', 3.) + QubitOperator('X3 Z4', 5.)) + op = ( + QubitOperator('X0 X1', 0.1) + + QubitOperator('X1 X2', 2.0j) + + QubitOperator('Y2 Z3', 3.0) + + QubitOperator('X3 Z4', 5.0) + ) sub_operators = group_into_tensor_product_basis_sets(op, seed=seed) expected1 = { - ((0, 'X'), (1, 'X'), (2, 'X'), (3, 'X'), (4, 'Z')): - (QubitOperator('X0 X1', 0.1) + QubitOperator('X1 X2', 2.j) + - QubitOperator('X3 Z4', 5.)), - ((2, 'Y'), (3, 'Z')): - QubitOperator('Y2 Z3', 3.) + ((0, 'X'), (1, 'X'), (2, 'X'), (3, 'X'), (4, 'Z')): ( + QubitOperator('X0 X1', 0.1) + + QubitOperator('X1 X2', 2.0j) + + QubitOperator('X3 Z4', 5.0) + ), + ((2, 'Y'), (3, 'Z')): QubitOperator('Y2 Z3', 3.0), } expected2 = { - ((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Z')): - (QubitOperator('X0 X1', 0.1) + QubitOperator('Y2 Z3', 3.)), - ((1, 'X'), (2, 'X'), (3, 'X'), (4, 'Z')): - (QubitOperator('X1 X2', 2.j) + QubitOperator('X3 Z4', 5.)) + ((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Z')): ( + QubitOperator('X0 X1', 0.1) + QubitOperator('Y2 Z3', 3.0) + ), + ((1, 'X'), (2, 'X'), (3, 'X'), (4, 'Z')): ( + QubitOperator('X1 X2', 2.0j) + QubitOperator('X3 Z4', 5.0) + ), } - self.assertTrue(sub_operators == expected1 or - sub_operators == expected2) + self.assertTrue(sub_operators == expected1 or sub_operators == expected2) def test_empty_qubit_operator(self): sub_operators = group_into_tensor_product_basis_sets(QubitOperator()) diff --git a/src/openfermion/measurements/rdm_equality_constraints.py b/src/openfermion/measurements/rdm_equality_constraints.py index 103541be6..ec3facdfa 100644 --- a/src/openfermion/measurements/rdm_equality_constraints.py +++ b/src/openfermion/measurements/rdm_equality_constraints.py @@ -64,11 +64,9 @@ def two_body_fermion_constraints(n_orbitals, n_fermions): constraint_operator = FermionOperator() for i in range(n_orbitals): for j in range(n_orbitals): - constraint_operator += FermionOperator( - ((i, 1), (j, 1), (j, 0), (i, 0))) + constraint_operator += FermionOperator(((i, 1), (j, 1), (j, 0), (i, 0))) if len(constraint_operator.terms): - constraint_operator -= FermionOperator((), - n_fermions * (n_fermions - 1)) + constraint_operator -= FermionOperator((), n_fermions * (n_fermions - 1)) yield constraint_operator # Two-body Hermiticity condition. @@ -76,10 +74,8 @@ def two_body_fermion_constraints(n_orbitals, n_fermions): i, j = (ij // n_orbitals), (ij % n_orbitals) for kl in range(ij + 1, n_orbitals**2): k, l = (kl // n_orbitals), (kl % n_orbitals) - constraint_operator = FermionOperator( - ((i, 1), (j, 1), (l, 0), (k, 0))) - constraint_operator -= FermionOperator( - ((k, 1), (l, 1), (j, 0), (i, 0))) + constraint_operator = FermionOperator(((i, 1), (j, 1), (l, 0), (k, 0))) + constraint_operator -= FermionOperator(((k, 1), (l, 1), (j, 0), (i, 0))) if len(constraint_operator.terms): yield constraint_operator @@ -88,10 +84,8 @@ def two_body_fermion_constraints(n_orbitals, n_fermions): for j in range(n_orbitals): constraint_operator = FermionOperator() for p in range(n_orbitals): - constraint_operator += FermionOperator( - ((i, 1), (p, 1), (p, 0), (j, 0))) - constraint_operator += FermionOperator(((i, 1), (j, 0)), - -(n_fermions - 1)) + constraint_operator += FermionOperator(((i, 1), (p, 1), (p, 0), (j, 0))) + constraint_operator += FermionOperator(((i, 1), (j, 0)), -(n_fermions - 1)) if len(constraint_operator.terms): yield constraint_operator @@ -102,13 +96,9 @@ def two_body_fermion_constraints(n_orbitals, n_fermions): k, l = (kl // n_orbitals), (kl % n_orbitals) # G-matrix condition. - constraint_operator = FermionOperator(((i, 1), (k, 0)), - 1.0 * (j == l)) - constraint_operator += FermionOperator( - ((i, 1), (l, 1), (k, 0), (j, 0))) - constraint_operator += FermionOperator( - ((i, 1), (l, 1), (j, 0), (k, 0))) - constraint_operator -= FermionOperator(((i, 1), (k, 0)), - 1.0 * (j == l)) + constraint_operator = FermionOperator(((i, 1), (k, 0)), 1.0 * (j == l)) + constraint_operator += FermionOperator(((i, 1), (l, 1), (k, 0), (j, 0))) + constraint_operator += FermionOperator(((i, 1), (l, 1), (j, 0), (k, 0))) + constraint_operator -= FermionOperator(((i, 1), (k, 0)), 1.0 * (j == l)) if len(constraint_operator.terms): yield constraint_operator diff --git a/src/openfermion/measurements/rdm_equality_constraints_test.py b/src/openfermion/measurements/rdm_equality_constraints_test.py index 7d9f70a5a..046a39283 100644 --- a/src/openfermion/measurements/rdm_equality_constraints_test.py +++ b/src/openfermion/measurements/rdm_equality_constraints_test.py @@ -16,23 +16,17 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData from openfermion.transforms.repconversions import get_interaction_operator -from openfermion.measurements import (one_body_fermion_constraints, - two_body_fermion_constraints) +from openfermion.measurements import one_body_fermion_constraints, two_body_fermion_constraints class FermionConstraintsTest(unittest.TestCase): - def setUp(self): - # Setup. - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + molecule = MolecularData(geometry, basis, multiplicity, filename=filename) molecule.load() self.n_fermions = molecule.n_electrons self.n_orbitals = molecule.n_qubits @@ -42,12 +36,10 @@ def setUp(self): self.fci_rdm = molecule.get_molecular_rdm(use_fci=1) def test_one_body_constraints(self): - for constraint in one_body_fermion_constraints(self.n_orbitals, - self.n_fermions): - interaction_operator = get_interaction_operator( - constraint, self.n_orbitals) + for constraint in one_body_fermion_constraints(self.n_orbitals, self.n_fermions): + interaction_operator = get_interaction_operator(constraint, self.n_orbitals) constraint_value = self.fci_rdm.expectation(interaction_operator) - self.assertAlmostEqual(constraint_value, 0.) + self.assertAlmostEqual(constraint_value, 0.0) for term, _ in constraint.terms.items(): if len(term) == 2: self.assertTrue(term[0][1]) @@ -56,12 +48,10 @@ def test_one_body_constraints(self): self.assertEqual(term, ()) def test_two_body_constraints(self): - for constraint in two_body_fermion_constraints(self.n_orbitals, - self.n_fermions): - interaction_operator = get_interaction_operator( - constraint, self.n_orbitals) + for constraint in two_body_fermion_constraints(self.n_orbitals, self.n_fermions): + interaction_operator = get_interaction_operator(constraint, self.n_orbitals) constraint_value = self.fci_rdm.expectation(interaction_operator) - self.assertAlmostEqual(constraint_value, 0.) + self.assertAlmostEqual(constraint_value, 0.0) for term, _ in constraint.terms.items(): if len(term) == 2: self.assertTrue(term[0][1]) diff --git a/src/openfermion/measurements/vpe_estimators.py b/src/openfermion/measurements/vpe_estimators.py index 7e0232191..5a38ce0fe 100644 --- a/src/openfermion/measurements/vpe_estimators.py +++ b/src/openfermion/measurements/vpe_estimators.py @@ -37,8 +37,7 @@ def get_simulation_points(self) -> numpy.ndarray: """ @abc.abstractmethod - def get_expectation_value(self, - phase_function: numpy.ndarray) -> numpy.ndarray: + def get_expectation_value(self, phase_function: numpy.ndarray) -> numpy.ndarray: """Estimates expectation values from an input phase function Given a phase function g(t), estimates the expectation value of the @@ -121,14 +120,11 @@ def get_amplitudes(self, phase_function: numpy.ndarray) -> numpy.ndarray: of the given frequencies (in the same order as in self.energies) """ times = self.get_simulation_points() - phase_function_shifted = numpy.array(phase_function) *\ - numpy.exp(1j * times * self.ref_eval) - amplitudes = fit_known_frequencies(phase_function_shifted, times, - self.evals) + phase_function_shifted = numpy.array(phase_function) * numpy.exp(1j * times * self.ref_eval) + amplitudes = fit_known_frequencies(phase_function_shifted, times, self.evals) return amplitudes - def get_expectation_value(self, - phase_function: numpy.ndarray) -> numpy.ndarray: + def get_expectation_value(self, phase_function: numpy.ndarray) -> numpy.ndarray: """Estates expectation values via amplitude fitting of known frequencies Arguments: @@ -139,16 +135,18 @@ def get_expectation_value(self, expectation_value [float] -- the estimated expectation value """ amplitudes = self.get_amplitudes(phase_function) - expectation_value = numpy.dot(numpy.abs(amplitudes), - self.evals) / numpy.sum( - numpy.abs(amplitudes)) + expectation_value = numpy.dot(numpy.abs(amplitudes), self.evals) / numpy.sum( + numpy.abs(amplitudes) + ) return expectation_value -def get_phase_function(results: Sequence[cirq.Result], - qubits: Sequence[cirq.Qid], - target_qid: int, - rotation_set: Optional[Sequence] = None): +def get_phase_function( + results: Sequence[cirq.Result], + qubits: Sequence[cirq.Qid], + target_qid: int, + rotation_set: Optional[Sequence] = None, +): """Generates an estimate of the phase function g(t) from circuit output The output from a VPE circuit is a set of measurements; from the frequency @@ -182,14 +180,15 @@ def get_phase_function(results: Sequence[cirq.Result], Returns: phase_function [complex] -- An estimate of g(t). """ - hs_index = 2**(len(qubits) - target_qid - 1) + hs_index = 2 ** (len(qubits) - target_qid - 1) if rotation_set is None: rotation_set = standard_vpe_rotation_set phase_function = 0 if len(results) != len(rotation_set): - raise ValueError("I have an incorrect number of TrialResults " - "in results. Correct length should be: {}".format( - len(rotation_set))) + raise ValueError( + "I have an incorrect number of TrialResults " + "in results. Correct length should be: {}".format(len(rotation_set)) + ) for result, rdata in zip(results, rotation_set): total_shots = result.data['msmt'].count() msmt_counts = result.data['msmt'].value_counts() diff --git a/src/openfermion/measurements/vpe_estimators_test.py b/src/openfermion/measurements/vpe_estimators_test.py index a174e5a70..9d9937cdf 100644 --- a/src/openfermion/measurements/vpe_estimators_test.py +++ b/src/openfermion/measurements/vpe_estimators_test.py @@ -16,10 +16,7 @@ import pandas import cirq -from .vpe_estimators import ( - PhaseFitEstimator, - get_phase_function, -) +from .vpe_estimators import PhaseFitEstimator, get_phase_function rng = numpy.random.RandomState(seed=42) @@ -39,13 +36,12 @@ def test_estimates_expectation_value_pauli_nonoise(): estimator = PhaseFitEstimator(evals) sim_points = estimator.get_simulation_points() - phase_function = numpy.array([ - numpy.sum([ - amp * numpy.exp(1j * ev * time) - for ev, amp in zip(evals, true_amps) - ]) - for time in sim_points - ]) + phase_function = numpy.array( + [ + numpy.sum([amp * numpy.exp(1j * ev * time) for ev, amp in zip(evals, true_amps)]) + for time in sim_points + ] + ) print(phase_function) test_expectation_value = estimator.get_expectation_value(phase_function) assert numpy.isclose(true_expectation_value, test_expectation_value) @@ -59,13 +55,12 @@ def test_estimates_expectation_value_scattered_nonoise(): estimator = PhaseFitEstimator(evals) sim_points = estimator.get_simulation_points() - phase_function = numpy.array([ - numpy.sum([ - amp * numpy.exp(1j * ev * time) - for ev, amp in zip(evals, true_amps) - ]) - for time in sim_points - ]) + phase_function = numpy.array( + [ + numpy.sum([amp * numpy.exp(1j * ev * time) for ev, amp in zip(evals, true_amps)]) + for time in sim_points + ] + ) test_expectation_value = estimator.get_expectation_value(phase_function) assert numpy.isclose(true_expectation_value, test_expectation_value) @@ -80,9 +75,7 @@ def test_phase_function_gen_raises_error(): def test_phase_function_gen(): - class FakeResult: - def __init__(self, data): self.data = {'msmt': pandas.Series(data)} diff --git a/src/openfermion/ops/operators/__init__.py b/src/openfermion/ops/operators/__init__.py index ef60debf9..d003eef5d 100644 --- a/src/openfermion/ops/operators/__init__.py +++ b/src/openfermion/ops/operators/__init__.py @@ -10,11 +10,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .binary_polynomial import ( - BinaryPolynomial, - BinaryPolynomialError, - binary_sum_rule, -) +from .binary_polynomial import BinaryPolynomial, BinaryPolynomialError, binary_sum_rule from .boson_operator import BosonOperator @@ -31,9 +27,4 @@ from .symbolic_operator import SymbolicOperator # out of alphabetical order to avoid circular import -from .binary_code import ( - double_decoding, - shift_decoder, - BinaryCode, - BinaryCodeError, -) +from .binary_code import double_decoding, shift_decoder, BinaryCode, BinaryCodeError diff --git a/src/openfermion/ops/operators/binary_code.py b/src/openfermion/ops/operators/binary_code.py index 1c7f9115f..e392ccf47 100644 --- a/src/openfermion/ops/operators/binary_code.py +++ b/src/openfermion/ops/operators/binary_code.py @@ -18,11 +18,12 @@ import scipy.sparse from openfermion.ops.operators import BinaryPolynomial + # import openfermion.ops.operators._binary_polynomial as bp def shift_decoder(decoder, shift_constant): - """ Shifts the indices of a decoder by a constant. + """Shifts the indices of a decoder by a constant. Args: decoder (iterable): list of BinaryPolynomial; the decoder @@ -32,9 +33,10 @@ def shift_decoder(decoder, shift_constant): """ decode_shifted = [] if not isinstance(shift_constant, (numpy.int64, numpy.int32, int)): - raise TypeError('the shift to the decoder must be integer. got {}' - 'of type {}'.format(shift_constant, - type(shift_constant))) + raise TypeError( + 'the shift to the decoder must be integer. got {}' + 'of type {}'.format(shift_constant, type(shift_constant)) + ) for entry in decoder: tmp_entry = copy.deepcopy(entry) tmp_entry.shift(shift_constant) @@ -43,7 +45,7 @@ def shift_decoder(decoder, shift_constant): def double_decoding(decoder_1, decoder_2): - """ Concatenates two decodings + """Concatenates two decodings Args: decoder_1 (iterable): list of BinaryPolynomial @@ -127,7 +129,7 @@ class BinaryCode(object): """ def __init__(self, encoding, decoding): - """ Initialization of a binary code. + """Initialization of a binary code. Args: encoding (np.ndarray or list): nested lists or binary 2D-array @@ -150,35 +152,35 @@ def __init__(self, encoding, decoding): if self.n_modes != len(decoding): raise BinaryCodeError( - 'size mismatch, decoder and encoder should have the same' - ' first dimension') + 'size mismatch, decoder and encoder should have the same' ' first dimension' + ) decoder_qubits = set() self.decoder = [] for symbolic_binary in decoding: - if isinstance(symbolic_binary, - (tuple, list, str, int, numpy.int32, numpy.int64)): + if isinstance(symbolic_binary, (tuple, list, str, int, numpy.int32, numpy.int64)): symbolic_binary = BinaryPolynomial(symbolic_binary) if isinstance(symbolic_binary, BinaryPolynomial): self.decoder.append(symbolic_binary) - decoder_qubits = decoder_qubits | set( - symbolic_binary.enumerate_qubits()) + decoder_qubits = decoder_qubits | set(symbolic_binary.enumerate_qubits()) else: raise TypeError( - 'decoder component provided ' - 'is not a suitable for BinaryPolynomial', symbolic_binary) + 'decoder component provided ' 'is not a suitable for BinaryPolynomial', + symbolic_binary, + ) if len(decoder_qubits) != self.n_qubits: - raise BinaryCodeError( - 'decoder and encoder provided has different number of qubits') + raise BinaryCodeError('decoder and encoder provided has different number of qubits') if max(decoder_qubits) + 1 > self.n_qubits: - raise BinaryCodeError('decoder is not indexing some qubits. Qubits' - 'indexed are: {}'.format(decoder_qubits)) + raise BinaryCodeError( + 'decoder is not indexing some qubits. Qubits' + 'indexed are: {}'.format(decoder_qubits) + ) def __iadd__(self, appendix): - """ In-place appending a binary code with +=. + """In-place appending a binary code with +=. Args: appendix (BinaryCode): The code to append to the present one. @@ -193,10 +195,9 @@ def __iadd__(self, appendix): raise TypeError('argument must be a BinaryCode.') self.decoder = numpy.append( - self.decoder, shift_decoder(appendix.decoder, - self.n_qubits)).tolist() - self.encoder = scipy.sparse.bmat([[self.encoder, None], - [None, appendix.encoder]]) + self.decoder, shift_decoder(appendix.decoder, self.n_qubits) + ).tolist() + self.encoder = scipy.sparse.bmat([[self.encoder, None], [None, appendix.encoder]]) self.n_qubits, self.n_modes = numpy.shape(self.encoder) return self @@ -233,8 +234,7 @@ def __imul__(self, factor): if isinstance(factor, BinaryCode): if self.n_qubits != factor.n_modes: - raise BinaryCodeError( - 'size mismatch between inner and outer code layer') + raise BinaryCodeError('size mismatch between inner and outer code layer') self.decoder = double_decoding(self.decoder, factor.decoder) self.encoder = factor.encoder.dot(self.encoder) @@ -243,23 +243,22 @@ def __imul__(self, factor): elif isinstance(factor, (numpy.int32, numpy.int64, int)): if factor < 1: - raise ValueError('integer factor has to be positive, ' - 'non-zero ') + raise ValueError('integer factor has to be positive, ' 'non-zero ') self.encoder = scipy.sparse.kron( - scipy.sparse.identity(factor, format='csc', dtype=int), - self.encoder, 'csc') + scipy.sparse.identity(factor, format='csc', dtype=int), self.encoder, 'csc' + ) tmp_decoder = self.decoder for index in numpy.arange(1, factor): self.decoder = numpy.append( - self.decoder, - shift_decoder(tmp_decoder, index * self.n_qubits)) + self.decoder, shift_decoder(tmp_decoder, index * self.n_qubits) + ) self.n_qubits *= factor self.n_modes *= factor return self def __mul__(self, factor): - """ Concatenation of two codes or appendage the same code factor times + """Concatenation of two codes or appendage the same code factor times in case of integer factor. Args: @@ -273,7 +272,7 @@ def __mul__(self, factor): return twin def __rmul__(self, factor): - """ Appending the same code factor times. + """Appending the same code factor times. Args: factor (int): integer defining number of appendages. @@ -286,12 +285,14 @@ def __rmul__(self, factor): if isinstance(factor, (numpy.int32, numpy.int64, int)): return self * factor else: - raise TypeError('the left multiplier must be an integer to a' - 'BinaryCode. Was given {} of ' - 'type {}'.format(factor, type(factor))) + raise TypeError( + 'the left multiplier must be an integer to a' + 'BinaryCode. Was given {} of ' + 'type {}'.format(factor, type(factor)) + ) def __str__(self): - """ Return an easy-to-read string representation.""" + """Return an easy-to-read string representation.""" string_return = [list(map(list, self.encoder.toarray()))] dec_str = '[' diff --git a/src/openfermion/ops/operators/binary_code_test.py b/src/openfermion/ops/operators/binary_code_test.py index 451030df1..626c4069b 100644 --- a/src/openfermion/ops/operators/binary_code_test.py +++ b/src/openfermion/ops/operators/binary_code_test.py @@ -13,73 +13,61 @@ import unittest from openfermion.ops.operators import BinaryPolynomial -from openfermion.ops.operators.binary_code import (BinaryCode, BinaryCodeError, - shift_decoder) +from openfermion.ops.operators.binary_code import BinaryCode, BinaryCodeError, shift_decoder class CodeOperatorTest(unittest.TestCase): - def test_init_errors(self): with self.assertRaises(TypeError): - BinaryCode( - 1, [BinaryPolynomial(' w1 + w0 '), - BinaryPolynomial('w0 + 1')]) + BinaryCode(1, [BinaryPolynomial(' w1 + w0 '), BinaryPolynomial('w0 + 1')]) with self.assertRaises(TypeError): BinaryCode([[0, 1], [1, 0]], '1+w1') with self.assertRaises(BinaryCodeError): BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w1 + w0 ')]) with self.assertRaises(TypeError): - BinaryCode([[0, 1, 1], [1, 0, 0]], - ['1 + w1', BinaryPolynomial('1 + w0'), 2.0]) + BinaryCode([[0, 1, 1], [1, 0, 0]], ['1 + w1', BinaryPolynomial('1 + w0'), 2.0]) with self.assertRaises(BinaryCodeError): - BinaryCode([[0, 1], [1, 0]], - [BinaryPolynomial(' w0 '), - BinaryPolynomial('w0 + 1')]) + BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w0 '), BinaryPolynomial('w0 + 1')]) with self.assertRaises(BinaryCodeError): - BinaryCode([[0, 1], [1, 0]], - [BinaryPolynomial(' w5 '), - BinaryPolynomial('w0 + 1')]) + BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w5 '), BinaryPolynomial('w0 + 1')]) def test_addition(self): - a = BinaryCode([[0, 1, 0], [1, 0, 1]], [ - BinaryPolynomial(' w1 + w0 '), - BinaryPolynomial('w0 + 1'), - BinaryPolynomial('w1') - ]) - d = BinaryCode([[0, 1], [1, 0]], - [BinaryPolynomial(' w0 '), - BinaryPolynomial('w0 w1')]) + a = BinaryCode( + [[0, 1, 0], [1, 0, 1]], + [BinaryPolynomial(' w1 + w0 '), BinaryPolynomial('w0 + 1'), BinaryPolynomial('w1')], + ) + d = BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w0 '), BinaryPolynomial('w0 w1')]) summation = a + d self.assertEqual( - str(summation), "[[[0, 1, 0, 0, 0]," + str(summation), + "[[[0, 1, 0, 0, 0]," " [1, 0, 1, 0, 0]," " [0, 0, 0, 0, 1]," " [0, 0, 0, 1, 0]]," " '[[W1] + [W0],[W0] + [1]," - "[W1],[W2],[W2 W3]]']") + "[W1],[W2],[W2 W3]]']", + ) with self.assertRaises(TypeError): summation += 5 def test_multiplication(self): - a = BinaryCode([[0, 1, 0], [1, 0, 1]], [ - BinaryPolynomial(' w1 + w0 '), - BinaryPolynomial('w0 + 1'), - BinaryPolynomial('w1') - ]) - d = BinaryCode([[0, 1], [1, 0]], - [BinaryPolynomial(' w0 '), - BinaryPolynomial('w0 w1')]) + a = BinaryCode( + [[0, 1, 0], [1, 0, 1]], + [BinaryPolynomial(' w1 + w0 '), BinaryPolynomial('w0 + 1'), BinaryPolynomial('w1')], + ) + d = BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w0 '), BinaryPolynomial('w0 w1')]) b = a * d self.assertEqual( - b.__repr__(), "[[[1, 0, 1], [0, 1, 0]]," - " '[[W0] + [W0 W1],[1] +" - " [W0],[W0 W1]]']") + b.__repr__(), "[[[1, 0, 1], [0, 1, 0]]," " '[[W0] + [W0 W1],[1] +" " [W0],[W0 W1]]']" + ) b = 2 * d self.assertEqual( - str(b), "[[[0, 1, 0, 0], [1, 0, 0, 0], " + str(b), + "[[[0, 1, 0, 0], [1, 0, 0, 0], " "[0, 0, 0, 1], [0, 0, 1, 0]], " - "'[[W0],[W0 W1],[W2],[W2 W3]]']") + "'[[W0],[W0 W1],[W2],[W2 W3]]']", + ) with self.assertRaises(BinaryCodeError): _ = d * a with self.assertRaises(TypeError): diff --git a/src/openfermion/ops/operators/binary_polynomial.py b/src/openfermion/ops/operators/binary_polynomial.py index 4af6443aa..3c42f2e7f 100644 --- a/src/openfermion/ops/operators/binary_polynomial.py +++ b/src/openfermion/ops/operators/binary_polynomial.py @@ -71,7 +71,7 @@ class BinaryPolynomial(object): """ def __init__(self, term=None): - """ Initialize the BinaryPolynomial based on term + """Initialize the BinaryPolynomial based on term Args: term (str, list, tuple): used for initializing a BinaryPolynomial @@ -110,7 +110,7 @@ def __init__(self, term=None): self._check_terms() def _check_terms(self): - """ Ensures all terms obey binary rules, updates terms in place.""" + """Ensures all terms obey binary rules, updates terms in place.""" sorted_input = [] for item in self.terms: if len(item): @@ -157,16 +157,17 @@ def _check_factor(term, factor): elif isinstance(factor, (numpy.int32, numpy.int64, int)): if factor < 0: - raise ValueError('Invalid factor {},' - 'must be a positive integer'.format(factor)) + raise ValueError('Invalid factor {},' 'must be a positive integer'.format(factor)) return tuple(set(term)) else: - raise ValueError('Invalid factor {}.' - 'valid factor is positive integers and {} ' - 'for constant 1'.format(factor, _SYMBOLIC_ONE)) + raise ValueError( + 'Invalid factor {}.' + 'valid factor is positive integers and {} ' + 'for constant 1'.format(factor, _SYMBOLIC_ONE) + ) def _parse_sequence(self, term): - """ Parse a term given as a sequence type (i.e., list, tuple, etc.). + """Parse a term given as a sequence type (i.e., list, tuple, etc.). e.g. [(0,1,2,3,_SYMBOLIC_ONE),...] -> [(0,1,2,3),...]. Updates terms in place @@ -186,7 +187,7 @@ def _parse_sequence(self, term): @staticmethod def _parse_string(term): - """ Parse a string term like 'w1 w2 w0' + """Parse a string term like 'w1 w2 w0' Args: term (str): string representation of BinaryPolynomial term. @@ -199,7 +200,7 @@ def _parse_string(term): term_list = [] add_one = False for factor in term.split(): - """ if 1 is already present; remove it since its not necessary to + """if 1 is already present; remove it since its not necessary to keep it if there are more than 1 terms""" if add_one: term_list.remove(_SYMBOLIC_ONE) # if 1 @@ -226,19 +227,16 @@ def _parse_string(term): return parsed_term def enumerate_qubits(self): - """ Enumerates all qubits indexed in a given BinaryPolynomial. + """Enumerates all qubits indexed in a given BinaryPolynomial. Returns (list): a list of qubits """ - qubits = [ - factor for summand in self.terms for factor in summand - if factor != _SYMBOLIC_ONE - ] + qubits = [factor for summand in self.terms for factor in summand if factor != _SYMBOLIC_ONE] return list(set(qubits)) def shift(self, const): - """ Shift all qubit indices by a given constant. + """Shift all qubit indices by a given constant. Args: const (int): the constant to shift the indices by @@ -247,8 +245,9 @@ def shift(self, const): TypeError: const must be integer """ if not isinstance(const, (numpy.int64, numpy.int32, int)): - raise TypeError('can only shift qubit indices by an integer' - 'received {}'.format(const)) + raise TypeError( + 'can only shift qubit indices by an integer' 'received {}'.format(const) + ) shifted_terms = [] for summand in self.terms: shifted_summand = [] @@ -283,7 +282,8 @@ def evaluate(self, binary_list): if max(all_qubits) >= len(binary_list): raise BinaryPolynomialError( 'the length of the binary list provided does not match' - ' the number of variables in the BinaryPolynomial') + ' the number of variables in the BinaryPolynomial' + ) evaluation = 0 for summand in self.terms: @@ -300,7 +300,7 @@ def evaluate(self, binary_list): return 0 def _add_one(self): - """ Adds constant 1 to a BinaryPolynomial. """ + """Adds constant 1 to a BinaryPolynomial.""" # (_SYMBOLIC_ONE,) can only exist as a loner in BinaryPolynomial if (_SYMBOLIC_ONE,) in self.terms: @@ -329,7 +329,7 @@ def identity(cls): return cls(term=[(_SYMBOLIC_ONE,)]) def __str__(self): - """ Return an easy-to-read string representation.""" + """Return an easy-to-read string representation.""" if not self.terms: return '0' string_rep = '' @@ -347,7 +347,7 @@ def __repr__(self): return str(self) def __imul__(self, multiplier): - """ In-place multiply (*=) with a scalar or operator of the same type. + """In-place multiply (*=) with a scalar or operator of the same type. Args: multiplier(int or BinaryPolynomial): multiplier @@ -371,12 +371,10 @@ def __imul__(self, multiplier): elif isinstance(multiplier, self.__class__): result_terms = [] for left_term in self.terms: - left_indices = set( - [term for term in left_term if term != _SYMBOLIC_ONE]) + left_indices = set([term for term in left_term if term != _SYMBOLIC_ONE]) for right_term in multiplier.terms: - right_indices = set( - [term for term in right_term if term != _SYMBOLIC_ONE]) + right_indices = set([term for term in right_term if term != _SYMBOLIC_ONE]) if len(left_indices) == 0 and len(right_indices) == 0: product_term = (_SYMBOLIC_ONE,) @@ -393,11 +391,14 @@ def __imul__(self, multiplier): # Invalid multiplier type else: - raise TypeError('Cannot multiply {} with {}'.format( - self.__class__.__name__, multiplier.__class__.__name__)) + raise TypeError( + 'Cannot multiply {} with {}'.format( + self.__class__.__name__, multiplier.__class__.__name__ + ) + ) def __rmul__(self, multiplier): - """ Return multiplier * self for a scalar or BinaryPolynomial. + """Return multiplier * self for a scalar or BinaryPolynomial. Args: multiplier (int or BinaryPolynomial): the multiplier of the @@ -409,10 +410,8 @@ def __rmul__(self, multiplier): Raises: TypeError: Object of invalid type cannot multiply BinaryPolynomial. """ - if not isinstance(multiplier, - (numpy.int64, numpy.int32, int, type(self))): - raise TypeError('Object of invalid type cannot multiply with ' + - str(type(self)) + '.') + if not isinstance(multiplier, (numpy.int64, numpy.int32, int, type(self))): + raise TypeError('Object of invalid type cannot multiply with ' + str(type(self)) + '.') return self * multiplier def __mul__(self, multiplier): @@ -433,8 +432,7 @@ def __mul__(self, multiplier): product *= multiplier return product else: - raise TypeError('Object of invalid type cannot multiply with ' + - str(type(self)) + '.') + raise TypeError('Object of invalid type cannot multiply with ' + str(type(self)) + '.') def __iadd__(self, addend): """In-place method for += addition of a int or a BinaryPolynomial. @@ -456,8 +454,7 @@ def __iadd__(self, addend): if mod_add: self._add_one() if not isinstance(addend, (numpy.int64, numpy.int32, int, type(self))): - raise TypeError('Object of invalid type cannot add with ' + - str(type(self)) + '.') + raise TypeError('Object of invalid type cannot add with ' + str(type(self)) + '.') return self def __radd__(self, addend): @@ -473,8 +470,7 @@ def __radd__(self, addend): TypeError: Cannot add invalid type. """ if not isinstance(addend, (numpy.int64, numpy.int32, int, type(self))): - raise TypeError('Object of invalid type cannot add with ' + - str(type(self)) + '.') + raise TypeError('Object of invalid type cannot add with ' + str(type(self)) + '.') return self + addend def __add__(self, addend): @@ -505,13 +501,12 @@ def __pow__(self, exponent): """ # Handle invalid exponents. if not isinstance(exponent, (numpy.int64, numpy.int32, int)): - raise TypeError('exponent must be int, but was {} {}'.format( - type(exponent), repr(exponent))) + raise TypeError( + 'exponent must be int, but was {} {}'.format(type(exponent), repr(exponent)) + ) else: if exponent < 0: - raise TypeError( - 'exponent must be non-negative, but was {}'.format( - exponent)) + raise TypeError('exponent must be non-negative, but was {}'.format(exponent)) # Check if exponent is zero - if yes return self, if not return zero. if exponent == 0: @@ -521,7 +516,7 @@ def __pow__(self, exponent): def binary_sum_rule(terms, summand): - """ Updates terms in place based on binary rules. + """Updates terms in place based on binary rules. Args: terms: symbolicBinary terms summand: new potential addition to term diff --git a/src/openfermion/ops/operators/binary_polynomial_test.py b/src/openfermion/ops/operators/binary_polynomial_test.py index d1abe4701..cef602bfa 100644 --- a/src/openfermion/ops/operators/binary_polynomial_test.py +++ b/src/openfermion/ops/operators/binary_polynomial_test.py @@ -15,13 +15,14 @@ import numpy -from openfermion.ops.operators.binary_polynomial import (BinaryPolynomial, - BinaryPolynomialError, - _SYMBOLIC_ONE) +from openfermion.ops.operators.binary_polynomial import ( + BinaryPolynomial, + BinaryPolynomialError, + _SYMBOLIC_ONE, +) class BinaryPolynomialTest(unittest.TestCase): - def test_init_long_string(self): operator1 = BinaryPolynomial('w1 w2 1 + 11') self.assertEqual(operator1.terms, [(1, 2), (_SYMBOLIC_ONE,)]) @@ -108,7 +109,7 @@ def test_power(self): with self.assertRaises(TypeError): _ = operator1**4.3 with self.assertRaises(TypeError): - _ = operator1**(-1) + _ = operator1 ** (-1) def test_init_binary_rule(self): operator1 = BinaryPolynomial('1 + w2 w2 + w2') @@ -182,9 +183,7 @@ def test_init_binary_rule2(self): def test_power_null(self): operator1 = BinaryPolynomial('w1 w1 + 1') is_one = operator1**0 - self.assertEqual(is_one.terms, [ - (_SYMBOLIC_ONE,), - ]) + self.assertEqual(is_one.terms, [(_SYMBOLIC_ONE,)]) def test_addition_evaluations(self): operator1 = BinaryPolynomial('w1 w1 + 1') diff --git a/src/openfermion/ops/operators/boson_operator.py b/src/openfermion/ops/operators/boson_operator.py index eeb45790d..d4c204591 100644 --- a/src/openfermion/ops/operators/boson_operator.py +++ b/src/openfermion/ops/operators/boson_operator.py @@ -92,8 +92,10 @@ def is_normal_ordered(self): for j in range(i, 0, -1): right_operator = term[j] left_operator = term[j - 1] - if (right_operator[0] == left_operator[0] and - right_operator[1] > left_operator[1]): + if ( + right_operator[0] == left_operator[0] + and right_operator[1] > left_operator[1] + ): return False return True @@ -107,7 +109,7 @@ def is_boson_preserving(self): # Make sure term conserves particle number particles = 0 for operator in term: - particles += (-1)**operator[1] # add 1 if create, else -1 + particles += (-1) ** operator[1] # add 1 if create, else -1 if not (particles == 0): return False return True diff --git a/src/openfermion/ops/operators/boson_operator_test.py b/src/openfermion/ops/operators/boson_operator_test.py index 2dc217ba3..e390b36bb 100644 --- a/src/openfermion/ops/operators/boson_operator_test.py +++ b/src/openfermion/ops/operators/boson_operator_test.py @@ -18,7 +18,6 @@ class BosonOperatorTest(unittest.TestCase): - def test_is_normal_ordered_empty(self): op = BosonOperator() * 2 self.assertTrue(op.is_normal_ordered()) diff --git a/src/openfermion/ops/operators/fermion_operator.py b/src/openfermion/ops/operators/fermion_operator.py index e7b7cf9a9..ea1643e54 100644 --- a/src/openfermion/ops/operators/fermion_operator.py +++ b/src/openfermion/ops/operators/fermion_operator.py @@ -93,8 +93,10 @@ def is_normal_ordered(self): left_operator = term[j - 1] if right_operator[1] and not left_operator[1]: return False - elif (right_operator[1] == left_operator[1] and - right_operator[0] >= left_operator[0]): + elif ( + right_operator[1] == left_operator[1] + and right_operator[0] >= left_operator[0] + ): return False return True @@ -118,8 +120,8 @@ def is_two_body_number_conserving(self, check_spin_symmetry=False): spin = 0 particles = 0 for operator in term: - particles += (-1)**operator[1] # add 1 if create, else -1 - spin += (-1)**(operator[0] + operator[1]) + particles += (-1) ** operator[1] # add 1 if create, else -1 + spin += (-1) ** (operator[0] + operator[1]) if particles: return False elif spin and check_spin_symmetry: diff --git a/src/openfermion/ops/operators/fermion_operator_test.py b/src/openfermion/ops/operators/fermion_operator_test.py index ab2415f08..13e57c465 100644 --- a/src/openfermion/ops/operators/fermion_operator_test.py +++ b/src/openfermion/ops/operators/fermion_operator_test.py @@ -17,7 +17,6 @@ class FermionOperatorTest(unittest.TestCase): - def test_is_normal_ordered_empty(self): op = FermionOperator() * 2 self.assertTrue(op.is_normal_ordered()) diff --git a/src/openfermion/ops/operators/majorana_operator.py b/src/openfermion/ops/operators/majorana_operator.py index 3977027f1..fcb1e5696 100644 --- a/src/openfermion/ops/operators/majorana_operator.py +++ b/src/openfermion/ops/operators/majorana_operator.py @@ -59,7 +59,7 @@ def __init__(self, term=None, coefficient=1.0): self.terms = {} if term is not None: term, parity = _sort_majorana_term(term) - self.terms[term] = coefficient * (-1)**parity + self.terms[term] = coefficient * (-1) ** parity @staticmethod def from_dict(terms): @@ -78,13 +78,10 @@ def from_dict(terms): def commutes_with(self, other): """Test commutation with another MajoranaOperator""" if not isinstance(other, type(self)): - raise TypeError( - 'Can only test commutation with another MajoranaOperator.') + raise TypeError('Can only test commutation with another MajoranaOperator.') if len(self.terms) == 1 and len(other.terms) == 1: - return _majorana_terms_commute( - list(self.terms.keys())[0], - list(other.terms.keys())[0]) + return _majorana_terms_commute(list(self.terms.keys())[0], list(other.terms.keys())[0]) return self * other == other * self def with_basis_rotated_by(self, transformation_matrix): @@ -174,18 +171,14 @@ def __mul__(self, other): return NotImplemented if isinstance(other, (int, float, complex)): - terms = { - term: coefficient * other - for term, coefficient in self.terms.items() - } + terms = {term: coefficient * other for term, coefficient in self.terms.items()} return MajoranaOperator.from_dict(terms) terms = {} for left_term, left_coefficient in self.terms.items(): for right_term, right_coefficient in other.terms.items(): new_term, parity = _merge_majorana_terms(left_term, right_term) - coefficient = left_coefficient * right_coefficient * ( - -1)**parity + coefficient = left_coefficient * right_coefficient * (-1) ** parity if new_term in terms: terms[new_term] += coefficient else: @@ -212,10 +205,7 @@ def __truediv__(self, other): if not isinstance(other, (int, float, complex)): return NotImplemented - terms = { - term: coefficient / other - for term, coefficient in self.terms.items() - } + terms = {term: coefficient / other for term, coefficient in self.terms.items()} return MajoranaOperator.from_dict(terms) def __itruediv__(self, other): @@ -371,5 +361,8 @@ def _rotate_basis(term, transformation_matrix): def _is_real_orthogonal(matrix): n, m = matrix.shape - return (n == m and numpy.allclose(numpy.imag(matrix), 0.0) and - numpy.allclose(numpy.dot(matrix.T, matrix), numpy.eye(n))) + return ( + n == m + and numpy.allclose(numpy.imag(matrix), 0.0) + and numpy.allclose(numpy.dot(matrix.T, matrix), numpy.eye(n)) + ) diff --git a/src/openfermion/ops/operators/majorana_operator_test.py b/src/openfermion/ops/operators/majorana_operator_test.py index eb4fce1eb..8ad523cae 100644 --- a/src/openfermion/ops/operators/majorana_operator_test.py +++ b/src/openfermion/ops/operators/majorana_operator_test.py @@ -62,17 +62,11 @@ def test_majorana_operator_with_basis_rotated_by(): b = MajoranaOperator((0,), 2.0) op = b.with_basis_rotated_by(H) - assert op == MajoranaOperator.from_dict({ - (0,): numpy.sqrt(2), - (1,): numpy.sqrt(2) - }) + assert op == MajoranaOperator.from_dict({(0,): numpy.sqrt(2), (1,): numpy.sqrt(2)}) c = MajoranaOperator((1,), 2.0) op = c.with_basis_rotated_by(H) - assert op == MajoranaOperator.from_dict({ - (0,): numpy.sqrt(2), - (1,): -numpy.sqrt(2) - }) + assert op == MajoranaOperator.from_dict({(0,): numpy.sqrt(2), (1,): -numpy.sqrt(2)}) P = numpy.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) @@ -97,7 +91,7 @@ def test_majorana_operator_add_subtract(): assert a.terms == {(0, 2, 3): -1.25, (1, 5, 7): 4.75, (3, 5, 7): 2.25} a += MajoranaOperator((0, 2, 3), 0.5) - assert a.terms == {(0, 2, 3): -.75, (1, 5, 7): 4.75, (3, 5, 7): 2.25} + assert a.terms == {(0, 2, 3): -0.75, (1, 5, 7): 4.75, (3, 5, 7): 2.25} a -= MajoranaOperator((0, 2, 3), 0.25) assert a.terms == {(0, 2, 3): -1.0, (1, 5, 7): 4.75, (3, 5, 7): 2.25} @@ -127,7 +121,7 @@ def test_majorana_operator_multiply(): (1, 3, 4, 7): 0.875, (0, 1, 2, 3, 6, 7): -0.125, (1, 3, 5, 6): 0.375, - (0, 1, 2, 3, 4, 5): -2.625 + (0, 1, 2, 3, 4, 5): -2.625, } assert (2 * a).terms == (a * 2).terms == {(0, 1, 5): 3.0, (1, 2, 7): -1.0} @@ -153,7 +147,7 @@ def test_majorana_operator_pow(): _ = a**-1 with pytest.raises(TypeError): - _ = a**'a' + _ = a ** 'a' def test_majorana_operator_divide(): @@ -177,10 +171,16 @@ def test_majorana_operator_neg(): def test_majorana_operator_eq(): a = MajoranaOperator((0, 1, 5), 1.5) + MajoranaOperator((1, 2, 7), -0.5) - b = (MajoranaOperator((0, 1, 5), 1.5) + MajoranaOperator( - (1, 2, 7), -0.5) + MajoranaOperator((3, 4, 5), 0.0)) - c = (MajoranaOperator((0, 1, 5), 1.5) + MajoranaOperator( - (1, 2, 7), -0.5) + MajoranaOperator((3, 4, 5), 0.1)) + b = ( + MajoranaOperator((0, 1, 5), 1.5) + + MajoranaOperator((1, 2, 7), -0.5) + + MajoranaOperator((3, 4, 5), 0.0) + ) + c = ( + MajoranaOperator((0, 1, 5), 1.5) + + MajoranaOperator((1, 2, 7), -0.5) + + MajoranaOperator((3, 4, 5), 0.1) + ) d = MajoranaOperator((0, 1, 5), 1.75) + MajoranaOperator((1, 2, 7), -0.75) e = MajoranaOperator((0, 1, 5), 1.5) - MajoranaOperator((0, 3, 6), 0.25) @@ -204,8 +204,11 @@ def test_majorana_operator_str(): assert str(still_zero) == '0' assert str(a) == '1.5 (0, 1, 5)' assert str(b) == '-0.5 (1, 2, 7)' - assert str(a + b) == """1.5 (0, 1, 5) + + assert ( + str(a + b) + == """1.5 (0, 1, 5) + -0.5 (1, 2, 7)""" + ) def test_majorana_operator_repr(): diff --git a/src/openfermion/ops/operators/quad_operator.py b/src/openfermion/ops/operators/quad_operator.py index bfb76d4a9..98eeada4b 100644 --- a/src/openfermion/ops/operators/quad_operator.py +++ b/src/openfermion/ops/operators/quad_operator.py @@ -98,9 +98,11 @@ def is_normal_ordered(self): for j in range(i, 0, -1): right_operator = term[j] left_operator = term[j - 1] - if (right_operator[0] == left_operator[0] and - right_operator[1] == 'q' and - left_operator[1] == 'p'): + if ( + right_operator[0] == left_operator[0] + and right_operator[1] == 'q' + and left_operator[1] == 'p' + ): return False return True diff --git a/src/openfermion/ops/operators/quad_operator_test.py b/src/openfermion/ops/operators/quad_operator_test.py index 0c9ea7769..1c08d7201 100644 --- a/src/openfermion/ops/operators/quad_operator_test.py +++ b/src/openfermion/ops/operators/quad_operator_test.py @@ -17,7 +17,6 @@ class QuadOperatorTest(unittest.TestCase): - def test_is_normal_ordered_empty(self): op = QuadOperator() * 2 self.assertTrue(op.is_normal_ordered()) diff --git a/src/openfermion/ops/operators/qubit_operator.py b/src/openfermion/ops/operators/qubit_operator.py index e79efb3d3..040fb49d8 100644 --- a/src/openfermion/ops/operators/qubit_operator.py +++ b/src/openfermion/ops/operators/qubit_operator.py @@ -17,22 +17,22 @@ # Define products of all Pauli operators for symbolic multiplication. _PAULI_OPERATOR_PRODUCTS = { - ('I', 'I'): (1., 'I'), - ('I', 'X'): (1., 'X'), - ('X', 'I'): (1., 'X'), - ('I', 'Y'): (1., 'Y'), - ('Y', 'I'): (1., 'Y'), - ('I', 'Z'): (1., 'Z'), - ('Z', 'I'): (1., 'Z'), - ('X', 'X'): (1., 'I'), - ('Y', 'Y'): (1., 'I'), - ('Z', 'Z'): (1., 'I'), - ('X', 'Y'): (1.j, 'Z'), - ('X', 'Z'): (-1.j, 'Y'), - ('Y', 'X'): (-1.j, 'Z'), - ('Y', 'Z'): (1.j, 'X'), - ('Z', 'X'): (1.j, 'Y'), - ('Z', 'Y'): (-1.j, 'X') + ('I', 'I'): (1.0, 'I'), + ('I', 'X'): (1.0, 'X'), + ('X', 'I'): (1.0, 'X'), + ('I', 'Y'): (1.0, 'Y'), + ('Y', 'I'): (1.0, 'Y'), + ('I', 'Z'): (1.0, 'Z'), + ('Z', 'I'): (1.0, 'Z'), + ('X', 'X'): (1.0, 'I'), + ('Y', 'Y'): (1.0, 'I'), + ('Z', 'Z'): (1.0, 'I'), + ('X', 'Y'): (1.0j, 'Z'), + ('X', 'Z'): (-1.0j, 'Y'), + ('Y', 'X'): (-1.0j, 'Z'), + ('Y', 'Z'): (1.0j, 'X'), + ('Z', 'X'): (1.0j, 'Y'), + ('Z', 'Y'): (-1.0j, 'X'), } @@ -128,8 +128,7 @@ def _simplify(self, term, coefficient=1.0): # Still on the same qubit, keep simplifying. if left_index == right_index: - new_coefficient, new_action = _PAULI_OPERATOR_PRODUCTS[ - left_action, right_action] + new_coefficient, new_action = _PAULI_OPERATOR_PRODUCTS[left_action, right_action] left_factor = (left_index, new_action) coefficient *= new_coefficient diff --git a/src/openfermion/ops/operators/qubit_operator_test.py b/src/openfermion/ops/operators/qubit_operator_test.py index 32b7a59c8..1f4fcd9da 100644 --- a/src/openfermion/ops/operators/qubit_operator_test.py +++ b/src/openfermion/ops/operators/qubit_operator_test.py @@ -14,28 +14,27 @@ import numpy import pytest -from openfermion.ops.operators.qubit_operator import (_PAULI_OPERATOR_PRODUCTS, - QubitOperator) +from openfermion.ops.operators.qubit_operator import _PAULI_OPERATOR_PRODUCTS, QubitOperator def test_pauli_operator_product(): correct = { - ('I', 'I'): (1., 'I'), - ('I', 'X'): (1., 'X'), - ('X', 'I'): (1., 'X'), - ('I', 'Y'): (1., 'Y'), - ('Y', 'I'): (1., 'Y'), - ('I', 'Z'): (1., 'Z'), - ('Z', 'I'): (1., 'Z'), - ('X', 'X'): (1., 'I'), - ('Y', 'Y'): (1., 'I'), - ('Z', 'Z'): (1., 'I'), - ('X', 'Y'): (1.j, 'Z'), - ('X', 'Z'): (-1.j, 'Y'), - ('Y', 'X'): (-1.j, 'Z'), - ('Y', 'Z'): (1.j, 'X'), - ('Z', 'X'): (1.j, 'Y'), - ('Z', 'Y'): (-1.j, 'X') + ('I', 'I'): (1.0, 'I'), + ('I', 'X'): (1.0, 'X'), + ('X', 'I'): (1.0, 'X'), + ('I', 'Y'): (1.0, 'Y'), + ('Y', 'I'): (1.0, 'Y'), + ('I', 'Z'): (1.0, 'Z'), + ('Z', 'I'): (1.0, 'Z'), + ('X', 'X'): (1.0, 'I'), + ('Y', 'Y'): (1.0, 'I'), + ('Z', 'Z'): (1.0, 'I'), + ('X', 'Y'): (1.0j, 'Z'), + ('X', 'Z'): (-1.0j, 'Y'), + ('Y', 'X'): (-1.0j, 'Z'), + ('Y', 'Z'): (1.0j, 'X'), + ('Z', 'X'): (1.0j, 'Y'), + ('Z', 'Y'): (-1.0j, 'X'), } assert _PAULI_OPERATOR_PRODUCTS == correct @@ -56,14 +55,13 @@ def test_init_simplify(): assert QubitOperator("Y3 Y3 Y3 Y3 Y3") == QubitOperator("Y3") assert QubitOperator("Y4 Y4 Y4 Y4 Y4 Y4") == QubitOperator.identity() assert QubitOperator("X0 Y1 Y0 X1") == QubitOperator("Z0 Z1") - assert QubitOperator("X0 Y1 Z3 X2 Z3 Y0") == QubitOperator("Z0 Y1 X2", - coefficient=1j) + assert QubitOperator("X0 Y1 Z3 X2 Z3 Y0") == QubitOperator("Z0 Y1 X2", coefficient=1j) def test_imul_inplace(): qubit_op = QubitOperator("X1") prev_id = id(qubit_op) - qubit_op *= 3. + qubit_op *= 3.0 assert id(qubit_op) == prev_id @@ -72,10 +70,7 @@ def test_different_indices_commute(): assert qubit_op.different_indices_commute is True -@pytest.mark.parametrize( - "multiplier", - [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j)]) +@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j)]) def test_imul_scalar(multiplier): loc_op = ((1, 'X'), (2, 'Y')) qubit_op = QubitOperator(loc_op) @@ -84,7 +79,7 @@ def test_imul_scalar(multiplier): def test_imul_qubit_op(): - op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j) + op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j) op2 = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op1 *= op2 correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X')) @@ -132,12 +127,12 @@ def test_mul_bad_multiplier(): def test_mul_out_of_place(): - op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j) + op1 = QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j) op2 = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op3 = op1 * op2 - correct_coefficient = 1.j * 3.0j * 0.5 + correct_coefficient = 1.0j * 3.0j * 0.5 correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X')) - assert op1 == QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j) + assert op1 == QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j) assert op2 == QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) assert op3 == QubitOperator(correct_term, correct_coefficient) @@ -169,8 +164,8 @@ def test_renormalize(): operator += QubitOperator(((2, 'Z'), (3, 'Y')), 1) operator.renormalize() for term in operator.terms: - assert operator.terms[term] == pytest.approx(1 / numpy.sqrt(2.)) - assert operator.induced_norm(2) == pytest.approx(1.) + assert operator.terms[term] == pytest.approx(1 / numpy.sqrt(2.0)) + assert operator.induced_norm(2) == pytest.approx(1.0) def test_get_operators_empty(): diff --git a/src/openfermion/ops/operators/symbolic_operator.py b/src/openfermion/ops/operators/symbolic_operator.py index 657f562e2..fbd8d6a81 100644 --- a/src/openfermion/ops/operators/symbolic_operator.py +++ b/src/openfermion/ops/operators/symbolic_operator.py @@ -119,11 +119,9 @@ def different_indices_commute(self): __hash__ = None - def __init__(self, term=None, coefficient=1.): + def __init__(self, term=None, coefficient=1.0): if not isinstance(coefficient, COEFFICIENT_TYPES): - raise ValueError( - 'Coefficient must be a numeric type. Got {}'.format( - type(coefficient))) + raise ValueError('Coefficient must be a numeric type. Got {}'.format(type(coefficient))) # Initialize the terms dictionary self.terms = {} @@ -165,7 +163,6 @@ def _long_string_init(self, long_string, coefficient): pattern = r'(.*?)\[(.*?)\]' # regex for a term for match in re.findall(pattern, long_string, flags=re.DOTALL): - # Determine the coefficient for this term coef_string = re.sub(r"\s+", "", match[0]) if coef_string and coef_string[0] == '+': @@ -184,8 +181,7 @@ def _long_string_init(self, long_string, coefficient): else: coef = float(coef_string) except ValueError: - raise ValueError( - 'Invalid coefficient {}.'.format(coef_string)) + raise ValueError('Invalid coefficient {}.'.format(coef_string)) coef *= coefficient # Parse the term, simpify it and add to the dict @@ -204,14 +200,16 @@ def _validate_factor(self, factor): index, action = factor if action not in self.actions: - raise ValueError('Invalid action in factor {}. ' - 'Valid actions are: {}'.format( - factor, self.actions)) + raise ValueError( + 'Invalid action in factor {}. ' 'Valid actions are: {}'.format(factor, self.actions) + ) if not isinstance(index, int) or index < 0: - raise ValueError('Invalid index in factor {}. ' - 'The index should be a non-negative ' - 'integer.'.format(factor)) + raise ValueError( + 'Invalid index in factor {}. ' + 'The index should be a non-negative ' + 'integer.'.format(factor) + ) def _simplify(self, term, coefficient=1.0): """Simplifies a term.""" @@ -261,9 +259,11 @@ def _parse_string(self, term): while index_start > 0 and factor[index_start - 1].isdigit(): index_start -= 1 if factor[index_start - 1] == '-': - raise ValueError('Invalid index in factor {}. ' - 'The index should be a non-negative ' - 'integer.'.format(factor)) + raise ValueError( + 'Invalid index in factor {}. ' + 'The index should be a non-negative ' + 'integer.'.format(factor) + ) index = int(factor[index_start:]) action_string = factor[:index_start] @@ -271,14 +271,15 @@ def _parse_string(self, term): # The index is at the beginning of the string; find where # it ends if factor[0] == '-': - raise ValueError('Invalid index in factor {}. ' - 'The index should be a non-negative ' - 'integer.'.format(factor)) + raise ValueError( + 'Invalid index in factor {}. ' + 'The index should be a non-negative ' + 'integer.'.format(factor) + ) if not factor[0].isdigit(): raise ValueError('Invalid factor {}.'.format(factor)) index_end = 1 - while (index_end <= len(factor) - 1 and - factor[index_end].isdigit()): + while index_end <= len(factor) - 1 and factor[index_end].isdigit(): index_end += 1 index = int(factor[:index_end]) @@ -288,9 +289,10 @@ def _parse_string(self, term): if action_string in self.action_strings: action = self.actions[self.action_strings.index(action_string)] else: - raise ValueError('Invalid action in factor {}. ' - 'Valid actions are: {}'.format( - factor, self.action_strings)) + raise ValueError( + 'Invalid action in factor {}. ' + 'Valid actions are: {}'.format(factor, self.action_strings) + ) # Add the factor to the list as a tuple processed_term.append((index, action)) @@ -378,7 +380,8 @@ def __imul__(self, multiplier): new_term = left_term + right_term new_coefficient, new_term = self._simplify( - new_term, coefficient=new_coefficient) + new_term, coefficient=new_coefficient + ) # Update result dict. if new_term in result_terms: @@ -390,8 +393,11 @@ def __imul__(self, multiplier): # Invalid multiplier type else: - raise TypeError('Cannot multiply {} with {}'.format( - self.__class__.__name__, multiplier.__class__.__name__)) + raise TypeError( + 'Cannot multiply {} with {}'.format( + self.__class__.__name__, multiplier.__class__.__name__ + ) + ) def __mul__(self, multiplier): """Return self * multiplier for a scalar, or a SymbolicOperator. @@ -410,8 +416,7 @@ def __mul__(self, multiplier): product *= multiplier return product else: - raise TypeError('Object of invalid type cannot multiply with ' + - type(self) + '.') + raise TypeError('Object of invalid type cannot multiply with ' + type(self) + '.') def __iadd__(self, addend): """In-place method for += addition of SymbolicOperator. @@ -428,8 +433,7 @@ def __iadd__(self, addend): """ if isinstance(addend, type(self)): for term in addend.terms: - self.terms[term] = (self.terms.get(term, 0.0) + - addend.terms[term]) + self.terms[term] = self.terms.get(term, 0.0) + addend.terms[term] if self._issmall(self.terms[term]): del self.terms[term] elif isinstance(addend, COEFFICIENT_TYPES): @@ -476,15 +480,13 @@ def __isub__(self, subtrahend): """ if isinstance(subtrahend, type(self)): for term in subtrahend.terms: - self.terms[term] = (self.terms.get(term, 0.0) - - subtrahend.terms[term]) + self.terms[term] = self.terms.get(term, 0.0) - subtrahend.terms[term] if self._issmall(self.terms[term]): del self.terms[term] elif isinstance(subtrahend, COEFFICIENT_TYPES): self.constant -= subtrahend else: - raise TypeError('Cannot subtract invalid type from {}.'.format( - type(self))) + raise TypeError('Cannot subtract invalid type from {}.'.format(type(self))) return self def __sub__(self, subtrahend): @@ -527,8 +529,7 @@ def __rmul__(self, multiplier): TypeError: Object of invalid type cannot multiply SymbolicOperator. """ if not isinstance(multiplier, COEFFICIENT_TYPES): - raise TypeError('Object of invalid type cannot multiply with ' + - type(self) + '.') + raise TypeError('Object of invalid type cannot multiply with ' + type(self) + '.') return self * multiplier def __truediv__(self, divisor): @@ -549,23 +550,21 @@ def __truediv__(self, divisor): """ if not isinstance(divisor, COEFFICIENT_TYPES): - raise TypeError('Cannot divide ' + type(self) + - ' by non-scalar type.') + raise TypeError('Cannot divide ' + type(self) + ' by non-scalar type.') return self * (1.0 / divisor) def __div__(self, divisor): - """ For compatibility with Python 2. """ + """For compatibility with Python 2.""" return self.__truediv__(divisor) def __itruediv__(self, divisor): if not isinstance(divisor, COEFFICIENT_TYPES): - raise TypeError('Cannot divide ' + type(self) + - ' by non-scalar type.') - self *= (1.0 / divisor) + raise TypeError('Cannot divide ' + type(self) + ' by non-scalar type.') + self *= 1.0 / divisor return self def __idiv__(self, divisor): - """ For compatibility with Python 2. """ + """For compatibility with Python 2.""" return self.__itruediv__(divisor) def __neg__(self): @@ -592,7 +591,9 @@ def __pow__(self, exponent): if not isinstance(exponent, int) or exponent < 0: raise ValueError( 'exponent must be a non-negative int, but was {} {}'.format( - type(exponent), repr(exponent))) + type(exponent), repr(exponent) + ) + ) # Initialized identity. exponentiated = self.__class__(()) @@ -665,7 +666,7 @@ def compress(self, abs_tol=EQ_TOLERANCE): coeff = sympy.re(coeff) if sympy.simplify(sympy.re(coeff) <= abs_tol) == True: coeff = 1j * sympy.im(coeff) - if (sympy.simplify(abs(coeff) <= abs_tol) != True): + if sympy.simplify(abs(coeff) <= abs_tol) != True: new_terms[term] = coeff continue @@ -673,7 +674,7 @@ def compress(self, abs_tol=EQ_TOLERANCE): if abs(coeff.imag) <= abs_tol: coeff = coeff.real if abs(coeff.real) <= abs_tol: - coeff = 1.j * coeff.imag + coeff = 1.0j * coeff.imag # Add the term if the coefficient is large enough if abs(coeff) > abs_tol: @@ -694,10 +695,10 @@ def induced_norm(self, order=1): Args: order(int): the order of the induced norm. """ - norm = 0. + norm = 0.0 for coefficient in self.terms.values(): - norm += abs(coefficient)**order - return norm**(1. / order) + norm += abs(coefficient) ** order + return norm ** (1.0 / order) def many_body_order(self): """Compute the many-body order of a SymbolicOperator. @@ -713,9 +714,8 @@ def many_body_order(self): return 0 else: return max( - len(term) - for term, coeff in self.terms.items() - if (self._issmall(coeff) is False)) + len(term) for term, coeff in self.terms.items() if (self._issmall(coeff) is False) + ) @classmethod def accumulate(cls, operators, start=None): @@ -744,13 +744,12 @@ def get_operator_groups(self, num_groups): self. """ if num_groups < 1: - warnings.warn('Invalid num_groups {} < 1.'.format(num_groups), - RuntimeWarning) + warnings.warn('Invalid num_groups {} < 1.'.format(num_groups), RuntimeWarning) num_groups = 1 operators = self.get_operators() num_groups = min(num_groups, len(self.terms)) for i in range(num_groups): yield self.accumulate( - itertools.islice(operators, - len(range(i, len(self.terms), num_groups)))) + itertools.islice(operators, len(range(i, len(self.terms), num_groups))) + ) diff --git a/src/openfermion/ops/operators/symbolic_operator_test.py b/src/openfermion/ops/operators/symbolic_operator_test.py index 2c5691ce1..a89cb9f21 100644 --- a/src/openfermion/ops/operators/symbolic_operator_test.py +++ b/src/openfermion/ops/operators/symbolic_operator_test.py @@ -90,10 +90,7 @@ def test_init_single_factor(self): group_1 = [DummyOperator1((3, 0)), DummyOperator1(((3, 0),))] group_2 = [DummyOperator2((5, 'X')), DummyOperator2(((5, 'X'),))] - group_3 = [ - DummyOperator2((5, 'X'), .5), - DummyOperator2(((5, 'X'),), .5) - ] + group_3 = [DummyOperator2((5, 'X'), 0.5), DummyOperator2(((5, 'X'),), 0.5)] equals_tester.add_equality_group(*group_1) equals_tester.add_equality_group(*group_2) @@ -105,40 +102,36 @@ def test_eq_and_ne(self): zeros_1 = [ DummyOperator1(), - DummyOperator1('1^ 0', 0.), + DummyOperator1('1^ 0', 0.0), DummyOperator1('1^ 0', -1j) * 0, DummyOperator1('1^ 0', 0 * sympy.Symbol('x')), - DummyOperator1('1^ 0', sympy.Symbol('x')) * 0 + DummyOperator1('1^ 0', sympy.Symbol('x')) * 0, ] zeros_2 = [ DummyOperator2(), - DummyOperator2(((1, 'Y'), (0, 'X')), 0.), + DummyOperator2(((1, 'Y'), (0, 'X')), 0.0), DummyOperator2(((1, 'Y'), (0, 'X')), -1j) * 0, DummyOperator2(((1, 'Y'), (0, 'X')), 0 * sympy.Symbol('x')), - DummyOperator2(((1, 'Y'), (0, 'X')), sympy.Symbol('x')) * 0 + DummyOperator2(((1, 'Y'), (0, 'X')), sympy.Symbol('x')) * 0, ] different_ops_1 = [ DummyOperator1(((1, 0),), -0.1j), DummyOperator1(((1, 1),), -0.1j), - (DummyOperator1(((1, 0),), -0.1j) + DummyOperator1( - ((1, 1),), -0.1j)) + (DummyOperator1(((1, 0),), -0.1j) + DummyOperator1(((1, 1),), -0.1j)), ] different_ops_2 = [ DummyOperator2(((1, 'Y'),), -0.1j), DummyOperator2(((1, 'X'),), -0.1j), - (DummyOperator2(((1, 'Y'),), -0.1j) + DummyOperator2( - ((2, 'Y'),), -0.1j)) + (DummyOperator2(((1, 'Y'),), -0.1j) + DummyOperator2(((2, 'Y'),), -0.1j)), ] sympy_ops_1 = [ DummyOperator1('1^ 0', sympy.Symbol('x')), DummyOperator1('1^ 0', 2 * sympy.Symbol('x')) / 2, - DummyOperator1('1^ 0', - sympy.Symbol('x') * sympy.Symbol('y')) * 1 / - sympy.Symbol('y') + DummyOperator1('1^ 0', sympy.Symbol('x') * sympy.Symbol('y')) * 1 / sympy.Symbol('y'), ] sympy_ops_2 = [DummyOperator1('1^ 0', sympy.Symbol('x') + 1)] @@ -164,7 +157,7 @@ def test_many_body_order(self): op4 = DummyOperator2('X0 X1 Y3') op5 = op4 - DummyOperator2('Z0') op6 = op5 - DummyOperator2('Z1 Z2 Y3 Y4 Y9 Y10') - op7 = op5 - DummyOperator2('Z1 Z2 Y3 Y4 Y9 Y10', EQ_TOLERANCE / 2.) + op7 = op5 - DummyOperator2('Z1 Z2 Y3 Y4 Y9 Y10', EQ_TOLERANCE / 2.0) self.assertEqual(zero.many_body_order(), 0) self.assertEqual(identity.many_body_order(), 0) @@ -226,14 +219,14 @@ def test_init_tuple_npcomplex128_coefficient(self): def test_init_list_real_coefficient(self): loc_op = [(0, 1), (5, 0), (6, 1)] - coefficient = 1. / 3 + coefficient = 1.0 / 3 fermion_op = DummyOperator1(loc_op, coefficient) self.assertEqual(len(fermion_op.terms), 1) self.assertEqual(fermion_op.terms[tuple(loc_op)], coefficient) def test_init_list_complex_coefficient(self): loc_op = [(0, 1), (5, 0), (6, 1)] - coefficient = 2j / 3. + coefficient = 2j / 3.0 fermion_op = DummyOperator1(loc_op, coefficient) self.assertEqual(len(fermion_op.terms), 1) self.assertEqual(fermion_op.terms[tuple(loc_op)], coefficient) @@ -301,7 +294,7 @@ def test_zero_is_multiplicative_nil(self): self.assertTrue(o == o * (f + g)) def test_init_str(self): - fermion_op = DummyOperator1('0^ 5 12^', -1.) + fermion_op = DummyOperator1('0^ 5 12^', -1.0) correct = ((0, 1), (5, 0), (12, 1)) self.assertIn(correct, fermion_op.terms) self.assertEqual(fermion_op.terms[correct], -1.0) @@ -318,18 +311,18 @@ def test_raises_error_negative_indices(self): _ = DummyOperator1('-1^ 0') def test_init_long_str(self): - fermion_op = DummyOperator1( - '(-2.0+3.0j) [0^ 1] +\n\n -1.0[ 2^ 3 ] - []', -1.) - correct = \ - DummyOperator1('0^ 1', complex(2., -3.)) + \ - DummyOperator1('2^ 3', 1.) + \ - DummyOperator1('', 1.) + fermion_op = DummyOperator1('(-2.0+3.0j) [0^ 1] +\n\n -1.0[ 2^ 3 ] - []', -1.0) + correct = ( + DummyOperator1('0^ 1', complex(2.0, -3.0)) + + DummyOperator1('2^ 3', 1.0) + + DummyOperator1('', 1.0) + ) self.assertEqual(len((fermion_op - correct).terms), 0) reparsed_op = DummyOperator1(str(fermion_op)) self.assertEqual(len((fermion_op - reparsed_op).terms), 0) fermion_op = DummyOperator1('1.7 [3^ 2] - 8 [4^]') - correct = DummyOperator1('3^ 2', 1.7) + DummyOperator1('4^', -8.) + correct = DummyOperator1('3^ 2', 1.7) + DummyOperator1('4^', -8.0) self.assertEqual(len((fermion_op - correct).terms), 0) fermion_op = DummyOperator1('-(2.3 + 1.7j) [3^ 2]') @@ -381,15 +374,15 @@ def test_init_invalid_tensor_factor(self): DummyOperator1(((-2, 1), (1, 0))) def test_DummyOperator1(self): - op = DummyOperator1((), 3.) - self.assertTrue(op == DummyOperator1(()) * 3.) + op = DummyOperator1((), 3.0) + self.assertTrue(op == DummyOperator1(()) * 3.0) def test_imul_inplace(self): fermion_op = DummyOperator1("1^") prev_id = id(fermion_op) - fermion_op *= 3. + fermion_op *= 3.0 self.assertEqual(id(fermion_op), prev_id) - self.assertEqual(fermion_op.terms[((1, 1),)], 3.) + self.assertEqual(fermion_op.terms[((1, 1),)], 3.0) def test_imul_scalar_real(self): loc_op = ((1, 0), (2, 1)) @@ -426,8 +419,7 @@ def test_imul_sympy_ops(self): coeff2 = sympy.Symbol('x') + 5 fermion_op = DummyOperator1(loc_op1, coeff1) fermion_op *= DummyOperator1(loc_op2, coeff2) - self.assertTrue(fermion_op.terms[loc_op1 + loc_op2] - - coeff1 * coeff2 == 0) + self.assertTrue(fermion_op.terms[loc_op1 + loc_op2] - coeff1 * coeff2 == 0) def test_imul_scalar_npfloat64(self): loc_op = ((1, 0), (2, 1)) @@ -444,11 +436,10 @@ def test_imul_scalar_npcomplex128(self): self.assertEqual(fermion_op.terms[loc_op], multiplier) def test_imul_fermion_op(self): - op1 = DummyOperator1(((0, 1), (3, 0), (8, 1), (8, 0), (11, 1)), 3.j) + op1 = DummyOperator1(((0, 1), (3, 0), (8, 1), (8, 0), (11, 1)), 3.0j) op2 = DummyOperator1(((1, 1), (3, 1), (8, 0)), 0.5) op1 *= op2 - correct_term = ((0, 1), (3, 0), (8, 1), (8, 0), (11, 1), (1, 1), (3, 1), - (8, 0)) + correct_term = ((0, 1), (3, 0), (8, 1), (8, 0), (11, 1), (1, 1), (3, 1), (8, 0)) self.assertEqual(len(op1.terms), 1) self.assertIn(correct_term, op1.terms) @@ -458,8 +449,7 @@ def test_imul_fermion_op_2(self): op3 *= op4 op4 *= op3 self.assertIn(((1, 1), (0, 0), (1, 0), (0, 1), (2, 1)), op3.terms) - self.assertEqual(op3.terms[((1, 1), (0, 0), (1, 0), (0, 1), (2, 1))], - 1.5j) + self.assertEqual(op3.terms[((1, 1), (0, 0), (1, 0), (0, 1), (2, 1))], 1.5j) def test_imul_fermion_op_duplicate_term(self): op1 = DummyOperator1('1 2 3') @@ -470,7 +460,7 @@ def test_imul_fermion_op_duplicate_term(self): op2 += DummyOperator1('2 3') op1 *= op2 - self.assertAlmostEqual(op1.terms[((1, 0), (2, 0), (3, 0))], 2.) + self.assertAlmostEqual(op1.terms[((1, 0), (2, 0), (3, 0))], 2.0) def test_imul_bidir(self): op_a = DummyOperator1(((1, 1), (0, 0)), -1j) @@ -478,14 +468,11 @@ def test_imul_bidir(self): op_a *= op_b op_b *= op_a self.assertIn(((1, 1), (0, 0), (1, 1), (0, 1), (2, 1)), op_a.terms) - self.assertEqual(op_a.terms[((1, 1), (0, 0), (1, 1), (0, 1), (2, 1))], - 1.5j) - self.assertIn( - ((1, 1), (0, 1), (2, 1), (1, 1), (0, 0), (1, 1), (0, 1), (2, 1)), - op_b.terms) + self.assertEqual(op_a.terms[((1, 1), (0, 0), (1, 1), (0, 1), (2, 1))], 1.5j) + self.assertIn(((1, 1), (0, 1), (2, 1), (1, 1), (0, 0), (1, 1), (0, 1), (2, 1)), op_b.terms) self.assertEqual( - op_b.terms[((1, 1), (0, 1), (2, 1), (1, 1), (0, 0), (1, 1), (0, 1), - (2, 1))], -2.25j) + op_b.terms[((1, 1), (0, 1), (2, 1), (1, 1), (0, 0), (1, 1), (0, 1), (2, 1))], -2.25j + ) def test_imul_bad_multiplier(self): op = DummyOperator1(((1, 1), (0, 1)), -1j) @@ -506,20 +493,17 @@ def test_mul_bad_multiplier(self): def test_mul_sympy_coeff(self): op = DummyOperator1(((1, 1), (0, 1)), -1j) op = op * sympy.Symbol('x') - self.assertTrue(op.terms[((1, 1), - (0, 1))] - (-1j * sympy.Symbol('x')) == 0) + self.assertTrue(op.terms[((1, 1), (0, 1))] - (-1j * sympy.Symbol('x')) == 0) def test_mul_out_of_place(self): - op1 = DummyOperator1(((0, 1), (3, 1), (3, 0), (11, 1)), 3.j) + op1 = DummyOperator1(((0, 1), (3, 1), (3, 0), (11, 1)), 3.0j) op2 = DummyOperator1(((1, 1), (3, 1), (8, 0)), 0.5) op3 = op1 * op2 correct_coefficient = 3.0j * 0.5 correct_term = ((0, 1), (3, 1), (3, 0), (11, 1), (1, 1), (3, 1), (8, 0)) - self.assertTrue(op1 == DummyOperator1(((0, 1), (3, 1), (3, 0), - (11, 1)), 3.j)) + self.assertTrue(op1 == DummyOperator1(((0, 1), (3, 1), (3, 0), (11, 1)), 3.0j)) self.assertTrue(op2 == DummyOperator1(((1, 1), (3, 1), (8, 0)), 0.5)) - self.assertTrue( - op3 == DummyOperator1(correct_term, correct_coefficient)) + self.assertTrue(op3 == DummyOperator1(correct_term, correct_coefficient)) def test_mul_npfloat64(self): op = DummyOperator1(((1, 0), (3, 1)), 0.5) @@ -531,9 +515,9 @@ def test_mul_multiple_terms(self): op += DummyOperator1(((1, 1), (9, 1)), 1.4j) res = op * op correct = DummyOperator1(((1, 0), (8, 1), (1, 0), (8, 1)), 0.5**2) - correct += (DummyOperator1( - ((1, 0), (8, 1), (1, 1), (9, 1)), 0.7j) + DummyOperator1( - ((1, 1), (9, 1), (1, 0), (8, 1)), 0.7j)) + correct += DummyOperator1(((1, 0), (8, 1), (1, 1), (9, 1)), 0.7j) + DummyOperator1( + ((1, 1), (9, 1), (1, 0), (8, 1)), 0.7j + ) correct += DummyOperator1(((1, 1), (9, 1), (1, 1), (9, 1)), 1.4j**2) self.assertTrue(res == correct) @@ -575,7 +559,7 @@ def test_truediv_and_div_real(self): divisor = 0.5 original = copy.deepcopy(op) res = op / divisor - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) self.assertTrue(res == correct) # Test if done out of place self.assertTrue(op == original) @@ -585,7 +569,7 @@ def test_truediv_and_div_complex(self): divisor = 0.6j original = copy.deepcopy(op) res = op / divisor - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) self.assertTrue(res == correct) # Test if done out of place self.assertTrue(op == original) @@ -595,7 +579,7 @@ def test_truediv_and_div_npfloat64(self): divisor = numpy.float64(2.303) original = copy.deepcopy(op) res = op / divisor - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) self.assertTrue(res == correct) # Test if done out of place self.assertTrue(op == original) @@ -605,7 +589,7 @@ def test_truediv_and_div_npcomplex128(self): divisor = numpy.complex128(566.4j + 0.3) original = copy.deepcopy(op) res = op / divisor - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) self.assertTrue(res == correct) # Test if done out of place self.assertTrue(op == original) @@ -619,7 +603,7 @@ def test_itruediv_and_idiv_real(self): op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) divisor = 0.5 original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor self.assertTrue(op == correct) # Test if done in-place @@ -629,7 +613,7 @@ def test_itruediv_and_idiv_complex(self): op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) divisor = 0.6j original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor self.assertTrue(op == correct) # Test if done in-place @@ -639,7 +623,7 @@ def test_itruediv_and_idiv_npfloat64(self): op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) divisor = numpy.float64(2.3030) original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor self.assertTrue(op == correct) # Test if done in-place @@ -649,7 +633,7 @@ def test_itruediv_and_idiv_npcomplex128(self): op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) divisor = numpy.complex128(12.3 + 7.4j) original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor self.assertTrue(op == correct) # Test if done in-place @@ -877,27 +861,27 @@ def test_pow_high_term(self): def test_pow_neg_error(self): with self.assertRaises(ValueError): - _ = DummyOperator1()**-1 + _ = DummyOperator1() ** -1 def test_pow_nonint_error(self): with self.assertRaises(ValueError): - _ = DummyOperator1('3 2^')**0.5 + _ = DummyOperator1('3 2^') ** 0.5 def test_compress_terms(self): - op = (DummyOperator1('3^ 1', 0.3 + 3e-11j) + - DummyOperator1('2^ 3', 5e-10) + DummyOperator1('1^ 3', 1e-3)) - op_compressed = (DummyOperator1('3^ 1', 0.3) + - DummyOperator1('1^ 3', 1e-3)) + op = ( + DummyOperator1('3^ 1', 0.3 + 3e-11j) + + DummyOperator1('2^ 3', 5e-10) + + DummyOperator1('1^ 3', 1e-3) + ) + op_compressed = DummyOperator1('3^ 1', 0.3) + DummyOperator1('1^ 3', 1e-3) op.compress(1e-7) self.assertTrue(op_compressed == op) def test_compress_sympy(self): - op = (DummyOperator1('', - sympy.Symbol('x') + sympy.Symbol('y')) + - DummyOperator1('3^ 1', - sympy.Symbol('x') + 1e-7 - sympy.Symbol('x'))) - op_compressed = DummyOperator1('', - sympy.Symbol('x') + sympy.Symbol('y')) + op = DummyOperator1('', sympy.Symbol('x') + sympy.Symbol('y')) + DummyOperator1( + '3^ 1', sympy.Symbol('x') + 1e-7 - sympy.Symbol('x') + ) + op_compressed = DummyOperator1('', sympy.Symbol('x') + sympy.Symbol('y')) op.compress(1e-6) self.assertTrue(op_compressed == op) @@ -906,7 +890,6 @@ def test_str_sympy(self): self.assertEqual(str(op), "x [0^]") def test_str(self): - op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) self.assertEqual(str(op), "0.5 [1^ 3 8^]") @@ -916,25 +899,33 @@ def test_str(self): op = DummyOperator1() self.assertEqual(str(op), "0") - op = (DummyOperator1(((3, 1), (4, 1), (5, 0)), 1.0) + DummyOperator1( - ((3, 1), (4, 1), (4, 0)), 2.0) + DummyOperator1( - ((2, 1), (4, 1), (5, 0)), 1.0) + DummyOperator1( - ((3, 0), (2, 1), (1, 1)), 2.0) + DummyOperator1( - ((3, 0), (2, 0), (1, 1)), 2.0)) + op = ( + DummyOperator1(((3, 1), (4, 1), (5, 0)), 1.0) + + DummyOperator1(((3, 1), (4, 1), (4, 0)), 2.0) + + DummyOperator1(((2, 1), (4, 1), (5, 0)), 1.0) + + DummyOperator1(((3, 0), (2, 1), (1, 1)), 2.0) + + DummyOperator1(((3, 0), (2, 0), (1, 1)), 2.0) + ) self.assertEqual( - str(op).strip(), """ + str(op).strip(), + """ 1.0 [2^ 4^ 5] + 2.0 [3 2 1^] + 2.0 [3 2^ 1^] + 2.0 [3^ 4^ 4] + 1.0 [3^ 4^ 5] -""".strip()) +""".strip(), + ) - op = (DummyOperator1(((3, 1), (4, 1), (5, 0)), 0.0) + DummyOperator1( - ((3, 1), (4, 1), (4, 0)), 2.0)) - self.assertEqual(str(op).strip(), """ + op = DummyOperator1(((3, 1), (4, 1), (5, 0)), 0.0) + DummyOperator1( + ((3, 1), (4, 1), (4, 0)), 2.0 + ) + self.assertEqual( + str(op).strip(), + """ 2.0 [3^ 4^ 4] -""".strip()) +""".strip(), + ) def test_rep(self): op = DummyOperator1(((1, 1), (3, 0), (8, 1)), 0.5) @@ -964,18 +955,18 @@ def test_init_list(self): self.assertTrue(qubit_op.terms[tuple(loc_op)] == coefficient) def test_init_str(self): - qubit_op = DummyOperator2('X0 Y5 Z12', -1.) + qubit_op = DummyOperator2('X0 Y5 Z12', -1.0) correct = ((0, 'X'), (5, 'Y'), (12, 'Z')) self.assertTrue(correct in qubit_op.terms) self.assertTrue(qubit_op.terms[correct] == -1.0) def test_init_long_str(self): - qubit_op = DummyOperator2( - '(-2.0+3.0j) [X0 Y1] +\n\n -1.0[ X2 Y3 ] - []', -1.) - correct = \ - DummyOperator2('X0 Y1', complex(2., -3.)) + \ - DummyOperator2('X2 Y3', 1.) + \ - DummyOperator2('', 1.) + qubit_op = DummyOperator2('(-2.0+3.0j) [X0 Y1] +\n\n -1.0[ X2 Y3 ] - []', -1.0) + correct = ( + DummyOperator2('X0 Y1', complex(2.0, -3.0)) + + DummyOperator2('X2 Y3', 1.0) + + DummyOperator2('', 1.0) + ) self.assertEqual(len((qubit_op - correct).terms), 0) reparsed_op = DummyOperator2(str(qubit_op)) self.assertEqual(len((qubit_op - reparsed_op).terms), 0) @@ -987,12 +978,12 @@ def test_init_long_str(self): def test_init_long_str_sympy(self): coeff = sympy.Symbol('x') - qubit_op = DummyOperator2( - '(-2.0+3.0j) [X0 Y1] +\n\n -1.0[ X2 Y3 ] - []', -coeff) - correct = \ - DummyOperator2('X0 Y1', complex(2., -3.) * coeff) + \ - DummyOperator2('X2 Y3', coeff) + \ - DummyOperator2('', coeff) + qubit_op = DummyOperator2('(-2.0+3.0j) [X0 Y1] +\n\n -1.0[ X2 Y3 ] - []', -coeff) + correct = ( + DummyOperator2('X0 Y1', complex(2.0, -3.0) * coeff) + + DummyOperator2('X2 Y3', coeff) + + DummyOperator2('', coeff) + ) self.assertEqual(len((qubit_op - correct).terms), 0) with self.assertRaises(ValueError): _ = DummyOperator2(str(qubit_op)) @@ -1002,10 +993,10 @@ def test_init_long_str_sympy_failure(self): _ = DummyOperator2('(x^) [X0 Y1]', -1) def test_init_str_identity(self): - qubit_op = DummyOperator2('', 2.) + qubit_op = DummyOperator2('', 2.0) self.assertTrue(len(qubit_op.terms) == 1) self.assertTrue(() in qubit_op.terms) - self.assertAlmostEqual(qubit_op.terms[()], 2.) + self.assertAlmostEqual(qubit_op.terms[()], 2.0) def test_init_bad_term(self): with self.assertRaises(ValueError): @@ -1040,30 +1031,30 @@ def test_init_bad_qubit_num(self): _ = DummyOperator2('X-1') def test_compress(self): - a = DummyOperator2('X0', .9e-12) + a = DummyOperator2('X0', 0.9e-12) self.assertTrue(len(a.terms) == 1) a.compress() self.assertTrue(len(a.terms) == 0) - a = DummyOperator2('X0', 1. + 1j) - a.compress(.5) + a = DummyOperator2('X0', 1.0 + 1j) + a.compress(0.5) self.assertTrue(len(a.terms) == 1) for term in a.terms: - self.assertTrue(a.terms[term] == 1. + 1j) + self.assertTrue(a.terms[term] == 1.0 + 1j) a = DummyOperator2('X0', 1.1 + 1j) - a.compress(1.) + a.compress(1.0) self.assertTrue(len(a.terms) == 1) for term in a.terms: self.assertTrue(a.terms[term] == 1.1) - a = DummyOperator2('X0', 1.1 + 1j) + DummyOperator2('X1', 1.e-6j) + a = DummyOperator2('X0', 1.1 + 1j) + DummyOperator2('X1', 1.0e-6j) a.compress() self.assertTrue(len(a.terms) == 2) for term in a.terms: self.assertTrue(isinstance(a.terms[term], complex)) - a.compress(1.e-5) + a.compress(1.0e-5) self.assertTrue(len(a.terms) == 1) for term in a.terms: self.assertTrue(isinstance(a.terms[term], complex)) - a.compress(1.) + a.compress(1.0) self.assertTrue(len(a.terms) == 1) for term in a.terms: self.assertTrue(isinstance(a.terms[term], float)) @@ -1095,7 +1086,7 @@ def test_truediv_and_div(self): original = copy.deepcopy(op) res = op / divisor res2 = op2.__div__(divisor) # To test python 2 version as well - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) self.assertTrue(res == correct) self.assertTrue(res2 == correct) # Test if done out of place @@ -1112,7 +1103,7 @@ def test_itruediv_and_idiv(self): op = DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op2 = copy.deepcopy(op) original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor op2.__idiv__(divisor) # To test python 2 version as well self.assertTrue(op == correct) @@ -1209,8 +1200,7 @@ def test_isub_bad_addend(self): def test_neg(self): op = DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) # out of place - self.assertTrue(op == DummyOperator2(((1, 'X'), (3, 'Y'), - (8, 'Z')), 0.5)) + self.assertTrue(op == DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)) correct = -1.0 * op self.assertTrue(correct == -op) @@ -1219,20 +1209,23 @@ def test_str(self): self.assertEqual(str(op), "0.5 [X1 Y3 Z8]") op2 = DummyOperator2((), 2) self.assertEqual(str(op2), "2 []") - op3 = (DummyOperator2( - ((3, 'X'), (4, 'Z'), (5, 'Y')), 3.0) + DummyOperator2( - ((1, 'X'), (4, 'Z'), (4, 'Z')), 2.0) + DummyOperator2( - ((2, 'Z'), (4, 'Z'), (5, 'X')), 1.0) + DummyOperator2( - ((3, 'Y'), (2, 'Y'), (1, 'Z')), 2.0) + DummyOperator2( - ((3, 'Y'), (2, 'Y'), (1, 'Y')), 2.0)) + op3 = ( + DummyOperator2(((3, 'X'), (4, 'Z'), (5, 'Y')), 3.0) + + DummyOperator2(((1, 'X'), (4, 'Z'), (4, 'Z')), 2.0) + + DummyOperator2(((2, 'Z'), (4, 'Z'), (5, 'X')), 1.0) + + DummyOperator2(((3, 'Y'), (2, 'Y'), (1, 'Z')), 2.0) + + DummyOperator2(((3, 'Y'), (2, 'Y'), (1, 'Y')), 2.0) + ) self.assertEqual( - str(op3).strip(), """ + str(op3).strip(), + """ 2.0 [X1 Z4 Z4] + 2.0 [Y1 Y2 Y3] + 2.0 [Z1 Y2 Y3] + 1.0 [Z2 Z4 X5] + 3.0 [X3 Z4 Y5] -""".strip()) +""".strip(), + ) def test_str_empty(self): op = DummyOperator2() @@ -1245,8 +1238,12 @@ def test_str_out_of_order(self): def test_str_multiple_terms(self): op = DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op += DummyOperator2(((1, 'Y'), (3, 'Y'), (8, 'Z')), 0.6) - self.assertTrue((str(op) == "0.5 [X1 Y3 Z8] +\n0.6 [Y1 Y3 Z8]" or - str(op) == "0.6 [Y1 Y3 Z8] +\n0.5 [X1 Y3 Z8]")) + self.assertTrue( + ( + str(op) == "0.5 [X1 Y3 Z8] +\n0.6 [Y1 Y3 Z8]" + or str(op) == "0.6 [Y1 Y3 Z8] +\n0.5 [X1 Y3 Z8]" + ) + ) op2 = DummyOperator2((), 2) self.assertEqual(str(op2), "2 []") @@ -1258,7 +1255,7 @@ def test_rep(self): def test_norm(self): op = DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), 1) op += DummyOperator2(((2, 'Z'), (3, 'Y')), 1) - self.assertAlmostEqual(op.induced_norm(2), numpy.sqrt(2.)) + self.assertAlmostEqual(op.induced_norm(2), numpy.sqrt(2.0)) def test_norm_sympy(self): x_sym = sympy.Symbol('x') @@ -1266,7 +1263,7 @@ def test_norm_sympy(self): op = DummyOperator2(((1, 'X'), (3, 'Y'), (8, 'Z')), x_sym) op += DummyOperator2(((2, 'Z'), (3, 'Y')), y_sym) norm = op.induced_norm(2) - self.assertTrue(norm - (abs(x_sym)**2 + abs(y_sym)**2)**(0.5) == 0) + self.assertTrue(norm - (abs(x_sym) ** 2 + abs(y_sym) ** 2) ** (0.5) == 0) def test_many_body_order_sympy(self): x_sym = sympy.Symbol('x') diff --git a/src/openfermion/ops/representations/__init__.py b/src/openfermion/ops/representations/__init__.py index 37ef64d99..55d3ba725 100644 --- a/src/openfermion/ops/representations/__init__.py +++ b/src/openfermion/ops/representations/__init__.py @@ -1,8 +1,4 @@ -from .polynomial_tensor import ( - PolynomialTensor, - PolynomialTensorError, - general_basis_change, -) +from .polynomial_tensor import PolynomialTensor, PolynomialTensorError, general_basis_change from .diagonal_coulomb_hamiltonian import DiagonalCoulombHamiltonian @@ -13,10 +9,7 @@ get_active_space_integrals, ) -from .interaction_rdm import ( - InteractionRDM, - InteractionRDMError, -) +from .interaction_rdm import InteractionRDM, InteractionRDMError from .quadratic_hamiltonian import ( QuadraticHamiltonian, diff --git a/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian.py b/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian.py index 52de901b4..73ca4fefb 100644 --- a/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian.py +++ b/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian.py @@ -36,10 +36,12 @@ class DiagonalCoulombHamiltonian: constant(float): The constant. """ - def __init__(self, one_body, two_body, constant=0.): + def __init__(self, one_body, two_body, constant=0.0): if two_body.dtype != numpy.float64: - raise ValueError('Two-body tensor has invalid dtype. Expected {} ' - 'but was {}'.format(numpy.float64, two_body.dtype)) + raise ValueError( + 'Two-body tensor has invalid dtype. Expected {} ' + 'but was {}'.format(numpy.float64, two_body.dtype) + ) if not numpy.allclose(two_body, two_body.T): raise ValueError('Two-body tensor must be symmetric.') if not numpy.allclose(one_body, one_body.T.conj()): @@ -48,7 +50,7 @@ def __init__(self, one_body, two_body, constant=0.): # Move the diagonal of two_body to one_body diag_indices = numpy.diag_indices(one_body.shape[0]) one_body[diag_indices] += two_body[diag_indices] - numpy.fill_diagonal(two_body, 0.) + numpy.fill_diagonal(two_body, 0.0) self.one_body = one_body self.two_body = two_body diff --git a/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian_test.py b/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian_test.py index 9db6cf86b..0c301f252 100644 --- a/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian_test.py +++ b/src/openfermion/ops/representations/diagonal_coulomb_hamiltonian_test.py @@ -15,22 +15,18 @@ from openfermion.transforms.opconversions import get_fermion_operator from openfermion.ops.representations import DiagonalCoulombHamiltonian -from openfermion.testing.testing_utils import \ - random_diagonal_coulomb_hamiltonian +from openfermion.testing.testing_utils import random_diagonal_coulomb_hamiltonian class DiagonalCoulombHamiltonianTest(unittest.TestCase): - def test_init(self): - one_body = numpy.array([[2., 3.], [3., 5.]]) - two_body = numpy.array([[7., 11.], [11., 13.]]) - constant = 17. + one_body = numpy.array([[2.0, 3.0], [3.0, 5.0]]) + two_body = numpy.array([[7.0, 11.0], [11.0, 13.0]]) + constant = 17.0 op = DiagonalCoulombHamiltonian(one_body, two_body, constant) - self.assertTrue( - numpy.allclose(op.one_body, numpy.array([[9., 3.], [3., 18.]]))) - self.assertTrue( - numpy.allclose(op.two_body, numpy.array([[0., 11.], [11., 0.]]))) - self.assertAlmostEqual(op.constant, 17.) + self.assertTrue(numpy.allclose(op.one_body, numpy.array([[9.0, 3.0], [3.0, 18.0]]))) + self.assertTrue(numpy.allclose(op.two_body, numpy.array([[0.0, 11.0], [11.0, 0.0]]))) + self.assertAlmostEqual(op.constant, 17.0) def test_multiply(self): n_qubits = 5 @@ -38,20 +34,19 @@ def test_multiply(self): op2 = op1 * 1.5 op3 = 1.5 * op1 self.assertEqual( - get_fermion_operator(op1) * 1.5, get_fermion_operator(op2), - get_fermion_operator(op3)) + get_fermion_operator(op1) * 1.5, get_fermion_operator(op2), get_fermion_operator(op3) + ) def test_divide(self): n_qubits = 5 op1 = random_diagonal_coulomb_hamiltonian(n_qubits) op2 = op1 / 1.5 - self.assertEqual( - get_fermion_operator(op1) / 1.5, get_fermion_operator(op2)) + self.assertEqual(get_fermion_operator(op1) / 1.5, get_fermion_operator(op2)) def test_exceptions(self): - mat1 = numpy.array([[2., 3.], [3., 5.]]) - mat2 = numpy.array([[2., 3. + 1.j], [3 - 1.j, 5.]]) - mat3 = numpy.array([[2., 3.], [4., 5.]]) + mat1 = numpy.array([[2.0, 3.0], [3.0, 5.0]]) + mat2 = numpy.array([[2.0, 3.0 + 1.0j], [3 - 1.0j, 5.0]]) + mat3 = numpy.array([[2.0, 3.0], [4.0, 5.0]]) with self.assertRaises(ValueError): _ = DiagonalCoulombHamiltonian(mat1, mat2) with self.assertRaises(ValueError): diff --git a/src/openfermion/ops/representations/doci_hamiltonian.py b/src/openfermion/ops/representations/doci_hamiltonian.py index 8990012f6..966348e12 100644 --- a/src/openfermion/ops/representations/doci_hamiltonian.py +++ b/src/openfermion/ops/representations/doci_hamiltonian.py @@ -13,8 +13,7 @@ import numpy from openfermion.ops import QubitOperator -from openfermion.ops.representations import (PolynomialTensor, - get_tensors_from_integrals) +from openfermion.ops.representations import PolynomialTensor, get_tensors_from_integrals COEFFICIENT_TYPES = (int, float, complex) @@ -124,8 +123,7 @@ def z_term(self, p): Returns: [QubitOperator] -- Z term on the chosen qubit. """ - return QubitOperator("Z" + str(p), - -self.hc[p] / 2 - sum(self.hr2[:, p]) / 2) + return QubitOperator("Z" + str(p), -self.hc[p] / 2 - sum(self.hr2[:, p]) / 2) def zz_term(self, p, q): """Returns the ZZ term on a single pair of qubits as a QubitOperator @@ -142,30 +140,30 @@ def identity_part(self): in QubitOperator form. """ return QubitOperator( - (), self.constant + numpy.sum(self.hc) / 2 + - numpy.sum(self.hr2) / 4 + numpy.sum(numpy.diag(self.hr2)) / 4) + (), + self.constant + + numpy.sum(self.hc) / 2 + + numpy.sum(self.hr2) / 4 + + numpy.sum(numpy.diag(self.hr2)) / 4, + ) @property def xx_part(self): """Returns the XX part of the QubitOperator representation of this DOCIHamiltonian """ - return sum([ - self.xx_term(p, q) - for p in range(self.n_qubits) - for q in range(p + 1, self.n_qubits) - ]) + return sum( + [self.xx_term(p, q) for p in range(self.n_qubits) for q in range(p + 1, self.n_qubits)] + ) @property def yy_part(self): """Returns the YY part of the QubitOperator representation of this DOCIHamiltonian """ - return sum([ - self.yy_term(p, q) - for p in range(self.n_qubits) - for q in range(p + 1, self.n_qubits) - ]) + return sum( + [self.yy_term(p, q) for p in range(self.n_qubits) for q in range(p + 1, self.n_qubits)] + ) @property def xy_part(self): @@ -179,18 +177,15 @@ def zz_part(self): """Returns the ZZ part of the QubitOperator representation of this DOCIHamiltonian """ - return sum([ - self.zz_term(p, q) - for p in range(self.n_qubits) - for q in range(p + 1, self.n_qubits) - ]) + return sum( + [self.zz_term(p, q) for p in range(self.n_qubits) for q in range(p + 1, self.n_qubits)] + ) @property def z_part(self): """Return the Z and ZZ part of the QubitOperator representation of this DOCI Hamiltonian""" - return self.zz_part + sum( - [self.z_term(p) for p in range(self.n_qubits)]) + return self.zz_part + sum([self.z_term(p) for p in range(self.n_qubits)]) @property def hc(self): @@ -230,41 +225,49 @@ def constant(self, value): # Override base class to generate on the fly @property def n_body_tensors(self): - one_body_coefficients, two_body_coefficients =\ - get_tensors_from_doci(self.hc, self.hr1, self.hr2) + one_body_coefficients, two_body_coefficients = get_tensors_from_doci( + self.hc, self.hr1, self.hr2 + ) n_body_tensors = { (): self.constant, (1, 0): one_body_coefficients, - (1, 1, 0, 0): two_body_coefficients + (1, 1, 0, 0): two_body_coefficients, } return n_body_tensors @n_body_tensors.setter def n_body_tensors(self, value): - raise TypeError('Raw edits of the n_body_tensors of a DOCIHamiltonian ' - 'is not allowed. Either adjust the hc/hr1/hr2 terms ' - 'or cast to another PolynomialTensor class.') + raise TypeError( + 'Raw edits of the n_body_tensors of a DOCIHamiltonian ' + 'is not allowed. Either adjust the hc/hr1/hr2 terms ' + 'or cast to another PolynomialTensor class.' + ) def _get_onebody_term(self, key, index): if key[0] != 1 or key[1] != 0: - raise IndexError('DOCIHamiltonian class only contains ' - 'one-body terms in the (1, 0) sector.') + raise IndexError( + 'DOCIHamiltonian class only contains ' 'one-body terms in the (1, 0) sector.' + ) if index[0] != index[1]: - raise IndexError('DOCIHamiltonian class only contains ' - 'diagonal one-body electron integrals.') + raise IndexError( + 'DOCIHamiltonian class only contains ' 'diagonal one-body electron integrals.' + ) return self.hc[index[0] // 2] / 2 def _get_twobody_term(self, key, index): if key[0] != 1 or key[1] != 1 or key[2] != 0 or key[3] != 0: - raise IndexError('DOCIHamiltonian class only contains ' - 'two-body terms in the (1, 1, 0, 0) sector.') + raise IndexError( + 'DOCIHamiltonian class only contains ' 'two-body terms in the (1, 1, 0, 0) sector.' + ) if index[0] == index[3] and index[1] == index[2]: return self.hr2[index[0] // 2, index[1] // 2] / 2 if index[0] // 2 == index[1] // 2 and index[2] // 2 == index[3] // 2: return self.hr1[index[0] // 2, index[2] // 2] / 2 - raise IndexError('DOCIHamiltonian class only contains ' - 'two-electron integrals corresponding ' - 'to a double excitation.') + raise IndexError( + 'DOCIHamiltonian class only contains ' + 'two-electron integrals corresponding ' + 'to a double excitation.' + ) # Override base class def __getitem__(self, args): @@ -284,8 +287,9 @@ def __getitem__(self, args): return self._get_onebody_term(key, index) if len(index) == 4: return self._get_twobody_term(key, index) - raise IndexError('DOCIHamiltonian class only contains ' - 'one and two-electron and constant terms.') + raise IndexError( + 'DOCIHamiltonian class only contains ' 'one and two-electron and constant terms.' + ) # Override root class def __setitem__(self, args, value): @@ -294,9 +298,11 @@ def __setitem__(self, args, value): # make the user update the hr1/hr2/hc terms --- if they really # want to play around with the n_body_tensors here they should # be casting this to a raw PolynomialTensor. - raise TypeError('Raw edits of the n_body_tensors of a DOCIHamiltonian ' - 'is not allowed. Either adjust the hc/hr1/hr2 terms ' - 'or cast to another PolynomialTensor class.') + raise TypeError( + 'Raw edits of the n_body_tensors of a DOCIHamiltonian ' + 'is not allowed. Either adjust the hc/hr1/hr2 terms ' + 'or cast to another PolynomialTensor class.' + ) # Override root class def __iadd__(self, addend): @@ -347,18 +353,20 @@ def __itruediv__(self, dividend): @classmethod def from_integrals(cls, constant, one_body_integrals, two_body_integrals): - hc, hr1, hr2 = get_doci_from_integrals(one_body_integrals, - two_body_integrals) + hc, hr1, hr2 = get_doci_from_integrals(one_body_integrals, two_body_integrals) return cls(constant, hc, hr1, hr2) @classmethod def zero(cls, n_qubits): - return cls(0, numpy.zeros((n_qubits,), dtype=numpy.complex128), - numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128), - numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128)) + return cls( + 0, + numpy.zeros((n_qubits,), dtype=numpy.complex128), + numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128), + numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128), + ) def get_projected_integrals(self): - ''' Creates the one and two body integrals that would correspond to a + '''Creates the one and two body integrals that would correspond to a hypothetic electronic structure Hamiltonian, which would satisfy the given set of hc, hr1 and hr2. @@ -396,8 +404,9 @@ def get_projected_integrals(self): projected_twobody_integrals [numpy array]: The corresponding two body integrals for the electronic structure Hamiltonian ''' - one_body_integrals, two_body_integrals = \ - get_projected_integrals_from_doci(self.hc, self.hr1, self.hr2) + one_body_integrals, two_body_integrals = get_projected_integrals_from_doci( + self.hc, self.hr1, self.hr2 + ) return one_body_integrals, two_body_integrals @@ -417,13 +426,12 @@ def get_tensors_from_doci(hc, hr1, hr2): two_body_coefficients [numpy array]: The corresponding two body tensor for the electronic structure Hamiltonian ''' - one_body_integrals, two_body_integrals =\ - get_projected_integrals_from_doci(hc, hr1, hr2) + one_body_integrals, two_body_integrals = get_projected_integrals_from_doci(hc, hr1, hr2) one_body_coefficients, two_body_coefficients = get_tensors_from_integrals( - one_body_integrals, two_body_integrals) + one_body_integrals, two_body_integrals + ) - two_body_coefficients = two_body_coefficients - numpy.einsum( - 'ijlk', two_body_coefficients) + two_body_coefficients = two_body_coefficients - numpy.einsum('ijlk', two_body_coefficients) return one_body_coefficients, two_body_coefficients @@ -458,10 +466,10 @@ def get_projected_integrals_from_doci(hc, hr1, hr2): integrals for the electronic structure Hamiltonian ''' n_qubits = hr1.shape[0] - projected_onebody_integrals = numpy.zeros((n_qubits, n_qubits), - dtype=hc.dtype) + projected_onebody_integrals = numpy.zeros((n_qubits, n_qubits), dtype=hc.dtype) projected_twobody_integrals = numpy.zeros( - (n_qubits, n_qubits, n_qubits, n_qubits), dtype=hc.dtype) + (n_qubits, n_qubits, n_qubits, n_qubits), dtype=hc.dtype + ) for p in range(n_qubits): projected_onebody_integrals[p, p] = hc[p] / 2 projected_twobody_integrals[p, p, p, p] = hr2[p, p] @@ -469,10 +477,8 @@ def get_projected_integrals_from_doci(hc, hr1, hr2): if p <= q: continue - projected_twobody_integrals[p, q, q, - p] = hr2[p, q] / 2 + hr1[p, q] / 2 - projected_twobody_integrals[q, p, p, - q] = hr2[q, p] / 2 + hr1[p, q] / 2 + projected_twobody_integrals[p, q, q, p] = hr2[p, q] / 2 + hr1[p, q] / 2 + projected_twobody_integrals[q, p, p, q] = hr2[q, p] / 2 + hr1[p, q] / 2 projected_twobody_integrals[p, p, q, q] += hr1[p, q] projected_twobody_integrals[p, q, p, q] += hr1[p, q] @@ -504,8 +510,7 @@ def get_doci_from_integrals(one_body_integrals, two_body_integrals): for p in range(n_qubits): hc[p] = 2 * one_body_integrals[p, p] for q in range(n_qubits): - hr2[p, q] = (2 * two_body_integrals[p, q, q, p] - - two_body_integrals[p, q, p, q]) + hr2[p, q] = 2 * two_body_integrals[p, q, q, p] - two_body_integrals[p, q, p, q] if p == q: continue hr1[p, q] = two_body_integrals[p, p, q, q] diff --git a/src/openfermion/ops/representations/doci_hamiltonian_test.py b/src/openfermion/ops/representations/doci_hamiltonian_test.py index 3b6dd88b6..d739f1e8c 100644 --- a/src/openfermion/ops/representations/doci_hamiltonian_test.py +++ b/src/openfermion/ops/representations/doci_hamiltonian_test.py @@ -22,35 +22,34 @@ from openfermion.transforms import jordan_wigner from openfermion.linalg import get_sparse_operator from openfermion.ops.representations.doci_hamiltonian import ( - DOCIHamiltonian, get_tensors_from_doci, get_projected_integrals_from_doci, - get_doci_from_integrals) -from openfermion import (get_fermion_operator, InteractionOperator, \ - normal_ordered) + DOCIHamiltonian, + get_tensors_from_doci, + get_projected_integrals_from_doci, + get_doci_from_integrals, +) +from openfermion import get_fermion_operator, InteractionOperator, normal_ordered numpy.set_printoptions(linewidth=2000, threshold=sys.maxsize) class IntegralTransformsTest(unittest.TestCase): - def setUp(self): - self.geometry = [('H', (0., 0., 0.)), ('Li', (0., 0., 1.45))] + self.geometry = [('H', (0.0, 0.0, 0.0)), ('Li', (0.0, 0.0, 1.45))] self.basis = 'sto-3g' self.multiplicity = 1 - self.filename = os.path.join(DATA_DIRECTORY, - 'H1-Li1_sto-3g_singlet_1.45') - self.molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - filename=self.filename) + self.filename = os.path.join(DATA_DIRECTORY, 'H1-Li1_sto-3g_singlet_1.45') + self.molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, filename=self.filename + ) self.molecule.load() def test_integrals_self_inverse(self): - hc, hr1, hr2 = get_doci_from_integrals(self.molecule.one_body_integrals, - self.molecule.two_body_integrals) + hc, hr1, hr2 = get_doci_from_integrals( + self.molecule.one_body_integrals, self.molecule.two_body_integrals + ) doci = DOCIHamiltonian(0, hc, hr1, hr2) proj_one_body, proj_two_body = doci.get_projected_integrals() - hc_test, hr1_test, hr2_test = get_doci_from_integrals( - proj_one_body, proj_two_body) + hc_test, hr1_test, hr2_test = get_doci_from_integrals(proj_one_body, proj_two_body) self.assertTrue(numpy.allclose(hc, hc_test)) self.assertTrue(numpy.allclose(hr1, hr1_test)) self.assertTrue(numpy.allclose(hr2, hr2_test)) @@ -58,48 +57,46 @@ def test_integrals_self_inverse(self): def test_fermionic_hamiltonian_from_integrals(self): constant = self.molecule.nuclear_repulsion doci_constant = constant - hc, hr1, hr2 = get_doci_from_integrals(self.molecule.one_body_integrals, - self.molecule.two_body_integrals) + hc, hr1, hr2 = get_doci_from_integrals( + self.molecule.one_body_integrals, self.molecule.two_body_integrals + ) doci = DOCIHamiltonian(doci_constant, hc, hr1, hr2) doci_qubit_op = doci.qubit_operator doci_mat = get_sparse_operator(doci_qubit_op).toarray() - #doci_eigvals, doci_eigvecs = numpy.linalg.eigh(doci_mat) + # doci_eigvals, doci_eigvecs = numpy.linalg.eigh(doci_mat) doci_eigvals, _ = numpy.linalg.eigh(doci_mat) tensors = doci.n_body_tensors - one_body_tensors, two_body_tensors = tensors[(1, 0)], tensors[(1, 1, 0, - 0)] + one_body_tensors, two_body_tensors = tensors[(1, 0)], tensors[(1, 1, 0, 0)] fermion_op1 = get_fermion_operator( - InteractionOperator(constant, one_body_tensors, - 0. * two_body_tensors)) + InteractionOperator(constant, one_body_tensors, 0.0 * two_body_tensors) + ) fermion_op2 = get_fermion_operator( - InteractionOperator(0, 0 * one_body_tensors, - 0.5 * two_body_tensors)) + InteractionOperator(0, 0 * one_body_tensors, 0.5 * two_body_tensors) + ) import openfermion as of + fermion_op1_jw = of.transforms.jordan_wigner(fermion_op1) fermion_op2_jw = of.transforms.jordan_wigner(fermion_op2) fermion_op_jw = fermion_op1_jw + fermion_op2_jw - #fermion_eigvals, fermion_eigvecs = numpy.linalg.eigh( + # fermion_eigvals, fermion_eigvecs = numpy.linalg.eigh( # get_sparse_operator(fermion_op_jw).toarray()) - fermion_eigvals, _ = numpy.linalg.eigh( - get_sparse_operator(fermion_op_jw).toarray()) + fermion_eigvals, _ = numpy.linalg.eigh(get_sparse_operator(fermion_op_jw).toarray()) for eigval in doci_eigvals: - assert any(abs(fermion_eigvals - - eigval) < 1e-6), "The DOCI spectrum should \ + assert any( + abs(fermion_eigvals - eigval) < 1e-6 + ), "The DOCI spectrum should \ have been contained in the spectrum of the fermionic operators" - fermion_diagonal = get_sparse_operator( - fermion_op_jw).toarray().diagonal() + fermion_diagonal = get_sparse_operator(fermion_op_jw).toarray().diagonal() qubit_diagonal = doci_mat.diagonal() - assert numpy.isclose( - fermion_diagonal[0], qubit_diagonal[0] - ) and numpy.isclose( + assert numpy.isclose(fermion_diagonal[0], qubit_diagonal[0]) and numpy.isclose( fermion_diagonal[-1], qubit_diagonal[-1] ), "The first and last elements of hte qubit and fermionic diagonal of \ the Hamiltonian maxtrix should be the same as the vaccum should be \ @@ -112,8 +109,7 @@ def test_fermionic_hamiltonian_from_integrals(self): def test_integrals_to_doci(self): one_body_integrals = self.molecule.one_body_integrals two_body_integrals = self.molecule.two_body_integrals - hc, hr1, hr2 = get_doci_from_integrals(one_body_integrals, - two_body_integrals) + hc, hr1, hr2 = get_doci_from_integrals(one_body_integrals, two_body_integrals) n_qubits = one_body_integrals.shape[0] self.assertEqual(hc.shape, (n_qubits,)) self.assertEqual(hr1.shape, (n_qubits, n_qubits)) @@ -121,27 +117,26 @@ def test_integrals_to_doci(self): for p in range(2): self.assertEqual( - hc[p] + hr2[p, p], - 2 * one_body_integrals[p, p] + two_body_integrals[p, p, p, p]) + hc[p] + hr2[p, p], 2 * one_body_integrals[p, p] + two_body_integrals[p, p, p, p] + ) for q in range(2): if p != q: self.assertEqual(hr1[p, q], two_body_integrals[p, p, q, q]) self.assertEqual( - hr2[p, q], 2 * two_body_integrals[p, q, q, p] - - two_body_integrals[p, q, p, q]) + hr2[p, q], + 2 * two_body_integrals[p, q, q, p] - two_body_integrals[p, q, p, q], + ) class DOCIHamiltonianTest(unittest.TestCase): - def setUp(self): - self.geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + self.geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] self.basis = 'sto-3g' self.multiplicity = 1 self.filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(self.geometry, - self.basis, - self.multiplicity, - filename=self.filename) + self.molecule = MolecularData( + self.geometry, self.basis, self.multiplicity, filename=self.filename + ) self.molecule.load() def test_n_body_tensor_errors(self): @@ -185,18 +180,18 @@ def test_basic_operations(self): doci_hamiltonian2 = DOCIHamiltonian.from_integrals( constant=self.molecule.nuclear_repulsion, one_body_integrals=self.molecule.one_body_integrals, - two_body_integrals=self.molecule.two_body_integrals) - self.assertTrue(doci_hamiltonian2 == doci_hamiltonian1 + - doci_hamiltonian2) - self.assertTrue(doci_hamiltonian1 - - doci_hamiltonian2 == doci_hamiltonian2 / -1) + two_body_integrals=self.molecule.two_body_integrals, + ) + self.assertTrue(doci_hamiltonian2 == doci_hamiltonian1 + doci_hamiltonian2) + self.assertTrue(doci_hamiltonian1 - doci_hamiltonian2 == doci_hamiltonian2 / -1) self.assertTrue(doci_hamiltonian2 * 0 == doci_hamiltonian1) def test_error(self): doci_hamiltonian = DOCIHamiltonian.from_integrals( constant=self.molecule.nuclear_repulsion, one_body_integrals=self.molecule.one_body_integrals, - two_body_integrals=self.molecule.two_body_integrals) + two_body_integrals=self.molecule.two_body_integrals, + ) with self.assertRaises(TypeError): doci_hamiltonian[((1, 0), (0, 1))] = 1 with self.assertRaises(IndexError): @@ -246,11 +241,11 @@ def test_from_integrals_to_qubit(self): doci_hamiltonian = DOCIHamiltonian.from_integrals( constant=self.molecule.nuclear_repulsion, one_body_integrals=self.molecule.one_body_integrals, - two_body_integrals=self.molecule.two_body_integrals).qubit_operator + two_body_integrals=self.molecule.two_body_integrals, + ).qubit_operator hamiltonian_matrix = get_sparse_operator(hamiltonian).toarray() - doci_hamiltonian_matrix = get_sparse_operator( - doci_hamiltonian).toarray() + doci_hamiltonian_matrix = get_sparse_operator(doci_hamiltonian).toarray() diagonal = numpy.real(numpy.diag(hamiltonian_matrix)) doci_diagonal = numpy.real(numpy.diag(doci_hamiltonian_matrix)) position_of_doci_diag_in_h = [0] * len(doci_diagonal) @@ -258,21 +253,28 @@ def test_from_integrals_to_qubit(self): closest_in_diagonal = None for idx2, eig in enumerate(diagonal): if closest_in_diagonal is None or abs(eig - doci_eigval) < abs( - closest_in_diagonal - doci_eigval): + closest_in_diagonal - doci_eigval + ): closest_in_diagonal = eig position_of_doci_diag_in_h[idx] = idx2 assert abs(closest_in_diagonal - doci_eigval) < EQ_TOLERANCE, ( - "Value " + str(doci_eigval) + " of the DOCI Hamiltonian " + - "diagonal did not appear in the diagonal of the full " + - "Hamiltonian. The closest value was " + - str(closest_in_diagonal)) - - sub_matrix = hamiltonian_matrix[numpy.ix_(position_of_doci_diag_in_h, - position_of_doci_diag_in_h)] + "Value " + + str(doci_eigval) + + " of the DOCI Hamiltonian " + + "diagonal did not appear in the diagonal of the full " + + "Hamiltonian. The closest value was " + + str(closest_in_diagonal) + ) + + sub_matrix = hamiltonian_matrix[ + numpy.ix_(position_of_doci_diag_in_h, position_of_doci_diag_in_h) + ] assert numpy.allclose(doci_hamiltonian_matrix, sub_matrix), ( - "The coupling between the DOCI states in the DOCI Hamiltonian " + - "should be identical to that between these states in the full " + - "Hamiltonian but the DOCI hamiltonian matrix\n" + - str(doci_hamiltonian_matrix) + - "\ndoes not match the corresponding sub-matrix of the full " + - "Hamiltonian\n" + str(sub_matrix)) + "The coupling between the DOCI states in the DOCI Hamiltonian " + + "should be identical to that between these states in the full " + + "Hamiltonian but the DOCI hamiltonian matrix\n" + + str(doci_hamiltonian_matrix) + + "\ndoes not match the corresponding sub-matrix of the full " + + "Hamiltonian\n" + + str(sub_matrix) + ) diff --git a/src/openfermion/ops/representations/interaction_operator.py b/src/openfermion/ops/representations/interaction_operator.py index 45d337c0f..32a384eeb 100644 --- a/src/openfermion/ops/representations/interaction_operator.py +++ b/src/openfermion/ops/representations/interaction_operator.py @@ -64,11 +64,9 @@ def __init__(self, constant, one_body_tensor, two_body_tensor): n_qubits numpy array of floats. """ # Make sure nonzero elements are only for normal ordered terms. - super(InteractionOperator, self).__init__({ - (): constant, - (1, 0): one_body_tensor, - (1, 1, 0, 0): two_body_tensor - }) + super(InteractionOperator, self).__init__( + {(): constant, (1, 0): one_body_tensor, (1, 1, 0, 0): two_body_tensor} + ) @property def one_body_tensor(self): @@ -126,18 +124,23 @@ def unique_iter(self, complex_valued=False): @classmethod def zero(cls, n_qubits): - return cls(0, numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128), - numpy.zeros((n_qubits,) * 4, dtype=numpy.complex128)) + return cls( + 0, + numpy.zeros((n_qubits,) * 2, dtype=numpy.complex128), + numpy.zeros((n_qubits,) * 4, dtype=numpy.complex128), + ) def projected(self, indices, exact=False): projected_n_body_tensors = self.projected_n_body_tensors(indices, exact) - return type(self)(*(projected_n_body_tensors[key] - for key in [(), (1, 0), (1, 1, 0, 0)])) + return type(self)(*(projected_n_body_tensors[key] for key in [(), (1, 0), (1, 1, 0, 0)])) def with_function_applied_elementwise(self, func): - return type(self)(*( - func(tensor) for tensor in - [self.constant, self.one_body_tensor, self.two_body_tensor])) + return type(self)( + *( + func(tensor) + for tensor in [self.constant, self.one_body_tensor, self.two_body_tensor] + ) + ) def _symmetric_two_body_terms(quad, complex_valued): @@ -167,50 +170,42 @@ def get_tensors_from_integrals(one_body_integrals, two_body_integrals): # Initialize Hamiltonian coefficients. one_body_coefficients = numpy.zeros((n_qubits, n_qubits)) - two_body_coefficients = numpy.zeros( - (n_qubits, n_qubits, n_qubits, n_qubits)) + two_body_coefficients = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits)) # Loop through integrals. for p in range(n_qubits // 2): for q in range(n_qubits // 2): - # Populate 1-body coefficients. Require p and q have same spin. one_body_coefficients[2 * p, 2 * q] = one_body_integrals[p, q] - one_body_coefficients[2 * p + 1, 2 * q + - 1] = one_body_integrals[p, q] + one_body_coefficients[2 * p + 1, 2 * q + 1] = one_body_integrals[p, q] # Continue looping to prepare 2-body coefficients. for r in range(n_qubits // 2): for s in range(n_qubits // 2): - # Mixed spin - two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * - s] = (two_body_integrals[p, q, r, s] / - 2.) - two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + - 1] = (two_body_integrals[p, q, r, s] / - 2.) + two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) + two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) # Same spin - two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * - s] = (two_body_integrals[p, q, r, s] / - 2.) - two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + - 1, 2 * s + - 1] = (two_body_integrals[p, q, r, s] / - 2.) + two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) + two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1] = ( + two_body_integrals[p, q, r, s] / 2.0 + ) # Truncate. - one_body_coefficients[ - numpy.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0. - two_body_coefficients[ - numpy.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0. + one_body_coefficients[numpy.absolute(one_body_coefficients) < EQ_TOLERANCE] = 0.0 + two_body_coefficients[numpy.absolute(two_body_coefficients) < EQ_TOLERANCE] = 0.0 return one_body_coefficients, two_body_coefficients -def get_active_space_integrals(one_body_integrals, - two_body_integrals, - occupied_indices=None, - active_indices=None): +def get_active_space_integrals( + one_body_integrals, two_body_integrals, occupied_indices=None, active_indices=None +): """Restricts a molecule at a spatial orbital level to an active space This active space may be defined by a list of active indices and @@ -240,7 +235,7 @@ def get_active_space_integrals(one_body_integrals, """ # Fix data type for a few edge cases occupied_indices = [] if occupied_indices is None else occupied_indices - if (len(active_indices) < 1): + if len(active_indices) < 1: raise ValueError('Some active indices required for reduction.') # Determine core constant @@ -248,8 +243,7 @@ def get_active_space_integrals(one_body_integrals, for i in occupied_indices: core_constant += 2 * one_body_integrals[i, i] for j in occupied_indices: - core_constant += (2 * two_body_integrals[i, j, j, i] - - two_body_integrals[i, j, i, j]) + core_constant += 2 * two_body_integrals[i, j, j, i] - two_body_integrals[i, j, i, j] # Modified one electron integrals one_body_integrals_new = numpy.copy(one_body_integrals) @@ -257,11 +251,14 @@ def get_active_space_integrals(one_body_integrals, for v in active_indices: for i in occupied_indices: one_body_integrals_new[u, v] += ( - 2 * two_body_integrals[i, u, v, i] - - two_body_integrals[i, u, i, v]) + 2 * two_body_integrals[i, u, v, i] - two_body_integrals[i, u, i, v] + ) # Restrict integral ranges and change M appropriately - return (core_constant, - one_body_integrals_new[numpy.ix_(active_indices, active_indices)], - two_body_integrals[numpy.ix_(active_indices, active_indices, - active_indices, active_indices)]) + return ( + core_constant, + one_body_integrals_new[numpy.ix_(active_indices, active_indices)], + two_body_integrals[ + numpy.ix_(active_indices, active_indices, active_indices, active_indices) + ], + ) diff --git a/src/openfermion/ops/representations/interaction_operator_test.py b/src/openfermion/ops/representations/interaction_operator_test.py index d66453393..e22cd05d2 100644 --- a/src/openfermion/ops/representations/interaction_operator_test.py +++ b/src/openfermion/ops/representations/interaction_operator_test.py @@ -21,29 +21,25 @@ class ActiveSpaceIntegralsTest(unittest.TestCase): - def test_raises_error(self): with self.assertRaises(ValueError): _ = get_active_space_integrals(None, None, [], []) class InteractionOperatorTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 - self.constant = 0. + self.constant = 0.0 self.one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) self.two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) - self.interaction_operator = InteractionOperator(self.constant, - self.one_body, - self.two_body) + (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float + ) + self.interaction_operator = InteractionOperator(self.constant, self.one_body, self.two_body) def test_four_point_iter(self): constant = 100.0 one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) one_body[1, 1] = 10.0 one_body[2, 3] = 11.0 one_body[3, 2] = 11.0 @@ -62,8 +58,7 @@ def test_four_point_iter(self): def test_eight_point_iter(self): constant = 100.0 one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) one_body[1, 1] = 10.0 one_body[2, 3] = 11.0 one_body[3, 2] = 11.0 @@ -84,60 +79,50 @@ def test_eight_point_iter(self): self.assertEqual(want_str, got_str) def test_addition(self): - interaction_op = InteractionOperator(0, numpy.ones((3, 3)), - numpy.ones((3, 3, 3, 3))) + interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) summed_op = interaction_op + interaction_op self.assertTrue( - numpy.array_equal(summed_op.one_body_tensor, - summed_op.n_body_tensors[(1, 0)])) + numpy.array_equal(summed_op.one_body_tensor, summed_op.n_body_tensors[(1, 0)]) + ) self.assertTrue( - numpy.array_equal(summed_op.two_body_tensor, - summed_op.n_body_tensors[(1, 1, 0, 0)])) + numpy.array_equal(summed_op.two_body_tensor, summed_op.n_body_tensors[(1, 1, 0, 0)]) + ) def test_neg(self): - interaction_op = InteractionOperator(0, numpy.ones((3, 3)), - numpy.ones((3, 3, 3, 3))) + interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) neg_interaction_op = -interaction_op assert isinstance(neg_interaction_op, InteractionOperator) assert neg_interaction_op == InteractionOperator( - 0, -numpy.ones((3, 3)), -numpy.ones((3, 3, 3, 3))) + 0, -numpy.ones((3, 3)), -numpy.ones((3, 3, 3, 3)) + ) def test_zero(self): - interaction_op = InteractionOperator(0, numpy.ones((3, 3)), - numpy.ones((3, 3, 3, 3))) - assert InteractionOperator.zero( - interaction_op.n_qubits) == interaction_op * 0 + interaction_op = InteractionOperator(0, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) + assert InteractionOperator.zero(interaction_op.n_qubits) == interaction_op * 0 def test_projected(self): - interaction_op = InteractionOperator(1, numpy.ones((3, 3)), - numpy.ones((3, 3, 3, 3))) - projected_two_body_tensor = numpy.zeros_like( - interaction_op.two_body_tensor) + interaction_op = InteractionOperator(1, numpy.ones((3, 3)), numpy.ones((3, 3, 3, 3))) + projected_two_body_tensor = numpy.zeros_like(interaction_op.two_body_tensor) for i in range(3): projected_two_body_tensor[(i,) * 4] = 1 - projected_op = InteractionOperator(0, numpy.eye(3), - projected_two_body_tensor) + projected_op = InteractionOperator(0, numpy.eye(3), projected_two_body_tensor) assert projected_op == interaction_op.projected(1, exact=True) projected_op.constant = 1 assert projected_op == interaction_op.projected(1, exact=False) - projected_op.one_body_tensor = numpy.zeros_like( - interaction_op.one_body_tensor) + projected_op.one_body_tensor = numpy.zeros_like(interaction_op.one_body_tensor) for pq in itertools.product(range(2), repeat=2): projected_op.one_body_tensor[pq] = 1 - projected_op.two_body_tensor = numpy.zeros_like( - interaction_op.two_body_tensor) + projected_op.two_body_tensor = numpy.zeros_like(interaction_op.two_body_tensor) for pqrs in itertools.product(range(2), repeat=4): projected_op.two_body_tensor[pqrs] = 1 assert projected_op == interaction_op.projected((0, 1), exact=False) projected_op.constant = 0 - projected_op.one_body_tensor = numpy.zeros_like( - interaction_op.one_body_tensor) + projected_op.one_body_tensor = numpy.zeros_like(interaction_op.one_body_tensor) projected_op.one_body_tensor[0, 1] = 1 projected_op.one_body_tensor[1, 0] = 1 - projected_op.two_body_tensor = numpy.zeros_like( - interaction_op.two_body_tensor) + projected_op.two_body_tensor = numpy.zeros_like(interaction_op.two_body_tensor) for pqrs in itertools.product(range(2), repeat=4): if len(set(pqrs)) > 1: projected_op.two_body_tensor[pqrs] = 1 diff --git a/src/openfermion/ops/representations/interaction_rdm.py b/src/openfermion/ops/representations/interaction_rdm.py index 2cdcff25e..583894a6a 100644 --- a/src/openfermion/ops/representations/interaction_rdm.py +++ b/src/openfermion/ops/representations/interaction_rdm.py @@ -14,8 +14,7 @@ import numpy from openfermion.ops.operators import FermionOperator, QubitOperator -from openfermion.ops.representations import (InteractionOperator, - PolynomialTensor) +from openfermion.ops.representations import InteractionOperator, PolynomialTensor class InteractionRDMError(Exception): @@ -39,10 +38,9 @@ def __init__(self, one_body_tensor, two_body_tensor): two_body_tensor: Expectation values . """ - super(InteractionRDM, self).__init__({ - (1, 0): one_body_tensor, - (1, 1, 0, 0): two_body_tensor - }) + super(InteractionRDM, self).__init__( + {(1, 0): one_body_tensor, (1, 1, 0, 0): two_body_tensor} + ) @property def one_body_tensor(self): @@ -80,14 +78,11 @@ def expectation(self, operator): expectation_op = self.get_qubit_expectations(operator) expectation = 0.0 for qubit_term in operator.terms: - expectation += (operator.terms[qubit_term] * - expectation_op.terms[qubit_term]) + expectation += operator.terms[qubit_term] * expectation_op.terms[qubit_term] elif isinstance(operator, InteractionOperator): expectation = operator.constant - expectation += numpy.sum(self.one_body_tensor * - operator.one_body_tensor) - expectation += numpy.sum(self.two_body_tensor * - operator.two_body_tensor) + expectation += numpy.sum(self.one_body_tensor * operator.one_body_tensor) + expectation += numpy.sum(self.two_body_tensor * operator.two_body_tensor) else: raise InteractionRDMError('Invalid operator type provided.') return expectation @@ -107,25 +102,22 @@ def get_qubit_expectations(self, qubit_operator): InteractionRDMError: Observable not contained in 1-RDM or 2-RDM. """ # Importing here instead of head of file to prevent circulars - from openfermion.transforms.opconversions import (reverse_jordan_wigner, - normal_ordered) + from openfermion.transforms.opconversions import reverse_jordan_wigner, normal_ordered + qubit_operator_expectations = copy.deepcopy(qubit_operator) for qubit_term in qubit_operator_expectations.terms: - expectation = 0. + expectation = 0.0 # Map qubits back to fermions. - reversed_fermion_operators = reverse_jordan_wigner( - QubitOperator(qubit_term)) - reversed_fermion_operators = normal_ordered( - reversed_fermion_operators) + reversed_fermion_operators = reverse_jordan_wigner(QubitOperator(qubit_term)) + reversed_fermion_operators = normal_ordered(reversed_fermion_operators) # Loop through fermion terms. for fermion_term in reversed_fermion_operators.terms: coefficient = reversed_fermion_operators.terms[fermion_term] # Handle molecular term. - if FermionOperator( - fermion_term).is_two_body_number_conserving(): + if FermionOperator(fermion_term).is_two_body_number_conserving(): if not fermion_term: expectation += coefficient else: @@ -141,7 +133,6 @@ def get_qubit_expectations(self, qubit_operator): # Handle non-molecular terms. elif len(fermion_term) > 4: - raise InteractionRDMError('Observable not contained ' - 'in 1-RDM or 2-RDM.') + raise InteractionRDMError('Observable not contained ' 'in 1-RDM or 2-RDM.') qubit_operator_expectations.terms[qubit_term] = expectation return qubit_operator_expectations diff --git a/src/openfermion/ops/representations/interaction_rdm_test.py b/src/openfermion/ops/representations/interaction_rdm_test.py index eb57b3103..4aff16f28 100644 --- a/src/openfermion/ops/representations/interaction_rdm_test.py +++ b/src/openfermion/ops/representations/interaction_rdm_test.py @@ -22,16 +22,12 @@ class InteractionRDMTest(unittest.TestCase): - def setUp(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() self.cisd_energy = self.molecule.cisd_energy self.rdm = self.molecule.get_molecular_rdm() @@ -44,8 +40,7 @@ def test_get_qubit_expectations(self): test_energy = 0.0 for qubit_term in qubit_expectations.terms: term_coefficient = qubit_operator.terms[qubit_term] - test_energy += (term_coefficient * - qubit_expectations.terms[qubit_term]) + test_energy += term_coefficient * qubit_expectations.terms[qubit_term] self.assertLess(abs(test_energy - self.cisd_energy), EQ_TOLERANCE) def test_get_qubit_expectations_nonmolecular_term(self): @@ -68,22 +63,16 @@ def test_expectation_bad_type(self): def test_addition(self): rdm2 = self.rdm + self.rdm - self.assertTrue( - numpy.array_equal(rdm2.one_body_tensor, - rdm2.n_body_tensors[(1, 0)])) - self.assertTrue( - numpy.array_equal(rdm2.two_body_tensor, - rdm2.n_body_tensors[(1, 1, 0, 0)])) + self.assertTrue(numpy.array_equal(rdm2.one_body_tensor, rdm2.n_body_tensors[(1, 0)])) + self.assertTrue(numpy.array_equal(rdm2.two_body_tensor, rdm2.n_body_tensors[(1, 1, 0, 0)])) def test_rdm_setters(self): temp_rdm = self.molecule.get_molecular_rdm() one_body_tensor_test = numpy.eye(4) temp_rdm.one_body_tensor = one_body_tensor_test - self.assertTrue( - numpy.array_equal(temp_rdm.n_body_tensors[(1, 0)], - one_body_tensor_test)) + self.assertTrue(numpy.array_equal(temp_rdm.n_body_tensors[(1, 0)], one_body_tensor_test)) two_body_tensor_test = numpy.zeros([4, 4, 4, 4]) temp_rdm.two_body_tensor = two_body_tensor_test self.assertTrue( - numpy.array_equal(temp_rdm.n_body_tensors[(1, 1, 0, 0)], - two_body_tensor_test)) + numpy.array_equal(temp_rdm.n_body_tensors[(1, 1, 0, 0)], two_body_tensor_test) + ) diff --git a/src/openfermion/ops/representations/polynomial_tensor.py b/src/openfermion/ops/representations/polynomial_tensor.py index 0d06bebbf..a2908d95a 100644 --- a/src/openfermion/ops/representations/polynomial_tensor.py +++ b/src/openfermion/ops/representations/polynomial_tensor.py @@ -78,21 +78,17 @@ def general_basis_change(general_tensor, rotation_matrix, key): subscripts_first = ''.join(chr(ord('a') + i) for i in range(order)) # The 'Aa,Bb,Cc,Dd' part of the subscripts - subscripts_rest = ','.join( - chr(ord('a') + i) + chr(ord('A') + i) for i in range(order)) + subscripts_rest = ','.join(chr(ord('a') + i) + chr(ord('A') + i) for i in range(order)) subscripts = subscripts_first + ',' + subscripts_rest # The list of rotation matrices, conjugated as necessary. - rotation_matrices = [ - rotation_matrix.conj() if x else rotation_matrix for x in key - ] + rotation_matrices = [rotation_matrix.conj() if x else rotation_matrix for x in key] # "optimize = True" does greedy optimization, which will be enough here. - transformed_general_tensor = numpy.einsum(subscripts, - general_tensor, - *rotation_matrices, - optimize=True) + transformed_general_tensor = numpy.einsum( + subscripts, general_tensor, *rotation_matrices, optimize=True + ) return transformed_general_tensor @@ -198,17 +194,16 @@ def __eq__(self, other): if self.n_qubits != other.n_qubits: return False - diff = 0. + diff = 0.0 self_keys = set(self.n_body_tensors.keys()) other_keys = set(other.n_body_tensors.keys()) - for key in (self_keys | other_keys): + for key in self_keys | other_keys: self_tensor = self.n_body_tensors.get(key) other_tensor = other.n_body_tensors.get(key) if self_tensor is not None and other_tensor is not None: - discrepancy = numpy.amax( - numpy.absolute(self_tensor - other_tensor)) + discrepancy = numpy.amax(numpy.absolute(self_tensor - other_tensor)) else: tensor = self_tensor if other_tensor is None else other_tensor discrepancy = numpy.amax(numpy.absolute(tensor)) @@ -221,7 +216,6 @@ def __ne__(self, other): return not (self == other) def __iadd__(self, addend): - if isinstance(addend, COEFFICIENT_TYPES): self.constant += addend return self @@ -234,8 +228,9 @@ def __iadd__(self, addend): for key in addend.n_body_tensors: if key in self.n_body_tensors: - self.n_body_tensors[key] = numpy.add(self.n_body_tensors[key], - addend.n_body_tensors[key]) + self.n_body_tensors[key] = numpy.add( + self.n_body_tensors[key], addend.n_body_tensors[key] + ) else: self.n_body_tensors[key] = addend.n_body_tensors[key] @@ -262,7 +257,6 @@ def __mod__(self, other): return self.with_function_applied_elementwise(lambda x: x % other) def __isub__(self, subtrahend): - if isinstance(subtrahend, COEFFICIENT_TYPES): self.constant -= subtrahend return self @@ -276,7 +270,8 @@ def __isub__(self, subtrahend): for key in subtrahend.n_body_tensors: if key in self.n_body_tensors: self.n_body_tensors[key] = numpy.subtract( - self.n_body_tensors[key], subtrahend.n_body_tensors[key]) + self.n_body_tensors[key], subtrahend.n_body_tensors[key] + ) else: self.n_body_tensors[key] = subtrahend.n_body_tensors[key] @@ -301,13 +296,12 @@ def __imul__(self, multiplier): for key in self.n_body_tensors: if key in multiplier.n_body_tensors: self.n_body_tensors[key] = numpy.multiply( - self.n_body_tensors[key], - multiplier.n_body_tensors[key]) + self.n_body_tensors[key], multiplier.n_body_tensors[key] + ) elif key == (): - self.constant = 0. + self.constant = 0.0 else: - self.n_body_tensors[key] = numpy.zeros( - self.n_body_tensors[key].shape) + self.n_body_tensors[key] = numpy.zeros(self.n_body_tensors[key].shape) else: raise TypeError('Invalid type.') @@ -355,8 +349,7 @@ def sort_key(key): yield () else: n_body_tensor = self.n_body_tensors[key] - for index in itertools.product(range(self.n_qubits), - repeat=len(key)): + for index in itertools.product(range(self.n_qubits), repeat=len(key)): if n_body_tensor[index]: yield tuple(zip(index, key)) @@ -381,7 +374,8 @@ def rotate_basis(self, rotation_matrix): pass else: self.n_body_tensors[key] = general_basis_change( - self.n_body_tensors[key], rotation_matrix, key) + self.n_body_tensors[key], rotation_matrix, key + ) def __repr__(self): return str(self) @@ -396,7 +390,7 @@ def projected_n_body_tensors(self, selection, exact=False): True) the specified indices. exact (bool): Whether or not the selection is strict. """ - comparator = (operator.eq if exact else operator.le) + comparator = operator.eq if exact else operator.le if isinstance(selection, int): pred = lambda index: comparator(len(set(index)), selection) dims = range(self.n_qubits) @@ -408,8 +402,7 @@ def projected_n_body_tensors(self, selection, exact=False): projected_n_body_tensors = dict() for key, tensor in self.n_body_tensors.items(): if not key: - projected_n_body_tensors[key] = ( - tensor if not (exact and selection) else 0) + projected_n_body_tensors[key] = tensor if not (exact and selection) else 0 continue projected_tensor = numpy.zeros_like(tensor) for index in itertools.product(dims, repeat=len(key)): diff --git a/src/openfermion/ops/representations/polynomial_tensor_test.py b/src/openfermion/ops/representations/polynomial_tensor_test.py index c608309fd..62b6d68db 100644 --- a/src/openfermion/ops/representations/polynomial_tensor_test.py +++ b/src/openfermion/ops/representations/polynomial_tensor_test.py @@ -18,19 +18,16 @@ from openfermion.ops.representations import PolynomialTensor from openfermion.transforms.opconversions import get_fermion_operator -from openfermion.circuits.slater_determinants_test import ( - random_quadratic_hamiltonian) +from openfermion.circuits.slater_determinants_test import random_quadratic_hamiltonian class PolynomialTensorTest(unittest.TestCase): - def setUp(self): self.n_qubits = 2 self.constant = 23.0 one_body_a = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_a = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_a = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_a[0, 1] = 2 one_body_a[1, 0] = 3 two_body_a[0, 1, 0, 1] = 4 @@ -38,126 +35,105 @@ def setUp(self): self.one_body_a = one_body_a self.two_body_a = two_body_a - self.polynomial_tensor_a = PolynomialTensor({ - (): self.constant, - (1, 0): one_body_a, - (1, 1, 0, 0): two_body_a - }) + self.polynomial_tensor_a = PolynomialTensor( + {(): self.constant, (1, 0): one_body_a, (1, 1, 0, 0): two_body_a} + ) self.one_body_operand = numpy.zeros((self.n_qubits, self.n_qubits)) self.two_body_operand = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits) + ) self.one_body_operand[0, 1] = 6 self.one_body_operand[1, 0] = 7 self.two_body_operand[0, 1, 0, 1] = 8 self.two_body_operand[1, 1, 0, 0] = 9 - self.polynomial_tensor_operand = PolynomialTensor({ - (1, 0): - self.one_body_operand, - (0, 0, 1, 1): - self.two_body_operand - }) - - self.polynomial_tensor_a_with_zeros = PolynomialTensor({ - (): - self.constant, - (1, 0): - one_body_a, - (1, 1, 0, 0): - two_body_a, - (1, 1, 0, 0, 0, 0): - numpy.zeros([self.n_qubits] * 6) - }) + self.polynomial_tensor_operand = PolynomialTensor( + {(1, 0): self.one_body_operand, (0, 0, 1, 1): self.two_body_operand} + ) + + self.polynomial_tensor_a_with_zeros = PolynomialTensor( + { + (): self.constant, + (1, 0): one_body_a, + (1, 1, 0, 0): two_body_a, + (1, 1, 0, 0, 0, 0): numpy.zeros([self.n_qubits] * 6), + } + ) one_body_na = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_na = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_na = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_na[0, 1] = -2 one_body_na[1, 0] = -3 two_body_na[0, 1, 0, 1] = -4 two_body_na[1, 1, 0, 0] = -5 - self.polynomial_tensor_na = PolynomialTensor({ - (): -self.constant, - (1, 0): one_body_na, - (1, 1, 0, 0): two_body_na - }) + self.polynomial_tensor_na = PolynomialTensor( + {(): -self.constant, (1, 0): one_body_na, (1, 1, 0, 0): two_body_na} + ) one_body_b = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_b = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_b = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_b[0, 1] = 1 one_body_b[1, 0] = 2 two_body_b[0, 1, 0, 1] = 3 two_body_b[1, 0, 0, 1] = 4 - self.polynomial_tensor_b = PolynomialTensor({ - (): self.constant, - (1, 0): one_body_b, - (1, 1, 0, 0): two_body_b - }) + self.polynomial_tensor_b = PolynomialTensor( + {(): self.constant, (1, 0): one_body_b, (1, 1, 0, 0): two_body_b} + ) one_body_ab = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_ab = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_ab = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_ab[0, 1] = 3 one_body_ab[1, 0] = 5 two_body_ab[0, 1, 0, 1] = 7 two_body_ab[1, 0, 0, 1] = 4 two_body_ab[1, 1, 0, 0] = 5 - self.polynomial_tensor_ab = PolynomialTensor({ - (): 2.0 * self.constant, - (1, 0): one_body_ab, - (1, 1, 0, 0): two_body_ab - }) + self.polynomial_tensor_ab = PolynomialTensor( + {(): 2.0 * self.constant, (1, 0): one_body_ab, (1, 1, 0, 0): two_body_ab} + ) constant_axb = self.constant * self.constant one_body_axb = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_axb = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_axb = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_axb[0, 1] = 2 one_body_axb[1, 0] = 6 two_body_axb[0, 1, 0, 1] = 12 - self.polynomial_tensor_axb = PolynomialTensor({ - (): constant_axb, - (1, 0): one_body_axb, - (1, 1, 0, 0): two_body_axb - }) + self.polynomial_tensor_axb = PolynomialTensor( + {(): constant_axb, (1, 0): one_body_axb, (1, 1, 0, 0): two_body_axb} + ) self.n_qubits_plus_one = self.n_qubits + 1 - one_body_c = numpy.zeros( - (self.n_qubits_plus_one, self.n_qubits_plus_one)) + one_body_c = numpy.zeros((self.n_qubits_plus_one, self.n_qubits_plus_one)) two_body_c = numpy.zeros( - (self.n_qubits_plus_one, self.n_qubits_plus_one, - self.n_qubits_plus_one, self.n_qubits_plus_one)) + ( + self.n_qubits_plus_one, + self.n_qubits_plus_one, + self.n_qubits_plus_one, + self.n_qubits_plus_one, + ) + ) one_body_c[0, 1] = 1 one_body_c[1, 0] = 2 two_body_c[0, 1, 0, 1] = 3 two_body_c[1, 0, 0, 1] = 4 - self.polynomial_tensor_c = PolynomialTensor({ - (): self.constant, - (1, 0): one_body_c, - (1, 1, 0, 0): two_body_c - }) + self.polynomial_tensor_c = PolynomialTensor( + {(): self.constant, (1, 0): one_body_c, (1, 1, 0, 0): two_body_c} + ) one_body_hole = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_hole = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_hole = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_hole[0, 1] = 2 one_body_hole[1, 0] = 3 two_body_hole[0, 1, 0, 1] = 4 two_body_hole[1, 1, 0, 0] = 5 - self.polynomial_tensor_hole = PolynomialTensor({ - (): - self.constant, - (0, 1): - one_body_hole, - (0, 0, 1, 1): - two_body_hole - }) + self.polynomial_tensor_hole = PolynomialTensor( + {(): self.constant, (0, 1): one_body_hole, (0, 0, 1, 1): two_body_hole} + ) one_body_spinful = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits)) - two_body_spinful = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits, - 2 * self.n_qubits, 2 * self.n_qubits)) + two_body_spinful = numpy.zeros( + (2 * self.n_qubits, 2 * self.n_qubits, 2 * self.n_qubits, 2 * self.n_qubits) + ) one_body_spinful[0, 1] = 2 one_body_spinful[1, 0] = 3 one_body_spinful[2, 3] = 6 @@ -167,14 +143,9 @@ def setUp(self): two_body_spinful[2, 1, 2, 3] = 8 two_body_spinful[3, 3, 2, 2] = 9 - self.polynomial_tensor_spinful = PolynomialTensor({ - (): - self.constant, - (1, 0): - one_body_spinful, - (1, 1, 0, 0): - two_body_spinful - }) + self.polynomial_tensor_spinful = PolynomialTensor( + {(): self.constant, (1, 0): one_body_spinful, (1, 1, 0, 0): two_body_spinful} + ) def test_set_n_body_tensors(self): pt_temp = copy.deepcopy(self.polynomial_tensor_a) @@ -190,8 +161,10 @@ def test_setitem_1body(self): self.polynomial_tensor_a[(0, 1), (1, 0)] = 3 self.polynomial_tensor_a[(1, 1), (0, 0)] = 2 self.assertTrue( - numpy.allclose(self.polynomial_tensor_a.n_body_tensors[(1, 0)], - expected_one_body_tensor)) + numpy.allclose( + self.polynomial_tensor_a.n_body_tensors[(1, 0)], expected_one_body_tensor + ) + ) def test_getitem_1body(self): self.assertEqual(self.polynomial_tensor_c[(0, 1), (1, 0)], 1) @@ -200,18 +173,12 @@ def test_getitem_1body(self): def test_setitem_2body(self): self.polynomial_tensor_a[(0, 1), (1, 1), (1, 0), (0, 0)] = 3 self.polynomial_tensor_a[(1, 1), (0, 1), (0, 0), (1, 0)] = 2 - self.assertEqual( - self.polynomial_tensor_a.n_body_tensors[(1, 1, 0, 0)][0, 1, 1, 0], - 3) - self.assertEqual( - self.polynomial_tensor_a.n_body_tensors[(1, 1, 0, 0)][1, 0, 0, 1], - 2) + self.assertEqual(self.polynomial_tensor_a.n_body_tensors[(1, 1, 0, 0)][0, 1, 1, 0], 3) + self.assertEqual(self.polynomial_tensor_a.n_body_tensors[(1, 1, 0, 0)][1, 0, 0, 1], 2) def test_getitem_2body(self): - self.assertEqual( - self.polynomial_tensor_c[(0, 1), (1, 1), (0, 0), (1, 0)], 3) - self.assertEqual( - self.polynomial_tensor_c[(1, 1), (0, 1), (0, 0), (1, 0)], 4) + self.assertEqual(self.polynomial_tensor_c[(0, 1), (1, 1), (0, 0), (1, 0)], 3) + self.assertEqual(self.polynomial_tensor_c[(1, 1), (0, 1), (0, 0), (1, 0)], 4) def test_invalid_getitem_indexing(self): with self.assertRaises(KeyError): @@ -224,16 +191,12 @@ def test_invalid_setitem_indexing(self): def test_eq(self): self.assertEqual(self.polynomial_tensor_a, self.polynomial_tensor_a) - self.assertNotEqual(self.polynomial_tensor_a, - self.polynomial_tensor_hole) - self.assertNotEqual(self.polynomial_tensor_a, - self.polynomial_tensor_spinful) + self.assertNotEqual(self.polynomial_tensor_a, self.polynomial_tensor_hole) + self.assertNotEqual(self.polynomial_tensor_a, self.polynomial_tensor_spinful) # OK to have different keys if arrays for differing keys are 0-arrays - self.assertEqual(self.polynomial_tensor_a, - self.polynomial_tensor_a_with_zeros) - self.assertEqual(self.polynomial_tensor_a_with_zeros, - self.polynomial_tensor_a) + self.assertEqual(self.polynomial_tensor_a, self.polynomial_tensor_a_with_zeros) + self.assertEqual(self.polynomial_tensor_a_with_zeros, self.polynomial_tensor_a) def test_ne(self): self.assertNotEqual(self.polynomial_tensor_a, self.polynomial_tensor_b) @@ -244,8 +207,7 @@ def test_add(self): def test_radd(self): new_tensor = 2 + self.polynomial_tensor_a - self.assertEqual(new_tensor.constant, - self.polynomial_tensor_a.constant + 2) + self.assertEqual(new_tensor.constant, self.polynomial_tensor_a.constant + 2) def test_sum_list(self): new_tensor1 = self.polynomial_tensor_a + self.polynomial_tensor_b @@ -254,8 +216,7 @@ def test_sum_list(self): def test_rsub(self): new_tensor = 2 - self.polynomial_tensor_a - self.assertEqual(new_tensor.constant, - 2 - self.polynomial_tensor_a.constant) + self.assertEqual(new_tensor.constant, 2 - self.polynomial_tensor_a.constant) new_tensor = new_tensor - 2 self.assertEqual(new_tensor, self.polynomial_tensor_a * -1) @@ -266,11 +227,9 @@ def test_mod(self): new_two_body = numpy.zeros_like(self.two_body_a) new_two_body[0, 1, 0, 1] = 1 new_two_body[1, 1, 0, 0] = 2 - new_tensor = PolynomialTensor({ - (): new_constant, - (1, 0): new_one_body, - (1, 1, 0, 0): new_two_body - }) + new_tensor = PolynomialTensor( + {(): new_constant, (1, 0): new_one_body, (1, 1, 0, 0): new_two_body} + ) assert new_tensor == (self.polynomial_tensor_a % 3) def test_iadd(self): @@ -288,16 +247,14 @@ def test_invalid_tensor_shape_add(self): def test_different_keys_add(self): result = self.polynomial_tensor_a + self.polynomial_tensor_operand - expected = PolynomialTensor({ - (): - self.constant, - (1, 0): - numpy.add(self.one_body_a, self.one_body_operand), - (1, 1, 0, 0): - self.two_body_a, - (0, 0, 1, 1): - self.two_body_operand - }) + expected = PolynomialTensor( + { + (): self.constant, + (1, 0): numpy.add(self.one_body_a, self.one_body_operand), + (1, 1, 0, 0): self.two_body_a, + (0, 0, 1, 1): self.two_body_operand, + } + ) self.assertEqual(result, expected) def test_neg(self): @@ -322,43 +279,49 @@ def test_invalid_tensor_shape_sub(self): def test_different_keys_sub(self): result = self.polynomial_tensor_a - self.polynomial_tensor_operand - expected = PolynomialTensor({ - (): - self.constant, - (1, 0): - numpy.subtract(self.one_body_a, self.one_body_operand), - (1, 1, 0, 0): - self.two_body_a, - (0, 0, 1, 1): - self.two_body_operand - }) + expected = PolynomialTensor( + { + (): self.constant, + (1, 0): numpy.subtract(self.one_body_a, self.one_body_operand), + (1, 1, 0, 0): self.two_body_a, + (0, 0, 1, 1): self.two_body_operand, + } + ) self.assertEqual(result, expected) def test_mul(self): new_tensor = self.polynomial_tensor_a * self.polynomial_tensor_b self.assertEqual(new_tensor, self.polynomial_tensor_axb) - new_tensor_1 = self.polynomial_tensor_a * 2. - new_tensor_2 = 2. * self.polynomial_tensor_a + new_tensor_1 = self.polynomial_tensor_a * 2.0 + new_tensor_2 = 2.0 * self.polynomial_tensor_a self.assertEqual( new_tensor_1, - PolynomialTensor({ - (): self.constant * 2., - (1, 0): self.one_body_a * 2., - (1, 1, 0, 0): self.two_body_a * 2. - })) + PolynomialTensor( + { + (): self.constant * 2.0, + (1, 0): self.one_body_a * 2.0, + (1, 1, 0, 0): self.two_body_a * 2.0, + } + ), + ) self.assertEqual( new_tensor_2, - PolynomialTensor({ - (): self.constant * 2., - (1, 0): self.one_body_a * 2., - (1, 1, 0, 0): self.two_body_a * 2. - })) - self.assertEqual(get_fermion_operator(new_tensor_1), - get_fermion_operator(self.polynomial_tensor_a) * 2.) - self.assertEqual(get_fermion_operator(new_tensor_2), - get_fermion_operator(self.polynomial_tensor_a) * 2.) + PolynomialTensor( + { + (): self.constant * 2.0, + (1, 0): self.one_body_a * 2.0, + (1, 1, 0, 0): self.two_body_a * 2.0, + } + ), + ) + self.assertEqual( + get_fermion_operator(new_tensor_1), get_fermion_operator(self.polynomial_tensor_a) * 2.0 + ) + self.assertEqual( + get_fermion_operator(new_tensor_2), get_fermion_operator(self.polynomial_tensor_a) * 2.0 + ) def test_imul(self): new_tensor = copy.deepcopy(self.polynomial_tensor_a) @@ -375,36 +338,43 @@ def test_invalid_tensor_shape_mult(self): def test_different_keys_mult(self): result = self.polynomial_tensor_a * self.polynomial_tensor_operand - expected = PolynomialTensor({ - (1, 0): - numpy.multiply(self.one_body_a, self.one_body_operand) - }) + expected = PolynomialTensor( + {(1, 0): numpy.multiply(self.one_body_a, self.one_body_operand)} + ) self.assertEqual(result, expected) def test_div(self): - new_tensor = self.polynomial_tensor_a / 2. + new_tensor = self.polynomial_tensor_a / 2.0 self.assertEqual( new_tensor, - PolynomialTensor({ - (): self.constant / 2., - (1, 0): self.one_body_a / 2., - (1, 1, 0, 0): self.two_body_a / 2. - })) - self.assertEqual(get_fermion_operator(new_tensor), - get_fermion_operator(self.polynomial_tensor_a) / 2.) + PolynomialTensor( + { + (): self.constant / 2.0, + (1, 0): self.one_body_a / 2.0, + (1, 1, 0, 0): self.two_body_a / 2.0, + } + ), + ) + self.assertEqual( + get_fermion_operator(new_tensor), get_fermion_operator(self.polynomial_tensor_a) / 2.0 + ) def test_idiv(self): new_tensor = copy.deepcopy(self.polynomial_tensor_a) - new_tensor /= 3. + new_tensor /= 3.0 self.assertEqual( new_tensor, - PolynomialTensor({ - (): self.constant / 3., - (1, 0): self.one_body_a / 3., - (1, 1, 0, 0): self.two_body_a / 3. - })) - self.assertEqual(get_fermion_operator(new_tensor), - get_fermion_operator(self.polynomial_tensor_a) / 3.) + PolynomialTensor( + { + (): self.constant / 3.0, + (1, 0): self.one_body_a / 3.0, + (1, 1, 0, 0): self.two_body_a / 3.0, + } + ), + ) + self.assertEqual( + get_fermion_operator(new_tensor), get_fermion_operator(self.polynomial_tensor_a) / 3.0 + ) def test_invalid_dividend(self): with self.assertRaises(TypeError): @@ -412,17 +382,13 @@ def test_invalid_dividend(self): def test_iter_and_str(self): one_body = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body[0, 1] = 11.0 two_body[0, 1, 1, 0] = 22.0 - polynomial_tensor = PolynomialTensor({ - (): self.constant, - (1, 0): one_body, - (1, 1, 0, 0): two_body - }) - want_str = ('() 23.0\n((0, 1), (1, 0)) 11.0\n' - '((0, 1), (1, 1), (1, 0), (0, 0)) 22.0\n') + polynomial_tensor = PolynomialTensor( + {(): self.constant, (1, 0): one_body, (1, 1, 0, 0): two_body} + ) + want_str = '() 23.0\n((0, 1), (1, 0)) 11.0\n' '((0, 1), (1, 1), (1, 0), (0, 0)) 22.0\n' self.assertEqual(str(polynomial_tensor), want_str) self.assertEqual(polynomial_tensor.__repr__(), want_str) @@ -432,11 +398,11 @@ def test_rotate_basis_identical(self): rotation_matrix_identical[1, 1] = 1 one_body = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_spinful = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits)) - two_body_spinful = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits, - 2 * self.n_qubits, 2 * self.n_qubits)) + two_body_spinful = numpy.zeros( + (2 * self.n_qubits, 2 * self.n_qubits, 2 * self.n_qubits, 2 * self.n_qubits) + ) i = 0 j = 0 for p in range(self.n_qubits): @@ -449,42 +415,30 @@ def test_rotate_basis_identical(self): for s in range(self.n_qubits): two_body[p, q, r, s] = j two_body_spinful[p, q, r, s] = j - two_body_spinful[p + self.n_qubits, q + - self.n_qubits, r + self.n_qubits, s + - self.n_qubits] = j + two_body_spinful[ + p + self.n_qubits, + q + self.n_qubits, + r + self.n_qubits, + s + self.n_qubits, + ] = j j = j + 1 - polynomial_tensor = PolynomialTensor({ - (): self.constant, - (1, 0): one_body, - (1, 1, 0, 0): two_body - }) - want_polynomial_tensor = PolynomialTensor({ - (): self.constant, - (1, 0): one_body, - (1, 1, 0, 0): two_body - }) - polynomial_tensor_spinful = PolynomialTensor({ - (): - self.constant, - (1, 0): - one_body_spinful, - (1, 1, 0, 0): - two_body_spinful - }) - want_polynomial_tensor_spinful = PolynomialTensor({ - (): - self.constant, - (1, 0): - one_body_spinful, - (1, 1, 0, 0): - two_body_spinful - }) + polynomial_tensor = PolynomialTensor( + {(): self.constant, (1, 0): one_body, (1, 1, 0, 0): two_body} + ) + want_polynomial_tensor = PolynomialTensor( + {(): self.constant, (1, 0): one_body, (1, 1, 0, 0): two_body} + ) + polynomial_tensor_spinful = PolynomialTensor( + {(): self.constant, (1, 0): one_body_spinful, (1, 1, 0, 0): two_body_spinful} + ) + want_polynomial_tensor_spinful = PolynomialTensor( + {(): self.constant, (1, 0): one_body_spinful, (1, 1, 0, 0): two_body_spinful} + ) polynomial_tensor.rotate_basis(rotation_matrix_identical) polynomial_tensor_spinful.rotate_basis(rotation_matrix_identical) self.assertEqual(polynomial_tensor, want_polynomial_tensor) - self.assertEqual(polynomial_tensor_spinful, - want_polynomial_tensor_spinful) + self.assertEqual(polynomial_tensor_spinful, want_polynomial_tensor_spinful) def test_rotate_basis_reverse(self): rotation_matrix_reverse = numpy.zeros((self.n_qubits, self.n_qubits)) @@ -492,11 +446,9 @@ def test_rotate_basis_reverse(self): rotation_matrix_reverse[1, 0] = 1 one_body = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) one_body_reverse = numpy.zeros((self.n_qubits, self.n_qubits)) - two_body_reverse = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) + two_body_reverse = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits)) i = 0 j = 0 i_reverse = pow(self.n_qubits, 2) - 1 @@ -513,16 +465,12 @@ def test_rotate_basis_reverse(self): j = j + 1 two_body_reverse[p, q, r, s] = j_reverse j_reverse = j_reverse - 1 - polynomial_tensor = PolynomialTensor({ - (): self.constant, - (1, 0): one_body, - (1, 1, 0, 0): two_body - }) - want_polynomial_tensor = PolynomialTensor({ - (): self.constant, - (1, 0): one_body_reverse, - (1, 1, 0, 0): two_body_reverse - }) + polynomial_tensor = PolynomialTensor( + {(): self.constant, (1, 0): one_body, (1, 1, 0, 0): two_body} + ) + want_polynomial_tensor = PolynomialTensor( + {(): self.constant, (1, 0): one_body_reverse, (1, 1, 0, 0): two_body_reverse} + ) polynomial_tensor.rotate_basis(rotation_matrix_reverse) self.assertEqual(polynomial_tensor, want_polynomial_tensor) @@ -543,8 +491,7 @@ def do_rotate_basis_quadratic_hamiltonian(self, real): orbital_energies, constant = quad_ham.orbital_energies() # Rotate a basis where the Hamiltonian is diagonal - _, diagonalizing_unitary, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + _, diagonalizing_unitary, _ = quad_ham.diagonalizing_bogoliubov_transform() quad_ham.rotate_basis(diagonalizing_unitary.T) # Check that the rotated Hamiltonian is diagonal with the correct @@ -583,8 +530,7 @@ def do_rotate_basis_high_order(self, order): # If order is odd, there are one more 0 than 1 in key if order % 2 == 1: num *= rotation - want_polynomial_tensor = PolynomialTensor( - {key: numpy.zeros(shape) + num}) + want_polynomial_tensor = PolynomialTensor({key: numpy.zeros(shape) + num}) polynomial_tensor.rotate_basis(numpy.array([[rotation]])) diff --git a/src/openfermion/ops/representations/quadratic_hamiltonian.py b/src/openfermion/ops/representations/quadratic_hamiltonian.py index 0b41f2cbf..e08b2e55f 100644 --- a/src/openfermion/ops/representations/quadratic_hamiltonian.py +++ b/src/openfermion/ops/representations/quadratic_hamiltonian.py @@ -48,11 +48,10 @@ class QuadraticHamiltonian(PolynomialTensor): Attributes: chemical_potential(float): The chemical potential $\mu$. """ - def __init__(self, - hermitian_part, - antisymmetric_part=None, - constant=0.0, - chemical_potential=0.0): + + def __init__( + self, hermitian_part, antisymmetric_part=None, constant=0.0, chemical_potential=0.0 + ): r""" Initialize the QuadraticHamiltonian class. @@ -76,28 +75,22 @@ def __init__(self, if not chemical_potential: combined_hermitian_part = hermitian_part else: - combined_hermitian_part = (hermitian_part - - chemical_potential * numpy.eye(n_qubits)) + combined_hermitian_part = hermitian_part - chemical_potential * numpy.eye(n_qubits) # Initialize the PolynomialTensor if antisymmetric_part is None: - super(QuadraticHamiltonian, self).__init__({ - (): - constant, - (1, 0): - combined_hermitian_part - }) + super(QuadraticHamiltonian, self).__init__( + {(): constant, (1, 0): combined_hermitian_part} + ) else: - super(QuadraticHamiltonian, self).__init__({ - (): - constant, - (1, 0): - combined_hermitian_part, - (1, 1): - 0.5 * antisymmetric_part, - (0, 0): - -0.5 * antisymmetric_part.conj() - }) + super(QuadraticHamiltonian, self).__init__( + { + (): constant, + (1, 0): combined_hermitian_part, + (1, 1): 0.5 * antisymmetric_part, + (0, 0): -0.5 * antisymmetric_part.conj(), + } + ) # Add remaining attributes self.chemical_potential = chemical_potential @@ -111,15 +104,14 @@ def combined_hermitian_part(self): def antisymmetric_part(self): """The antisymmetric part.""" if (1, 1) in self.n_body_tensors: - return 2. * self.n_body_tensors[1, 1] + return 2.0 * self.n_body_tensors[1, 1] else: return numpy.zeros((self.n_qubits, self.n_qubits), complex) @property def hermitian_part(self): """The Hermitian part not including the chemical potential.""" - return (self.combined_hermitian_part + - self.chemical_potential * numpy.eye(self.n_qubits)) + return self.combined_hermitian_part + self.chemical_potential * numpy.eye(self.n_qubits) @property def conserves_particle_number(self): @@ -129,16 +121,13 @@ def conserves_particle_number(self): def add_chemical_potential(self, chemical_potential): """Increase (or decrease) the chemical potential by some value.""" - self.n_body_tensors[1, 0] -= (chemical_potential * - numpy.eye(self.n_qubits)) + self.n_body_tensors[1, 0] -= chemical_potential * numpy.eye(self.n_qubits) self.chemical_potential += chemical_potential def ground_energy(self): """Return the ground energy.""" - orbital_energies, _, constant = ( - self.diagonalizing_bogoliubov_transform()) - return numpy.sum( - orbital_energies[numpy.where(orbital_energies < 0.0)[0]]) + constant + orbital_energies, _, constant = self.diagonalizing_bogoliubov_transform() + return numpy.sum(orbital_energies[numpy.where(orbital_energies < 0.0)[0]]) + constant def majorana_form(self): r"""Return the Majorana represention of the Hamiltonian. @@ -167,25 +156,48 @@ def majorana_form(self): # Compute the Majorana matrix using block matrix manipulations majorana_matrix = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits)) # Set upper left block - majorana_matrix[:self.n_qubits, :self.n_qubits] = numpy.real( - -0.5j * (hermitian_part - hermitian_part.conj() + - antisymmetric_part - antisymmetric_part.conj())) + majorana_matrix[: self.n_qubits, : self.n_qubits] = numpy.real( + -0.5j + * ( + hermitian_part + - hermitian_part.conj() + + antisymmetric_part + - antisymmetric_part.conj() + ) + ) # Set upper right block - majorana_matrix[:self.n_qubits, self.n_qubits:] = numpy.real( - 0.5 * (hermitian_part + hermitian_part.conj() - antisymmetric_part - - antisymmetric_part.conj())) + majorana_matrix[: self.n_qubits, self.n_qubits :] = numpy.real( + 0.5 + * ( + hermitian_part + + hermitian_part.conj() + - antisymmetric_part + - antisymmetric_part.conj() + ) + ) # Set lower left block - majorana_matrix[self.n_qubits:, :self.n_qubits] = numpy.real( - -0.5 * (hermitian_part + hermitian_part.conj() + - antisymmetric_part + antisymmetric_part.conj())) + majorana_matrix[self.n_qubits :, : self.n_qubits] = numpy.real( + -0.5 + * ( + hermitian_part + + hermitian_part.conj() + + antisymmetric_part + + antisymmetric_part.conj() + ) + ) # Set lower right block - majorana_matrix[self.n_qubits:, self.n_qubits:] = numpy.real( - -0.5j * (hermitian_part - hermitian_part.conj() - - antisymmetric_part + antisymmetric_part.conj())) + majorana_matrix[self.n_qubits :, self.n_qubits :] = numpy.real( + -0.5j + * ( + hermitian_part + - hermitian_part.conj() + - antisymmetric_part + + antisymmetric_part.conj() + ) + ) # Compute the constant - majorana_constant = (0.5 * numpy.real(numpy.trace(hermitian_part)) + - self.n_body_tensors[()]) + majorana_constant = 0.5 * numpy.real(numpy.trace(hermitian_part)) + self.n_body_tensors[()] return majorana_matrix, majorana_constant @@ -267,8 +279,8 @@ def diagonalizing_bogoliubov_transform(self, spin_sector=None): n_modes = self.combined_hermitian_part.shape[0] if spin_sector is not None and n_modes % 2: raise ValueError( - 'Spin sector was specified but Hamiltonian contains ' - 'an odd number of modes') + 'Spin sector was specified but Hamiltonian contains ' 'an odd number of modes' + ) if self.conserves_particle_number: return self._particle_conserving_bogoliubov_transform(spin_sector) @@ -277,9 +289,9 @@ def diagonalizing_bogoliubov_transform(self, spin_sector=None): if spin_sector is not None: raise NotImplementedError( 'Specifying spin sector for non-particle-conserving ' - 'Hamiltonians is not yet supported.') - return self._non_particle_conserving_bogoliubov_transform( - spin_sector) + 'Hamiltonians is not yet supported.' + ) + return self._non_particle_conserving_bogoliubov_transform(spin_sector) def _particle_conserving_bogoliubov_transform(self, spin_sector): n_modes = self.combined_hermitian_part.shape[0] @@ -290,33 +302,26 @@ def index_map(i): return i + spin_sector * n_sites spin_indices = [index_map(i) for i in range(n_sites)] - matrix = self.combined_hermitian_part[numpy.ix_( - spin_indices, spin_indices)] - orbital_energies, diagonalizing_unitary_T = numpy.linalg.eigh( - matrix) + matrix = self.combined_hermitian_part[numpy.ix_(spin_indices, spin_indices)] + orbital_energies, diagonalizing_unitary_T = numpy.linalg.eigh(matrix) else: matrix = self.combined_hermitian_part if _is_spin_block_diagonal(matrix): - up_block = matrix[:n_modes // 2, :n_modes // 2] - down_block = matrix[n_modes // 2:, n_modes // 2:] - - up_orbital_energies, up_diagonalizing_unitary_T = ( - numpy.linalg.eigh(up_block)) - down_orbital_energies, down_diagonalizing_unitary_T = ( - numpy.linalg.eigh(down_block)) - - orbital_energies = numpy.concatenate( - (up_orbital_energies, down_orbital_energies)) - diagonalizing_unitary_T = numpy.zeros((n_modes, n_modes), - dtype=complex) - diagonalizing_unitary_T[:n_modes // 2, :n_modes // - 2] = up_diagonalizing_unitary_T - diagonalizing_unitary_T[n_modes // 2:, n_modes // - 2:] = down_diagonalizing_unitary_T + up_block = matrix[: n_modes // 2, : n_modes // 2] + down_block = matrix[n_modes // 2 :, n_modes // 2 :] + + up_orbital_energies, up_diagonalizing_unitary_T = numpy.linalg.eigh(up_block) + down_orbital_energies, down_diagonalizing_unitary_T = numpy.linalg.eigh(down_block) + + orbital_energies = numpy.concatenate((up_orbital_energies, down_orbital_energies)) + diagonalizing_unitary_T = numpy.zeros((n_modes, n_modes), dtype=complex) + diagonalizing_unitary_T[: n_modes // 2, : n_modes // 2] = up_diagonalizing_unitary_T + diagonalizing_unitary_T[ + n_modes // 2 :, n_modes // 2 : + ] = down_diagonalizing_unitary_T else: - orbital_energies, diagonalizing_unitary_T = numpy.linalg.eigh( - matrix) + orbital_energies, diagonalizing_unitary_T = numpy.linalg.eigh(matrix) return orbital_energies, diagonalizing_unitary_T.T, self.constant @@ -326,27 +331,23 @@ def _non_particle_conserving_bogoliubov_transform(self, spin_sector): # Get the orthogonal transformation that puts majorana_matrix # into canonical form canonical, orthogonal = antisymmetric_canonical_form(majorana_matrix) - orbital_energies = canonical[range(self.n_qubits), - range(self.n_qubits, 2 * self.n_qubits)] + orbital_energies = canonical[range(self.n_qubits), range(self.n_qubits, 2 * self.n_qubits)] constant = -0.5 * numpy.sum(orbital_energies) + majorana_constant # Create the matrix that converts between fermionic ladder and # Majorana bases - normalized_identity = (numpy.eye(self.n_qubits, dtype=complex) / - numpy.sqrt(2.)) - majorana_basis_change = numpy.eye(2 * self.n_qubits, - dtype=complex) / numpy.sqrt(2.) - majorana_basis_change[self.n_qubits:, self.n_qubits:] *= -1.j - majorana_basis_change[:self.n_qubits, self. - n_qubits:] = normalized_identity - majorana_basis_change[self.n_qubits:, :self. - n_qubits] = 1.j * normalized_identity + normalized_identity = numpy.eye(self.n_qubits, dtype=complex) / numpy.sqrt(2.0) + majorana_basis_change = numpy.eye(2 * self.n_qubits, dtype=complex) / numpy.sqrt(2.0) + majorana_basis_change[self.n_qubits :, self.n_qubits :] *= -1.0j + majorana_basis_change[: self.n_qubits, self.n_qubits :] = normalized_identity + majorana_basis_change[self.n_qubits :, : self.n_qubits] = 1.0j * normalized_identity # Compute the unitary and return diagonalizing_unitary = majorana_basis_change.T.conj().dot( - orthogonal.dot(majorana_basis_change)) + orthogonal.dot(majorana_basis_change) + ) - return orbital_energies, diagonalizing_unitary[:self.n_qubits], constant + return orbital_energies, diagonalizing_unitary[: self.n_qubits], constant def diagonalizing_circuit(self): r"""Get a circuit for a unitary that diagonalizes this Hamiltonian @@ -374,14 +375,16 @@ def diagonalizing_circuit(self): # Adding inline import here to prevent circular issues # TODO: move this out once we have a better solution from openfermion.linalg.givens_rotations import ( - fermionic_gaussian_decomposition, givens_decomposition_square) + fermionic_gaussian_decomposition, + givens_decomposition_square, + ) + _, transformation_matrix, _ = self.diagonalizing_bogoliubov_transform() if self.conserves_particle_number: # The Hamiltonian conserves particle number, so we don't need # to use the most general procedure. - decomposition, _ = givens_decomposition_square( - transformation_matrix) + decomposition, _ = givens_decomposition_square(transformation_matrix) circuit_description = list(reversed(decomposition)) else: # The Hamiltonian does not conserve particle number, so we @@ -389,24 +392,23 @@ def diagonalizing_circuit(self): # Rearrange the transformation matrix because the circuit # generation routine expects it to describe annihilation # operators rather than creation operators. - left_block = transformation_matrix[:, :self.n_qubits] - right_block = transformation_matrix[:, self.n_qubits:] + left_block = transformation_matrix[:, : self.n_qubits] + right_block = transformation_matrix[:, self.n_qubits :] # Can't use numpy.block because that requires numpy>=1.13.0 new_transformation_matrix = numpy.empty( - (self.n_qubits, 2 * self.n_qubits), dtype=complex) - new_transformation_matrix[:, :self.n_qubits] = numpy.conjugate( - right_block) - new_transformation_matrix[:, self.n_qubits:] = numpy.conjugate( - left_block) + (self.n_qubits, 2 * self.n_qubits), dtype=complex + ) + new_transformation_matrix[:, : self.n_qubits] = numpy.conjugate(right_block) + new_transformation_matrix[:, self.n_qubits :] = numpy.conjugate(left_block) # Get the circuit description - decomposition, left_decomposition, _, _ = ( - fermionic_gaussian_decomposition(new_transformation_matrix)) + decomposition, left_decomposition, _, _ = fermionic_gaussian_decomposition( + new_transformation_matrix + ) # need to use left_diagonal too - circuit_description = list( - reversed(decomposition + left_decomposition)) + circuit_description = list(reversed(decomposition + left_decomposition)) return circuit_description @@ -442,10 +444,11 @@ def orbital_energies(self, non_negative=False): warnings.warn( 'The method `orbital_energies` is deprecated. ' 'Use the method `diagonalizing_bogoliubov_transform` ' - 'instead.', DeprecationWarning) + 'instead.', + DeprecationWarning, + ) - orbital_energies, _, constant = ( - self.diagonalizing_bogoliubov_transform()) + orbital_energies, _, constant = self.diagonalizing_bogoliubov_transform() return orbital_energies, constant @@ -476,11 +479,11 @@ def antisymmetric_canonical_form(antisymmetric_matrix): # Shifted here to prevent circular import issues # TODO: move this out when a better solution is found. from openfermion.linalg.givens_rotations import swap_columns, swap_rows + m, p = antisymmetric_matrix.shape if m != p or p % 2 != 0: - raise ValueError('The input matrix must be square with even ' - 'dimension.') + raise ValueError('The input matrix must be square with even ' 'dimension.') # Check that input matrix is antisymmetric matrix_plus_transpose = antisymmetric_matrix + antisymmetric_matrix.T @@ -545,7 +548,6 @@ def _is_spin_block_diagonal(matrix): n = matrix.shape[0] if n % 2: return False - max_upper_right = numpy.max(numpy.abs(matrix[:n // 2, n // 2:])) - max_lower_left = numpy.max(numpy.abs(matrix[n // 2:, :n // 2])) - return (numpy.isclose(max_upper_right, 0.0) and - numpy.isclose(max_lower_left, 0.0)) + max_upper_right = numpy.max(numpy.abs(matrix[: n // 2, n // 2 :])) + max_lower_left = numpy.max(numpy.abs(matrix[n // 2 :, : n // 2])) + return numpy.isclose(max_upper_right, 0.0) and numpy.isclose(max_lower_left, 0.0) diff --git a/src/openfermion/ops/representations/quadratic_hamiltonian_test.py b/src/openfermion/ops/representations/quadratic_hamiltonian_test.py index c8fcfaa70..9fe9261d3 100644 --- a/src/openfermion/ops/representations/quadratic_hamiltonian_test.py +++ b/src/openfermion/ops/representations/quadratic_hamiltonian_test.py @@ -19,67 +19,61 @@ from openfermion.hamiltonians.special_operators import majorana_operator from openfermion.ops.operators import FermionOperator from openfermion.ops.representations import QuadraticHamiltonian -from openfermion.transforms.opconversions import (get_fermion_operator, - normal_ordered, reorder) +from openfermion.transforms.opconversions import get_fermion_operator, normal_ordered, reorder from openfermion.transforms.repconversions import get_quadratic_hamiltonian -from openfermion.linalg.sparse_tools import (get_ground_state, - get_sparse_operator) -from openfermion.testing.testing_utils import (random_antisymmetric_matrix, - random_hermitian_matrix, - random_quadratic_hamiltonian) +from openfermion.linalg.sparse_tools import get_ground_state, get_sparse_operator +from openfermion.testing.testing_utils import ( + random_antisymmetric_matrix, + random_hermitian_matrix, + random_quadratic_hamiltonian, +) from openfermion.utils import up_then_down -from openfermion.ops.representations.quadratic_hamiltonian import ( - antisymmetric_canonical_form) +from openfermion.ops.representations.quadratic_hamiltonian import antisymmetric_canonical_form from openfermion.linalg.sparse_tools import ( - jw_sparse_givens_rotation, jw_sparse_particle_hole_transformation_last_mode) + jw_sparse_givens_rotation, + jw_sparse_particle_hole_transformation_last_mode, +) class QuadraticHamiltonianTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 self.constant = 1.7 - self.chemical_potential = 2. + self.chemical_potential = 2.0 # Obtain random Hermitian and antisymmetric matrices self.hermitian_mat = random_hermitian_matrix(self.n_qubits) self.antisymmetric_mat = random_antisymmetric_matrix(self.n_qubits) - self.combined_hermitian = ( - self.hermitian_mat - - self.chemical_potential * numpy.eye(self.n_qubits)) + self.combined_hermitian = self.hermitian_mat - self.chemical_potential * numpy.eye( + self.n_qubits + ) # Initialize a particle-number-conserving Hamiltonian - self.quad_ham_pc = QuadraticHamiltonian(self.hermitian_mat, - constant=self.constant) + self.quad_ham_pc = QuadraticHamiltonian(self.hermitian_mat, constant=self.constant) # Initialize a non-particle-number-conserving Hamiltonian - self.quad_ham_npc = QuadraticHamiltonian(self.hermitian_mat, - self.antisymmetric_mat, - self.constant, - self.chemical_potential) + self.quad_ham_npc = QuadraticHamiltonian( + self.hermitian_mat, self.antisymmetric_mat, self.constant, self.chemical_potential + ) # Initialize the sparse operators and get their ground energies self.quad_ham_pc_sparse = get_sparse_operator(self.quad_ham_pc) self.quad_ham_npc_sparse = get_sparse_operator(self.quad_ham_npc) - self.pc_ground_energy, self.pc_ground_state = get_ground_state( - self.quad_ham_pc_sparse) - self.npc_ground_energy, self.npc_ground_state = get_ground_state( - self.quad_ham_npc_sparse) + self.pc_ground_energy, self.pc_ground_state = get_ground_state(self.quad_ham_pc_sparse) + self.npc_ground_energy, self.npc_ground_state = get_ground_state(self.quad_ham_npc_sparse) def test_combined_hermitian_part(self): """Test getting the combined Hermitian part.""" combined_hermitian_part = self.quad_ham_pc.combined_hermitian_part for i in numpy.ndindex(combined_hermitian_part.shape): - self.assertAlmostEqual(self.hermitian_mat[i], - combined_hermitian_part[i]) + self.assertAlmostEqual(self.hermitian_mat[i], combined_hermitian_part[i]) combined_hermitian_part = self.quad_ham_npc.combined_hermitian_part for i in numpy.ndindex(combined_hermitian_part.shape): - self.assertAlmostEqual(self.combined_hermitian[i], - combined_hermitian_part[i]) + self.assertAlmostEqual(self.combined_hermitian[i], combined_hermitian_part[i]) def test_hermitian_part(self): """Test getting the Hermitian part.""" @@ -95,12 +89,11 @@ def test_antisymmetric_part(self): """Test getting the antisymmetric part.""" antisymmetric_part = self.quad_ham_pc.antisymmetric_part for i in numpy.ndindex(antisymmetric_part.shape): - self.assertAlmostEqual(0., antisymmetric_part[i]) + self.assertAlmostEqual(0.0, antisymmetric_part[i]) antisymmetric_part = self.quad_ham_npc.antisymmetric_part for i in numpy.ndindex(antisymmetric_part.shape): - self.assertAlmostEqual(self.antisymmetric_mat[i], - antisymmetric_part[i]) + self.assertAlmostEqual(self.antisymmetric_mat[i], antisymmetric_part[i]) def test_conserves_particle_number(self): """Test checking whether Hamiltonian conserves particle number.""" @@ -114,8 +107,7 @@ def test_add_chemical_potential(self): combined_hermitian_part = self.quad_ham_npc.combined_hermitian_part hermitian_part = self.quad_ham_npc.hermitian_part - want_combined = (self.combined_hermitian - - 2.4 * numpy.eye(self.n_qubits)) + want_combined = self.combined_hermitian - 2.4 * numpy.eye(self.n_qubits) want_hermitian = self.hermitian_mat for i in numpy.ndindex(combined_hermitian_part.shape): @@ -124,8 +116,7 @@ def test_add_chemical_potential(self): for i in numpy.ndindex(hermitian_part.shape): self.assertAlmostEqual(hermitian_part[i], want_hermitian[i]) - self.assertAlmostEqual(2.4 + self.chemical_potential, - self.quad_ham_npc.chemical_potential) + self.assertAlmostEqual(2.4 + self.chemical_potential, self.quad_ham_npc.chemical_potential) def test_orbital_energies(self): """Test getting the orbital energies.""" @@ -155,34 +146,32 @@ def test_majorana_form(self): majorana_matrix, majorana_constant = self.quad_ham_npc.majorana_form() # Convert the Majorana form to a FermionOperator majorana_op = FermionOperator((), majorana_constant) - normalization = 1. / numpy.sqrt(2.) + normalization = 1.0 / numpy.sqrt(2.0) for i in range(2 * self.n_qubits): if i < self.n_qubits: left_op = majorana_operator((i, 0), normalization) else: - left_op = majorana_operator((i - self.n_qubits, 1), - normalization) + left_op = majorana_operator((i - self.n_qubits, 1), normalization) for j in range(2 * self.n_qubits): if j < self.n_qubits: - right_op = majorana_operator( - (j, 0), majorana_matrix[i, j] * normalization) + right_op = majorana_operator((j, 0), majorana_matrix[i, j] * normalization) else: right_op = majorana_operator( - (j - self.n_qubits, 1), - majorana_matrix[i, j] * normalization) - majorana_op += .5j * left_op * right_op + (j - self.n_qubits, 1), majorana_matrix[i, j] * normalization + ) + majorana_op += 0.5j * left_op * right_op # Get FermionOperator for original Hamiltonian - fermion_operator = normal_ordered( - get_fermion_operator(self.quad_ham_npc)) + fermion_operator = normal_ordered(get_fermion_operator(self.quad_ham_npc)) self.assertTrue(normal_ordered(majorana_op) == fermion_operator) def test_diagonalizing_bogoliubov_transform(self): """Test diagonalizing Bogoliubov transform.""" hermitian_part = numpy.array( - [[0.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 0.0]], dtype=complex) + [[0.0, 1.0, 0.0], [1.0, 0.0, 1.0], [0.0, 1.0, 0.0]], dtype=complex + ) antisymmetric_part = numpy.array( - [[0.0, 1.0j, 0.0], [-1.0j, 0.0, 1.0j], [0.0, -1.0j, 0.0]], - dtype=complex) + [[0.0, 1.0j, 0.0], [-1.0j, 0.0, 1.0j], [0.0, -1.0j, 0.0]], dtype=complex + ) quad_ham = QuadraticHamiltonian(hermitian_part, antisymmetric_part) block_matrix = numpy.zeros((6, 6), dtype=complex) block_matrix[:3, :3] = antisymmetric_part @@ -190,8 +179,7 @@ def test_diagonalizing_bogoliubov_transform(self): block_matrix[3:, :3] = -hermitian_part.conj() block_matrix[3:, 3:] = -antisymmetric_part.conj() - _, transformation_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + _, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() left_block = transformation_matrix[:, :3] right_block = transformation_matrix[:, 3:] ferm_unitary = numpy.zeros((6, 6), dtype=complex) @@ -203,8 +191,7 @@ def test_diagonalizing_bogoliubov_transform(self): # Check that the transformation is diagonalizing majorana_matrix, _ = quad_ham.majorana_form() canonical, _ = antisymmetric_canonical_form(majorana_matrix) - diagonalized = ferm_unitary.conj().dot( - block_matrix.dot(ferm_unitary.T.conj())) + diagonalized = ferm_unitary.conj().dot(block_matrix.dot(ferm_unitary.T.conj())) for i in numpy.ndindex((6, 6)): self.assertAlmostEqual(diagonalized[i], canonical[i]) @@ -212,128 +199,117 @@ def test_diagonalizing_bogoliubov_transform_non_particle_conserving(self): """Test non-particle-conserving diagonalizing Bogoliubov transform.""" hermitian_part = self.quad_ham_npc.combined_hermitian_part antisymmetric_part = self.quad_ham_npc.antisymmetric_part - block_matrix = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits), - dtype=complex) - block_matrix[:self.n_qubits, :self.n_qubits] = antisymmetric_part - block_matrix[:self.n_qubits, self.n_qubits:] = hermitian_part - block_matrix[self.n_qubits:, :self.n_qubits] = -hermitian_part.conj() - block_matrix[self.n_qubits:, self.n_qubits:] = ( - -antisymmetric_part.conj()) - - _, transformation_matrix, _ = ( - self.quad_ham_npc.diagonalizing_bogoliubov_transform()) - left_block = transformation_matrix[:, :self.n_qubits] - right_block = transformation_matrix[:, self.n_qubits:] - ferm_unitary = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits), - dtype=complex) - ferm_unitary[:self.n_qubits, :self.n_qubits] = left_block - ferm_unitary[:self.n_qubits, self.n_qubits:] = right_block - ferm_unitary[self.n_qubits:, :self.n_qubits] = numpy.conjugate( - right_block) - ferm_unitary[self.n_qubits:, self.n_qubits:] = numpy.conjugate( - left_block) + block_matrix = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits), dtype=complex) + block_matrix[: self.n_qubits, : self.n_qubits] = antisymmetric_part + block_matrix[: self.n_qubits, self.n_qubits :] = hermitian_part + block_matrix[self.n_qubits :, : self.n_qubits] = -hermitian_part.conj() + block_matrix[self.n_qubits :, self.n_qubits :] = -antisymmetric_part.conj() + + _, transformation_matrix, _ = self.quad_ham_npc.diagonalizing_bogoliubov_transform() + left_block = transformation_matrix[:, : self.n_qubits] + right_block = transformation_matrix[:, self.n_qubits :] + ferm_unitary = numpy.zeros((2 * self.n_qubits, 2 * self.n_qubits), dtype=complex) + ferm_unitary[: self.n_qubits, : self.n_qubits] = left_block + ferm_unitary[: self.n_qubits, self.n_qubits :] = right_block + ferm_unitary[self.n_qubits :, : self.n_qubits] = numpy.conjugate(right_block) + ferm_unitary[self.n_qubits :, self.n_qubits :] = numpy.conjugate(left_block) # Check that the transformation is diagonalizing majorana_matrix, _ = self.quad_ham_npc.majorana_form() canonical, _ = antisymmetric_canonical_form(majorana_matrix) - diagonalized = ferm_unitary.conj().dot( - block_matrix.dot(ferm_unitary.T.conj())) + diagonalized = ferm_unitary.conj().dot(block_matrix.dot(ferm_unitary.T.conj())) for i in numpy.ndindex((2 * self.n_qubits, 2 * self.n_qubits)): self.assertAlmostEqual(diagonalized[i], canonical[i]) - lower_unitary = ferm_unitary[self.n_qubits:] - lower_left = lower_unitary[:, :self.n_qubits] - lower_right = lower_unitary[:, self.n_qubits:] + lower_unitary = ferm_unitary[self.n_qubits :] + lower_left = lower_unitary[:, : self.n_qubits] + lower_right = lower_unitary[:, self.n_qubits :] # Check that lower_left and lower_right satisfy the constraints # necessary for the transformed fermionic operators to satisfy # the fermionic anticommutation relations - constraint_matrix_1 = (lower_left.dot(lower_left.T.conj()) + - lower_right.dot(lower_right.T.conj())) - constraint_matrix_2 = (lower_left.dot(lower_right.T) + - lower_right.dot(lower_left.T)) + constraint_matrix_1 = lower_left.dot(lower_left.T.conj()) + lower_right.dot( + lower_right.T.conj() + ) + constraint_matrix_2 = lower_left.dot(lower_right.T) + lower_right.dot(lower_left.T) identity = numpy.eye(self.n_qubits, dtype=complex) for i in numpy.ndindex((self.n_qubits, self.n_qubits)): self.assertAlmostEqual(identity[i], constraint_matrix_1[i]) - self.assertAlmostEqual(0., constraint_matrix_2[i]) + self.assertAlmostEqual(0.0, constraint_matrix_2[i]) def test_diagonalizing_bogoliubov_transform_particle_conserving(self): """Test particle-conserving diagonalizing Bogoliubov transform.""" # Spin-symmetric - quad_ham = random_quadratic_hamiltonian(5, - conserves_particle_number=True, - expand_spin=True) - quad_ham = get_quadratic_hamiltonian( - reorder(get_fermion_operator(quad_ham), up_then_down)) - - orbital_energies, transformation_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) + quad_ham = random_quadratic_hamiltonian(5, conserves_particle_number=True, expand_spin=True) + quad_ham = get_quadratic_hamiltonian(reorder(get_fermion_operator(quad_ham), up_then_down)) + + orbital_energies, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() max_upper_right = numpy.max(numpy.abs(transformation_matrix[:5, 5:])) max_lower_left = numpy.max(numpy.abs(transformation_matrix[5:, :5])) - numpy.testing.assert_allclose(orbital_energies[:5], - orbital_energies[5:]) - numpy.testing.assert_allclose(transformation_matrix.dot( - quad_ham.combined_hermitian_part.T.dot( - transformation_matrix.T.conj())), - numpy.diag(orbital_energies), - atol=1e-7) + numpy.testing.assert_allclose(orbital_energies[:5], orbital_energies[5:]) + numpy.testing.assert_allclose( + transformation_matrix.dot( + quad_ham.combined_hermitian_part.T.dot(transformation_matrix.T.conj()) + ), + numpy.diag(orbital_energies), + atol=1e-7, + ) numpy.testing.assert_allclose(max_upper_right, 0.0) numpy.testing.assert_allclose(max_lower_left, 0.0) # Specific spin sector - quad_ham = random_quadratic_hamiltonian(5, - conserves_particle_number=True, - expand_spin=True) - quad_ham = get_quadratic_hamiltonian( - reorder(get_fermion_operator(quad_ham), up_then_down)) + quad_ham = random_quadratic_hamiltonian(5, conserves_particle_number=True, expand_spin=True) + quad_ham = get_quadratic_hamiltonian(reorder(get_fermion_operator(quad_ham), up_then_down)) for spin_sector in range(2): - orbital_energies, transformation_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform( - spin_sector=spin_sector)) + ( + orbital_energies, + transformation_matrix, + _, + ) = quad_ham.diagonalizing_bogoliubov_transform(spin_sector=spin_sector) def index_map(i): return i + spin_sector * 5 spin_indices = [index_map(i) for i in range(5)] - spin_matrix = quad_ham.combined_hermitian_part[numpy.ix_( - spin_indices, spin_indices)] - numpy.testing.assert_allclose(transformation_matrix.dot( - spin_matrix.T.dot(transformation_matrix.T.conj())), - numpy.diag(orbital_energies), - atol=1e-7) + spin_matrix = quad_ham.combined_hermitian_part[numpy.ix_(spin_indices, spin_indices)] + numpy.testing.assert_allclose( + transformation_matrix.dot(spin_matrix.T.dot(transformation_matrix.T.conj())), + numpy.diag(orbital_energies), + atol=1e-7, + ) # Not spin-symmetric - quad_ham = random_quadratic_hamiltonian(5, - conserves_particle_number=True, - expand_spin=False) + quad_ham = random_quadratic_hamiltonian( + 5, conserves_particle_number=True, expand_spin=False + ) orbital_energies, _ = quad_ham.orbital_energies() - _, transformation_matrix, _ = ( - quad_ham.diagonalizing_bogoliubov_transform()) - numpy.testing.assert_allclose(transformation_matrix.dot( - quad_ham.combined_hermitian_part.T.dot( - transformation_matrix.T.conj())), - numpy.diag(orbital_energies), - atol=1e-7) + _, transformation_matrix, _ = quad_ham.diagonalizing_bogoliubov_transform() + numpy.testing.assert_allclose( + transformation_matrix.dot( + quad_ham.combined_hermitian_part.T.dot(transformation_matrix.T.conj()) + ), + numpy.diag(orbital_energies), + atol=1e-7, + ) def test_diagonalizing_bogoliubov_transform_exceptions(self): quad_ham = random_quadratic_hamiltonian(5) with self.assertRaises(ValueError): _ = quad_ham.diagonalizing_bogoliubov_transform(spin_sector=0) - quad_ham = random_quadratic_hamiltonian(5, - conserves_particle_number=False, - expand_spin=True) + quad_ham = random_quadratic_hamiltonian( + 5, conserves_particle_number=False, expand_spin=True + ) with self.assertRaises(NotImplementedError): _ = quad_ham.diagonalizing_bogoliubov_transform(spin_sector=0) class DiagonalizingCircuitTest(unittest.TestCase): - def setUp(self): self.n_qubits_range = range(3, 9) @@ -344,27 +320,24 @@ def test_particle_conserving(self): sparse_operator = get_sparse_operator(quadratic_hamiltonian) # Diagonalize the Hamiltonian using the circuit - circuit_description = reversed( - quadratic_hamiltonian.diagonalizing_circuit()) + circuit_description = reversed(quadratic_hamiltonian.diagonalizing_circuit()) for parallel_ops in circuit_description: for op in parallel_ops: i, j, theta, phi = op gate = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits) - sparse_operator = ( - gate.getH().dot(sparse_operator).dot(gate)) + sparse_operator = gate.getH().dot(sparse_operator).dot(gate) # Check that the result is diagonal diag = scipy.sparse.diags(sparse_operator.diagonal()) difference = sparse_operator - diag - discrepancy = 0. + discrepancy = 0.0 if difference.nnz: discrepancy = max(abs(difference.data)) numpy.testing.assert_allclose(discrepancy, 0.0, atol=1e-7) # Check that the eigenvalues are in the expected order - orbital_energies, constant = ( - quadratic_hamiltonian.orbital_energies()) + orbital_energies, constant = quadratic_hamiltonian.orbital_energies() for index in range(2**n_qubits): bitstring = bin(index)[2:].zfill(n_qubits) subset = [j for j in range(n_qubits) if bitstring[j] == '1'] @@ -374,38 +347,34 @@ def test_particle_conserving(self): def test_non_particle_conserving(self): for n_qubits in self.n_qubits_range: # Initialize a particle-number-conserving Hamiltonian - quadratic_hamiltonian = random_quadratic_hamiltonian( - n_qubits, False) + quadratic_hamiltonian = random_quadratic_hamiltonian(n_qubits, False) sparse_operator = get_sparse_operator(quadratic_hamiltonian) # Diagonalize the Hamiltonian using the circuit - circuit_description = reversed( - quadratic_hamiltonian.diagonalizing_circuit()) + circuit_description = reversed(quadratic_hamiltonian.diagonalizing_circuit()) - particle_hole_transformation = ( - jw_sparse_particle_hole_transformation_last_mode(n_qubits)) + particle_hole_transformation = jw_sparse_particle_hole_transformation_last_mode( + n_qubits + ) for parallel_ops in circuit_description: for op in parallel_ops: if op == 'pht': gate = particle_hole_transformation else: i, j, theta, phi = op - gate = jw_sparse_givens_rotation( - i, j, theta, phi, n_qubits) - sparse_operator = ( - gate.getH().dot(sparse_operator).dot(gate)) + gate = jw_sparse_givens_rotation(i, j, theta, phi, n_qubits) + sparse_operator = gate.getH().dot(sparse_operator).dot(gate) # Check that the result is diagonal diag = scipy.sparse.diags(sparse_operator.diagonal()) difference = sparse_operator - diag - discrepancy = 0. + discrepancy = 0.0 if difference.nnz: discrepancy = max(abs(difference.data)) numpy.testing.assert_allclose(discrepancy, 0.0, atol=1e-7) # Check that the eigenvalues are in the expected order - orbital_energies, constant = ( - quadratic_hamiltonian.orbital_energies()) + orbital_energies, constant = quadratic_hamiltonian.orbital_energies() for index in range(2**n_qubits): bitstring = bin(index)[2:].zfill(n_qubits) subset = [j for j in range(n_qubits) if bitstring[j] == '1'] @@ -414,15 +383,11 @@ def test_non_particle_conserving(self): class AntisymmetricCanonicalFormTest(unittest.TestCase): - def test_equality(self): """Test that the decomposition is valid.""" n = 7 - antisymmetric_matrix = random_antisymmetric_matrix(2 * n, - real=True, - seed=9799) - canonical, orthogonal = antisymmetric_canonical_form( - antisymmetric_matrix) + antisymmetric_matrix = random_antisymmetric_matrix(2 * n, real=True, seed=9799) + canonical, orthogonal = antisymmetric_canonical_form(antisymmetric_matrix) result_matrix = orthogonal.dot(antisymmetric_matrix.dot(orthogonal.T)) for i in numpy.ndindex(result_matrix.shape): self.assertAlmostEqual(result_matrix[i], canonical[i]) @@ -431,9 +396,7 @@ def test_canonical(self): """Test that the returned canonical matrix has the right form.""" n = 7 # Obtain a random antisymmetric matrix - antisymmetric_matrix = random_antisymmetric_matrix(2 * n, - real=True, - seed=29206) + antisymmetric_matrix = random_antisymmetric_matrix(2 * n, real=True, seed=29206) canonical, _ = antisymmetric_canonical_form(antisymmetric_matrix) for i in range(2 * n): for j in range(2 * n): @@ -442,7 +405,7 @@ def test_canonical(self): elif i >= n and j == i - n: self.assertTrue(canonical[i, j] < 0.0) else: - self.assertAlmostEqual(canonical[i, j], 0.) + self.assertAlmostEqual(canonical[i, j], 0.0) diagonal = canonical[range(n), range(n, 2 * n)] for i in range(n - 1): diff --git a/src/openfermion/resource_estimates/__init__.py b/src/openfermion/resource_estimates/__init__.py index 70e476466..1b0e4e35b 100644 --- a/src/openfermion/resource_estimates/__init__.py +++ b/src/openfermion/resource_estimates/__init__.py @@ -14,6 +14,7 @@ try: import jax import pyscf + HAVE_DEPS_FOR_RESOURCE_ESTIMATES = True except ModuleNotFoundError: HAVE_DEPS_FOR_RESOURCE_ESTIMATES = False diff --git a/src/openfermion/resource_estimates/df/__init__.py b/src/openfermion/resource_estimates/df/__init__.py index c8888221d..2e25d980e 100644 --- a/src/openfermion/resource_estimates/df/__init__.py +++ b/src/openfermion/resource_estimates/df/__init__.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # Copyright 2020 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,4 +19,4 @@ from .compute_cost_df import compute_cost from .compute_lambda_df import compute_lambda from .factorize_df import factorize - from .generate_costing_table_df import generate_costing_table \ No newline at end of file + from .generate_costing_table_df import generate_costing_table diff --git a/src/openfermion/resource_estimates/df/compute_cost_df.py b/src/openfermion/resource_estimates/df/compute_cost_df.py index 53c778a44..737dec818 100644 --- a/src/openfermion/resource_estimates/df/compute_cost_df.py +++ b/src/openfermion/resource_estimates/df/compute_cost_df.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Determine costs for DF decomposition in QC """ from typing import Tuple import numpy as np @@ -6,16 +6,18 @@ from openfermion.resource_estimates.utils import QR, QI, power_two -def compute_cost(n: int, - lam: float, - dE: float, - L: int, - Lxi: int, - chi: int, - beta: int, - stps: int, - verbose: bool = False) -> Tuple[int, int, int]: - """ Determine fault-tolerant costs using DF decomposition in quantum chem +def compute_cost( + n: int, + lam: float, + dE: float, + L: int, + Lxi: int, + chi: int, + beta: int, + stps: int, + verbose: bool = False, +) -> Tuple[int, int, int]: + """Determine fault-tolerant costs using DF decomposition in quantum chem Args: n (int) - the number of spin-orbitals @@ -52,11 +54,30 @@ def compute_cost(n: int, oh = [0] * 20 for p in range(20): # JJG note: arccos arg may be > 1 - v = np.round(np.power(2,p+1) / (2 * np.pi) * arccos(np.power(2,nL) /\ - np.sqrt((L + 1)/2**eta)/2)) - oh[p] = np.real(stps * (1 / (np.sin(3 * arcsin(np.cos(v * 2 * np.pi / \ - np.power(2,p+1)) * \ - np.sqrt((L + 1)/2**eta) / np.power(2,nL)))**2) - 1) + 4 * (p + 1)) + v = np.round( + np.power(2, p + 1) + / (2 * np.pi) + * arccos(np.power(2, nL) / np.sqrt((L + 1) / 2**eta) / 2) + ) + oh[p] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt((L + 1) / 2**eta) + / np.power(2, nL) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br = int(np.argmin(oh) + 1) @@ -107,8 +128,7 @@ def compute_cost(n: int, # The cost of the QROMs and inverse QROMs for the state preparation, where # in the first one we need + n/2 to account for the one-electron terms. - cost3c = QR(Lxi + n // 2, bp2)[1] + QI(Lxi + n // 2)[1] + QR( - Lxi, bp2)[1] + QI(Lxi)[1] + cost3c = QR(Lxi + n // 2, bp2)[1] + QI(Lxi + n // 2)[1] + QR(Lxi, bp2)[1] + QI(Lxi)[1] # The inequality test and state preparations. cost3d = 4 * (nxi + chi) @@ -120,8 +140,12 @@ def compute_cost(n: int, cost4ah = 4 * (nLxi - 1) # The costs of the QROMs and their inverses in steps 4 (b) and (g). - cost4bg = QR(Lxi + n // 2, n * beta // 2)[1] + QI(Lxi + n // 2)[1] + QR( - Lxi, n * beta // 2)[1] + QI(Lxi)[1] + cost4bg = ( + QR(Lxi + n // 2, n * beta // 2)[1] + + QI(Lxi + n // 2)[1] + + QR(Lxi, n * beta // 2)[1] + + QI(Lxi)[1] + ) # The cost of the controlled swaps based on the spin qubit in steps 4c and f cost4cf = 2 * n @@ -202,8 +226,7 @@ def compute_cost(n: int, print(" [+] cost = ", cost) print(" [+] iters = ", iters) - ancilla_cost = ac1 + ac2 + ac3 + ac4 + ac5 + ac6 + ac8 + ac9 + ac10 + ac11\ - + ac12 + ac13 + ancilla_cost = ac1 + ac2 + ac3 + ac4 + ac5 + ac6 + ac8 + ac9 + ac10 + ac11 + ac12 + ac13 # Sanity checks before returning as int assert cost.is_integer() diff --git a/src/openfermion/resource_estimates/df/compute_cost_df_test.py b/src/openfermion/resource_estimates/df/compute_cost_df_test.py index 6632a1446..e87448c7f 100644 --- a/src/openfermion/resource_estimates/df/compute_cost_df_test.py +++ b/src/openfermion/resource_estimates/df/compute_cost_df_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for costing_df.py """ import pytest @@ -9,10 +9,9 @@ from openfermion.resource_estimates import df -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_reiher_df(): - """ Reproduce Reiher et al orbital DF FT costs from paper """ + """Reproduce Reiher et al orbital DF FT costs from paper""" DE = 0.001 CHI = 10 @@ -32,10 +31,9 @@ def test_reiher_df(): assert output == (21753, 10073183463, 3725) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_li_df(): - """ Reproduce Li et al orbital DF FT costs from paper """ + """Reproduce Li et al orbital DF FT costs from paper""" DE = 0.001 CHI = 10 diff --git a/src/openfermion/resource_estimates/df/compute_lambda_df.py b/src/openfermion/resource_estimates/df/compute_lambda_df.py index 342cc0711..3499acacf 100644 --- a/src/openfermion/resource_estimates/df/compute_lambda_df.py +++ b/src/openfermion/resource_estimates/df/compute_lambda_df.py @@ -1,11 +1,11 @@ -#coverage:ignore +# coverage:ignore """ Compute lambda for double low rank factoriz. method of von Burg, et al """ import numpy as np from openfermion.resource_estimates.molecule import pyscf_to_cas def compute_lambda(pyscf_mf, df_factors): - """ Compute lambda for Hamiltonian using DF method of von Burg, et al. + """Compute lambda for Hamiltonian using DF method of von Burg, et al. Args: pyscf_mf - Pyscf mean field object @@ -18,8 +18,7 @@ def compute_lambda(pyscf_mf, df_factors): h1, eri_full, _, _, _ = pyscf_to_cas(pyscf_mf) # one body contributions - T = h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum( - "llij->ij", eri_full) + T = h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum("llij->ij", eri_full) e, _ = np.linalg.eigh(T) lambda_T = np.sum(np.abs(e)) @@ -27,9 +26,9 @@ def compute_lambda(pyscf_mf, df_factors): lambda_F = 0.0 for vector in range(df_factors.shape[2]): Lij = df_factors[:, :, vector] - #e, v = np.linalg.eigh(Lij) + # e, v = np.linalg.eigh(Lij) e = np.linalg.eigvalsh(Lij) # just need eigenvals - lambda_F += 0.25 * np.sum(np.abs(e))**2 + lambda_F += 0.25 * np.sum(np.abs(e)) ** 2 lambda_tot = lambda_T + lambda_F diff --git a/src/openfermion/resource_estimates/df/compute_lambda_df_test.py b/src/openfermion/resource_estimates/df/compute_lambda_df_test.py index c5a1403aa..966ea06cb 100644 --- a/src/openfermion/resource_estimates/df/compute_lambda_df_test.py +++ b/src/openfermion/resource_estimates/df/compute_lambda_df_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for compute_lambda_df.py """ from os import path @@ -12,10 +12,9 @@ from openfermion.resource_estimates.molecule import load_casfile_to_pyscf -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason="pyscf and/or jax not installed.") +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason="pyscf and/or jax not installed.") def test_reiher_df_lambda(): - """ Reproduce Reiher et al orbital DF lambda from paper """ + """Reproduce Reiher et al orbital DF lambda from paper""" THRESH = 0.00125 NAME = path.join(path.dirname(__file__), '../integrals/eri_reiher.h5') diff --git a/src/openfermion/resource_estimates/df/factorize_df.py b/src/openfermion/resource_estimates/df/factorize_df.py index 13cb96876..945440d29 100644 --- a/src/openfermion/resource_estimates/df/factorize_df.py +++ b/src/openfermion/resource_estimates/df/factorize_df.py @@ -1,11 +1,11 @@ -#coverage:ignore +# coverage:ignore """ Double factorization rank reduction of ERIs """ import numpy as np from openfermion.resource_estimates.utils import eigendecomp def factorize(eri_full, thresh): - """ Do double factorization of the ERI tensor + """Do double factorization of the ERI tensor Args: eri_full (np.ndarray) - 4D (N x N x N x N) full ERI tensor diff --git a/src/openfermion/resource_estimates/df/generate_costing_table_df.py b/src/openfermion/resource_estimates/df/generate_costing_table_df.py index 018dee4f9..ff11419e3 100644 --- a/src/openfermion/resource_estimates/df/generate_costing_table_df.py +++ b/src/openfermion/resource_estimates/df/generate_costing_table_df.py @@ -1,21 +1,22 @@ -#coverage:ignore +# coverage:ignore """ Pretty-print a table comparing DF vector thresh vs accuracy and cost """ import numpy as np from pyscf import scf from openfermion.resource_estimates import df -from openfermion.resource_estimates.molecule import (factorized_ccsd_t, - cas_to_pyscf, pyscf_to_cas) - - -def generate_costing_table(pyscf_mf, - name='molecule', - thresh_range=None, - dE=0.001, - chi=10, - beta=20, - use_kernel=True, - no_triples=False): - """ Print a table to file for testing how various DF thresholds impact cost, +from openfermion.resource_estimates.molecule import factorized_ccsd_t, cas_to_pyscf, pyscf_to_cas + + +def generate_costing_table( + pyscf_mf, + name='molecule', + thresh_range=None, + dE=0.001, + chi=10, + beta=20, + use_kernel=True, + no_triples=False, +): + """Print a table to file for testing how various DF thresholds impact cost, accuracy, etc. Args: @@ -62,80 +63,80 @@ def generate_costing_table(pyscf_mf, _, pyscf_mf = cas_to_pyscf(*pyscf_to_cas(pyscf_mf)) # Reference calculation (eri_rr= None is full rank / exact ERIs) - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr=None, - use_kernel=use_kernel, - no_triples=no_triples) + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr=None, use_kernel=use_kernel, no_triples=no_triples + ) - #exact_ecor = ecor + # exact_ecor = ecor exact_etot = etot filename = 'double_factorization_' + name + '.txt' with open(filename, 'w') as f: - print("\n Double low rank factorization data for '" + name + "'.", - file=f) + print("\n Double low rank factorization data for '" + name + "'.", file=f) print(" [*] using " + cas_info, file=f) print(" [+] E(SCF): %18.8f" % escf, file=f) if no_triples: - print(" [+] Active space CCSD E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD E(tot): %18.8f" % etot, file=f) else: - print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, file=f) print("{}".format('=' * 139), file=f) if no_triples: - print("{:^12} {:^18} {:^12} {:^12} {:^12} {:^24} {:^20} {:^20}". - format('threshold', '||ERI - DF||', 'L', 'eigenvectors', - 'lambda', 'CCSD error (mEh)', 'logical qubits', - 'Toffoli count'), - file=f) + print( + "{:^12} {:^18} {:^12} {:^12} {:^12} {:^24} {:^20} {:^20}".format( + 'threshold', + '||ERI - DF||', + 'L', + 'eigenvectors', + 'lambda', + 'CCSD error (mEh)', + 'logical qubits', + 'Toffoli count', + ), + file=f, + ) else: - print("{:^12} {:^18} {:^12} {:^12} {:^12} {:^24} {:^20} {:^20}". - format('threshold', '||ERI - DF||', 'L', 'eigenvectors', - 'lambda', 'CCSD(T) error (mEh)', 'logical qubits', - 'Toffoli count'), - file=f) + print( + "{:^12} {:^18} {:^12} {:^12} {:^12} {:^24} {:^20} {:^20}".format( + 'threshold', + '||ERI - DF||', + 'L', + 'eigenvectors', + 'lambda', + 'CCSD(T) error (mEh)', + 'logical qubits', + 'Toffoli count', + ), + file=f, + ) print("{}".format('-' * 139), file=f) for thresh in thresh_range: # First, up: lambda and CCSD(T) eri_rr, LR, L, Lxi = df.factorize(pyscf_mf._eri, thresh=thresh) lam = df.compute_lambda(pyscf_mf, LR) - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr, - use_kernel=use_kernel, - no_triples=no_triples) - error = (etot - exact_etot) * 1E3 # to mEh - l2_norm_error_eri = np.linalg.norm( - eri_rr - pyscf_mf._eri) # ERI reconstruction error + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr, use_kernel=use_kernel, no_triples=no_triples + ) + error = (etot - exact_etot) * 1e3 # to mEh + l2_norm_error_eri = np.linalg.norm(eri_rr - pyscf_mf._eri) # ERI reconstruction error # now do costing - stps1 = df.compute_cost(num_spinorb, - lam, - DE, - L=L, - Lxi=Lxi, - chi=CHI, - beta=BETA, - stps=20000)[0] - _, df_total_cost, df_logical_qubits = df.compute_cost(num_spinorb, - lam, - DE, - L=L, - Lxi=Lxi, - chi=CHI, - beta=BETA, - stps=stps1) + stps1 = df.compute_cost(num_spinorb, lam, DE, L=L, Lxi=Lxi, chi=CHI, beta=BETA, stps=20000)[ + 0 + ] + _, df_total_cost, df_logical_qubits = df.compute_cost( + num_spinorb, lam, DE, L=L, Lxi=Lxi, chi=CHI, beta=BETA, stps=stps1 + ) with open(filename, 'a') as f: print( "{:^12.6f} {:^18.4e} {:^12} {:^12} {:^12.1f} {:^24.2f} {:^20} \ - {:^20.1e}".format(thresh, l2_norm_error_eri, L, Lxi, lam, - error, df_logical_qubits, df_total_cost), - file=f) + {:^20.1e}".format( + thresh, l2_norm_error_eri, L, Lxi, lam, error, df_logical_qubits, df_total_cost + ), + file=f, + ) with open(filename, 'a') as f: print("{}".format('=' * 139), file=f) diff --git a/src/openfermion/resource_estimates/molecule/__init__.py b/src/openfermion/resource_estimates/molecule/__init__.py index b9713f52c..37656fa04 100644 --- a/src/openfermion/resource_estimates/molecule/__init__.py +++ b/src/openfermion/resource_estimates/molecule/__init__.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # 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 @@ -14,7 +14,16 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from .pyscf_utils import (avas_active_space, cas_to_pyscf, ccsd_t, - factorized_ccsd_t, get_num_active_alpha_beta, - load_casfile_to_pyscf, localize, open_shell_t1_d1, - pyscf_to_cas, save_pyscf_to_casfile, stability) + from .pyscf_utils import ( + avas_active_space, + cas_to_pyscf, + ccsd_t, + factorized_ccsd_t, + get_num_active_alpha_beta, + load_casfile_to_pyscf, + localize, + open_shell_t1_d1, + pyscf_to_cas, + save_pyscf_to_casfile, + stability, + ) diff --git a/src/openfermion/resource_estimates/molecule/pyscf_utils.py b/src/openfermion/resource_estimates/molecule/pyscf_utils.py index f70ff636a..4c22498f6 100644 --- a/src/openfermion/resource_estimates/molecule/pyscf_utils.py +++ b/src/openfermion/resource_estimates/molecule/pyscf_utils.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Drivers for various PySCF electronic structure routines """ from typing import Tuple, Optional import sys @@ -26,7 +26,7 @@ def stability(pyscf_mf): def localize(pyscf_mf, loc_type='pm', verbose=0): - """ Localize orbitals given a PySCF mean-field object + """Localize orbitals given a PySCF mean-field object Args: pyscf_mf: PySCF mean field object @@ -44,52 +44,42 @@ def localize(pyscf_mf, loc_type='pm', verbose=0): # followed by calling mf.kernel()). But consistent localization is not a # given (not unique) despite restoring data this way, hence the message. if len(pyscf_mf.mol.atom) == 0: - sys.exit("`localize()` requires atom loc. and atomic basis to be" + \ - " defined.\n " + \ - "It also can be sensitive to the initial guess and MO" + \ - " coefficients.\n " + \ - "Best to try re-creating the PySCF molecule and doing the" + \ - " SCF, rather than\n " + \ - "try to load the mean-field object with" + \ - " `load_casfile_to_pyscf()`. You can \n " + \ - "try to provide the missing information, but consistency" + \ - " cannot be guaranteed!") + sys.exit( + "`localize()` requires atom loc. and atomic basis to be" + + " defined.\n " + + "It also can be sensitive to the initial guess and MO" + + " coefficients.\n " + + "Best to try re-creating the PySCF molecule and doing the" + + " SCF, rather than\n " + + "try to load the mean-field object with" + + " `load_casfile_to_pyscf()`. You can \n " + + "try to provide the missing information, but consistency" + + " cannot be guaranteed!" + ) # Split-localize (localize DOCC, SOCC, and virtual separately) - docc_idx = np.where(np.isclose(pyscf_mf.mo_occ, 2.))[0] - socc_idx = np.where(np.isclose(pyscf_mf.mo_occ, 1.))[0] - virt_idx = np.where(np.isclose(pyscf_mf.mo_occ, 0.))[0] + docc_idx = np.where(np.isclose(pyscf_mf.mo_occ, 2.0))[0] + socc_idx = np.where(np.isclose(pyscf_mf.mo_occ, 1.0))[0] + virt_idx = np.where(np.isclose(pyscf_mf.mo_occ, 0.0))[0] # Pipek-Mezey if loc_type.lower() == 'pm': print("Localizing doubly occupied ... ", end="") - loc_docc_mo = lo.PM( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, docc_idx]).kernel(verbose=verbose) + loc_docc_mo = lo.PM(pyscf_mf.mol, pyscf_mf.mo_coeff[:, docc_idx]).kernel(verbose=verbose) print("singly occupied ... ", end="") - loc_socc_mo = lo.PM( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, socc_idx]).kernel(verbose=verbose) + loc_socc_mo = lo.PM(pyscf_mf.mol, pyscf_mf.mo_coeff[:, socc_idx]).kernel(verbose=verbose) print("virtual ... ", end="") - loc_virt_mo = lo.PM( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, virt_idx]).kernel(verbose=verbose) + loc_virt_mo = lo.PM(pyscf_mf.mol, pyscf_mf.mo_coeff[:, virt_idx]).kernel(verbose=verbose) print("DONE") # Edmiston-Rudenberg elif loc_type.lower() == 'er': print("Localizing doubly occupied ... ", end="") - loc_docc_mo = lo.ER( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, docc_idx]).kernel(verbose=verbose) + loc_docc_mo = lo.ER(pyscf_mf.mol, pyscf_mf.mo_coeff[:, docc_idx]).kernel(verbose=verbose) print("singly occupied ... ", end="") - loc_socc_mo = lo.ER( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, socc_idx]).kernel(verbose=verbose) + loc_socc_mo = lo.ER(pyscf_mf.mol, pyscf_mf.mo_coeff[:, socc_idx]).kernel(verbose=verbose) print("virtual ... ", end="") - loc_virt_mo = lo.ER( - pyscf_mf.mol, - pyscf_mf.mo_coeff[:, virt_idx]).kernel(verbose=verbose) + loc_virt_mo = lo.ER(pyscf_mf.mol, pyscf_mf.mo_coeff[:, virt_idx]).kernel(verbose=verbose) print("DONE") # overwrite orbitals with localized orbitals @@ -100,11 +90,8 @@ def localize(pyscf_mf, loc_type='pm', verbose=0): return pyscf_mf -def avas_active_space(pyscf_mf, - ao_list=None, - molden_fname='avas_localized_orbitals', - **kwargs): - """ Return AVAS active space as PySCF molecule and mean-field object +def avas_active_space(pyscf_mf, ao_list=None, molden_fname='avas_localized_orbitals', **kwargs): + """Return AVAS active space as PySCF molecule and mean-field object Args: pyscf_mf: PySCF mean field object @@ -128,11 +115,7 @@ def avas_active_space(pyscf_mf, # Note: requires openshell_option = 3 for this to work, which keeps all # singly occupied in CAS # we also require canonicalize = False so that we don't destroy local orbs - avas_output = avas.avas(pyscf_mf, - ao_list, - canonicalize=False, - openshell_option=3, - **kwargs) + avas_output = avas.avas(pyscf_mf, ao_list, canonicalize=False, openshell_option=3, **kwargs) active_norb, active_ne, reordered_orbitals = avas_output active_alpha, _ = get_num_active_alpha_beta(pyscf_mf, active_ne) @@ -148,25 +131,26 @@ def avas_active_space(pyscf_mf, active_space_idx = slice(frozen_alpha, frozen_alpha + active_norb) active_mos = reordered_orbitals[:, active_space_idx] - tools.molden.from_mo(pyscf_mf.mol, - molden_fname + '.molden', - mo_coeff=active_mos) + tools.molden.from_mo(pyscf_mf.mol, molden_fname + '.molden', mo_coeff=active_mos) # Choosing an active space changes the molecule ("freezing" electrons, # for example), so we # form the active space tensors first, then re-form the PySCF objects to # ensure consistency pyscf_active_space_mol, pyscf_active_space_mf = cas_to_pyscf( - *pyscf_to_cas(pyscf_mf, - cas_orbitals=active_norb, - cas_electrons=active_ne, - avas_orbs=reordered_orbitals)) + *pyscf_to_cas( + pyscf_mf, + cas_orbitals=active_norb, + cas_electrons=active_ne, + avas_orbs=reordered_orbitals, + ) + ) return pyscf_active_space_mol, pyscf_active_space_mf def cas_to_pyscf(h1, eri, ecore, num_alpha, num_beta): - """ Return a PySCF molecule and mean-field object from pre-computed CAS Ham + """Return a PySCF molecule and mean-field object from pre-computed CAS Ham Args: h1 (ndarray) - 2D matrix containing one-body terms (MO basis) @@ -193,12 +177,12 @@ def cas_to_pyscf(h1, eri, ecore, num_alpha, num_beta): # have two h1s, etc. if num_alpha == num_beta: pyscf_mf = scf.RHF(pyscf_mol) - scf_energy = ecore + \ - 2*np.einsum('ii', h1[:num_alpha,:num_alpha]) + \ - 2*np.einsum('iijj', - eri[:num_alpha,:num_alpha,:num_alpha,:num_alpha]) - \ - np.einsum('ijji', - eri[:num_alpha,:num_alpha,:num_alpha,:num_alpha]) + scf_energy = ( + ecore + + 2 * np.einsum('ii', h1[:num_alpha, :num_alpha]) + + 2 * np.einsum('iijj', eri[:num_alpha, :num_alpha, :num_alpha, :num_alpha]) + - np.einsum('ijji', eri[:num_alpha, :num_alpha, :num_alpha, :num_alpha]) + ) else: pyscf_mf = scf.ROHF(pyscf_mol) @@ -206,17 +190,19 @@ def cas_to_pyscf(h1, eri, ecore, num_alpha, num_beta): # grab singly and doubly occupied orbitals (assume high-spin open shell) docc = slice(None, min(num_alpha, num_beta)) socc = slice(min(num_alpha, num_beta), max(num_alpha, num_beta)) - scf_energy = ecore + \ - 2.0*np.einsum('ii',h1[docc, docc]) + \ - np.einsum('ii',h1[socc, socc]) + \ - 2.0*np.einsum('iijj',eri[docc, docc, docc, docc]) - \ - np.einsum('ijji',eri[docc, docc, docc, docc]) + \ - np.einsum('iijj',eri[socc, socc, docc, docc]) - \ - 0.5*np.einsum('ijji',eri[socc, docc, docc, socc]) + \ - np.einsum('iijj',eri[docc, docc, socc, socc]) - \ - 0.5*np.einsum('ijji',eri[docc, socc, socc, docc]) + \ - 0.5*np.einsum('iijj',eri[socc, socc, socc, socc]) - \ - 0.5*np.einsum('ijji',eri[socc, socc, socc, socc]) + scf_energy = ( + ecore + + 2.0 * np.einsum('ii', h1[docc, docc]) + + np.einsum('ii', h1[socc, socc]) + + 2.0 * np.einsum('iijj', eri[docc, docc, docc, docc]) + - np.einsum('ijji', eri[docc, docc, docc, docc]) + + np.einsum('iijj', eri[socc, socc, docc, docc]) + - 0.5 * np.einsum('ijji', eri[socc, docc, docc, socc]) + + np.einsum('iijj', eri[docc, docc, socc, socc]) + - 0.5 * np.einsum('ijji', eri[docc, socc, socc, docc]) + + 0.5 * np.einsum('iijj', eri[socc, socc, socc, socc]) + - 0.5 * np.einsum('ijji', eri[socc, socc, socc, socc]) + ) pyscf_mf.get_hcore = lambda *args: np.asarray(h1) pyscf_mf.get_ovlp = lambda *args: np.eye(h1.shape[0]) @@ -232,11 +218,13 @@ def cas_to_pyscf(h1, eri, ecore, num_alpha, num_beta): return pyscf_mol, pyscf_mf -def pyscf_to_cas(pyscf_mf, - cas_orbitals: Optional[int] = None, - cas_electrons: Optional[int] = None, - avas_orbs=None): - """ Return CAS Hamiltonian tensors from a PySCF mean-field object +def pyscf_to_cas( + pyscf_mf, + cas_orbitals: Optional[int] = None, + cas_electrons: Optional[int] = None, + avas_orbs=None, +): + """Return CAS Hamiltonian tensors from a PySCF mean-field object Args: pyscf_mf: PySCF mean field object @@ -274,7 +262,7 @@ def pyscf_to_cas(pyscf_mf, def get_num_active_alpha_beta(pyscf_mf, cas_electrons): - """ Return number of alpha and beta electrons in the active space given + """Return number of alpha and beta electrons in the active space given number of CAS electrons This assumes that all the unpaired electrons are in the active space @@ -307,10 +295,8 @@ def get_num_active_alpha_beta(pyscf_mf, cas_electrons): return num_alpha, num_beta -def load_casfile_to_pyscf(fname, - num_alpha: Optional[int] = None, - num_beta: Optional[int] = None): - """ Load CAS Hamiltonian from pre-computed HD5 file into a PySCF molecule +def load_casfile_to_pyscf(fname, num_alpha: Optional[int] = None, num_beta: Optional[int] = None): + """Load CAS Hamiltonian from pre-computed HD5 file into a PySCF molecule and mean-field object Args: @@ -350,31 +336,37 @@ def load_casfile_to_pyscf(fname, try: num_alpha = int(f['active_nalpha'][()]) except KeyError: - sys.exit("In `load_casfile_to_pyscf()`: \n" + \ - " No values found on file for num_alpha " + \ - "(key: 'active_nalpha' in h5). " + \ - " Try passing in a value for num_alpha, or" + \ - " re-check integral file.") + sys.exit( + "In `load_casfile_to_pyscf()`: \n" + + " No values found on file for num_alpha " + + "(key: 'active_nalpha' in h5). " + + " Try passing in a value for num_alpha, or" + + " re-check integral file." + ) try: num_beta = int(f['active_nbeta'][()]) except KeyError: - sys.exit("In `load_casfile_to_pyscf()`: \n" + \ - " No values found on file for num_beta " + \ - "(key: 'active_nbeta' in h5). " + \ - " Try passing in a value for num_beta, or" + \ - " re-check integral file.") + sys.exit( + "In `load_casfile_to_pyscf()`: \n" + + " No values found on file for num_beta " + + "(key: 'active_nbeta' in h5). " + + " Try passing in a value for num_beta, or" + + " re-check integral file." + ) pyscf_mol, pyscf_mf = cas_to_pyscf(h1, eri, ecore, num_alpha, num_beta) return pyscf_mol, pyscf_mf -def save_pyscf_to_casfile(fname, - pyscf_mf, - cas_orbitals: Optional[int] = None, - cas_electrons: Optional[int] = None, - avas_orbs=None): - """ Save CAS Hamiltonian from a PySCF mean-field object to an HD5 file +def save_pyscf_to_casfile( + fname, + pyscf_mf, + cas_orbitals: Optional[int] = None, + cas_electrons: Optional[int] = None, + avas_orbs=None, +): + """Save CAS Hamiltonian from a PySCF mean-field object to an HD5 file Args: fname (str): path to hd5 file to be created containing CAS terms @@ -383,22 +375,22 @@ def save_pyscf_to_casfile(fname, cas_electrons (int, optional): number of elec in CAS, default all elec avas_orbs (ndarray, optional): orbitals selected by AVAS in PySCF """ - h1, eri, ecore, num_alpha, num_beta = \ - pyscf_to_cas(pyscf_mf, cas_orbitals, cas_electrons, avas_orbs) + h1, eri, ecore, num_alpha, num_beta = pyscf_to_cas( + pyscf_mf, cas_orbitals, cas_electrons, avas_orbs + ) with h5py.File(fname, 'w') as fid: fid.create_dataset('ecore', data=float(ecore), dtype=float) - fid.create_dataset( - 'h0', - data=h1) # note the name change to be consistent with THC paper + fid.create_dataset('h0', data=h1) # note the name change to be consistent with THC paper fid.create_dataset('eri', data=eri) fid.create_dataset('active_nalpha', data=int(num_alpha), dtype=int) fid.create_dataset('active_nbeta', data=int(num_beta), dtype=int) -def factorized_ccsd_t(pyscf_mf, eri_rr = None, use_kernel = True,\ - no_triples=False) -> Tuple[float, float, float]: - """ Compute CCSD(T) energy using rank-reduced ERIs +def factorized_ccsd_t( + pyscf_mf, eri_rr=None, use_kernel=True, no_triples=False +) -> Tuple[float, float, float]: + """Compute CCSD(T) energy using rank-reduced ERIs Args: pyscf_mf - PySCF mean field object @@ -417,15 +409,17 @@ def factorized_ccsd_t(pyscf_mf, eri_rr = None, use_kernel = True,\ if eri_rr is None: eri_rr = eri_full - e_scf, e_cor, e_tot = ccsd_t(h1, eri_rr, ecore, num_alpha, num_beta,\ - eri_full, use_kernel, no_triples) + e_scf, e_cor, e_tot = ccsd_t( + h1, eri_rr, ecore, num_alpha, num_beta, eri_full, use_kernel, no_triples + ) return e_scf, e_cor, e_tot -def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ - use_kernel=True, no_triples=False) -> Tuple[float, float, float]: - """ Helper function to do CCSD(T) on set of one- and two-body Hamil elems +def ccsd_t( + h1, eri, ecore, num_alpha: int, num_beta: int, eri_full=None, use_kernel=True, no_triples=False +) -> Tuple[float, float, float]: + """Helper function to do CCSD(T) on set of one- and two-body Hamil elems Args: h1 (ndarray) - 2D matrix containing one-body terms (MO basis) @@ -458,16 +452,12 @@ def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ # either RHF or ROHF ... should be OK since UHF will have two h1s, etc. if num_alpha == num_beta: mf = scf.RHF(mol) - scf_energy = ecore + \ - 2*np.einsum('ii',h1[:num_alpha,:num_alpha]) + \ - 2*np.einsum('iijj',eri_full[:num_alpha,\ - :num_alpha,\ - :num_alpha,\ - :num_alpha]) - \ - np.einsum('ijji',eri_full[:num_alpha,\ - :num_alpha,\ - :num_alpha,\ - :num_alpha]) + scf_energy = ( + ecore + + 2 * np.einsum('ii', h1[:num_alpha, :num_alpha]) + + 2 * np.einsum('iijj', eri_full[:num_alpha, :num_alpha, :num_alpha, :num_alpha]) + - np.einsum('ijji', eri_full[:num_alpha, :num_alpha, :num_alpha, :num_alpha]) + ) else: mf = scf.ROHF(mol) @@ -475,17 +465,19 @@ def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ # grab singly and doubly occupied orbitals (assume high-spin open shell) docc = slice(None, min(num_alpha, num_beta)) socc = slice(min(num_alpha, num_beta), max(num_alpha, num_beta)) - scf_energy = ecore + \ - 2.0*np.einsum('ii',h1[docc, docc]) + \ - np.einsum('ii',h1[socc, socc]) + \ - 2.0*np.einsum('iijj',eri_full[docc, docc, docc, docc]) - \ - np.einsum('ijji',eri_full[docc, docc, docc, docc]) + \ - np.einsum('iijj',eri_full[socc, socc, docc, docc]) - \ - 0.5*np.einsum('ijji',eri_full[socc, docc, docc, socc]) + \ - np.einsum('iijj',eri_full[docc, docc, socc, socc]) - \ - 0.5*np.einsum('ijji',eri_full[docc, socc, socc, docc]) + \ - 0.5*np.einsum('iijj',eri_full[socc, socc, socc, socc]) - \ - 0.5*np.einsum('ijji',eri_full[socc, socc, socc, socc]) + scf_energy = ( + ecore + + 2.0 * np.einsum('ii', h1[docc, docc]) + + np.einsum('ii', h1[socc, socc]) + + 2.0 * np.einsum('iijj', eri_full[docc, docc, docc, docc]) + - np.einsum('ijji', eri_full[docc, docc, docc, docc]) + + np.einsum('iijj', eri_full[socc, socc, docc, docc]) + - 0.5 * np.einsum('ijji', eri_full[socc, docc, docc, socc]) + + np.einsum('iijj', eri_full[docc, docc, socc, socc]) + - 0.5 * np.einsum('ijji', eri_full[docc, socc, socc, docc]) + + 0.5 * np.einsum('iijj', eri_full[socc, socc, socc, socc]) + - 0.5 * np.einsum('ijji', eri_full[socc, socc, socc, socc]) + ) mf.get_hcore = lambda *args: np.asarray(h1) mf.get_ovlp = lambda *args: np.eye(h1.shape[0]) @@ -510,8 +502,7 @@ def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ mf.level_shift = 0.5 mf.conv_check = False mf.max_cycle = 800 - mf.kernel(mf.make_rdm1(mf.mo_coeff, - mf.mo_occ)) # use MO info to generate guess + mf.kernel(mf.make_rdm1(mf.mo_coeff, mf.mo_occ)) # use MO info to generate guess mf = stability(mf) mf = stability(mf) mf = stability(mf) @@ -521,12 +512,10 @@ def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ assert np.isclose(scf_energy, mf.e_tot, rtol=1e-14) except AssertionError: print( - "WARNING: E(SCF) from input integrals does not match E(SCF)" + \ - " from mf.kernel()") - print(" Will use E(SCF) = {:12.6f} from mf.kernel going forward.". - format(mf.e_tot)) - print("E(SCF, ints) = {:12.6f} whereas E(SCF) = {:12.6f}".format( - scf_energy, mf.e_tot)) + "WARNING: E(SCF) from input integrals does not match E(SCF)" + " from mf.kernel()" + ) + print(" Will use E(SCF) = {:12.6f} from mf.kernel going forward.".format(mf.e_tot)) + print("E(SCF, ints) = {:12.6f} whereas E(SCF) = {:12.6f}".format(scf_energy, mf.e_tot)) # New SCF energy and orbitals for CCSD(T) scf_energy = mf.e_tot @@ -537,8 +526,8 @@ def ccsd_t(h1, eri, ecore, num_alpha: int, num_beta: int, eri_full = None,\ mycc = cc.CCSD(mf) mycc.max_cycle = 800 - mycc.conv_tol = 1E-8 - mycc.conv_tol_normt = 1E-4 + mycc.conv_tol = 1e-8 + mycc.conv_tol_normt = 1e-4 mycc.diis_space = 24 mycc.verbose = 4 mycc.kernel() @@ -588,14 +577,14 @@ def open_shell_t1_d1(t1a, t1b, mo_occ, nalpha, nbeta): doi: 10.1063/1.464352. """ # compute t1-diagnostic - docc_idx = np.where(np.isclose(mo_occ, 2.))[0] - socc_idx = np.where(np.isclose(mo_occ, 1.))[0] - virt_idx = np.where(np.isclose(mo_occ, 0.))[0] + docc_idx = np.where(np.isclose(mo_occ, 2.0))[0] + socc_idx = np.where(np.isclose(mo_occ, 1.0))[0] + virt_idx = np.where(np.isclose(mo_occ, 0.0))[0] t1a_docc = t1a[docc_idx, :] # double occ-> virtual - t1b_docc = t1b[docc_idx, :][:, -len(virt_idx):] # double occ-> virtual + t1b_docc = t1b[docc_idx, :][:, -len(virt_idx) :] # double occ-> virtual if len(socc_idx) > 0: t1_xa = t1a[socc_idx, :] # single occ -> virtual - t1_ix = t1b[docc_idx, :][:, :len(socc_idx)] # double occ -> single occ + t1_ix = t1b[docc_idx, :][:, : len(socc_idx)] # double occ -> single occ else: t1_xa = np.array(()) t1_ix = np.array(()) @@ -603,7 +592,9 @@ def open_shell_t1_d1(t1a, t1b, mo_occ, nalpha, nbeta): if nalpha - nbeta + len(virt_idx) != t1b.shape[1]: raise ValueError( "Inconsistent shapes na {}, nb {}, t1b.shape {},{}".format( - nalpha, nbeta, t1b.shape[0], t1b.shape[1])) + nalpha, nbeta, t1b.shape[0], t1b.shape[1] + ) + ) if t1a_docc.shape != (len(docc_idx), len(virt_idx)): raise ValueError("T1a_ia does not have the right shape") @@ -616,8 +607,8 @@ def open_shell_t1_d1(t1a, t1b, mo_occ, nalpha, nbeta): raise ValueError("T1_xa does not have the right shape") t1_diagnostic = np.sqrt( - np.sum((t1a_docc + t1b_docc)**2) + 2 * np.sum(t1_xa**2) + - 2 * np.sum(t1_ix**2)) / (2 * np.sqrt(nalpha + nbeta)) + np.sum((t1a_docc + t1b_docc) ** 2) + 2 * np.sum(t1_xa**2) + 2 * np.sum(t1_ix**2) + ) / (2 * np.sqrt(nalpha + nbeta)) # compute D1-diagnostic f_ia = 0.5 * (t1a_docc + t1b_docc) s_f_ia_2, _ = np.linalg.eigh(f_ia @ f_ia.T) @@ -634,7 +625,6 @@ def open_shell_t1_d1(t1a, t1b, mo_occ, nalpha, nbeta): s_f_xa_2_norm = np.sqrt(np.max(s_f_xa_2, initial=0)) s_f_ix_2_norm = np.sqrt(np.max(s_f_ix_2, initial=0)) - d1_diagnostic = np.max( - np.array([s_f_ia_2_norm, s_f_xa_2_norm, s_f_ix_2_norm])) + d1_diagnostic = np.max(np.array([s_f_ia_2_norm, s_f_xa_2_norm, s_f_ix_2_norm])) return t1_diagnostic, d1_diagnostic diff --git a/src/openfermion/resource_estimates/molecule/pyscf_utils_test.py b/src/openfermion/resource_estimates/molecule/pyscf_utils_test.py index c092efee2..864ddc23c 100644 --- a/src/openfermion/resource_estimates/molecule/pyscf_utils_test.py +++ b/src/openfermion/resource_estimates/molecule/pyscf_utils_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for pyscf_utils.py """ import unittest @@ -14,18 +14,21 @@ from openfermion.resource_estimates import df, sf from openfermion.resource_estimates.molecule import ( - ccsd_t, factorized_ccsd_t, load_casfile_to_pyscf, open_shell_t1_d1, - pyscf_to_cas, stability) + ccsd_t, + factorized_ccsd_t, + load_casfile_to_pyscf, + open_shell_t1_d1, + pyscf_to_cas, + stability, + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow class OpenFermionPyscfUtilsTest(unittest.TestCase): - def test_full_ccsd_t(self): - """ Test resource_estimates full CCSD(T) from h1/eri/ecore tensors - matches regular PySCF CCSD(T) + """Test resource_estimates full CCSD(T) from h1/eri/ecore tensors + matches regular PySCF CCSD(T) """ for scf_type in ['rhf', 'rohf']: @@ -53,8 +56,8 @@ def test_full_ccsd_t(self): # Do PySCF CCSD(T) mycc = cc.CCSD(mf) mycc.max_cycle = 500 - mycc.conv_tol = 1E-9 - mycc.conv_tol_normt = 1E-5 + mycc.conv_tol = 1e-9 + mycc.conv_tol_normt = 1e-5 mycc.diis_space = 24 mycc.diis_start_cycle = 4 mycc.kernel() @@ -68,18 +71,15 @@ def test_full_ccsd_t(self): n_elec = mol.nelectron n_orb = mf.mo_coeff[0].shape[-1] - resource_estimates_results = ccsd_t( - *pyscf_to_cas(mf, n_orb, n_elec)) + resource_estimates_results = ccsd_t(*pyscf_to_cas(mf, n_orb, n_elec)) resource_estimates_results = np.asarray(resource_estimates_results) # ignore relative tolerance, we just want absolute tolerance - assert np.allclose(pyscf_results, - resource_estimates_results, - rtol=1E-14) + assert np.allclose(pyscf_results, resource_estimates_results, rtol=1e-14) def test_reduced_ccsd_t(self): - """ Test resource_estimates reduced (2e space) CCSD(T) from tensors - matches PySCF CAS(2e,No) + """Test resource_estimates reduced (2e space) CCSD(T) from tensors + matches PySCF CAS(2e,No) """ for scf_type in ['rhf', 'rohf']: @@ -113,35 +113,34 @@ def test_reduced_ccsd_t(self): # Don't do triples (it's zero anyway for 2e) b/c div by zero w/ ROHF _, _, resource_estimates_etot = ccsd_t( - *pyscf_to_cas(mf, n_orb, n_elec), no_triples=True) + *pyscf_to_cas(mf, n_orb, n_elec), no_triples=True + ) # ignore relative tolerance, we just want absolute tolerance - assert np.isclose(pyscf_etot, resource_estimates_etot, rtol=1E-14) + assert np.isclose(pyscf_etot, resource_estimates_etot, rtol=1e-14) def test_reiher_sf_ccsd_t(self): - """ Reproduce Reiher et al FeMoco SF CCSD(T) errors from paper """ + """Reproduce Reiher et al FeMoco SF CCSD(T) errors from paper""" NAME = path.join(path.dirname(__file__), '../integrals/eri_reiher.h5') _, mf = load_casfile_to_pyscf(NAME, num_alpha=27, num_beta=27) - _, ecorr, _ = factorized_ccsd_t( - mf, eri_rr=None) # use full (local) ERIs for 2-body + _, ecorr, _ = factorized_ccsd_t(mf, eri_rr=None) # use full (local) ERIs for 2-body exact_energy = ecorr rank = 100 eri_rr, _ = sf.factorize(mf._eri, rank) _, ecorr, _ = factorized_ccsd_t(mf, eri_rr) appx_energy = ecorr - error = (appx_energy - exact_energy) * 1E3 # mEh + error = (appx_energy - exact_energy) * 1e3 # mEh assert np.isclose(np.round(error, decimals=2), 1.55) def test_reiher_df_ccsd_t(self): - """ Reproduce Reiher et al FeMoco DF CCSD(T) errors from paper """ + """Reproduce Reiher et al FeMoco DF CCSD(T) errors from paper""" NAME = path.join(path.dirname(__file__), '../integrals/eri_reiher.h5') _, mf = load_casfile_to_pyscf(NAME, num_alpha=27, num_beta=27) - _, ecorr, _ = factorized_ccsd_t( - mf, eri_rr=None) # use full (local) ERIs for 2-body + _, ecorr, _ = factorized_ccsd_t(mf, eri_rr=None) # use full (local) ERIs for 2-body exact_energy = ecorr appx_energy = [] THRESH = 0.00125 @@ -149,7 +148,7 @@ def test_reiher_df_ccsd_t(self): _, ecorr, _ = factorized_ccsd_t(mf, eri_rr) appx_energy = ecorr - error = (appx_energy - exact_energy) * 1E3 # mEh + error = (appx_energy - exact_energy) * 1e3 # mEh assert np.isclose(np.round(error, decimals=2), 0.44) @@ -174,8 +173,8 @@ def test_t1_d1_openshell(self): mycc_uhf.kernel() t1a, t1b = mycc_uhf.t1 test_t1d, test_d1d = open_shell_t1_d1( - t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], - uhf_mf.nelec[1]) + t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], uhf_mf.nelec[1] + ) assert np.isclose(test_t1d, true_t1d) assert np.isclose(test_d1d, true_d1d) @@ -232,8 +231,8 @@ def test_t1_d1_oxygen(self): t1a, t1b = mycc_uhf.t1 test_t1d, test_d1d = open_shell_t1_d1( - t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], - uhf_mf.nelec[1]) + t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], uhf_mf.nelec[1] + ) assert np.isclose(mf.e_tot, -149.651708, atol=1e-6) assert np.isclose(mycc_uhf.e_corr, -0.464507, atol=1e-6) @@ -256,6 +255,6 @@ def test_t1_d1_bound(self): mycc_uhf.kernel() t1a, t1b = mycc_uhf.t1 test_t1d, test_d1d = open_shell_t1_d1( - t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], - uhf_mf.nelec[1]) + t1a, t1b, uhf_mf.mo_occ[0] + uhf_mf.mo_occ[1], uhf_mf.nelec[0], uhf_mf.nelec[1] + ) assert np.sqrt(2) * test_t1d <= test_d1d diff --git a/src/openfermion/resource_estimates/pbc/df/compute_df_resources.py b/src/openfermion/resource_estimates/pbc/df/compute_df_resources.py index 43379dc46..ec6488877 100644 --- a/src/openfermion/resource_estimates/pbc/df/compute_df_resources.py +++ b/src/openfermion/resource_estimates/pbc/df/compute_df_resources.py @@ -18,14 +18,10 @@ from openfermion.resource_estimates.utils import QI -from openfermion.resource_estimates.pbc.resources import ( - ResourceEstimates, - QR3, -) +from openfermion.resource_estimates.pbc.resources import ResourceEstimates, QR3 -def compute_beta_for_resources(num_spin_orbs: int, num_kpts: int, - de_for_qpe: float): +def compute_beta_for_resources(num_spin_orbs: int, num_kpts: int, de_for_qpe: float): """Compute beta (number of bits for controlled rotations). Uses expression from https://arxiv.org/pdf/2007.14460.pdf. @@ -39,14 +35,14 @@ def compute_beta_for_resources(num_spin_orbs: int, num_kpts: int, def compute_cost( - num_spin_orbs: int, - lambda_tot: float, - num_aux: int, - num_eig: int, - kmesh: List[int], - dE_for_qpe: float = 0.0016, - chi: int = 10, - beta: Union[int, None] = None, + num_spin_orbs: int, + lambda_tot: float, + num_aux: int, + num_eig: int, + kmesh: List[int], + dE_for_qpe: float = 0.0016, + chi: int = 10, + beta: Union[int, None] = None, ) -> ResourceEstimates: """Determine fault-tolerant costs using double factorized Hamiltonian. @@ -95,26 +91,24 @@ def compute_cost( steps=steps, ) estimates = ResourceEstimates( - toffolis_per_step=final_cost[0], - total_toffolis=final_cost[1], - logical_qubits=final_cost[2], + toffolis_per_step=final_cost[0], total_toffolis=final_cost[1], logical_qubits=final_cost[2] ) return estimates def _compute_cost( - n: int, - lam: float, - dE: float, - L: int, - Lxi: int, - chi: int, - beta: int, - Nkx: int, - Nky: int, - Nkz: int, - steps: int, - verbose: bool = False, + n: int, + lam: float, + dE: float, + L: int, + Lxi: int, + chi: int, + beta: int, + Nkx: int, + Nky: int, + Nkz: int, + steps: int, + verbose: bool = False, ) -> Tuple[int, int, int]: """Determine fault-tolerant costs using DF decomposition. @@ -139,8 +133,11 @@ def _compute_cost( total_cost: Total number of Toffolis ancilla_cost: Total ancilla cost """ - nNk = (max(np.ceil(np.log2(Nkx)), 1) + max(np.ceil(np.log2(Nky)), 1) + - max(np.ceil(np.log2(Nkz)), 1)) + nNk = ( + max(np.ceil(np.log2(Nkx)), 1) + + max(np.ceil(np.log2(Nky)), 1) + + max(np.ceil(np.log2(Nkz)), 1) + ) Nk = Nkx * Nky * Nkz # The number of bits used for the second register. @@ -162,11 +159,29 @@ def _compute_cost( oh = [0] * 20 for p in range(20): v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nL) / np.sqrt((L + 1) / 2**eta) / 2)) - oh[p] = np.real(steps * (1 / (np.sin(3 * arcsin( - np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt( - (L + 1) / 2**eta) / np.power(2, nL)))**2) - 1) + 4 * (p + 1)) + np.power(2, p + 1) + / (2 * np.pi) + * arccos(np.power(2, nL) / np.sqrt((L + 1) / 2**eta) / 2) + ) + oh[p] = np.real( + steps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt((L + 1) / 2**eta) + / np.power(2, nL) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br = int(np.argmin(oh) + 1) @@ -218,8 +233,9 @@ def _compute_cost( # The cost of the QROMs and inverse QROMs for the state preparation, where # in the first one we need + n/2 to account for the one-electron terms. - cost3c = (QR3(Lxi + Nk * n // 2, bp2)[1] + QI(Lxi + Nk * n // 2)[1] + - QR3(Lxi, bp2)[1] + QI(Lxi)[1]) + cost3c = ( + QR3(Lxi + Nk * n // 2, bp2)[1] + QI(Lxi + Nk * n // 2)[1] + QR3(Lxi, bp2)[1] + QI(Lxi)[1] + ) # The inequality test and state preparations. cost3d = 4 * (nxi + chi) @@ -231,8 +247,12 @@ def _compute_cost( cost4ah = 4 * (nLxi - 1) # The costs of the QROMs and their inverses in steps 4 (b) and (g). - cost4bg = (QR3(Lxi + Nk * n // 2, 2 * n * beta + nNk)[1] + - QI(Lxi + Nk * n // 2)[1] + QR3(Lxi, 4 * n)[1] + QI(Lxi)[1]) + cost4bg = ( + QR3(Lxi + Nk * n // 2, 2 * n * beta + nNk)[1] + + QI(Lxi + Nk * n // 2)[1] + + QR3(Lxi, 4 * n)[1] + + QI(Lxi)[1] + ) # The cost of the controlled swaps based on the spin qubit in steps 4c and f cost4cf = 2 * n * Nk @@ -247,11 +267,11 @@ def _compute_cost( ) # extra cost of swapping into working registers and computing k-Q. # (*Adjustment for modular arithmetic costs.*) - if Nkx == 2**np.ceil(np.log2(Nkx)): + if Nkx == 2 ** np.ceil(np.log2(Nkx)): cost4e = cost4e - 2 * np.ceil(np.log2(Nkx)) - if Nky == 2**np.ceil(np.log2(Nky)): + if Nky == 2 ** np.ceil(np.log2(Nky)): cost4e = cost4e - 2 * np.ceil(np.log2(Nky)) - if Nkz == 2**np.ceil(np.log2(Nkz)): + if Nkz == 2 ** np.ceil(np.log2(Nkz)): cost4e = cost4e - 2 * np.ceil(np.log2(Nkz)) # This is the cost of the controlled rotations for step 4. @@ -327,8 +347,7 @@ def _compute_cost( print(" [+] cost = ", cost) print(" [+] iters = ", iters) - ancilla_cost = (ac1 + ac2 + ac3 + ac4 + ac5 + ac6 + ac8 + ac9 + ac10 + - ac11 + ac12 + ac13 + ac14) + ancilla_cost = ac1 + ac2 + ac3 + ac4 + ac5 + ac6 + ac8 + ac9 + ac10 + ac11 + ac12 + ac13 + ac14 # Sanity checks before returning as int assert cost.is_integer() diff --git a/src/openfermion/resource_estimates/pbc/df/compute_df_resources_test.py b/src/openfermion/resource_estimates/pbc/df/compute_df_resources_test.py index 2672e25dd..19ffc26f2 100644 --- a/src/openfermion/resource_estimates/pbc/df/compute_df_resources_test.py +++ b/src/openfermion/resource_estimates/pbc/df/compute_df_resources_test.py @@ -22,8 +22,7 @@ ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_costing(): nRe = 108 lamRe = 294.8 @@ -40,42 +39,34 @@ def test_costing(): LxiLi = 20115 betaLi = 20 - res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 2, 2, 2, - 20_000) - res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 2, 2, 2, - res[0]) + res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 2, 2, 2, 20_000) + res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 2, 2, 2, res[0]) # 48250, 22343175750, 8174 assert np.isclose(res[0], 48250) assert np.isclose(res[1], 22343175750) assert np.isclose(res[2], 8174) - res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 3, 5, 1, - 20_000) - res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 3, 5, 1, - res[0]) + res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 3, 5, 1, 20_000) + res = _compute_cost(nRe, lamRe, dE, LRe, LxiRe, chi, betaRe, 3, 5, 1, res[0]) # 53146, 24610371366, 8945 assert np.isclose(res[0], 53146) assert np.isclose(res[1], 24610371366) assert np.isclose(res[2], 8945) - res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 2, 2, 2, - 20_000) - res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 2, 2, 2, - res[0]) + res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 2, 2, 2, 20_000) + res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 2, 2, 2, res[0]) # print(res) # 79212, 145727663004, 13873 assert np.isclose(res[0], 79212) assert np.isclose(res[1], 145727663004) assert np.isclose(res[2], 13873) - res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 3, 5, 1, - res[0]) + res = _compute_cost(nLi, lamLi, dE, LLi, LxiLi, chi, betaLi, 3, 5, 1, res[0]) # print(res) # 86042, 158292930114, 14952 assert np.isclose(res[0], 86042) assert np.isclose(res[1], 158292930114) assert np.isclose(res[2], 14952) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_costing_helper(): nRe = 108 lamRe = 294.8 diff --git a/src/openfermion/resource_estimates/pbc/df/compute_lambda_df.py b/src/openfermion/resource_estimates/pbc/df/compute_lambda_df.py index 875dd4da1..2bb584323 100644 --- a/src/openfermion/resource_estimates/pbc/df/compute_lambda_df.py +++ b/src/openfermion/resource_estimates/pbc/df/compute_lambda_df.py @@ -14,10 +14,8 @@ import numpy as np import numpy.typing as npt -from openfermion.resource_estimates.pbc.df.df_integrals import ( - DFABKpointIntegrals,) -from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) +from openfermion.resource_estimates.pbc.df.df_integrals import DFABKpointIntegrals +from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties @dataclass @@ -31,8 +29,7 @@ class DFHamiltonianProperties(HamiltonianProperties): num_eig: int -def compute_lambda(hcore: npt.NDArray, - df_obj: DFABKpointIntegrals) -> DFHamiltonianProperties: +def compute_lambda(hcore: npt.NDArray, df_obj: DFABKpointIntegrals) -> DFHamiltonianProperties: """Compute lambda for double-factorized Hamiltonian. one-body term h_pq(k) = hcore_{pq}(k) @@ -64,8 +61,7 @@ def compute_lambda(hcore: npt.NDArray, for qidx in range(len(kpts)): # - 0.5 * sum_{Q}sum_{r}(pkrQ|rQqk) eri_kqqk_pqrs = df_obj.get_eri_exact([kidx, qidx, qidx, kidx]) - h1_neg -= (np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / - nkpts) + h1_neg -= np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / nkpts # + 0.5 sum_{Q}sum_{r}(pkqk|rQrQ) eri_kkqq_pqrs = df_obj.get_eri_exact([kidx, kidx, qidx, qidx]) h1_pos += np.einsum("pqrr->pq", eri_kkqq_pqrs) / nkpts @@ -85,10 +81,8 @@ def compute_lambda(hcore: npt.NDArray, # A and B are W if df_obj.amat_lambda_vecs[kidx, qidx, nn] is None: continue - eigs_a_fixed_n_q = df_obj.amat_lambda_vecs[kidx, qidx, - nn] / np.sqrt(nkpts) - eigs_b_fixed_n_q = df_obj.bmat_lambda_vecs[kidx, qidx, - nn] / np.sqrt(nkpts) + eigs_a_fixed_n_q = df_obj.amat_lambda_vecs[kidx, qidx, nn] / np.sqrt(nkpts) + eigs_b_fixed_n_q = df_obj.bmat_lambda_vecs[kidx, qidx, nn] / np.sqrt(nkpts) first_number_to_square += np.sum(np.abs(eigs_a_fixed_n_q)) num_eigs += len(eigs_a_fixed_n_q) if eigs_b_fixed_n_q is not None: diff --git a/src/openfermion/resource_estimates/pbc/df/compute_lambda_df_test.py b/src/openfermion/resource_estimates/pbc/df/compute_lambda_df_test.py index 7835598ea..b9dc75449 100644 --- a/src/openfermion/resource_estimates/pbc/df/compute_lambda_df_test.py +++ b/src/openfermion/resource_estimates/pbc/df/compute_lambda_df_test.py @@ -19,18 +19,13 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import mp - from openfermion.resource_estimates.pbc.df.compute_lambda_df import ( - compute_lambda,) - from openfermion.resource_estimates.pbc.df.df_integrals import ( - DFABKpointIntegrals,) - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.df.compute_lambda_df import compute_lambda + from openfermion.resource_estimates.pbc.df.df_integrals import DFABKpointIntegrals + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_lambda_calc(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) @@ -39,10 +34,9 @@ def test_lambda_calc(): helper.double_factorize(thresh=1.0e-13) hcore_ao = mf.get_hcore() - hcore_mo = np.asarray([ - reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) - for k, mo in enumerate(mf.mo_coeff) - ]) + hcore_mo = np.asarray( + [reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) for k, mo in enumerate(mf.mo_coeff)] + ) lambda_data = compute_lambda(hcore_mo, helper) assert np.isclose(lambda_data.lambda_total, 179.62240330857406) @@ -63,15 +57,13 @@ def test_lambda_calc(): Bmats /= np.sqrt(nkpts) wa, _ = np.linalg.eigh(Amats) wb, _ = np.linalg.eigh(Bmats) - aval_to_square += np.einsum("npq->n", np.abs(Amats)**2) - bval_to_square += np.einsum("npq->n", np.abs(Bmats)**2) + aval_to_square += np.einsum("npq->n", np.abs(Amats) ** 2) + bval_to_square += np.einsum("npq->n", np.abs(Bmats) ** 2) - aval_to_square_v2 += np.sum(np.abs(wa)**2, axis=-1) - bval_to_square_v2 += np.sum(np.abs(wb)**2, axis=-1) + aval_to_square_v2 += np.sum(np.abs(wa) ** 2, axis=-1) + bval_to_square_v2 += np.sum(np.abs(wb) ** 2, axis=-1) assert np.allclose( - np.sum(np.abs(wa)**2, axis=-1), - np.einsum("npq->n", - np.abs(Amats)**2), + np.sum(np.abs(wa) ** 2, axis=-1), np.einsum("npq->n", np.abs(Amats) ** 2) ) lambda_two_body += np.sum(aval_to_square) diff --git a/src/openfermion/resource_estimates/pbc/df/df_integrals.py b/src/openfermion/resource_estimates/pbc/df/df_integrals.py index 0169ad471..62b65af68 100644 --- a/src/openfermion/resource_estimates/pbc/df/df_integrals.py +++ b/src/openfermion/resource_estimates/pbc/df/df_integrals.py @@ -17,12 +17,12 @@ from pyscf.pbc import scf -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping -def get_df_factor(mat: npt.NDArray, thresh: float, verify_adjoint: bool = False - ) -> Tuple[npt.NDArray, npt.NDArray]: +def get_df_factor( + mat: npt.NDArray, thresh: float, verify_adjoint: bool = False +) -> Tuple[npt.NDArray, npt.NDArray]: """Represent a matrix via non-zero eigenvalue vector pairs. Anything above thresh is considered non-zero @@ -54,7 +54,6 @@ def get_df_factor(mat: npt.NDArray, thresh: float, verify_adjoint: bool = False class DFABKpointIntegrals: - def __init__(self, cholesky_factor: npt.NDArray, kmf: scf.HF): """Class defining double factorized ERIs. @@ -78,11 +77,9 @@ def __init__(self, cholesky_factor: npt.NDArray, kmf: scf.HF): naux = max(self.chol[i, j].shape[0], naux) self.naux = naux self.nao = cholesky_factor[0, 0].shape[-1] - k_transfer_map = build_momentum_transfer_mapping( - self.kmf.cell, self.kmf.kpts) + k_transfer_map = build_momentum_transfer_mapping(self.kmf.cell, self.kmf.kpts) self.k_transfer_map = k_transfer_map - self.reverse_k_transfer_map = np.zeros_like( - self.k_transfer_map) # [kidx, kmq_idx] = qidx + self.reverse_k_transfer_map = np.zeros_like(self.k_transfer_map) # [kidx, kmq_idx] = qidx for kidx in range(self.nk): for qidx in range(self.nk): kmq_idx = self.k_transfer_map[qidx, kidx] @@ -121,29 +118,23 @@ def build_A_B_n_q_k_from_chol(self, qidx: int, kidx: int): Bmat = np.zeros((naux, 2 * nmo, 2 * nmo), dtype=np.complex128) if k_minus_q_idx == kidx: Amat[:, :nmo, :nmo] = self.chol[ - kidx, k_minus_q_idx] # beacuse L_{pK, qK,n}= L_{qK,pK,n}^{*} + kidx, k_minus_q_idx + ] # beacuse L_{pK, qK,n}= L_{qK,pK,n}^{*} Bmat[:, :nmo, :nmo] = 0.5j * ( - self.chol[kidx, k_minus_q_idx] - - self.chol[kidx, k_minus_q_idx].conj().transpose(0, 2, 1)) + self.chol[kidx, k_minus_q_idx] + - self.chol[kidx, k_minus_q_idx].conj().transpose(0, 2, 1) + ) else: - Amat[:, :nmo, nmo:] = (0.5 * self.chol[kidx, k_minus_q_idx] - ) # [naux, nmo, nmo] - Amat[:, nmo:, :nmo] = 0.5 * self.chol[kidx, k_minus_q_idx].conj( - ).transpose(0, 2, 1) + Amat[:, :nmo, nmo:] = 0.5 * self.chol[kidx, k_minus_q_idx] # [naux, nmo, nmo] + Amat[:, nmo:, :nmo] = 0.5 * self.chol[kidx, k_minus_q_idx].conj().transpose(0, 2, 1) - Bmat[:, :nmo, nmo:] = (0.5j * self.chol[kidx, k_minus_q_idx] - ) # [naux, nmo, nmo] - Bmat[:, nmo:, :nmo] = -0.5j * self.chol[kidx, k_minus_q_idx].conj( - ).transpose(0, 2, 1) + Bmat[:, :nmo, nmo:] = 0.5j * self.chol[kidx, k_minus_q_idx] # [naux, nmo, nmo] + Bmat[:, nmo:, :nmo] = -0.5j * self.chol[kidx, k_minus_q_idx].conj().transpose(0, 2, 1) return Amat, Bmat def build_chol_part_from_A_B( - self, - kidx: int, - qidx: int, - Amats: npt.NDArray, - Bmats: npt.NDArray, + self, kidx: int, qidx: int, Amats: npt.NDArray, Bmats: npt.NDArray ) -> npt.NDArray: r"""Construct rho_{n, k, Q}. @@ -183,10 +174,8 @@ def double_factorize(self, thresh=None) -> None: nkpts = self.nk nmo = self.nao naux = self.naux - self.amat_n_mats = np.zeros((nkpts, nkpts, naux, 2 * nmo, 2 * nmo), - dtype=np.complex128) - self.bmat_n_mats = np.zeros((nkpts, nkpts, naux, 2 * nmo, 2 * nmo), - dtype=np.complex128) + self.amat_n_mats = np.zeros((nkpts, nkpts, naux, 2 * nmo, 2 * nmo), dtype=np.complex128) + self.bmat_n_mats = np.zeros((nkpts, nkpts, naux, 2 * nmo, 2 * nmo), dtype=np.complex128) self.amat_lambda_vecs = np.empty((nkpts, nkpts, naux), dtype=object) self.bmat_lambda_vecs = np.empty((nkpts, nkpts, naux), dtype=object) for qidx, kidx in itertools.product(range(nkpts), repeat=2): @@ -196,12 +185,14 @@ def double_factorize(self, thresh=None) -> None: for nc in range(naux_qk): amat_n_eigs, amat_n_eigv = get_df_factor(Amats[nc], thresh) self.amat_n_mats[kidx, qidx][nc, :, :] = ( - amat_n_eigv @ np.diag(amat_n_eigs) @ amat_n_eigv.conj().T) + amat_n_eigv @ np.diag(amat_n_eigs) @ amat_n_eigv.conj().T + ) self.amat_lambda_vecs[kidx, qidx, nc] = amat_n_eigs bmat_n_eigs, bmat_n_eigv = get_df_factor(Bmats[nc], thresh) self.bmat_n_mats[kidx, qidx][nc, :, :] = ( - bmat_n_eigv @ np.diag(bmat_n_eigs) @ bmat_n_eigv.conj().T) + bmat_n_eigv @ np.diag(bmat_n_eigs) @ bmat_n_eigv.conj().T + ) self.bmat_lambda_vecs[kidx, qidx, nc] = bmat_n_eigs def get_eri(self, ikpts: list) -> npt.NDArray: @@ -221,17 +212,14 @@ def get_eri(self, ikpts: list) -> npt.NDArray: # build Cholesky vector from truncated A and B chol_val_k_kmq = self.build_chol_part_from_A_B( - ikp, qidx, self.amat_n_mats[ikp, qidx], self.bmat_n_mats[ikp, qidx]) + ikp, qidx, self.amat_n_mats[ikp, qidx], self.bmat_n_mats[ikp, qidx] + ) chol_val_kp_kpmq = self.build_chol_part_from_A_B( - iks, qidx, self.amat_n_mats[iks, qidx], self.bmat_n_mats[iks, qidx]) - - return np.einsum( - "npq,nsr->pqrs", - chol_val_k_kmq, - chol_val_kp_kpmq.conj(), - optimize=True, + iks, qidx, self.amat_n_mats[iks, qidx], self.bmat_n_mats[iks, qidx] ) + return np.einsum("npq,nsr->pqrs", chol_val_k_kmq, chol_val_kp_kpmq.conj(), optimize=True) + def get_eri_exact(self, ikpts: list) -> npt.NDArray: """Construct (pkp qkq| rkr sks) exactly from Cholesky vector. @@ -247,8 +235,5 @@ def get_eri_exact(self, ikpts: list) -> npt.NDArray: """ ikp, ikq, ikr, iks = ikpts return np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ) diff --git a/src/openfermion/resource_estimates/pbc/df/df_integrals_test.py b/src/openfermion/resource_estimates/pbc/df/df_integrals_test.py index 53f8749de..d9e0dc8ff 100644 --- a/src/openfermion/resource_estimates/pbc/df/df_integrals_test.py +++ b/src/openfermion/resource_estimates/pbc/df/df_integrals_test.py @@ -20,16 +20,12 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import mp - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) - from openfermion.resource_estimates.pbc.df.df_integrals import ( - DFABKpointIntegrals,) - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv + from openfermion.resource_estimates.pbc.df.df_integrals import DFABKpointIntegrals + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_df_amat_bmat(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) @@ -81,8 +77,6 @@ def test_df_amat_bmat(): for qidx in range(nkpts): kmq_idx = dfk_inst.k_transfer_map[qidx, kidx] kpmq_idx = dfk_inst.k_transfer_map[qidx, kpidx] - exact_eri_block = dfk_inst.get_eri_exact( - [kidx, kmq_idx, kpmq_idx, kpidx]) - test_eri_block = dfk_inst.get_eri( - [kidx, kmq_idx, kpmq_idx, kpidx]) + exact_eri_block = dfk_inst.get_eri_exact([kidx, kmq_idx, kpmq_idx, kpidx]) + test_eri_block = dfk_inst.get_eri([kidx, kmq_idx, kpmq_idx, kpidx]) assert np.allclose(exact_eri_block, test_eri_block) diff --git a/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df.py b/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df.py index f132eb3f7..0325976c3 100644 --- a/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df.py +++ b/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df.py @@ -20,19 +20,18 @@ from pyscf.pbc.tools.k2gamma import kpts_to_kmesh from openfermion.resource_estimates.pbc.resources import PBCResources -from openfermion.resource_estimates.pbc.df.df_integrals import ( - DFABKpointIntegrals,) -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_hamiltonian,) +from openfermion.resource_estimates.pbc.df.df_integrals import DFABKpointIntegrals +from openfermion.resource_estimates.pbc.hamiltonian import build_hamiltonian from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( build_approximate_eris, build_cc_inst, build_approximate_eris_rohf, ) -from openfermion.resource_estimates.pbc.df.compute_lambda_df import ( - compute_lambda,) +from openfermion.resource_estimates.pbc.df.compute_lambda_df import compute_lambda from openfermion.resource_estimates.pbc.df.compute_df_resources import ( - compute_cost, compute_beta_for_resources) + compute_cost, + compute_beta_for_resources, +) @dataclass @@ -46,18 +45,19 @@ class DFResources(PBCResources): this is not truncated. beta: The number of bits for the controlled rotations. """ + num_aux: int = -1 beta: int = 20 def generate_costing_table( - pyscf_mf: scf.HF, - cutoffs: np.ndarray, - name: str = "pbc", - chi: int = 10, - beta: int = 20, - dE_for_qpe: float = 0.0016, - energy_method: str = "MP2", + pyscf_mf: scf.HF, + cutoffs: np.ndarray, + name: str = "pbc", + chi: int = 10, + beta: int = 20, + dE_for_qpe: float = 0.0016, + energy_method: str = "MP2", ) -> pd.DataFrame: """Generate resource estimate costing table given a set of cutoffs for double-factorized Hamiltonian. @@ -120,13 +120,9 @@ def generate_costing_table( df_helper.double_factorize(thresh=cutoff) df_lambda = compute_lambda(hcore, df_helper) if pyscf_mf.cell.spin == 0: - approx_eris = build_approximate_eris(cc_inst, - df_helper, - eris=approx_eris) + approx_eris = build_approximate_eris(cc_inst, df_helper, eris=approx_eris) else: - approx_eris = build_approximate_eris_rohf(cc_inst, - df_helper, - eris=approx_eris) + approx_eris = build_approximate_eris_rohf(cc_inst, df_helper, eris=approx_eris) approx_energy, _, _ = energy_function(approx_eris) df_res_cost = compute_cost( num_spin_orbs, diff --git a/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df_test.py b/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df_test.py index 5c09a077f..e8c4b14d2 100644 --- a/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df_test.py +++ b/src/openfermion/resource_estimates/pbc/df/generate_costing_table_df_test.py @@ -16,27 +16,20 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from openfermion.resource_estimates.pbc.df.\ - generate_costing_table_df import generate_costing_table - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.df.generate_costing_table_df import ( + generate_costing_table, + ) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_generate_costing_table_df(): mf = make_diamond_113_szv() - thresh = np.array([0.1, 1e-2, - 1e-14]) # Eigenvalue threshold for second factorization. - table = generate_costing_table(mf, - cutoffs=thresh, - chi=10, - beta=22, - dE_for_qpe=1e-3) + thresh = np.array([0.1, 1e-2, 1e-14]) # Eigenvalue threshold for second factorization. + table = generate_costing_table(mf, cutoffs=thresh, chi=10, beta=22, dE_for_qpe=1e-3) assert np.allclose(table.dE, 1e-3) assert np.allclose(table.chi, 10) assert np.allclose(table.beta, 22) assert np.allclose(table.cutoff, thresh) assert np.allclose(table.num_aux, [648] * 3) - assert np.isclose(table.approx_energy.values[2], - table.exact_energy.values[0]) + assert np.isclose(table.approx_energy.values[2], table.exact_energy.values[0]) diff --git a/src/openfermion/resource_estimates/pbc/hamiltonian/__init__.py b/src/openfermion/resource_estimates/pbc/hamiltonian/__init__.py index b3d338134..4f6034d4c 100644 --- a/src/openfermion/resource_estimates/pbc/hamiltonian/__init__.py +++ b/src/openfermion/resource_estimates/pbc/hamiltonian/__init__.py @@ -14,6 +14,9 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from .hamiltonian import (build_hamiltonian, - build_momentum_transfer_mapping, - cholesky_from_df_ints, HamiltonianProperties) + from .hamiltonian import ( + build_hamiltonian, + build_momentum_transfer_mapping, + cholesky_from_df_ints, + HamiltonianProperties, + ) diff --git a/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions.py b/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions.py index ddc6f9f8a..0f36208e7 100644 --- a/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions.py +++ b/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions.py @@ -58,12 +58,10 @@ def build_approximate_eris(krcc_inst, eri_helper, eris=None): nkpts = krcc_inst.nkpts dtype = krcc_inst.mo_coeff[0].dtype if eris is not None: - log.info("Modifying coupled cluster _ERIS object inplace using " - f"{eri_helper.__class__}.") + log.info("Modifying coupled cluster _ERIS object inplace using " f"{eri_helper.__class__}.") out_eris = eris else: - log.info(f"Rebuilding coupled cluster _ERIS object using " - " {eri_helper.__class__}.") + log.info(f"Rebuilding coupled cluster _ERIS object using " " {eri_helper.__class__}.") out_eris = _ERIS(krcc_inst) for ikp, ikq, ikr in khelper.symm_map.keys(): iks = kconserv[ikp, ikq, ikr] @@ -73,8 +71,7 @@ def build_approximate_eris(krcc_inst, eri_helper, eris=None): eri_kpt = eri_kpt.real eri_kpt = eri_kpt for kp, kq, kr in khelper.symm_map[(ikp, ikq, ikr)]: - eri_kpt_symm = khelper.transform_symm(eri_kpt, kp, kq, - kr).transpose(0, 2, 1, 3) + eri_kpt_symm = khelper.transform_symm(eri_kpt, kp, kq, kr).transpose(0, 2, 1, 3) out_eris.oooo[kp, kr, kq] = eri_kpt_symm[:nocc, :nocc, :nocc, :nocc] out_eris.ooov[kp, kr, kq] = eri_kpt_symm[:nocc, :nocc, :nocc, nocc:] out_eris.oovv[kp, kr, kq] = eri_kpt_symm[:nocc, :nocc, nocc:, nocc:] @@ -106,12 +103,10 @@ def build_approximate_eris_rohf(kucc_inst, eri_helper, eris=None): nocca, noccb = kucc_inst.nocc nkpts = kucc_inst.nkpts if eris is not None: - log.info("Modifying coupled cluster _ERIS object inplace using " - f"{eri_helper.__class__}.") + log.info("Modifying coupled cluster _ERIS object inplace using " f"{eri_helper.__class__}.") out_eris = eris else: - log.info("Rebuilding coupled cluster _ERIS object using " - f"{eri_helper.__class__}.") + log.info("Rebuilding coupled cluster _ERIS object using " f"{eri_helper.__class__}.") out_eris = _make_eris_incore(kucc_inst) for kp, kq, kr in loop_kkk(nkpts): ks = kconserv[kp, kq, kr] @@ -121,10 +116,8 @@ def build_approximate_eris_rohf(kucc_inst, eri_helper, eris=None): out_eris.ooov[kp, kq, kr] = tmp[:nocca, :nocca, :nocca, nocca:] out_eris.oovv[kp, kq, kr] = tmp[:nocca, :nocca, nocca:, nocca:] out_eris.ovov[kp, kq, kr] = tmp[:nocca, nocca:, :nocca, nocca:] - out_eris.voov[kq, kp, ks] = ( - tmp[:nocca, nocca:, nocca:, :nocca].conj().transpose(1, 0, 3, 2)) - out_eris.vovv[kq, kp, ks] = ( - tmp[:nocca, nocca:, nocca:, nocca:].conj().transpose(1, 0, 3, 2)) + out_eris.voov[kq, kp, ks] = tmp[:nocca, nocca:, nocca:, :nocca].conj().transpose(1, 0, 3, 2) + out_eris.vovv[kq, kp, ks] = tmp[:nocca, nocca:, nocca:, nocca:].conj().transpose(1, 0, 3, 2) for kp, kq, kr in loop_kkk(nkpts): ks = kconserv[kp, kq, kr] @@ -134,10 +127,8 @@ def build_approximate_eris_rohf(kucc_inst, eri_helper, eris=None): out_eris.OOOV[kp, kq, kr] = tmp[:noccb, :noccb, :noccb, noccb:] out_eris.OOVV[kp, kq, kr] = tmp[:noccb, :noccb, noccb:, noccb:] out_eris.OVOV[kp, kq, kr] = tmp[:noccb, noccb:, :noccb, noccb:] - out_eris.VOOV[kq, kp, ks] = ( - tmp[:noccb, noccb:, noccb:, :noccb].conj().transpose(1, 0, 3, 2)) - out_eris.VOVV[kq, kp, ks] = ( - tmp[:noccb, noccb:, noccb:, noccb:].conj().transpose(1, 0, 3, 2)) + out_eris.VOOV[kq, kp, ks] = tmp[:noccb, noccb:, noccb:, :noccb].conj().transpose(1, 0, 3, 2) + out_eris.VOVV[kq, kp, ks] = tmp[:noccb, noccb:, noccb:, noccb:].conj().transpose(1, 0, 3, 2) for kp, kq, kr in loop_kkk(nkpts): ks = kconserv[kp, kq, kr] @@ -147,10 +138,8 @@ def build_approximate_eris_rohf(kucc_inst, eri_helper, eris=None): out_eris.ooOV[kp, kq, kr] = tmp[:nocca, :nocca, :noccb, noccb:] out_eris.ooVV[kp, kq, kr] = tmp[:nocca, :nocca, noccb:, noccb:] out_eris.ovOV[kp, kq, kr] = tmp[:nocca, nocca:, :noccb, noccb:] - out_eris.voOV[kq, kp, ks] = ( - tmp[:nocca, nocca:, noccb:, :noccb].conj().transpose(1, 0, 3, 2)) - out_eris.voVV[kq, kp, ks] = ( - tmp[:nocca, nocca:, noccb:, noccb:].conj().transpose(1, 0, 3, 2)) + out_eris.voOV[kq, kp, ks] = tmp[:nocca, nocca:, noccb:, :noccb].conj().transpose(1, 0, 3, 2) + out_eris.voVV[kq, kp, ks] = tmp[:nocca, nocca:, noccb:, noccb:].conj().transpose(1, 0, 3, 2) for kp, kq, kr in loop_kkk(nkpts): ks = kconserv[kp, kq, kr] @@ -160,10 +149,8 @@ def build_approximate_eris_rohf(kucc_inst, eri_helper, eris=None): out_eris.OOov[kp, kq, kr] = tmp[:noccb, :noccb, :nocca, nocca:] out_eris.OOvv[kp, kq, kr] = tmp[:noccb, :noccb, nocca:, nocca:] out_eris.OVov[kp, kq, kr] = tmp[:noccb, noccb:, :nocca, nocca:] - out_eris.VOov[kq, kp, ks] = ( - tmp[:noccb, noccb:, nocca:, :nocca].conj().transpose(1, 0, 3, 2)) - out_eris.VOvv[kq, kp, ks] = ( - tmp[:noccb, noccb:, nocca:, nocca:].conj().transpose(1, 0, 3, 2)) + out_eris.VOov[kq, kp, ks] = tmp[:noccb, noccb:, nocca:, :nocca].conj().transpose(1, 0, 3, 2) + out_eris.VOvv[kq, kp, ks] = tmp[:noccb, noccb:, nocca:, nocca:].conj().transpose(1, 0, 3, 2) # Force CCSD to use eri tensors. out_eris.Lpv = None out_eris.LPV = None diff --git a/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions_test.py b/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions_test.py index 25afc51bb..b441d8313 100644 --- a/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions_test.py +++ b/src/openfermion/resource_estimates/pbc/hamiltonian/cc_extensions_test.py @@ -21,20 +21,17 @@ from pyscf.pbc import gto, scf, mp from pyscf.pbc.cc import KRCCSD from pyscf.lib import chkfile - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( build_approximate_eris_rohf, build_approximate_eris, ) - from openfermion.resource_estimates.pbc.sf.sf_integrals import ( - SingleFactorization,) + from openfermion.resource_estimates.pbc.sf.sf_integrals import SingleFactorization _TEST_CHK = os.path.join(os.path.dirname(__file__), "../test_data/scf.chk") -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_cc_extension_rohf(): cell = gto.Cell() @@ -94,8 +91,7 @@ def test_cc_extension_rohf(): "VOvv", ] for block in eri_blocks: - assert np.allclose(test_eris.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert np.allclose(test_eris.__dict__[block][:], ref_eris.__dict__[block][:]) # Test MP2 energy is the correct emp2_approx, _, _ = cc_inst.init_amps(test_eris) assert abs(emp2_approx - emp2_ref) < 1e-12 @@ -104,8 +100,7 @@ def test_cc_extension_rohf(): helper = SingleFactorization(cholesky_factor=Luv, kmf=mf, naux=10) test_eris_approx = build_approximate_eris_rohf(cc_inst, helper) for block in eri_blocks: - assert not np.allclose(test_eris_approx.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert not np.allclose(test_eris_approx.__dict__[block][:], ref_eris.__dict__[block][:]) emp2_approx, _, _ = cc_inst.init_amps(test_eris_approx) # MP2 energy should be pretty bad assert abs(emp2_approx - emp2_ref) > 1e-12 @@ -123,8 +118,7 @@ def test_cc_extension_rohf(): helper = SingleFactorization(cholesky_factor=Luv, kmf=mf, naux=10) test_eris = build_approximate_eris_rohf(cc_approx, helper) for block in eri_blocks: - assert not np.allclose(test_eris.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert not np.allclose(test_eris.__dict__[block][:], ref_eris.__dict__[block][:]) # Want to avoid ccsd helper using density-fitted integrals which will be # "exact" assert test_eris.Lpv is None @@ -136,8 +130,7 @@ def test_cc_extension_rohf(): assert abs(ecc_exact - ecc_approx) > 1e-12 -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_cc_helper_rhf(): cell = gto.Cell() @@ -177,17 +170,9 @@ def test_cc_helper_rhf(): helper = SingleFactorization(cholesky_factor=Luv, kmf=mf) test_eris = build_approximate_eris(cc_inst, helper) - eri_blocks = [ - "oooo", - "ooov", - "oovv", - "ovov", - "voov", - "vovv", - ] + eri_blocks = ["oooo", "ooov", "oovv", "ovov", "voov", "vovv"] for block in eri_blocks: - assert np.allclose(test_eris.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert np.allclose(test_eris.__dict__[block][:], ref_eris.__dict__[block][:]) # Test MP2 energy is the correct emp2_approx, _, _ = cc_inst.init_amps(test_eris) assert abs(emp2_approx - emp2_ref) < 1e-12 @@ -196,8 +181,7 @@ def test_cc_helper_rhf(): helper = SingleFactorization(cholesky_factor=Luv, kmf=mf, naux=10) test_eris_approx = build_approximate_eris(cc_inst, helper) for block in eri_blocks: - assert not np.allclose(test_eris_approx.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert not np.allclose(test_eris_approx.__dict__[block][:], ref_eris.__dict__[block][:]) emp2_approx, _, _ = cc_inst.init_amps(test_eris_approx) # MP2 energy should be pretty bad assert abs(emp2_approx - emp2_ref) > 1e-12 @@ -215,8 +199,7 @@ def test_cc_helper_rhf(): helper = SingleFactorization(cholesky_factor=Luv, kmf=mf, naux=10) test_eris = build_approximate_eris(cc_approx, helper) for block in eri_blocks: - assert not np.allclose(test_eris.__dict__[block][:], - ref_eris.__dict__[block][:]) + assert not np.allclose(test_eris.__dict__[block][:], ref_eris.__dict__[block][:]) # Want to avoid ccsd helper using density-fitted integrals which will be # "exact" cc_approx = KRCCSD(mf) diff --git a/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian.py b/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian.py index 5822ca8a7..0e30c32fe 100644 --- a/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian.py +++ b/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian.py @@ -57,11 +57,8 @@ def build_hamiltonian(mf: "scf.KRHF") -> Tuple[npt.NDArray, npt.NDArray]: # Build temporary mp2 object so MO coeffs can potentially be padded if mean # field solution yields different number of MOs per k-point. tmp_mp2 = mp.KMP2(mf) - mo_coeff_padded = _add_padding(tmp_mp2, tmp_mp2.mo_coeff, - tmp_mp2.mo_energy)[0] - hcore_mo = np.asarray([ - C.conj().T @ hk @ C for (C, hk) in zip(mo_coeff_padded, mf.get_hcore()) - ]) + mo_coeff_padded = _add_padding(tmp_mp2, tmp_mp2.mo_coeff, tmp_mp2.mo_energy)[0] + hcore_mo = np.asarray([C.conj().T @ hk @ C for (C, hk) in zip(mo_coeff_padded, mf.get_hcore())]) chol = cholesky_from_df_ints(tmp_mp2) return hcore_mo, chol @@ -102,15 +99,15 @@ def cholesky_from_df_ints(mp2_inst, pad_mos_with_zeros=True) -> npt.NDArray: mo_coeff = mp2_inst._scf.mo_coeff kpts = mp2_inst.kpts if pad_mos_with_zeros: - mo_coeff = _add_padding(mp2_inst, mp2_inst.mo_coeff, - mp2_inst.mo_energy)[0] + mo_coeff = _add_padding(mp2_inst, mp2_inst.mo_coeff, mp2_inst.mo_energy)[0] nmo = mp2_inst.nmo else: nmo = nao num_mo_per_kpt = np.array([C.shape[-1] for C in mo_coeff]) if not (num_mo_per_kpt == nmo).all(): - log.info("Number of MOs differs at each k-point or is not the same " - "as the number of AOs.") + log.info( + "Number of MOs differs at each k-point or is not the same " "as the number of AOs." + ) nkpts = len(kpts) if gamma_point(kpts): dtype = np.double @@ -138,10 +135,7 @@ def cholesky_from_df_ints(mp2_inst, pad_mos_with_zeros=True) -> npt.NDArray: mo = np.asarray(mo, dtype=dtype, order="F") if dtype == np.double: out = _ao2mo.nr_e2( - Lpq_ao, - mo, - (bra_start, bra_end, ket_start, ket_end), - aosym="s2", + Lpq_ao, mo, (bra_start, bra_end, ket_start, ket_end), aosym="s2" ) else: # Note: Lpq.shape[0] != naux if linear dependency is found @@ -149,11 +143,7 @@ def cholesky_from_df_ints(mp2_inst, pad_mos_with_zeros=True) -> npt.NDArray: if Lpq_ao[0].size != nao**2: # aosym = 's2' Lpq_ao = lib.unpack_tril(Lpq_ao).astype(np.complex128) out = _ao2mo.r_e2( - Lpq_ao, - mo, - (bra_start, bra_end, ket_start, ket_end), - tao, - ao_loc, + Lpq_ao, mo, (bra_start, bra_end, ket_start, ket_end), tao, ao_loc ) Lchol[ki, kj] = out.reshape(-1, nmo, nmo) @@ -162,19 +152,18 @@ def cholesky_from_df_ints(mp2_inst, pad_mos_with_zeros=True) -> npt.NDArray: return Lchol -def build_momentum_transfer_mapping(cell: gto.Cell, - kpoints: np.ndarray) -> np.ndarray: +def build_momentum_transfer_mapping(cell: gto.Cell, kpoints: np.ndarray) -> np.ndarray: # Define mapping momentum_transfer_map[Q][k1] = k2 that satisfies # k1 - k2 + G = Q. a = cell.lattice_vectors() / (2 * np.pi) - delta_k1_k2_Q = (kpoints[:, None, None, :] - kpoints[None, :, None, :] - - kpoints[None, None, :, :]) + delta_k1_k2_Q = ( + kpoints[:, None, None, :] - kpoints[None, :, None, :] - kpoints[None, None, :, :] + ) delta_k1_k2_Q += kpoints[0][None, None, None, :] # shift to center delta_dot_a = np.einsum("wx,kpQx->kpQw", a, delta_k1_k2_Q) int_delta_dot_a = np.rint(delta_dot_a) # Should be zero if transfer is statisfied (2*pi*n) - mapping = np.where( - np.sum(np.abs(delta_dot_a - int_delta_dot_a), axis=3) < 1e-10) + mapping = np.where(np.sum(np.abs(delta_dot_a - int_delta_dot_a), axis=3) < 1e-10) num_kpoints = len(kpoints) momentum_transfer_map = np.zeros((num_kpoints,) * 2, dtype=np.int32) # Note index flip due to Q being first index in map but broadcasted last.. diff --git a/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian_test.py b/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian_test.py index 3dbc2a096..a33f884a1 100644 --- a/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian_test.py +++ b/src/openfermion/resource_estimates/pbc/hamiltonian/hamiltonian_test.py @@ -21,12 +21,13 @@ from pyscf.pbc import cc, mp from openfermion.resource_estimates.pbc.hamiltonian import ( - build_hamiltonian, cholesky_from_df_ints) + build_hamiltonian, + cholesky_from_df_ints, + ) from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_build_hamiltonian(): mf = make_diamond_113_szv() nmo = mf.mo_coeff[0].shape[-1] @@ -38,8 +39,7 @@ def test_build_hamiltonian(): assert chol[0, 0].shape == (naux, nmo, nmo) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_pyscf_chol_from_df(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) @@ -69,13 +69,10 @@ def test_pyscf_chol_from_df(): Ltest = _init_mp_df_eris(mymp) for ik, jk in itertools.product(range(nkpts), repeat=2): - assert np.allclose(Luv[ik, jk][:, :nocc, nocc:], - Ltest[ik, jk], - atol=1e-12) + assert np.allclose(Luv[ik, jk][:, :nocc, nocc:], Ltest[ik, jk], atol=1e-12) # 3. Test that the DF integrals have correct vvvv block (vv) - Ivvvv = np.zeros((nkpts, nkpts, nkpts, nvir, nvir, nvir, nvir), - dtype=np.complex128) + Ivvvv = np.zeros((nkpts, nkpts, nkpts, nvir, nvir, nvir, nvir), dtype=np.complex128) for ik, jk, kk in itertools.product(range(nkpts), repeat=3): lk = mymp.khelper.kconserv[ik, jk, kk] Lij = Luv[ik, jk][:, nocc:, nocc:] @@ -85,6 +82,4 @@ def test_pyscf_chol_from_df(): mycc = cc.KRCCSD(mf) eris = mycc.ao2mo() - assert np.allclose(eris.vvvv, - Ivvvv.transpose(0, 2, 1, 3, 5, 4, 6), - atol=1e-12) + assert np.allclose(eris.vvvv, Ivvvv.transpose(0, 2, 1, 3, 5, 4, 6), atol=1e-12) diff --git a/src/openfermion/resource_estimates/pbc/resources/data_types.py b/src/openfermion/resource_estimates/pbc/resources/data_types.py index 64f2f25a5..4eb2580a2 100644 --- a/src/openfermion/resource_estimates/pbc/resources/data_types.py +++ b/src/openfermion/resource_estimates/pbc/resources/data_types.py @@ -14,8 +14,7 @@ from dataclasses import dataclass, asdict, field import pandas as pd -from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) +from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties @dataclass(frozen=True) @@ -77,11 +76,11 @@ def to_dataframe(self) -> pd.DataFrame: return df def add_resources( - self, - ham_properties: HamiltonianProperties, - resource_estimates: ResourceEstimates, - cutoff: float, - approx_energy: float, + self, + ham_properties: HamiltonianProperties, + resource_estimates: ResourceEstimates, + cutoff: float, + approx_energy: float, ) -> None: """Add resource estimates to container for given cutoff value. diff --git a/src/openfermion/resource_estimates/pbc/resources/data_types_test.py b/src/openfermion/resource_estimates/pbc/resources/data_types_test.py index 252245658..d3b846884 100644 --- a/src/openfermion/resource_estimates/pbc/resources/data_types_test.py +++ b/src/openfermion/resource_estimates/pbc/resources/data_types_test.py @@ -20,43 +20,32 @@ PBCResources, ResourceEstimates, ) - from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) + from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_pbc_resources(): nmo = 10 np.random.seed(7) pbc_resources = PBCResources( - "pbc", - num_spin_orbitals=nmo, - num_kpts=12, - dE=1e-7, - chi=13, - exact_energy=-13.3, + "pbc", num_spin_orbitals=nmo, num_kpts=12, dE=1e-7, chi=13, exact_energy=-13.3 ) for cutoff in np.logspace(-1, -3, 5): lv = np.random.random(3) - lambdas = HamiltonianProperties(lambda_total=lv[0], - lambda_one_body=lv[1], - lambda_two_body=lv[2]) + lambdas = HamiltonianProperties( + lambda_total=lv[0], lambda_one_body=lv[1], lambda_two_body=lv[2] + ) resource = ResourceEstimates( toffolis_per_step=np.random.randint(0, 1000), total_toffolis=np.random.randint(0, 1000), logical_qubits=13, ) pbc_resources.add_resources( - ham_properties=lambdas, - resource_estimates=resource, - approx_energy=-12, - cutoff=cutoff, + ham_properties=lambdas, resource_estimates=resource, approx_energy=-12, cutoff=cutoff ) df = pbc_resources.to_dataframe() assert np.allclose( - df.lambda_total.values, - [0.07630829, 0.97798951, 0.26843898, 0.38094113, 0.21338535], + df.lambda_total.values, [0.07630829, 0.97798951, 0.26843898, 0.38094113, 0.21338535] ) assert (df.toffolis_per_step.values == [919, 366, 895, 787, 949]).all() - assert (df.total_toffolis.values == [615, 554, 391, 444, 112]).all() \ No newline at end of file + assert (df.total_toffolis.values == [615, 554, 391, 444, 112]).all() diff --git a/src/openfermion/resource_estimates/pbc/resources/qrom.py b/src/openfermion/resource_estimates/pbc/resources/qrom.py index 92b674a46..e244e0f9f 100644 --- a/src/openfermion/resource_estimates/pbc/resources/qrom.py +++ b/src/openfermion/resource_estimates/pbc/resources/qrom.py @@ -48,10 +48,7 @@ def QR2(L1, L2, M): min_val = np.inf for k1 in range(1, 11): for k2 in range(1, 11): - test_val = np.ceil(L1 / - (2**k1)) * np.ceil(L2 / - (2**k2)) + M * (2** - (k1 + k2) - 1) + test_val = np.ceil(L1 / (2**k1)) * np.ceil(L2 / (2**k2)) + M * (2 ** (k1 + k2) - 1) if test_val < min_val: min_val = test_val return int(min_val) @@ -68,8 +65,7 @@ def QI2(L1: int, Lv2: int): min_val = np.inf for k1 in range(1, 11): for k2 in range(1, 11): - test_val = np.ceil(L1 / (2**k1)) * np.ceil(Lv2 / - (2**k2)) + 2**(k1 + k2) + test_val = np.ceil(L1 / (2**k1)) * np.ceil(Lv2 / (2**k2)) + 2 ** (k1 + k2) if test_val < min_val: min_val = test_val return int(min_val) diff --git a/src/openfermion/resource_estimates/pbc/resources/qrom_test.py b/src/openfermion/resource_estimates/pbc/resources/qrom_test.py index b7b6f7138..62f5ab277 100644 --- a/src/openfermion/resource_estimates/pbc/resources/qrom_test.py +++ b/src/openfermion/resource_estimates/pbc/resources/qrom_test.py @@ -12,7 +12,7 @@ # limitations under the License. import numpy as np -from openfermion.resource_estimates.pbc.resources.qrom import (QR2, QI2) +from openfermion.resource_estimates.pbc.resources.qrom import QR2, QI2 def test_qr2(): diff --git a/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf.py b/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf.py index d1029d13f..585c04566 100644 --- a/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf.py +++ b/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf.py @@ -13,11 +13,9 @@ from dataclasses import dataclass import numpy as np import numpy.typing as npt -from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) +from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties -from openfermion.resource_estimates.pbc.sf.sf_integrals import ( - SingleFactorization,) +from openfermion.resource_estimates.pbc.sf.sf_integrals import SingleFactorization @dataclass @@ -31,8 +29,7 @@ class SFHamiltonianProperties(HamiltonianProperties): num_aux: int -def compute_lambda(hcore: npt.NDArray, - sf_obj: SingleFactorization) -> SFHamiltonianProperties: +def compute_lambda(hcore: npt.NDArray, sf_obj: SingleFactorization) -> SFHamiltonianProperties: """Lambda for single-factorized Hamiltonian. Compute one-body and two-body lambda for qubitization of @@ -75,15 +72,13 @@ def compute_lambda(hcore: npt.NDArray, for qidx in range(len(kpts)): # - 0.5 * sum_{Q}sum_{r}(pkrQ|rQqk) eri_kqqk_pqrs = sf_obj.get_eri([kidx, qidx, qidx, kidx]) - h1_neg -= (np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / - nkpts) + h1_neg -= np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / nkpts # + sum_{Q}sum_{r}(pkqk|rQrQ) eri_kkqq_pqrs = sf_obj.get_eri([kidx, kidx, qidx, qidx]) h1_pos += np.einsum("pqrr->pq", eri_kkqq_pqrs) / nkpts one_body_mat[kidx] = hcore[kidx] + 0.5 * h1_neg + h1_pos - lambda_one_body += np.sum( - np.abs(one_body_mat[kidx].real) + np.abs(one_body_mat[kidx].imag)) + lambda_one_body += np.sum(np.abs(one_body_mat[kidx].real) + np.abs(one_body_mat[kidx].imag)) ############################################################################ # @@ -103,12 +98,8 @@ def compute_lambda(hcore: npt.NDArray, A /= np.sqrt(nkpts) B /= np.sqrt(nkpts) # sum_q sum_n (sum_{pq} |Re{A_{pq}^n}| + |Im{A_{pq}^n|)^2 - lambda_two_body += np.sum( - np.einsum("npq->n", - np.abs(A.real) + np.abs(A.imag))**2) - lambda_two_body += np.sum( - np.einsum("npq->n", - np.abs(B.real) + np.abs(B.imag))**2) + lambda_two_body += np.sum(np.einsum("npq->n", np.abs(A.real) + np.abs(A.imag)) ** 2) + lambda_two_body += np.sum(np.einsum("npq->n", np.abs(B.real) + np.abs(B.imag)) ** 2) lambda_two_body *= 0.5 diff --git a/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf_test.py b/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf_test.py index 75aa204c3..272380d09 100644 --- a/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf_test.py +++ b/src/openfermion/resource_estimates/pbc/sf/compute_lambda_sf_test.py @@ -15,26 +15,23 @@ import pytest from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES + if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from ase.build import bulk from pyscf.pbc import gto, scf, mp from pyscf.pbc.tools import pyscf_ase - from openfermion.resource_estimates.pbc.sf.compute_lambda_sf import ( - compute_lambda,) - from openfermion.resource_estimates.pbc.sf.sf_integrals import ( - SingleFactorization,) + from openfermion.resource_estimates.pbc.sf.compute_lambda_sf import compute_lambda + from openfermion.resource_estimates.pbc.sf.sf_integrals import SingleFactorization from openfermion.resource_estimates.pbc.hamiltonian import ( build_hamiltonian, cholesky_from_df_ints, ) - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_lambda_calc(): mf = make_diamond_113_szv() hcore, Luv = build_hamiltonian(mf) @@ -43,8 +40,7 @@ def test_lambda_calc(): assert np.isclose(lambda_data.lambda_total, 2123.4342903006627) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_padding(): ase_atom = bulk("H", "bcc", a=2.0, cubic=True) cell = gto.Cell() @@ -71,26 +67,21 @@ def test_padding(): assert mf.mo_coeff[0].shape[-1] != mo_coeff_padded[0].shape[-1] hcore_ao = mf.get_hcore() - hcore_no_padding = np.asarray([ - reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) - for k, mo in enumerate(mf.mo_coeff) - ]) - hcore_padded = np.asarray([ - reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) - for k, mo in enumerate(mo_coeff_padded) - ]) + hcore_no_padding = np.asarray( + [reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) for k, mo in enumerate(mf.mo_coeff)] + ) + hcore_padded = np.asarray( + [reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) for k, mo in enumerate(mo_coeff_padded)] + ) assert hcore_no_padding[0].shape != hcore_padded[0].shape assert np.isclose(np.sum(hcore_no_padding), np.sum(hcore_padded)) Luv_no_padding = cholesky_from_df_ints(mymp, pad_mos_with_zeros=False) for k1 in range(nkpts): for k2 in range(nkpts): - assert np.isclose(np.sum(Luv_padded[k1, k2]), - np.sum(Luv_no_padding[k1, k2])) + assert np.isclose(np.sum(Luv_padded[k1, k2]), np.sum(Luv_no_padding[k1, k2])) - helper_no_padding = SingleFactorization(cholesky_factor=Luv_no_padding, - kmf=mf) + helper_no_padding = SingleFactorization(cholesky_factor=Luv_no_padding, kmf=mf) lambda_data_pad = compute_lambda(hcore_no_padding, helper_no_padding) helper = SingleFactorization(cholesky_factor=Luv_padded, kmf=mf) lambda_data_no_pad = compute_lambda(hcore_padded, helper) - assert np.isclose(lambda_data_pad.lambda_total, - lambda_data_no_pad.lambda_total) + assert np.isclose(lambda_data_pad.lambda_total, lambda_data_no_pad.lambda_total) diff --git a/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources.py b/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources.py index 93c3029c0..b930921ae 100644 --- a/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources.py +++ b/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources.py @@ -23,21 +23,16 @@ from openfermion.resource_estimates.utils import QI from openfermion.resource_estimates.utils import QR2 as QR2_of -from openfermion.resource_estimates.pbc.resources import ( - ResourceEstimates, - QR3, - QR2, - QI2, -) +from openfermion.resource_estimates.pbc.resources import ResourceEstimates, QR3, QR2, QI2 def compute_cost( - num_spin_orbs: int, - lambda_tot: float, - num_aux: int, - kmesh: list[int], - dE_for_qpe: float = 0.0016, - chi: int = 10, + num_spin_orbs: int, + lambda_tot: float, + num_aux: int, + kmesh: list[int], + dE_for_qpe: float = 0.0016, + chi: int = 10, ) -> ResourceEstimates: """Determine fault-tolerant costs using single factorizated Hamiltonian. @@ -55,47 +50,29 @@ def compute_cost( """ # run once to determine stps parameter init_cost = _compute_cost( - num_spin_orbs, - lambda_tot, - num_aux, - dE_for_qpe, - chi, - 20_000, - kmesh[0], - kmesh[1], - kmesh[2], + num_spin_orbs, lambda_tot, num_aux, dE_for_qpe, chi, 20_000, kmesh[0], kmesh[1], kmesh[2] ) steps = init_cost[0] final_cost = _compute_cost( - num_spin_orbs, - lambda_tot, - num_aux, - dE_for_qpe, - chi, - steps, - kmesh[0], - kmesh[1], - kmesh[2], + num_spin_orbs, lambda_tot, num_aux, dE_for_qpe, chi, steps, kmesh[0], kmesh[1], kmesh[2] ) estimates = ResourceEstimates( - toffolis_per_step=final_cost[0], - total_toffolis=final_cost[1], - logical_qubits=final_cost[2], + toffolis_per_step=final_cost[0], total_toffolis=final_cost[1], logical_qubits=final_cost[2] ) return estimates def _compute_cost( - n: int, - lam: float, - M: int, - dE: float, - chi: int, - stps: int, - Nkx: int, - Nky: int, - Nkz: int, - verbose: bool = False, + n: int, + lam: float, + M: int, + dE: float, + chi: int, + stps: int, + Nkx: int, + Nky: int, + Nkz: int, + verbose: bool = False, ) -> Tuple[int, int, int]: """Determine fault-tolerant costs using SF decomposition in quantum chem @@ -119,8 +96,11 @@ def _compute_cost( total_cost: Total number of Toffolis total_qubit_count: Total qubit count """ - nNk = (max(np.ceil(np.log2(Nkx)), 1) + max(np.ceil(np.log2(Nky)), 1) + - max(np.ceil(np.log2(Nkz)), 1)) + nNk = ( + max(np.ceil(np.log2(Nkx)), 1) + + max(np.ceil(np.log2(Nky)), 1) + + max(np.ceil(np.log2(Nkz)), 1) + ) Nk = Nkx * Nky * Nkz L = Nk * n**2 // 2 @@ -144,15 +124,31 @@ def _compute_cost( # JJG note: arccos arg may be > 1 # v = Round[2^p/(2*\[Pi])*ArcCos[2^nL/Sqrt[(L + 1)/2^\[Eta]]/2]]; v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nMN) / np.sqrt(M * Nk + 1) / 2)) + np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, nMN) / np.sqrt(M * Nk + 1) / 2) + ) # oh[[p]] = stps*(1/N[Sin[3*ArcSin[Cos[v*2*\[Pi]/2^p]*Sqrt[(L + # 1)/2^\[Eta]]/2^nL]]^2] - 1) + 4*p]; # stps*(1/N[Sin[3*ArcSin[Cos[v*2*\[Pi]/2^p]*Sqrt[M*Nk + 1]/2^nMN]]^2] # - 1) + 4*p] - oh[p] = np.real(stps * (1 / (np.sin(3 * arcsin( - np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt(M * Nk + 1) / - np.power(2, nMN)))**2) - 1) + 4 * (p + 1)) + oh[p] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt(M * Nk + 1) + / np.power(2, nMN) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br1 = int(np.argmin(oh) + 1) @@ -162,16 +158,33 @@ def _compute_cost( # JJG note: arccos arg may be > 1 # v = Round[2^p/(2*\[Pi])*ArcCos[2^nL/Sqrt[(L + 1)/2^\[Eta]]/2]]; v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nL) / np.sqrt(L / np.power(2, eta)) / 2)) + np.power(2, p + 1) + / (2 * np.pi) + * arccos(np.power(2, nL) / np.sqrt(L / np.power(2, eta)) / 2) + ) # oh[[p]] = stps*(1/N[Sin[3*ArcSin[Cos[v*2*\[Pi]/2^p]*Sqrt[(L + # 1)/2^\[Eta]]/2^nL]]^2] - 1) + 4*p]; oh2[[p]] = # stps*(1/N[Sin[3*ArcSin[Cos[v*2*\[Pi]/2^p]*Sqrt[L/2^\[Eta]]/2^nL]]^2] # - 1) + 4*p]; - oh2[p] = np.real(stps * (1 / (np.sin(3 * arcsin( - np.cos(v * 2 * np.pi / np.power(2, p + 1)) * - np.sqrt(L / np.power(2, eta)) / np.power(2, nL)))**2) - 1) + 4 * - (p + 1)) + oh2[p] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt(L / np.power(2, eta)) + / np.power(2, nL) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) br2 = int(np.argmin(oh2) + 1) # Cost of preparing the equal superposition state on the 1st register in 1a @@ -195,8 +208,7 @@ def _compute_cost( bp = 6 * np.ceil(np.log2(Nk) / 3) + 4 * nN + chi + 3 # Cost of QROMs for state preparation on p and q in step 2 (c). - cost2b = (QR2(M * Nk + 1, L, bp) + QI2(M * Nk + 1, L) + QR2(M * Nk, L, bp) + - QI2(M * Nk, L)) + cost2b = QR2(M * Nk + 1, L, bp) + QI2(M * Nk + 1, L) + QR2(M * Nk, L, bp) + QI2(M * Nk, L) # The cost of the inequality test and controlled swap for the # quantum alias sampling in steps 2(c) and (d). @@ -205,11 +217,11 @@ def _compute_cost( # Computing k - Q and k' - Q, then below we perform adjustments for non - # modular arithmetic. cost3 = 4 * nNk - if Nkx == 2**np.ceil(np.log2(Nkx)): + if Nkx == 2 ** np.ceil(np.log2(Nkx)): cost3 = cost3 - 2 * np.ceil(np.log2(Nkx)) - if Nky == 2**np.ceil(np.log2(Nky)): + if Nky == 2 ** np.ceil(np.log2(Nky)): cost3 = cost3 - 2 * np.ceil(np.log2(Nky)) - if Nkz == 2**np.ceil(np.log2(Nkz)): + if Nkz == 2 ** np.ceil(np.log2(Nkz)): cost3 = cost3 - 2 * np.ceil(np.log2(Nkz)) # The SELECT operation in step 4, which needs to be done twice. @@ -234,8 +246,20 @@ def _compute_cost( iters = np.ceil(np.pi * lam / (dE * 2)) # The total Toffoli costs for a step. - cost = (cost1a + cost1b + cost1cd + cost2a + cost2b + cost2cd + cost3 + - cost4 + cost6 + cost7 + cost9 + cost10) + cost = ( + cost1a + + cost1b + + cost1cd + + cost2a + + cost2b + + cost2cd + + cost3 + + cost4 + + cost6 + + cost7 + + cost9 + + cost10 + ) # Control for phase estimation and its iteration ac1 = 2 * np.ceil(np.log2(iters)) - 1 @@ -263,8 +287,7 @@ def _compute_cost( ac8 = 4 # The QROM on the p & q registers - ac9 = (kp[0] * kp[1] * bp + np.ceil(np.log2( - (M * Nk + 1) / kp[0])) + np.ceil(np.log2(L / kp[1]))) + ac9 = kp[0] * kp[1] * bp + np.ceil(np.log2((M * Nk + 1) / kp[0])) + np.ceil(np.log2(L / kp[1])) if verbose: print("[*] Top of routine") diff --git a/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources_test.py b/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources_test.py index bccc404b8..1e35c3041 100644 --- a/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources_test.py +++ b/src/openfermion/resource_estimates/pbc/sf/compute_sf_resources_test.py @@ -22,8 +22,7 @@ ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_estimate(): n = 152 lam = 3071.8 @@ -52,8 +51,7 @@ def test_estimate(): assert np.isclose(res[2], 219526) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_estimate_helper(): n = 152 lam = 3071.8 @@ -62,24 +60,14 @@ def test_estimate_helper(): chi = 10 res = compute_cost( - num_spin_orbs=n, - lambda_tot=lam, - num_aux=L, - kmesh=[3, 3, 3], - dE_for_qpe=dE, - chi=chi, + num_spin_orbs=n, lambda_tot=lam, num_aux=L, kmesh=[3, 3, 3], dE_for_qpe=dE, chi=chi ) assert np.isclose(res.toffolis_per_step, 1663707) assert np.isclose(res.total_toffolis, 8027674096311) assert np.isclose(res.logical_qubits, 438452) res = compute_cost( - num_spin_orbs=n, - lambda_tot=lam, - num_aux=L, - kmesh=[3, 5, 1], - dE_for_qpe=dE, - chi=chi, + num_spin_orbs=n, lambda_tot=lam, num_aux=L, kmesh=[3, 5, 1], dE_for_qpe=dE, chi=chi ) # 1663687, 8027577592851, 438447} assert np.isclose(res.toffolis_per_step, 907828) diff --git a/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf.py b/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf.py index 764c354f9..c6f2b9c5d 100644 --- a/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf.py +++ b/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf.py @@ -17,29 +17,25 @@ from pyscf.pbc import scf from pyscf.pbc.tools.k2gamma import kpts_to_kmesh -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_hamiltonian,) +from openfermion.resource_estimates.pbc.hamiltonian import build_hamiltonian from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( build_approximate_eris, build_cc_inst, build_approximate_eris_rohf, ) -from openfermion.resource_estimates.pbc.sf.compute_lambda_sf import ( - compute_lambda,) -from openfermion.resource_estimates.pbc.sf.compute_sf_resources import ( - compute_cost,) -from openfermion.resource_estimates.pbc.sf.sf_integrals import ( - SingleFactorization,) +from openfermion.resource_estimates.pbc.sf.compute_lambda_sf import compute_lambda +from openfermion.resource_estimates.pbc.sf.compute_sf_resources import compute_cost +from openfermion.resource_estimates.pbc.sf.sf_integrals import SingleFactorization from openfermion.resource_estimates.pbc.resources import PBCResources def generate_costing_table( - pyscf_mf: scf.HF, - naux_cutoffs: npt.NDArray[np.int32], - name: str = "pbc", - chi: int = 10, - dE_for_qpe=0.0016, - energy_method="MP2", + pyscf_mf: scf.HF, + naux_cutoffs: npt.NDArray[np.int32], + name: str = "pbc", + chi: int = 10, + dE_for_qpe=0.0016, + energy_method="MP2", ) -> pd.DataFrame: """Generate resource estimate costing table given a set of cutoffs for single-factorized Hamiltonian. @@ -81,17 +77,11 @@ def generate_costing_table( ) approx_eris = exact_eris for cutoff in naux_cutoffs: - sf_helper = SingleFactorization(cholesky_factor=chol, - kmf=pyscf_mf, - naux=cutoff) + sf_helper = SingleFactorization(cholesky_factor=chol, kmf=pyscf_mf, naux=cutoff) if pyscf_mf.cell.spin == 0: - approx_eris = build_approximate_eris(cc_inst, - sf_helper, - eris=approx_eris) + approx_eris = build_approximate_eris(cc_inst, sf_helper, eris=approx_eris) else: - approx_eris = build_approximate_eris_rohf(cc_inst, - sf_helper, - eris=approx_eris) + approx_eris = build_approximate_eris_rohf(cc_inst, sf_helper, eris=approx_eris) approx_energy, _, _ = energy_function(approx_eris) sf_lambda = compute_lambda(hcore, sf_helper) diff --git a/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf_test.py b/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf_test.py index c5c6a42c7..4a5597d0b 100644 --- a/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf_test.py +++ b/src/openfermion/resource_estimates/pbc/sf/generate_costing_table_sf_test.py @@ -16,23 +16,18 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from openfermion.resource_estimates.pbc.sf.\ - generate_costing_table_sf import generate_costing_table - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.sf.generate_costing_table_sf import ( + generate_costing_table, + ) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_generate_costing_table_sf(): mf = make_diamond_113_szv() thresh = np.array([10, 54, 108]) - table = generate_costing_table(mf, - naux_cutoffs=thresh, - chi=17, - dE_for_qpe=1e-3) + table = generate_costing_table(mf, naux_cutoffs=thresh, chi=17, dE_for_qpe=1e-3) assert np.allclose(table.dE, 1e-3) assert np.allclose(table.chi, 17) assert np.allclose(table.num_aux, [10, 54, 108]) - assert np.isclose(table.approx_energy.values[2], - table.exact_energy.values[0]) + assert np.isclose(table.approx_energy.values[2], table.exact_energy.values[0]) diff --git a/src/openfermion/resource_estimates/pbc/sf/sf_integrals.py b/src/openfermion/resource_estimates/pbc/sf/sf_integrals.py index 6e38ea143..f9d91e242 100644 --- a/src/openfermion/resource_estimates/pbc/sf/sf_integrals.py +++ b/src/openfermion/resource_estimates/pbc/sf/sf_integrals.py @@ -16,17 +16,12 @@ from pyscf.pbc import scf -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping # Single-Factorization class SingleFactorization: - - def __init__(self, - cholesky_factor: npt.NDArray, - kmf: scf.HF, - naux: int = None): + def __init__(self, cholesky_factor: npt.NDArray, kmf: scf.HF, naux: int = None): """Class defining single-factorized ERIs. Args: @@ -44,8 +39,7 @@ def __init__(self, naux = cholesky_factor[0, 0].shape[0] self.naux = naux self.nao = cholesky_factor[0, 0].shape[-1] - k_transfer_map = build_momentum_transfer_mapping( - self.kmf.cell, self.kmf.kpts) + k_transfer_map = build_momentum_transfer_mapping(self.kmf.cell, self.kmf.kpts) self.k_transfer_map = k_transfer_map def build_AB_from_chol(self, qidx: int): @@ -66,17 +60,16 @@ def build_AB_from_chol(self, qidx: int): # now form A and B # First set up matrices to store. We will loop over Q index and # construct entire set of matrices index by n-aux. - rho = np.zeros((naux, nmo * self.nk, nmo * self.nk), - dtype=np.complex128) + rho = np.zeros((naux, nmo * self.nk, nmo * self.nk), dtype=np.complex128) for kidx in range(self.nk): k_minus_q_idx = self.k_transfer_map[qidx, kidx] for p, q in itertools.product(range(nmo), repeat=2): P = int(kidx * nmo + p) # a^_{pK} Q = int(k_minus_q_idx * nmo + q) # a_{q(K-Q)} - rho[:, P, Q] += self.chol[ - kidx, k_minus_q_idx][:naux, p, - q] # L_{pK, q(K-Q)}a^_{pK}a_{q(K-Q)} + rho[:, P, Q] += self.chol[kidx, k_minus_q_idx][ + :naux, p, q + ] # L_{pK, q(K-Q)}a^_{pK}a_{q(K-Q)} A = 0.5 * (rho + rho.transpose((0, 2, 1)).conj()) B = 0.5j * (rho - rho.transpose((0, 2, 1)).conj()) @@ -112,15 +105,9 @@ def get_eri(self, ikpts: list, check_eq=False): optimize=True, ), np.einsum( - "npq,nrs->pqrs", - self.chol[ikp, ikq][:n], - self.chol[ikr, iks][:n], - optimize=True, + "npq,nrs->pqrs", self.chol[ikp, ikq][:n], self.chol[ikr, iks][:n], optimize=True ), ) return np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq][:n], - self.chol[iks, ikr][:n].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq][:n], self.chol[iks, ikr][:n].conj(), optimize=True ) diff --git a/src/openfermion/resource_estimates/pbc/sf/sf_integrals_test.py b/src/openfermion/resource_estimates/pbc/sf/sf_integrals_test.py index 5e7e6a122..42f2554e0 100644 --- a/src/openfermion/resource_estimates/pbc/sf/sf_integrals_test.py +++ b/src/openfermion/resource_estimates/pbc/sf/sf_integrals_test.py @@ -17,19 +17,14 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import mp, cc - from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( - build_approximate_eris,) + from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import build_approximate_eris - from openfermion.resource_estimates.pbc.sf.sf_integrals import ( - SingleFactorization,) - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.sf.sf_integrals import SingleFactorization + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_sf_helper_trunc(): mf = make_diamond_113_szv() diff --git a/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse.py b/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse.py index a192f4910..bc9ad1637 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse.py +++ b/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse.py @@ -14,10 +14,8 @@ import numpy as np import numpy.typing as npt -from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) -from openfermion.resource_estimates.pbc.sparse.sparse_integrals import ( - SparseFactorization,) +from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties +from openfermion.resource_estimates.pbc.sparse.sparse_integrals import SparseFactorization @dataclass @@ -31,8 +29,9 @@ class SparseHamiltonianProperties(HamiltonianProperties): num_sym_unique: int -def compute_lambda(hcore: npt.NDArray, sparse_int_obj: SparseFactorization - ) -> SparseHamiltonianProperties: +def compute_lambda( + hcore: npt.NDArray, sparse_int_obj: SparseFactorization +) -> SparseHamiltonianProperties: """Compute lambda value for sparse method Arguments: @@ -60,19 +59,17 @@ def compute_lambda(hcore: npt.NDArray, sparse_int_obj: SparseFactorization h1_neg = np.zeros_like(hcore[kidx]) for qidx in range(len(kpts)): # - 0.5 * sum_{Q}sum_{r}(pkrQ|rQqk) - eri_kqqk_pqrs = sparse_int_obj.get_eri_exact( - [kidx, qidx, qidx, kidx]) - h1_neg -= (np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / - nkpts) + eri_kqqk_pqrs = sparse_int_obj.get_eri_exact([kidx, qidx, qidx, kidx]) + h1_neg -= np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / nkpts # + sum_{Q}sum_{r}(pkqk|rQrQ) - eri_kkqq_pqrs = sparse_int_obj.get_eri_exact( - [kidx, kidx, qidx, qidx]) + eri_kkqq_pqrs = sparse_int_obj.get_eri_exact([kidx, kidx, qidx, qidx]) h1_pos += np.einsum("pqrr->pq", eri_kkqq_pqrs) / nkpts one_body_mat[kidx] = hcore[kidx] + 0.5 * h1_neg + h1_pos lambda_one_body += np.sum(np.abs(one_body_mat[kidx].real)) + np.sum( - np.abs(one_body_mat[kidx].imag)) + np.abs(one_body_mat[kidx].imag) + ) lambda_two_body = 0.0 nkpts = len(kpts) @@ -82,18 +79,16 @@ def compute_lambda(hcore: npt.NDArray, sparse_int_obj: SparseFactorization for qidx in range(nkpts): kmq_idx = sparse_int_obj.k_transfer_map[qidx, kidx] kpmq_idx = sparse_int_obj.k_transfer_map[qidx, kpidx] - test_eri_block = ( - sparse_int_obj.get_eri([kidx, kmq_idx, kpmq_idx, kpidx]) / - nkpts) + test_eri_block = sparse_int_obj.get_eri([kidx, kmq_idx, kpmq_idx, kpidx]) / nkpts lambda_two_body += np.sum(np.abs(test_eri_block.real)) + np.sum( - np.abs(test_eri_block.imag)) + np.abs(test_eri_block.imag) + ) lambda_tot = lambda_one_body + lambda_two_body sparse_data = SparseHamiltonianProperties( lambda_total=lambda_tot, lambda_one_body=lambda_one_body, lambda_two_body=lambda_two_body, - num_sym_unique=sparse_int_obj.get_total_unique_terms_above_thresh( - return_nk_counter=False), + num_sym_unique=sparse_int_obj.get_total_unique_terms_above_thresh(return_nk_counter=False), ) return sparse_data diff --git a/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse_test.py b/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse_test.py index b0a4e1ce6..9b9bf935e 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse_test.py +++ b/src/openfermion/resource_estimates/pbc/sparse/compute_lambda_sparse_test.py @@ -19,18 +19,13 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import mp - from openfermion.resource_estimates.pbc.sparse.\ - compute_lambda_sparse import compute_lambda - from openfermion.resource_estimates.pbc.sparse.sparse_integrals import ( - SparseFactorization,) - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.sparse.compute_lambda_sparse import compute_lambda + from openfermion.resource_estimates.pbc.sparse.sparse_integrals import SparseFactorization + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_lambda_sparse(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) @@ -38,8 +33,7 @@ def test_lambda_sparse(): helper = SparseFactorization(cholesky_factor=Luv, kmf=mf) hcore_ao = mf.get_hcore() - hcore_mo = np.asarray([ - reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) - for k, mo in enumerate(mf.mo_coeff) - ]) + hcore_mo = np.asarray( + [reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) for k, mo in enumerate(mf.mo_coeff)] + ) compute_lambda(hcore_mo, helper) diff --git a/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources.py b/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources.py index 1883e9662..a9d40f5f8 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources.py +++ b/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources.py @@ -19,17 +19,16 @@ from sympy import factorint from openfermion.resource_estimates.utils import QI -from openfermion.resource_estimates.pbc.resources import (ResourceEstimates, - QR3) +from openfermion.resource_estimates.pbc.resources import ResourceEstimates, QR3 def compute_cost( - num_spin_orbs: int, - lambda_tot: float, - num_sym_unique: int, - kmesh: list[int], - dE_for_qpe: float = 0.0016, - chi: int = 10, + num_spin_orbs: int, + lambda_tot: float, + num_sym_unique: int, + kmesh: list[int], + dE_for_qpe: float = 0.0016, + chi: int = 10, ) -> ResourceEstimates: """Determine fault-tolerant costs using sparse representaion of Hamiltonian. @@ -47,42 +46,20 @@ def compute_cost( """ # run once to determine stps parameter init_cost = _compute_cost( - num_spin_orbs, - lambda_tot, - num_sym_unique, - dE_for_qpe, - chi, - 20_000, - *kmesh, + num_spin_orbs, lambda_tot, num_sym_unique, dE_for_qpe, chi, 20_000, *kmesh ) steps = init_cost[0] final_cost = _compute_cost( - num_spin_orbs, - lambda_tot, - num_sym_unique, - dE_for_qpe, - chi, - steps, - *kmesh, + num_spin_orbs, lambda_tot, num_sym_unique, dE_for_qpe, chi, steps, *kmesh ) estimates = ResourceEstimates( - toffolis_per_step=final_cost[0], - total_toffolis=final_cost[1], - logical_qubits=final_cost[2], + toffolis_per_step=final_cost[0], total_toffolis=final_cost[1], logical_qubits=final_cost[2] ) return estimates def _compute_cost( - n: int, - lam: float, - d: int, - dE: float, - chi: int, - stps: int, - Nkx: int, - Nky: int, - Nkz: int, + n: int, lam: float, d: int, dE: float, chi: int, stps: int, Nkx: int, Nky: int, Nkz: int ) -> Tuple[int, int, int]: """Determine fault-tolerant costs using sparse representaion of Hamiltonian. @@ -112,8 +89,11 @@ def _compute_cost( eta = 0 nN = np.ceil(np.log2(n // 2)) - nNk = (max(np.ceil(np.log2(Nkx)), 1) + max(np.ceil(np.log2(Nky)), 1) + - max(np.ceil(np.log2(Nkz)), 1)) + nNk = ( + max(np.ceil(np.log2(Nkx)), 1) + + max(np.ceil(np.log2(Nky)), 1) + + max(np.ceil(np.log2(Nkz)), 1) + ) Nk = Nkx * Nky * Nkz m = chi + 8 * nN + 6 * nNk + 5 # Eq 26 @@ -125,11 +105,27 @@ def _compute_cost( for p in range(2, 22): # JJG note: arccos arg may be > 1 v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nM) / np.sqrt(d / 2**eta) / 2)) - oh[p - 2] = np.real(stps * (1 / (np.sin(3 * arcsin( - np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt(d / 2**eta) / - np.power(2, nM)))**2) - 1) + 4 * (p + 1)) + np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, nM) / np.sqrt(d / 2**eta) / 2) + ) + oh[p - 2] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt(d / 2**eta) + / np.power(2, nM) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br = int(np.argmin(oh) + 1) + 2 @@ -138,16 +134,26 @@ def _compute_cost( k1 = QR3(d, m)[0] # Equation (A17) - cost = (QR3(d, m)[1] + QI(d)[1] + 6 * n * Nk + 8 * nN + 12 * nNk + 2 * chi + - 7 * np.ceil(np.log2(d)) - 6 * eta + 4 * br - 8) + cost = ( + QR3(d, m)[1] + + QI(d)[1] + + 6 * n * Nk + + 8 * nN + + 12 * nNk + + 2 * chi + + 7 * np.ceil(np.log2(d)) + - 6 * eta + + 4 * br + - 8 + ) # The following are adjustments if we don't need to do explicit arithmetic # to make subtraction modular - if Nkx == 2**np.ceil(np.log2(Nkx)): + if Nkx == 2 ** np.ceil(np.log2(Nkx)): cost = cost - 2 * np.ceil(np.log2(Nkx)) - if Nky == 2**np.ceil(np.log2(Nky)): + if Nky == 2 ** np.ceil(np.log2(Nky)): cost = cost - 2 * np.ceil(np.log2(Nky)) - if Nkz == 2**np.ceil(np.log2(Nkz)): + if Nkz == 2 ** np.ceil(np.log2(Nkz)): cost = cost - 2 * np.ceil(np.log2(Nkz)) # Number of iterations needed for the phase estimation. diff --git a/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources_test.py b/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources_test.py index fa14e489d..8a7fe1ecc 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources_test.py +++ b/src/openfermion/resource_estimates/pbc/sparse/compute_sparse_resources_test.py @@ -16,12 +16,13 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from openfermion.resource_estimates.pbc.sparse.\ - compute_sparse_resources import _compute_cost, compute_cost + from openfermion.resource_estimates.pbc.sparse.compute_sparse_resources import ( + _compute_cost, + compute_cost, + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_compute_cost(): nRe = 108 lam_re = 2135.3 @@ -62,8 +63,7 @@ def test_compute_cost(): assert res[2] == 9231 -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_compute_cost_helper(): dE = 0.001 chi = 10 diff --git a/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse.py b/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse.py index 9f9e8cb34..abbe95d24 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse.py +++ b/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse.py @@ -18,28 +18,24 @@ from pyscf.pbc.tools.k2gamma import kpts_to_kmesh from openfermion.resource_estimates.pbc.resources import PBCResources -from openfermion.resource_estimates.pbc.sparse.sparse_integrals import ( - SparseFactorization,) -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_hamiltonian,) +from openfermion.resource_estimates.pbc.sparse.sparse_integrals import SparseFactorization +from openfermion.resource_estimates.pbc.hamiltonian import build_hamiltonian from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( build_approximate_eris, build_cc_inst, build_approximate_eris_rohf, ) -from openfermion.resource_estimates.pbc.sparse.compute_lambda_sparse import ( - compute_lambda,) -from openfermion.resource_estimates.pbc.sparse.compute_sparse_resources import ( - compute_cost,) +from openfermion.resource_estimates.pbc.sparse.compute_lambda_sparse import compute_lambda +from openfermion.resource_estimates.pbc.sparse.compute_sparse_resources import compute_cost def generate_costing_table( - pyscf_mf: scf.HF, - name="pbc", - chi: int = 10, - thresholds: np.ndarray = np.logspace(-1, -5, 6), - dE_for_qpe=0.0016, - energy_method="MP2", + pyscf_mf: scf.HF, + name="pbc", + chi: int = 10, + thresholds: np.ndarray = np.logspace(-1, -5, 6), + dE_for_qpe=0.0016, + energy_method="MP2", ) -> pd.DataFrame: """Generate resource estimate costing table given a set of cutoffs for sparse Hamiltonian. @@ -82,17 +78,11 @@ def generate_costing_table( ) approx_eris = exact_eris for thresh in thresholds: - sparse_helper = SparseFactorization(cholesky_factor=chol, - kmf=pyscf_mf, - threshold=thresh) + sparse_helper = SparseFactorization(cholesky_factor=chol, kmf=pyscf_mf, threshold=thresh) if pyscf_mf.cell.spin == 0: - approx_eris = build_approximate_eris(cc_inst, - sparse_helper, - eris=approx_eris) + approx_eris = build_approximate_eris(cc_inst, sparse_helper, eris=approx_eris) else: - approx_eris = build_approximate_eris_rohf(cc_inst, - sparse_helper, - eris=approx_eris) + approx_eris = build_approximate_eris_rohf(cc_inst, sparse_helper, eris=approx_eris) approx_energy, _, _ = energy_function(approx_eris) sparse_data = compute_lambda(hcore, sparse_helper) diff --git a/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse_test.py b/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse_test.py index 56e04bed3..bf2a376c1 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse_test.py +++ b/src/openfermion/resource_estimates/pbc/sparse/generate_costing_table_sparse_test.py @@ -18,23 +18,16 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from openfermion.resource_estimates.pbc import sparse - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_generate_costing_table_sparse(): mf = make_diamond_113_szv() thresh = np.array([1e-1, 1e-2, 1e-12]) - table = sparse.generate_costing_table(mf, - thresholds=thresh, - chi=17, - dE_for_qpe=1e-3) + table = sparse.generate_costing_table(mf, thresholds=thresh, chi=17, dE_for_qpe=1e-3) assert np.allclose(table.dE, 1e-3) assert np.allclose(table.chi, 17) assert np.allclose(table.cutoff, thresh) - assert np.isclose(table.approx_energy.values[2], - table.exact_energy.values[0]) - assert not np.isclose(table.approx_energy.values[0], - table.exact_energy.values[0]) + assert np.isclose(table.approx_energy.values[2], table.exact_energy.values[0]) + assert not np.isclose(table.approx_energy.values[0], table.exact_energy.values[0]) diff --git a/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals.py b/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals.py index 3a4c65c75..207db3880 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals.py +++ b/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals.py @@ -18,8 +18,7 @@ from pyscf.pbc import scf from pyscf.pbc.lib.kpts_helper import KptsHelper, loop_kkk -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping def _symmetric_two_body_terms(quad: Tuple[int, ...], complex_valued: bool): @@ -105,11 +104,7 @@ def unique_iter_pr_qs(nmo): class SparseFactorization: - - def __init__(self, - cholesky_factor: np.ndarray, - kmf: scf.HF, - threshold=1.0e-14): + def __init__(self, cholesky_factor: np.ndarray, kmf: scf.HF, threshold=1.0e-14): """Initialize a ERI object for CCSD from sparse integrals. Args: @@ -126,13 +121,13 @@ def __init__(self, self.kmf = kmf self.nk = len(self.kmf.kpts) self.nao = cholesky_factor[0, 0].shape[-1] - k_transfer_map = build_momentum_transfer_mapping( - self.kmf.cell, self.kmf.kpts) + k_transfer_map = build_momentum_transfer_mapping(self.kmf.cell, self.kmf.kpts) self.k_transfer_map = k_transfer_map self.threshold = threshold - def get_total_unique_terms_above_thresh(self, return_nk_counter=False - ) -> Union[int, Tuple[int, int]]: + def get_total_unique_terms_above_thresh( + self, return_nk_counter=False + ) -> Union[int, Tuple[int, int]]: """Determine all unique (pkp, qkq|rkr, sks). Accounts for momentum conservation and four fold symmetry. @@ -240,23 +235,12 @@ def get_eri(self, ikpts, check_eq=False) -> npt.NDArray: if check_eq: assert np.allclose( np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, - ), - np.einsum( - "npq,nrs->pqrs", - self.chol[ikp, ikq], - self.chol[ikr, iks], - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ), + np.einsum("npq,nrs->pqrs", self.chol[ikp, ikq], self.chol[ikr, iks], optimize=True), ) eri = np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ) zero_mask = np.abs(eri) < self.threshold eri[zero_mask] = 0 @@ -277,8 +261,5 @@ def get_eri_exact(self, kpts) -> npt.NDArray: """ ikp, ikq, ikr, iks = kpts return np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ) diff --git a/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals_test.py b/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals_test.py index 818abfc97..a594cc762 100644 --- a/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals_test.py +++ b/src/openfermion/resource_estimates/pbc/sparse/sparse_integrals_test.py @@ -18,26 +18,25 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import mp - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) - from openfermion.resource_estimates.pbc.testing.systems import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints + from openfermion.resource_estimates.pbc.testing.systems import make_diamond_113_szv from openfermion.resource_estimates.pbc.sparse.sparse_integrals import ( - unique_iter, unique_iter_pr_qs, unique_iter_ps_qr, unique_iter_pq_rs, - SparseFactorization) + unique_iter, + unique_iter_pr_qs, + unique_iter_ps_qr, + unique_iter_pq_rs, + SparseFactorization, + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_sparse_int_obj(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) Luv = cholesky_from_df_ints(mymp) for thresh in [1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6]: abs_sum_coeffs = 0 - helper = SparseFactorization(cholesky_factor=Luv, - kmf=mf, - threshold=thresh) + helper = SparseFactorization(cholesky_factor=Luv, kmf=mf, threshold=thresh) nkpts = len(mf.kpts) # recall (k, k-q|k'-q, k') for kidx in range(nkpts): @@ -45,15 +44,13 @@ def test_sparse_int_obj(): for qidx in range(nkpts): kmq_idx = helper.k_transfer_map[qidx, kidx] kpmq_idx = helper.k_transfer_map[qidx, kpidx] - test_eri_block = helper.get_eri( - [kidx, kmq_idx, kpmq_idx, kpidx]) - abs_sum_coeffs += np.sum(np.abs( - test_eri_block.real)) + np.sum( - np.abs(test_eri_block.imag)) + test_eri_block = helper.get_eri([kidx, kmq_idx, kpmq_idx, kpidx]) + abs_sum_coeffs += np.sum(np.abs(test_eri_block.real)) + np.sum( + np.abs(test_eri_block.imag) + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_get_num_unique(): mf = make_diamond_113_szv() mymp = mp.KMP2(mf) @@ -102,7 +99,7 @@ def test_get_num_unique(): completed[kp, kq, kr] = True tally[kp, kq, kr] += 1 for ftuple in unique_iter( - nmo + nmo ): # iterate over unique whenever all momentum indices are the # same p, q, r, s = ftuple @@ -154,8 +151,7 @@ def test_get_num_unique(): fulltally[kp, kq, kr, q, p, s, r] += 1 fulltally[kr, ks, kp, s, r, q, p] += 1 - for p, q, r, s in itertools.product(range(helper.nao), - repeat=4): + for p, q, r, s in itertools.product(range(helper.nao), repeat=4): if not np.isclose(test_block[p, q, r, s], 1): print(p, q, r, s, test_block[p, q, r, s]) assert np.allclose(test_block, 1) @@ -185,8 +181,7 @@ def test_get_num_unique(): fulltally[kp, kq, kr, s, r, q, p] += 1 fulltally[kr, ks, kp, q, p, s, r] += 1 - for p, q, r, s in itertools.product(range(helper.nao), - repeat=4): + for p, q, r, s in itertools.product(range(helper.nao), repeat=4): if not np.isclose(test_block[p, q, r, s], 1): print(p, q, r, s, test_block[p, q, r, s]) assert np.allclose(test_block, 1) @@ -215,8 +210,7 @@ def test_get_num_unique(): fulltally[kp, kq, kr, r, s, p, q] += 1 fulltally[kq, kp, ks, s, r, q, p] += 1 - for p, q, r, s in itertools.product(range(helper.nao), - repeat=4): + for p, q, r, s in itertools.product(range(helper.nao), repeat=4): if not np.isclose(test_block[p, q, r, s], 1): print(p, q, r, s, test_block[p, q, r, s]) assert np.allclose(test_block, 1) diff --git a/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc.py b/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc.py index 5240b97a0..1b54efd20 100644 --- a/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc.py +++ b/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc.py @@ -15,10 +15,8 @@ import numpy as np import numpy.typing as npt -from openfermion.resource_estimates.pbc.thc.thc_integrals import ( - KPTHCDoubleTranslation,) -from openfermion.resource_estimates.pbc.hamiltonian import ( - HamiltonianProperties,) +from openfermion.resource_estimates.pbc.thc.thc_integrals import KPTHCDoubleTranslation +from openfermion.resource_estimates.pbc.hamiltonian import HamiltonianProperties @dataclass @@ -33,10 +31,7 @@ class THCHamiltonianProperties(HamiltonianProperties): def compute_lambda_real( - h1: npt.NDArray, - etaPp: npt.NDArray, - MPQ: npt.NDArray, - chol: npt.NDArray, + h1: npt.NDArray, etaPp: npt.NDArray, MPQ: npt.NDArray, chol: npt.NDArray ) -> Tuple[float, float, float]: """Compute lambda assuming real THC factors (molecular way) @@ -56,10 +51,8 @@ def compute_lambda_real( # projecting into the THC basis requires each THC factor mu to be nrmlzd. # we roll the normalization constant into the central tensor zeta - SPQ = etaPp.dot( - etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric - cP = np.diag(np.diag( - SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) + SPQ = etaPp.dot(etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric + cP = np.diag(np.diag(SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) # no sqrts because we have two normalized THC vectors (index by mu and nu) # on each side. MPQ_normalized = cP.dot(MPQ).dot(cP) # get normalized zeta in Eq. 11 & 12 @@ -67,20 +60,21 @@ def compute_lambda_real( lambda_z = np.sum(np.abs(MPQ_normalized)) * 0.5 # Eq. 13 # NCR: originally Joonho's code add np.einsum('llij->ij', eri_thc) # NCR: I don't know how much this matters. - T = (h1 - 0.5 * np.einsum("nil,nlj->ij", chol, chol, optimize=True) + - np.einsum("nll,nij->ij", chol, chol, optimize=True)) # Eq. 3 + Eq. 18 + T = ( + h1 + - 0.5 * np.einsum("nil,nlj->ij", chol, chol, optimize=True) + + np.einsum("nll,nij->ij", chol, chol, optimize=True) + ) # Eq. 3 + Eq. 18 # e, v = np.linalg.eigh(T) e = np.linalg.eigvalsh(T) # only need eigenvalues - lambda_T = np.sum( - np.abs(e)) # Eq. 19. NOTE: sum over spin orbitals removes 1/2 factor + lambda_T = np.sum(np.abs(e)) # Eq. 19. NOTE: sum over spin orbitals removes 1/2 factor lambda_tot = lambda_z + lambda_T # Eq. 20 return lambda_tot, lambda_T, lambda_z -def compute_lambda(hcore: npt.NDArray, - thc_obj: KPTHCDoubleTranslation) -> HamiltonianProperties: +def compute_lambda(hcore: npt.NDArray, thc_obj: KPTHCDoubleTranslation) -> HamiltonianProperties: """Compute one-body and two-body lambda for qubitization the THC LCU. Args: @@ -103,8 +97,7 @@ def compute_lambda(hcore: npt.NDArray, for qidx in range(len(kpts)): # - 0.5 * sum_{Q}sum_{r}(pkrQ|rQqk) eri_kqqk_pqrs = thc_obj.get_eri_exact([kidx, qidx, qidx, kidx]) - h1_neg -= (np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / - nkpts) + h1_neg -= np.einsum("prrq->pq", eri_kqqk_pqrs, optimize=True) / nkpts one_body_mat[kidx] = hcore[kidx] + 0.5 * h1_neg # + h1_pos one_eigs, _ = np.linalg.eigh(one_body_mat[kidx]) @@ -114,10 +107,7 @@ def compute_lambda(hcore: npt.NDArray, # normalized. we roll the normalization constant into the central tensor # zeta # no sqrts because we have two normalized thc vectors # (index by mu and nu) on each side. - norm_kP = (np.einsum("kpP,kpP->kP", - thc_obj.chi.conj(), - thc_obj.chi, - optimize=True)**0.5) + norm_kP = np.einsum("kpP,kpP->kP", thc_obj.chi.conj(), thc_obj.chi, optimize=True) ** 0.5 lambda_two_body = 0 for iq, zeta_Q in enumerate(thc_obj.zeta): # xy einsum subscript indexes G index. @@ -129,13 +119,10 @@ def compute_lambda(hcore: npt.NDArray, gsr = thc_obj.g_mapping[iq, ik_prime] norm_left = norm_kP[ik] * norm_kP[ik_minus_q] norm_right = norm_kP[ik_prime_minus_q] * norm_kP[ik_prime] - MPQ_normalized = (np.einsum( - "P,PQ,Q->PQ", - norm_left, - zeta_Q[gpq, gsr], - norm_right, - optimize=True, - ) / nkpts) + MPQ_normalized = ( + np.einsum("P,PQ,Q->PQ", norm_left, zeta_Q[gpq, gsr], norm_right, optimize=True) + / nkpts + ) lambda_two_body += np.sum(np.abs(MPQ_normalized.real)) lambda_two_body += np.sum(np.abs(MPQ_normalized.imag)) lambda_two_body *= 2 diff --git a/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc_test.py b/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc_test.py index 67e50d20f..859824814 100644 --- a/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/compute_lambda_thc_test.py @@ -16,20 +16,16 @@ import pytest from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES + if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import gto, mp, scf - from openfermion.resource_estimates.pbc.hamiltonian import ( - cholesky_from_df_ints,) - from openfermion.resource_estimates.pbc.thc.factorizations.thc_jax import ( - kpoint_thc_via_isdf,) - from openfermion.resource_estimates.pbc.thc.compute_lambda_thc import ( - compute_lambda,) - from openfermion.resource_estimates.pbc.thc.thc_integrals import ( - KPTHCDoubleTranslation,) + from openfermion.resource_estimates.pbc.hamiltonian import cholesky_from_df_ints + from openfermion.resource_estimates.pbc.thc.factorizations.thc_jax import kpoint_thc_via_isdf + from openfermion.resource_estimates.pbc.thc.compute_lambda_thc import compute_lambda + from openfermion.resource_estimates.pbc.thc.thc_integrals import KPTHCDoubleTranslation -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_kpoint_thc_lambda(): cell = gto.Cell() @@ -77,13 +73,9 @@ def test_kpoint_thc_lambda(): verbose=False, ) hcore_ao = mf.get_hcore() - hcore_mo = np.asarray([ - reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) - for k, mo in enumerate(mf.mo_coeff) - ]) - helper = KPTHCDoubleTranslation(kpt_thc.chi, kpt_thc.zeta, mf) - lambda_data = compute_lambda( - hcore_mo, - helper, + hcore_mo = np.asarray( + [reduce(np.dot, (mo.T.conj(), hcore_ao[k], mo)) for k, mo in enumerate(mf.mo_coeff)] ) + helper = KPTHCDoubleTranslation(kpt_thc.chi, kpt_thc.zeta, mf) + lambda_data = compute_lambda(hcore_mo, helper) assert np.isclose(lambda_data.lambda_total, 93.84613761765415) diff --git a/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources.py b/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources.py index 134ee191b..1cda1493b 100644 --- a/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources.py +++ b/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources.py @@ -18,19 +18,18 @@ from sympy import factorint from openfermion.resource_estimates.utils import QI -from openfermion.resource_estimates.pbc.resources.data_types import ( - ResourceEstimates,) +from openfermion.resource_estimates.pbc.resources.data_types import ResourceEstimates from openfermion.resource_estimates.pbc.resources.qrom import QR3 def compute_cost( - num_spin_orbs: int, - lambda_tot: float, - thc_dim: int, - kmesh: list[int], - dE_for_qpe: float = 0.0016, - chi: int = 10, - beta: Union[int, None] = None, + num_spin_orbs: int, + lambda_tot: float, + thc_dim: int, + kmesh: list[int], + dE_for_qpe: float = 0.0016, + chi: int = 10, + beta: Union[int, None] = None, ) -> ResourceEstimates: """Determine fault-tolerant costs using THC factorization representation of symmetry adapted integrals. @@ -62,25 +61,23 @@ def compute_cost( stps=20_000, # not used ) resources = ResourceEstimates( - toffolis_per_step=thc_costs[0], - total_toffolis=thc_costs[1], - logical_qubits=thc_costs[2], + toffolis_per_step=thc_costs[0], total_toffolis=thc_costs[1], logical_qubits=thc_costs[2] ) return resources def _compute_cost( - n: int, - lam: float, - dE: float, - chi: int, - beta: int, - M: int, - Nkx: int, - Nky: int, - Nkz: int, - stps: int, - verbose: bool = False, + n: int, + lam: float, + dE: float, + chi: int, + beta: int, + M: int, + Nkx: int, + Nky: int, + Nkz: int, + stps: int, + verbose: bool = False, ) -> Tuple[int, int, int]: """Determine fault-tolerant costs using THC decomposition in quantum chem @@ -107,8 +104,11 @@ def _compute_cost( total_cost: Total number of Toffolis ancilla_cost: Total ancilla cost """ - nk = (max(np.ceil(np.log2(Nkx)), 1) + max(np.ceil(np.log2(Nky)), 1) + - max(np.ceil(np.log2(Nkz)), 1)) + nk = ( + max(np.ceil(np.log2(Nkx)), 1) + + max(np.ceil(np.log2(Nky)), 1) + + max(np.ceil(np.log2(Nkz)), 1) + ) Nk = Nkx * Nky * Nkz # (*Temporarily set as number of even numbers.*) @@ -133,12 +133,20 @@ def _compute_cost( oh = [0] * 20 for p in range(20): # arccos arg may be > 1 - v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nc) / np.sqrt(d) / 2)) - oh[p] = stps * (1 / (np.sin(3 * arcsin( - np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt(d) / - np.power(2, nc)))**2) - 1) + 4 * (p + 1) + v = np.round(np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, nc) / np.sqrt(d) / 2)) + oh[p] = stps * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt(d) / np.power(2, nc) + ) + ) + ** 2 + ) + - 1 + ) + 4 * (p + 1) # Set it to be the number of bits that minimises the cost, usually 7. # Python is 0-index, so need to add the one back in vs mathematica nb @@ -193,8 +201,7 @@ def _compute_cost( cs3 = 16 * n * (beta - 2) # Cost for constructing contiguous register for outputting rotations. - cs4 = (12 * Nk + 4 * np.ceil(np.log2(Nk * (M + n / 2))) + - 4 * np.ceil(np.log2(Nk * M))) + cs4 = 12 * Nk + 4 * np.ceil(np.log2(Nk * (M + n / 2))) + 4 * np.ceil(np.log2(Nk * M)) # The cost of the controlled selection of the X vs Y. cs5 = 2 + 4 * (Nk - 1) diff --git a/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources_test.py b/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources_test.py index ec0b82c08..bec5c74c3 100644 --- a/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/compute_thc_resources_test.py @@ -22,8 +22,7 @@ ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_thc_resources(): lam = 307.68 dE = 0.001 @@ -54,8 +53,7 @@ def test_thc_resources(): assert np.isclose(res[2], 77517) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_thc_resources_helper(): lam = 307.68 dE = 0.001 diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic.py index 17bb72b1b..afddb87df 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic.py @@ -73,9 +73,11 @@ def get_delta_kp_kq_q(int_scaled_kpts): Returns: np.ndarray mapping D_{kp, kq, Q}. """ - delta_k1_k2_q_int = (int_scaled_kpts[:, None, None, :] - - int_scaled_kpts[None, :, None, :] - - int_scaled_kpts[None, None, :, :]) + delta_k1_k2_q_int = ( + int_scaled_kpts[:, None, None, :] + - int_scaled_kpts[None, :, None, :] + - int_scaled_kpts[None, None, :, :] + ) return delta_k1_k2_q_int @@ -107,7 +109,7 @@ def build_transfer_map(kmesh, scaled_kpts): np.rint(delta_k1_k2_q_int[kpidx, kqidx, qidx][1]) % kmesh[1], np.rint(delta_k1_k2_q_int[kpidx, kqidx, qidx][2]) % kmesh[2], ], - 0, + 0, ): transfer_map[qidx, kpidx] = kqidx return transfer_map @@ -124,19 +126,17 @@ def build_conjugate_map(kmesh, scaled_kpts): kconj_map: conjugate k-point mapping """ nkpts = len(scaled_kpts) - kpoint_dict = dict( - zip( - [tuple(map(int, scaled_kpts[x])) for x in range(nkpts)], - range(nkpts), - )) + kpoint_dict = dict(zip([tuple(map(int, scaled_kpts[x])) for x in range(nkpts)], range(nkpts))) kconj_map = np.zeros((nkpts), dtype=int) for kidx in range(nkpts): negative_k_scaled = -scaled_kpts[kidx] - fb_negative_k_scaled = tuple(( - int(negative_k_scaled[0]) % kmesh[0], - int(negative_k_scaled[1]) % kmesh[1], - int(negative_k_scaled[2]) % kmesh[2], - )) + fb_negative_k_scaled = tuple( + ( + int(negative_k_scaled[0]) % kmesh[0], + int(negative_k_scaled[1]) % kmesh[1], + int(negative_k_scaled[2]) % kmesh[2], + ) + ) kconj_map[kidx] = kpoint_dict[fb_negative_k_scaled] return kconj_map diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic_test.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic_test.py index 6643ad354..6fe30a389 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/gvec_map_logic_test.py @@ -19,12 +19,16 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from openfermion.resource_estimates.pbc.thc.factorizations.gvec_map_logic import ( # pylint: disable=line-too-long - build_conjugate_map, build_G_vectors, build_gpq_mapping, - build_transfer_map, get_delta_kp_kq_q, get_miller_indices) + build_conjugate_map, + build_G_vectors, + build_gpq_mapping, + build_transfer_map, + get_delta_kp_kq_q, + get_miller_indices, + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_get_miller_indices(): kmesh = [3, 1, 1] int_scaled_kpts = get_miller_indices(kmesh) @@ -38,8 +42,7 @@ def test_get_miller_indices(): assert np.allclose(int_scaled_kpts[:, 1], [0, 1, 0, 1, 0, 1]) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_get_delta_k1_k2_Q(): kmesh = [3, 2, 1] nkpts = np.prod(kmesh) @@ -53,8 +56,7 @@ def test_get_delta_k1_k2_Q(): ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_transfer_map(): kmesh = [3, 1, 1] nkpts = np.prod(kmesh) @@ -62,14 +64,14 @@ def test_transfer_map(): transfer_map = build_transfer_map(kmesh, scaled_kpts=scaled_kpts) for kpidx, kqidx in itertools.product(range(nkpts), repeat=2): q_scaled_kpt = scaled_kpts[kpidx] - scaled_kpts[kqidx] - q_scaled_kpt_aliased = np.array([ - q_scaled_kpt[0] % kmesh[0], - q_scaled_kpt[1] % kmesh[1], - q_scaled_kpt[2] % kmesh[2], - ]) - qidx = np.where((scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) & - (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) & - (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]))[0] + q_scaled_kpt_aliased = np.array( + [q_scaled_kpt[0] % kmesh[0], q_scaled_kpt[1] % kmesh[1], q_scaled_kpt[2] % kmesh[2]] + ) + qidx = np.where( + (scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) + & (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) + & (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]) + )[0] assert transfer_map[qidx, kpidx] == kqidx true_transfer_map = np.array([[0, 1, 2], [2, 0, 1], [1, 2, 0]]) @@ -80,18 +82,17 @@ def test_transfer_map(): transfer_map = build_transfer_map(kmesh, scaled_kpts=scaled_kpts) for kpidx, kqidx in itertools.product(range(nkpts), repeat=2): q_scaled_kpt = scaled_kpts[kpidx] - scaled_kpts[kqidx] - q_scaled_kpt_aliased = np.array([ - q_scaled_kpt[0] % kmesh[0], - q_scaled_kpt[1] % kmesh[1], - q_scaled_kpt[2] % kmesh[2], - ]) - qidx = np.where((scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) & - (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) & - (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]))[0] + q_scaled_kpt_aliased = np.array( + [q_scaled_kpt[0] % kmesh[0], q_scaled_kpt[1] % kmesh[1], q_scaled_kpt[2] % kmesh[2]] + ) + qidx = np.where( + (scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) + & (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) + & (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]) + )[0] assert transfer_map[qidx, kpidx] == kqidx - true_transfer_map = np.array([[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], - [1, 2, 3, 0]]) + true_transfer_map = np.array([[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], [1, 2, 3, 0]]) assert np.allclose(transfer_map, true_transfer_map) kmesh = [3, 2, 1] @@ -100,41 +101,40 @@ def test_transfer_map(): transfer_map = build_transfer_map(kmesh, scaled_kpts=scaled_kpts) for kpidx, kqidx in itertools.product(range(nkpts), repeat=2): q_scaled_kpt = scaled_kpts[kpidx] - scaled_kpts[kqidx] - q_scaled_kpt_aliased = np.array([ - q_scaled_kpt[0] % kmesh[0], - q_scaled_kpt[1] % kmesh[1], - q_scaled_kpt[2] % kmesh[2], - ]) - qidx = np.where((scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) & - (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) & - (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]))[0] + q_scaled_kpt_aliased = np.array( + [q_scaled_kpt[0] % kmesh[0], q_scaled_kpt[1] % kmesh[1], q_scaled_kpt[2] % kmesh[2]] + ) + qidx = np.where( + (scaled_kpts[:, 0] == q_scaled_kpt_aliased[0]) + & (scaled_kpts[:, 1] == q_scaled_kpt_aliased[1]) + & (scaled_kpts[:, 2] == q_scaled_kpt_aliased[2]) + )[0] assert transfer_map[qidx, kpidx] == kqidx - true_transfer_map = np.array([ - [0, 1, 2, 3, 4, 5], - [1, 0, 3, 2, 5, 4], - [4, 5, 0, 1, 2, 3], - [5, 4, 1, 0, 3, 2], - [2, 3, 4, 5, 0, 1], - [3, 2, 5, 4, 1, 0], - ]) + true_transfer_map = np.array( + [ + [0, 1, 2, 3, 4, 5], + [1, 0, 3, 2, 5, 4], + [4, 5, 0, 1, 2, 3], + [5, 4, 1, 0, 3, 2], + [2, 3, 4, 5, 0, 1], + [3, 2, 5, 4, 1, 0], + ] + ) assert np.allclose(transfer_map, true_transfer_map) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_build_Gvectors(): kmesh = [3, 2, 1] g_dict = build_G_vectors(kmesh) indx = 0 for n1, n2, n3 in itertools.product([0, -1], repeat=3): - assert np.isclose(g_dict[(n1 * kmesh[0], n2 * kmesh[1], n3 * kmesh[2])], - indx) + assert np.isclose(g_dict[(n1 * kmesh[0], n2 * kmesh[1], n3 * kmesh[2])], indx) indx += 1 -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_gpq_mapping(): kmesh = [3, 2, 1] nkpts = np.prod(kmesh) @@ -155,8 +155,7 @@ def test_gpq_mapping(): assert g_val[2] in [0, -kmesh[2]] -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_build_conjugate_map(): kmesh = [4, 3, 3] nkpts = np.prod(kmesh) @@ -171,8 +170,7 @@ def test_build_conjugate_map(): assert gval[2] in [0, kmesh[2]] -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_compliment_g(): # setup kmesh = [4, 1, 1] @@ -187,8 +185,9 @@ def test_compliment_g(): kqidx = transfer_map[qidx, kpidx] kridx = transfer_map[qidx, ksidx] - conserved_k = (scaled_kpts[kpidx] - scaled_kpts[kqidx] + - scaled_kpts[kridx] - scaled_kpts[ksidx]) + conserved_k = ( + scaled_kpts[kpidx] - scaled_kpts[kqidx] + scaled_kpts[kridx] - scaled_kpts[ksidx] + ) assert conserved_k[0] % kmesh[0] == 0 assert conserved_k[1] % kmesh[1] == 0 assert conserved_k[2] % kmesh[2] == 0 @@ -213,10 +212,8 @@ def test_compliment_g(): for qidx in range(nkpts): sub_val = 0 for g1idx, g2idx in itertools.product(range(8), repeat=2): - total_k += len(q_idx_g_idx[(qidx, g1idx)]) * len( - q_idx_g_idx[(qidx, g2idx)]) - sub_val += len(q_idx_g_idx[(qidx, g1idx)]) * len( - q_idx_g_idx[(qidx, g2idx)]) + total_k += len(q_idx_g_idx[(qidx, g1idx)]) * len(q_idx_g_idx[(qidx, g2idx)]) + sub_val += len(q_idx_g_idx[(qidx, g1idx)]) * len(q_idx_g_idx[(qidx, g2idx)]) assert np.isclose(sub_val, nkpts**2) assert np.isclose(total_k, nkpts**3) diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf.py index 48b67afaa..10649af13 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf.py @@ -37,16 +37,12 @@ from pyscf.pbc.dft.gen_grid import UniformGrids from pyscf.pbc.lib.kpts_helper import conj_mapping, get_kconserv, unique -from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import ( - KMeansCVT) -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import KMeansCVT +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping def check_isdf_solution( - orbitals: npt.NDArray, - interp_orbitals: npt.NDArray, - xi: npt.NDArray, + orbitals: npt.NDArray, interp_orbitals: npt.NDArray, xi: npt.NDArray ) -> float: r"""Check accuracy of isdf least squares solution. @@ -65,16 +61,12 @@ def check_isdf_solution( """ lhs = np.einsum("Ri,Rj->Rij", orbitals.conj(), orbitals, optimize=True) - rhs = np.einsum("mi,mj->mij", - interp_orbitals.conj(), - interp_orbitals, - optimize=True) + rhs = np.einsum("mi,mj->mij", interp_orbitals.conj(), interp_orbitals, optimize=True) lhs_check = np.einsum("Rm,mij->Rij", xi, rhs, optimize=True) return np.linalg.norm(lhs - lhs_check) -def solve_isdf(orbitals: npt.NDArray, - interp_indx: npt.NDArray) -> Tuple[npt.NDArray, npt.NDArray]: +def solve_isdf(orbitals: npt.NDArray, interp_indx: npt.NDArray) -> Tuple[npt.NDArray, npt.NDArray]: """Solve for interpolating vectors given interpolating points and orbitals. Used for both supercell and k-point ISDF factorizations. @@ -95,16 +87,10 @@ def solve_isdf(orbitals: npt.NDArray, interp_orbitals = orbitals[interp_indx] # Form pseudo-densities # P[R, r_mu] = \sum_{i} phi_{R,i}^* phi_{mu,i} - pseudo_density = np.einsum("Ri,mi->Rm", - orbitals.conj(), - interp_orbitals, - optimize=True) + pseudo_density = np.einsum("Ri,mi->Rm", orbitals.conj(), interp_orbitals, optimize=True) # [Z C^]_{J, mu} = (sum_i phi_{i, J}^* phi_{i, mu}) (sum_j phi_{j, J} # phi_{i, mu}) - zc_dag = np.einsum("Rm,Rm->Rm", - pseudo_density, - pseudo_density.conj(), - optimize=True) + zc_dag = np.einsum("Rm,Rm->Rm", pseudo_density, pseudo_density.conj(), optimize=True) # Just down sample from ZC_dag cc_dag = zc_dag[interp_indx].copy() # Solve ZC_dag = Theta CC_dag @@ -112,18 +98,16 @@ def solve_isdf(orbitals: npt.NDArray, # Solve ZC_dag = Theta CC_dag # -> ZC_dag^T = CC_dag^T Theta^T # rcond = None uses MACH_EPS * max(M,N) for least squares convergence. - theta_dag, _, _, _ = np.linalg.lstsq(cc_dag.conj().T, - zc_dag.conj().T, - rcond=None) + theta_dag, _, _, _ = np.linalg.lstsq(cc_dag.conj().T, zc_dag.conj().T, rcond=None) return theta_dag.conj().T, interp_orbitals def supercell_isdf( - mydf: df.FFTDF, - interp_indx: npt.NDArray, - orbitals: npt.NDArray, - grid_points: npt.NDArray, - kpoint=np.zeros(3), + mydf: df.FFTDF, + interp_indx: npt.NDArray, + orbitals: npt.NDArray, + grid_points: npt.NDArray, + kpoint=np.zeros(3), ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray]: r""" Build ISDF-THC tensors. @@ -167,12 +151,12 @@ def supercell_isdf( def build_kpoint_zeta( - df_inst: df.FFTDF, - q: int, - delta_g: npt.NDArray, - delta_g_prime: npt.NDArray, - grid_points: npt.NDArray, - xi_mu: npt.NDArray, + df_inst: df.FFTDF, + q: int, + delta_g: npt.NDArray, + delta_g_prime: npt.NDArray, + grid_points: npt.NDArray, + xi_mu: npt.NDArray, ) -> npt.NDArray: """Build k-point THC zeta (central tensor). @@ -196,8 +180,9 @@ def build_kpoint_zeta( num_grid_points = grid_points.shape[0] # delta_G - delta_G_prime because we have Gpq and Gsr and Gsr = -Grs, phase # = Delta G = Gpq + Grs - phase_factor = np.exp(-1j * (np.einsum( - "x,Rx->R", delta_g - delta_g_prime, grid_points, optimize=True))) + phase_factor = np.exp( + -1j * (np.einsum("x,Rx->R", delta_g - delta_g_prime, grid_points, optimize=True)) + ) # Minus sign again due to we use Q = kp - kq, but we should have # V(G + k_q - k_p) coulg = tools.get_coulG(cell, k=-(q + delta_g), mesh=df_inst.mesh) @@ -210,11 +195,7 @@ def build_kpoint_zeta( def build_kpoint_zeta_single_tranlsation( - df_inst: df.FFTDF, - q: int, - delta_g: npt.NDArray, - grid_points: npt.NDArray, - xi_mu: npt.NDArray, + df_inst: df.FFTDF, q: int, delta_g: npt.NDArray, grid_points: npt.NDArray, xi_mu: npt.NDArray ) -> npt.NDArray: """Build k-point THC zeta (central tensor) @@ -238,8 +219,7 @@ def build_kpoint_zeta_single_tranlsation( num_grid_points = grid_points.shape[0] # delta_G - delta_G_prime because we have Gpq and Gsr and Gsr = -Grs, phase # = Delta G = Gpq + Grs - phase_factor = np.exp( - -1j * (np.einsum("x,Rx->R", delta_g, grid_points, optimize=True))) + phase_factor = np.exp(-1j * (np.einsum("x,Rx->R", delta_g, grid_points, optimize=True))) # Minus sign again due to we use Q = kp - kq, but we should have # V(G + k_q - k_p) coulg = tools.get_coulG(cell, k=-q, mesh=df_inst.mesh) @@ -269,18 +249,16 @@ def build_g_vectors(cell: gto.Cell) -> npt.NDArray: indx = 0 for n1, n2, n3 in itertools.product(range(-1, 2), repeat=3): g_dict[(n1, n2, n3)] = indx - g_vectors[indx] = np.einsum("n,ng->g", (n1, n2, n3), - cell.reciprocal_vectors()) - miller_indx = np.rint( - np.einsum("nx,x->n", lattice_vectors, g_vectors[indx]) / - (2 * np.pi)) + g_vectors[indx] = np.einsum("n,ng->g", (n1, n2, n3), cell.reciprocal_vectors()) + miller_indx = np.rint(np.einsum("nx,x->n", lattice_vectors, g_vectors[indx]) / (2 * np.pi)) assert (miller_indx == (n1, n2, n3)).all() indx += 1 return g_dict, g_vectors -def find_unique_g_vectors(g_vectors: npt.NDArray, g_mapping: npt.NDArray - ) -> Tuple[npt.NDArray, npt.NDArray]: +def find_unique_g_vectors( + g_vectors: npt.NDArray, g_mapping: npt.NDArray +) -> Tuple[npt.NDArray, npt.NDArray]: """Find all unique G-vectors and build mapping to original set. Args: @@ -299,17 +277,13 @@ def find_unique_g_vectors(g_vectors: npt.NDArray, g_mapping: npt.NDArray unique_g = np.unique(g_mapping[iq]) delta_gs[iq] = g_vectors[unique_g] # build map to unique index - unique_mapping[iq] = [ - ix for el in g_mapping[iq] for ix in np.where(unique_g == el)[0] - ] + unique_mapping[iq] = [ix for el in g_mapping[iq] for ix in np.where(unique_g == el)[0]] return unique_mapping, delta_gs def build_g_vector_mappings_double_translation( - cell: gto.Cell, - kpts: npt.NDArray, - momentum_map: npt.NDArray, + cell: gto.Cell, kpts: npt.NDArray, momentum_map: npt.NDArray ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray, npt.NDArray]: """build g-vector mappings that map k-point differences to 1bz. @@ -338,8 +312,7 @@ def build_g_vector_mappings_double_translation( for ikp in range(num_kpts): ikq = momentum_map[iq, ikp] delta_gpq = (kpts[ikp] - kpts[ikq]) - kpts[iq] - miller_indx = np.rint( - np.einsum("wx,x->w", lattice_vectors, delta_gpq) / (2 * np.pi)) + miller_indx = np.rint(np.einsum("wx,x->w", lattice_vectors, delta_gpq) / (2 * np.pi)) gpq_mapping[iq, ikp] = g_dict[tuple(miller_indx)] gpq_mapping_unique, delta_gs = find_unique_g_vectors(g_vectors, gpq_mapping) return g_vectors, gpq_mapping, gpq_mapping_unique, delta_gs @@ -356,13 +329,13 @@ def get_miller(lattice_vectors: npt.NDArray, g: npt.NDArray) -> npt.NDArray: miller_index: 3d array of miller indices. """ - miller_indx = np.rint( - np.einsum("wx,x->w", lattice_vectors, g) / (2 * np.pi)).astype(np.int32) + miller_indx = np.rint(np.einsum("wx,x->w", lattice_vectors, g) / (2 * np.pi)).astype(np.int32) return miller_indx -def build_minus_q_g_mapping(cell: gto.Cell, kpts: npt.NDArray, - momentum_map: npt.NDArray) -> npt.NDArray: +def build_minus_q_g_mapping( + cell: gto.Cell, kpts: npt.NDArray, momentum_map: npt.NDArray +) -> npt.NDArray: """Build conjugat g map Mapping satisfies (-Q) + G + (Q + Gpq) = 0 (*) @@ -382,12 +355,9 @@ def build_minus_q_g_mapping(cell: gto.Cell, kpts: npt.NDArray, minus_q_mapping_unique[indx_minus_q, k], and deltags is built by build_g_vector_mappings_double_translation. """ - ( - g_vecs, - g_map, - _, - delta_gs, - ) = build_g_vector_mappings_double_translation(cell, kpts, momentum_map) + (g_vecs, g_map, _, delta_gs) = build_g_vector_mappings_double_translation( + cell, kpts, momentum_map + ) g_dict, _ = build_g_vectors(cell) num_kpts = len(kpts) lattice_vectors = cell.lattice_vectors() @@ -403,22 +373,18 @@ def build_minus_q_g_mapping(cell: gto.Cell, kpts: npt.NDArray, # find index in original set of 27 igpq_comp = g_dict[tuple(get_miller(lattice_vectors, gpq_comp))] minus_q_mapping[minus_iq, ik] = igpq_comp - indx_delta_gs = np.array([ - g_dict[tuple(get_miller(lattice_vectors, g))] - for g in delta_gs[minus_iq] - ]) + indx_delta_gs = np.array( + [g_dict[tuple(get_miller(lattice_vectors, g))] for g in delta_gs[minus_iq]] + ) minus_q_mapping_unique[minus_iq] = [ - ix for el in minus_q_mapping[minus_iq] - for ix in np.where(el == indx_delta_gs)[0] + ix for el in minus_q_mapping[minus_iq] for ix in np.where(el == indx_delta_gs)[0] ] return minus_q_mapping, minus_q_mapping_unique def build_g_vector_mappings_single_translation( - cell: gto.Cell, - kpts: npt.NDArray, - kpts_pq: npt.NDArray, + cell: gto.Cell, kpts: npt.NDArray, kpts_pq: npt.NDArray ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray, npt.NDArray]: """Build g-vector mappings that map k-point differences to 1BZ. @@ -446,19 +412,15 @@ def build_g_vector_mappings_single_translation( for ikr in range(num_kpts): iks = kconserv[ikp, ikq, ikr] delta_gpqr = q + kpts[ikr] - kpts[iks] - miller_indx = np.rint( - np.einsum("wx,x->w", lattice_vectors, delta_gpqr) / (2 * np.pi)) + miller_indx = np.rint(np.einsum("wx,x->w", lattice_vectors, delta_gpqr) / (2 * np.pi)) gpqr_mapping[iq, ikr] = g_dict[tuple(miller_indx)] - gpqr_mapping_unique, delta_gs = find_unique_g_vectors( - g_vectors, gpqr_mapping) + gpqr_mapping_unique, delta_gs = find_unique_g_vectors(g_vectors, gpqr_mapping) return g_vectors, gpqr_mapping, gpqr_mapping_unique, delta_gs def inverse_g_map_double_translation( - cell: gto.Cell, - kpts: npt.NDArray, - momentum_map: npt.NDArray, + cell: gto.Cell, kpts: npt.NDArray, momentum_map: npt.NDArray ) -> npt.NDArray: """for given q and g figure out all k which satisfy q - k + g = 0 @@ -482,31 +444,21 @@ def inverse_g_map_double_translation( for ikp in range(num_kpts): ikq = momentum_map[iq, ikp] delta_gpq = (kpts[ikp] - kpts[ikq]) - kpts[iq] - miller_indx = np.rint( - np.einsum("wx,x->w", lattice_vectors, delta_gpq) / (2 * np.pi)) + miller_indx = np.rint(np.einsum("wx,x->w", lattice_vectors, delta_gpq) / (2 * np.pi)) gpq_mapping[iq, ikp] = g_dict[tuple(miller_indx)] - inverse_map = np.zeros( - ( - num_kpts, - 27, - ), - dtype=object, - ) + inverse_map = np.zeros((num_kpts, 27), dtype=object) for iq in range(num_kpts): for ig in range(len(g_vectors)): inverse_map[iq, ig] = np.array( - [ik for ik in range(num_kpts) if gpq_mapping[iq, ik] == ig]) + [ik for ik in range(num_kpts) if gpq_mapping[iq, ik] == ig] + ) return inverse_map def build_eri_isdf_double_translation( - chi: npt.NDArray, - zeta: npt.NDArray, - q_indx: int, - kpts_indx: list, - g_mapping: npt.NDArray, + chi: npt.NDArray, zeta: npt.NDArray, q_indx: int, kpts_indx: list, g_mapping: npt.NDArray ) -> npt.NDArray: """Build (pkp qkq | rkr sks) from k-point ISDF factors. @@ -538,11 +490,7 @@ def build_eri_isdf_double_translation( def build_eri_isdf_single_translation( - chi: npt.NDArray, - zeta: npt.NDArray, - q_indx: int, - kpts_indx: list, - g_mapping: npt.NDArray, + chi: npt.NDArray, zeta: npt.NDArray, q_indx: int, kpts_indx: list, g_mapping: npt.NDArray ) -> npt.NDArray: """Build (pkp qkq | rkr sks) from k-point ISDF factors. @@ -574,12 +522,12 @@ def build_eri_isdf_single_translation( def kpoint_isdf_double_translation( - df_inst: df.FFTDF, - interp_indx: npt.NDArray, - kpts: npt.NDArray, - orbitals: npt.NDArray, - grid_points: npt.NDArray, - only_unique_g: bool = True, + df_inst: df.FFTDF, + interp_indx: npt.NDArray, + kpts: npt.NDArray, + orbitals: npt.NDArray, + grid_points: npt.NDArray, + only_unique_g: bool = True, ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray, npt.NDArray]: r"""Build kpoint ISDF-THC tensors. @@ -632,8 +580,7 @@ def kpoint_isdf_double_translation( g_mapping, g_mapping_unique, delta_gs_unique, - ) = build_g_vector_mappings_double_translation(df_inst.cell, kpts, - momentum_map) + ) = build_g_vector_mappings_double_translation(df_inst.cell, kpts, momentum_map) if only_unique_g: g_mapping = g_mapping_unique delta_gs = delta_gs_unique @@ -644,24 +591,24 @@ def kpoint_isdf_double_translation( for iq in range(num_kpts): num_g = len(delta_gs[iq]) out_array = np.zeros( - (num_g, num_g, num_interp_points, num_interp_points), - dtype=np.complex128, + (num_g, num_g, num_interp_points, num_interp_points), dtype=np.complex128 ) for ig, delta_g in enumerate(delta_gs[iq]): for ig_prime, delta_g_prime in enumerate(delta_gs[iq]): - zeta_indx = build_kpoint_zeta(df_inst, kpts[iq], delta_g, - delta_g_prime, grid_points, xi) + zeta_indx = build_kpoint_zeta( + df_inst, kpts[iq], delta_g, delta_g_prime, grid_points, xi + ) out_array[ig, ig_prime] = zeta_indx zeta[iq] = out_array return chi, zeta, xi, g_mapping def kpoint_isdf_single_translation( - df_inst: df.FFTDF, - interp_indx: npt.NDArray, - kpts: npt.NDArray, - orbitals: npt.NDArray, - grid_points: npt.NDArray, + df_inst: df.FFTDF, + interp_indx: npt.NDArray, + kpts: npt.NDArray, + orbitals: npt.NDArray, + grid_points: npt.NDArray, ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray, npt.NDArray]: r"""Build kpoint ISDF-THC tensors. @@ -706,25 +653,22 @@ def kpoint_isdf_single_translation( num_kpts = len(kpts) num_interp_points = xi.shape[1] assert xi.shape == (num_grid_points, num_interp_points) - kpts_pq = np.array([(kp, kpts[ikq]) - for ikp, kp in enumerate(kpts) - for ikq in range(num_kpts)]) - kpts_pq_indx = np.array([ - (ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts) - ]) + kpts_pq = np.array([(kp, kpts[ikq]) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)]) + kpts_pq_indx = np.array([(ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)]) transfers = kpts_pq[:, 0] - kpts_pq[:, 1] unique_q, unique_indx, _ = unique(transfers) _, _, g_map_unique, delta_gs = build_g_vector_mappings_single_translation( - df_inst.cell, kpts, kpts_pq_indx[unique_indx]) + df_inst.cell, kpts, kpts_pq_indx[unique_indx] + ) num_q_vectors = len(unique_q) zeta = np.zeros((num_q_vectors,), dtype=object) for iq in range(len(unique_q)): num_g = len(delta_gs[iq]) - out_array = np.zeros((num_g, num_interp_points, num_interp_points), - dtype=np.complex128) + out_array = np.zeros((num_g, num_interp_points, num_interp_points), dtype=np.complex128) for ig, delta_g in enumerate(delta_gs[iq]): zeta_indx = build_kpoint_zeta_single_tranlsation( - df_inst, unique_q[iq], delta_g, grid_points, xi) + df_inst, unique_q[iq], delta_g, grid_points, xi + ) out_array[ig] = zeta_indx zeta[iq] = out_array return chi, zeta, xi, g_map_unique @@ -748,27 +692,22 @@ def build_isdf_orbital_inputs(mf_inst: scf.RHF) -> npt.NDArray: grid_points = cell.gen_uniform_grids(mf_inst.with_df.mesh) num_mo = mf_inst.mo_coeff[0].shape[-1] # assuming the same for each k-point num_grid_points = grid_points.shape[0] - bloch_orbitals_ao = np.array( - numint.eval_ao_kpts(cell, grid_points, kpts=kpts)) - bloch_orbitals_mo = np.einsum("kRp,kpi->kRi", - bloch_orbitals_ao, - mf_inst.mo_coeff, - optimize=True) + bloch_orbitals_ao = np.array(numint.eval_ao_kpts(cell, grid_points, kpts=kpts)) + bloch_orbitals_mo = np.einsum( + "kRp,kpi->kRi", bloch_orbitals_ao, mf_inst.mo_coeff, optimize=True + ) exp_minus_ikr = np.exp(-1j * np.einsum("kx,Rx->kR", kpts, grid_points)) - cell_periodic_mo = np.einsum("kR,kRi->kRi", exp_minus_ikr, - bloch_orbitals_mo) + cell_periodic_mo = np.einsum("kR,kRi->kRi", exp_minus_ikr, bloch_orbitals_mo) # go from kRi->Rki # AO ISDF cell_periodic_mo = cell_periodic_mo.transpose((1, 0, 2)).reshape( - (num_grid_points, num_kpts * num_mo)) + (num_grid_points, num_kpts * num_mo) + ) return cell_periodic_mo def density_guess( - density: npt.NDArray, - grid_inst: UniformGrids, - grid_points: npt.NDArray, - num_interp_points: int, + density: npt.NDArray, grid_inst: UniformGrids, grid_points: npt.NDArray, num_interp_points: int ) -> npt.NDArray: """Select initial centroids based on electronic density. @@ -784,18 +723,13 @@ def density_guess( """ norm_factor = np.einsum("R,R->", density, grid_inst.weights).real prob_dist = (density.real * grid_inst.weights) / norm_factor - indx = np.random.choice( - len(grid_points), - num_interp_points, - replace=False, - p=prob_dist, - ) + indx = np.random.choice(len(grid_points), num_interp_points, replace=False, p=prob_dist) return grid_points[indx] -def interp_indx_from_qrcp(Z: npt.NDArray, - num_interp_pts: npt.NDArray, - return_diagonal: bool = False): +def interp_indx_from_qrcp( + Z: npt.NDArray, num_interp_pts: npt.NDArray, return_diagonal: bool = False +): """Find interpolating points via QRCP Z^T P = Q R, where R has diagonal elements that are in descending order of @@ -825,8 +759,9 @@ def interp_indx_from_qrcp(Z: npt.NDArray, return interp_indx -def setup_isdf(mf_inst: scf.RHF, verbose: bool = False - ) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray]: +def setup_isdf( + mf_inst: scf.RHF, verbose: bool = False +) -> Tuple[npt.NDArray, npt.NDArray, npt.NDArray]: """Setup common data for ISDF solution. Args: @@ -848,26 +783,23 @@ def setup_isdf(mf_inst: scf.RHF, verbose: bool = False grid_points = cell.gen_uniform_grids(mf_inst.with_df.mesh) num_grid_points = grid_points.shape[0] if verbose: - print("Real space grid shape: ({}, {})".format(grid_points.shape[0], - grid_points.shape[1])) + print("Real space grid shape: ({}, {})".format(grid_points.shape[0], grid_points.shape[1])) print("Number of grid points: {}".format(num_grid_points)) - bloch_orbitals_ao = np.array( - numint.eval_ao_kpts(cell, grid_points, kpts=kpts)) - bloch_orbitals_mo = np.einsum("kRp,kpi->kRi", - bloch_orbitals_ao, - mf_inst.mo_coeff, - optimize=True) + bloch_orbitals_ao = np.array(numint.eval_ao_kpts(cell, grid_points, kpts=kpts)) + bloch_orbitals_mo = np.einsum( + "kRp,kpi->kRi", bloch_orbitals_ao, mf_inst.mo_coeff, optimize=True + ) num_mo = mf_inst.mo_coeff[0].shape[-1] # assuming the same for each k-point num_kpts = len(kpts) # Cell periodic part # u = e^{-ik.r} phi(r) exp_minus_ikr = np.exp(-1j * np.einsum("kx,Rx->kR", kpts, grid_points)) - cell_periodic_mo = np.einsum("kR,kRi->kRi", exp_minus_ikr, - bloch_orbitals_mo) + cell_periodic_mo = np.einsum("kR,kRi->kRi", exp_minus_ikr, bloch_orbitals_mo) # go from kRi->Rki # AO ISDF cell_periodic_mo = cell_periodic_mo.transpose((1, 0, 2)).reshape( - (num_grid_points, num_kpts * num_mo)) + (num_grid_points, num_kpts * num_mo) + ) return grid_points, cell_periodic_mo, bloch_orbitals_mo @@ -920,13 +852,13 @@ def num_thc_factors(self) -> int: def solve_kmeans_kpisdf( - mf_inst: scf.RHF, - num_interp_points: int, - max_kmeans_iteration: int = 500, - single_translation: bool = False, - use_density_guess: bool = True, - kmeans_weighting_function: str = "density", - verbose: bool = True, + mf_inst: scf.RHF, + num_interp_points: int, + max_kmeans_iteration: int = 500, + single_translation: bool = False, + use_density_guess: bool = True, + kmeans_weighting_function: str = "density", + verbose: bool = True, ) -> KPointTHC: r"""Solve for k-point THC factors using k-means CVT ISDF procedure. @@ -964,8 +896,7 @@ def solve_kmeans_kpisdf( optimize=True, ) if use_density_guess: - initial_centroids = density_guess(density, grid_inst, grid_points, - num_interp_points) + initial_centroids = density_guess(density, grid_inst, grid_points, num_interp_points) else: initial_centroids = None weighting_function = None @@ -975,29 +906,19 @@ def solve_kmeans_kpisdf( elif kmeans_weighting_function == "orbital_density": # w(r) = sum_{ij} |phi_i(r)| |phi_j(r)| tmp = np.einsum( - "kRi,pRj->Rkipj", - abs(bloch_orbitals_mo), - abs(bloch_orbitals_mo), - optimize=True, + "kRi,pRj->Rkipj", abs(bloch_orbitals_mo), abs(bloch_orbitals_mo), optimize=True ) weighting_function = np.einsum("Rkipj->R", tmp) elif kmeans_weighting_function == "sum_squares": # w(r) = sum_{i} |phi_{ki}(r)| weighting_function = np.einsum( - "kRi,kRi->R", - bloch_orbitals_mo.conj(), - bloch_orbitals_mo, - optimize=True, + "kRi,kRi->R", bloch_orbitals_mo.conj(), bloch_orbitals_mo, optimize=True ) weighting_function = weighting_function else: - raise ValueError( - f"Unknown value for weighting function {kmeans_weighting_function}") + raise ValueError(f"Unknown value for weighting function {kmeans_weighting_function}") interp_indx = kmeans.find_interpolating_points( - num_interp_points, - weighting_function.real, - verbose=verbose, - centroids=initial_centroids, + num_interp_points, weighting_function.real, verbose=verbose, centroids=initial_centroids ) solution = solve_for_thc_factors( mf_inst, @@ -1011,10 +932,7 @@ def solve_kmeans_kpisdf( def solve_qrcp_isdf( - mf_inst: scf.RHF, - num_interp_points: int, - single_translation: bool = True, - verbose: bool = True, + mf_inst: scf.RHF, num_interp_points: int, single_translation: bool = True, verbose: bool = True ) -> KPointTHC: r"""Solve for k-point THC factors using QRCP ISDF procedure. @@ -1035,11 +953,9 @@ def solve_qrcp_isdf( # Z_{R, (ki)(k'j)} = u_{ki}(r)* u_{k'j}(r) num_grid_points = len(grid_points) num_orbs = cell_periodic_mo.shape[1] - pair_products = np.einsum("Ri,Rj->Rij", - cell_periodic_mo.conj(), - cell_periodic_mo, - optimize=True).reshape( - (num_grid_points, num_orbs**2)) + pair_products = np.einsum( + "Ri,Rj->Rij", cell_periodic_mo.conj(), cell_periodic_mo, optimize=True + ).reshape((num_grid_points, num_orbs**2)) interp_indx = interp_indx_from_qrcp(pair_products, num_interp_points) # Solve for THC factors. solution = solve_for_thc_factors( @@ -1054,12 +970,12 @@ def solve_qrcp_isdf( def solve_for_thc_factors( - mf_inst, - interp_points_index, - cell_periodic_mo, - grid_points, - single_translation=True, - verbose=True, + mf_inst, + interp_points_index, + cell_periodic_mo, + grid_points, + single_translation=True, + verbose=True, ) -> KPointTHC: r"""Solve for k-point THC factors using interpolating points as input. @@ -1084,19 +1000,11 @@ def solve_for_thc_factors( assert isinstance(mf_inst.with_df, df.FFTDF), "mf object must use FFTDF" if single_translation: chi, zeta, xi, g_mapping = kpoint_isdf_single_translation( - mf_inst.with_df, - interp_points_index, - mf_inst.kpts, - cell_periodic_mo, - grid_points, + mf_inst.with_df, interp_points_index, mf_inst.kpts, cell_periodic_mo, grid_points ) else: chi, zeta, xi, g_mapping = kpoint_isdf_double_translation( - mf_inst.with_df, - interp_points_index, - mf_inst.kpts, - cell_periodic_mo, - grid_points, + mf_inst.with_df, interp_points_index, mf_inst.kpts, cell_periodic_mo, grid_points ) num_interp_points = len(interp_points_index) num_kpts = len(mf_inst.kpts) diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf_test.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf_test.py index 3cfd81b08..938b64480 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/isdf_test.py @@ -22,21 +22,25 @@ from pyscf.pbc.lib.kpts_helper import get_kconserv, member, unique from pyscf.pbc.tools import pyscf_ase - from openfermion.resource_estimates.pbc.hamiltonian import \ - build_momentum_transfer_mapping + from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping from openfermion.resource_estimates.pbc.thc.factorizations.isdf import ( - build_eri_isdf_double_translation, build_eri_isdf_single_translation, + build_eri_isdf_double_translation, + build_eri_isdf_single_translation, build_g_vector_mappings_double_translation, - build_g_vector_mappings_single_translation, build_g_vectors, - build_kpoint_zeta, build_minus_q_g_mapping, get_miller, - inverse_g_map_double_translation, solve_kmeans_kpisdf, solve_qrcp_isdf, - supercell_isdf) - from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import \ - KMeansCVT + build_g_vector_mappings_single_translation, + build_g_vectors, + build_kpoint_zeta, + build_minus_q_g_mapping, + get_miller, + inverse_g_map_double_translation, + solve_kmeans_kpisdf, + solve_qrcp_isdf, + supercell_isdf, + ) + from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import KMeansCVT -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_supercell_isdf_gamma(): cell = gto.Cell() cell.atom = """ @@ -68,35 +72,20 @@ def test_supercell_isdf_gamma(): num_interp_points = np.prod(cell.mesh) interp_indx = np.arange(np.prod(cell.mesh)) - chi, zeta, Theta = supercell_isdf(mf.with_df, - interp_indx, - orbitals=orbitals_mo, - grid_points=grid_points) + chi, zeta, Theta = supercell_isdf( + mf.with_df, interp_indx, orbitals=orbitals_mo, grid_points=grid_points + ) assert Theta.shape == (len(grid_points), num_interp_points) # Check overlap # Evaluate overlap from orbitals. Do it integral way to ensure # discretization error is the same from using coarse FFT grid for testing # speed - ovlp_ao = np.einsum( - "mp,mq,m->pq", - orbitals.conj(), - orbitals, - grid_inst.weights, - optimize=True, - ) - ovlp_mo = np.einsum("pi,pq,qj->ij", - mf.mo_coeff.conj(), - ovlp_ao, - mf.mo_coeff, - optimize=True) + ovlp_ao = np.einsum("mp,mq,m->pq", orbitals.conj(), orbitals, grid_inst.weights, optimize=True) + ovlp_mo = np.einsum("pi,pq,qj->ij", mf.mo_coeff.conj(), ovlp_ao, mf.mo_coeff, optimize=True) ovlp_mu = np.einsum("Rm,R->m", Theta, grid_inst.weights, optimize=True) orbitals_mo_interp = orbitals_mo[interp_indx] ovlp_isdf = np.einsum( - "mi,mj,m->ij", - orbitals_mo_interp.conj(), - orbitals_mo_interp, - ovlp_mu, - optimize=True, + "mi,mj,m->ij", orbitals_mo_interp.conj(), orbitals_mo_interp, ovlp_mu, optimize=True ) assert np.allclose(ovlp_mo, ovlp_isdf) # Check ERIs. @@ -110,8 +99,7 @@ def test_supercell_isdf_gamma(): assert np.allclose(eri_thc, eri_ref) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_supercell_isdf_complex(): cell = gto.Cell() cell.atom = """ @@ -144,47 +132,25 @@ def test_supercell_isdf_complex(): num_interp_points = 10 * num_mo nocc = cell.nelec[0] density = np.einsum( - "Ri,Ri->R", - orbitals_mo[:, :nocc].conj(), - orbitals_mo[:, :nocc], - optimize=True, + "Ri,Ri->R", orbitals_mo[:, :nocc].conj(), orbitals_mo[:, :nocc], optimize=True ) kmeans = KMeansCVT(grid_points, max_iteration=500) - interp_indx = kmeans.find_interpolating_points( - num_interp_points, - density.real, - verbose=False, - ) + interp_indx = kmeans.find_interpolating_points(num_interp_points, density.real, verbose=False) - chi, zeta, Theta = supercell_isdf(mf.with_df, - interp_indx, - orbitals=orbitals_mo, - grid_points=grid_points) + chi, zeta, Theta = supercell_isdf( + mf.with_df, interp_indx, orbitals=orbitals_mo, grid_points=grid_points + ) assert Theta.shape == (len(grid_points), num_interp_points) # Check overlap # Evaluate overlap from orbitals. Do it integral way to ensure # discretization error is the same from using coarse FFT grid for testing # speed - ovlp_ao = np.einsum( - "mp,mq,m->pq", - orbitals.conj(), - orbitals, - grid_inst.weights, - optimize=True, - ) - ovlp_mo = np.einsum("pi,pq,qj->ij", - mf.mo_coeff.conj(), - ovlp_ao, - mf.mo_coeff, - optimize=True) + ovlp_ao = np.einsum("mp,mq,m->pq", orbitals.conj(), orbitals, grid_inst.weights, optimize=True) + ovlp_mo = np.einsum("pi,pq,qj->ij", mf.mo_coeff.conj(), ovlp_ao, mf.mo_coeff, optimize=True) ovlp_mu = np.einsum("Rm,R->m", Theta, grid_inst.weights, optimize=True) orbitals_mo_interp = orbitals_mo[interp_indx] ovlp_isdf = np.einsum( - "mi,mj,m->ij", - orbitals_mo_interp.conj(), - orbitals_mo_interp, - ovlp_mu, - optimize=True, + "mi,mj,m->ij", orbitals_mo_interp.conj(), orbitals_mo_interp, ovlp_mu, optimize=True ) assert np.allclose(ovlp_mo, ovlp_isdf) # Check ERIs. @@ -199,8 +165,7 @@ def test_supercell_isdf_complex(): assert np.allclose(eri_thc, eri_ref) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_G_vector_mapping_double_translation(): ase_atom = bulk("AlN", "wurtzite", a=3.11, c=4.98) cell = gto.Cell() @@ -217,12 +182,9 @@ def test_G_vector_mapping_double_translation(): kpts = cell.make_kpts(kmesh) momentum_map = build_momentum_transfer_mapping(cell, kpts) - ( - G_vecs, - G_map, - G_unique, - _, - ) = build_g_vector_mappings_double_translation(cell, kpts, momentum_map) + (G_vecs, G_map, G_unique, _) = build_g_vector_mappings_double_translation( + cell, kpts, momentum_map + ) num_kpts = len(kpts) for iq in range(num_kpts): for ikp in range(num_kpts): @@ -242,8 +204,7 @@ def test_G_vector_mapping_double_translation(): assert ik in inv_G_map[iq, ix_G_qk] -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_G_vector_mapping_single_translation(): cell = gto.Cell() cell.atom = """ @@ -265,23 +226,15 @@ def test_G_vector_mapping_single_translation(): kpts = cell.make_kpts(kmesh) num_kpts = len(kpts) - kpts_pq = np.array([(kp, kpts[ikq]) - for ikp, kp in enumerate(kpts) - for ikq in range(num_kpts)]) + kpts_pq = np.array([(kp, kpts[ikq]) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)]) - kpts_pq_indx = np.array([ - (ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts) - ]) + kpts_pq_indx = np.array([(ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)]) transfers = kpts_pq[:, 0] - kpts_pq[:, 1] - assert len(transfers) == (nk**3)**2 + assert len(transfers) == (nk**3) ** 2 _, unique_indx, _ = unique(transfers) - ( - _, - _, - G_unique, - delta_Gs, - ) = build_g_vector_mappings_single_translation(cell, kpts, - kpts_pq_indx[unique_indx]) + (_, _, G_unique, delta_Gs) = build_g_vector_mappings_single_translation( + cell, kpts, kpts_pq_indx[unique_indx] + ) kconserv = get_kconserv(cell, kpts) for ikp in range(num_kpts): for ikq in range(num_kpts): @@ -299,8 +252,7 @@ def test_G_vector_mapping_single_translation(): assert np.allclose(delta_G_expected, delta_G) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_kpoint_isdf_double_translation(): cell = gto.Cell() cell.atom = """ @@ -327,11 +279,7 @@ def test_kpoint_isdf_double_translation(): num_mo = mf.mo_coeff[0].shape[-1] num_thc = np.prod(cell.mesh) kpt_thc = solve_kmeans_kpisdf( - mf, - num_thc, - use_density_guess=True, - verbose=False, - single_translation=False, + mf, num_thc, use_density_guess=True, verbose=False, single_translation=False ) num_kpts = len(momentum_map) for iq in range(1, num_kpts): @@ -341,27 +289,17 @@ def test_kpoint_isdf_double_translation(): ikr = momentum_map[iq, iks] _ = kpt_thc.g_mapping[iq, iks] kpt_pqrs = [kpts[ikp], kpts[ikq], kpts[ikr], kpts[iks]] - mos_pqrs = [ - mf.mo_coeff[ikp], - mf.mo_coeff[ikq], - mf.mo_coeff[ikr], - mf.mo_coeff[iks], - ] - eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, - compact=False).reshape( - (num_mo,) * 4) + mos_pqrs = [mf.mo_coeff[ikp], mf.mo_coeff[ikq], mf.mo_coeff[ikr], mf.mo_coeff[iks]] + eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, compact=False).reshape( + (num_mo,) * 4 + ) eri_pqrs_isdf = build_eri_isdf_double_translation( - kpt_thc.chi, - kpt_thc.zeta, - iq, - [ikp, ikq, ikr, iks], - kpt_thc.g_mapping, + kpt_thc.chi, kpt_thc.zeta, iq, [ikp, ikq, ikr, iks], kpt_thc.g_mapping ) assert np.allclose(eri_pqrs, eri_pqrs_isdf) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_kpoint_isdf_single_translation(): cell = gto.Cell() cell.atom = """ @@ -387,15 +325,9 @@ def test_kpoint_isdf_single_translation(): num_thc = np.prod(cell.mesh) num_kpts = len(kpts) kpt_thc = solve_kmeans_kpisdf( - mf, - num_thc, - use_density_guess=True, - verbose=False, - single_translation=True, + mf, num_thc, use_density_guess=True, verbose=False, single_translation=True ) - kpts_pq = np.array([(kp, kpts[ikq]) - for ikp, kp in enumerate(kpts) - for ikq in range(num_kpts)]) + kpts_pq = np.array([(kp, kpts[ikq]) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)]) transfers = kpts_pq[:, 0] - kpts_pq[:, 1] _, unique_indx, _ = unique(transfers) kconserv = get_kconserv(cell, kpts) @@ -405,23 +337,14 @@ def test_kpoint_isdf_single_translation(): for ikr in range(num_kpts): iks = kconserv[ikp, ikq, ikr] kpt_pqrs = [kpts[ikp], kpts[ikq], kpts[ikr], kpts[iks]] - mos_pqrs = [ - mf.mo_coeff[ikp], - mf.mo_coeff[ikq], - mf.mo_coeff[ikr], - mf.mo_coeff[iks], - ] - eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, - compact=False).reshape( - (num_mo,) * 4) + mos_pqrs = [mf.mo_coeff[ikp], mf.mo_coeff[ikq], mf.mo_coeff[ikr], mf.mo_coeff[iks]] + eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, compact=False).reshape( + (num_mo,) * 4 + ) q = kpts[ikp] - kpts[ikq] qindx = member(q, transfers[unique_indx])[0] eri_pqrs_isdf = build_eri_isdf_single_translation( - kpt_thc.chi, - kpt_thc.zeta, - qindx, - [ikp, ikq, ikr, iks], - kpt_thc.g_mapping, + kpt_thc.chi, kpt_thc.zeta, qindx, [ikp, ikq, ikr, iks], kpt_thc.g_mapping ) assert np.allclose(eri_pqrs, eri_pqrs_isdf) @@ -432,8 +355,7 @@ def get_complement(miller_indx, kmesh): return complement -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_kpoint_isdf_symmetries(): cell = gto.Cell() @@ -456,22 +378,14 @@ def test_kpoint_isdf_symmetries(): kpts = cell.make_kpts(kmesh) mf = scf.KRHF(cell, kpts) mf.kernel() - num_thc = np.prod( - cell.mesh) # should be no THC error when selecting all the grid points. + num_thc = np.prod(cell.mesh) # should be no THC error when selecting all the grid points. kpt_thc = solve_kmeans_kpisdf( - mf, - num_thc, - use_density_guess=True, - verbose=False, - single_translation=False, + mf, num_thc, use_density_guess=True, verbose=False, single_translation=False ) momentum_map = build_momentum_transfer_mapping(cell, kpts) - ( - _, - _, - G_unique, - delta_Gs, - ) = build_g_vector_mappings_double_translation(cell, kpts, momentum_map) + (_, _, G_unique, delta_Gs) = build_g_vector_mappings_double_translation( + cell, kpts, momentum_map + ) _, minus_Q_G_map_unique = build_minus_q_g_mapping(cell, kpts, momentum_map) num_kpts = len(kpts) # Test symmetries from Appendix D of https://arxiv.org/pdf/2302.05531.pdf @@ -488,12 +402,8 @@ def test_kpoint_isdf_symmetries(): iGsr = G_unique[iq, ik_prime] ik_prime_minus_q = momentum_map[iq, ik_prime] # Sanity check G mappings - assert np.allclose(kpts[ik] - kpts[ik_minus_q] - kpts[iq], - delta_Gs[iq][iGpq]) - assert np.allclose( - kpts[ik_prime] - kpts[ik_prime_minus_q] - kpts[iq], - delta_Gs[iq][iGsr], - ) + assert np.allclose(kpts[ik] - kpts[ik_minus_q] - kpts[iq], delta_Gs[iq][iGpq]) + assert np.allclose(kpts[ik_prime] - kpts[ik_prime_minus_q] - kpts[iq], delta_Gs[iq][iGsr]) # (pk qk-Q | rk'-Q sk') = (q k-Q p k | sk' rk'-Q)* ik_prime_minus_q = momentum_map[iq, ik_prime] # uncomment to check normal eris @@ -520,12 +430,7 @@ def test_kpoint_isdf_symmetries(): # Sanity check do literal minus signs (should be complex # conjugate) zeta_test = build_kpoint_zeta( - mf.with_df, - -kpts[iq], - -delta_Gs[iq][iGpq], - -delta_Gs[iq][iGsr], - grid_points, - kpt_thc.xi, + mf.with_df, -kpts[iq], -delta_Gs[iq][iGpq], -delta_Gs[iq][iGsr], grid_points, kpt_thc.xi ) assert np.allclose(zeta_ref, zeta_test.conj()) # (pk qk-Q | rk'-Q sk') = (rk'-Q s k'| pk qk-Q) @@ -548,8 +453,7 @@ def test_kpoint_isdf_symmetries(): assert np.allclose(zeta_ref, zeta_test.conj().T) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_symmetry_of_G_maps(): cell = gto.Cell() cell.atom = """ @@ -569,12 +473,9 @@ def test_symmetry_of_G_maps(): kmesh = [3, 3, 3] kpts = cell.make_kpts(kmesh) momentum_map = build_momentum_transfer_mapping(cell, kpts) - ( - G_vecs, - G_map, - _, - delta_Gs, - ) = build_g_vector_mappings_double_translation(cell, kpts, momentum_map) + (G_vecs, G_map, _, delta_Gs) = build_g_vector_mappings_double_translation( + cell, kpts, momentum_map + ) G_dict, _ = build_g_vectors(cell) num_kpts = len(kpts) lattice_vectors = cell.lattice_vectors() @@ -589,8 +490,7 @@ def test_symmetry_of_G_maps(): Gpq_comp = -(kpts[minus_iq] + kpts[iq] + Gpq) iGpq_comp = G_dict[tuple(get_miller(lattice_vectors, Gpq_comp))] G_indx_unique = [ - G_dict[tuple(get_miller(lattice_vectors, G))] - for G in delta_Gs[minus_iq] + G_dict[tuple(get_miller(lattice_vectors, G))] for G in delta_Gs[minus_iq] ] if iq == 1: pass @@ -604,8 +504,7 @@ def test_symmetry_of_G_maps(): assert iGsr_comp in G_indx_unique # Check minus Q mapping - minus_Q_G_map, minus_Q_G_map_unique = build_minus_q_g_mapping( - cell, kpts, momentum_map) + minus_Q_G_map, minus_Q_G_map_unique = build_minus_q_g_mapping(cell, kpts, momentum_map) for iq in range(1, num_kpts): minus_iq = minus_k_map[iq] for ik in range(num_kpts): @@ -618,8 +517,7 @@ def test_symmetry_of_G_maps(): assert np.allclose(Gpq_comp, Gpq_comp_from_map) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_isdf_qrcp(): cell = gto.Cell() cell.atom = """ @@ -653,20 +551,11 @@ def test_isdf_qrcp(): for iks in range(num_kpts): ikr = momentum_map[iq, iks] kpt_pqrs = [kpts[ikp], kpts[ikq], kpts[ikr], kpts[iks]] - mos_pqrs = [ - mf.mo_coeff[ikp], - mf.mo_coeff[ikq], - mf.mo_coeff[ikr], - mf.mo_coeff[iks], - ] - eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, - compact=False).reshape( - (num_mo,) * 4) + mos_pqrs = [mf.mo_coeff[ikp], mf.mo_coeff[ikq], mf.mo_coeff[ikr], mf.mo_coeff[iks]] + eri_pqrs = mf.with_df.ao2mo(mos_pqrs, kpt_pqrs, compact=False).reshape( + (num_mo,) * 4 + ) eri_pqrs_isdf = build_eri_isdf_double_translation( - kpt_thc.chi, - kpt_thc.zeta, - iq, - [ikp, ikq, ikr, iks], - kpt_thc.g_mapping, + kpt_thc.chi, kpt_thc.zeta, iq, [ikp, ikq, ikr, iks], kpt_thc.g_mapping ) assert np.allclose(eri_pqrs, eri_pqrs_isdf) diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans.py index b6e434e84..223fe278c 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans.py @@ -12,6 +12,7 @@ # limitations under the License. import numpy as np import numpy.typing as npt + """Module for performing K-Means CVT algorithm to find interpolating points. Provides centroidal Veronoi tesselation of the grid of real space points @@ -20,13 +21,7 @@ class KMeansCVT(object): - - def __init__( - self, - grid: npt.NDArray, - max_iteration: int = 100, - threshold: float = 1e-6, - ): + def __init__(self, grid: npt.NDArray, max_iteration: int = 100, threshold: float = 1e-6): """Initialize k-means solver to find interpolating points for ISDF. Args: @@ -44,8 +39,7 @@ def __init__( self.threshold = threshold @staticmethod - def classify_grid_points(grid_points: npt.NDArray, - centroids: npt.NDArray) -> npt.NDArray: + def classify_grid_points(grid_points: npt.NDArray, centroids: npt.NDArray) -> npt.NDArray: r"""Assign grid points to centroids. Find centroid closest to each given grid point. @@ -72,8 +66,7 @@ def classify_grid_points(grid_points: npt.NDArray, classification = np.argmin(distances, axis=1) return classification - def compute_new_centroids(self, weighting, grid_mapping, - current_centroids) -> npt.NDArray: + def compute_new_centroids(self, weighting, grid_mapping, current_centroids) -> npt.NDArray: r""" Centroids are defined via: @@ -112,11 +105,7 @@ def map_centroids_to_grid(self, centroids): return grid_mapping def find_interpolating_points( - self, - num_interp_points: int, - weighting_factor: npt.NDArray, - centroids=None, - verbose=True, + self, num_interp_points: int, weighting_factor: npt.NDArray, centroids=None, verbose=True ) -> npt.NDArray: """Find interpolating points using KMeans-CVT algorithm. @@ -134,9 +123,7 @@ def find_interpolating_points( num_grid_points = self.grid.shape[0] if centroids is None: # Randomly select grid points as centroids. - centroids_indx = np.random.choice(num_grid_points, - num_interp_points, - replace=False) + centroids_indx = np.random.choice(num_grid_points, num_interp_points, replace=False) centroids = self.grid[centroids_indx].copy() else: assert len(centroids) == num_interp_points @@ -148,8 +135,7 @@ def find_interpolating_points( for iteration in range(self.max_iteration): grid_mapping = self.classify_grid_points(self.grid, centroids) # Global reduce - new_centroids[:] = self.compute_new_centroids( - weighting_factor, grid_mapping, centroids) + new_centroids[:] = self.compute_new_centroids(weighting_factor, grid_mapping, centroids) delta_grid = np.linalg.norm(new_centroids - centroids) if verbose and iteration % 10 == 0: print(f"{iteration:<9d} {delta_grid:13.8e}") diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans_test.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans_test.py index 3b2fb4a1b..1ba60abe3 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/kmeans_test.py @@ -12,13 +12,11 @@ # limitations under the License. import numpy as np -from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import ( - KMeansCVT) +from openfermion.resource_estimates.pbc.thc.factorizations.kmeans import KMeansCVT def gaussian(dx, sigma): - return np.exp(-np.einsum("Gx,Gx->G", dx, dx) / - sigma**2.0) / (2 * (sigma * np.pi)**0.5) + return np.exp(-np.einsum("Gx,Gx->G", dx, dx) / sigma**2.0) / (2 * (sigma * np.pi) ** 0.5) def gen_gaussian(xx, yy, grid, sigma=0.125): @@ -52,9 +50,7 @@ def test_kmeans(): weight = gen_gaussian(xx, yy, grid) kmeans = KMeansCVT(grid) - interp_points = kmeans.find_interpolating_points(num_interp_points, - weight, - verbose=False) + interp_points = kmeans.find_interpolating_points(num_interp_points, weight, verbose=False) interp_points_ref = [37, 27, 77, 81, 38, 24, 73, 62, 76, 22] assert np.allclose(interp_points, interp_points_ref) diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax.py index 80cbd0b3d..6c86bbef6 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax.py @@ -36,6 +36,7 @@ from scipy.optimize import minimize from jax.config import config + config.update("jax_enable_x64", True) import jax @@ -47,8 +48,7 @@ KPointTHC, solve_kmeans_kpisdf, ) -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping def load_thc_factors(chkfile_name: str) -> KPointTHC: @@ -76,11 +76,11 @@ def load_thc_factors(chkfile_name: str) -> KPointTHC: def save_thc_factors( - chkfile_name: str, - chi: npt.NDArray, - zeta: npt.NDArray, - gpq_map: npt.NDArray, - xi: Union[npt.NDArray, None] = None, + chkfile_name: str, + chi: npt.NDArray, + zeta: npt.NDArray, + gpq_map: npt.NDArray, + xi: Union[npt.NDArray, None] = None, ) -> None: """Write THC factors to file @@ -116,11 +116,7 @@ def get_zeta_size(zeta: npt.NDArray) -> int: def unpack_thc_factors( - xcur: npt.NDArray, - num_thc: int, - num_orb: int, - num_kpts: int, - num_g_per_q: list, + xcur: npt.NDArray, num_thc: int, num_orb: int, num_kpts: int, num_g_per_q: list ) -> Tuple[npt.NDArray, npt.NDArray]: """Unpack THC factors from flattened array used for reoptimization. @@ -138,25 +134,24 @@ def unpack_thc_factors( # leaf tensor (num_kpts, num_orb, num_thc) chi_size = num_kpts * num_orb * num_thc chi_real = xcur[:chi_size].reshape(num_kpts, num_orb, num_thc) - chi_imag = xcur[chi_size:2 * chi_size].reshape(num_kpts, num_orb, num_thc) + chi_imag = xcur[chi_size : 2 * chi_size].reshape(num_kpts, num_orb, num_thc) chi = chi_real + 1j * chi_imag - zeta_packed = xcur[2 * chi_size:] + zeta_packed = xcur[2 * chi_size :] zeta = [] start = 0 for iq in range(num_kpts): num_g = num_g_per_q[iq] size = num_g * num_g * num_thc * num_thc - zeta_real = zeta_packed[start:start + size].reshape( - (num_g, num_g, num_thc, num_thc)) - zeta_imag = zeta_packed[start + size:start + 2 * size].reshape( - (num_g, num_g, num_thc, num_thc)) + zeta_real = zeta_packed[start : start + size].reshape((num_g, num_g, num_thc, num_thc)) + zeta_imag = zeta_packed[start + size : start + 2 * size].reshape( + (num_g, num_g, num_thc, num_thc) + ) zeta.append(zeta_real + 1j * zeta_imag) start += 2 * size return chi, zeta -def pack_thc_factors(chi: npt.NDArray, zeta: npt.NDArray, - buffer: npt.NDArray) -> None: +def pack_thc_factors(chi: npt.NDArray, zeta: npt.NDArray, buffer: npt.NDArray) -> None: """Pack THC factors into flattened array used for reoptimization. Args: @@ -165,27 +160,25 @@ def pack_thc_factors(chi: npt.NDArray, zeta: npt.NDArray, buffer: Flattened array containing k-point THC factors. Modified inplace """ assert len(chi.shape) == 3 - buffer[:chi.size] = chi.real.ravel() - buffer[chi.size:2 * chi.size] = chi.imag.ravel() + buffer[: chi.size] = chi.real.ravel() + buffer[chi.size : 2 * chi.size] = chi.imag.ravel() start = 2 * chi.size num_kpts = len(zeta) for iq in range(num_kpts): size = zeta[iq].size - buffer[start:start + size] = zeta[iq].real.ravel() - buffer[start + size:start + 2 * size] = zeta[iq].imag.ravel() + buffer[start : start + size] = zeta[iq].real.ravel() + buffer[start + size : start + 2 * size] = zeta[iq].imag.ravel() start += 2 * size @jax.jit def compute_objective_batched( - chis: Tuple[jnpt.ArrayLike, jnpt.ArrayLike, jnpt.ArrayLike, jnpt. - ArrayLike], - zetas: jnpt.ArrayLike, - chols: Tuple[jnpt.ArrayLike, jnpt.ArrayLike], - norm_factors: Tuple[jnpt.ArrayLike, jnpt.ArrayLike, jnpt. - ArrayLike, jnpt.ArrayLike], - num_kpts: int, - penalty_param: float = 0.0, + chis: Tuple[jnpt.ArrayLike, jnpt.ArrayLike, jnpt.ArrayLike, jnpt.ArrayLike], + zetas: jnpt.ArrayLike, + chols: Tuple[jnpt.ArrayLike, jnpt.ArrayLike], + norm_factors: Tuple[jnpt.ArrayLike, jnpt.ArrayLike, jnpt.ArrayLike, jnpt.ArrayLike], + num_kpts: int, + penalty_param: float = 0.0, ) -> float: """Compute THC objective function. @@ -215,19 +208,18 @@ def compute_objective_batched( deri = (eri_thc - eri_ref) / num_kpts norm_left = norm_factors[0] * norm_factors[1] norm_right = norm_factors[2] * norm_factors[3] - mpq_normalized = (jnp.einsum( - "JP,JPQ,JQ->JPQ", norm_left, zetas, norm_right, optimize=True) / - num_kpts) + mpq_normalized = ( + jnp.einsum("JP,JPQ,JQ->JPQ", norm_left, zetas, norm_right, optimize=True) / num_kpts + ) - lambda_z = jnp.sum(jnp.einsum("jpq->j", 0.5 * jnp.abs(mpq_normalized))**2.0) + lambda_z = jnp.sum(jnp.einsum("jpq->j", 0.5 * jnp.abs(mpq_normalized)) ** 2.0) - res = 0.5 * jnp.sum((jnp.abs(deri))**2) + penalty_param * lambda_z + res = 0.5 * jnp.sum((jnp.abs(deri)) ** 2) + penalty_param * lambda_z return res def prepare_batched_data_indx_arrays( - momentum_map: npt.NDArray, - gpq_map: npt.NDArray, + momentum_map: npt.NDArray, gpq_map: npt.NDArray ) -> Tuple[npt.NDArray, npt.NDArray]: r"""Create arrays to batch over. @@ -255,20 +247,14 @@ def prepare_batched_data_indx_arrays( for ik_prime in range(num_kpts): ik_prime_minus_q = momentum_map[iq, ik_prime] gsr = gpq_map[iq, ik_prime] - indx_pqrs[iq, indx] = [ - ik, - ik_minus_q, - ik_prime_minus_q, - ik_prime, - ] + indx_pqrs[iq, indx] = [ik, ik_minus_q, ik_prime_minus_q, ik_prime] zetas[iq, indx] = [gpq, gsr] indx += 1 return indx_pqrs, zetas @jax.jit -def get_batched_data_1indx(array: jnpt.ArrayLike, - indx: jnpt.ArrayLike) -> jnpt.ArrayLike: +def get_batched_data_1indx(array: jnpt.ArrayLike, indx: jnpt.ArrayLike) -> jnpt.ArrayLike: """Helper function to extract entries of array given another array. Args: @@ -282,8 +268,9 @@ def get_batched_data_1indx(array: jnpt.ArrayLike, @jax.jit -def get_batched_data_2indx(array: jnpt.ArrayLike, indxa: jnpt.ArrayLike, - indxb: jnpt.ArrayLike) -> jnpt.ArrayLike: +def get_batched_data_2indx( + array: jnpt.ArrayLike, indxa: jnpt.ArrayLike, indxb: jnpt.ArrayLike +) -> jnpt.ArrayLike: """Helper function to extract entries of 2D array given another array Args: @@ -298,15 +285,15 @@ def get_batched_data_2indx(array: jnpt.ArrayLike, indxa: jnpt.ArrayLike, def thc_objective_regularized_batched( - xcur: jnpt.ArrayLike, - num_orb: int, - num_thc: int, - momentum_map: npt.NDArray, - gpq_map: npt.NDArray, - chol: jnpt.ArrayLike, - indx_arrays: Tuple[jnpt.ArrayLike, jnpt.ArrayLike], - batch_size: int, - penalty_param=0.0, + xcur: jnpt.ArrayLike, + num_orb: int, + num_thc: int, + momentum_map: npt.NDArray, + gpq_map: npt.NDArray, + chol: jnpt.ArrayLike, + indx_arrays: Tuple[jnpt.ArrayLike, jnpt.ArrayLike], + batch_size: int, + penalty_param=0.0, ) -> float: """Compute THC objective function. @@ -331,11 +318,10 @@ def thc_objective_regularized_batched( """ num_kpts = momentum_map.shape[0] num_g_per_q = [len(np.unique(gq)) for gq in gpq_map] - chi, zeta = unpack_thc_factors(xcur, num_thc, num_orb, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(xcur, num_thc, num_orb, num_kpts, num_g_per_q) # Normalization factor, no factor of sqrt as there are 4 chis in total when # building ERI. - norm_kp = jnp.einsum("kpP,kpP->kP", chi.conj(), chi, optimize=True)**0.5 + norm_kp = jnp.einsum("kpP,kpP->kP", chi.conj(), chi, optimize=True) ** 0.5 num_batches = math.ceil(num_kpts**2 / batch_size) indx_pqrs, indx_zeta = indx_arrays @@ -348,25 +334,19 @@ def thc_objective_regularized_batched( chi_q = get_batched_data_1indx(chi, indx_pqrs[iq, start:end, 1]) chi_r = get_batched_data_1indx(chi, indx_pqrs[iq, start:end, 2]) chi_s = get_batched_data_1indx(chi, indx_pqrs[iq, start:end, 3]) - norm_k1 = get_batched_data_1indx(norm_kp, - indx_pqrs[iq, start:end, 0]) - norm_k2 = get_batched_data_1indx(norm_kp, - indx_pqrs[iq, start:end, 1]) - norm_k3 = get_batched_data_1indx(norm_kp, - indx_pqrs[iq, start:end, 2]) - norm_k4 = get_batched_data_1indx(norm_kp, - indx_pqrs[iq, start:end, 3]) + norm_k1 = get_batched_data_1indx(norm_kp, indx_pqrs[iq, start:end, 0]) + norm_k2 = get_batched_data_1indx(norm_kp, indx_pqrs[iq, start:end, 1]) + norm_k3 = get_batched_data_1indx(norm_kp, indx_pqrs[iq, start:end, 2]) + norm_k4 = get_batched_data_1indx(norm_kp, indx_pqrs[iq, start:end, 3]) zeta_batch = get_batched_data_2indx( - zeta[iq], - indx_zeta[iq, start:end, 0], - indx_zeta[iq, start:end, 1], + zeta[iq], indx_zeta[iq, start:end, 0], indx_zeta[iq, start:end, 1] + ) + chol_batch_pq = get_batched_data_2indx( + chol, indx_pqrs[iq, start:end, 0], indx_pqrs[iq, start:end, 1] + ) + chol_batch_rs = get_batched_data_2indx( + chol, indx_pqrs[iq, start:end, 2], indx_pqrs[iq, start:end, 3] ) - chol_batch_pq = get_batched_data_2indx(chol, - indx_pqrs[iq, start:end, 0], - indx_pqrs[iq, start:end, 1]) - chol_batch_rs = get_batched_data_2indx(chol, - indx_pqrs[iq, start:end, 2], - indx_pqrs[iq, start:end, 3]) objective += compute_objective_batched( (chi_p, chi_q, chi_r, chi_s), zeta_batch, @@ -379,13 +359,7 @@ def thc_objective_regularized_batched( def thc_objective_regularized( - xcur, - num_orb, - num_thc, - momentum_map, - gpq_map, - chol, - penalty_param=0.0, + xcur, num_orb, num_thc, momentum_map, gpq_map, chol, penalty_param=0.0 ): """Compute THC objective function. Non-batched version. @@ -406,10 +380,9 @@ def thc_objective_regularized( res = 0.0 num_kpts = momentum_map.shape[0] num_g_per_q = [len(np.unique(GQ)) for GQ in gpq_map] - chi, zeta = unpack_thc_factors(xcur, num_thc, num_orb, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(xcur, num_thc, num_orb, num_kpts, num_g_per_q) num_kpts = momentum_map.shape[0] - norm_kP = jnp.einsum("kpP,kpP->kP", chi.conj(), chi, optimize=True)**0.5 + norm_kP = jnp.einsum("kpP,kpP->kP", chi.conj(), chi, optimize=True) ** 0.5 for iq in range(num_kpts): for ik in range(num_kpts): ik_minus_q = momentum_map[iq, ik] @@ -426,34 +399,31 @@ def thc_objective_regularized( chi[ik_prime], ) eri_ref = jnp.einsum( - "npq,nsr->pqrs", - chol[ik, ik_minus_q], - chol[ik_prime, ik_prime_minus_q].conj(), + "npq,nsr->pqrs", chol[ik, ik_minus_q], chol[ik_prime, ik_prime_minus_q].conj() ) deri = (eri_thc - eri_ref) / num_kpts norm_left = norm_kP[ik] * norm_kP[ik_minus_q] norm_right = norm_kP[ik_prime_minus_q] * norm_kP[ik_prime] - MPQ_normalized = (jnp.einsum("P,PQ,Q->PQ", norm_left, - zeta[iq][Gpq, Gsr], norm_right) / - num_kpts) + MPQ_normalized = ( + jnp.einsum("P,PQ,Q->PQ", norm_left, zeta[iq][Gpq, Gsr], norm_right) / num_kpts + ) lambda_z = 0.5 * jnp.sum(jnp.abs(MPQ_normalized)) - res += 0.5 * jnp.sum( - (jnp.abs(deri))**2) + penalty_param * (lambda_z**2) + res += 0.5 * jnp.sum((jnp.abs(deri)) ** 2) + penalty_param * (lambda_z**2) return res def lbfgsb_opt_kpthc_l2reg( - chi: npt.NDArray, - zeta: npt.NDArray, - momentum_map: npt.NDArray, - gpq_map: npt.NDArray, - chol: npt.NDArray, - chkfile_name: Union[str, None] = None, - maxiter: int = 150_000, - disp_freq: int = 98, - penalty_param: Union[float, None] = None, + chi: npt.NDArray, + zeta: npt.NDArray, + momentum_map: npt.NDArray, + gpq_map: npt.NDArray, + chol: npt.NDArray, + chkfile_name: Union[str, None] = None, + maxiter: int = 150_000, + disp_freq: int = 98, + penalty_param: Union[float, None] = None, ) -> Tuple[npt.NDArray, float]: """Least-squares fit of two-electron integral tensors with L-BFGS-B @@ -480,8 +450,7 @@ def lbfgsb_opt_kpthc_l2reg( Returns: objective: THC objective function """ - initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), - dtype=np.float64) + initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, initial_guess) assert len(chi.shape) == 3 assert len(zeta[0].shape) == 4 @@ -510,7 +479,7 @@ def lbfgsb_opt_kpthc_l2reg( penalty_param=1.0, ) # set penalty - lambda_z = (reg_loss - loss)**0.5 + lambda_z = (reg_loss - loss) ** 0.5 if penalty_param is None: # loss + lambda_z^2 - loss penalty_param = loss / lambda_z @@ -530,25 +499,15 @@ def lbfgsb_opt_kpthc_l2reg( gpq_map, jnp.array(chol), penalty_param, - )) + ) + ) res = minimize( thc_objective_regularized, initial_guess, - args=( - num_orb, - num_thc, - momentum_map, - gpq_map, - jnp.array(chol), - penalty_param, - ), + args=(num_orb, num_thc, momentum_map, gpq_map, jnp.array(chol), penalty_param), jac=thc_grad, method="L-BFGS-B", - options={ - "disp": disp_freq > 0, - "iprint": disp_freq, - "maxiter": maxiter, - }, + options={"disp": disp_freq > 0, "iprint": disp_freq, "maxiter": maxiter}, ) params = np.array(res.x) @@ -563,23 +522,22 @@ def lbfgsb_opt_kpthc_l2reg( ) if chkfile_name is not None: num_g_per_q = [len(np.unique(GQ)) for GQ in gpq_map] - chi, zeta = unpack_thc_factors(params, num_thc, num_orb, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(params, num_thc, num_orb, num_kpts, num_g_per_q) save_thc_factors(chkfile_name, chi, zeta, gpq_map) return np.array(params), loss def lbfgsb_opt_kpthc_l2reg_batched( - chi: npt.NDArray, - zeta: npt.NDArray, - momentum_map: npt.NDArray, - gpq_map: npt.NDArray, - chol: npt.NDArray, - chkfile_name: Union[str, None] = None, - maxiter: int = 150_000, - disp_freq: int = 98, - penalty_param: Union[float, None] = None, - batch_size: Union[int, None] = None, + chi: npt.NDArray, + zeta: npt.NDArray, + momentum_map: npt.NDArray, + gpq_map: npt.NDArray, + chol: npt.NDArray, + chkfile_name: Union[str, None] = None, + maxiter: int = 150_000, + disp_freq: int = 98, + penalty_param: Union[float, None] = None, + batch_size: Union[int, None] = None, ) -> Tuple[npt.NDArray, float]: """Least-squares fit of two-electron integral tensors with L-BFGS-B. @@ -608,8 +566,7 @@ def lbfgsb_opt_kpthc_l2reg_batched( Returns: objective: THC objective function """ - initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), - dtype=np.float64) + initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, initial_guess) assert len(chi.shape) == 3 assert len(zeta[0].shape) == 4 @@ -621,9 +578,7 @@ def lbfgsb_opt_kpthc_l2reg_batched( if batch_size is None: batch_size = num_kpts**2 indx_arrays = prepare_batched_data_indx_arrays(momentum_map, gpq_map) - data_amount = batch_size * ( - 4 * num_orb * num_thc + num_thc * num_thc # chi[p,m] + zeta[m,n] - ) + data_amount = batch_size * (4 * num_orb * num_thc + num_thc * num_thc) # chi[p,m] + zeta[m,n] data_size_gb = (data_amount * 16) / (1024**3) print(f"Batch size in GB: {data_size_gb}") loss = thc_objective_regularized_batched( @@ -652,7 +607,7 @@ def lbfgsb_opt_kpthc_l2reg_batched( print("Time to evaluate loss function : {:.4f}".format(time.time() - start)) print("loss {}".format(loss)) # set penalty - lambda_z = (reg_loss - loss)**0.5 + lambda_z = (reg_loss - loss) ** 0.5 if penalty_param is None: # loss + lambda_z^2 - loss penalty_param = loss / lambda_z @@ -674,7 +629,8 @@ def lbfgsb_opt_kpthc_l2reg_batched( indx_arrays, batch_size, penalty_param, - )) + ) + ) print("# Time to evaluate gradient: {:.4f}".format(time.time() - start)) res = minimize( thc_objective_regularized_batched, @@ -691,11 +647,7 @@ def lbfgsb_opt_kpthc_l2reg_batched( ), jac=thc_grad, method="L-BFGS-B", - options={ - "disp": disp_freq > 0, - "iprint": disp_freq, - "maxiter": maxiter, - }, + options={"disp": disp_freq > 0, "iprint": disp_freq, "maxiter": maxiter}, ) loss = thc_objective_regularized_batched( jnp.array(res.x), @@ -712,24 +664,23 @@ def lbfgsb_opt_kpthc_l2reg_batched( params = np.array(res.x) if chkfile_name is not None: num_g_per_q = [len(np.unique(gq)) for gq in gpq_map] - chi, zeta = unpack_thc_factors(params, num_thc, num_orb, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(params, num_thc, num_orb, num_kpts, num_g_per_q) save_thc_factors(chkfile_name, chi, zeta, gpq_map) return np.array(params), loss def adagrad_opt_kpthc_batched( - chi, - zeta, - momentum_map, - gpq_map, - chol, - batch_size=None, - chkfile_name=None, - stepsize=0.01, - momentum=0.9, - maxiter=50_000, - gtol=1.0e-5, + chi, + zeta, + momentum_map, + gpq_map, + chol, + batch_size=None, + chkfile_name=None, + stepsize=0.01, + momentum=0.9, + maxiter=50_000, + gtol=1.0e-5, ) -> Tuple[npt.NDArray, float]: """Adagrad optimization of THC factors. @@ -765,11 +716,9 @@ def adagrad_opt_kpthc_batched( assert zeta[0].shape[-1] == num_thc assert zeta[0].shape[-2] == num_thc # set initial guess - initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), - dtype=np.float64) + initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, initial_guess) - opt_init, opt_update, get_params = adagrad(step_size=stepsize, - momentum=momentum) + opt_init, opt_update, get_params = adagrad(step_size=stepsize, momentum=momentum) opt_state = opt_init(initial_guess) thc_grad = jax.grad(thc_objective_regularized_batched, argnums=[0]) @@ -780,14 +729,7 @@ def adagrad_opt_kpthc_batched( def update(i, opt_state): params = get_params(opt_state) gradient = thc_grad( - params, - num_orb, - num_thc, - momentum_map, - gpq_map, - chol, - indx_arrays, - batch_size, + params, num_orb, num_thc, momentum_map, gpq_map, chol, indx_arrays, batch_size ) grad_norm_l1 = np.linalg.norm(gradient[0], ord=1) return opt_update(i, gradient[0], opt_state), grad_norm_l1 @@ -797,14 +739,7 @@ def update(i, opt_state): params = get_params(opt_state) if t % 500 == 0: fval = thc_objective_regularized_batched( - params, - num_orb, - num_thc, - momentum_map, - gpq_map, - chol, - indx_arrays, - batch_size, + params, num_orb, num_thc, momentum_map, gpq_map, chol, indx_arrays, batch_size ) outline = "Objective val {: 5.15f}".format(fval) outline += "\tGrad L1-norm {: 5.15f}".format(grad_l1) @@ -818,19 +753,11 @@ def update(i, opt_state): # save results before returning x = np.array(params) loss = thc_objective_regularized_batched( - params, - num_orb, - num_thc, - momentum_map, - gpq_map, - chol, - indx_arrays, - batch_size, + params, num_orb, num_thc, momentum_map, gpq_map, chol, indx_arrays, batch_size ) if chkfile_name is not None: num_g_per_q = [len(np.unique(GQ)) for GQ in gpq_map] - chi, zeta = unpack_thc_factors(x, num_thc, num_orb, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(x, num_thc, num_orb, num_kpts, num_g_per_q) save_thc_factors(chkfile_name, chi, zeta, gpq_map) return params, loss @@ -855,14 +782,7 @@ def make_contiguous_cholesky(cholesky: npt.NDArray) -> npt.NDArray: # Alternatively todo: padd with zeros min_naux = min([cholesky[k1, k1].shape[0] for k1 in range(num_kpts)]) cholesky_contiguous = np.zeros( - ( - num_kpts, - num_kpts, - min_naux, - num_mo, - num_mo, - ), - dtype=np.complex128, + (num_kpts, num_kpts, min_naux, num_mo, num_mo), dtype=np.complex128 ) for ik1 in range(num_kpts): for ik2 in range(num_kpts): @@ -888,8 +808,7 @@ def compute_isdf_loss(chi, zeta, momentum_map, gpq_map, chol): Returns: loss: ISDF loss in ERIs. """ - initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), - dtype=np.float64) + initial_guess = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, initial_guess) assert len(chi.shape) == 3 assert len(zeta[0].shape) == 4 @@ -908,22 +827,22 @@ def compute_isdf_loss(chi, zeta, momentum_map, gpq_map, chol): def kpoint_thc_via_isdf( - kmf: scf.RHF, - cholesky: npt.NDArray, - num_thc: int, - perform_bfgs_opt: bool = True, - perform_adagrad_opt: bool = True, - bfgs_maxiter: int = 3000, - adagrad_maxiter: int = 3000, - checkpoint_basename: str = "thc", - save_checkpoints: bool = False, - use_batched_algos: bool = True, - penalty_param: Union[None, float] = None, - batch_size: Union[None, bool] = None, - max_kmeans_iteration: int = 500, - verbose: bool = False, - initial_guess: Union[None, KPointTHC] = None, - isdf_density_guess: bool = False, + kmf: scf.RHF, + cholesky: npt.NDArray, + num_thc: int, + perform_bfgs_opt: bool = True, + perform_adagrad_opt: bool = True, + bfgs_maxiter: int = 3000, + adagrad_maxiter: int = 3000, + checkpoint_basename: str = "thc", + save_checkpoints: bool = False, + use_batched_algos: bool = True, + penalty_param: Union[None, float] = None, + batch_size: Union[None, bool] = None, + max_kmeans_iteration: int = 500, + verbose: bool = False, + initial_guess: Union[None, KPointTHC] = None, + isdf_density_guess: bool = False, ) -> Tuple[KPointTHC, dict]: """ Solve k-point THC using ISDF as an initial guess. @@ -974,8 +893,7 @@ def kpoint_thc_via_isdf( use_density_guess=isdf_density_guess, ) if verbose: - print("Time for generating initial guess {:.4f}".format(time.time() - - start)) + print("Time for generating initial guess {:.4f}".format(time.time() - start)) num_mo = kmf.mo_coeff[0].shape[-1] num_kpts = len(kmf.kpts) chi, zeta, g_mapping = kpt_thc.chi, kpt_thc.zeta, kpt_thc.g_mapping @@ -985,8 +903,9 @@ def kpoint_thc_via_isdf( momentum_map = build_momentum_transfer_mapping(kmf.cell, kmf.kpts) if cholesky is not None: cholesky_contiguous = make_contiguous_cholesky(cholesky) - info["loss_isdf"] = compute_isdf_loss(chi, zeta, momentum_map, - g_mapping, cholesky_contiguous) + info["loss_isdf"] = compute_isdf_loss( + chi, zeta, momentum_map, g_mapping, cholesky_contiguous + ) start = time.time() if perform_bfgs_opt: if save_checkpoints: @@ -1021,8 +940,7 @@ def kpoint_thc_via_isdf( ) info["loss_bfgs"] = loss_bfgs num_g_per_q = [len(np.unique(GQ)) for GQ in g_mapping] - chi, zeta = unpack_thc_factors(opt_params, num_thc, num_mo, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(opt_params, num_thc, num_mo, num_kpts, num_g_per_q) if verbose: print("Time for BFGS {:.4f}".format(time.time() - start)) start = time.time() @@ -1044,8 +962,7 @@ def kpoint_thc_via_isdf( ) info["loss_adagrad"] = loss_ada num_g_per_q = [len(np.unique(GQ)) for GQ in g_mapping] - chi, zeta = unpack_thc_factors(opt_params, num_thc, num_mo, num_kpts, - num_g_per_q) + chi, zeta = unpack_thc_factors(opt_params, num_thc, num_mo, num_kpts, num_g_per_q) if verbose: print("Time for ADAGRAD {:.4f}".format(time.time() - start)) result = KPointTHC(chi=chi, zeta=zeta, g_mapping=g_mapping, xi=kpt_thc.xi) diff --git a/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax_test.py b/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax_test.py index 869b6f54f..65ceda3ff 100644 --- a/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/factorizations/thc_jax_test.py @@ -21,23 +21,30 @@ from pyscf.pbc import gto, mp, scf from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping, cholesky_from_df_ints) - from openfermion.resource_estimates.pbc.thc.factorizations.isdf import \ - solve_kmeans_kpisdf + build_momentum_transfer_mapping, + cholesky_from_df_ints, + ) + from openfermion.resource_estimates.pbc.thc.factorizations.isdf import solve_kmeans_kpisdf from openfermion.resource_estimates.pbc.thc.factorizations.thc_jax import ( - adagrad_opt_kpthc_batched, get_zeta_size, kpoint_thc_via_isdf, - lbfgsb_opt_kpthc_l2reg, lbfgsb_opt_kpthc_l2reg_batched, - make_contiguous_cholesky, pack_thc_factors, - prepare_batched_data_indx_arrays, thc_objective_regularized, - thc_objective_regularized_batched, unpack_thc_factors) - from openfermion.resource_estimates.thc.utils.thc_factorization import \ - lbfgsb_opt_thc_l2reg - from openfermion.resource_estimates.thc.utils.thc_factorization import \ - thc_objective_regularized as thc_obj_mol + adagrad_opt_kpthc_batched, + get_zeta_size, + kpoint_thc_via_isdf, + lbfgsb_opt_kpthc_l2reg, + lbfgsb_opt_kpthc_l2reg_batched, + make_contiguous_cholesky, + pack_thc_factors, + prepare_batched_data_indx_arrays, + thc_objective_regularized, + thc_objective_regularized_batched, + unpack_thc_factors, + ) + from openfermion.resource_estimates.thc.utils.thc_factorization import lbfgsb_opt_thc_l2reg + from openfermion.resource_estimates.thc.utils.thc_factorization import ( + thc_objective_regularized as thc_obj_mol, + ) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_kpoint_thc_reg_gamma(): cell = gto.Cell() @@ -63,18 +70,15 @@ def test_kpoint_thc_reg_gamma(): mf.kernel() num_mo = mf.mo_coeff[0].shape[-1] num_interp_points = 10 * mf.mo_coeff[0].shape[-1] - kpt_thc = solve_kmeans_kpisdf(mf, - num_interp_points, - single_translation=False, - verbose=False) + kpt_thc = solve_kmeans_kpisdf(mf, num_interp_points, single_translation=False, verbose=False) chi, zeta, g_mapping = kpt_thc.chi, kpt_thc.zeta, kpt_thc.g_mapping momentum_map = build_momentum_transfer_mapping(cell, kpts) buffer = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, buffer) num_G_per_Q = [z.shape[0] for z in zeta] - chi_unpacked, zeta_unpacked = unpack_thc_factors(buffer, num_interp_points, - num_mo, num_kpts, - num_G_per_Q) + chi_unpacked, zeta_unpacked = unpack_thc_factors( + buffer, num_interp_points, num_mo, num_kpts, num_G_per_Q + ) assert np.allclose(chi_unpacked, chi) for iq in range(num_kpts): assert np.allclose(zeta[iq], zeta_unpacked[iq]) @@ -91,19 +95,14 @@ def test_kpoint_thc_reg_gamma(): eri = np.einsum("npq,nrs->pqrs", Luv_cont[0, 0], Luv_cont[0, 0]).real buffer = np.zeros((chi.size + get_zeta_size(zeta)), dtype=np.float64) # transposed in openfermion - buffer[:chi.size] = chi.T.real.ravel() - buffer[chi.size:] = zeta[iq].real.ravel() + buffer[: chi.size] = chi.T.real.ravel() + buffer[chi.size :] = zeta[iq].real.ravel() np.random.seed(7) opt_param = lbfgsb_opt_thc_l2reg( - eri, - num_interp_points, - maxiter=10, - initial_guess=buffer, - penalty_param=None, + eri, num_interp_points, maxiter=10, initial_guess=buffer, penalty_param=None ) - chi_unpacked_mol = (opt_param[:chi.size].reshape( - (num_interp_points, num_mo)).T) - zeta_unpacked_mol = opt_param[chi.size:].reshape(zeta[0].shape) + chi_unpacked_mol = opt_param[: chi.size].reshape((num_interp_points, num_mo)).T + zeta_unpacked_mol = opt_param[chi.size :].reshape(zeta[0].shape) opt_param, _ = lbfgsb_opt_kpthc_l2reg( chi, zeta, @@ -114,28 +113,21 @@ def test_kpoint_thc_reg_gamma(): penalty_param=None, disp_freq=-1, ) - chi_unpacked, zeta_unpacked = unpack_thc_factors(opt_param, - num_interp_points, num_mo, - num_kpts, num_G_per_Q) + chi_unpacked, zeta_unpacked = unpack_thc_factors( + opt_param, num_interp_points, num_mo, num_kpts, num_G_per_Q + ) assert np.allclose(chi_unpacked[0], chi_unpacked_mol) assert np.allclose(zeta_unpacked[0], zeta_unpacked_mol) mol_obj = thc_obj_mol(buffer, num_mo, num_interp_points, eri, 1e-3) buffer = np.zeros(2 * (chi.size + get_zeta_size(zeta)), dtype=np.float64) pack_thc_factors(chi, zeta, buffer) gam_obj = thc_objective_regularized( - buffer, - num_mo, - num_interp_points, - momentum_map, - g_mapping, - Luv_cont, - 1e-3, + buffer, num_mo, num_interp_points, momentum_map, g_mapping, Luv_cont, 1e-3 ) assert mol_obj - gam_obj < 1e-12 -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_kpoint_thc_reg_batched(): cell = gto.Cell() @@ -161,10 +153,7 @@ def test_kpoint_thc_reg_batched(): mf.kernel() momentum_map = build_momentum_transfer_mapping(cell, kpts) num_interp_points = 10 * cell.nao - kpt_thc = solve_kmeans_kpisdf(mf, - num_interp_points, - single_translation=False, - verbose=False) + kpt_thc = solve_kmeans_kpisdf(mf, num_interp_points, single_translation=False, verbose=False) chi, zeta, g_mapping = kpt_thc.chi, kpt_thc.zeta, kpt_thc.g_mapping rsmf = scf.KRHF(mf.cell, mf.kpts).rs_density_fit() rsmf.mo_occ = mf.mo_occ @@ -179,9 +168,9 @@ def test_kpoint_thc_reg_batched(): pack_thc_factors(chi, zeta, buffer) num_G_per_Q = [z.shape[0] for z in zeta] num_mo = mf.mo_coeff[0].shape[-1] - chi_unpacked, zeta_unpacked = unpack_thc_factors(buffer, num_interp_points, - num_mo, num_kpts, - num_G_per_Q) + chi_unpacked, zeta_unpacked = unpack_thc_factors( + buffer, num_interp_points, num_mo, num_kpts, num_G_per_Q + ) # Test packing/unpacking operation assert np.allclose(chi_unpacked, chi) for iq in range(num_kpts): @@ -189,13 +178,7 @@ def test_kpoint_thc_reg_batched(): # Test objective is the same batched/non-batched penalty = 1e-3 obj_ref = thc_objective_regularized( - buffer, - num_mo, - num_interp_points, - momentum_map, - g_mapping, - Luv_cont, - penalty, + buffer, num_mo, num_interp_points, momentum_map, g_mapping, Luv_cont, penalty ) # # Test gradient is the same indx_arrays = prepare_batched_data_indx_arrays(momentum_map, g_mapping) @@ -214,13 +197,7 @@ def test_kpoint_thc_reg_batched(): assert abs(obj_ref - obj_batched) < 1e-12 grad_ref_fun = jax.grad(thc_objective_regularized) grad_ref = grad_ref_fun( - buffer, - num_mo, - num_interp_points, - momentum_map, - g_mapping, - Luv_cont, - penalty, + buffer, num_mo, num_interp_points, momentum_map, g_mapping, Luv_cont, penalty ) # Test gradient is the same grad_batched_fun = jax.grad(thc_objective_regularized_batched) @@ -271,30 +248,17 @@ def test_kpoint_thc_reg_batched(): ) assert np.allclose(opt_param_batched, opt_param_batched_diff_batch) ada_param, _ = adagrad_opt_kpthc_batched( - chi, - zeta, - momentum_map, - g_mapping, - jnp.array(Luv_cont), - maxiter=2, - batch_size=1, + chi, zeta, momentum_map, g_mapping, jnp.array(Luv_cont), maxiter=2, batch_size=1 ) assert np.allclose(opt_param, opt_param_batched) batch_size = 7 ada_param_diff_batch, _ = adagrad_opt_kpthc_batched( - chi, - zeta, - momentum_map, - g_mapping, - jnp.array(Luv_cont), - batch_size=batch_size, - maxiter=2, + chi, zeta, momentum_map, g_mapping, jnp.array(Luv_cont), batch_size=batch_size, maxiter=2 ) assert np.allclose(ada_param, ada_param_diff_batch) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_kpoint_thc_helper(): cell = gto.Cell() @@ -329,11 +293,7 @@ def test_kpoint_thc_helper(): num_mo = mf.mo_coeff[0].shape[-1] # Just testing function runs kpt_thc, _ = kpoint_thc_via_isdf( - mf, - Luv, - cthc * num_mo, - perform_adagrad_opt=False, - perform_bfgs_opt=False, + mf, Luv, cthc * num_mo, perform_adagrad_opt=False, perform_bfgs_opt=False ) kpt_thc_bfgs, _ = kpoint_thc_via_isdf( mf, diff --git a/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc.py b/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc.py index 6420a46e6..70ec61a68 100644 --- a/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc.py +++ b/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc.py @@ -20,22 +20,17 @@ from pyscf.pbc import scf from pyscf.pbc.tools.k2gamma import kpts_to_kmesh -from openfermion.resource_estimates.pbc.thc.factorizations.thc_jax import ( - kpoint_thc_via_isdf,) +from openfermion.resource_estimates.pbc.thc.factorizations.thc_jax import kpoint_thc_via_isdf from openfermion.resource_estimates.pbc.resources.data_types import PBCResources -from openfermion.resource_estimates.pbc.thc.thc_integrals import ( - KPTHCDoubleTranslation,) +from openfermion.resource_estimates.pbc.thc.thc_integrals import KPTHCDoubleTranslation from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( build_approximate_eris, build_cc_inst, build_approximate_eris_rohf, ) -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_hamiltonian,) -from openfermion.resource_estimates.pbc.thc.compute_lambda_thc import ( - compute_lambda,) -from openfermion.resource_estimates.pbc.thc.compute_thc_resources import ( - compute_cost,) +from openfermion.resource_estimates.pbc.hamiltonian import build_hamiltonian +from openfermion.resource_estimates.pbc.thc.compute_lambda_thc import compute_lambda +from openfermion.resource_estimates.pbc.thc.compute_thc_resources import compute_cost @dataclass @@ -44,17 +39,17 @@ class THCResources(PBCResources): def generate_costing_table( - pyscf_mf: scf.HF, - thc_rank_params: Union[list, npt.NDArray], - name="pbc", - chi: int = 10, - beta: int = 20, - dE_for_qpe: float = 0.0016, - reoptimize: bool = True, - bfgs_maxiter: int = 3000, - adagrad_maxiter: int = 3000, - fft_df_mesh: Union[None, list] = None, - energy_method: str = "MP2", + pyscf_mf: scf.HF, + thc_rank_params: Union[list, npt.NDArray], + name="pbc", + chi: int = 10, + beta: int = 20, + dE_for_qpe: float = 0.0016, + reoptimize: bool = True, + bfgs_maxiter: int = 3000, + adagrad_maxiter: int = 3000, + fft_df_mesh: Union[None, list] = None, + energy_method: str = "MP2", ) -> pd.DataFrame: """Generate resource estimate costing table for THC Hamiltonian. @@ -128,20 +123,13 @@ def generate_costing_table( bfgs_maxiter=bfgs_maxiter, adagrad_maxiter=adagrad_maxiter, ) - thc_helper = KPTHCDoubleTranslation(kpt_thc.chi, - kpt_thc.zeta, - pyscf_mf, - chol=chol) + thc_helper = KPTHCDoubleTranslation(kpt_thc.chi, kpt_thc.zeta, pyscf_mf, chol=chol) thc_lambda = compute_lambda(hcore, thc_helper) kmesh = kpts_to_kmesh(pyscf_mf.cell, pyscf_mf.kpts) if pyscf_mf.cell.spin == 0: - approx_eris = build_approximate_eris(cc_inst, - thc_helper, - eris=approx_eris) + approx_eris = build_approximate_eris(cc_inst, thc_helper, eris=approx_eris) else: - approx_eris = build_approximate_eris_rohf(cc_inst, - thc_helper, - eris=approx_eris) + approx_eris = build_approximate_eris_rohf(cc_inst, thc_helper, eris=approx_eris) approx_energy, _, _ = energy_function(approx_eris) thc_res_cost = compute_cost( num_spin_orbs, diff --git a/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc_test.py b/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc_test.py index ab5ad7c25..235d86924 100644 --- a/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/generate_costing_table_thc_test.py @@ -16,14 +16,13 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from openfermion.resource_estimates.pbc.thc.\ - generate_costing_table_thc import generate_costing_table - from openfermion.resource_estimates.pbc.testing import ( - make_diamond_113_szv,) + from openfermion.resource_estimates.pbc.thc.generate_costing_table_thc import ( + generate_costing_table, + ) + from openfermion.resource_estimates.pbc.testing import make_diamond_113_szv -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_generate_costing_table_thc(): mf = make_diamond_113_szv() diff --git a/src/openfermion/resource_estimates/pbc/thc/thc_integrals.py b/src/openfermion/resource_estimates/pbc/thc/thc_integrals.py index 6c32b98a7..da323fc62 100644 --- a/src/openfermion/resource_estimates/pbc/thc/thc_integrals.py +++ b/src/openfermion/resource_estimates/pbc/thc/thc_integrals.py @@ -17,8 +17,7 @@ from pyscf.pbc import scf from pyscf.pbc.lib.kpts_helper import unique, member -from openfermion.resource_estimates.pbc.hamiltonian import ( - build_momentum_transfer_mapping,) +from openfermion.resource_estimates.pbc.hamiltonian import build_momentum_transfer_mapping from openfermion.resource_estimates.pbc.thc.factorizations.isdf import ( build_g_vector_mappings_double_translation, @@ -29,13 +28,12 @@ class KPTHCDoubleTranslation: - def __init__( - self, - chi: npt.NDArray, - zeta: npt.NDArray, - kmf: scf.HF, - chol: Union[npt.NDArray, None] = None, + self, + chi: npt.NDArray, + zeta: npt.NDArray, + kmf: scf.HF, + chol: Union[npt.NDArray, None] = None, ): """Class for constructing THC factorized ERIs. @@ -56,17 +54,16 @@ def __init__( self.kmf = kmf self.nk = len(self.kmf.kpts) self.kpts = self.kmf.kpts - self.k_transfer_map = build_momentum_transfer_mapping( - self.kmf.cell, self.kmf.kpts) - self.reverse_k_transfer_map = np.zeros_like( - self.k_transfer_map) # [kidx, kmq_idx] = qidx + self.k_transfer_map = build_momentum_transfer_mapping(self.kmf.cell, self.kmf.kpts) + self.reverse_k_transfer_map = np.zeros_like(self.k_transfer_map) # [kidx, kmq_idx] = qidx for kidx in range(self.nk): for qidx in range(self.nk): kmq_idx = self.k_transfer_map[qidx, kidx] self.reverse_k_transfer_map[kidx, kmq_idx] = qidx # Two-translation ISDF zeta[iq, dG, dG'] _, _, g_map_unique, _ = build_g_vector_mappings_double_translation( - self.kmf.cell, self.kmf.kpts, self.k_transfer_map) + self.kmf.cell, self.kmf.kpts, self.k_transfer_map + ) self.g_mapping = g_map_unique self.chol = chol @@ -87,8 +84,7 @@ def get_eri(self, ikpts: list) -> npt.NDArray: """ ikp, ikq, _, _ = ikpts q_indx = self.reverse_k_transfer_map[ikp, ikq] - return build_eri_isdf_double_translation(self.chi, self.zeta, q_indx, - ikpts, self.g_mapping) + return build_eri_isdf_double_translation(self.chi, self.zeta, q_indx, ikpts, self.g_mapping) def get_eri_exact(self, kpts: list) -> npt.NDArray: """Construct (pkp qkq| rkr sks) exactly from cholesky factors. @@ -103,10 +99,7 @@ def get_eri_exact(self, kpts: list) -> npt.NDArray: ikp, ikq, ikr, iks = kpts if self.chol is not None: return np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ) else: eri_kpt = self.kmf.with_df.ao2mo( @@ -114,21 +107,13 @@ def get_eri_exact(self, kpts: list) -> npt.NDArray: [self.kmf.kpts[i] for i in (ikp, ikq, ikr, iks)], compact=False, ) - shape_pqrs = [ - self.kmf.mo_coeff[i].shape[-1] for i in (ikp, ikq, ikr, iks) - ] + shape_pqrs = [self.kmf.mo_coeff[i].shape[-1] for i in (ikp, ikq, ikr, iks)] eri_kpt = eri_kpt.reshape(shape_pqrs) return eri_kpt class KPTHCSingleTranslation(KPTHCDoubleTranslation): - - def __init__( - self, - chi: npt.NDArray, - zeta: npt.NDArray, - kmf: scf.HF, - ): + def __init__(self, chi: npt.NDArray, zeta: npt.NDArray, kmf: scf.HF): """Class for constructing THC factorized ERIs. Assumes one delta G (i.e. a single translation vector.) @@ -147,21 +132,17 @@ def __init__( # one-translation ISDF zeta[iq, dG] num_kpts = len(self.kmf.kpts) kpts = self.kmf.kpts - kpts_pq = np.array([(kp, kpts[ikq]) - for ikp, kp in enumerate(kpts) - for ikq in range(num_kpts)]) - kpts_pq_indx = np.array([ - (ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts) - ]) + kpts_pq = np.array( + [(kp, kpts[ikq]) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)] + ) + kpts_pq_indx = np.array( + [(ikp, ikq) for ikp, kp in enumerate(kpts) for ikq in range(num_kpts)] + ) transfers = kpts_pq[:, 0] - kpts_pq[:, 1] _, unique_indx, _ = unique(transfers) - ( - _, - _, - g_map_unique, - _, - ) = build_g_vector_mappings_single_translation( - kmf.cell, kpts, kpts_pq_indx[unique_indx]) + (_, _, g_map_unique, _) = build_g_vector_mappings_single_translation( + kmf.cell, kpts, kpts_pq_indx[unique_indx] + ) self.g_mapping = g_map_unique self.momentum_transfers = transfers[unique_indx] @@ -185,8 +166,7 @@ def get_eri(self, ikpts): ikp, ikq, _, _ = ikpts mom_transfer = self.kpts[ikp] - self.kpts[ikq] q_indx = member(mom_transfer, self.momentum_transfers)[0] - return build_eri_isdf_single_translation(self.chi, self.zeta, q_indx, - ikpts, self.g_mapping) + return build_eri_isdf_single_translation(self.chi, self.zeta, q_indx, ikpts, self.g_mapping) def get_eri_exact(self, kpts): """Construct (pkp qkq| rkr sks) exactly from cholesky factors. @@ -201,10 +181,7 @@ def get_eri_exact(self, kpts): ikp, ikq, ikr, iks = kpts if self.chol is not None: return np.einsum( - "npq,nsr->pqrs", - self.chol[ikp, ikq], - self.chol[iks, ikr].conj(), - optimize=True, + "npq,nsr->pqrs", self.chol[ikp, ikq], self.chol[iks, ikr].conj(), optimize=True ) else: eri_kpt = self.kmf.with_df.ao2mo( @@ -212,8 +189,6 @@ def get_eri_exact(self, kpts): [self.kmf.kpts[i] for i in (ikp, ikq, ikr, iks)], compact=False, ) - shape_pqrs = [ - self.kmf.mo_coeff[i].shape[-1] for i in (ikp, ikq, ikr, iks) - ] + shape_pqrs = [self.kmf.mo_coeff[i].shape[-1] for i in (ikp, ikq, ikr, iks)] eri_kpt = eri_kpt.reshape(shape_pqrs) return eri_kpt diff --git a/src/openfermion/resource_estimates/pbc/thc/thc_integrals_test.py b/src/openfermion/resource_estimates/pbc/thc/thc_integrals_test.py index 5888be962..62903a81c 100644 --- a/src/openfermion/resource_estimates/pbc/thc/thc_integrals_test.py +++ b/src/openfermion/resource_estimates/pbc/thc/thc_integrals_test.py @@ -18,18 +18,15 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf.pbc import gto, scf, cc - from openfermion.resource_estimates.pbc.thc.factorizations.isdf import ( - solve_kmeans_kpisdf,) + from openfermion.resource_estimates.pbc.thc.factorizations.isdf import solve_kmeans_kpisdf from openfermion.resource_estimates.pbc.thc.thc_integrals import ( KPTHCDoubleTranslation, KPTHCSingleTranslation, ) - from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import ( - build_approximate_eris,) + from openfermion.resource_estimates.pbc.hamiltonian.cc_extensions import build_approximate_eris -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') @pytest.mark.slow def test_thc_helper(): cell = gto.Cell() @@ -75,29 +72,22 @@ def test_thc_helper(): ik_minus_q = helper.k_transfer_map[iq, ik] for ik_prime in range(num_kpts): ik_prime_minus_q = helper.k_transfer_map[iq, ik_prime] - eri_thc = helper.get_eri( - [ik, ik_minus_q, ik_prime_minus_q, ik_prime]) - eri_exact = helper.get_eri_exact( - [ik, ik_minus_q, ik_prime_minus_q, ik_prime]) + eri_thc = helper.get_eri([ik, ik_minus_q, ik_prime_minus_q, ik_prime]) + eri_exact = helper.get_eri_exact([ik, ik_minus_q, ik_prime_minus_q, ik_prime]) assert np.allclose(eri_thc, eri_exact) eris_approx = build_approximate_eris(approx_cc, helper) emp2, _, _ = approx_cc.init_amps(eris_approx) assert np.isclose(emp2, exact_emp2) - kpt_thc = solve_kmeans_kpisdf(mf, - np.prod(cell.mesh), - single_translation=True, - verbose=False) + kpt_thc = solve_kmeans_kpisdf(mf, np.prod(cell.mesh), single_translation=True, verbose=False) helper = KPTHCSingleTranslation(kpt_thc.chi, kpt_thc.zeta, mf) for iq in range(num_kpts): for ik in range(num_kpts): ik_minus_q = helper.k_transfer_map[iq, ik] for ik_prime in range(num_kpts): ik_prime_minus_q = helper.k_transfer_map[iq, ik_prime] - eri_thc = helper.get_eri( - [ik, ik_minus_q, ik_prime_minus_q, ik_prime]) - eri_exact = helper.get_eri_exact( - [ik, ik_minus_q, ik_prime_minus_q, ik_prime]) + eri_thc = helper.get_eri([ik, ik_minus_q, ik_prime_minus_q, ik_prime]) + eri_exact = helper.get_eri_exact([ik, ik_minus_q, ik_prime_minus_q, ik_prime]) assert np.allclose(eri_thc, eri_exact) eris_approx = build_approximate_eris(approx_cc, helper) diff --git a/src/openfermion/resource_estimates/sf/__init__.py b/src/openfermion/resource_estimates/sf/__init__.py index 1825cf686..d3ad7b06d 100644 --- a/src/openfermion/resource_estimates/sf/__init__.py +++ b/src/openfermion/resource_estimates/sf/__init__.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # Copyright 2020 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/openfermion/resource_estimates/sf/compute_cost_sf.py b/src/openfermion/resource_estimates/sf/compute_cost_sf.py index f1686a2d5..7f10e198e 100644 --- a/src/openfermion/resource_estimates/sf/compute_cost_sf.py +++ b/src/openfermion/resource_estimates/sf/compute_cost_sf.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Determine costs for SF decomposition in QC """ from typing import Tuple import numpy as np @@ -6,14 +6,10 @@ from openfermion.resource_estimates.utils import QR, QI, QR2, power_two -def compute_cost(n: int, - lam: float, - dE: float, - L: int, - chi: int, - stps: int, - verbose: bool = False) -> Tuple[int, int, int]: - """ Determine fault-tolerant costs using SF decomposition in quantum chem +def compute_cost( + n: int, lam: float, dE: float, L: int, chi: int, stps: int, verbose: bool = False +) -> Tuple[int, int, int]: + """Determine fault-tolerant costs using SF decomposition in quantum chem Args: n (int) - the number of spin-orbitals @@ -44,11 +40,30 @@ def compute_cost(n: int, oh = [0] * 20 for p in range(20): # JJG note: arccos arg may be > 1 - v = np.round(np.power(2,p+1) / (2 * np.pi) * arccos(np.power(2,nL) /\ - np.sqrt((L + 1)/2**eta)/2)) - oh[p] = np.real(stps * (1 / (np.sin(3 * arcsin(np.cos(v * 2 * np.pi / \ - np.power(2,p+1)) * \ - np.sqrt((L + 1)/2**eta) / np.power(2,nL)))**2) - 1) + 4 * (p + 1)) + v = np.round( + np.power(2, p + 1) + / (2 * np.pi) + * arccos(np.power(2, nL) / np.sqrt((L + 1) / 2**eta) / 2) + ) + oh[p] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt((L + 1) / 2**eta) + / np.power(2, nL) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br = int(np.argmin(oh) + 1) @@ -72,11 +87,27 @@ def compute_cost(n: int, nprime = int(n**2 // 8 + n // 4) for p in range(20): v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, 2 * nN) / np.sqrt(nprime) / 2)) - oh[p] = np.real(20000 * (1 / (np.sin(3 * arcsin(np.cos(v * 2 * np.pi / \ - np.power(2, p+1)) * \ - np.sqrt(nprime) / np.power(2,2*nN)))**2) - 1) + 4 * (p + 1)) + np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, 2 * nN) / np.sqrt(nprime) / 2) + ) + oh[p] = np.real( + 20000 + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt(nprime) + / np.power(2, 2 * nN) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation for preparing the next equal # superposition states @@ -98,8 +129,7 @@ def compute_cost(n: int, bp = int(2 * nN + chi + 2) # Cost of QROMs for state preparation on p and q in step 2 (c). - cost2c = QR2(L + 1, nprime, bp)[-1] + QI(n1)[-1] + QR2(L, nprime, - bp)[-1] + QI(n2)[-1] + cost2c = QR2(L + 1, nprime, bp)[-1] + QI(n1)[-1] + QR2(L, nprime, bp)[-1] + QI(n2)[-1] # The cost of the inequality test and controlled swap for the quantum alias # sampling in steps 2 (d) and (e). @@ -130,8 +160,21 @@ def compute_cost(n: int, iters = np.ceil(np.pi * lam / (dE * 2)) # The total Toffoli costs for a step. - cost = cost1a + cost1b + cost1cd + cost2a + cost2b + cost2c + cost2de + \ - cost3 + cost4 + cost6 + cost7 + cost9 + cost10 + cost = ( + cost1a + + cost1b + + cost1cd + + cost2a + + cost2b + + cost2c + + cost2de + + cost3 + + cost4 + + cost6 + + cost7 + + cost9 + + cost10 + ) # Control for phase estimation and its iteration ac1 = 2 * np.ceil(np.log2(iters)) - 1 @@ -160,8 +203,7 @@ def compute_cost(n: int, ac8 = br # The QROM on the p & q registers - ac9 = kp[0] * kp[1] * bp + np.ceil(np.log2( - (L + 1) / kp[0])) + np.ceil(np.log2(nprime / kp[1])) + ac9 = kp[0] * kp[1] * bp + np.ceil(np.log2((L + 1) / kp[0])) + np.ceil(np.log2(nprime / kp[1])) if verbose: print("[*] Top of routine") diff --git a/src/openfermion/resource_estimates/sf/compute_cost_sf_test.py b/src/openfermion/resource_estimates/sf/compute_cost_sf_test.py index 340bcde0d..1c44a37df 100644 --- a/src/openfermion/resource_estimates/sf/compute_cost_sf_test.py +++ b/src/openfermion/resource_estimates/sf/compute_cost_sf_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for costing_sf.py """ import pytest @@ -9,10 +9,9 @@ from openfermion.resource_estimates import sf -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_reiher_sf(): - """ Reproduce Reiher et al orbital SF FT costs from paper """ + """Reproduce Reiher et al orbital SF FT costs from paper""" DE = 0.001 CHI = 10 @@ -30,10 +29,9 @@ def test_reiher_sf(): assert output == (14184, 94868988984, 3320) -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_li_sf(): - """ Reproduce Li et al orbital SF FT costs from paper """ + """Reproduce Li et al orbital SF FT costs from paper""" DE = 0.001 CHI = 10 diff --git a/src/openfermion/resource_estimates/sf/compute_lambda_sf.py b/src/openfermion/resource_estimates/sf/compute_lambda_sf.py index d0a46dc4c..34a621011 100644 --- a/src/openfermion/resource_estimates/sf/compute_lambda_sf.py +++ b/src/openfermion/resource_estimates/sf/compute_lambda_sf.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Compute lambda for single low rank factorization method of Berry, et al """ import numpy as np @@ -6,7 +6,7 @@ def compute_lambda(pyscf_mf, sf_factors): - """ Compute lambda for Hamiltonian using SF method of Berry, et al. + """Compute lambda for Hamiltonian using SF method of Berry, et al. Args: pyscf_mf - PySCF mean field object @@ -21,14 +21,16 @@ def compute_lambda(pyscf_mf, sf_factors): h1, eri_full, _, _, _ = pyscf_to_cas(pyscf_mf) # Effective one electron operator contribution - T = h1 - 0.5 * np.einsum("pqqs->ps", eri_full, optimize=True) +\ - np.einsum("pqrr->pq", eri_full, optimize = True) + T = ( + h1 + - 0.5 * np.einsum("pqqs->ps", eri_full, optimize=True) + + np.einsum("pqrr->pq", eri_full, optimize=True) + ) lambda_T = np.sum(np.abs(T)) # Two electron operator contributions - lambda_W = 0.25 * np.einsum( - "ijP,klP->", np.abs(sf_factors), np.abs(sf_factors), optimize=True) + lambda_W = 0.25 * np.einsum("ijP,klP->", np.abs(sf_factors), np.abs(sf_factors), optimize=True) lambda_tot = lambda_T + lambda_W return lambda_tot diff --git a/src/openfermion/resource_estimates/sf/compute_lambda_sf_test.py b/src/openfermion/resource_estimates/sf/compute_lambda_sf_test.py index 612ba8b5b..61e5348ef 100644 --- a/src/openfermion/resource_estimates/sf/compute_lambda_sf_test.py +++ b/src/openfermion/resource_estimates/sf/compute_lambda_sf_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for compute_lambda_sf.py """ from os import path @@ -12,10 +12,9 @@ from openfermion.resource_estimates.molecule import load_casfile_to_pyscf -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_reiher_sf_lambda(): - """ Reproduce Reiher et al orbital SF lambda from paper """ + """Reproduce Reiher et al orbital SF lambda from paper""" RANK = 200 diff --git a/src/openfermion/resource_estimates/sf/factorize_sf.py b/src/openfermion/resource_estimates/sf/factorize_sf.py index c4f7391b5..41b3cc367 100644 --- a/src/openfermion/resource_estimates/sf/factorize_sf.py +++ b/src/openfermion/resource_estimates/sf/factorize_sf.py @@ -1,11 +1,11 @@ -#coverage:ignore +# coverage:ignore """ Single factorization of the ERI tensor """ import numpy as np from openfermion.resource_estimates.utils import eigendecomp def factorize(eri_full, rank): - """ Do single factorization of the ERI tensor + """Do single factorization of the ERI tensor Args: eri_full (np.ndarray) - 4D (N x N x N x N) full ERI tensor @@ -33,7 +33,7 @@ def factorize(eri_full, rank): assert LR.shape[2] == rank except AssertionError: sys.exit( - "LR.shape: %s\nrank: %s\nLR.shape and rank are inconsistent" - % (LR.shape, rank)) + "LR.shape: %s\nrank: %s\nLR.shape and rank are inconsistent" % (LR.shape, rank) + ) return eri_rr, LR diff --git a/src/openfermion/resource_estimates/sf/generate_costing_table_sf.py b/src/openfermion/resource_estimates/sf/generate_costing_table_sf.py index 003feafb5..020b02b25 100644 --- a/src/openfermion/resource_estimates/sf/generate_costing_table_sf.py +++ b/src/openfermion/resource_estimates/sf/generate_costing_table_sf.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Pretty-print a table comparing number of SF vectors versus acc and cost """ import numpy as np import pytest @@ -8,21 +8,24 @@ if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf import scf - from openfermion.resource_estimates.molecule import (cas_to_pyscf, - factorized_ccsd_t, - pyscf_to_cas) - - -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') -def generate_costing_table(pyscf_mf, - name='molecule', - rank_range=range(50, 401, 25), - chi=10, - dE=0.001, - use_kernel=True, - no_triples=False): - """ Print a table to file for how various SF ranks impact cost, acc., etc. + from openfermion.resource_estimates.molecule import ( + cas_to_pyscf, + factorized_ccsd_t, + pyscf_to_cas, + ) + + +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') +def generate_costing_table( + pyscf_mf, + name='molecule', + rank_range=range(50, 401, 25), + chi=10, + dE=0.001, + use_kernel=True, + no_triples=False, +): + """Print a table to file for how various SF ranks impact cost, acc., etc. Args: pyscf_mf - PySCF mean field object @@ -63,72 +66,73 @@ def generate_costing_table(pyscf_mf, _, pyscf_mf = cas_to_pyscf(*pyscf_to_cas(pyscf_mf)) # Reference calculation (eri_rr = None is full rank / exact ERIs) - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr=None, - use_kernel=use_kernel, - no_triples=no_triples) + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr=None, use_kernel=use_kernel, no_triples=no_triples + ) exact_etot = etot filename = 'single_factorization_' + name + '.txt' with open(filename, 'w') as f: - print("\n Single low rank factorization data for '" + name + "'.", - file=f) + print("\n Single low rank factorization data for '" + name + "'.", file=f) print(" [*] using " + cas_info, file=f) print(" [+] E(SCF): %18.8f" % escf, file=f) if no_triples: - print(" [+] Active space CCSD E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD E(tot): %18.8f" % etot, file=f) else: - print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, file=f) print("{}".format('=' * 108), file=f) if no_triples: - print("{:^12} {:^18} {:^12} {:^24} {:^20} {:^20}".format('L',\ - '||ERI - SF||','lambda','CCSD error (mEh)',\ - 'logical qubits', 'Toffoli count'),file=f) + print( + "{:^12} {:^18} {:^12} {:^24} {:^20} {:^20}".format( + 'L', + '||ERI - SF||', + 'lambda', + 'CCSD error (mEh)', + 'logical qubits', + 'Toffoli count', + ), + file=f, + ) else: - print("{:^12} {:^18} {:^12} {:^24} {:^20} {:^20}".format('L',\ - '||ERI - SF||','lambda','CCSD(T) error (mEh)',\ - 'logical qubits', 'Toffoli count'),file=f) + print( + "{:^12} {:^18} {:^12} {:^24} {:^20} {:^20}".format( + 'L', + '||ERI - SF||', + 'lambda', + 'CCSD(T) error (mEh)', + 'logical qubits', + 'Toffoli count', + ), + file=f, + ) print("{}".format('-' * 108), file=f) for rank in rank_range: # First, up: lambda and CCSD(T) eri_rr, LR = sf.factorize(pyscf_mf._eri, rank) lam = sf.compute_lambda(pyscf_mf, LR) - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr, - use_kernel=use_kernel, - no_triples=no_triples) - error = (etot - exact_etot) * 1E3 # to mEh - l2_norm_error_eri = np.linalg.norm( - eri_rr - pyscf_mf._eri) # eri reconstruction error + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr, use_kernel=use_kernel, no_triples=no_triples + ) + error = (etot - exact_etot) * 1e3 # to mEh + l2_norm_error_eri = np.linalg.norm(eri_rr - pyscf_mf._eri) # eri reconstruction error # now do costing - stps1 = sf.compute_cost(num_spinorb, - lam, - DE, - L=rank, - chi=CHI, - stps=20000)[0] - - _, sf_total_cost, sf_logical_qubits = sf.compute_cost(num_spinorb, - lam, - DE, - L=rank, - chi=CHI, - stps=stps1) + stps1 = sf.compute_cost(num_spinorb, lam, DE, L=rank, chi=CHI, stps=20000)[0] + + _, sf_total_cost, sf_logical_qubits = sf.compute_cost( + num_spinorb, lam, DE, L=rank, chi=CHI, stps=stps1 + ) with open(filename, 'a') as f: print( "{:^12} {:^18.4e} {:^12.1f} {:^24.2f} {:^20} {:^20.1e}".format( - rank, l2_norm_error_eri, lam, error, sf_logical_qubits, - sf_total_cost), - file=f) + rank, l2_norm_error_eri, lam, error, sf_logical_qubits, sf_total_cost + ), + file=f, + ) with open(filename, 'a') as f: print("{}".format('=' * 108), file=f) diff --git a/src/openfermion/resource_estimates/sparse/costing_sparse.py b/src/openfermion/resource_estimates/sparse/costing_sparse.py index a33f2c272..b7389af4a 100644 --- a/src/openfermion/resource_estimates/sparse/costing_sparse.py +++ b/src/openfermion/resource_estimates/sparse/costing_sparse.py @@ -7,9 +7,8 @@ from openfermion.resource_estimates.utils import QI, power_two -def cost_sparse(n: int, lam: float, d: int, dE: float, chi: int, - stps: int) -> Tuple[int, int, int]: - """ Determine fault-tolerant costs using sparse decomposition in quantum +def cost_sparse(n: int, lam: float, d: int, dE: float, chi: int, stps: int) -> Tuple[int, int, int]: + """Determine fault-tolerant costs using sparse decomposition in quantum chemistry Args: @@ -48,11 +47,28 @@ def cost_sparse(n: int, lam: float, d: int, dE: float, chi: int, for p in range(2, 22): # JJG note: arccos arg may be > 1 - v = np.round(np.power(2,p+1) / (2 * np.pi) * arccos(np.power(2,nM) /\ - np.sqrt(d/2**eta)/2)) - oh[p-2] = np.real(stps * (1 / (np.sin(3 * arcsin(np.cos(v * 2 * np.pi /\ - np.power(2,p+1)) * \ - np.sqrt(d/2**eta) / np.power(2,nM)))**2) - 1) + 4 * (p + 1)) + v = np.round( + np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, nM) / np.sqrt(d / 2**eta) / 2) + ) + oh[p - 2] = np.real( + stps + * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) + * np.sqrt(d / 2**eta) + / np.power(2, nM) + ) + ) + ** 2 + ) + - 1 + ) + + 4 * (p + 1) + ) # Bits of precision for rotation br = int(np.argmin(oh) + 1) + 2 @@ -61,8 +77,18 @@ def cost_sparse(n: int, lam: float, d: int, dE: float, chi: int, k1 = 32 # Equation (A17) - cost = np.ceil(d/k1) + m * (k1 -1) + QI(d)[1] + 4 * n + 8 * nN + 2 * chi + \ - 7 * np.ceil(np.log2(d)) - 6 * eta + 4 * br - 19 + cost = ( + np.ceil(d / k1) + + m * (k1 - 1) + + QI(d)[1] + + 4 * n + + 8 * nN + + 2 * chi + + 7 * np.ceil(np.log2(d)) + - 6 * eta + + 4 * br + - 19 + ) # Number of iterations needed for the phase estimation. iters = np.ceil(np.pi * lam / (dE * 2)) diff --git a/src/openfermion/resource_estimates/sparse/costing_sparse_test.py b/src/openfermion/resource_estimates/sparse/costing_sparse_test.py index 0de9ad1b4..90b2de17b 100644 --- a/src/openfermion/resource_estimates/sparse/costing_sparse_test.py +++ b/src/openfermion/resource_estimates/sparse/costing_sparse_test.py @@ -4,7 +4,7 @@ def test_reiher_sparse(): - """ Reproduce Reiher et al orbital sparse FT costs from paper """ + """Reproduce Reiher et al orbital sparse FT costs from paper""" DE = 0.001 CHI = 10 @@ -23,7 +23,7 @@ def test_reiher_sparse(): def test_li_sparse(): - """ Reproduce Li et al orbital sparse FT costs from paper """ + """Reproduce Li et al orbital sparse FT costs from paper""" DE = 0.001 CHI = 10 diff --git a/src/openfermion/resource_estimates/surface_code_compilation/physical_costing.py b/src/openfermion/resource_estimates/surface_code_compilation/physical_costing.py index f74751209..297429b55 100644 --- a/src/openfermion/resource_estimates/surface_code_compilation/physical_costing.py +++ b/src/openfermion/resource_estimates/surface_code_compilation/physical_costing.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore import dataclasses import datetime import math @@ -41,61 +41,55 @@ def estimate_cost(self) -> CostEstimate: - The bottleneck time cost is waiting for magic states. """ logical_storage = int( - math.ceil(self.max_allocated_logical_qubits * - (1 + self.routing_overhead_proportion))) + math.ceil(self.max_allocated_logical_qubits * (1 + self.routing_overhead_proportion)) + ) storage_area = logical_storage * _physical_qubits_per_logical_qubit( - self.logical_data_qubit_distance) - distillation_area = self.factory_count \ - * self.magic_state_factory.physical_qubit_footprint - rounds = int(self.toffoli_count / self.factory_count * - self.magic_state_factory.rounds) - distillation_failure = self.magic_state_factory.failure_rate \ - * self.toffoli_count - data_failure = self.proportion_of_bounding_box \ - * _topological_error_per_unit_cell( - self.logical_data_qubit_distance, - gate_err=self.physical_error_rate) * logical_storage * rounds - - return CostEstimate(physical_qubit_count=storage_area + - distillation_area, - duration=rounds * self.surface_code_cycle_time, - algorithm_failure_probability=min( - 1., data_failure + distillation_failure)) - - -def _topological_error_per_unit_cell(code_distance: int, - gate_err: float) -> float: - return 0.1 * (100 * gate_err)**((code_distance + 1) / 2) - - -def _total_topological_error(code_distance: int, gate_err: float, - unit_cells: int) -> float: - return unit_cells * _topological_error_per_unit_cell( - code_distance, gate_err) - - -def iter_known_factories(physical_error_rate: float - ) -> Iterator[MagicStateFactory]: + self.logical_data_qubit_distance + ) + distillation_area = self.factory_count * self.magic_state_factory.physical_qubit_footprint + rounds = int(self.toffoli_count / self.factory_count * self.magic_state_factory.rounds) + distillation_failure = self.magic_state_factory.failure_rate * self.toffoli_count + data_failure = ( + self.proportion_of_bounding_box + * _topological_error_per_unit_cell( + self.logical_data_qubit_distance, gate_err=self.physical_error_rate + ) + * logical_storage + * rounds + ) + + return CostEstimate( + physical_qubit_count=storage_area + distillation_area, + duration=rounds * self.surface_code_cycle_time, + algorithm_failure_probability=min(1.0, data_failure + distillation_failure), + ) + + +def _topological_error_per_unit_cell(code_distance: int, gate_err: float) -> float: + return 0.1 * (100 * gate_err) ** ((code_distance + 1) / 2) + + +def _total_topological_error(code_distance: int, gate_err: float, unit_cells: int) -> float: + return unit_cells * _topological_error_per_unit_cell(code_distance, gate_err) + + +def iter_known_factories(physical_error_rate: float) -> Iterator[MagicStateFactory]: if physical_error_rate == 0.001: - yield _two_level_t_state_factory_1p1000( - physical_error_rate=physical_error_rate) + yield _two_level_t_state_factory_1p1000(physical_error_rate=physical_error_rate) yield from iter_auto_ccz_factories(physical_error_rate) -def _two_level_t_state_factory_1p1000(physical_error_rate: float - ) -> MagicStateFactory: +def _two_level_t_state_factory_1p1000(physical_error_rate: float) -> MagicStateFactory: assert physical_error_rate == 0.001 return MagicStateFactory( details="https://arxiv.org/abs/1808.06709", failure_rate=4 * 9 * 10**-17, - physical_qubit_footprint=(12 * 8) * (4) * - _physical_qubits_per_logical_qubit(31), + physical_qubit_footprint=(12 * 8) * (4) * _physical_qubits_per_logical_qubit(31), rounds=6 * 31, ) -def _autoccz_factory_dimensions(l1_distance: int, - l2_distance: int) -> Tuple[int, int, float]: +def _autoccz_factory_dimensions(l1_distance: int, l2_distance: int) -> Tuple[int, int, float]: """Determine the width, height, depth of the magic state factory.""" t1_height = 4 * l1_distance / l2_distance t1_width = 8 * l1_distance / l2_distance @@ -118,43 +112,43 @@ def _autoccz_factory_dimensions(l1_distance: int, return width, height, depth -def iter_auto_ccz_factories(physical_error_rate: float - ) -> Iterator[MagicStateFactory]: +def iter_auto_ccz_factories(physical_error_rate: float) -> Iterator[MagicStateFactory]: for l1_distance in range(5, 25, 2): for l2_distance in range(l1_distance + 2, 41, 2): - w, h, d = _autoccz_factory_dimensions(l1_distance=l1_distance, - l2_distance=l2_distance) + w, h, d = _autoccz_factory_dimensions(l1_distance=l1_distance, l2_distance=l2_distance) f = _compute_autoccz_distillation_error( l1_distance=l1_distance, l2_distance=l2_distance, - physical_error_rate=physical_error_rate) + physical_error_rate=physical_error_rate, + ) yield MagicStateFactory( - details= - f"AutoCCZ({physical_error_rate=},{l1_distance=},{l2_distance=}", - physical_qubit_footprint=w * h * - _physical_qubits_per_logical_qubit(l2_distance), + details=f"AutoCCZ({physical_error_rate=},{l1_distance=},{l2_distance=}", + physical_qubit_footprint=w * h * _physical_qubits_per_logical_qubit(l2_distance), rounds=d * l2_distance, failure_rate=f, ) -def _compute_autoccz_distillation_error(l1_distance: int, l2_distance: int, - physical_error_rate: float) -> float: +def _compute_autoccz_distillation_error( + l1_distance: int, l2_distance: int, physical_error_rate: float +) -> float: # Level 0 L0_distance = l1_distance // 2 L0_distillation_error = physical_error_rate L0_topological_error = _total_topological_error( unit_cells=100, # Estimated 100 for T injection. code_distance=L0_distance, - gate_err=physical_error_rate) + gate_err=physical_error_rate, + ) L0_total_T_error = L0_distillation_error + L0_topological_error # Level 1 L1_topological_error = _total_topological_error( unit_cells=1100, # Estimated 1000 for factory, 100 for T injection. code_distance=l1_distance, - gate_err=physical_error_rate) + gate_err=physical_error_rate, + ) L1_distillation_error = 35 * L0_total_T_error**3 L1_total_T_error = L1_distillation_error + L1_topological_error @@ -162,7 +156,8 @@ def _compute_autoccz_distillation_error(l1_distance: int, l2_distance: int, L2_topological_error = _total_topological_error( unit_cells=1000, # Estimated 1000 for factory. code_distance=l2_distance, - gate_err=physical_error_rate) + gate_err=physical_error_rate, + ) L2_distillation_error = 28 * L1_total_T_error**2 L2_total_CCZ_or_2T_error = L2_topological_error + L2_distillation_error @@ -170,13 +165,12 @@ def _compute_autoccz_distillation_error(l1_distance: int, l2_distance: int, def _physical_qubits_per_logical_qubit(code_distance: int) -> int: - return (code_distance + 1)**2 * 2 + return (code_distance + 1) ** 2 * 2 -def cost_estimator(num_logical_qubits, - num_toffoli, - physical_error_rate=1.0E-3, - portion_of_bounding_box=1.): +def cost_estimator( + num_logical_qubits, num_toffoli, physical_error_rate=1.0e-3, portion_of_bounding_box=1.0 +): """ Produce best cost in terms of physical qubits and real run time based on number of toffoli, number of logical qubits, and physical error rate. @@ -184,8 +178,7 @@ def cost_estimator(num_logical_qubits, """ best_cost = None best_params = None - for factory in iter_known_factories( - physical_error_rate=physical_error_rate): + for factory in iter_known_factories(physical_error_rate=physical_error_rate): for logical_data_qubit_distance in range(7, 35, 2): params = AlgorithmParameters( physical_error_rate=physical_error_rate, @@ -196,12 +189,16 @@ def cost_estimator(num_logical_qubits, max_allocated_logical_qubits=num_logical_qubits, factory_count=4, routing_overhead_proportion=0.5, - proportion_of_bounding_box=portion_of_bounding_box) + proportion_of_bounding_box=portion_of_bounding_box, + ) cost = params.estimate_cost() if cost.algorithm_failure_probability > 0.1: continue - if best_cost is None or cost.physical_qubit_count * cost.duration \ - < best_cost.physical_qubit_count * best_cost.duration: + if ( + best_cost is None + or cost.physical_qubit_count * cost.duration + < best_cost.physical_qubit_count * best_cost.duration + ): best_cost = cost best_params = params return best_cost, best_params diff --git a/src/openfermion/resource_estimates/thc/__init__.py b/src/openfermion/resource_estimates/thc/__init__.py index 2dadc8d94..8df0159fe 100644 --- a/src/openfermion/resource_estimates/thc/__init__.py +++ b/src/openfermion/resource_estimates/thc/__init__.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # Copyright 2020 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/openfermion/resource_estimates/thc/compute_cost_thc.py b/src/openfermion/resource_estimates/thc/compute_cost_thc.py index 23df1a929..54330238b 100644 --- a/src/openfermion/resource_estimates/thc/compute_cost_thc.py +++ b/src/openfermion/resource_estimates/thc/compute_cost_thc.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Determine costs for THC decomposition in QC """ from typing import Tuple import numpy as np @@ -6,15 +6,10 @@ from openfermion.resource_estimates.utils import QR, QI -def compute_cost(n: int, - lam: float, - dE: float, - chi: int, - beta: int, - M: int, - stps: int, - verbose: bool = False) -> Tuple[int, int, int]: - """ Determine fault-tolerant costs using THC decomposition in quantum chem +def compute_cost( + n: int, lam: float, dE: float, chi: int, beta: int, M: int, stps: int, verbose: bool = False +) -> Tuple[int, int, int]: + """Determine fault-tolerant costs using THC decomposition in quantum chem Args: n (int) - the number of spin-orbitals @@ -48,12 +43,20 @@ def compute_cost(n: int, oh = [0] * 20 for p in range(20): # arccos arg may be > 1 - v = np.round( - np.power(2, p + 1) / (2 * np.pi) * - arccos(np.power(2, nM) / np.sqrt(d) / 2)) - oh[p] = stps * (1 / (np.sin(3 * arcsin(np.cos(v * 2 * np.pi / \ - np.power(2,p+1)) * \ - np.sqrt(d) / np.power(2,nM)))**2) - 1) + 4 * (p + 1) + v = np.round(np.power(2, p + 1) / (2 * np.pi) * arccos(np.power(2, nM) / np.sqrt(d) / 2)) + oh[p] = stps * ( + 1 + / ( + np.sin( + 3 + * arcsin( + np.cos(v * 2 * np.pi / np.power(2, p + 1)) * np.sqrt(d) / np.power(2, nM) + ) + ) + ** 2 + ) + - 1 + ) + 4 * (p + 1) # Set it to be the number of bits that minimises the cost, usually 7. # Python is 0-index, so need to add the one back in vs mathematica nb diff --git a/src/openfermion/resource_estimates/thc/compute_cost_thc_test.py b/src/openfermion/resource_estimates/thc/compute_cost_thc_test.py index c2f4943ea..fcece3f71 100644 --- a/src/openfermion/resource_estimates/thc/compute_cost_thc_test.py +++ b/src/openfermion/resource_estimates/thc/compute_cost_thc_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Test cases for costing_thc.py """ import unittest @@ -9,9 +9,8 @@ class THCCostTest(unittest.TestCase): - def test_reiher_thc(self): - """ Reproduce Reiher et al orbital THC FT costs from paper """ + """Reproduce Reiher et al orbital THC FT costs from paper""" DE = 0.001 CHI = 10 @@ -30,7 +29,7 @@ def test_reiher_thc(self): assert output == (10912, 5250145120, 2142) def test_li_thc(self): - """ Reproduce Li et al orbital THC FT costs from paper """ + """Reproduce Li et al orbital THC FT costs from paper""" DE = 0.001 CHI = 10 diff --git a/src/openfermion/resource_estimates/thc/compute_lambda_thc.py b/src/openfermion/resource_estimates/thc/compute_lambda_thc.py index 16fa0ef8d..8bfaf14e5 100644 --- a/src/openfermion/resource_estimates/thc/compute_lambda_thc.py +++ b/src/openfermion/resource_estimates/thc/compute_lambda_thc.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Compute lambdas for THC according to PRX QUANTUM 2, 030305 (2021) Section II. D. @@ -7,10 +7,7 @@ from openfermion.resource_estimates.molecule import pyscf_to_cas -def compute_lambda(pyscf_mf, - etaPp: np.ndarray, - MPQ: np.ndarray, - use_eri_thc_for_t=False): +def compute_lambda(pyscf_mf, etaPp: np.ndarray, MPQ: np.ndarray, use_eri_thc_for_t=False): """ Compute lambda thc @@ -29,29 +26,20 @@ def compute_lambda(pyscf_mf, h1, eri_full, _, _, _ = pyscf_to_cas(pyscf_mf) # computing Least-squares THC residual - CprP = np.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + CprP = np.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) BprQ = np.tensordot(CprP, MPQ, axes=([2], [0])) Iapprox = np.tensordot(CprP, np.transpose(BprQ), axes=([2], [0])) deri = eri_full - Iapprox - res = 0.5 * np.sum((deri)**2) + res = 0.5 * np.sum((deri) ** 2) # NOTE: remove in future once we resolve why it was used in the first place. # NOTE: see T construction for details. - eri_thc = np.einsum("Pp,Pr,Qq,Qs,PQ->prqs", - etaPp, - etaPp, - etaPp, - etaPp, - MPQ, - optimize=True) + eri_thc = np.einsum("Pp,Pr,Qq,Qs,PQ->prqs", etaPp, etaPp, etaPp, etaPp, MPQ, optimize=True) # projecting into the THC basis requires each THC factor mu to be nrmlzd. # we roll the normalization constant into the central tensor zeta - SPQ = etaPp.dot( - etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric - cP = np.diag(np.diag( - SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) + SPQ = etaPp.dot(etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric + cP = np.diag(np.diag(SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) # no sqrts because we have two normalized THC vectors (index by mu and nu) # on each side. MPQ_normalized = cP.dot(MPQ).dot(cP) # get normalized zeta in Eq. 11 & 12 @@ -62,17 +50,18 @@ def compute_lambda(pyscf_mf, if use_eri_thc_for_t: # use eri_thc for second coulomb contraction. This was in the original # code which is different than what the paper says. - T = h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum( - "llij->ij", eri_thc) # Eq. 3 + Eq. 18 + T = ( + h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum("llij->ij", eri_thc) + ) # Eq. 3 + Eq. 18 else: - T = h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum( - "llij->ij", eri_full) # Eq. 3 + Eq. 18 - #e, v = np.linalg.eigh(T) + T = ( + h1 - 0.5 * np.einsum("illj->ij", eri_full) + np.einsum("llij->ij", eri_full) + ) # Eq. 3 + Eq. 18 + # e, v = np.linalg.eigh(T) e = np.linalg.eigvalsh(T) # only need eigenvalues - lambda_T = np.sum( - np.abs(e)) # Eq. 19. NOTE: sum over spin orbitals removes 1/2 factor + lambda_T = np.sum(np.abs(e)) # Eq. 19. NOTE: sum over spin orbitals removes 1/2 factor lambda_tot = lambda_z + lambda_T # Eq. 20 - #return nthc, np.sqrt(res), res, lambda_T, lambda_z, lambda_tot + # return nthc, np.sqrt(res), res, lambda_T, lambda_z, lambda_tot return lambda_tot, nthc, np.sqrt(res), res, lambda_T, lambda_z diff --git a/src/openfermion/resource_estimates/thc/compute_lambda_thc_test.py b/src/openfermion/resource_estimates/thc/compute_lambda_thc_test.py index 5c7427d9e..058541d2d 100644 --- a/src/openfermion/resource_estimates/thc/compute_lambda_thc_test.py +++ b/src/openfermion/resource_estimates/thc/compute_lambda_thc_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore import os import h5py @@ -6,15 +6,13 @@ import pytest import openfermion.resource_estimates.integrals as int_folder -from openfermion.resource_estimates import (HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - thc) +from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES, thc if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from openfermion.resource_estimates.molecule import load_casfile_to_pyscf -@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - reason='pyscf and/or jax not installed.') +@pytest.mark.skipif(not HAVE_DEPS_FOR_RESOURCE_ESTIMATES, reason='pyscf and/or jax not installed.') def test_lambda(): integral_path = int_folder.__file__.replace('__init__.py', '') thc_factor_file = os.path.join(integral_path, 'M_250_beta_16_eta_10.h5') @@ -25,7 +23,6 @@ def test_lambda(): _, mf = load_casfile_to_pyscf(eri_file, num_alpha=27, num_beta=27) - lambda_tot, nthc, _, _, _, _ = \ - thc.compute_lambda(mf, etaPp=etaPp, MPQ=MPQ) + lambda_tot, nthc, _, _, _, _ = thc.compute_lambda(mf, etaPp=etaPp, MPQ=MPQ) assert nthc == 250 assert np.isclose(np.round(lambda_tot), 294) diff --git a/src/openfermion/resource_estimates/thc/factorize_thc.py b/src/openfermion/resource_estimates/thc/factorize_thc.py index 3c2ac47a9..29838c7d4 100644 --- a/src/openfermion/resource_estimates/thc/factorize_thc.py +++ b/src/openfermion/resource_estimates/thc/factorize_thc.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ THC rank reduction of ERIs """ import time @@ -8,15 +8,17 @@ from openfermion.resource_estimates.thc.utils import lbfgsb_opt_thc_l2reg -def thc_via_cp3(eri_full, - nthc, - thc_save_file=None, - first_factor_thresh=1.0E-14, - conv_eps=1.0E-4, - perform_bfgs_opt=True, - bfgs_maxiter=5000, - random_start_thc=True, - verify=False): +def thc_via_cp3( + eri_full, + nthc, + thc_save_file=None, + first_factor_thresh=1.0e-14, + conv_eps=1.0e-4, + perform_bfgs_opt=True, + bfgs_maxiter=5000, + random_start_thc=True, + verify=False, +): """ THC-CP3 performs an SVD decomposition of the eri matrix followed by a CP decomposition via pybtas. The CP decomposition is assumes the tensor is @@ -45,8 +47,7 @@ def thc_via_cp3(eri_full, try: import pybtas except ImportError: - raise ImportError( - "pybtas could not be imported. Is it installed and in PYTHONPATH?") + raise ImportError("pybtas could not be imported. Is it installed and in PYTHONPATH?") info = locals() info.pop('eri_full', None) # data too big for info dict @@ -54,14 +55,10 @@ def thc_via_cp3(eri_full, norb = eri_full.shape[0] if verify: - assert np.allclose(eri_full, - eri_full.transpose(1, 0, 2, 3)) # (ij|kl) == (ji|kl) - assert np.allclose(eri_full, - eri_full.transpose(0, 1, 3, 2)) # (ij|kl) == (ij|lk) - assert np.allclose(eri_full, - eri_full.transpose(1, 0, 3, 2)) # (ij|kl) == (ji|lk) - assert np.allclose(eri_full, - eri_full.transpose(2, 3, 0, 1)) # (ij|kl) == (kl|ij) + assert np.allclose(eri_full, eri_full.transpose(1, 0, 2, 3)) # (ij|kl) == (ji|kl) + assert np.allclose(eri_full, eri_full.transpose(0, 1, 3, 2)) # (ij|kl) == (ij|lk) + assert np.allclose(eri_full, eri_full.transpose(1, 0, 3, 2)) # (ij|kl) == (ji|lk) + assert np.allclose(eri_full, eri_full.transpose(2, 3, 0, 1)) # (ij|kl) == (kl|ij) eri_mat = eri_full.transpose(0, 1, 3, 2).reshape((norb**2, norb**2)) if verify: @@ -78,28 +75,24 @@ def thc_via_cp3(eri_full, u_chol = u_chol @ np.diag(np.sqrt(sigma[non_zero_sv])) if verify: - test_eri_mat_mulliken = u[:, - non_zero_sv] @ diag_sigma @ vh[non_zero_sv, :] + test_eri_mat_mulliken = u[:, non_zero_sv] @ diag_sigma @ vh[non_zero_sv, :] assert np.allclose(test_eri_mat_mulliken, eri_mat) start_time = time.time() # timing results if requested by user - beta, gamma, scale = pybtas.cp3_from_cholesky(u_chol.copy(), - nthc, - random_start=random_start_thc, - conv_eps=conv_eps) + beta, gamma, scale = pybtas.cp3_from_cholesky( + u_chol.copy(), nthc, random_start=random_start_thc, conv_eps=conv_eps + ) cp3_calc_time = time.time() - start_time if verify: u_alpha = np.zeros((norb, norb, len(non_zero_sv))) for ii in range(len(non_zero_sv)): - u_alpha[:, :, ii] = np.sqrt(sigma[ii]) * u[:, ii].reshape( - (norb, norb)) + u_alpha[:, :, ii] = np.sqrt(sigma[ii]) * u[:, ii].reshape((norb, norb)) assert np.allclose( - u_alpha[:, :, ii], - u_alpha[:, :, ii].T) # consequence of working with Mulliken rep + u_alpha[:, :, ii], u_alpha[:, :, ii].T + ) # consequence of working with Mulliken rep - u_alpha_test = np.einsum("ar,br,xr,r->abx", beta, beta, gamma, - scale.ravel()) + u_alpha_test = np.einsum("ar,br,xr,r->abx", beta, beta, gamma, scale.ravel()) print("\tu_alpha l2-norm ", np.linalg.norm(u_alpha_test - u_alpha)) thc_leaf = beta.T @@ -107,38 +100,33 @@ def thc_via_cp3(eri_full, thc_central = thc_gamma.T @ thc_gamma if verify: - eri_thc = np.einsum("Pp,Pr,Qq,Qs,PQ->prqs", - thc_leaf, - thc_leaf, - thc_leaf, - thc_leaf, - thc_central, - optimize=True) + eri_thc = np.einsum( + "Pp,Pr,Qq,Qs,PQ->prqs", + thc_leaf, + thc_leaf, + thc_leaf, + thc_leaf, + thc_central, + optimize=True, + ) print("\tERI L2 CP3-THC ", np.linalg.norm(eri_thc - eri_full)) print("\tCP3 timing: ", cp3_calc_time) if perform_bfgs_opt: x = np.hstack((thc_leaf.ravel(), thc_central.ravel())) - #lbfgs_start_time = time.time() - x = lbfgsb_opt_thc_l2reg(eri_full, - nthc, - initial_guess=x, - maxiter=bfgs_maxiter) - #lbfgs_calc_time = time.time() - lbfgs_start_time - thc_leaf = x[:norb * nthc].reshape(nthc, - norb) # leaf tensor nthc x norb - thc_central = x[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - - #total_calc_time = time.time() - start_time - - eri_thc = np.einsum("Pp,Pr,Qq,Qs,PQ->prqs", - thc_leaf, - thc_leaf, - thc_leaf, - thc_leaf, - thc_central, - optimize=True) + # lbfgs_start_time = time.time() + x = lbfgsb_opt_thc_l2reg(eri_full, nthc, initial_guess=x, maxiter=bfgs_maxiter) + # lbfgs_calc_time = time.time() - lbfgs_start_time + thc_leaf = x[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + thc_central = x[norb * nthc : norb * nthc + nthc * nthc].reshape( + nthc, nthc + ) # central tensor + + # total_calc_time = time.time() - start_time + + eri_thc = np.einsum( + "Pp,Pr,Qq,Qs,PQ->prqs", thc_leaf, thc_leaf, thc_leaf, thc_leaf, thc_central, optimize=True + ) if thc_save_file is not None: with h5py.File(thc_save_file + '.h5', 'w') as fid: diff --git a/src/openfermion/resource_estimates/thc/generate_costing_table_thc.py b/src/openfermion/resource_estimates/thc/generate_costing_table_thc.py index ec8278940..abdf660bb 100644 --- a/src/openfermion/resource_estimates/thc/generate_costing_table_thc.py +++ b/src/openfermion/resource_estimates/thc/generate_costing_table_thc.py @@ -1,29 +1,32 @@ -#coverage:ignore +# coverage:ignore """ Pretty-print a table comparing number of THC vectors vs accy and cost """ import numpy as np -from openfermion.resource_estimates import (HAVE_DEPS_FOR_RESOURCE_ESTIMATES, - thc) +from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES, thc if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: from pyscf import scf - from openfermion.resource_estimates.molecule import (cas_to_pyscf, - factorized_ccsd_t, - pyscf_to_cas) - - -def generate_costing_table(pyscf_mf, - name='molecule', - nthc_range=None, - dE=0.001, - chi=10, - beta=20, - save_thc=False, - use_kernel=True, - no_triples=False, - **kwargs): - """ Print a table to file for testing how various THC thresholds impact + from openfermion.resource_estimates.molecule import ( + cas_to_pyscf, + factorized_ccsd_t, + pyscf_to_cas, + ) + + +def generate_costing_table( + pyscf_mf, + name='molecule', + nthc_range=None, + dE=0.001, + chi=10, + beta=20, + save_thc=False, + use_kernel=True, + no_triples=False, + **kwargs, +): + """Print a table to file for testing how various THC thresholds impact cost, accuracy, etc. Args: @@ -71,10 +74,9 @@ def generate_costing_table(pyscf_mf, _, pyscf_mf = cas_to_pyscf(*pyscf_to_cas(pyscf_mf)) # Reference calculation (eri_rr= None is full rank / exact ERIs) - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr=None, - use_kernel=use_kernel, - no_triples=no_triples) + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr=None, use_kernel=use_kernel, no_triples=no_triples + ) exact_etot = etot @@ -85,69 +87,66 @@ def generate_costing_table(pyscf_mf, print(" [*] using " + cas_info, file=f) print(" [+] E(SCF): %18.8f" % escf, file=f) if no_triples: - print(" [+] Active space CCSD E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD E(tot): %18.8f" % etot, file=f) else: - print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, - file=f) - print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, - file=f) + print(" [+] Active space CCSD(T) E(cor): %18.8f" % ecor, file=f) + print(" [+] Active space CCSD(T) E(tot): %18.8f" % etot, file=f) print("{}".format('=' * 111), file=f) if no_triples: - print("{:^12} {:^18} {:^24} {:^12} {:^20} {:^20}".format( - 'M', '||ERI - THC||', 'CCSD error (mEh)', 'lambda', - 'Toffoli count', 'logical qubits'), - file=f) + print( + "{:^12} {:^18} {:^24} {:^12} {:^20} {:^20}".format( + 'M', + '||ERI - THC||', + 'CCSD error (mEh)', + 'lambda', + 'Toffoli count', + 'logical qubits', + ), + file=f, + ) else: - print("{:^12} {:^18} {:^24} {:^12} {:^20} {:^20}".format( - 'M', '||ERI - THC||', 'CCSD(T) error (mEh)', 'lambda', - 'Toffoli count', 'logical qubits'), - file=f) + print( + "{:^12} {:^18} {:^24} {:^12} {:^20} {:^20}".format( + 'M', + '||ERI - THC||', + 'CCSD(T) error (mEh)', + 'lambda', + 'Toffoli count', + 'logical qubits', + ), + file=f, + ) print("{}".format('-' * 111), file=f) for nthc in nthc_range: # First, up: lambda and CCSD(T) if save_thc: - fname = name + '_nTHC_' + str(nthc).zfill( - 5) # will save as HDF5 and add .h5 extension + fname = name + '_nTHC_' + str(nthc).zfill(5) # will save as HDF5 and add .h5 extension else: fname = None - eri_rr, thc_leaf, thc_central, info = thc.factorize(pyscf_mf._eri, - nthc, - thc_save_file=fname, - **kwargs) + eri_rr, thc_leaf, thc_central, info = thc.factorize( + pyscf_mf._eri, nthc, thc_save_file=fname, **kwargs + ) lam = thc.compute_lambda(pyscf_mf, thc_leaf, thc_central)[0] - escf, ecor, etot = factorized_ccsd_t(pyscf_mf, - eri_rr, - use_kernel=use_kernel, - no_triples=no_triples) - error = (etot - exact_etot) * 1E3 # to mEh - l2_norm_error_eri = np.linalg.norm( - eri_rr - pyscf_mf._eri) # ERI reconstruction error + escf, ecor, etot = factorized_ccsd_t( + pyscf_mf, eri_rr, use_kernel=use_kernel, no_triples=no_triples + ) + error = (etot - exact_etot) * 1e3 # to mEh + l2_norm_error_eri = np.linalg.norm(eri_rr - pyscf_mf._eri) # ERI reconstruction error # now do costing - stps1 = thc.compute_cost(num_spinorb, - lam, - DE, - chi=CHI, - beta=BETA, - M=nthc, - stps=20000)[0] - _, thc_total_cost, thc_logical_qubits = thc.compute_cost(num_spinorb, - lam, - DE, - chi=CHI, - beta=BETA, - M=nthc, - stps=stps1) + stps1 = thc.compute_cost(num_spinorb, lam, DE, chi=CHI, beta=BETA, M=nthc, stps=20000)[0] + _, thc_total_cost, thc_logical_qubits = thc.compute_cost( + num_spinorb, lam, DE, chi=CHI, beta=BETA, M=nthc, stps=stps1 + ) with open(filename, 'a') as f: print( "{:^12} {:^18.4e} {:^24.2f} {:^12.1f} {:^20.1e} {:^20}".format( - nthc, l2_norm_error_eri, error, lam, thc_total_cost, - thc_logical_qubits), - file=f) + nthc, l2_norm_error_eri, error, lam, thc_total_cost, thc_logical_qubits + ), + file=f, + ) with open(filename, 'a') as f: print("{}".format('=' * 111), file=f) diff --git a/src/openfermion/resource_estimates/thc/spacetime.py b/src/openfermion/resource_estimates/thc/spacetime.py index c0e01dd76..55c3d9204 100644 --- a/src/openfermion/resource_estimates/thc/spacetime.py +++ b/src/openfermion/resource_estimates/thc/spacetime.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Compute qubit vs toffoli for THC LCU""" from math import pi import itertools @@ -8,15 +8,7 @@ from openfermion.resource_estimates.utils import QR, QI -def qubit_vs_toffoli(lam, - dE, - eps, - n, - chi, - beta, - M, - algorithm='half', - verbose=False): +def qubit_vs_toffoli(lam, dE, eps, n, chi, beta, M, algorithm='half', verbose=False): """ Args: lam (float) - the lambda-value for the Hamiltonian @@ -43,7 +35,7 @@ def qubit_vs_toffoli(lam, iters = np.ceil(pi * lam / (dE * 2)) # The number of bits used for each register. nM = np.ceil(np.log2(M + 1)) - #This is number of distinct items of data we need to output, see Eq. (28). + # This is number of distinct items of data we need to output, see Eq. (28). d = M * (M + 1) / 2 + n / 2 # The number of bits used for the contiguous register. nc = np.ceil(np.log2(d)) @@ -59,9 +51,8 @@ def qubit_vs_toffoli(lam, cos_term = arccos(np.power(2, nM) / np.sqrt(d) / 2) # print(cos_term) v = np.round(np.power(2, p) / (2 * pi) * cos_term) - asin_term = arcsin( - np.cos(v * 2 * pi / np.power(2, p)) * np.sqrt(d) / np.power(2, nM)) - sin_term = np.sin(3 * asin_term)**2 + asin_term = arcsin(np.cos(v * 2 * pi / np.power(2, p)) * np.sqrt(d) / np.power(2, nM)) + sin_term = np.sin(3 * asin_term) ** 2 oh[p - 1] = (20_000 * (1 / sin_term - 1) + 4 * p).real # br is the number of bits used in the rotation. br = np.argmin(oh) + 1 @@ -90,7 +81,7 @@ def qubit_vs_toffoli(lam, # This is the cost of swapping based on the spin register. These costs are # from the list on page 15, and this is steps 1 and 7. cs1 = 2 * n - k1 = 2**QI(M + n / 2)[0] + k1 = 2 ** QI(M + n / 2)[0] cs2a = M + n / 2 - 2 + np.ceil(M / k1) + np.ceil(n / 2 / k1) + k1 # The QROM for the rotation angles the first time. Here M+n/2-2 is the cost @@ -120,7 +111,7 @@ def qubit_vs_toffoli(lam, ac8 = beta ac9 = nc - kt = 2**QR(d, m)[0] + kt = 2 ** QR(d, m)[0] ac10 = m * kt + np.ceil(np.log2(d / kt)) # This is for the equal superposition state to perform the inequality test # with the keep register. @@ -182,7 +173,7 @@ def qubit_vs_toffoli(lam, # inequality tests. That should be 3*nM+nN-4. There are an other two # qubits in output at the end that will be kept until this step is undone. # Note: not used? - #nN = np.ceil(np.log2(n / 2)) + # nN = np.ceil(np.log2(n / 2)) # This is the maximum number of qubits used while preparing the equal # superposition state. @@ -268,8 +259,7 @@ def qubit_vs_toffoli(lam, tof9 = n * (beta - 2) / 2 # Make a list where we keep subtr the data qubits that can be erased. # Table[-j*beta,{j,0,n/4-1}]+perm+(beta-2) - qu10 = np.array([-j * beta for j in range(int(n / 4))]) \ - + perm + beta - 2 + qu10 = np.array([-j * beta for j in range(int(n / 4))]) + perm + beta - 2 # The cost of the rotations. # Table[2*(beta-2),{j,0,n/4-1}] tof10 = np.array([2 * (beta - 2) for j in range(int(n / 4))]) @@ -280,8 +270,7 @@ def qubit_vs_toffoli(lam, tof9 = n * (beta - 2) # Make a list where we keep subtr the data qubits that can be erased. # Table[-j*beta,{j,0,n/2-1}]+perm+(beta-2) - qu10 = np.array([-j * beta for j in range(int(n / 2))]) \ - + perm + beta - 2 + qu10 = np.array([-j * beta for j in range(int(n / 2))]) + perm + beta - 2 # The cost of the rotations. # Table[2*(beta-2),{j,0,n/2-1}] tof10 = np.array([2 * (beta - 2) for j in range(int(n / 2))]) @@ -289,7 +278,7 @@ def qubit_vs_toffoli(lam, perm = perm - beta * n / 2 # Find the k for the phase fixup for the erasure of the rotations. - k1 = 2**QI(M + n / 2)[0] + k1 = 2 ** QI(M + n / 2)[0] # Temp qubits used. Data qubits were already erased, so don't change perm. qu11 = perm + k1 + np.ceil(np.log2(M / k1)) @@ -334,8 +323,7 @@ def qubit_vs_toffoli(lam, if algorithm == 'half': # Make a list where we keep subtr the data qubits that can be erased. # Table[-j*beta,{j,0,n/4-1}]+perm+(beta-2) - qu17 = np.array([-j * beta for j in range(int(n / 4)) - ]) + perm + beta - 2 + qu17 = np.array([-j * beta for j in range(int(n / 4))]) + perm + beta - 2 # The cost of the rotations. # Table[2*(beta-2),{j,0,n/4-1}] tof17 = np.array([2 * (beta - 2) for j in range(int(n / 4))]) @@ -344,8 +332,7 @@ def qubit_vs_toffoli(lam, elif algorithm == 'full': # Make a list where we keep subtr the data qubits that can be erased. # Table[-j*beta,{j,0,n/2-1}]+perm+(beta-2) - qu17 = np.array([-j * beta for j in range(int(n / 2))]) \ - + perm + beta - 2 + qu17 = np.array([-j * beta for j in range(int(n / 2))]) + perm + beta - 2 # The cost of the rotations. # Table[2*(beta-2),{j,0,n/2-1}] tof17 = np.array([2 * (beta - 2) for j in range(int(n / 2))]) @@ -353,7 +340,7 @@ def qubit_vs_toffoli(lam, perm = perm - beta * n / 2 # Find the k for the phase fixup for the erasure of the rotations. - k1 = 2**QI(M)[0] + k1 = 2 ** QI(M)[0] # The temp qubits used. The data qubits were already erased, # so don't change perm. @@ -386,7 +373,7 @@ def qubit_vs_toffoli(lam, # then do the phase fixup. perm = perm - m - kt = 2**QI(d)[0] + kt = 2 ** QI(d)[0] # This is the number of qubits needed during the QROM. qu23 = perm + kt + np.ceil(np.log2(d / kt)) # The number of Toffolis for the QROM. @@ -436,75 +423,78 @@ def qubit_vs_toffoli(lam, rq: '#F59236', ri: '#E3D246', ro: '#36B83E', - iq: '#E83935' + iq: '#E83935', } if algorithm == 'half': - tgates = np.hstack((np.array([ - tof1, tof2, tof3, tof4, tof5, tof6, tof7, tof8, tof9, tof8, tof9, - tof9, tof8 - ]), tof10, - np.array([ - tof11, tof12, tof12a, tof13, tof14, tof15, - tof14, tof15, tof16, tof15, tof14 - ]), tof17, - np.array([ - tof18, tof19, tof20, tof21, tof22, tof23, tof24, - tof25, tof26, tof27 - ]))) + tgates = np.hstack( + ( + np.array( + [tof1, tof2, tof3, tof4, tof5, tof6, tof7, tof8, tof9, tof8, tof9, tof9, tof8] + ), + tof10, + np.array( + [tof11, tof12, tof12a, tof13, tof14, tof15, tof14, tof15, tof16, tof15, tof14] + ), + tof17, + np.array([tof18, tof19, tof20, tof21, tof22, tof23, tof24, tof25, tof26, tof27]), + ) + ) qubits = np.hstack( - (np.array([ - qu1, qu2, qu3, qu4, qu5, qu6, qu7, qu8, qu9, qu8, qu9, qu9, qu8 - ]), qu10, - np.array([ - qu11, qu12, qu12a, qu13, qu14, qu15, qu14, qu15, qu16, qu15, - qu14 - ]), qu17, - np.array( - [qu18, qu19, qu20, qu21, qu22, qu23, qu24, qu25, qu26, qu27]))) - labels = [sm, sm, pq, sm, sm, sm, sm, rq, ri, rq, ri, ro, rq] + \ - [ro] * len(qu10) + \ - [rq, sm, sm, sm, rq, ri, rq, ri, sm, ro, rq] + \ - [ro] * len(qu17) + \ - [rq, sm, sm, sm, sm, iq, sm, sm, sm, sm] + ( + np.array([qu1, qu2, qu3, qu4, qu5, qu6, qu7, qu8, qu9, qu8, qu9, qu9, qu8]), + qu10, + np.array([qu11, qu12, qu12a, qu13, qu14, qu15, qu14, qu15, qu16, qu15, qu14]), + qu17, + np.array([qu18, qu19, qu20, qu21, qu22, qu23, qu24, qu25, qu26, qu27]), + ) + ) + labels = ( + [sm, sm, pq, sm, sm, sm, sm, rq, ri, rq, ri, ro, rq] + + [ro] * len(qu10) + + [rq, sm, sm, sm, rq, ri, rq, ri, sm, ro, rq] + + [ro] * len(qu17) + + [rq, sm, sm, sm, sm, iq, sm, sm, sm, sm] + ) colors = [color_dict[i] for i in labels] elif algorithm == 'full': tgates = np.hstack( - (np.array([tof1, tof2, tof3, tof4, tof5, tof6, tof7, tof8, - tof9]), tof10, - np.array([tof11, tof12, tof12a, tof13, tof14, tof15, - tof16]), tof17, - np.array([ - tof18, tof19, tof20, tof21, tof22, tof23, tof24, tof25, tof26, - tof27 - ]))) + ( + np.array([tof1, tof2, tof3, tof4, tof5, tof6, tof7, tof8, tof9]), + tof10, + np.array([tof11, tof12, tof12a, tof13, tof14, tof15, tof16]), + tof17, + np.array([tof18, tof19, tof20, tof21, tof22, tof23, tof24, tof25, tof26, tof27]), + ) + ) qubits = np.hstack( - (np.array([qu1, qu2, qu3, qu4, qu5, qu6, qu7, qu8, qu9]), qu10, - np.array([qu11, qu12, qu12a, qu13, qu14, qu15, qu16]), qu17, - np.array( - [qu18, qu19, qu20, qu21, qu22, qu23, qu24, qu25, qu26, qu27]))) - labels = [sm, sm, pq, sm, sm, sm, sm, rq, ri] + \ - [ro] * len(qu10) + \ - [rq, sm, sm, sm, rq, ri, sm] + \ - [ro] * len(qu17) + \ - [rq, sm, sm, sm, sm, iq, sm, sm, sm, sm] + ( + np.array([qu1, qu2, qu3, qu4, qu5, qu6, qu7, qu8, qu9]), + qu10, + np.array([qu11, qu12, qu12a, qu13, qu14, qu15, qu16]), + qu17, + np.array([qu18, qu19, qu20, qu21, qu22, qu23, qu24, qu25, qu26, qu27]), + ) + ) + labels = ( + [sm, sm, pq, sm, sm, sm, sm, rq, ri] + + [ro] * len(qu10) + + [rq, sm, sm, sm, rq, ri, sm] + + [ro] * len(qu17) + + [rq, sm, sm, sm, sm, iq, sm, sm, sm, sm] + ) colors = [color_dict[i] for i in labels] # check lists are at least consistent - assert all( - len(element) == len(tgates) for element in [qubits, labels, colors]) + assert all(len(element) == len(tgates) for element in [qubits, labels, colors]) return tgates, qubits, labels, colors -def plot_qubit_vs_toffoli(tgates, - qubits, - labels, - colors, - tgate_label_thresh=100): - """ Helper function to plot qubit vs toffoli similar to Figs 11 and 12 from +def plot_qubit_vs_toffoli(tgates, qubits, labels, colors, tgate_label_thresh=100): + """Helper function to plot qubit vs toffoli similar to Figs 11 and 12 from 'Even more efficient quantum...' paper (arXiv:2011.03494) Args: @@ -516,19 +506,14 @@ def plot_qubit_vs_toffoli(tgates, """ # To align the bars on the right edge pass a negative width and align='edge' ax = plt.gca() - plt.bar(np.cumsum(tgates), - qubits, - width=-tgates, - align='edge', - color=colors) + plt.bar(np.cumsum(tgates), qubits, width=-tgates, align='edge', color=colors) plt.bar(0, qubits[-1], width=sum(tgates), align='edge', color='#D7C4F2') plt.xlabel('Toffoli count') plt.ylabel('Number of qubits') # Now add the labels # First, group labels and neighboring tgates - labels_grouped, tgates_grouped, qubits_grouped = group_steps( - labels, tgates, qubits) + labels_grouped, tgates_grouped, qubits_grouped = group_steps(labels, tgates, qubits) for step, label in enumerate(labels_grouped): if 'small' in label: # skip the steps identified as 'small' @@ -539,23 +524,23 @@ def plot_qubit_vs_toffoli(tgates, else: x = np.cumsum(tgates_grouped)[step] - (tgates_grouped[step] * 0.5) y = 0.5 * (qubits_grouped[step] - qubits[-1]) + qubits[-1] - ax.text(x, - y, - label, - rotation='vertical', - va='center', - ha='center', - fontsize='x-small') + ax.text(x, y, label, rotation='vertical', va='center', ha='center', fontsize='x-small') # Finally add system and control qubit label - ax.text(0.5*np.sum(tgates), 0.5*qubits[-1], "System and control qubits", \ - va='center', ha='center',fontsize='x-small') + ax.text( + 0.5 * np.sum(tgates), + 0.5 * qubits[-1], + "System and control qubits", + va='center', + ha='center', + fontsize='x-small', + ) plt.show() def table_qubit_vs_toffoli(tgates, qubits, labels, colors): - """ Helper function to generate qubit vs toffoli table .. text version of + """Helper function to generate qubit vs toffoli table .. text version of Fig 11 and Fig 12 in arXiv:2011.03494 Args: @@ -566,35 +551,34 @@ def table_qubit_vs_toffoli(tgates, qubits, labels, colors): """ print("=" * 60) - print("{:>8s}{:>11s}{:>9s}{:>20s}{:>12s}".format('STEP', 'TOFFOLI', - 'QUBIT*', 'LABEL', - 'COLOR')) + print("{:>8s}{:>11s}{:>9s}{:>20s}{:>12s}".format('STEP', 'TOFFOLI', 'QUBIT*', 'LABEL', 'COLOR')) print("-" * 60) for step in range(len(tgates)): - print('{:8d}{:11d}{:9d}{:>20s}{:>12s}'.format(step, int(tgates[step]), - int(qubits[step]), - labels[step], - colors[step])) + print( + '{:8d}{:11d}{:9d}{:>20s}{:>12s}'.format( + step, int(tgates[step]), int(qubits[step]), labels[step], colors[step] + ) + ) print("=" * 60) print(" *Includes {:d} system and control qubits".format(int(qubits[-1]))) def group_steps(labels, tgates, qubits): - """ Group similar adjacent steps by label. In addition to the grouped - labels, also returning the total Toffoli count and average qubits - allocated for that grouping. - Useful for collecting similar steps in the spacetime plots. - - Example: - Input: - labels = ['R', 'R', 'QROM', 'QROM, 'I-QROM', 'QROM', 'QROM', 'R'] - tgates = [ 5, 8, 20, 10, 14, 30, 10, 20] - qubits = [ 10, 10, 40, 20, 4, 80, 60, 60] - - Output: - grouped_labels = ['R', 'QROM', 'I-QROM', 'QROM', 'R'] - grouped_tgates = [ 13, 30, 14, 40, 20] (sum) - grouped_qubits = [ 10, 30, 4, 70, 60] (mean) + """Group similar adjacent steps by label. In addition to the grouped + labels, also returning the total Toffoli count and average qubits + allocated for that grouping. + Useful for collecting similar steps in the spacetime plots. + + Example: + Input: + labels = ['R', 'R', 'QROM', 'QROM, 'I-QROM', 'QROM', 'QROM', 'R'] + tgates = [ 5, 8, 20, 10, 14, 30, 10, 20] + qubits = [ 10, 10, 40, 20, 4, 80, 60, 60] + + Output: + grouped_labels = ['R', 'QROM', 'I-QROM', 'QROM', 'R'] + grouped_tgates = [ 13, 30, 14, 40, 20] (sum) + grouped_qubits = [ 10, 30, 4, 70, 60] (mean) """ assert len(labels) == len(tgates) diff --git a/src/openfermion/resource_estimates/thc/spacetime_test.py b/src/openfermion/resource_estimates/thc/spacetime_test.py index 7b5c9829a..2944c3758 100644 --- a/src/openfermion/resource_estimates/thc/spacetime_test.py +++ b/src/openfermion/resource_estimates/thc/spacetime_test.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """Tests for computing qubit vs toffoli for THC LCU""" import numpy as np from openfermion.resource_estimates.thc import qubit_vs_toffoli @@ -14,36 +14,286 @@ def test_qubit_vs_toffoli_original_strategy(): M = 350 # tof, qu array from costingTHCsteps.nb - ref_tof = np.asarray([95,89,2852,10,18,10,54,402,1512,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,\ - 42,54,1,54,348,1512,\ - 1,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,38,54,10,18,10,497,89,95,31,1]) - ref_qub = np.asarray([228,285,1182,251,241,241,241,1114,1119,1119,1103,\ - 1087,1071,1055,1039,\ - 1023,1007,991,975,959,943,927,911,895,879,863,847,\ - 831,815,799,783,767,\ - 751,735,719,703,687,671,655,639,623,607,591,575,559,\ - 543,527,511,495,479,\ - 463,447,431,415,399,383,367,351,335,319,303,287,271,\ - 262,241,241,241,1113,\ - 1119,1105,1119,1103,1087,1071,1055,1039,1023,1007,\ - 991,975,959,943,927,\ - 911,895,879,863,847,831,815,799,783,767,751,735,719,\ - 703,687,671,655,639,\ - 623,607,591,575,559,543,527,511,495,479,463,447,431,\ - 415,399,383,367,351,\ - 335,319,303,287,271,262,241,242,241,251,475,285,230,\ - 224,193]) + ref_tof = np.asarray( + [ + 95, + 89, + 2852, + 10, + 18, + 10, + 54, + 402, + 1512, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 42, + 54, + 1, + 54, + 348, + 1512, + 1, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 38, + 54, + 10, + 18, + 10, + 497, + 89, + 95, + 31, + 1, + ] + ) + ref_qub = np.asarray( + [ + 228, + 285, + 1182, + 251, + 241, + 241, + 241, + 1114, + 1119, + 1119, + 1103, + 1087, + 1071, + 1055, + 1039, + 1023, + 1007, + 991, + 975, + 959, + 943, + 927, + 911, + 895, + 879, + 863, + 847, + 831, + 815, + 799, + 783, + 767, + 751, + 735, + 719, + 703, + 687, + 671, + 655, + 639, + 623, + 607, + 591, + 575, + 559, + 543, + 527, + 511, + 495, + 479, + 463, + 447, + 431, + 415, + 399, + 383, + 367, + 351, + 335, + 319, + 303, + 287, + 271, + 262, + 241, + 241, + 241, + 1113, + 1119, + 1105, + 1119, + 1103, + 1087, + 1071, + 1055, + 1039, + 1023, + 1007, + 991, + 975, + 959, + 943, + 927, + 911, + 895, + 879, + 863, + 847, + 831, + 815, + 799, + 783, + 767, + 751, + 735, + 719, + 703, + 687, + 671, + 655, + 639, + 623, + 607, + 591, + 575, + 559, + 543, + 527, + 511, + 495, + 479, + 463, + 447, + 431, + 415, + 399, + 383, + 367, + 351, + 335, + 319, + 303, + 287, + 271, + 262, + 241, + 242, + 241, + 251, + 475, + 285, + 230, + 224, + 193, + ] + ) - tgates, qubits, _, _ = qubit_vs_toffoli(lam, dE, eps, n, chi, beta, M, \ - algorithm='full', verbose=False) + tgates, qubits, _, _ = qubit_vs_toffoli( + lam, dE, eps, n, chi, beta, M, algorithm='full', verbose=False + ) assert np.allclose(tgates.astype(int), ref_tof) assert np.allclose(qubits.astype(int), ref_qub) @@ -59,27 +309,194 @@ def test_qubit_vs_toffoli_improved_strategy(): M = 350 # tof, qu array from costingTHCsteps2.nb - ref2_tof = np.asarray([95,89,4293,10,18,10,54,402,756,402,756,756,402,28,\ - 28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,\ - 28,28,42,54,1,54,348,\ - 756,348,756,1,756,348,28,28,28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,\ - 28,28,28,28,28,28,28,28,28,28,28,38,54,10,18,10,497,\ - 89,95,50,1]) - ref2_qub = np.asarray([210,267,685,233,223,223,223,664,669,664,669,669,664,\ - 669,653,637,621,605,\ - 589,573,557,541,525,509,493,477,461,445,429,413,397,\ - 381,365,349,333,317,\ - 301,285,269,253,244,223,223,223,663,669,663,669,655,\ - 669,663,669,653,637,\ - 621,605,589,573,557,541,525,509,493,477,461,445,429,\ - 413,397,381,365,349,\ - 333,317,301,285,269,253,244,223,224,223,233,457,267,\ - 212,225,175]) + ref2_tof = np.asarray( + [ + 95, + 89, + 4293, + 10, + 18, + 10, + 54, + 402, + 756, + 402, + 756, + 756, + 402, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 42, + 54, + 1, + 54, + 348, + 756, + 348, + 756, + 1, + 756, + 348, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 28, + 38, + 54, + 10, + 18, + 10, + 497, + 89, + 95, + 50, + 1, + ] + ) + ref2_qub = np.asarray( + [ + 210, + 267, + 685, + 233, + 223, + 223, + 223, + 664, + 669, + 664, + 669, + 669, + 664, + 669, + 653, + 637, + 621, + 605, + 589, + 573, + 557, + 541, + 525, + 509, + 493, + 477, + 461, + 445, + 429, + 413, + 397, + 381, + 365, + 349, + 333, + 317, + 301, + 285, + 269, + 253, + 244, + 223, + 223, + 223, + 663, + 669, + 663, + 669, + 655, + 669, + 663, + 669, + 653, + 637, + 621, + 605, + 589, + 573, + 557, + 541, + 525, + 509, + 493, + 477, + 461, + 445, + 429, + 413, + 397, + 381, + 365, + 349, + 333, + 317, + 301, + 285, + 269, + 253, + 244, + 223, + 224, + 223, + 233, + 457, + 267, + 212, + 225, + 175, + ] + ) - tgates, qubits, _, _ = qubit_vs_toffoli(lam, dE, eps, n, chi, beta, M, \ - algorithm='half', verbose=False) + tgates, qubits, _, _ = qubit_vs_toffoli( + lam, dE, eps, n, chi, beta, M, algorithm='half', verbose=False + ) assert np.allclose(tgates.astype(int), ref2_tof) assert np.allclose(qubits.astype(int), ref2_qub) diff --git a/src/openfermion/resource_estimates/thc/utils/__init__.py b/src/openfermion/resource_estimates/thc/utils/__init__.py index f29620149..ea1478737 100644 --- a/src/openfermion/resource_estimates/thc/utils/__init__.py +++ b/src/openfermion/resource_estimates/thc/utils/__init__.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # Copyright 2020 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,12 +16,27 @@ from openfermion.resource_estimates import HAVE_DEPS_FOR_RESOURCE_ESTIMATES if HAVE_DEPS_FOR_RESOURCE_ESTIMATES: - from .adagrad import (adagrad, constant, exponential_decay, - inverse_time_decay, make_schedule, piecewise_constant, - polynomial_decay) - from .thc_factorization import (adagrad_opt_thc, lbfgsb_opt_cholesky, - lbfgsb_opt_thc, lbfgsb_opt_thc_l2reg) - from .thc_objectives import (cp_ls_cholesky_factor_objective, thc_objective, - thc_objective_and_grad, thc_objective_grad, - thc_objective_grad_jax, thc_objective_jax, - thc_objective_regularized) \ No newline at end of file + from .adagrad import ( + adagrad, + constant, + exponential_decay, + inverse_time_decay, + make_schedule, + piecewise_constant, + polynomial_decay, + ) + from .thc_factorization import ( + adagrad_opt_thc, + lbfgsb_opt_cholesky, + lbfgsb_opt_thc, + lbfgsb_opt_thc_l2reg, + ) + from .thc_objectives import ( + cp_ls_cholesky_factor_objective, + thc_objective, + thc_objective_and_grad, + thc_objective_grad, + thc_objective_grad_jax, + thc_objective_jax, + thc_objective_regularized, + ) diff --git a/src/openfermion/resource_estimates/thc/utils/adagrad.py b/src/openfermion/resource_estimates/thc/utils/adagrad.py index 9206f6709..c77b2f95f 100644 --- a/src/openfermion/resource_estimates/thc/utils/adagrad.py +++ b/src/openfermion/resource_estimates/thc/utils/adagrad.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore # Copyright 2018 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,15 +21,15 @@ def adagrad(step_size, momentum=0.9): """Construct optimizer triple for Adagrad. - Adaptive Subgradient Methods for Online Learning and Stochastic Optimization: - http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf - Args: - step_size: positive scalar, or a callable representing a step size schedule - that maps the iteration index to positive scalar. - momentum: optional, a positive scalar value for momentum - Returns: - An (init_fun, update_fun, get_params) triple. - """ + Adaptive Subgradient Methods for Online Learning and Stochastic Optimization: + http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf + Args: + step_size: positive scalar, or a callable representing a step size schedule + that maps the iteration index to positive scalar. + momentum: optional, a positive scalar value for momentum + Returns: + An (init_fun, update_fun, get_params) triple. + """ step_size = make_schedule(step_size) def init(x0): @@ -40,8 +40,8 @@ def init(x0): def update(i, g, state): x, g_sq, m = state g_sq += np.square(g) - g_sq_inv_sqrt = np.where(g_sq > 0, 1. / np.sqrt(g_sq), 0.0) - m = (1. - momentum) * (g * g_sq_inv_sqrt) + momentum * m + g_sq_inv_sqrt = np.where(g_sq > 0, 1.0 / np.sqrt(g_sq), 0.0) + m = (1.0 - momentum) * (g * g_sq_inv_sqrt) + momentum * m x = x - step_size(i) * m return x, g_sq, m @@ -56,7 +56,6 @@ def get_params(state): def constant(step_size) -> Schedule: - def schedule(i): return step_size @@ -64,9 +63,8 @@ def schedule(i): def exponential_decay(step_size, decay_steps, decay_rate): - def schedule(i): - return step_size * decay_rate**(i / decay_steps) + return step_size * decay_rate ** (i / decay_steps) return schedule @@ -76,6 +74,7 @@ def inverse_time_decay(step_size, decay_steps, decay_rate, staircase=False): def schedule(i): return step_size / (1 + decay_rate * np.floor(i / decay_steps)) + else: def schedule(i): @@ -85,10 +84,9 @@ def schedule(i): def polynomial_decay(step_size, decay_steps, final_step_size, power=1.0): - def schedule(step_num): step_num = np.minimum(step_num, decay_steps) - step_mult = (1 - step_num / decay_steps)**power + step_mult = (1 - step_num / decay_steps) ** power return step_mult * (step_size - final_step_size) + final_step_size return schedule @@ -100,8 +98,7 @@ def piecewise_constant(boundaries: Any, values: Any): if not boundaries.ndim == values.ndim == 1: raise ValueError("boundaries and values must be sequences") if not boundaries.shape[0] == values.shape[0] - 1: - raise ValueError( - "boundaries length must be one shorter than values length") + raise ValueError("boundaries length must be one shorter than values length") def schedule(i): return values[np.sum(i > boundaries)] diff --git a/src/openfermion/resource_estimates/thc/utils/thc_factorization.py b/src/openfermion/resource_estimates/thc/utils/thc_factorization.py index 817cb5bb6..922b63172 100644 --- a/src/openfermion/resource_estimates/thc/utils/thc_factorization.py +++ b/src/openfermion/resource_estimates/thc/utils/thc_factorization.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore import os from uuid import uuid4 import h5py @@ -11,10 +11,13 @@ from jax.config import config from jax import jit, grad from .adagrad import adagrad -from .thc_objectives import (thc_objective, thc_objective_grad, - thc_objective_and_grad, - cp_ls_cholesky_factor_objective, - thc_objective_regularized) +from .thc_objectives import ( + thc_objective, + thc_objective_grad, + thc_objective_and_grad, + cp_ls_cholesky_factor_objective, + thc_objective_regularized, +) # set mkl thread count for numpy einsum/tensordot calls # leave one CPU un used so we can still access this computer @@ -23,10 +26,9 @@ class CallBackStore: - def __init__(self, chkpoint_file, freqency=500): """Generic callback function for storing intermediates from BFGS and - Adagrad optimizations + Adagrad optimizations """ self.chkpoint_file = chkpoint_file self.freq = freqency @@ -39,13 +41,9 @@ def __call__(self, xk): f.close() -def lbfgsb_opt_thc(eri, - nthc, - chkfile_name=None, - initial_guess=None, - random_seed=None, - maxiter=150_000, - disp=False): +def lbfgsb_opt_thc( + eri, nthc, chkfile_name=None, initial_guess=None, random_seed=None, maxiter=150_000, disp=False +): """ Least-squares fit of two-electron integral tensors with L-BFGS-B """ @@ -68,35 +66,36 @@ def lbfgsb_opt_thc(eri, x = initial_guess # add more checks here for safety # L-BFGS-B optimization - res = minimize(thc_objective_and_grad, - x, - args=(norb, nthc, eri), - jac=True, - method='L-BFGS-B', - options={ - 'disp': disp, - 'maxiter': maxiter - }, - callback=callback_func) + res = minimize( + thc_objective_and_grad, + x, + args=(norb, nthc, eri), + jac=True, + method='L-BFGS-B', + options={'disp': disp, 'maxiter': maxiter}, + callback=callback_func, + ) # print(res) params = res.x x = numpy.array(params) f = h5py.File(chkfile_name, "w") - f["etaPp"] = x[:norb * nthc].reshape(nthc, norb) - f["ZPQ"] = x[norb * nthc:].reshape(nthc, nthc) + f["etaPp"] = x[: norb * nthc].reshape(nthc, norb) + f["ZPQ"] = x[norb * nthc :].reshape(nthc, nthc) f.close() return params -def lbfgsb_opt_thc_l2reg(eri, - nthc, - chkfile_name=None, - initial_guess=None, - random_seed=None, - maxiter=150_000, - disp_freq=98, - penalty_param=None, - disp=False): +def lbfgsb_opt_thc_l2reg( + eri, + nthc, + chkfile_name=None, + initial_guess=None, + random_seed=None, + maxiter=150_000, + disp_freq=98, + penalty_param=None, + disp=False, +): """ Least-squares fit of two-electron integral tensors with L-BFGS-B with l2-regularization of lambda @@ -105,12 +104,10 @@ def lbfgsb_opt_thc_l2reg(eri, disp_freq sets the freqnecy of printing """ if disp_freq > 98 or disp_freq < 1: - raise ValueError( - "disp_freq {} is not valid. must be between [1, 98]".format( - disp_freq)) + raise ValueError("disp_freq {} is not valid. must be between [1, 98]".format(disp_freq)) if chkfile_name is None: - #chkfile_name = str(uuid4()) + '.h5' + # chkfile_name = str(uuid4()) + '.h5' callback_func = None else: # callback func stores checkpoints @@ -128,29 +125,21 @@ def lbfgsb_opt_thc_l2reg(eri, x = initial_guess # add more checks here for safety # compute inital lambda to set penalty param - etaPp = x[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = x[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - SPQ = etaPp.dot( - etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric - cP = jnp.diag(jnp.diag( - SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) + etaPp = x[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = x[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor + SPQ = etaPp.dot(etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric + cP = jnp.diag(jnp.diag(SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) # no sqrts because we have two normalized THC vectors (index by mu and nu) # on each side. MPQ_normalized = cP.dot(MPQ).dot(cP) # get normalized zeta in Eq. 11 & 12 lambda_z = jnp.sum(jnp.abs(MPQ_normalized)) * 0.5 # lambda_z = jnp.sum(MPQ_normalized**2) * 0.5 - CprP = jnp.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=[(0, 1), (0, 1)]) + CprP = jnp.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=[(0, 1), (0, 1)]) deri = eri - Iapprox # set penalty if penalty_param is None: - sum_square_loss = 0.5 * numpy.sum((deri)**2) + sum_square_loss = 0.5 * numpy.sum((deri) ** 2) penalty_param = sum_square_loss / lambda_z print("lambda_z {}".format(lambda_z)) print("penalty_param {}".format(penalty_param)) @@ -160,38 +149,38 @@ def lbfgsb_opt_thc_l2reg(eri, print("Initial Grad") print(thc_grad(jnp.array(x), norb, nthc, jnp.array(eri), penalty_param)) print() - res = minimize(thc_objective_regularized, - jnp.array(x), - args=(norb, nthc, jnp.array(eri), penalty_param), - method='L-BFGS-B', - jac=thc_grad, - options={ - 'disp': None, - 'iprint': disp_freq, - 'maxiter': maxiter - }, - callback=callback_func) + res = minimize( + thc_objective_regularized, + jnp.array(x), + args=(norb, nthc, jnp.array(eri), penalty_param), + method='L-BFGS-B', + jac=thc_grad, + options={'disp': None, 'iprint': disp_freq, 'maxiter': maxiter}, + callback=callback_func, + ) # print(res) params = numpy.array(res.x) x = numpy.array(params) if chkfile_name is not None: f = h5py.File(chkfile_name, "w") - f["etaPp"] = x[:norb * nthc].reshape(nthc, norb) - f["ZPQ"] = x[norb * nthc:].reshape(nthc, nthc) + f["etaPp"] = x[: norb * nthc].reshape(nthc, norb) + f["ZPQ"] = x[norb * nthc :].reshape(nthc, nthc) f.close() return params -def adagrad_opt_thc(eri, - nthc, - chkfile_name=None, - initial_guess=None, - random_seed=None, - stepsize=0.01, - momentum=0.9, - maxiter=50_000, - gtol=1.0E-5): +def adagrad_opt_thc( + eri, + nthc, + chkfile_name=None, + initial_guess=None, + random_seed=None, + stepsize=0.01, + momentum=0.9, + maxiter=50_000, + gtol=1.0e-5, +): """ THC opt usually starts with BFGS and then is completed with Adagrad or other first order solver. This function implements an Adagrad optimization. @@ -216,8 +205,7 @@ def adagrad_opt_thc(eri, x = numpy.random.randn(norb * nthc + nthc * nthc) else: x = initial_guess # add more checks here for safety - opt_init, opt_update, get_params = adagrad(step_size=stepsize, - momentum=momentum) + opt_init, opt_update, get_params = adagrad(step_size=stepsize, momentum=momentum) opt_state = opt_init(x) def update(i, opt_state): @@ -244,17 +232,15 @@ def update(i, opt_state): # save results before returning x = numpy.array(params) f = h5py.File(chkfile_name, "w") - f["etaPp"] = x[:norb * nthc].reshape(nthc, norb) - f["ZPQ"] = x[norb * nthc:].reshape(nthc, nthc) + f["etaPp"] = x[: norb * nthc].reshape(nthc, norb) + f["ZPQ"] = x[norb * nthc :].reshape(nthc, nthc) f.close() return params -def lbfgsb_opt_cholesky(cholesky_factor, - nthc, - chkfile_name=None, - initial_guess=None, - random_seed=None): +def lbfgsb_opt_cholesky( + cholesky_factor, nthc, chkfile_name=None, initial_guess=None, random_seed=None +): """ Least-squares fit of cholesky tensors with L-BFGS-B @@ -281,21 +267,20 @@ def lbfgsb_opt_cholesky(cholesky_factor, x = initial_guess # add more checks here for safety # L-BFGS-B optimization - res = minimize(cp_ls_cholesky_factor_objective, - x, - args=(norb, nthc, cholesky_factor, True), - jac=True, - method='L-BFGS-B', - options={ - 'disp': True, - 'ftol': 1.0E-4 - }, - callback=callback_func) + res = minimize( + cp_ls_cholesky_factor_objective, + x, + args=(norb, nthc, cholesky_factor, True), + jac=True, + method='L-BFGS-B', + options={'disp': True, 'ftol': 1.0e-4}, + callback=callback_func, + ) print(res) params = res.x x = numpy.array(params) f = h5py.File(chkfile_name, "w") - f["etaPp"] = x[:norb * nthc].reshape(nthc, norb) - f["gamma"] = x[norb * nthc:].reshape(nthc, cholesky_factor.shape[-1]) + f["etaPp"] = x[: norb * nthc].reshape(nthc, norb) + f["gamma"] = x[norb * nthc :].reshape(nthc, cholesky_factor.shape[-1]) f.close() return params diff --git a/src/openfermion/resource_estimates/thc/utils/thc_objectives.py b/src/openfermion/resource_estimates/thc/utils/thc_objectives.py index ecb0af321..3cbdd96a0 100644 --- a/src/openfermion/resource_estimates/thc/utils/thc_objectives.py +++ b/src/openfermion/resource_estimates/thc/utils/thc_objectives.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore import os from uuid import uuid4 import scipy.optimize @@ -32,19 +32,13 @@ def thc_objective_jax(xcur, norb, nthc, eri): :param eri: two-electron repulsion integrals in chemist notation :return: """ - etaPp = xcur[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = xcur[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - - CprP = jnp.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=[(0, 1), (0, 1)]) + etaPp = xcur[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = xcur[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor + + CprP = jnp.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=[(0, 1), (0, 1)]) deri = eri - Iapprox - res = 0.5 * jnp.sum((deri)**2) + res = 0.5 * jnp.sum((deri) ** 2) return res @@ -58,38 +52,25 @@ def thc_objective_grad_jax(xcur, norb, nthc, eri): :param eri: two-electron repulsion integrals in chemist notation :param verbose: optional (False) for print iteration residual and inf norm """ - etaPp = xcur[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = xcur[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor + etaPp = xcur[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = xcur[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor # m indexes the nthc and p,q,r,s are orbital indices - CprP = jnp.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=[(0, 1), (0, 1)]) + CprP = jnp.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=[(0, 1), (0, 1)]) deri = eri - Iapprox # O(norb^5) - dL_dZab = -jnp.einsum( - 'pqrs,pqA,rsB->AB', deri, CprP, CprP, optimize=[(0, 1), (0, 1)]) + dL_dZab = -jnp.einsum('pqrs,pqA,rsB->AB', deri, CprP, CprP, optimize=[(0, 1), (0, 1)]) # O(norb^5) - dL_dX_GT = -2 * jnp.einsum('Tqrs,Gq,Gv,rsv->GT', - deri, - etaPp, - MPQ, - CprP, - optimize=[(0, 3), (1, 2), (0, 1)]) - - dL_dX_GT -= 2 * jnp.einsum('pqTs,pqu,uG,Gs->GT', - deri, - CprP, - MPQ, - etaPp, - optimize=[(0, 1), (0, 2), (0, 1)]) + dL_dX_GT = -2 * jnp.einsum( + 'Tqrs,Gq,Gv,rsv->GT', deri, etaPp, MPQ, CprP, optimize=[(0, 3), (1, 2), (0, 1)] + ) + + dL_dX_GT -= 2 * jnp.einsum( + 'pqTs,pqu,uG,Gs->GT', deri, CprP, MPQ, etaPp, optimize=[(0, 1), (0, 2), (0, 1)] + ) return jnp.hstack((dL_dX_GT.ravel(), dL_dZab.ravel())) @@ -109,33 +90,23 @@ def thc_objective(xcur, norb, nthc, eri, verbose=False): :param verbose: optional (False) for print iteration residual and inf norm :return: """ - etaPp = xcur[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = xcur[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - - CprP = numpy.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = numpy.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=['einsum_path', (0, 1), (0, 1)]) + etaPp = xcur[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = xcur[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor + + CprP = numpy.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = numpy.einsum( + 'pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=['einsum_path', (0, 1), (0, 1)] + ) deri = eri - Iapprox - res = 0.5 * numpy.sum((deri)**2) + res = 0.5 * numpy.sum((deri) ** 2) if verbose: - print("res, max, lambda = {}, {}".format(res, - numpy.max(numpy.abs(deri)))) + print("res, max, lambda = {}, {}".format(res, numpy.max(numpy.abs(deri)))) return res -def thc_objective_regularized(xcur, - norb, - nthc, - eri, - penalty_param, - verbose=False): +def thc_objective_regularized(xcur, norb, nthc, eri, penalty_param, verbose=False): """ Loss function for THC factorization @@ -150,30 +121,22 @@ def thc_objective_regularized(xcur, :param verbose: optional (False) for print iteration residual and inf norm :return: """ - etaPp = xcur[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = xcur[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - - CprP = jnp.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=[(0, 1), (0, 1)]) + etaPp = xcur[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = xcur[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor + + CprP = jnp.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = jnp.einsum('pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=[(0, 1), (0, 1)]) deri = eri - Iapprox - SPQ = etaPp.dot( - etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric - cP = jnp.diag(jnp.diag( - SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) + SPQ = etaPp.dot(etaPp.T) # (nthc x norb) x (norb x nthc) -> (nthc x nthc) metric + cP = jnp.diag(jnp.diag(SPQ)) # grab diagonal elements. equivalent to np.diag(np.diagonal(SPQ)) # no sqrts because we have two normalized THC vectors (index by mu and nu) # on each side. MPQ_normalized = cP.dot(MPQ).dot(cP) # get normalized zeta in Eq. 11 & 12 lambda_z = jnp.sum(jnp.abs(MPQ_normalized)) * 0.5 - res = 0.5 * jnp.sum((deri)**2) + penalty_param * (lambda_z**2) + res = 0.5 * jnp.sum((deri) ** 2) + penalty_param * (lambda_z**2) if verbose: print("res, max, lambda**2 = {}, {}".format(res, lambda_z**2)) @@ -191,32 +154,26 @@ def thc_objective_grad(xcur, norb, nthc, eri, verbose=False): :param eri: two-electron repulsion integrals in chemist notation :param verbose: optional (False) for print iteration residual and inf norm """ - etaPp = numpy.array(xcur[:norb * nthc]).reshape( - nthc, norb) # leaf tensor nthc x norb - MPQ = numpy.array(xcur[norb * nthc:norb * nthc + nthc * nthc]).reshape( - nthc, nthc) # central tensor + etaPp = numpy.array(xcur[: norb * nthc]).reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = numpy.array(xcur[norb * nthc : norb * nthc + nthc * nthc]).reshape( + nthc, nthc + ) # central tensor # m indexes the nthc and p,q,r,s are orbital indices - CprP = numpy.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - Iapprox = numpy.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=['einsum_path', (0, 1), (0, 1)]) + CprP = numpy.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + Iapprox = numpy.einsum( + 'pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=['einsum_path', (0, 1), (0, 1)] + ) deri = eri - Iapprox - res = 0.5 * numpy.sum((deri)**2) + res = 0.5 * numpy.sum((deri) ** 2) if verbose: - print("res, max, lambda = {}, {}".format(res, - numpy.max(numpy.abs(deri)))) + print("res, max, lambda = {}, {}".format(res, numpy.max(numpy.abs(deri)))) # O(norb^5) - dL_dZab = -numpy.einsum('pqrs,pqA,rsB->AB', - deri, - CprP, - CprP, - optimize=['einsum_path', (0, 1), (0, 1)]) + dL_dZab = -numpy.einsum( + 'pqrs,pqA,rsB->AB', deri, CprP, CprP, optimize=['einsum_path', (0, 1), (0, 1)] + ) # O(norb^5) dL_dX_GT = -2 * numpy.einsum( 'Tqrs,Gq,Gv,rsv->GT', @@ -224,7 +181,8 @@ def thc_objective_grad(xcur, norb, nthc, eri, verbose=False): etaPp, MPQ, CprP, - optimize=['einsum_path', (0, 3), (1, 2), (0, 1)]) + optimize=['einsum_path', (0, 3), (1, 2), (0, 1)], + ) dL_dX_GT -= 2 * numpy.einsum( 'pqTs,pqu,uG,Gs->GT', @@ -232,7 +190,8 @@ def thc_objective_grad(xcur, norb, nthc, eri, verbose=False): CprP, MPQ, etaPp, - optimize=['einsum_path', (0, 1), (0, 2), (0, 1)]) + optimize=['einsum_path', (0, 1), (0, 2), (0, 1)], + ) return numpy.hstack((dL_dX_GT.ravel(), dL_dZab.ravel())) @@ -252,25 +211,19 @@ def thc_objective_and_grad(xcur, norb, nthc, eri, verbose=False): :param verbose: optional (False) for print iteration residual and inf norm :return: """ - etaPp = xcur[:norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb - MPQ = xcur[norb * nthc:norb * nthc + nthc * nthc].reshape( - nthc, nthc) # central tensor - CprP = numpy.einsum("Pp,Pr->prP", etaPp, - etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) - - Iapprox = numpy.einsum('pqU,UV,rsV->pqrs', - CprP, - MPQ, - CprP, - optimize=['einsum_path', (0, 1), (0, 1)]) + etaPp = xcur[: norb * nthc].reshape(nthc, norb) # leaf tensor nthc x norb + MPQ = xcur[norb * nthc : norb * nthc + nthc * nthc].reshape(nthc, nthc) # central tensor + CprP = numpy.einsum("Pp,Pr->prP", etaPp, etaPp) # this is einsum('mp,mq->pqm', etaPp, etaPp) + + Iapprox = numpy.einsum( + 'pqU,UV,rsV->pqrs', CprP, MPQ, CprP, optimize=['einsum_path', (0, 1), (0, 1)] + ) deri = eri - Iapprox - res = 0.5 * numpy.sum((deri)**2) + res = 0.5 * numpy.sum((deri) ** 2) # O(norb^5) - dL_dZab = -numpy.einsum('pqrs,pqA,rsB->AB', - deri, - CprP, - CprP, - optimize=['einsum_path', (0, 1), (0, 1)]) + dL_dZab = -numpy.einsum( + 'pqrs,pqA,rsB->AB', deri, CprP, CprP, optimize=['einsum_path', (0, 1), (0, 1)] + ) # O(norb^4 * nthc) dL_dX_GT = -2 * numpy.einsum( 'Tqrs,Gq,Gv,rsv->GT', @@ -278,7 +231,8 @@ def thc_objective_and_grad(xcur, norb, nthc, eri, verbose=False): etaPp, MPQ, CprP, - optimize=['einsum_path', (0, 3), (1, 2), (0, 1)]) + optimize=['einsum_path', (0, 3), (1, 2), (0, 1)], + ) dL_dX_GT -= 2 * numpy.einsum( 'pqTs,pqu,uG,Gs->GT', @@ -286,16 +240,13 @@ def thc_objective_and_grad(xcur, norb, nthc, eri, verbose=False): CprP, MPQ, etaPp, - optimize=['einsum_path', (0, 1), (0, 2), (0, 1)]) + optimize=['einsum_path', (0, 1), (0, 2), (0, 1)], + ) return res, numpy.hstack((dL_dX_GT.ravel(), dL_dZab.ravel())) -def cp_ls_cholesky_factor_objective(beta_gamma, - norb, - nthc, - cholesky_factor, - calcgrad=False): +def cp_ls_cholesky_factor_objective(beta_gamma, norb, nthc, cholesky_factor, calcgrad=False): """cholesky_factor is reshaped into (norb, norb, num_cholesky) Cholesky factor B_{ab,x} @@ -307,28 +258,24 @@ def cp_ls_cholesky_factor_objective(beta_gamma, """ # compute objective num_cholfactors = cholesky_factor.shape[-1] - beta_bR = beta_gamma[:norb * nthc].reshape((norb, nthc)) - gamma_yR = beta_gamma[norb * nthc:norb * nthc + - nthc * num_cholfactors].reshape( - (num_cholfactors, nthc)) + beta_bR = beta_gamma[: norb * nthc].reshape((norb, nthc)) + gamma_yR = beta_gamma[norb * nthc : norb * nthc + nthc * num_cholfactors].reshape( + (num_cholfactors, nthc) + ) beta_abR = numpy.einsum('aR,bR->abR', beta_bR, beta_bR) chol_approx = numpy.einsum('abR,XR->abX', beta_abR, gamma_yR) delta = cholesky_factor - chol_approx - fval = 0.5 * numpy.sum((delta)**2) + fval = 0.5 * numpy.sum((delta) ** 2) if calcgrad: # compute grad # \partial O / \partial beta_{c,s} - grad_beta = -2 * numpy.einsum('Cbx,bS,xS->CS', - delta, - beta_bR, - gamma_yR, - optimize=['einsum_path', (0, 2), (0, 1)]) - grad_gamma = -numpy.einsum('abY,aS,bS->YS', - delta, - beta_bR, - beta_bR, - optimize=['einsum_path', (1, 2), (0, 1)]) + grad_beta = -2 * numpy.einsum( + 'Cbx,bS,xS->CS', delta, beta_bR, gamma_yR, optimize=['einsum_path', (0, 2), (0, 1)] + ) + grad_gamma = -numpy.einsum( + 'abY,aS,bS->YS', delta, beta_bR, beta_bR, optimize=['einsum_path', (1, 2), (0, 1)] + ) grad = numpy.hstack((grad_beta.ravel(), grad_gamma.ravel())) return fval, grad else: diff --git a/src/openfermion/resource_estimates/utils.py b/src/openfermion/resource_estimates/utils.py index 3630788e1..a08a17474 100644 --- a/src/openfermion/resource_estimates/utils.py +++ b/src/openfermion/resource_estimates/utils.py @@ -1,4 +1,4 @@ -#coverage:ignore +# coverage:ignore """ Utilities for FT costing calculations """ from typing import Tuple, Optional import sys @@ -8,7 +8,7 @@ def QR(L: int, M1: int) -> Tuple[int, int]: - """ This gives the optimal k and minimum cost for a QROM over L values of + """This gives the optimal k and minimum cost for a QROM over L values of size M. Args: @@ -23,8 +23,10 @@ def QR(L: int, M1: int) -> Tuple[int, int]: try: assert k >= 0 except AssertionError: - sys.exit("In function QR: L is smaller than M: increase RANK or lower " - "THRESH (or alternatively decrease CHI)") + sys.exit( + "In function QR: L is smaller than M: increase RANK or lower " + "THRESH (or alternatively decrease CHI)" + ) value = lambda k: L / np.power(2.0, k) + M1 * (np.power(2.0, k) - 1) k_int = [np.floor(k), np.ceil(k)] # restrict optimal k to integers k_opt = k_int[np.argmin(value(k_int))] # obtain optimal k @@ -35,7 +37,7 @@ def QR(L: int, M1: int) -> Tuple[int, int]: def QR2(L1: int, L2: int, M1: int) -> Tuple[int, int, int]: - """ This gives the optimal k values and minimum cost for a QROM using + """This gives the optimal k values and minimum cost for a QROM using two L values of size M, e.g. the optimal k values for the QROM on two registers. Args: @@ -55,9 +57,9 @@ def QR2(L1: int, L2: int, M1: int) -> Tuple[int, int, int]: # Biggest concern is if k1 / k2 range is not large enough! for k1 in range(1, 17): for k2 in range(1, 17): - value = np.ceil(L1 / np.power(2.0, k1)) * np.ceil(L2 / \ - np.power(2.0, k2)) +\ - M1 * (np.power(2.0, k1 + k2) - 1) + value = np.ceil(L1 / np.power(2.0, k1)) * np.ceil(L2 / np.power(2.0, k2)) + M1 * ( + np.power(2.0, k1 + k2) - 1 + ) if value < val_opt: val_opt = value k1_opt = k1 @@ -68,7 +70,7 @@ def QR2(L1: int, L2: int, M1: int) -> Tuple[int, int, int]: def QI(L: int) -> Tuple[int, int]: - """ This gives the opt k and minimum cost for an inverse QROM over L vals + """This gives the opt k and minimum cost for an inverse QROM over L vals Args: L (int) - @@ -90,7 +92,7 @@ def QI(L: int) -> Tuple[int, int]: # Is this ever used? It's defined in costingsf.nb, but I don't it's ever called. def QI2(L1: int, L2: int) -> Tuple[int, int, int]: - """ This gives the optimal k values and minimum cost for inverse QROM + """This gives the optimal k values and minimum cost for inverse QROM using two L values, e.g. the optimal k values for the inverse QROM on two registers. @@ -110,9 +112,9 @@ def QI2(L1: int, L2: int) -> Tuple[int, int, int]: # Biggest concern is if k1 / k2 range is not large enough! for k1 in range(1, 17): for k2 in range(1, 17): - value = np.ceil(L1 / np.power(2.0, k1)) * np.ceil(L2 / \ - np.power(2.0, k2)) +\ - np.power(2.0, k1 + k2) + value = np.ceil(L1 / np.power(2.0, k1)) * np.ceil(L2 / np.power(2.0, k2)) + np.power( + 2.0, k1 + k2 + ) if value < val_opt: val_opt = value k1_opt = k1 @@ -123,7 +125,7 @@ def QI2(L1: int, L2: int) -> Tuple[int, int, int]: def power_two(m: int) -> int: - """ Return the power of two that is a factor of m """ + """Return the power of two that is a factor of m""" assert m >= 0 if m % 2 == 0: count = 0 @@ -135,13 +137,13 @@ def power_two(m: int) -> int: class RunSilent(object): - """ Context manager to prevent function writing to stdout/stderr - e.g. for noisy_function(), wrap it like so + """Context manager to prevent function writing to stdout/stderr + e.g. for noisy_function(), wrap it like so - with RunSilent(): - noisy_function() + with RunSilent(): + noisy_function() - ... and your terminal will no longer be littered with prints + ... and your terminal will no longer be littered with prints """ def __init__(self, stdout=None, stderr=None): @@ -163,8 +165,8 @@ def __exit__(self, exc_type, exc_value, traceback): self.devnull.close() -def eigendecomp(M, tol=1.15E-16): - """ Decompose matrix M into L.L^T where rank(L) < rank(M) to some threshold +def eigendecomp(M, tol=1.15e-16): + """Decompose matrix M into L.L^T where rank(L) < rank(M) to some threshold Args: M (np.ndarray) - (N x N) positive semi-definite matrix to be decomposed diff --git a/src/openfermion/resource_estimates/utils_test.py b/src/openfermion/resource_estimates/utils_test.py index d4bb0dc3e..ffcfbcf00 100644 --- a/src/openfermion/resource_estimates/utils_test.py +++ b/src/openfermion/resource_estimates/utils_test.py @@ -1,12 +1,12 @@ -#coverage:ignore +# coverage:ignore """Test cases for util.py """ from openfermion.resource_estimates.utils import QR, QI, QR2, QI2, power_two def test_QR(): - """ Tests function QR which gives the minimum cost for a QROM over L values - of size M. + """Tests function QR which gives the minimum cost for a QROM over L values + of size M. """ # Tests checked against Mathematica noteboook `costingTHC.nb` # Arguments are otherwise random @@ -15,8 +15,8 @@ def test_QR(): def test_QI(): - """ Tests function QI which gives the minimum cost for inverse QROM over - L values. + """Tests function QI which gives the minimum cost for inverse QROM over + L values. """ # Tests checked against Mathematica noteboook `costingTHC.nb` # Arguments are otherwise random @@ -25,8 +25,8 @@ def test_QI(): def test_QR2(): - """ Tests function QR2 which gives the minimum cost for a QROM with two - registers. + """Tests function QR2 which gives the minimum cost for a QROM with two + registers. """ # Tests checked against Mathematica noteboook `costingsf.nb` # Arguments are otherwise random @@ -35,8 +35,8 @@ def test_QR2(): def test_QI2(): - """ Tests function QI which gives the minimum cost for inverse QROM with - two registers. + """Tests function QI which gives the minimum cost for inverse QROM with + two registers. """ # Tests checked against Mathematica noteboook `costingsf.nb` # Arguments are otherwise random @@ -45,7 +45,7 @@ def test_QI2(): def test_power_two(): - """ Test for power_two(m) which returns power of 2 that is a factor of m """ + """Test for power_two(m) which returns power of 2 that is a factor of m""" try: power_two(-1234) except AssertionError: diff --git a/src/openfermion/testing/__init__.py b/src/openfermion/testing/__init__.py index 855166399..344e8add9 100644 --- a/src/openfermion/testing/__init__.py +++ b/src/openfermion/testing/__init__.py @@ -26,7 +26,4 @@ module_importable, ) -from .wrapped import ( - assert_equivalent_repr, - assert_implements_consistent_protocols, -) +from .wrapped import assert_equivalent_repr, assert_implements_consistent_protocols diff --git a/src/openfermion/testing/circuit_validation.py b/src/openfermion/testing/circuit_validation.py index 44712bd1c..a1236701a 100644 --- a/src/openfermion/testing/circuit_validation.py +++ b/src/openfermion/testing/circuit_validation.py @@ -20,9 +20,9 @@ from openfermion.config import EQ_TOLERANCE -def validate_trotterized_evolution(circuit: cirq.Circuit, - ops: List['openfermion.QubitOperator'], - qubits: List['cirq.Qid']): +def validate_trotterized_evolution( + circuit: cirq.Circuit, ops: List['openfermion.QubitOperator'], qubits: List['cirq.Qid'] +): r'''Checks whether a circuit implements Trotterized evolution Takes a circuit that is supposed to implement evolution of the @@ -53,9 +53,7 @@ def validate_trotterized_evolution(circuit: cirq.Circuit, target_unitary = op_unitary.dot(target_unitary) actual_unitary = circuit.unitary(qubit_order=qubits) - overlap = float( - numpy.abs(numpy.trace(target_unitary.dot( - actual_unitary.conj().T)))) / hs_dim + overlap = float(numpy.abs(numpy.trace(target_unitary.dot(actual_unitary.conj().T)))) / hs_dim if 1 - overlap > EQ_TOLERANCE: return False return True diff --git a/src/openfermion/testing/circuit_validation_test.py b/src/openfermion/testing/circuit_validation_test.py index 65f34445d..7896fde61 100644 --- a/src/openfermion/testing/circuit_validation_test.py +++ b/src/openfermion/testing/circuit_validation_test.py @@ -14,8 +14,7 @@ import cirq from openfermion import QubitOperator -from openfermion.testing.circuit_validation import\ - validate_trotterized_evolution +from openfermion.testing.circuit_validation import validate_trotterized_evolution def test_checking_passes(): @@ -35,12 +34,8 @@ def test_checking_passes_twoops(): z_rotation_op = anglez * QubitOperator('Z0') x_rotation_op = anglex * QubitOperator('X0') qubits = [cirq.GridQubit(0, 0)] - circuit = cirq.Circuit([ - cirq.rz(-2 * anglez).on(qubits[0]), - cirq.rx(-2 * anglex).on(qubits[0]) - ]) - res = validate_trotterized_evolution(circuit, - [z_rotation_op, x_rotation_op], qubits) + circuit = cirq.Circuit([cirq.rz(-2 * anglez).on(qubits[0]), cirq.rx(-2 * anglex).on(qubits[0])]) + res = validate_trotterized_evolution(circuit, [z_rotation_op, x_rotation_op], qubits) assert res is True diff --git a/src/openfermion/testing/examples_test.py b/src/openfermion/testing/examples_test.py index 6ec0c393b..d1ca93eaf 100644 --- a/src/openfermion/testing/examples_test.py +++ b/src/openfermion/testing/examples_test.py @@ -19,11 +19,10 @@ class ExamplesTest(unittest.TestCase): - def setUp(self): """Construct test info""" self.testing_folder = os.path.join( - os.path.dirname(__file__), # Start at this file's directory. + os.path.dirname(__file__) # Start at this file's directory. ) def test_example(self): @@ -37,7 +36,7 @@ def test_example(self): def test_can_run_examples_jupyter_notebooks(self): # pragma: no cover """No coverage on this test because it is not run. - The test is kept as an example. + The test is kept as an example. """ for filename in os.listdir(self.testing_folder): if not filename.endswith('.ipynb'): @@ -63,12 +62,9 @@ def is_matplotlib_cell(cell): # pragma: no cover def strip_magics_and_shows(text): # pragma: no cover """Remove Jupyter magics and pyplot show commands.""" - lines = [ - line for line in text.split('\n') if not contains_magic_or_show(line) - ] + lines = [line for line in text.split('\n') if not contains_magic_or_show(line)] return '\n'.join(lines) def contains_magic_or_show(line): # pragma: no cover - return (line.strip().startswith('%') or 'pyplot.show(' in line or - 'plt.show(' in line) + return line.strip().startswith('%') or 'pyplot.show(' in line or 'plt.show(' in line diff --git a/src/openfermion/testing/hydrogen_integration_test.py b/src/openfermion/testing/hydrogen_integration_test.py index a6c622608..7a15b67f5 100644 --- a/src/openfermion/testing/hydrogen_integration_test.py +++ b/src/openfermion/testing/hydrogen_integration_test.py @@ -18,29 +18,30 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData from openfermion.utils.operator_utils import count_qubits -from openfermion.linalg.sparse_tools import (get_sparse_operator, - get_ground_state, expectation, - get_density_matrix, - jw_hartree_fock_state) +from openfermion.linalg.sparse_tools import ( + get_sparse_operator, + get_ground_state, + expectation, + get_density_matrix, + jw_hartree_fock_state, +) from openfermion.measurements import get_interaction_rdm -from openfermion.transforms.opconversions import (jordan_wigner, - get_fermion_operator, - reverse_jordan_wigner, - normal_ordered) +from openfermion.transforms.opconversions import ( + jordan_wigner, + get_fermion_operator, + reverse_jordan_wigner, + normal_ordered, +) from openfermion.transforms.repconversions import get_interaction_operator class HydrogenIntegrationTest(unittest.TestCase): - def setUp(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. @@ -55,26 +56,23 @@ def setUp(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. - self.fermion_hamiltonian = normal_ordered( - get_fermion_operator(self.molecular_hamiltonian)) + self.fermion_hamiltonian = normal_ordered(get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. - self.hamiltonian_matrix = get_sparse_operator( - self.molecular_hamiltonian) + self.hamiltonian_matrix = get_sparse_operator(self.molecular_hamiltonian) def test_integral_data(self): - # Initialize coefficients given in arXiv 1208.5986: g0 = 0.71375 g1 = -1.2525 g2 = -0.47593 - g3 = 0.67449 / 2. - g4 = 0.69740 / 2. - g5 = 0.66347 / 2. - g6 = 0.18129 / 2. + g3 = 0.67449 / 2.0 + g4 = 0.69740 / 2.0 + g5 = 0.66347 / 2.0 + g6 = 0.18129 / 2.0 # Check the integrals in the test data. self.assertAlmostEqual(self.nuclear_repulsion, g0, places=4) @@ -108,7 +106,6 @@ def test_integral_data(self): self.assertAlmostEqual(self.two_body[0, 1, 3, 2], g6, places=4) def test_qubit_operator(self): - # Below are qubit term coefficients, also from arXiv 1208.5986: f1 = 0.1712 f2 = -0.2228 @@ -119,69 +116,35 @@ def test_qubit_operator(self): f7 = 0.04532 # Test the local Hamiltonian terms. - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'),)], - f1, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'),)], - f1, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((2, 'Z'),)], - f2, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((3, 'Z'),)], - f2, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (1, - 'Z'))], - f3, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (2, - 'Z'))], - f4, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'), (3, - 'Z'))], - f4, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'), (2, - 'Z'))], - f5, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (3, - 'Z'))], - f5, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((2, 'Z'), (3, - 'Z'))], - f6, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Y'), (1, 'Y'), - (2, 'X'), (3, - 'X'))], - -f7, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'X'), (1, 'X'), - (2, 'Y'), (3, - 'Y'))], - -f7, - places=4) - - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'X'), (1, 'Y'), - (2, 'Y'), (3, - 'X'))], - f7, - places=4) - self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Y'), (1, 'X'), - (2, 'X'), (3, - 'Y'))], - f7, - places=4) + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'),)], f1, places=4) + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'),)], f1, places=4) + + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((2, 'Z'),)], f2, places=4) + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((3, 'Z'),)], f2, places=4) + + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (1, 'Z'))], f3, places=4) + + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (2, 'Z'))], f4, places=4) + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'), (3, 'Z'))], f4, places=4) + + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((1, 'Z'), (2, 'Z'))], f5, places=4) + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((0, 'Z'), (3, 'Z'))], f5, places=4) + + self.assertAlmostEqual(self.qubit_hamiltonian.terms[((2, 'Z'), (3, 'Z'))], f6, places=4) + + self.assertAlmostEqual( + self.qubit_hamiltonian.terms[((0, 'Y'), (1, 'Y'), (2, 'X'), (3, 'X'))], -f7, places=4 + ) + self.assertAlmostEqual( + self.qubit_hamiltonian.terms[((0, 'X'), (1, 'X'), (2, 'Y'), (3, 'Y'))], -f7, places=4 + ) + + self.assertAlmostEqual( + self.qubit_hamiltonian.terms[((0, 'X'), (1, 'Y'), (2, 'Y'), (3, 'X'))], f7, places=4 + ) + self.assertAlmostEqual( + self.qubit_hamiltonian.terms[((0, 'Y'), (1, 'X'), (2, 'X'), (3, 'Y'))], f7, places=4 + ) def test_reverse_jordan_wigner(self): fermion_hamiltonian = reverse_jordan_wigner(self.qubit_hamiltonian) @@ -189,10 +152,8 @@ def test_reverse_jordan_wigner(self): self.assertTrue(self.fermion_hamiltonian == fermion_hamiltonian) def test_interaction_operator_mapping(self): - # Make sure mapping of FermionOperator to InteractionOperator works. - molecular_hamiltonian = get_interaction_operator( - self.fermion_hamiltonian) + molecular_hamiltonian = get_interaction_operator(self.fermion_hamiltonian) fermion_hamiltonian = get_fermion_operator(molecular_hamiltonian) self.assertTrue(self.fermion_hamiltonian == fermion_hamiltonian) @@ -201,13 +162,14 @@ def test_interaction_operator_mapping(self): self.assertTrue(self.qubit_hamiltonian == qubit_hamiltonian) def test_rdm_numerically(self): - # Test energy of RDM. fci_rdm_energy = self.nuclear_repulsion - fci_rdm_energy += numpy.sum(self.fci_rdm.one_body_tensor * - self.molecular_hamiltonian.one_body_tensor) - fci_rdm_energy += numpy.sum(self.fci_rdm.two_body_tensor * - self.molecular_hamiltonian.two_body_tensor) + fci_rdm_energy += numpy.sum( + self.fci_rdm.one_body_tensor * self.molecular_hamiltonian.one_body_tensor + ) + fci_rdm_energy += numpy.sum( + self.fci_rdm.two_body_tensor * self.molecular_hamiltonian.two_body_tensor + ) self.assertAlmostEqual(fci_rdm_energy, self.molecule.fci_energy) # Confirm expectation on qubit Hamiltonian using reverse JW matches. @@ -223,7 +185,6 @@ def test_rdm_numerically(self): self.assertAlmostEqual(fci_rdm_energy, self.molecule.fci_energy) def test_sparse_numerically(self): - # Check FCI energy. energy, wavefunction = get_ground_state(self.hamiltonian_matrix) self.assertAlmostEqual(energy, self.molecule.fci_energy) @@ -231,17 +192,17 @@ def test_sparse_numerically(self): self.assertAlmostEqual(expected_energy, energy) # Make sure you can reproduce Hartree energy. - hf_state = jw_hartree_fock_state(self.molecule.n_electrons, - count_qubits(self.qubit_hamiltonian)) - hf_density = get_density_matrix([hf_state], [1.]) + hf_state = jw_hartree_fock_state( + self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian) + ) + hf_density = get_density_matrix([hf_state], [1.0]) # Make sure you can reproduce Hartree-Fock energy. - hf_state = jw_hartree_fock_state(self.molecule.n_electrons, - count_qubits(self.qubit_hamiltonian)) - hf_density = get_density_matrix([hf_state], [1.]) - expected_hf_density_energy = expectation(self.hamiltonian_matrix, - hf_density) + hf_state = jw_hartree_fock_state( + self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian) + ) + hf_density = get_density_matrix([hf_state], [1.0]) + expected_hf_density_energy = expectation(self.hamiltonian_matrix, hf_density) expected_hf_energy = expectation(self.hamiltonian_matrix, hf_state) self.assertAlmostEqual(expected_hf_energy, self.molecule.hf_energy) - self.assertAlmostEqual(expected_hf_density_energy, - self.molecule.hf_energy) + self.assertAlmostEqual(expected_hf_density_energy, self.molecule.hf_energy) diff --git a/src/openfermion/testing/lih_integration_test.py b/src/openfermion/testing/lih_integration_test.py index 3176cd943..5a07fa763 100644 --- a/src/openfermion/testing/lih_integration_test.py +++ b/src/openfermion/testing/lih_integration_test.py @@ -17,37 +17,34 @@ from openfermion.config import DATA_DIRECTORY from openfermion.chem import MolecularData -from openfermion.transforms.opconversions import (get_fermion_operator, - normal_ordered, jordan_wigner, - reverse_jordan_wigner) +from openfermion.transforms.opconversions import ( + get_fermion_operator, + normal_ordered, + jordan_wigner, + reverse_jordan_wigner, +) from openfermion.transforms.repconversions import freeze_orbitals from openfermion.measurements import get_interaction_rdm from openfermion.linalg import get_sparse_operator, get_ground_state -from openfermion.linalg.sparse_tools import (expectation, jw_hartree_fock_state, - get_density_matrix) +from openfermion.linalg.sparse_tools import expectation, jw_hartree_fock_state, get_density_matrix from openfermion.utils.operator_utils import count_qubits class LiHIntegrationTest(unittest.TestCase): - def setUp(self): # Set up molecule. - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H1-Li1_sto-3g_singlet_1.45') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian self.molecular_hamiltonian = self.molecule.get_molecular_hamiltonian() - self.molecular_hamiltonian_no_core = ( - self.molecule.get_molecular_hamiltonian( - occupied_indices=[0], - active_indices=range(1, self.molecule.n_orbitals))) + self.molecular_hamiltonian_no_core = self.molecule.get_molecular_hamiltonian( + occupied_indices=[0], active_indices=range(1, self.molecule.n_orbitals) + ) # Get FCI RDM. self.fci_rdm = self.molecule.get_molecular_rdm(use_fci=1) @@ -58,8 +55,7 @@ def setUp(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. - self.fermion_hamiltonian = normal_ordered( - get_fermion_operator(self.molecular_hamiltonian)) + self.fermion_hamiltonian = normal_ordered(get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) @@ -70,13 +66,10 @@ def setUp(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get matrix form. - self.hamiltonian_matrix = get_sparse_operator( - self.molecular_hamiltonian) - self.hamiltonian_matrix_no_core = get_sparse_operator( - self.molecular_hamiltonian_no_core) + self.hamiltonian_matrix = get_sparse_operator(self.molecular_hamiltonian) + self.hamiltonian_matrix_no_core = get_sparse_operator(self.molecular_hamiltonian_no_core) def test_all(self): - # Test reverse Jordan-Wigner. fermion_hamiltonian = reverse_jordan_wigner(self.qubit_hamiltonian) fermion_hamiltonian = normal_ordered(fermion_hamiltonian) @@ -89,10 +82,8 @@ def test_all(self): # Test RDM energy. fci_rdm_energy = self.nuclear_repulsion - fci_rdm_energy += numpy.sum(self.fci_rdm.one_body_tensor * - self.one_body) - fci_rdm_energy += numpy.sum(self.fci_rdm.two_body_tensor * - self.two_body) + fci_rdm_energy += numpy.sum(self.fci_rdm.one_body_tensor * self.one_body) + fci_rdm_energy += numpy.sum(self.fci_rdm.two_body_tensor * self.two_body) self.assertAlmostEqual(fci_rdm_energy, self.molecule.fci_energy) # Confirm expectation on qubit Hamiltonian using reverse JW matches. @@ -114,26 +105,26 @@ def test_all(self): self.assertAlmostEqual(expected_energy, energy) # Make sure you can reproduce Hartree-Fock energy. - hf_state = jw_hartree_fock_state(self.molecule.n_electrons, - count_qubits(self.qubit_hamiltonian)) - hf_density = get_density_matrix([hf_state], [1.]) - expected_hf_density_energy = expectation(self.hamiltonian_matrix, - hf_density) + hf_state = jw_hartree_fock_state( + self.molecule.n_electrons, count_qubits(self.qubit_hamiltonian) + ) + hf_density = get_density_matrix([hf_state], [1.0]) + expected_hf_density_energy = expectation(self.hamiltonian_matrix, hf_density) expected_hf_energy = expectation(self.hamiltonian_matrix, hf_state) self.assertAlmostEqual(expected_hf_energy, self.molecule.hf_energy) - self.assertAlmostEqual(expected_hf_density_energy, - self.molecule.hf_energy) + self.assertAlmostEqual(expected_hf_density_energy, self.molecule.hf_energy) # Check that frozen core result matches frozen core FCI from psi4. # Recore frozen core result from external calculation. self.frozen_core_fci_energy = -7.8807607374168 - no_core_fci_energy = numpy.linalg.eigh( - self.hamiltonian_matrix_no_core.toarray())[0][0] + no_core_fci_energy = numpy.linalg.eigh(self.hamiltonian_matrix_no_core.toarray())[0][0] self.assertAlmostEqual(no_core_fci_energy, self.frozen_core_fci_energy) # Check that the freeze_orbitals function has the same effect as the # as the occupied_indices option of get_molecular_hamiltonian. frozen_hamiltonian = freeze_orbitals( - get_fermion_operator(self.molecular_hamiltonian), [0, 1]) - self.assertTrue(frozen_hamiltonian == get_fermion_operator( - self.molecular_hamiltonian_no_core)) + get_fermion_operator(self.molecular_hamiltonian), [0, 1] + ) + self.assertTrue( + frozen_hamiltonian == get_fermion_operator(self.molecular_hamiltonian_no_core) + ) diff --git a/src/openfermion/testing/performance_benchmarks.py b/src/openfermion/testing/performance_benchmarks.py index b18f5aaf4..9e67d7632 100644 --- a/src/openfermion/testing/performance_benchmarks.py +++ b/src/openfermion/testing/performance_benchmarks.py @@ -18,15 +18,15 @@ from openfermion.utils import commutator, Grid from openfermion.ops import FermionOperator, QubitOperator from openfermion.hamiltonians import jellium_model -from openfermion.transforms.opconversions import (jordan_wigner, - get_fermion_operator, - normal_ordered) -from openfermion.linalg import (jordan_wigner_sparse, - LinearQubitOperatorOptions, LinearQubitOperator, - ParallelLinearQubitOperator) +from openfermion.transforms.opconversions import jordan_wigner, get_fermion_operator, normal_ordered +from openfermion.linalg import ( + jordan_wigner_sparse, + LinearQubitOperatorOptions, + LinearQubitOperator, + ParallelLinearQubitOperator, +) from openfermion.testing.testing_utils import random_interaction_operator -from openfermion.transforms import \ - commutator_ordered_diagonal_coulomb_with_two_body_operator +from openfermion.transforms import commutator_ordered_diagonal_coulomb_with_two_body_operator def benchmark_molecular_operator_jordan_wigner(n_qubits): @@ -75,26 +75,21 @@ def benchmark_fermion_math_and_normal_order(n_qubits, term_length, power): operators_a = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] operators_b = [(numpy.random.randint(n_qubits), numpy.random.randint(2))] for _ in range(term_length): - # Make sure the operator is not trivially zero. operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_a == operators_a[-1]: - operator_a = (numpy.random.randint(n_qubits), - numpy.random.randint(2)) + operator_a = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_a += [operator_a] # Do the same for the other operator. operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) while operator_b == operators_b[-1]: - operator_b = (numpy.random.randint(n_qubits), - numpy.random.randint(2)) + operator_b = (numpy.random.randint(n_qubits), numpy.random.randint(2)) operators_b += [operator_b] # Initialize FermionTerms and then sum them together. - fermion_term_a = FermionOperator(tuple(operators_a), - float(numpy.random.randn())) - fermion_term_b = FermionOperator(tuple(operators_b), - float(numpy.random.randn())) + fermion_term_a = FermionOperator(tuple(operators_a), float(numpy.random.randn())) + fermion_term_b = FermionOperator(tuple(operators_b), float(numpy.random.randn())) fermion_operator = fermion_term_a + fermion_term_b # Exponentiate. @@ -145,11 +140,7 @@ def benchmark_linear_qubit_operator(n_qubits, n_terms, processes=None): runtime_matvec: The time it takes to perform matrix multiplication. """ # Generates Qubit Operator with specified number of terms. - map_int_to_operator = { - 0: 'X', - 1: 'Y', - 2: 'Z', - } + map_int_to_operator = {0: 'X', 1: 'Y', 2: 'Z'} qubit_operator = QubitOperator.zero() for _ in range(n_terms): tuples = [] @@ -168,8 +159,8 @@ def benchmark_linear_qubit_operator(n_qubits, n_terms, processes=None): linear_operator = LinearQubitOperator(qubit_operator, n_qubits) else: linear_operator = ParallelLinearQubitOperator( - qubit_operator, n_qubits, - LinearQubitOperatorOptions(processes=processes)) + qubit_operator, n_qubits, LinearQubitOperatorOptions(processes=processes) + ) end = time.time() runtime_operator = end - start @@ -183,8 +174,7 @@ def benchmark_linear_qubit_operator(n_qubits, n_terms, processes=None): return runtime_operator, runtime_matvec -def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( - side_length): +def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium(side_length): """Test speed of computing commutators using specialized functions. Args: @@ -199,8 +189,7 @@ def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( runtime_diagonal_commutator: The time it takes to compute the same commutator using methods restricted to diagonal Coulomb operators. """ - hamiltonian = normal_ordered( - jellium_model(Grid(2, side_length, 1.), plane_wave=False)) + hamiltonian = normal_ordered(jellium_model(Grid(2, side_length, 1.0), plane_wave=False)) part_a = FermionOperator.zero() part_b = FermionOperator.zero() @@ -219,8 +208,7 @@ def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( runtime_commutator = end - start start = time.time() - _ = commutator_ordered_diagonal_coulomb_with_two_body_operator( - part_a, part_b) + _ = commutator_ordered_diagonal_coulomb_with_two_body_operator(part_a, part_b) end = time.time() runtime_diagonal_commutator = end - start @@ -230,13 +218,10 @@ def benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( # Sets up each benchmark run. def run_molecular_operator_jordan_wigner(n_qubits=18): """Run InteractionOperator.jordan_wigner_transform() benchmark.""" - logging.info('Starting test on ' - 'InteractionOperator.jordan_wigner_transform()') + logging.info('Starting test on ' 'InteractionOperator.jordan_wigner_transform()') logging.info('n_qubits = %d.', n_qubits) runtime = benchmark_molecular_operator_jordan_wigner(n_qubits) - logging.info( - 'InteractionOperator.jordan_wigner_transform() takes %f ' - 'seconds.\n', runtime) + logging.info('InteractionOperator.jordan_wigner_transform() takes %f ' 'seconds.\n', runtime) return runtime @@ -244,12 +229,13 @@ def run_molecular_operator_jordan_wigner(n_qubits=18): def run_fermion_math_and_normal_order(n_qubits=20, term_length=10, power=15): """Run benchmark on FermionOperator math and normal-ordering.""" logging.info('Starting test on FermionOperator math and normal ordering.') - logging.info('(n_qubits, term_length, power) = (%d, %d, %d).', n_qubits, - term_length, power) + logging.info('(n_qubits, term_length, power) = (%d, %d, %d).', n_qubits, term_length, power) runtime_math, runtime_normal = benchmark_fermion_math_and_normal_order( - n_qubits, term_length, power) - logging.info('Math took %f seconds. Normal ordering took %f seconds.\n', - runtime_math, runtime_normal) + n_qubits, term_length, power + ) + logging.info( + 'Math took %f seconds. Normal ordering took %f seconds.\n', runtime_math, runtime_normal + ) return runtime_math, runtime_normal @@ -270,13 +256,16 @@ def run_linear_qubit_operator(n_qubits=16, n_terms=10, processes=10): logging.info('Starting test on linear_qubit_operator().') logging.info('(n_qubits, n_terms) = (%d, %d).', n_qubits, n_terms) _, runtime_sequential = benchmark_linear_qubit_operator(n_qubits, n_terms) - _, runtime_parallel = benchmark_linear_qubit_operator( - n_qubits, n_terms, processes) + _, runtime_parallel = benchmark_linear_qubit_operator(n_qubits, n_terms, processes) logging.info( 'LinearQubitOperator took %f seconds, while ' 'ParallelQubitOperator took %f seconds with %d processes, ' - 'and ratio is %.2f.\n', runtime_sequential, runtime_parallel, processes, - runtime_sequential / runtime_parallel) + 'and ratio is %.2f.\n', + runtime_sequential, + runtime_parallel, + processes, + runtime_sequential / runtime_parallel, + ) return runtime_sequential, runtime_parallel @@ -285,16 +274,19 @@ def run_diagonal_commutator(side_length=4): """Run commutator_diagonal_coulomb_operators benchmark.""" logging.info( - 'Starting test on ' - 'commutator_ordered_diagonal_coulomb_with_two_body_operator().') - runtime_commutator, runtime_diagonal_commutator = ( - benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium( - side_length=side_length)) + 'Starting test on ' 'commutator_ordered_diagonal_coulomb_with_two_body_operator().' + ) + ( + runtime_commutator, + runtime_diagonal_commutator, + ) = benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium(side_length=side_length) logging.info( 'Regular commutator computation took %f seconds, while ' 'commutator_ordered_diagonal_coulomb_with_two_body_operator' - ' took %f seconds. Ratio is %.2f.\n', runtime_commutator, + ' took %f seconds. Ratio is %.2f.\n', + runtime_commutator, runtime_diagonal_commutator, - runtime_commutator / runtime_diagonal_commutator) + runtime_commutator / runtime_diagonal_commutator, + ) return runtime_commutator, runtime_diagonal_commutator diff --git a/src/openfermion/testing/performance_benchmarks_test.py b/src/openfermion/testing/performance_benchmarks_test.py index 0c8a98058..3fd6b87f2 100644 --- a/src/openfermion/testing/performance_benchmarks_test.py +++ b/src/openfermion/testing/performance_benchmarks_test.py @@ -38,8 +38,7 @@ def test_linear_qop(): def test_comm_diag_coulomb(): - r1, r2 = \ - benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium(4) + r1, r2 = benchmark_commutator_diagonal_coulomb_operators_2D_spinless_jellium(4) assert isinstance(r1, float) assert isinstance(r2, float) diff --git a/src/openfermion/testing/random.py b/src/openfermion/testing/random.py index fe60850ba..9a1003eb3 100644 --- a/src/openfermion/testing/random.py +++ b/src/openfermion/testing/random.py @@ -23,9 +23,7 @@ def random_interaction_operator_term( - order: int, - real: bool = True, - seed: Optional[int] = None, + order: int, real: bool = True, seed: Optional[int] = None ) -> 'openfermion.InteractionOperator': """Generates a random interaction operator with non-zero coefficients only on terms corresponding to the given number of unique orbitals. diff --git a/src/openfermion/testing/random_test.py b/src/openfermion/testing/random_test.py index c17d29f30..e636583d7 100644 --- a/src/openfermion/testing/random_test.py +++ b/src/openfermion/testing/random_test.py @@ -19,9 +19,10 @@ from openfermion.testing import random_interaction_operator_term -@pytest.mark.parametrize('order,real,seed', [(k, r, random.randrange(2 << 30)) - for k in [1, 2, 3, 4, 5] - for r in [0, 1] for _ in range(5)]) +@pytest.mark.parametrize( + 'order,real,seed', + [(k, r, random.randrange(2 << 30)) for k in [1, 2, 3, 4, 5] for r in [0, 1] for _ in range(5)], +) def test_random_interaction_operator_term(order, real, seed): op = random_interaction_operator_term(order, real, seed) diff --git a/src/openfermion/testing/testing_utils.py b/src/openfermion/testing/testing_utils.py index 03624d735..cd0d6b8a9 100644 --- a/src/openfermion/testing/testing_utils.py +++ b/src/openfermion/testing/testing_utils.py @@ -18,9 +18,11 @@ from scipy.linalg import qr from openfermion.ops.operators import QubitOperator -from openfermion.ops.representations import (DiagonalCoulombHamiltonian, - QuadraticHamiltonian, - InteractionOperator) +from openfermion.ops.representations import ( + DiagonalCoulombHamiltonian, + QuadraticHamiltonian, + InteractionOperator, +) def haar_random_vector(n, seed=None): @@ -28,7 +30,7 @@ def haar_random_vector(n, seed=None): if seed is not None: numpy.random.seed(seed) vector = numpy.random.randn(n).astype(complex) - vector += 1.j * numpy.random.randn(n).astype(complex) + vector += 1.0j * numpy.random.randn(n).astype(complex) normalization = numpy.sqrt(vector.dot(numpy.conjugate(vector))) return vector / normalization @@ -41,7 +43,7 @@ def random_antisymmetric_matrix(n, real=False, seed=None): if real: rand_mat = numpy.random.randn(n, n) else: - rand_mat = numpy.random.randn(n, n) + 1.j * numpy.random.randn(n, n) + rand_mat = numpy.random.randn(n, n) + 1.0j * numpy.random.randn(n, n) antisymmetric_mat = rand_mat - rand_mat.T return antisymmetric_mat @@ -54,7 +56,7 @@ def random_hermitian_matrix(n, real=False, seed=None): if real: rand_mat = numpy.random.randn(n, n) else: - rand_mat = numpy.random.randn(n, n) + 1.j * numpy.random.randn(n, n) + rand_mat = numpy.random.randn(n, n) + 1.0j * numpy.random.randn(n, n) hermitian_mat = rand_mat + rand_mat.T.conj() return hermitian_mat @@ -67,15 +69,12 @@ def random_unitary_matrix(n, real=False, seed=None): if real: rand_mat = numpy.random.randn(n, n) else: - rand_mat = numpy.random.randn(n, n) + 1.j * numpy.random.randn(n, n) + rand_mat = numpy.random.randn(n, n) + 1.0j * numpy.random.randn(n, n) Q, _ = qr(rand_mat) return Q -def random_qubit_operator(n_qubits=16, - max_num_terms=16, - max_many_body_order=16, - seed=None): +def random_qubit_operator(n_qubits=16, max_num_terms=16, max_many_body_order=16, seed=None): prng = numpy.random.RandomState(seed) op = QubitOperator() num_terms = prng.randint(1, max_num_terms + 1) @@ -107,10 +106,7 @@ def random_diagonal_coulomb_hamiltonian(n_qubits, real=False, seed=None): return DiagonalCoulombHamiltonian(one_body, two_body, constant) -def random_interaction_operator(n_orbitals, - expand_spin=False, - real=True, - seed=None): +def random_interaction_operator(n_orbitals, expand_spin=False, real=True, seed=None): """Generate a random instance of InteractionOperator. Args: @@ -136,12 +132,11 @@ def random_interaction_operator(n_orbitals, one_body_coefficients = random_hermitian_matrix(n_orbitals, real) # Generate random two-body coefficients. - two_body_coefficients = numpy.zeros( - (n_orbitals, n_orbitals, n_orbitals, n_orbitals), dtype) + two_body_coefficients = numpy.zeros((n_orbitals, n_orbitals, n_orbitals, n_orbitals), dtype) for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): coeff = numpy.random.randn() if not real and len(set([p, q, r, s])) >= 3: - coeff += 1.j * numpy.random.randn() + coeff += 1.0j * numpy.random.randn() # Four point symmetry. two_body_coefficients[p, q, r, s] = coeff @@ -165,36 +160,31 @@ def random_interaction_operator(n_orbitals, # Expand two-body tensor. new_two_body_coefficients = numpy.zeros( - (n_spin_orbitals, n_spin_orbitals, n_spin_orbitals, - n_spin_orbitals), - dtype=complex) + (n_spin_orbitals, n_spin_orbitals, n_spin_orbitals, n_spin_orbitals), dtype=complex + ) for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): coefficient = two_body_coefficients[p, q, r, s] # Mixed spin. - new_two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * - s] = (coefficient) - new_two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + - 1] = (coefficient) + new_two_body_coefficients[2 * p, 2 * q + 1, 2 * r + 1, 2 * s] = coefficient + new_two_body_coefficients[2 * p + 1, 2 * q, 2 * r, 2 * s + 1] = coefficient # Same spin. new_two_body_coefficients[2 * p, 2 * q, 2 * r, 2 * s] = coefficient - new_two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + - 1] = coefficient + new_two_body_coefficients[2 * p + 1, 2 * q + 1, 2 * r + 1, 2 * s + 1] = coefficient two_body_coefficients = new_two_body_coefficients # Create the InteractionOperator. - interaction_operator = InteractionOperator(constant, one_body_coefficients, - two_body_coefficients) + interaction_operator = InteractionOperator( + constant, one_body_coefficients, two_body_coefficients + ) return interaction_operator -def random_quadratic_hamiltonian(n_orbitals, - conserves_particle_number=False, - real=False, - expand_spin=False, - seed=None): +def random_quadratic_hamiltonian( + n_orbitals, conserves_particle_number=False, real=False, expand_spin=False, seed=None +): """Generate a random instance of QuadraticHamiltonian. Args: @@ -226,8 +216,7 @@ def random_quadratic_hamiltonian(n_orbitals, if antisymmetric_mat is not None: antisymmetric_mat = numpy.kron(antisymmetric_mat, numpy.eye(2)) - return QuadraticHamiltonian(hermitian_mat, antisymmetric_mat, constant, - chemical_potential) + return QuadraticHamiltonian(hermitian_mat, antisymmetric_mat, constant, chemical_potential) class EqualsTester(object): @@ -261,15 +250,15 @@ def add_equality_group(self, *group_items): self.test_case.assertTrue(not v1 != v2) # __eq__ and __ne__ should both be correct or not implemented. - self.test_case.assertTrue( - hasattr(v1, '__eq__') == hasattr(v1, '__ne__')) + self.test_case.assertTrue(hasattr(v1, '__eq__') == hasattr(v1, '__ne__')) # Careful: python2 int doesn't have __eq__ or __ne__. if hasattr(v1, '__eq__'): eq = v1.__eq__(v2) ne = v1.__ne__(v2) - self.test_case.assertIn((eq, ne), - [(True, False), (NotImplemented, False), - (NotImplemented, NotImplemented)]) + self.test_case.assertIn( + (eq, ne), + [(True, False), (NotImplemented, False), (NotImplemented, NotImplemented)], + ) # Check that this group's items don't overlap with other groups. for other_group in self.groups: @@ -279,31 +268,30 @@ def add_equality_group(self, *group_items): self.test_case.assertTrue(v1 != v2) # __eq__ and __ne__ should both be correct or not implemented. - self.test_case.assertTrue( - hasattr(v1, '__eq__') == hasattr(v1, '__ne__')) + self.test_case.assertTrue(hasattr(v1, '__eq__') == hasattr(v1, '__ne__')) # Careful: python2 int doesn't have __eq__ or __ne__. if hasattr(v1, '__eq__'): eq = v1.__eq__(v2) ne = v1.__ne__(v2) - self.test_case.assertIn((eq, ne), - [(False, True), - (NotImplemented, True), - (NotImplemented, NotImplemented)]) + self.test_case.assertIn( + (eq, ne), + [(False, True), (NotImplemented, True), (NotImplemented, NotImplemented)], + ) # Check that group items hash to the same thing, or are all unhashable. - hashes = [ - hash(v) if isinstance(v, collections.abc.Hashable) else None - for v in group_items - ] + hashes = [hash(v) if isinstance(v, collections.abc.Hashable) else None for v in group_items] if len(set(hashes)) > 1: - examples = ((v1, h1, v2, h2) - for v1, h1 in zip(group_items, hashes) - for v2, h2 in zip(group_items, hashes) - if h1 != h2) + examples = ( + (v1, h1, v2, h2) + for v1, h1 in zip(group_items, hashes) + for v2, h2 in zip(group_items, hashes) + if h1 != h2 + ) example = next(examples) raise AssertionError( 'Items in the same group produced different hashes. ' - 'Example: hash({}) is {} but hash({}) is {}.'.format(*example)) + 'Example: hash({}) is {} but hash({}) is {}.'.format(*example) + ) # Remember this group, to enable disjoint checks vs later groups. self.groups.append(group_items) @@ -351,12 +339,15 @@ def module_importable(module): """ import sys + if sys.version_info >= (3, 4): from importlib import util + plug_spec = util.find_spec(module) else: # Won't enter unless Python<3.4, so ignore for testing import pkgutil # pragma: ignore + plug_spec = pkgutil.find_loader(module) # pragma: no cover if plug_spec is None: return False diff --git a/src/openfermion/testing/testing_utils_test.py b/src/openfermion/testing/testing_utils_test.py index 83f1aff71..938958dbf 100644 --- a/src/openfermion/testing/testing_utils_test.py +++ b/src/openfermion/testing/testing_utils_test.py @@ -20,17 +20,22 @@ from openfermion.transforms.opconversions import get_fermion_operator from openfermion.utils import count_qubits, is_hermitian from openfermion.testing.testing_utils import ( - EqualsTester, haar_random_vector, random_antisymmetric_matrix, - random_diagonal_coulomb_hamiltonian, random_hermitian_matrix, - random_interaction_operator, random_quadratic_hamiltonian, - random_qubit_operator, random_unitary_matrix, module_importable, - _ClassUnknownToSubjects) + EqualsTester, + haar_random_vector, + random_antisymmetric_matrix, + random_diagonal_coulomb_hamiltonian, + random_hermitian_matrix, + random_interaction_operator, + random_quadratic_hamiltonian, + random_qubit_operator, + random_unitary_matrix, + module_importable, + _ClassUnknownToSubjects, +) def test_random_qubit_operator(): - op = random_qubit_operator(n_qubits=20, - max_num_terms=20, - max_many_body_order=20) + op = random_qubit_operator(n_qubits=20, max_num_terms=20, max_many_body_order=20) assert isinstance(op, QubitOperator) assert op.many_body_order() <= 20 @@ -50,18 +55,16 @@ def test_hashing_unknown_class(): class EqualsTesterTest(unittest.TestCase): - def test_add_equality_group_correct(self): eq = EqualsTester(self) eq.add_equality_group(fractions.Fraction(1, 1)) - eq.add_equality_group(fractions.Fraction(1, 2), - fractions.Fraction(2, 4)) + eq.add_equality_group(fractions.Fraction(1, 2), fractions.Fraction(2, 4)) - eq.add_equality_group(fractions.Fraction(2, 3), - fractions.Fraction(12, 18), - fractions.Fraction(14, 21)) + eq.add_equality_group( + fractions.Fraction(2, 3), fractions.Fraction(12, 18), fractions.Fraction(14, 21) + ) eq.add_equality_group(2, 2.0, fractions.Fraction(2, 1)) @@ -100,9 +103,7 @@ def test_add_equality_group_not_disjoint(self): eq.add_equality_group(1) def test_add_equality_group_bad_hash(self): - class KeyHash(object): - def __init__(self, k, h): self._k = k self._h = h @@ -123,9 +124,7 @@ def __hash__(self): eq.add_equality_group(KeyHash('c', 2), KeyHash('c', 3)) def test_add_equality_group_exception_hash(self): - class FailHash(object): - def __hash__(self): raise ValueError('injected failure') @@ -137,7 +136,6 @@ def test_can_fail_when_forgot_type_check(self): eq = EqualsTester(self) class NoTypeCheckEqualImplementation(object): - def __init__(self): self.x = 1 @@ -174,7 +172,6 @@ def test_fails_when_ne_is_inconsistent(self): eq = EqualsTester(self) class InconsistentNeImplementation(object): - def __init__(self): self.x = 1 @@ -191,7 +188,6 @@ def test_fails_when_not_reflexive(self): eq = EqualsTester(self) class NotReflexiveImplementation(object): - def __init__(self): self.x = 1 @@ -205,7 +201,6 @@ def test_fails_when_not_commutative(self): eq = EqualsTester(self) class NotCommutativeImplementation(object): - def __init__(self, x): self.x = x @@ -216,16 +211,13 @@ def __ne__(self, other): return not self == other with self.assertRaises(AssertionError): - eq.add_equality_group(NotCommutativeImplementation(0), - NotCommutativeImplementation(1)) + eq.add_equality_group(NotCommutativeImplementation(0), NotCommutativeImplementation(1)) with self.assertRaises(AssertionError): - eq.add_equality_group(NotCommutativeImplementation(1), - NotCommutativeImplementation(0)) + eq.add_equality_group(NotCommutativeImplementation(1), NotCommutativeImplementation(0)) class RandomInteractionOperatorTest(unittest.TestCase): - def test_hermiticity(self): n_orbitals = 5 @@ -235,9 +227,7 @@ def test_hermiticity(self): self.assertTrue(is_hermitian(ferm_op)) # Real, spin - iop = random_interaction_operator(n_orbitals, - expand_spin=True, - real=True) + iop = random_interaction_operator(n_orbitals, expand_spin=True, real=True) ferm_op = get_fermion_operator(iop) self.assertTrue(is_hermitian(ferm_op)) @@ -247,9 +237,7 @@ def test_hermiticity(self): self.assertTrue(is_hermitian(ferm_op)) # Complex, spin - iop = random_interaction_operator(n_orbitals, - expand_spin=True, - real=False) + iop = random_interaction_operator(n_orbitals, expand_spin=True, real=False) ferm_op = get_fermion_operator(iop) self.assertTrue(is_hermitian(ferm_op)) @@ -257,48 +245,50 @@ def test_symmetry(self): n_orbitals = 5 # Real. - iop = random_interaction_operator(n_orbitals, - expand_spin=False, - real=True) + iop = random_interaction_operator(n_orbitals, expand_spin=False, real=True) ferm_op = get_fermion_operator(iop) self.assertTrue(is_hermitian(ferm_op)) two_body_coefficients = iop.two_body_tensor for p, q, r, s in itertools.product(range(n_orbitals), repeat=4): + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[r, q, p, s] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[r, q, p, s]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[p, s, r, q] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[p, s, r, q]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[s, r, q, p] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[s, r, q, p]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[q, p, s, r] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[q, p, s, r]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[r, s, p, q] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[r, s, p, q]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[s, p, q, r] + ) - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[s, p, q, r]) - - self.assertAlmostEqual(two_body_coefficients[p, q, r, s], - two_body_coefficients[q, r, s, p]) + self.assertAlmostEqual( + two_body_coefficients[p, q, r, s], two_body_coefficients[q, r, s, p] + ) class HaarRandomVectorTest(unittest.TestCase): - def test_vector_norm(self): n = 15 seed = 8317 vector = haar_random_vector(n, seed) norm = vector.dot(numpy.conjugate(vector)) - self.assertAlmostEqual(1. + 0.j, norm) + self.assertAlmostEqual(1.0 + 0.0j, norm) class RandomSeedingTest(unittest.TestCase): - def test_random_operators_are_reproducible(self): op1 = random_diagonal_coulomb_hamiltonian(5, seed=5947) op2 = random_diagonal_coulomb_hamiltonian(5, seed=5947) @@ -312,10 +302,8 @@ def test_random_operators_are_reproducible(self): op1 = random_quadratic_hamiltonian(5, seed=17711) op2 = random_quadratic_hamiltonian(5, seed=17711) - numpy.testing.assert_allclose(op1.combined_hermitian_part, - op2.combined_hermitian_part) - numpy.testing.assert_allclose(op1.antisymmetric_part, - op2.antisymmetric_part) + numpy.testing.assert_allclose(op1.combined_hermitian_part, op2.combined_hermitian_part) + numpy.testing.assert_allclose(op1.antisymmetric_part, op2.antisymmetric_part) op1 = random_antisymmetric_matrix(5, seed=24074) op2 = random_antisymmetric_matrix(5, seed=24074) diff --git a/src/openfermion/testing/wrapped.py b/src/openfermion/testing/wrapped.py index 21ccbbcd3..7fd8f2a19 100644 --- a/src/openfermion/testing/wrapped.py +++ b/src/openfermion/testing/wrapped.py @@ -16,14 +16,10 @@ import cirq -_setup_code = ("import cirq\n" - "import numpy as np\n" - "import sympy\n" - "import openfermion\n") +_setup_code = "import cirq\n" "import numpy as np\n" "import sympy\n" "import openfermion\n" -def assert_equivalent_repr(value: Any, *, - setup_code: str = _setup_code) -> None: +def assert_equivalent_repr(value: Any, *, setup_code: str = _setup_code) -> None: """Checks that eval(repr(v)) == v. Args: @@ -36,15 +32,15 @@ def assert_equivalent_repr(value: Any, *, def assert_implements_consistent_protocols( - val: Any, - *, - exponents: Sequence[Any] = (0, 1, -1, 0.5, 0.25, -0.5, 0.1, - sympy.Symbol("s")), - qubit_count: Optional[int] = None, - ignoring_global_phase: bool = False, - setup_code: str = _setup_code, - global_vals: Optional[Dict[str, Any]] = None, - local_vals: Optional[Dict[str, Any]] = None) -> None: + val: Any, + *, + exponents: Sequence[Any] = (0, 1, -1, 0.5, 0.25, -0.5, 0.1, sympy.Symbol("s")), + qubit_count: Optional[int] = None, + ignoring_global_phase: bool = False, + setup_code: str = _setup_code, + global_vals: Optional[Dict[str, Any]] = None, + local_vals: Optional[Dict[str, Any]] = None, +) -> None: """Checks that a value is internally consistent and has a good __repr__.""" try: # Cirq 0.14 diff --git a/src/openfermion/transforms/opconversions/__init__.py b/src/openfermion/transforms/opconversions/__init__.py index 2e2df6d1a..1ed6ab220 100644 --- a/src/openfermion/transforms/opconversions/__init__.py +++ b/src/openfermion/transforms/opconversions/__init__.py @@ -19,12 +19,7 @@ interleaved_code, ) -from .binary_code_transform import ( - binary_code_transform, - extractor, - dissolve, - make_parity_list, -) +from .binary_code_transform import binary_code_transform, extractor, dissolve, make_parity_list from .bksf import ( bravyi_kitaev_fast, @@ -37,16 +32,13 @@ generate_fermions, ) -from .bravyi_kitaev import ( - bravyi_kitaev, - inline_sum, - inline_product, -) +from .bravyi_kitaev import bravyi_kitaev, inline_sum, inline_product from .bravyi_kitaev_tree import bravyi_kitaev_tree from .commutator_diagonal_coulomb_operator import ( - commutator_ordered_diagonal_coulomb_with_two_body_operator,) + commutator_ordered_diagonal_coulomb_with_two_body_operator, +) from .conversions import ( get_fermion_operator, @@ -56,27 +48,14 @@ check_no_sympy, ) -from .fenwick_tree import ( - FenwickNode, - FenwickTree, -) +from .fenwick_tree import FenwickNode, FenwickTree -from .jordan_wigner import ( - jordan_wigner, - jordan_wigner_one_body, - jordan_wigner_two_body, -) +from .jordan_wigner import jordan_wigner, jordan_wigner_one_body, jordan_wigner_two_body from .qubitoperator_to_paulisum import qubit_operator_to_pauli_sum from .reverse_jordan_wigner import reverse_jordan_wigner -from .remove_symmetry_qubits import ( - symmetry_conserving_bravyi_kitaev, - edit_hamiltonian_for_spin, -) +from .remove_symmetry_qubits import symmetry_conserving_bravyi_kitaev, edit_hamiltonian_for_spin -from .verstraete_cirac import ( - verstraete_cirac_2d_square, - vertical_edges_snake, -) +from .verstraete_cirac import verstraete_cirac_2d_square, vertical_edges_snake diff --git a/src/openfermion/transforms/opconversions/binary_code_transform.py b/src/openfermion/transforms/opconversions/binary_code_transform.py index 9a4d26200..02e84110f 100644 --- a/src/openfermion/transforms/opconversions/binary_code_transform.py +++ b/src/openfermion/transforms/opconversions/binary_code_transform.py @@ -16,12 +16,11 @@ import numpy -from openfermion.ops.operators import (BinaryCode, FermionOperator, - QubitOperator, BinaryPolynomial) +from openfermion.ops.operators import BinaryCode, FermionOperator, QubitOperator, BinaryPolynomial def extractor(binary_op: BinaryPolynomial) -> QubitOperator: - """ Applies the extraction superoperator to a binary expression + """Applies the extraction superoperator to a binary expression to obtain the corresponding qubit operators. Args: @@ -63,7 +62,7 @@ def dissolve(term: Tuple[Any, Any]) -> QubitOperator: for var in term: if not isinstance(var, (numpy.int32, numpy.int64, int)): raise ValueError('dissolve only works on integers') - prod *= (QubitOperator((), 0.5) - QubitOperator('Z' + str(var), 0.5)) + prod *= QubitOperator((), 0.5) - QubitOperator('Z' + str(var), 0.5) return QubitOperator((), 1.0) - prod @@ -87,9 +86,8 @@ def make_parity_list(code: BinaryCode) -> List[BinaryPolynomial]: return parity_binaries -def binary_code_transform(hamiltonian: FermionOperator, - code: BinaryCode) -> QubitOperator: - """ Transforms a Hamiltonian written in fermionic basis into a Hamiltonian +def binary_code_transform(hamiltonian: FermionOperator, code: BinaryCode) -> QubitOperator: + """Transforms a Hamiltonian written in fermionic basis into a Hamiltonian written in qubit basis, via a binary code. The role of the binary code is to relate the occupation vectors (v0 v1 v2 @@ -118,20 +116,20 @@ def binary_code_transform(hamiltonian: FermionOperator, a BinaryCode """ if not isinstance(hamiltonian, FermionOperator): - raise TypeError('hamiltonian provided must be a FermionOperator' - 'received {}'.format(type(hamiltonian))) + raise TypeError( + 'hamiltonian provided must be a FermionOperator' 'received {}'.format(type(hamiltonian)) + ) if not isinstance(code, BinaryCode): - raise TypeError('code provided must be a BinaryCode' - 'received {}'.format(type(code))) + raise TypeError('code provided must be a BinaryCode' 'received {}'.format(type(code))) new_hamiltonian = QubitOperator() parity_list = make_parity_list(code) # for each term in hamiltonian for term, term_coefficient in hamiltonian.terms.items(): - """ the updated parity and occupation account for sign changes due - changed occupations mid-way in the term """ + """the updated parity and occupation account for sign changes due + changed occupations mid-way in the term""" updated_parity = 0 # parity sign exponent parity_term = BinaryPolynomial() changed_occupation_vector = [0] * code.n_modes @@ -144,14 +142,12 @@ def binary_code_transform(hamiltonian: FermionOperator, for op_idx, op_tuple in enumerate(reversed(term)): # get count exponent, parity exponent addition fermionic_indices = numpy.append(fermionic_indices, op_tuple[0]) - count = numpy.count_nonzero( - fermionic_indices[:op_idx] == op_tuple[0]) - updated_parity += numpy.count_nonzero( - fermionic_indices[:op_idx] < op_tuple[0]) + count = numpy.count_nonzero(fermionic_indices[:op_idx] == op_tuple[0]) + updated_parity += numpy.count_nonzero(fermionic_indices[:op_idx] < op_tuple[0]) # update term extracted = extractor(code.decoder[op_tuple[0]]) - extracted *= (((-1)**count) * ((-1)**(op_tuple[1])) * 0.5) + extracted *= ((-1) ** count) * ((-1) ** (op_tuple[1])) * 0.5 transformed_term *= QubitOperator((), 0.5) - extracted # update parity term and occupation vector @@ -159,12 +155,11 @@ def binary_code_transform(hamiltonian: FermionOperator, parity_term += parity_list[op_tuple[0]] # the parity sign and parity term - transformed_term *= QubitOperator((), (-1)**updated_parity) + transformed_term *= QubitOperator((), (-1) ** updated_parity) transformed_term *= extractor(parity_term) # the update operator - changed_qubit_vector = numpy.mod( - code.encoder.dot(changed_occupation_vector), 2) + changed_qubit_vector = numpy.mod(code.encoder.dot(changed_occupation_vector), 2) update_operator = QubitOperator(()) for index, q_vec in enumerate(changed_qubit_vector): @@ -172,8 +167,7 @@ def binary_code_transform(hamiltonian: FermionOperator, update_operator *= QubitOperator('X' + str(index)) # append new term to new hamiltonian - new_hamiltonian += term_coefficient * update_operator * \ - transformed_term + new_hamiltonian += term_coefficient * update_operator * transformed_term new_hamiltonian.compress() return new_hamiltonian diff --git a/src/openfermion/transforms/opconversions/binary_code_transform_test.py b/src/openfermion/transforms/opconversions/binary_code_transform_test.py index 1029acf5f..c4553876e 100644 --- a/src/openfermion/transforms/opconversions/binary_code_transform_test.py +++ b/src/openfermion/transforms/opconversions/binary_code_transform_test.py @@ -13,16 +13,17 @@ import unittest from openfermion.ops.operators import BinaryCode, FermionOperator, QubitOperator -from openfermion.transforms.opconversions.binary_code_transform import \ - binary_code_transform, dissolve, make_parity_list +from openfermion.transforms.opconversions.binary_code_transform import ( + binary_code_transform, + dissolve, + make_parity_list, +) class CodeTransformTest(unittest.TestCase): - def test_transform(self): code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 + W1']) - hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator( - '2^ 0', 0.5) + hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator('2^ 0', 0.5) transform = binary_code_transform(hamiltonian, code) correct_op = QubitOperator('X0 Z1', 0.25) + QubitOperator('X0', 0.25) self.assertTrue(transform == correct_op) @@ -34,14 +35,15 @@ def test_transform(self): def test_dissolve(self): code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 W1']) - hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator( - '2^ 0', 0.5) + hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator('2^ 0', 0.5) transform = binary_code_transform(hamiltonian, code) - correct_op = QubitOperator('X0 Z1', 0.375) + \ - QubitOperator('X0', -0.125) + \ - QubitOperator('Y0', -0.125j) + \ - QubitOperator('Y0 Z1', -0.125j) + correct_op = ( + QubitOperator('X0 Z1', 0.375) + + QubitOperator('X0', -0.125) + + QubitOperator('Y0', -0.125j) + + QubitOperator('Y0 Z1', -0.125j) + ) self.assertTrue(transform == correct_op) with self.assertRaises(ValueError): @@ -49,4 +51,4 @@ def test_dissolve(self): def test_make_parity_list_raises(self): with self.assertRaises(TypeError): - make_parity_list('A') \ No newline at end of file + make_parity_list('A') diff --git a/src/openfermion/transforms/opconversions/binary_codes.py b/src/openfermion/transforms/opconversions/binary_codes.py index c725ff1f3..99e75c104 100644 --- a/src/openfermion/transforms/opconversions/binary_codes.py +++ b/src/openfermion/transforms/opconversions/binary_codes.py @@ -19,9 +19,8 @@ from openfermion.ops.operators import BinaryCode, BinaryPolynomial -def linearize_decoder(matrix: Union[numpy.ndarray, list] - ) -> List[BinaryPolynomial]: - """ Outputs linear decoding function from input matrix +def linearize_decoder(matrix: Union[numpy.ndarray, list]) -> List[BinaryPolynomial]: + """Outputs linear decoding function from input matrix Args: matrix (np.ndarray or list): list of lists or 2D numpy array @@ -43,7 +42,7 @@ def linearize_decoder(matrix: Union[numpy.ndarray, list] def _encoder_bk(n_modes: int) -> numpy.ndarray: - """ Helper function for bravyi_kitaev_code that outputs the binary-tree + """Helper function for bravyi_kitaev_code that outputs the binary-tree (dimension x dimension)-matrix used for the encoder in the Bravyi-Kitaev transform. @@ -57,12 +56,12 @@ def _encoder_bk(n_modes: int) -> numpy.ndarray: for repetition in numpy.arange(1, reps + 1): mtx = numpy.kron(numpy.eye(2, dtype=int), mtx) for column in numpy.arange(0, 2**repetition): - mtx[2**(repetition + 1) - 1, column] = 1 + mtx[2 ** (repetition + 1) - 1, column] = 1 return mtx[0:n_modes, 0:n_modes] def _decoder_bk(n_modes: int) -> numpy.ndarray: - """ Helper function for bravyi_kitaev_code that outputs the inverse of the + """Helper function for bravyi_kitaev_code that outputs the inverse of the binary tree matrix utilized for decoder in the Bravyi-Kitaev transform. Args: @@ -74,12 +73,12 @@ def _decoder_bk(n_modes: int) -> numpy.ndarray: mtx = numpy.array([[1, 0], [1, 1]]) for repetition in numpy.arange(1, reps + 1): mtx = numpy.kron(numpy.eye(2, dtype=int), mtx) - mtx[2**(repetition + 1) - 1, 2**repetition - 1] = 1 + mtx[2 ** (repetition + 1) - 1, 2**repetition - 1] = 1 return mtx[0:n_modes, 0:n_modes] def _encoder_checksum(modes: int) -> numpy.ndarray: - """ Helper function for checksum_code that outputs the encoder matrix. + """Helper function for checksum_code that outputs the encoder matrix. Args: modes (int): matrix size is (modes - 1) x modes @@ -92,9 +91,8 @@ def _encoder_checksum(modes: int) -> numpy.ndarray: return enc -def _decoder_checksum(modes: int, - odd: Union[int, bool]) -> List[BinaryPolynomial]: - """ Helper function for checksum_code that outputs the decoder. +def _decoder_checksum(modes: int, odd: Union[int, bool]) -> List[BinaryPolynomial]: + """Helper function for checksum_code that outputs the decoder. Args: modes (int): number of modes @@ -117,9 +115,8 @@ def _decoder_checksum(modes: int, return djw -def _binary_address(digits: int, - address: int) -> Tuple[List[int], BinaryPolynomial]: - """ Helper function to fill in an encoder column/decoder component of a +def _binary_address(digits: int, address: int) -> Tuple[List[int], BinaryPolynomial]: + """Helper function to fill in an encoder column/decoder component of a certain number. Args: @@ -134,14 +131,13 @@ def _binary_address(digits: int, address = bin(address)[2:] address = ('0' * (digits - len(address))) + address for index in numpy.arange(digits): - binary_expression *= BinaryPolynomial('w' + str(index) + ' + 1 + ' + - address[index]) + binary_expression *= BinaryPolynomial('w' + str(index) + ' + 1 + ' + address[index]) return list(map(int, list(address))), binary_expression def checksum_code(n_modes: int, odd: Union[int, bool]) -> BinaryCode: - """ Checksum code for either even or odd Hamming weight. The Hamming weight + """Checksum code for either even or odd Hamming weight. The Hamming weight is defined such that it yields the total occupation number for a given basis state. A Checksum code with odd weight will encode all states with odd occupation number. This code saves one qubit: n_qubits = n_modes - 1. @@ -153,24 +149,24 @@ def checksum_code(n_modes: int, odd: Union[int, bool]) -> BinaryCode: Returns (BinaryCode): The checksum BinaryCode """ - return BinaryCode(_encoder_checksum(n_modes), - _decoder_checksum(n_modes, odd)) + return BinaryCode(_encoder_checksum(n_modes), _decoder_checksum(n_modes, odd)) def jordan_wigner_code(n_modes: int) -> BinaryCode: - """ The Jordan-Wigner transform as binary code. + """The Jordan-Wigner transform as binary code. Args: n_modes (int): number of modes Returns (BinaryCode): The Jordan-Wigner BinaryCode """ - return BinaryCode(numpy.identity(n_modes, dtype=int), - linearize_decoder(numpy.identity(n_modes, dtype=int))) + return BinaryCode( + numpy.identity(n_modes, dtype=int), linearize_decoder(numpy.identity(n_modes, dtype=int)) + ) def bravyi_kitaev_code(n_modes: int) -> BinaryCode: - """ The Bravyi-Kitaev transform as binary code. The implementation + """The Bravyi-Kitaev transform as binary code. The implementation follows arXiv:1208.5986. Args: @@ -178,12 +174,11 @@ def bravyi_kitaev_code(n_modes: int) -> BinaryCode: Returns (BinaryCode): The Bravyi-Kitaev BinaryCode """ - return BinaryCode(_encoder_bk(n_modes), - linearize_decoder(_decoder_bk(n_modes))) + return BinaryCode(_encoder_bk(n_modes), linearize_decoder(_decoder_bk(n_modes))) def parity_code(n_modes: int) -> BinaryCode: - """ The parity transform (arXiv:1208.5986) as binary code. This code is + """The parity transform (arXiv:1208.5986) as binary code. This code is very similar to the Jordan-Wigner transform, but with long update strings instead of parity strings. It does not save qubits: n_qubits = n_modes. @@ -193,15 +188,16 @@ def parity_code(n_modes: int) -> BinaryCode: Returns (BinaryCode): The parity transform BinaryCode """ dec_mtx = numpy.reshape( - ([1] + [0] * (n_modes - 1)) + ([1, 1] + (n_modes - 1) * [0]) * - (n_modes - 2) + [1, 1], (n_modes, n_modes)) + ([1] + [0] * (n_modes - 1)) + ([1, 1] + (n_modes - 1) * [0]) * (n_modes - 2) + [1, 1], + (n_modes, n_modes), + ) enc_mtx = numpy.tril(numpy.ones((n_modes, n_modes), dtype=int)) return BinaryCode(enc_mtx, linearize_decoder(dec_mtx)) def weight_one_binary_addressing_code(exponent: int) -> BinaryCode: - """ Weight-1 binary addressing code (arXiv:1712.07067). This highly + """Weight-1 binary addressing code (arXiv:1712.07067). This highly non-linear code works for a number of modes that is an integer power of two. It encodes all possible vectors with Hamming weight 1, which corresponds to all states with total occupation one. The amount of @@ -219,13 +215,12 @@ def weight_one_binary_addressing_code(exponent: int) -> BinaryCode: encoder = numpy.zeros((exponent, 2**exponent), dtype=int) decoder = [0] * (2**exponent) for counter in numpy.arange(2**exponent): - encoder[:, counter], decoder[counter] = \ - _binary_address(exponent, counter) + encoder[:, counter], decoder[counter] = _binary_address(exponent, counter) return BinaryCode(encoder, decoder) def weight_one_segment_code() -> BinaryCode: - """ Weight-1 segment code (arXiv:1712.07067). Outputs a 3-mode, 2-qubit + """Weight-1 segment code (arXiv:1712.07067). Outputs a 3-mode, 2-qubit code, which encodes all the vectors (states) with Hamming weight (occupation) 0 and 1. n_qubits = 2, n_modes = 3. A linear amount of qubits can be saved appending several instances of this @@ -236,12 +231,11 @@ def weight_one_segment_code() -> BinaryCode: Returns (BinaryCode): weight one segment code """ - return BinaryCode([[1, 0, 1], [0, 1, 1]], - ['w0 w1 + w0', 'w0 w1 + w1', ' w0 w1']) + return BinaryCode([[1, 0, 1], [0, 1, 1]], ['w0 w1 + w0', 'w0 w1 + w1', ' w0 w1']) def weight_two_segment_code() -> BinaryCode: - """ Weight-2 segment code (arXiv:1712.07067). Outputs a 5-mode, 4-qubit + """Weight-2 segment code (arXiv:1712.07067). Outputs a 5-mode, 4-qubit code, which encodes all the vectors (states) with Hamming weight (occupation) 2 and 1. n_qubits = 4, n_modes = 5. A linear amount of qubits can be saved appending several instances of this @@ -252,18 +246,16 @@ def weight_two_segment_code() -> BinaryCode: Returns (BinaryCode): weight-2 segment code """ - switch = ('w0 w1 w2 + w0 w1 w3 + w0 w2 w3 + w1 w2 w3 + w0 w1 w2 +' - ' w0 w1 w2 w3') + switch = 'w0 w1 w2 + w0 w1 w3 + w0 w2 w3 + w1 w2 w3 + w0 w1 w2 +' ' w0 w1 w2 w3' return BinaryCode( - [[1, 0, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 1, 0, 1], [0, 0, 0, 1, 1]], [ - 'w0 + ' + switch, 'w1 + ' + switch, 'w2 + ' + switch, - 'w3 + ' + switch, switch - ]) + [[1, 0, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 1, 0, 1], [0, 0, 0, 1, 1]], + ['w0 + ' + switch, 'w1 + ' + switch, 'w2 + ' + switch, 'w3 + ' + switch, switch], + ) def interleaved_code(modes: int) -> BinaryCode: - """ Linear code that reorders orbitals from even-odd to up-then-down. + """Linear code that reorders orbitals from even-odd to up-then-down. In up-then-down convention, one can append two instances of the same code 'c' in order to have two symmetric subcodes that are symmetric for spin-up and -down modes: ' c + c '. diff --git a/src/openfermion/transforms/opconversions/binary_codes_test.py b/src/openfermion/transforms/opconversions/binary_codes_test.py index 075563527..9487f07aa 100644 --- a/src/openfermion/transforms/opconversions/binary_codes_test.py +++ b/src/openfermion/transforms/opconversions/binary_codes_test.py @@ -14,60 +14,67 @@ from openfermion.chem import MolecularData from openfermion.ops.operators import FermionOperator, QubitOperator -from openfermion.transforms.opconversions import (binary_code_transform, - bravyi_kitaev, - get_fermion_operator, - jordan_wigner) +from openfermion.transforms.opconversions import ( + binary_code_transform, + bravyi_kitaev, + get_fermion_operator, + jordan_wigner, +) from openfermion.linalg import eigenspectrum from openfermion.transforms.opconversions.binary_codes import ( - bravyi_kitaev_code, checksum_code, interleaved_code, jordan_wigner_code, - parity_code, weight_one_binary_addressing_code, weight_one_segment_code, - weight_two_segment_code, linearize_decoder) + bravyi_kitaev_code, + checksum_code, + interleaved_code, + jordan_wigner_code, + parity_code, + weight_one_binary_addressing_code, + weight_one_segment_code, + weight_two_segment_code, + linearize_decoder, +) def lih_hamiltonian(): - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] active_space_start = 1 active_space_stop = 3 molecule = MolecularData(geometry, 'sto-3g', 1, description="1.45") molecule.load() molecular_hamiltonian = molecule.get_molecular_hamiltonian( occupied_indices=range(active_space_start), - active_indices=range(active_space_start, active_space_stop)) + active_indices=range(active_space_start, active_space_stop), + ) hamiltonian = get_fermion_operator(molecular_hamiltonian) ground_state_energy = eigenspectrum(hamiltonian)[0] return hamiltonian, ground_state_energy class LinearizeDecoderTest(unittest.TestCase): - def test_linearize(self): a = linearize_decoder([[0, 1, 1], [1, 0, 0]]) self.assertListEqual([str(a[0]), str(a[1])], ['[W1] + [W2]', '[W0]']) class CodeTransformTest(unittest.TestCase): - def test_tranform_function(self): ferm_op = FermionOperator('2') n_modes = 5 qubit_op = binary_code_transform(ferm_op, parity_code(n_modes)) - correct_op = QubitOperator(((1, 'Z'), (2, 'X'), (3, 'X'), (4, 'X')), - 0.5) + \ - QubitOperator(((2, 'Y'), (3, 'X'), (4, 'X')), 0.5j) + correct_op = QubitOperator(((1, 'Z'), (2, 'X'), (3, 'X'), (4, 'X')), 0.5) + QubitOperator( + ((2, 'Y'), (3, 'X'), (4, 'X')), 0.5j + ) self.assertTrue(qubit_op == correct_op) ferm_op = FermionOperator('2^') n_modes = 5 qubit_op = binary_code_transform(ferm_op, parity_code(n_modes)) - correct_op = QubitOperator(((1, 'Z'), (2, 'X'), (3, 'X'), (4, 'X')), - 0.5) \ - + QubitOperator(((2, 'Y'), (3, 'X'), (4, 'X')), -0.5j) + correct_op = QubitOperator(((1, 'Z'), (2, 'X'), (3, 'X'), (4, 'X')), 0.5) + QubitOperator( + ((2, 'Y'), (3, 'X'), (4, 'X')), -0.5j + ) self.assertTrue(qubit_op == correct_op) ferm_op = FermionOperator('5^') - op2 = QubitOperator('Z0 Z1 Z2 Z3 Z4 X5', 0.5) \ - - QubitOperator('Z0 Z1 Z2 Z3 Z4 Y5', 0.5j) + op2 = QubitOperator('Z0 Z1 Z2 Z3 Z4 X5', 0.5) - QubitOperator('Z0 Z1 Z2 Z3 Z4 Y5', 0.5j) op1 = binary_code_transform(ferm_op, jordan_wigner_code(6)) self.assertTrue(op1 == op2) @@ -82,8 +89,7 @@ def test_jordan_wigner(self): code = jordan_wigner_code(4) qubit_hamiltonian = binary_code_transform(hamiltonian, code) self.assertAlmostEqual(gs_energy, eigenspectrum(qubit_hamiltonian)[0]) - self.assertDictEqual(qubit_hamiltonian.terms, - jordan_wigner(hamiltonian).terms) + self.assertDictEqual(qubit_hamiltonian.terms, jordan_wigner(hamiltonian).terms) def test_bravyi_kitaev(self): hamiltonian, gs_energy = lih_hamiltonian() diff --git a/src/openfermion/transforms/opconversions/bksf.py b/src/openfermion/transforms/opconversions/bksf.py index 32166275c..00c090baf 100644 --- a/src/openfermion/transforms/opconversions/bksf.py +++ b/src/openfermion/transforms/opconversions/bksf.py @@ -45,8 +45,7 @@ def bravyi_kitaev_fast(operator: InteractionOperator) -> QubitOperator: raise TypeError("operator must be an InteractionOperator.") -def bravyi_kitaev_fast_interaction_op(iop: InteractionOperator - ) -> QubitOperator: +def bravyi_kitaev_fast_interaction_op(iop: InteractionOperator) -> QubitOperator: r""" Transform from InteractionOperator to QubitOperator for Bravyi-Kitaev fast algorithm. @@ -86,16 +85,15 @@ def bravyi_kitaev_fast_interaction_op(iop: InteractionOperator qubit_operator = QubitOperator((), iop.constant) edge_matrix = bravyi_kitaev_fast_edge_matrix(iop) edge_matrix_indices = numpy.array( - numpy.nonzero( - numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) + numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix))) + ) # Loop through all indices. for p in range(n_qubits): for q in range(n_qubits): # Handle one-body terms. coefficient = complex(iop[(p, 1), (q, 0)]) if coefficient and p >= q: - qubit_operator += (coefficient * - _one_body(edge_matrix_indices, p, q)) + qubit_operator += coefficient * _one_body(edge_matrix_indices, p, q) # Keep looping for the two-body terms. for r in range(n_qubits): @@ -114,9 +112,8 @@ def bravyi_kitaev_fast_interaction_op(iop: InteractionOperator continue # Handle case of 3 unique indices elif len(set([p, q, r, s])) == 3: - transformed_term = _two_body( - edge_matrix_indices, p, q, r, s) - transformed_term *= .5 * coefficient + transformed_term = _two_body(edge_matrix_indices, p, q, r, s) + transformed_term *= 0.5 * coefficient qubit_operator += transformed_term continue elif p != r and q < p: @@ -124,16 +121,15 @@ def bravyi_kitaev_fast_interaction_op(iop: InteractionOperator continue # pragma: no cover # Handle the two-body terms. - transformed_term = _two_body(edge_matrix_indices, p, q, r, - s) + transformed_term = _two_body(edge_matrix_indices, p, q, r, s) transformed_term *= coefficient qubit_operator += transformed_term return qubit_operator -def bravyi_kitaev_fast_edge_matrix(iop: InteractionOperator, - n_qubits: Optional[int] = None - ) -> numpy.ndarray: +def bravyi_kitaev_fast_edge_matrix( + iop: InteractionOperator, n_qubits: Optional[int] = None +) -> numpy.ndarray: """ Use InteractionOperator to construct edge matrix required for the algorithm @@ -153,7 +149,6 @@ def bravyi_kitaev_fast_edge_matrix(iop: InteractionOperator, # Loop through all indices. for p in range(n_qubits): for q in range(n_qubits): - # Handle one-body terms. coefficient = complex(iop[(p, 1), (q, 0)]) if coefficient and p >= q: @@ -179,40 +174,31 @@ def bravyi_kitaev_fast_edge_matrix(iop: InteractionOperator, # Handle case of four unique indices. if len(set([p, q, r, s])) == 4: - if coefficient2 and p >= q: - edge_matrix[p, q] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[p, q] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) a, b = sorted([r, s]) - edge_matrix[b, a] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[b, a] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) # Handle case of three unique indices. elif len(set([p, q, r, s])) == 3: - # Identify equal tensor factors. if p == r: a, b = sorted([q, s]) - edge_matrix[b, a] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[b, a] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) elif p == s: a, b = sorted([q, r]) - edge_matrix[b, a] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[b, a] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) elif q == r: a, b = sorted([p, s]) - edge_matrix[b, a] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[b, a] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) elif q == s: a, b = sorted([p, r]) - edge_matrix[b, a] = bool( - complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) + edge_matrix[b, a] = bool(complex(iop[(p, 1), (q, 1), (r, 0), (s, 0)])) return edge_matrix.transpose() -def _one_body(edge_matrix_indices: numpy.ndarray, p: int, - q: int) -> QubitOperator: +def _one_body(edge_matrix_indices: numpy.ndarray, p: int, q: int) -> QubitOperator: r""" Map the term a^\dagger_p a_q + a^\dagger_q a_p to QubitOperator. @@ -232,18 +218,17 @@ def _one_body(edge_matrix_indices: numpy.ndarray, p: int, B_a = edge_operator_b(edge_matrix_indices, a) B_b = edge_operator_b(edge_matrix_indices, b) A_ab = edge_operator_aij(edge_matrix_indices, a, b) - qubit_operator += -1j / 2. * (A_ab * B_b + B_a * A_ab) + qubit_operator += -1j / 2.0 * (A_ab * B_b + B_a * A_ab) # Handle diagonal terms. else: B_p = edge_operator_b(edge_matrix_indices, p) - qubit_operator += (QubitOperator((), 1) - B_p) / 2. + qubit_operator += (QubitOperator((), 1) - B_p) / 2.0 return qubit_operator -def _two_body(edge_matrix_indices: numpy.ndarray, p: int, q: int, r: int, - s: int) -> QubitOperator: +def _two_body(edge_matrix_indices: numpy.ndarray, p: int, q: int, r: int, s: int) -> QubitOperator: r""" Map the term a^\dagger_p a^\dagger_q a_r a_s + h.c. to QubitOperator. @@ -267,10 +252,22 @@ def _two_body(edge_matrix_indices: numpy.ndarray, p: int, q: int, r: int, B_s = edge_operator_b(edge_matrix_indices, s) A_pq = edge_operator_aij(edge_matrix_indices, p, q) A_rs = edge_operator_aij(edge_matrix_indices, r, s) - qubit_operator += 1 / 8. * A_pq * A_rs * (-QubitOperator( - ()) - B_p * B_q + B_p * B_r + B_p * B_s + B_q * B_r + B_q * B_s - - B_r * B_s + - B_p * B_q * B_r * B_s) + qubit_operator += ( + 1 + / 8.0 + * A_pq + * A_rs + * ( + -QubitOperator(()) + - B_p * B_q + + B_p * B_r + + B_p * B_s + + B_q * B_r + + B_q * B_s + - B_r * B_s + + B_p * B_q * B_r * B_s + ) + ) return qubit_operator # Handle case of three unique indices. @@ -281,32 +278,28 @@ def _two_body(edge_matrix_indices: numpy.ndarray, p: int, q: int, r: int, B_q = edge_operator_b(edge_matrix_indices, q) B_s = edge_operator_b(edge_matrix_indices, s) A_qs = edge_operator_aij(edge_matrix_indices, q, s) - qubit_operator += 1j * (A_qs * B_s + B_q * A_qs) * (QubitOperator( - ()) - B_p) / 4. + qubit_operator += 1j * (A_qs * B_s + B_q * A_qs) * (QubitOperator(()) - B_p) / 4.0 elif p == s: B_p = edge_operator_b(edge_matrix_indices, p) B_q = edge_operator_b(edge_matrix_indices, q) B_r = edge_operator_b(edge_matrix_indices, r) A_qr = edge_operator_aij(edge_matrix_indices, q, r) - qubit_operator += -1j * (A_qr * B_r + B_q * A_qr) * (QubitOperator( - ()) - B_p) / 4. + qubit_operator += -1j * (A_qr * B_r + B_q * A_qr) * (QubitOperator(()) - B_p) / 4.0 elif q == r: B_p = edge_operator_b(edge_matrix_indices, p) B_q = edge_operator_b(edge_matrix_indices, q) B_s = edge_operator_b(edge_matrix_indices, s) A_ps = edge_operator_aij(edge_matrix_indices, p, s) - qubit_operator += -1j * (A_ps * B_s + B_p * A_ps) * (QubitOperator( - ()) - B_q) / 4. + qubit_operator += -1j * (A_ps * B_s + B_p * A_ps) * (QubitOperator(()) - B_q) / 4.0 elif q == s: B_p = edge_operator_b(edge_matrix_indices, p) B_q = edge_operator_b(edge_matrix_indices, q) B_r = edge_operator_b(edge_matrix_indices, r) A_pr = edge_operator_aij(edge_matrix_indices, p, r) - qubit_operator += 1j * (A_pr * B_r + B_p * A_pr) * (QubitOperator( - ()) - B_q) / 4. + qubit_operator += 1j * (A_pr * B_r + B_p * A_pr) * (QubitOperator(()) - B_q) / 4.0 # Handle case of two unique indices. elif len(set([p, q, r, s])) == 2: @@ -314,20 +307,17 @@ def _two_body(edge_matrix_indices: numpy.ndarray, p: int, q: int, r: int, if p == s: B_p = edge_operator_b(edge_matrix_indices, p) B_q = edge_operator_b(edge_matrix_indices, q) - qubit_operator += (QubitOperator(()) - B_p) * (QubitOperator( - ()) - B_q) / 4. + qubit_operator += (QubitOperator(()) - B_p) * (QubitOperator(()) - B_q) / 4.0 else: B_p = edge_operator_b(edge_matrix_indices, p) B_q = edge_operator_b(edge_matrix_indices, q) - qubit_operator += -(QubitOperator(()) - B_p) * (QubitOperator( - ()) - B_q) / 4. + qubit_operator += -(QubitOperator(()) - B_p) * (QubitOperator(()) - B_q) / 4.0 return qubit_operator -def edge_operator_b(edge_matrix_indices: numpy.ndarray, - i: int) -> QubitOperator: +def edge_operator_b(edge_matrix_indices: numpy.ndarray, i: int) -> QubitOperator: """Calculate the edge operator B_i. The definitions used here are consistent with arXiv:quant-ph/0003137 @@ -349,8 +339,7 @@ def edge_operator_b(edge_matrix_indices: numpy.ndarray, return B_i -def edge_operator_aij(edge_matrix_indices: numpy.ndarray, i: int, - j: int) -> QubitOperator: +def edge_operator_aij(edge_matrix_indices: numpy.ndarray, i: int, j: int) -> QubitOperator: """Calculate the edge operator A_ij. The definitions used here are consistent with arXiv:quant-ph/0003137 @@ -371,13 +360,21 @@ def edge_operator_aij(edge_matrix_indices: numpy.ndarray, i: int, operator += ((int(position_ij), 'X'),) for edge_index in range(numpy.size(qubit_position_i[0, :])): - if edge_matrix_indices[int(not (qubit_position_i[0, edge_index]))][ - qubit_position_i[1, edge_index]] < j: + if ( + edge_matrix_indices[int(not (qubit_position_i[0, edge_index]))][ + qubit_position_i[1, edge_index] + ] + < j + ): operator += ((int(qubit_position_i[1, edge_index]), 'Z'),) qubit_position_j = numpy.array(numpy.where(edge_matrix_indices == j)) for edge_index in range(numpy.size(qubit_position_j[0, :])): - if edge_matrix_indices[int(not (qubit_position_j[0, edge_index]))][ - qubit_position_j[1, edge_index]] < i: + if ( + edge_matrix_indices[int(not (qubit_position_j[0, edge_index]))][ + qubit_position_j[1, edge_index] + ] + < i + ): operator += ((int(qubit_position_j[1, edge_index]), 'Z'),) a_ij += QubitOperator(operator, 1) if j < i: @@ -401,16 +398,13 @@ def vacuum_operator(edge_matrix_indices: numpy.ndarray) -> QubitOperator: stabs = numpy.array(networkx.cycle_basis(g)) vac_operator = QubitOperator(()) for stab in stabs: - A = QubitOperator(()) stab = numpy.array(stab) for i in range(numpy.size(stab)): if i == (numpy.size(stab) - 1): - A = (1j) * A * edge_operator_aij(edge_matrix_indices, stab[i], - stab[0]) + A = (1j) * A * edge_operator_aij(edge_matrix_indices, stab[i], stab[0]) else: - A = (1j) * A * edge_operator_aij(edge_matrix_indices, stab[i], - stab[i + 1]) + A = (1j) * A * edge_operator_aij(edge_matrix_indices, stab[i], stab[i + 1]) vac_operator = vac_operator * (QubitOperator(()) + A) / numpy.sqrt(2) return vac_operator @@ -428,27 +422,26 @@ def number_operator(iop, mode_number=None): Return: A QubitOperator - """ + """ n_qubit = iop.n_qubits num_operator = QubitOperator() edge_matrix = bravyi_kitaev_fast_edge_matrix(iop) edge_matrix_indices = numpy.array( - numpy.nonzero( - numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) + numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix))) + ) if mode_number is None: for i in range(n_qubit): - num_operator += (QubitOperator( - ()) - edge_operator_b(edge_matrix_indices, i)) / 2. + num_operator += (QubitOperator(()) - edge_operator_b(edge_matrix_indices, i)) / 2.0 else: - num_operator += (QubitOperator( - ()) - edge_operator_b(edge_matrix_indices, mode_number)) / 2. + num_operator += ( + QubitOperator(()) - edge_operator_b(edge_matrix_indices, mode_number) + ) / 2.0 return num_operator -def generate_fermions(edge_matrix_indices: numpy.ndarray, i: int, - j: int) -> QubitOperator: +def generate_fermions(edge_matrix_indices: numpy.ndarray, i: int, j: int) -> QubitOperator: """The QubitOperator for generating fermions in bravyi_kitaev_fast representation @@ -459,9 +452,8 @@ def generate_fermions(edge_matrix_indices: numpy.ndarray, i: int, Return: A QubitOperator """ - gen_fer_operator = (-1j / - 2.) * (edge_operator_aij(edge_matrix_indices, i, j) * - edge_operator_b(edge_matrix_indices, j) - - edge_operator_b(edge_matrix_indices, i) * - edge_operator_aij(edge_matrix_indices, i, j)) + gen_fer_operator = (-1j / 2.0) * ( + edge_operator_aij(edge_matrix_indices, i, j) * edge_operator_b(edge_matrix_indices, j) + - edge_operator_b(edge_matrix_indices, i) * edge_operator_aij(edge_matrix_indices, i, j) + ) return gen_fer_operator diff --git a/src/openfermion/transforms/opconversions/bksf_test.py b/src/openfermion/transforms/opconversions/bksf_test.py index 14d5d6cc5..d4673b8eb 100644 --- a/src/openfermion/transforms/opconversions/bksf_test.py +++ b/src/openfermion/transforms/opconversions/bksf_test.py @@ -20,25 +20,19 @@ from openfermion.chem import MolecularData from openfermion.ops.operators import FermionOperator, QubitOperator from openfermion.ops.representations import InteractionOperator -from openfermion.transforms.opconversions import (bksf, get_fermion_operator, - normal_ordered) -from openfermion.transforms.opconversions.jordan_wigner import ( - jordan_wigner, jordan_wigner_one_body) +from openfermion.transforms.opconversions import bksf, get_fermion_operator, normal_ordered +from openfermion.transforms.opconversions.jordan_wigner import jordan_wigner, jordan_wigner_one_body from openfermion.linalg import get_sparse_operator, eigenspectrum from openfermion.utils import count_qubits class bravyi_kitaev_fastTransformTest(unittest.TestCase): - def setUp(self): - geometry = [('H', (0., 0., 0.)), ('H', (0., 0., 0.7414))] + geometry = [('H', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 0.7414))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414') - self.molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + self.molecule = MolecularData(geometry, basis, multiplicity, filename=filename) self.molecule.load() # Get molecular Hamiltonian. @@ -52,15 +46,13 @@ def setUp(self): self.two_body = self.molecular_hamiltonian.two_body_tensor # Get fermion Hamiltonian. - self.fermion_hamiltonian = normal_ordered( - get_fermion_operator(self.molecular_hamiltonian)) + self.fermion_hamiltonian = normal_ordered(get_fermion_operator(self.molecular_hamiltonian)) # Get qubit Hamiltonian. self.qubit_hamiltonian = jordan_wigner(self.fermion_hamiltonian) # Get the sparse matrix. - self.hamiltonian_matrix = get_sparse_operator( - self.molecular_hamiltonian) + self.hamiltonian_matrix = get_sparse_operator(self.molecular_hamiltonian) def test_bad_input(self): with self.assertRaises(TypeError): @@ -70,8 +62,8 @@ def test_bravyi_kitaev_fast_edgeoperator_Bi(self): # checking the edge operators edge_matrix = numpy.triu(numpy.ones((4, 4))) edge_matrix_indices = numpy.array( - numpy.nonzero( - numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) + numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix))) + ) correct_operators_b0 = ((0, 'Z'), (1, 'Z'), (2, 'Z')) correct_operators_b1 = ((0, 'Z'), (3, 'Z'), (4, 'Z')) @@ -82,28 +74,23 @@ def test_bravyi_kitaev_fast_edgeoperator_Bi(self): qterm_b1 = QubitOperator(correct_operators_b1, 1) qterm_b2 = QubitOperator(correct_operators_b2, 1) qterm_b3 = QubitOperator(correct_operators_b3, 1) - self.assertTrue( - qterm_b0 == bksf.edge_operator_b(edge_matrix_indices, 0)) - self.assertTrue( - qterm_b1 == bksf.edge_operator_b(edge_matrix_indices, 1)) - self.assertTrue( - qterm_b2 == bksf.edge_operator_b(edge_matrix_indices, 2)) - self.assertTrue( - qterm_b3 == bksf.edge_operator_b(edge_matrix_indices, 3)) + self.assertTrue(qterm_b0 == bksf.edge_operator_b(edge_matrix_indices, 0)) + self.assertTrue(qterm_b1 == bksf.edge_operator_b(edge_matrix_indices, 1)) + self.assertTrue(qterm_b2 == bksf.edge_operator_b(edge_matrix_indices, 2)) + self.assertTrue(qterm_b3 == bksf.edge_operator_b(edge_matrix_indices, 3)) def test_bravyi_kitaev_fast_edgeoperator_Aij(self): # checking the edge operators edge_matrix = numpy.triu(numpy.ones((4, 4))) edge_matrix_indices = numpy.array( - numpy.nonzero( - numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) + numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix))) + ) correct_operators_a01 = ((0, 'X'),) correct_operators_a02 = ((0, 'Z'), (1, 'X')) correct_operators_a03 = ((0, 'Z'), (1, 'Z'), (2, 'X')) correct_operators_a12 = ((0, 'Z'), (1, 'Z'), (3, 'X')) correct_operators_a13 = ((0, 'Z'), (2, 'Z'), (3, 'Z'), (4, 'X')) - correct_operators_a23 = ((1, 'Z'), (2, 'Z'), (3, 'Z'), (4, 'Z'), (5, - 'X')) + correct_operators_a23 = ((1, 'Z'), (2, 'Z'), (3, 'Z'), (4, 'Z'), (5, 'X')) qterm_a01 = QubitOperator(correct_operators_a01, 1) qterm_a02 = QubitOperator(correct_operators_a02, 1) @@ -112,18 +99,12 @@ def test_bravyi_kitaev_fast_edgeoperator_Aij(self): qterm_a13 = QubitOperator(correct_operators_a13, 1) qterm_a23 = QubitOperator(correct_operators_a23, 1) - self.assertTrue( - qterm_a01 == bksf.edge_operator_aij(edge_matrix_indices, 0, 1)) - self.assertTrue( - qterm_a02 == bksf.edge_operator_aij(edge_matrix_indices, 0, 2)) - self.assertTrue( - qterm_a03 == bksf.edge_operator_aij(edge_matrix_indices, 0, 3)) - self.assertTrue( - qterm_a12 == bksf.edge_operator_aij(edge_matrix_indices, 1, 2)) - self.assertTrue( - qterm_a13 == bksf.edge_operator_aij(edge_matrix_indices, 1, 3)) - self.assertTrue( - qterm_a23 == bksf.edge_operator_aij(edge_matrix_indices, 2, 3)) + self.assertTrue(qterm_a01 == bksf.edge_operator_aij(edge_matrix_indices, 0, 1)) + self.assertTrue(qterm_a02 == bksf.edge_operator_aij(edge_matrix_indices, 0, 2)) + self.assertTrue(qterm_a03 == bksf.edge_operator_aij(edge_matrix_indices, 0, 3)) + self.assertTrue(qterm_a12 == bksf.edge_operator_aij(edge_matrix_indices, 1, 2)) + self.assertTrue(qterm_a13 == bksf.edge_operator_aij(edge_matrix_indices, 1, 3)) + self.assertTrue(qterm_a23 == bksf.edge_operator_aij(edge_matrix_indices, 2, 3)) def test_bravyi_kitaev_fast_jw_number_operator(self): # bksf algorithm allows for even number of particles. So, compare the @@ -139,19 +120,15 @@ def test_bravyi_kitaev_fast_jw_number_operator(self): bravyi_kitaev_fast_eig_spec = eigenspectrum(bravyi_kitaev_fast_n) evensector = 0 for i in range(numpy.size(jw_eig_spec)): - if bool( - numpy.size( - numpy.where( - jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): + if bool(numpy.size(numpy.where(jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): evensector += 1 - self.assertEqual(evensector, 2**(n_qubits - 1)) + self.assertEqual(evensector, 2 ** (n_qubits - 1)) def test_bravyi_kitaev_fast_jw_hamiltonian(self): # make sure half of the jordan-wigner Hamiltonian eigenspectrum can # be found in bksf Hamiltonian eigenspectrum. n_qubits = count_qubits(self.molecular_hamiltonian) - bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast( - self.molecular_hamiltonian) + bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast(self.molecular_hamiltonian) jw_H = jordan_wigner(self.molecular_hamiltonian) bravyi_kitaev_fast_H_eig = eigenspectrum(bravyi_kitaev_fast_H) jw_H_eig = eigenspectrum(jw_H) @@ -159,41 +136,34 @@ def test_bravyi_kitaev_fast_jw_hamiltonian(self): jw_H_eig = jw_H_eig.round(5) evensector = 0 for i in range(numpy.size(jw_H_eig)): - if bool( - numpy.size( - numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): + if bool(numpy.size(numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): evensector += 1 - self.assertEqual(evensector, 2**(n_qubits - 1)) + self.assertEqual(evensector, 2 ** (n_qubits - 1)) def test_bravyi_kitaev_fast_generate_fermions(self): n_qubits = count_qubits(self.molecular_hamiltonian) # test for generating two fermions - edge_matrix = bksf.bravyi_kitaev_fast_edge_matrix( - self.molecular_hamiltonian) + edge_matrix = bksf.bravyi_kitaev_fast_edge_matrix(self.molecular_hamiltonian) edge_matrix_indices = numpy.array( - numpy.nonzero( - numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix)))) - fermion_generation_operator = bksf.generate_fermions( - edge_matrix_indices, 2, 3) - fermion_generation_sp_matrix = get_sparse_operator( - fermion_generation_operator) + numpy.nonzero(numpy.triu(edge_matrix) - numpy.diag(numpy.diag(edge_matrix))) + ) + fermion_generation_operator = bksf.generate_fermions(edge_matrix_indices, 2, 3) + fermion_generation_sp_matrix = get_sparse_operator(fermion_generation_operator) fermion_generation_matrix = fermion_generation_sp_matrix.toarray() bksf_vacuum_state_operator = bksf.vacuum_operator(edge_matrix_indices) - bksf_vacuum_state_sp_matrix = get_sparse_operator( - bksf_vacuum_state_operator) + bksf_vacuum_state_sp_matrix = get_sparse_operator(bksf_vacuum_state_operator) bksf_vacuum_state_matrix = bksf_vacuum_state_sp_matrix.toarray() - vacuum_state = numpy.zeros((2**(n_qubits), 1)) - vacuum_state[0] = 1. + vacuum_state = numpy.zeros((2 ** (n_qubits), 1)) + vacuum_state[0] = 1.0 bksf_vacuum_state = numpy.dot(bksf_vacuum_state_matrix, vacuum_state) - two_fermion_state = numpy.dot(fermion_generation_matrix, - bksf_vacuum_state) + two_fermion_state = numpy.dot(fermion_generation_matrix, bksf_vacuum_state) # using total number operator to check the number of fermions generated tot_number_operator = bksf.number_operator(self.molecular_hamiltonian) number_operator_sp_matrix = get_sparse_operator(tot_number_operator) number_operator_matrix = number_operator_sp_matrix.toarray() tot_fermions = numpy.dot( - two_fermion_state.conjugate().T, - numpy.dot(number_operator_matrix, two_fermion_state)) + two_fermion_state.conjugate().T, numpy.dot(number_operator_matrix, two_fermion_state) + ) # checking the occupation number of site 2 and 3 number_operator_2 = bksf.number_operator(self.molecular_hamiltonian, 2) number_operator_3 = bksf.number_operator(self.molecular_hamiltonian, 3) @@ -201,20 +171,18 @@ def test_bravyi_kitaev_fast_generate_fermions(self): number_operator_23_sp_matrix = get_sparse_operator(number_operator_23) number_operator_23_matrix = number_operator_23_sp_matrix.toarray() tot_23_fermions = numpy.dot( - two_fermion_state.conjugate().T, - numpy.dot(number_operator_23_matrix, two_fermion_state)) + two_fermion_state.conjugate().T, numpy.dot(number_operator_23_matrix, two_fermion_state) + ) self.assertTrue(2.0 - float(tot_fermions.real) < 1e-13) self.assertTrue(2.0 - float(tot_23_fermions.real) < 1e-13) def test_bravyi_kitaev_fast_excitation_terms(self): # Testing on-site and excitation terms in Hamiltonian constant = 0 - one_body = numpy.array([[1, 2, 0, 3], [2, 1, 2, 0], [0, 2, 1, 2.5], - [3, 0, 2.5, 1]]) + one_body = numpy.array([[1, 2, 0, 3], [2, 1, 2, 0], [0, 2, 1, 2.5], [3, 0, 2.5, 1]]) # No Coloumb interaction two_body = numpy.zeros((4, 4, 4, 4)) - molecular_hamiltonian = InteractionOperator(constant, one_body, - two_body) + molecular_hamiltonian = InteractionOperator(constant, one_body, two_body) n_qubits = count_qubits(molecular_hamiltonian) # comparing the eigenspectrum of Hamiltonian bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast(molecular_hamiltonian) @@ -225,9 +193,7 @@ def test_bravyi_kitaev_fast_excitation_terms(self): jw_H_eig = jw_H_eig.round(5) evensector_H = 0 for i in range(numpy.size(jw_H_eig)): - if bool( - numpy.size( - numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): + if bool(numpy.size(numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): evensector_H += 1 # comparing eigenspectrum of number operator @@ -240,22 +206,19 @@ def test_bravyi_kitaev_fast_excitation_terms(self): bravyi_kitaev_fast_eig_spec = eigenspectrum(bravyi_kitaev_fast_n) evensector_n = 0 for i in range(numpy.size(jw_eig_spec)): - if bool( - numpy.size( - numpy.where( - jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): + if bool(numpy.size(numpy.where(jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): evensector_n += 1 - self.assertEqual(evensector_H, 2**(n_qubits - 1)) - self.assertEqual(evensector_n, 2**(n_qubits - 1)) + self.assertEqual(evensector_H, 2 ** (n_qubits - 1)) + self.assertEqual(evensector_n, 2 ** (n_qubits - 1)) def test_bravyi_kitaev_fast_number_excitation_operator(self): # using hydrogen Hamiltonian and introducing some number operator terms constant = 0 one_body = numpy.zeros((4, 4)) - one_body[(0, 0)] = .4 - one_body[(1, 1)] = .5 - one_body[(2, 2)] = .6 - one_body[(3, 3)] = .7 + one_body[(0, 0)] = 0.4 + one_body[(1, 1)] = 0.5 + one_body[(2, 2)] = 0.6 + one_body[(3, 3)] = 0.7 two_body = self.molecular_hamiltonian.two_body_tensor # initiating number operator terms for all the possible cases two_body[(1, 2, 3, 1)] = 0.1 @@ -267,8 +230,7 @@ def test_bravyi_kitaev_fast_number_excitation_operator(self): two_body[(1, 2, 3, 2)] = 0.11 two_body[(2, 3, 2, 1)] = 0.11 two_body[(2, 2, 2, 2)] = 0.1 - molecular_hamiltonian = InteractionOperator(constant, one_body, - two_body) + molecular_hamiltonian = InteractionOperator(constant, one_body, two_body) # comparing the eigenspectrum of Hamiltonian n_qubits = count_qubits(molecular_hamiltonian) bravyi_kitaev_fast_H = bksf.bravyi_kitaev_fast(molecular_hamiltonian) @@ -279,9 +241,7 @@ def test_bravyi_kitaev_fast_number_excitation_operator(self): jw_H_eig = jw_H_eig.round(5) evensector_H = 0 for i in range(numpy.size(jw_H_eig)): - if bool( - numpy.size( - numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): + if bool(numpy.size(numpy.where(jw_H_eig[i] == bravyi_kitaev_fast_H_eig))): evensector_H += 1 # comparing eigenspectrum of number operator @@ -294,10 +254,7 @@ def test_bravyi_kitaev_fast_number_excitation_operator(self): bravyi_kitaev_fast_eig_spec = eigenspectrum(bravyi_kitaev_fast_n) evensector_n = 0 for i in range(numpy.size(jw_eig_spec)): - if bool( - numpy.size( - numpy.where( - jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): + if bool(numpy.size(numpy.where(jw_eig_spec[i] == bravyi_kitaev_fast_eig_spec))): evensector_n += 1 - self.assertEqual(evensector_H, 2**(n_qubits - 1)) - self.assertEqual(evensector_n, 2**(n_qubits - 1)) + self.assertEqual(evensector_H, 2 ** (n_qubits - 1)) + self.assertEqual(evensector_n, 2 ** (n_qubits - 1)) diff --git a/src/openfermion/transforms/opconversions/bravyi_kitaev.py b/src/openfermion/transforms/opconversions/bravyi_kitaev.py index dab1b2519..eae00e557 100644 --- a/src/openfermion/transforms/opconversions/bravyi_kitaev.py +++ b/src/openfermion/transforms/opconversions/bravyi_kitaev.py @@ -11,8 +11,7 @@ # limitations under the License. """Bravyi-Kitaev transform on fermionic operators.""" -from openfermion.ops.operators import (FermionOperator, MajoranaOperator, - QubitOperator) +from openfermion.ops.operators import FermionOperator, MajoranaOperator, QubitOperator from openfermion.ops.representations import InteractionOperator from openfermion.utils.operator_utils import count_qubits @@ -47,8 +46,9 @@ def bravyi_kitaev(operator, n_qubits=None): return _bravyi_kitaev_majorana_operator(operator, n_qubits) if isinstance(operator, InteractionOperator): return _bravyi_kitaev_interaction_operator(operator, n_qubits) - raise TypeError("Couldn't apply the Bravyi-Kitaev Transform to object " - "of type {}.".format(type(operator))) + raise TypeError( + "Couldn't apply the Bravyi-Kitaev Transform to object " "of type {}.".format(type(operator)) + ) def _update_set(index, n_qubits): @@ -108,19 +108,19 @@ def _bravyi_kitaev_majorana_operator(operator, n_qubits): raise ValueError('Invalid number of qubits specified.') # Compute transformed operator. - transformed_terms = (_transform_majorana_term(term=term, - coefficient=coeff, - n_qubits=n_qubits) - for term, coeff in operator.terms.items()) + transformed_terms = ( + _transform_majorana_term(term=term, coefficient=coeff, n_qubits=n_qubits) + for term, coeff in operator.terms.items() + ) return inline_sum(summands=transformed_terms, seed=QubitOperator()) def _transform_majorana_term(term, coefficient, n_qubits): # Build the Bravyi-Kitaev transformed operators. - transformed_ops = (_transform_majorana_operator(majorana_index, n_qubits) - for majorana_index in term) - return inline_product(factors=transformed_ops, - seed=QubitOperator((), coefficient)) + transformed_ops = ( + _transform_majorana_operator(majorana_index, n_qubits) for majorana_index in term + ) + return inline_product(factors=transformed_ops, seed=QubitOperator((), coefficient)) def _transform_majorana_operator(majorana_index, n_qubits): @@ -132,12 +132,13 @@ def _transform_majorana_operator(majorana_index, n_qubits): parity_set = _parity_set(q) if b: - return QubitOperator([(q, 'Y')] + [(i, 'X') for i in update_set - {q}] + - [(i, 'Z') - for i in (parity_set ^ occupation_set) - {q}]) + return QubitOperator( + [(q, 'Y')] + + [(i, 'X') for i in update_set - {q}] + + [(i, 'Z') for i in (parity_set ^ occupation_set) - {q}] + ) else: - return QubitOperator([(i, 'X') for i in update_set] + - [(i, 'Z') for i in parity_set]) + return QubitOperator([(i, 'X') for i in update_set] + [(i, 'Z') for i in parity_set]) def _transform_operator_term(term, coefficient, n_qubits): @@ -152,10 +153,10 @@ def _transform_operator_term(term, coefficient, n_qubits): """ # Build the Bravyi-Kitaev transformed operators. - transformed_ladder_ops = (_transform_ladder_operator( - ladder_operator, n_qubits) for ladder_operator in term) - return inline_product(factors=transformed_ladder_ops, - seed=QubitOperator((), coefficient)) + transformed_ladder_ops = ( + _transform_ladder_operator(ladder_operator, n_qubits) for ladder_operator in term + ) + return inline_product(factors=transformed_ladder_ops, seed=QubitOperator((), coefficient)) def _bravyi_kitaev_fermion_operator(operator, n_qubits): @@ -167,9 +168,10 @@ def _bravyi_kitaev_fermion_operator(operator, n_qubits): raise ValueError('Invalid number of qubits specified.') # Compute transformed operator. - transformed_terms = (_transform_operator_term( - term=term, coefficient=operator.terms[term], n_qubits=n_qubits) - for term in operator.terms) + transformed_terms = ( + _transform_operator_term(term=term, coefficient=operator.terms[term], n_qubits=n_qubits) + for term in operator.terms + ) return inline_sum(summands=transformed_terms, seed=QubitOperator()) @@ -189,13 +191,17 @@ def _transform_ladder_operator(ladder_operator, n_qubits): parity_set = _parity_set(index) # Initialize the transformed majorana operator (a_p^\dagger + a_p) / 2 - transformed_operator = QubitOperator([(i, 'X') for i in update_set] + - [(i, 'Z') for i in parity_set], .5) + transformed_operator = QubitOperator( + [(i, 'X') for i in update_set] + [(i, 'Z') for i in parity_set], 0.5 + ) # Get the transformed (a_p^\dagger - a_p) / 2 # Below is equivalent to X(update_set) * Z(parity_set ^ occupation_set) transformed_majorana_difference = QubitOperator( - [(index, 'Y')] + [(i, 'X') for i in update_set - {index}] + - [(i, 'Z') for i in (parity_set ^ occupation_set) - {index}], -.5j) + [(index, 'Y')] + + [(i, 'X') for i in update_set - {index}] + + [(i, 'Z') for i in (parity_set ^ occupation_set) - {index}], + -0.5j, + ) # Raising if action == 1: @@ -261,29 +267,25 @@ def _bravyi_kitaev_interaction_operator(interaction_operator, n_qubits): # A. Number operators: n_i if abs(one_body[i, i]) > 0: qubit_hamiltonian += _qubit_operator_creation( - *_seeley_richard_love(i, i, one_body[i, i], n_qubits)) + *_seeley_richard_love(i, i, one_body[i, i], n_qubits) + ) for j in range(i): # Case B: Coulomb and exchange operators if abs(one_body[i, j]) > 0: - operators, coef_list = _seeley_richard_love( - i, j, one_body[i, j], n_qubits) + operators, coef_list = _seeley_richard_love(i, j, one_body[i, j], n_qubits) qubit_hamiltonian_op.extend(operators) qubit_hamiltonian_coef.extend(coef_list) - operators, coef_list = _seeley_richard_love( - j, i, one_body[i, j].conj(), n_qubits) + operators, coef_list = _seeley_richard_love(j, i, one_body[i, j].conj(), n_qubits) qubit_hamiltonian_op.extend(operators) qubit_hamiltonian_coef.extend(coef_list) coef = _two_body_coef(two_body, i, j, j, i) / 4 if abs(coef) > 0: - qubit_hamiltonian_op.append( - tuple((index, "Z") for index in _occupation_set(i))) - qubit_hamiltonian_op.append( - tuple((index, "Z") for index in _occupation_set(j))) - qubit_hamiltonian_op.append( - tuple((index, "Z") for index in _F_ij_set(i, j))) + qubit_hamiltonian_op.append(tuple((index, "Z") for index in _occupation_set(i))) + qubit_hamiltonian_op.append(tuple((index, "Z") for index in _occupation_set(j))) + qubit_hamiltonian_op.append(tuple((index, "Z") for index in _F_ij_set(i, j))) qubit_hamiltonian_coef.append(-coef) qubit_hamiltonian_coef.append(-coef) @@ -298,17 +300,15 @@ def _bravyi_kitaev_interaction_operator(interaction_operator, n_qubits): coef = _two_body_coef(two_body, i, j, k, i) if abs(coef) > 0: - number = _qubit_operator_creation( - *_seeley_richard_love(i, i, 1, n_qubits)) + number = _qubit_operator_creation(*_seeley_richard_love(i, i, 1, n_qubits)) - excitation_op, excitation_coef = _seeley_richard_love( - j, k, coef, n_qubits) + excitation_op, excitation_coef = _seeley_richard_love(j, k, coef, n_qubits) operators_hc, coef_list_hc = _seeley_richard_love( - k, j, coef.conj(), n_qubits) + k, j, coef.conj(), n_qubits + ) excitation_op.extend(operators_hc) excitation_coef.extend(coef_list_hc) - excitation = _qubit_operator_creation( - excitation_op, excitation_coef) + excitation = _qubit_operator_creation(excitation_op, excitation_coef) number *= excitation qubit_hamiltonian += number @@ -320,49 +320,40 @@ def _bravyi_kitaev_interaction_operator(interaction_operator, n_qubits): for l in range(k): coef = -_two_body_coef(two_body, i, j, k, l) if abs(coef) > 0: - qubit_hamiltonian += _hermitian_one_body_product( - i, j, k, l, coef, n_qubits) + qubit_hamiltonian += _hermitian_one_body_product(i, j, k, l, coef, n_qubits) coef = -_two_body_coef(two_body, i, k, j, l) if abs(coef) > 0: - qubit_hamiltonian += _hermitian_one_body_product( - i, k, j, l, coef, n_qubits) + qubit_hamiltonian += _hermitian_one_body_product(i, k, j, l, coef, n_qubits) coef = -_two_body_coef(two_body, i, l, j, k) if abs(coef) > 0: - qubit_hamiltonian += _hermitian_one_body_product( - i, l, j, k, coef, n_qubits) + qubit_hamiltonian += _hermitian_one_body_product(i, l, j, k, coef, n_qubits) qubit_hamiltonian_op.append(()) qubit_hamiltonian_coef.append(constant_term) - qubit_hamiltonian += _qubit_operator_creation(qubit_hamiltonian_op, - qubit_hamiltonian_coef) + qubit_hamiltonian += _qubit_operator_creation(qubit_hamiltonian_op, qubit_hamiltonian_coef) return qubit_hamiltonian def _two_body_coef(two_body, a, b, c, d): - return two_body[a, b, c, d] - two_body[a, b, d, c] + two_body[ - b, a, d, c] - two_body[b, a, c, d] + return two_body[a, b, c, d] - two_body[a, b, d, c] + two_body[b, a, d, c] - two_body[b, a, c, d] def _hermitian_one_body_product(a, b, c, d, coef, n_qubits): - """ Takes the 4 indices for a two-body operator and constructs the + """Takes the 4 indices for a two-body operator and constructs the Bravyi-Kitaev form by splitting the two-body into 2 one-body operators, multiplying them together and then re-adding the Hermitian conjugate to - give a Hermitian operator. """ + give a Hermitian operator.""" - c_dag_c_ac = _qubit_operator_creation( - *_seeley_richard_love(a, c, coef, n_qubits)) - c_dag_c_bd = _qubit_operator_creation( - *_seeley_richard_love(b, d, 1, n_qubits)) + c_dag_c_ac = _qubit_operator_creation(*_seeley_richard_love(a, c, coef, n_qubits)) + c_dag_c_bd = _qubit_operator_creation(*_seeley_richard_love(b, d, 1, n_qubits)) c_dag_c_ac *= c_dag_c_bd hermitian_sum = c_dag_c_ac - c_dag_c_ca = _qubit_operator_creation( - *_seeley_richard_love(c, a, coef.conj(), n_qubits)) - c_dag_c_db = _qubit_operator_creation( - *_seeley_richard_love(d, b, 1, n_qubits)) + c_dag_c_ca = _qubit_operator_creation(*_seeley_richard_love(c, a, coef.conj(), n_qubits)) + c_dag_c_db = _qubit_operator_creation(*_seeley_richard_love(d, b, 1, n_qubits)) c_dag_c_ca *= c_dag_c_db hermitian_sum += c_dag_c_ca @@ -370,7 +361,7 @@ def _hermitian_one_body_product(a, b, c, d, coef, n_qubits): def _qubit_operator_creation(operators, coefficents): - """ Takes a list of tuples for operators/indices, and another for + """Takes a list of tuples for operators/indices, and another for coefficents""" qubit_operator = QubitOperator() @@ -392,8 +383,7 @@ def _seeley_richard_love(i, j, coef, n_qubits): coef *= 0.25 # Case 0 if i == j: # Simplifies to the number operator - seeley_richard_love_op.append( - tuple((index, "Z") for index in _occupation_set(i))) + seeley_richard_love_op.append(tuple((index, "Z") for index in _occupation_set(i))) seeley_richard_love_coef.append(-coef * 2) seeley_richard_love_op.append(()) @@ -403,8 +393,7 @@ def _seeley_richard_love(i, j, coef, n_qubits): elif i % 2 == 0 and j % 2 == 0: x_pad = tuple((index, "X") for index in _U_diff_a_set(i, j, n_qubits)) y_pad = tuple((index, "Y") for index in _alpha_set(i, j, n_qubits)) - z_pad = tuple( - (index, "Z") for index in _P0_ij_diff_a_set(i, j, n_qubits)) + z_pad = tuple((index, "Z") for index in _P0_ij_diff_a_set(i, j, n_qubits)) left_pad = x_pad + y_pad + z_pad @@ -432,21 +421,13 @@ def _seeley_richard_love(i, j, coef, n_qubits): left_pad = x_pad + y_pad - right_pad_1 = tuple( - (index, "Z") - for index in _P0_ij_set(i, j) - _alpha_set(i, j, n_qubits)) - right_pad_2 = tuple( - (index, "Z") - for index in _P2_ij_set(i, j) - _alpha_set(i, j, n_qubits)) - - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_1) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_1) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_2) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_2) + right_pad_1 = tuple((index, "Z") for index in _P0_ij_set(i, j) - _alpha_set(i, j, n_qubits)) + right_pad_2 = tuple((index, "Z") for index in _P2_ij_set(i, j) - _alpha_set(i, j, n_qubits)) + + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_2) if i < j: seeley_richard_love_coef.append(coef) @@ -466,44 +447,33 @@ def _seeley_richard_love(i, j, coef, n_qubits): right_pad_1 = tuple((index, "Z") for index in _P0_ij_set(i, j) - {i}) right_pad_2 = tuple((index, "Z") for index in _P2_ij_set(i, j) - {i}) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_1) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_1) seeley_richard_love_coef.append(complex(0, -coef)) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_2) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_2) seeley_richard_love_coef.append(complex(0, coef)) # Case 4 - elif i % 2 == 0 and j % 2 == 1 and i not in _parity_set( - j) and j not in _update_set(i, n_qubits): + elif ( + i % 2 == 0 and j % 2 == 1 and i not in _parity_set(j) and j not in _update_set(i, n_qubits) + ): x_pad = tuple((index, "X") for index in _U_diff_a_set(i, j, n_qubits)) y_pad = tuple((index, "Y") for index in _alpha_set(i, j, n_qubits)) left_pad = x_pad + y_pad - right_pad_1 = tuple( - (index, "Z") - for index in _P0_ij_set(i, j) - _alpha_set(i, j, n_qubits)) - right_pad_2 = tuple( - (index, "Z") - for index in _P1_ij_set(i, j) - _alpha_set(i, j, n_qubits)) - - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_1) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_1) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_2) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_2) + right_pad_1 = tuple((index, "Z") for index in _P0_ij_set(i, j) - _alpha_set(i, j, n_qubits)) + right_pad_2 = tuple((index, "Z") for index in _P1_ij_set(i, j) - _alpha_set(i, j, n_qubits)) + + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_2) if i < j: seeley_richard_love_coef.append(-coef) @@ -517,8 +487,7 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_coef.append(-coef) # Case 5 - elif i % 2 == 0 and j % 2 == 1 and i not in _parity_set( - j) and j in _update_set(i, n_qubits): + elif i % 2 == 0 and j % 2 == 1 and i not in _parity_set(j) and j in _update_set(i, n_qubits): x_range_1 = _U_ij_set(i, j, n_qubits) - {j} left_pad_1 = tuple((index, "X") for index in x_range_1) @@ -537,8 +506,9 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_coef.append(-coef) seeley_richard_love_op.append(left_pad_2 + ((i, "X"),) + right_pad_1) - seeley_richard_love_coef.append(complex( - 0, -coef)) # Phase flip of -1 relative to original paper + seeley_richard_love_coef.append( + complex(0, -coef) + ) # Phase flip of -1 relative to original paper seeley_richard_love_op.append(left_pad_1 + ((i, "Y"),) + right_pad_2) seeley_richard_love_coef.append(complex(0, coef)) @@ -547,10 +517,8 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_coef.append(-coef) # Case 6 - elif i % 2 == 0 and j % 2 == 1 and i in _parity_set(j) and j in _update_set( - i, n_qubits): - left_pad = tuple( - (index, "X") for index in _U_ij_set(i, j, n_qubits) - {j}) + elif i % 2 == 0 and j % 2 == 1 and i in _parity_set(j) and j in _update_set(i, n_qubits): + left_pad = tuple((index, "X") for index in _U_ij_set(i, j, n_qubits) - {j}) right_pad = tuple((index, "Z") for index in _P1_ij_set(i, j).union({j})) seeley_richard_love_op.append(left_pad + ((i, "X"),)) @@ -566,8 +534,9 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_coef.append(-coef) # Case 7 - elif i % 2 == 1 and j % 2 == 1 and i not in _parity_set( - j) and j not in _update_set(i, n_qubits): + elif ( + i % 2 == 1 and j % 2 == 1 and i not in _parity_set(j) and j not in _update_set(i, n_qubits) + ): x_pad = tuple((index, "X") for index in _U_diff_a_set(i, j, n_qubits)) y_pad = tuple((index, "Y") for index in _alpha_set(i, j, n_qubits)) left_pad = x_pad + y_pad @@ -585,42 +554,33 @@ def _seeley_richard_love(i, j, coef, n_qubits): right_pad_4 = tuple((index, "Z") for index in z_range_4) if i < j: - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_1) seeley_richard_love_coef.append(complex(0, -coef)) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_2) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_3) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_3) seeley_richard_love_coef.append(-coef) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_4) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_4) seeley_richard_love_coef.append(complex(0, -coef)) else: - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_1) seeley_richard_love_coef.append(-coef) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_2) seeley_richard_love_coef.append(complex(0, -coef)) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_3) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_3) seeley_richard_love_coef.append(complex(0, coef)) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_4) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_4) seeley_richard_love_coef.append(-coef) # Case 8 - elif i % 2 == 1 and j % 2 == 1 and i in _parity_set( - j) and j not in _update_set(i, n_qubits): + elif i % 2 == 1 and j % 2 == 1 and i in _parity_set(j) and j not in _update_set(i, n_qubits): left_pad = tuple((index, "X") for index in _U_ij_set(i, j, n_qubits)) z_range_1 = _P0_ij_set(i, j) - {i} @@ -635,25 +595,20 @@ def _seeley_richard_love(i, j, coef, n_qubits): z_range_4 = _P3_ij_set(i, j) - {i} right_pad_4 = tuple((index, "Z") for index in z_range_4) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "Y")) + right_pad_1) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "Y")) + right_pad_1) seeley_richard_love_coef.append(complex(0, -coef)) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "Y")) + right_pad_2) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "Y")) + right_pad_2) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "X"), - (i, "X")) + right_pad_3) + seeley_richard_love_op.append(left_pad + ((j, "X"), (i, "X")) + right_pad_3) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "Y"), - (i, "X")) + right_pad_4) + seeley_richard_love_op.append(left_pad + ((j, "Y"), (i, "X")) + right_pad_4) seeley_richard_love_coef.append(complex(0, coef)) # Case 9 - elif i % 2 == 1 and j % 2 == 1 and i not in _parity_set( - j) and j in _update_set(i, n_qubits): + elif i % 2 == 1 and j % 2 == 1 and i not in _parity_set(j) and j in _update_set(i, n_qubits): x_range_1 = _U_ij_set(i, j, n_qubits) - {j} left_pad_3 = tuple((index, "X") for index in x_range_1) @@ -680,12 +635,10 @@ def _seeley_richard_love(i, j, coef, n_qubits): right_pad_4 = tuple((index, "Z") for index in z_range_4) seeley_richard_love_op.append(left_pad_1 + ((i, "Y"),) + right_pad_1) - seeley_richard_love_coef.append( - -coef) # phase of -j relative to original paper + seeley_richard_love_coef.append(-coef) # phase of -j relative to original paper seeley_richard_love_op.append(left_pad_2 + right_pad_2) - seeley_richard_love_coef.append(complex( - 0, -coef)) # phase of -j relative to original paper + seeley_richard_love_coef.append(complex(0, -coef)) # phase of -j relative to original paper seeley_richard_love_op.append(left_pad_3 + ((i, "X"),) + right_pad_3) seeley_richard_love_coef.append(-coef) @@ -694,10 +647,8 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_coef.append(complex(0, coef)) # Case 10 - elif i % 2 == 1 and j % 2 == 1 and i in _parity_set(j) and j in _update_set( - i, n_qubits): - left_pad = tuple( - (index, "X") for index in _U_ij_set(i, j, n_qubits) - {j}) + elif i % 2 == 1 and j % 2 == 1 and i in _parity_set(j) and j in _update_set(i, n_qubits): + left_pad = tuple((index, "X") for index in _U_ij_set(i, j, n_qubits) - {j}) right_pad_1 = tuple((index, "Z") for index in _P0_ij_set(i, j) - {i}) right_pad_2 = tuple((index, "Z") for index in _P2_ij_set(i, j) - {i}) right_pad_3 = tuple((index, "Z") for index in _P1_ij_set(i, j)) @@ -709,12 +660,10 @@ def _seeley_richard_love(i, j, coef, n_qubits): seeley_richard_love_op.append(left_pad + ((i, "X"),) + right_pad_2) seeley_richard_love_coef.append(coef) - seeley_richard_love_op.append(left_pad + ((j, "Z"), - (i, "X")) + right_pad_3) + seeley_richard_love_op.append(left_pad + ((j, "Z"), (i, "X")) + right_pad_3) seeley_richard_love_coef.append(-coef) - seeley_richard_love_op.append(left_pad + ((j, "Z"), - (i, "Y")) + right_pad_4) + seeley_richard_love_op.append(left_pad + ((j, "Z"), (i, "Y")) + right_pad_4) seeley_richard_love_coef.append(complex(0, coef)) return seeley_richard_love_op, seeley_richard_love_coef @@ -729,38 +678,37 @@ def _F_ij_set(i, j): def _P0_ij_set(i, j): - """ The symmetric difference of sets P(i) and P(j). """ + """The symmetric difference of sets P(i) and P(j).""" return _parity_set(i).symmetric_difference(_parity_set(j)) def _P1_ij_set(i, j): - """ The symmetric difference of sets P(i) and R(j). """ + """The symmetric difference of sets P(i) and R(j).""" return _parity_set(i).symmetric_difference(_remainder_set(j)) def _P2_ij_set(i, j): - """ The symmetric difference of sets R(i) and P(j). """ + """The symmetric difference of sets R(i) and P(j).""" return _remainder_set(i).symmetric_difference(_parity_set(j)) def _P3_ij_set(i, j): - """ The symmetric difference of sets R(i) and R(j). """ + """The symmetric difference of sets R(i) and R(j).""" return _remainder_set(i).symmetric_difference(_remainder_set(j)) def _U_ij_set(i, j, n_qubits): - """ The symmetric difference of sets U(i) and U(j)""" - return _update_set(i, - n_qubits).symmetric_difference(_update_set(j, n_qubits)) + """The symmetric difference of sets U(i) and U(j)""" + return _update_set(i, n_qubits).symmetric_difference(_update_set(j, n_qubits)) def _U_diff_a_set(i, j, n_qubits): - """ Calculates the set {member U_ij diff alpha_ij}. """ + """Calculates the set {member U_ij diff alpha_ij}.""" return _U_ij_set(i, j, n_qubits) - _alpha_set(i, j, n_qubits) def _P0_ij_diff_a_set(i, j, n_qubits): - """ Calculates the set {member P_ij diff alpha_ij}. """ + """Calculates the set {member P_ij diff alpha_ij}.""" return _P0_ij_set(i, j) - _alpha_set(i, j, n_qubits) diff --git a/src/openfermion/transforms/opconversions/bravyi_kitaev_test.py b/src/openfermion/transforms/opconversions/bravyi_kitaev_test.py index 09cd3eee8..3a95702d7 100644 --- a/src/openfermion/transforms/opconversions/bravyi_kitaev_test.py +++ b/src/openfermion/transforms/opconversions/bravyi_kitaev_test.py @@ -16,22 +16,23 @@ import numpy import sympy -from openfermion.ops.operators import (FermionOperator, MajoranaOperator, - QubitOperator) - -from openfermion.transforms.opconversions import (jordan_wigner, bravyi_kitaev, - get_fermion_operator, - normal_ordered) - -from openfermion.transforms import (get_interaction_operator) -from openfermion.testing.testing_utils import (random_interaction_operator) -from openfermion.utils.operator_utils import (count_qubits) +from openfermion.ops.operators import FermionOperator, MajoranaOperator, QubitOperator + +from openfermion.transforms.opconversions import ( + jordan_wigner, + bravyi_kitaev, + get_fermion_operator, + normal_ordered, +) + +from openfermion.transforms import get_interaction_operator +from openfermion.testing.testing_utils import random_interaction_operator +from openfermion.utils.operator_utils import count_qubits from openfermion.linalg import eigenspectrum from openfermion.hamiltonians import number_operator class BravyiKitaevTransformTest(unittest.TestCase): - def test_bravyi_kitaev_transform(self): # Check that the QubitOperators are two-term. lowering = bravyi_kitaev(FermionOperator(((3, 0),))) @@ -59,8 +60,7 @@ def test_bravyi_kitaev_transform(self): lowering = bravyi_kitaev(FermionOperator(((9, 0),)), n_qubits) raising = bravyi_kitaev(FermionOperator(((9, 1),)), n_qubits) - correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, - 'X')) + correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, 'X')) correct_operators_d = ((7, 'Z'), (9, 'Y'), (11, 'X'), (15, 'X')) self.assertEqual(lowering.terms[correct_operators_c], 0.5) @@ -77,11 +77,10 @@ def test_bravyi_kitaev_transform_sympy(self): lowering = bravyi_kitaev(FermionOperator(((9, 0),)) * coeff, n_qubits) raising = bravyi_kitaev(FermionOperator(((9, 1),)) * coeff, n_qubits) sum_lr = bravyi_kitaev( - FermionOperator(((9, 0),)) * coeff + FermionOperator( - ((9, 1),)) * coeff, n_qubits) + FermionOperator(((9, 0),)) * coeff + FermionOperator(((9, 1),)) * coeff, n_qubits + ) - correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, - 'X')) + correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, 'X')) correct_operators_d = ((7, 'Z'), (9, 'Y'), (11, 'X'), (15, 'X')) self.assertEqual(lowering.terms[correct_operators_c], 0.5 * coeff) @@ -101,9 +100,7 @@ def test_bk_n_qubits_too_small(self): with self.assertRaises(ValueError): bravyi_kitaev(MajoranaOperator((2, 3, 9, 0)), n_qubits=4) with self.assertRaises(ValueError): - bravyi_kitaev(get_interaction_operator( - FermionOperator('2^ 3^ 5 0')), - n_qubits=4) + bravyi_kitaev(get_interaction_operator(FermionOperator('2^ 3^ 5 0')), n_qubits=4) def test_bk_jw_number_operator(self): # Check if number operator has the same spectrum in both @@ -116,8 +113,7 @@ def test_bk_jw_number_operator(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_number_operators(self): # Check if a number operator has the same spectrum in both @@ -134,8 +130,7 @@ def test_bk_jw_number_operators(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_number_operator_scaled(self): # Check if number operator has the same spectrum in both @@ -149,13 +144,11 @@ def test_bk_jw_number_operator_scaled(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_hopping_operator(self): # Check if the spectrum fits for a single hoppping operator - ho = FermionOperator(((1, 1), (4, 0))) + FermionOperator( - ((4, 1), (1, 0))) + ho = FermionOperator(((1, 1), (4, 0))) + FermionOperator(((4, 1), (1, 0))) jw_ho = jordan_wigner(ho) bk_ho = bravyi_kitaev(ho) @@ -163,8 +156,7 @@ def test_bk_jw_hopping_operator(self): jw_spectrum = eigenspectrum(jw_ho) bk_spectrum = eigenspectrum(bk_ho) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_majoranas(self): # Check if the Majorana operators have the same spectrum @@ -182,10 +174,8 @@ def test_bk_jw_majoranas(self): c_spectrum = [eigenspectrum(c_spins[0]), eigenspectrum(c_spins[1])] d_spectrum = [eigenspectrum(d_spins[0]), eigenspectrum(d_spins[1])] - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(c_spectrum[0] - c_spectrum[1]))) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(d_spectrum[0] - d_spectrum[1]))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(c_spectrum[0] - c_spectrum[1]))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(d_spectrum[0] - d_spectrum[1]))) def test_bk_jw_integration(self): # This is a legacy test, which was a minimal failing example when @@ -200,14 +190,12 @@ def test_bk_jw_integration(self): jw_spectrum = eigenspectrum(jw) bk_spectrum = eigenspectrum(bk) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_integration_original(self): # This is a legacy test, which was an example proposed by Ryan, # failing when optimization for hermitian operators was used. - fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), - -4.3) + fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), -4.3) fermion_operator += FermionOperator(((3, 1), (1, 0)), 8.17) fermion_operator += 3.2 * FermionOperator() @@ -218,10 +206,7 @@ def test_bk_jw_integration_original(self): # Diagonalize and make sure the spectra are the same. jw_spectrum = eigenspectrum(jw_qubit_operator) bk_spectrum = eigenspectrum(bk_qubit_operator) - self.assertAlmostEqual(0., - numpy.amax( - numpy.absolute(jw_spectrum - bk_spectrum)), - places=5) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)), places=5) def test_bk_bad_type(self): with self.assertRaises(TypeError): @@ -229,13 +214,15 @@ def test_bk_bad_type(self): def test_bravyi_kitaev_majorana_op_consistent(): - op = (MajoranaOperator((1, 3, 4), 0.5) + MajoranaOperator( - (3, 7, 8, 9, 10, 12), 1.8) + MajoranaOperator((0, 4))) + op = ( + MajoranaOperator((1, 3, 4), 0.5) + + MajoranaOperator((3, 7, 8, 9, 10, 12), 1.8) + + MajoranaOperator((0, 4)) + ) assert bravyi_kitaev(op) == bravyi_kitaev(get_fermion_operator(op)) class BravyiKitaevInterOpTest(unittest.TestCase): - test_range = 8 def one_op(self, a, b): @@ -245,15 +232,15 @@ def two_op(self, a, b): return self.one_op(a, b) + self.one_op(b, a) def coulomb_exchange_operator(self, a, b): - return FermionOperator( - ((a, 1), (b, 1), (a, 0), (b, 0))) + FermionOperator( - ((a, 1), (b, 1), (b, 0), (a, 0))) + return FermionOperator(((a, 1), (b, 1), (a, 0), (b, 0))) + FermionOperator( + ((a, 1), (b, 1), (b, 0), (a, 0)) + ) def number_excitation_operator(self, a, b, c): return normal_ordered( - FermionOperator(((a, 1), (b, 1), (b, 0), - (c, 0))) + FermionOperator(((b, 1), (c, 1), (a, 0), - (b, 0)))) + FermionOperator(((a, 1), (b, 1), (b, 0), (c, 0))) + + FermionOperator(((b, 1), (c, 1), (a, 0), (b, 0))) + ) def four_op(self, a, b, c, d): return normal_ordered(FermionOperator(((a, 1), (b, 1), (c, 0), (d, 0)))) @@ -295,8 +282,7 @@ def test_number_excitation_op_success(self): ham = self.number_excitation_operator(i, j, k) opf_ham = bravyi_kitaev(ham) - custom = bravyi_kitaev( - get_interaction_operator(ham)) + custom = bravyi_kitaev(get_interaction_operator(ham)) assert custom == opf_ham @@ -308,13 +294,11 @@ def test_double_excitation_op_success(self): for l in range(self.test_range): if len({i, j, k, l}) == 4: print(i, j, k, l) - ham = self.four_op(i, j, k, l) + self.four_op( - k, l, i, j) + ham = self.four_op(i, j, k, l) + self.four_op(k, l, i, j) n_qubits = count_qubits(ham) opf_ham = bravyi_kitaev(ham, n_qubits) - custom = bravyi_kitaev( - get_interaction_operator(ham)) + custom = bravyi_kitaev(get_interaction_operator(ham)) assert custom == opf_ham diff --git a/src/openfermion/transforms/opconversions/bravyi_kitaev_tree.py b/src/openfermion/transforms/opconversions/bravyi_kitaev_tree.py index 7b22a3eb0..2259ce57f 100644 --- a/src/openfermion/transforms/opconversions/bravyi_kitaev_tree.py +++ b/src/openfermion/transforms/opconversions/bravyi_kitaev_tree.py @@ -12,8 +12,7 @@ """Bravyi-Kitaev transform on fermionic operators.""" from openfermion.ops.operators import QubitOperator -from openfermion.transforms.opconversions.bravyi_kitaev import (inline_product, - inline_sum) +from openfermion.transforms.opconversions.bravyi_kitaev import inline_product, inline_sum from openfermion.transforms.opconversions.fenwick_tree import FenwickTree @@ -44,6 +43,7 @@ def bravyi_kitaev_tree(operator, n_qubits=None): """ # Compute the number of qubits. from openfermion.utils import count_qubits + if n_qubits is None: n_qubits = count_qubits(operator) if n_qubits < count_qubits(operator): @@ -53,9 +53,12 @@ def bravyi_kitaev_tree(operator, n_qubits=None): fenwick_tree = FenwickTree(n_qubits) # Compute transformed operator. - transformed_terms = (_transform_operator_term( - term=term, coefficient=operator.terms[term], fenwick_tree=fenwick_tree) - for term in operator.terms) + transformed_terms = ( + _transform_operator_term( + term=term, coefficient=operator.terms[term], fenwick_tree=fenwick_tree + ) + for term in operator.terms + ) return inline_sum(summands=transformed_terms, seed=QubitOperator()) @@ -71,10 +74,10 @@ def _transform_operator_term(term, coefficient, fenwick_tree): """ # Build the Bravyi-Kitaev transformed operators. - transformed_ladder_ops = (_transform_ladder_operator( - ladder_operator, fenwick_tree) for ladder_operator in term) - return inline_product(factors=transformed_ladder_ops, - seed=QubitOperator((), coefficient)) + transformed_ladder_ops = ( + _transform_ladder_operator(ladder_operator, fenwick_tree) for ladder_operator in term + ) + return inline_product(factors=transformed_ladder_ops, seed=QubitOperator((), coefficient)) def _transform_ladder_operator(ladder_operator, fenwick_tree): @@ -94,21 +97,29 @@ def _transform_ladder_operator(ladder_operator, fenwick_tree): ancestors = [node.index for node in fenwick_tree.get_update_set(index)] # The C(j) set. - ancestor_children = [ - node.index for node in fenwick_tree.get_remainder_set(index) - ] + ancestor_children = [node.index for node in fenwick_tree.get_remainder_set(index)] # Switch between lowering/raising operators. - d_coefficient = -.5j if ladder_operator[1] else .5j + d_coefficient = -0.5j if ladder_operator[1] else 0.5j # The fermion lowering operator is given by # a = (c+id)/2 where c, d are the majoranas. - d_majorana_component = QubitOperator((((ladder_operator[0], 'Y'),) + tuple( - (index, 'Z') for index in ancestor_children) + tuple( - (index, 'X') for index in ancestors)), d_coefficient) - - c_majorana_component = QubitOperator((((ladder_operator[0], 'X'),) + tuple( - (index, 'Z') for index in parity_set) + tuple( - (index, 'X') for index in ancestors)), 0.5) + d_majorana_component = QubitOperator( + ( + ((ladder_operator[0], 'Y'),) + + tuple((index, 'Z') for index in ancestor_children) + + tuple((index, 'X') for index in ancestors) + ), + d_coefficient, + ) + + c_majorana_component = QubitOperator( + ( + ((ladder_operator[0], 'X'),) + + tuple((index, 'Z') for index in parity_set) + + tuple((index, 'X') for index in ancestors) + ), + 0.5, + ) return c_majorana_component + d_majorana_component diff --git a/src/openfermion/transforms/opconversions/bravyi_kitaev_tree_test.py b/src/openfermion/transforms/opconversions/bravyi_kitaev_tree_test.py index 85ed92d46..47e588379 100644 --- a/src/openfermion/transforms/opconversions/bravyi_kitaev_tree_test.py +++ b/src/openfermion/transforms/opconversions/bravyi_kitaev_tree_test.py @@ -15,15 +15,13 @@ import numpy -from openfermion.ops.operators import (FermionOperator, QubitOperator) -from openfermion.transforms.opconversions import bravyi_kitaev_tree, \ - jordan_wigner +from openfermion.ops.operators import FermionOperator, QubitOperator +from openfermion.transforms.opconversions import bravyi_kitaev_tree, jordan_wigner from openfermion.linalg import eigenspectrum from openfermion.hamiltonians import number_operator class BravyiKitaevTransformTest(unittest.TestCase): - def test_bravyi_kitaev_tree_transform(self): # Check that the QubitOperators are two-term. lowering = bravyi_kitaev_tree(FermionOperator(((3, 0),))) @@ -36,8 +34,7 @@ def test_bravyi_kitaev_tree_transform(self): n_qubits = 16 invariant = numpy.log2(n_qubits) + 1 for index in range(n_qubits): - operator = bravyi_kitaev_tree(FermionOperator(((index, 0),)), - n_qubits) + operator = bravyi_kitaev_tree(FermionOperator(((index, 0),)), n_qubits) qubit_terms = operator.terms.items() # Get the majorana terms. for item in qubit_terms: @@ -52,8 +49,7 @@ def test_bravyi_kitaev_tree_transform(self): lowering = bravyi_kitaev_tree(FermionOperator(((9, 0),)), n_qubits) raising = bravyi_kitaev_tree(FermionOperator(((9, 1),)), n_qubits) - correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, - 'X')) + correct_operators_c = ((7, 'Z'), (8, 'Z'), (9, 'X'), (11, 'X'), (15, 'X')) correct_operators_d = ((7, 'Z'), (9, 'Y'), (11, 'X'), (15, 'X')) self.assertEqual(lowering.terms[correct_operators_c], 0.5) @@ -62,8 +58,7 @@ def test_bravyi_kitaev_tree_transform(self): self.assertEqual(raising.terms[correct_operators_c], 0.5) def test_bk_identity(self): - self.assertTrue( - bravyi_kitaev_tree(FermionOperator(())) == QubitOperator(())) + self.assertTrue(bravyi_kitaev_tree(FermionOperator(())) == QubitOperator(())) def test_bk_n_qubits_too_small(self): with self.assertRaises(ValueError): @@ -80,8 +75,7 @@ def test_bk_jw_number_operator(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_number_operators(self): # Check if a number operator has the same spectrum in both @@ -98,8 +92,7 @@ def test_bk_jw_number_operators(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_number_operator_scaled(self): # Check if number operator has the same spectrum in both @@ -113,13 +106,11 @@ def test_bk_jw_number_operator_scaled(self): jw_spectrum = eigenspectrum(jw_n) bk_spectrum = eigenspectrum(bk_n) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_hopping_operator(self): # Check if the spectrum fits for a single hoppping operator - ho = FermionOperator(((1, 1), (4, 0))) + FermionOperator( - ((4, 1), (1, 0))) + ho = FermionOperator(((1, 1), (4, 0))) + FermionOperator(((4, 1), (1, 0))) jw_ho = jordan_wigner(ho) bk_ho = bravyi_kitaev_tree(ho) @@ -127,8 +118,7 @@ def test_bk_jw_hopping_operator(self): jw_spectrum = eigenspectrum(jw_ho) bk_spectrum = eigenspectrum(bk_ho) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_majoranas(self): # Check if the Majorana operators have the same spectrum @@ -146,10 +136,8 @@ def test_bk_jw_majoranas(self): c_spectrum = [eigenspectrum(c_spins[0]), eigenspectrum(c_spins[1])] d_spectrum = [eigenspectrum(d_spins[0]), eigenspectrum(d_spins[1])] - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(c_spectrum[0] - c_spectrum[1]))) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(d_spectrum[0] - d_spectrum[1]))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(c_spectrum[0] - c_spectrum[1]))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(d_spectrum[0] - d_spectrum[1]))) def test_bk_jw_integration(self): # This is a legacy test, which was a minimal failing example when @@ -164,14 +152,12 @@ def test_bk_jw_integration(self): jw_spectrum = eigenspectrum(jw) bk_spectrum = eigenspectrum(bk) - self.assertAlmostEqual( - 0., numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum))) def test_bk_jw_integration_original(self): # This is a legacy test, which was an example proposed by Ryan, # failing when optimization for hermitian operators was used. - fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), - -4.3) + fermion_operator = FermionOperator(((3, 1), (2, 1), (1, 0), (0, 0)), -4.3) fermion_operator += FermionOperator(((3, 1), (1, 0)), 8.17) fermion_operator += 3.2 * FermionOperator() @@ -182,7 +168,4 @@ def test_bk_jw_integration_original(self): # Diagonalize and make sure the spectra are the same. jw_spectrum = eigenspectrum(jw_qubit_operator) bk_spectrum = eigenspectrum(bk_qubit_operator) - self.assertAlmostEqual(0., - numpy.amax( - numpy.absolute(jw_spectrum - bk_spectrum)), - places=5) + self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(jw_spectrum - bk_spectrum)), places=5) diff --git a/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator.py b/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator.py index 18fe88235..d4480c867 100644 --- a/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator.py +++ b/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator.py @@ -18,7 +18,8 @@ def commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b, prior_terms=None): + operator_a, operator_b, prior_terms=None +): """Compute the commutator of two-body operators provided that both are normal-ordered and that the first only has diagonal Coulomb interactions. @@ -53,28 +54,29 @@ def commutator_ordered_diagonal_coulomb_with_two_body_operator( continue # Case 1: both operators are two-body, operator_a is i^ j^ i j. - if (len(term_a) == len(term_b) == 4 and - term_a[0][0] == term_a[2][0] and - term_a[1][0] == term_a[3][0]): + if ( + len(term_a) == len(term_b) == 4 + and term_a[0][0] == term_a[2][0] + and term_a[1][0] == term_a[3][0] + ): _commutator_two_body_diagonal_with_two_body( - term_a, term_b, coefficient, prior_terms) + term_a, term_b, coefficient, prior_terms + ) # Case 2: commutator of a 1-body and a 2-body operator - elif (len(term_b) == 4 and - len(term_a) == 2) or (len(term_a) == 4 and len(term_b) == 2): - _commutator_one_body_with_two_body(term_a, term_b, coefficient, - prior_terms) + elif (len(term_b) == 4 and len(term_a) == 2) or (len(term_a) == 4 and len(term_b) == 2): + _commutator_one_body_with_two_body(term_a, term_b, coefficient, prior_terms) # Case 3: both terms are one-body operators (both length 2) elif len(term_a) == 2 and len(term_b) == 2: - _commutator_one_body_with_one_body(term_a, term_b, coefficient, - prior_terms) + _commutator_one_body_with_one_body(term_a, term_b, coefficient, prior_terms) # Final case (case 4): violation of the input promise. Still # compute the commutator, but warn the user. else: - warnings.warn('Defaulted to standard commutator evaluation ' - 'due to an out-of-spec operator.') + warnings.warn( + 'Defaulted to standard commutator evaluation ' 'due to an out-of-spec operator.' + ) additional = FermionOperator.zero() additional.terms[term_a + term_b] = coefficient additional.terms[term_b + term_a] = -coefficient @@ -85,8 +87,9 @@ def commutator_ordered_diagonal_coulomb_with_two_body_operator( return prior_terms -def _commutator_one_body_with_one_body(one_body_action_a, one_body_action_b, - coefficient, prior_terms): +def _commutator_one_body_with_one_body( + one_body_action_a, one_body_action_b, coefficient, prior_terms +): """Compute the commutator of two one-body operators specified by actions. Args: @@ -97,35 +100,34 @@ def _commutator_one_body_with_one_body(one_body_action_a, one_body_action_b, """ # In the case that both the creation and annihilation operators of the # two actions pair, two new terms must be added. - if (one_body_action_a[0][0] == one_body_action_b[1][0] and - one_body_action_b[0][0] == one_body_action_a[1][0]): - new_one_body_action_a = ((one_body_action_a[0][0], 1), - (one_body_action_a[0][0], 0)) - new_one_body_action_b = ((one_body_action_b[0][0], 1), - (one_body_action_b[0][0], 0)) + if ( + one_body_action_a[0][0] == one_body_action_b[1][0] + and one_body_action_b[0][0] == one_body_action_a[1][0] + ): + new_one_body_action_a = ((one_body_action_a[0][0], 1), (one_body_action_a[0][0], 0)) + new_one_body_action_b = ((one_body_action_b[0][0], 1), (one_body_action_b[0][0], 0)) prior_terms.terms[new_one_body_action_a] = ( - prior_terms.terms.get(new_one_body_action_a, 0.0) + coefficient) + prior_terms.terms.get(new_one_body_action_a, 0.0) + coefficient + ) prior_terms.terms[new_one_body_action_b] = ( - prior_terms.terms.get(new_one_body_action_b, 0.0) - coefficient) + prior_terms.terms.get(new_one_body_action_b, 0.0) - coefficient + ) # A single pairing causes the mixed action a[0]^ b[1] to be added elif one_body_action_a[1][0] == one_body_action_b[0][0]: action_ab = ((one_body_action_a[0][0], 1), (one_body_action_b[1][0], 0)) - prior_terms.terms[action_ab] = (prior_terms.terms.get(action_ab, 0.0) + - coefficient) + prior_terms.terms[action_ab] = prior_terms.terms.get(action_ab, 0.0) + coefficient # The other single pairing adds the mixed action b[0]^ a[1] elif one_body_action_a[0][0] == one_body_action_b[1][0]: action_ba = ((one_body_action_b[0][0], 1), (one_body_action_a[1][0], 0)) - prior_terms.terms[action_ba] = (prior_terms.terms.get(action_ba, 0.0) - - coefficient) + prior_terms.terms[action_ba] = prior_terms.terms.get(action_ba, 0.0) - coefficient -def _commutator_one_body_with_two_body(action_a, action_b, coefficient, - prior_terms): +def _commutator_one_body_with_two_body(action_a, action_b, coefficient, prior_terms): """Compute commutator of action-specified one- and two-body operators. Args: @@ -153,8 +155,7 @@ def _commutator_one_body_with_two_body(action_a, action_b, coefficient, new_action = list(action_b) # If both terms are composed of number operators, they commute. - if one_body_create == one_body_annihilate and ( - two_body_create == two_body_annihilate): + if one_body_create == one_body_annihilate and (two_body_create == two_body_annihilate): return # If the one-body annihilation is in the two-body creation parts @@ -170,14 +171,14 @@ def _commutator_one_body_with_two_body(action_a, action_b, coefficient, # Normal order if necessary if new_inner_action[0][0] < new_inner_action[1][0]: - new_inner_action[0], new_inner_action[1] = (new_inner_action[1], - new_inner_action[0]) + new_inner_action[0], new_inner_action[1] = (new_inner_action[1], new_inner_action[0]) new_coeff *= -1 # Add the resulting term. if new_inner_action[0][0] > new_inner_action[1][0]: prior_terms.terms[tuple(new_inner_action)] = ( - prior_terms.terms.get(tuple(new_inner_action), 0.0) + new_coeff) + prior_terms.terms.get(tuple(new_inner_action), 0.0) + new_coeff + ) # If the one-body creation is in the two-body annihilation parts if one_body_create in two_body_annihilate: @@ -197,12 +198,13 @@ def _commutator_one_body_with_two_body(action_a, action_b, coefficient, # Add the resulting term. if new_action[2][0] > new_action[3][0]: prior_terms.terms[tuple(new_action)] = ( - prior_terms.terms.get(tuple(new_action), 0.0) + new_coeff) + prior_terms.terms.get(tuple(new_action), 0.0) + new_coeff + ) -def _commutator_two_body_diagonal_with_two_body(diagonal_coulomb_action, - arbitrary_two_body_action, - coefficient, prior_terms): +def _commutator_two_body_diagonal_with_two_body( + diagonal_coulomb_action, arbitrary_two_body_action, coefficient, prior_terms +): """Compute the commutator of two two-body operators specified by actions. Args: @@ -223,50 +225,60 @@ def _commutator_two_body_diagonal_with_two_body(diagonal_coulomb_action, utility. """ # Identify creation and annihilation parts of arbitrary_two_body_action. - arb_2bdy_create = (arbitrary_two_body_action[0][0], - arbitrary_two_body_action[1][0]) - arb_2bdy_annihilate = (arbitrary_two_body_action[2][0], - arbitrary_two_body_action[3][0]) + arb_2bdy_create = (arbitrary_two_body_action[0][0], arbitrary_two_body_action[1][0]) + arb_2bdy_annihilate = (arbitrary_two_body_action[2][0], arbitrary_two_body_action[3][0]) # The first two sub-cases cover when the creations and annihilations of # diagonal_coulomb_action and arbitrary_two_body_action totally pair up. - if (diagonal_coulomb_action[2][0] == arbitrary_two_body_action[0][0] and - diagonal_coulomb_action[3][0] == arbitrary_two_body_action[1][0]): + if ( + diagonal_coulomb_action[2][0] == arbitrary_two_body_action[0][0] + and diagonal_coulomb_action[3][0] == arbitrary_two_body_action[1][0] + ): prior_terms.terms[arbitrary_two_body_action] = ( - prior_terms.terms.get(arbitrary_two_body_action, 0.0) - coefficient) + prior_terms.terms.get(arbitrary_two_body_action, 0.0) - coefficient + ) - elif (diagonal_coulomb_action[0][0] == arbitrary_two_body_action[2][0] and - diagonal_coulomb_action[1][0] == arbitrary_two_body_action[3][0]): + elif ( + diagonal_coulomb_action[0][0] == arbitrary_two_body_action[2][0] + and diagonal_coulomb_action[1][0] == arbitrary_two_body_action[3][0] + ): prior_terms.terms[arbitrary_two_body_action] = ( - prior_terms.terms.get(arbitrary_two_body_action, 0.0) + coefficient) + prior_terms.terms.get(arbitrary_two_body_action, 0.0) + coefficient + ) # Exactly one of diagonal_coulomb_action's creations matches one of # arbitrary_two_body_action's annihilations. elif diagonal_coulomb_action[0][0] in arb_2bdy_annihilate: # Nothing gets added if there's an unbalanced double creation. if diagonal_coulomb_action[1][0] in arb_2bdy_create or ( - diagonal_coulomb_action[0][0] in arb_2bdy_create): + diagonal_coulomb_action[0][0] in arb_2bdy_create + ): return - _add_three_body_term(arbitrary_two_body_action, coefficient, - diagonal_coulomb_action[1][0], prior_terms) + _add_three_body_term( + arbitrary_two_body_action, coefficient, diagonal_coulomb_action[1][0], prior_terms + ) elif diagonal_coulomb_action[1][0] in arb_2bdy_annihilate: # Nothing gets added if there's an unbalanced double creation. if diagonal_coulomb_action[0][0] in arb_2bdy_create or ( - diagonal_coulomb_action[1][0] in arb_2bdy_create): + diagonal_coulomb_action[1][0] in arb_2bdy_create + ): return - _add_three_body_term(arbitrary_two_body_action, coefficient, - diagonal_coulomb_action[0][0], prior_terms) + _add_three_body_term( + arbitrary_two_body_action, coefficient, diagonal_coulomb_action[0][0], prior_terms + ) elif diagonal_coulomb_action[0][0] in arb_2bdy_create: - _add_three_body_term(arbitrary_two_body_action, -coefficient, - diagonal_coulomb_action[1][0], prior_terms) + _add_three_body_term( + arbitrary_two_body_action, -coefficient, diagonal_coulomb_action[1][0], prior_terms + ) elif diagonal_coulomb_action[1][0] in arb_2bdy_create: - _add_three_body_term(arbitrary_two_body_action, -coefficient, - diagonal_coulomb_action[0][0], prior_terms) + _add_three_body_term( + arbitrary_two_body_action, -coefficient, diagonal_coulomb_action[0][0], prior_terms + ) def _add_three_body_term(two_body_action, coefficient, mode, prior_terms): @@ -296,4 +308,5 @@ def _add_three_body_term(two_body_action, coefficient, mode, prior_terms): # Add the new normal-ordered term to the prior terms. prior_terms.terms[tuple(new_action)] = ( - prior_terms.terms.get(tuple(new_action), 0.0) + coefficient) + prior_terms.terms.get(tuple(new_action), 0.0) + coefficient + ) diff --git a/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator_test.py b/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator_test.py index bbd23489b..fb260a302 100644 --- a/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator_test.py +++ b/src/openfermion/transforms/opconversions/commutator_diagonal_coulomb_operator_test.py @@ -19,61 +19,72 @@ from openfermion.hamiltonians import jellium_model from openfermion.utils import commutator, Grid from openfermion.transforms.opconversions import normal_ordered -from openfermion.transforms.opconversions\ - .commutator_diagonal_coulomb_operator import ( - commutator_ordered_diagonal_coulomb_with_two_body_operator) -from openfermion.testing.testing_utils import ( - random_diagonal_coulomb_hamiltonian) +from openfermion.transforms.opconversions.commutator_diagonal_coulomb_operator import ( + commutator_ordered_diagonal_coulomb_with_two_body_operator, +) +from openfermion.testing.testing_utils import random_diagonal_coulomb_hamiltonian class DiagonalHamiltonianCommutatorTest(unittest.TestCase): - def test_commutator(self): operator_a = ( - FermionOperator('0^ 0', 0.3) + FermionOperator('1^ 1', 0.1j) + - FermionOperator('1^ 0^ 1 0', -0.2) + FermionOperator('1^ 3') + - FermionOperator('3^ 0') + FermionOperator('3^ 2', 0.017) - - FermionOperator('2^ 3', 1.99) + FermionOperator('3^ 1^ 3 1', .09) + - FermionOperator('2^ 0^ 2 0', .126j) + FermionOperator('4^ 2^ 4 2') + - FermionOperator('3^ 0^ 3 0')) + FermionOperator('0^ 0', 0.3) + + FermionOperator('1^ 1', 0.1j) + + FermionOperator('1^ 0^ 1 0', -0.2) + + FermionOperator('1^ 3') + + FermionOperator('3^ 0') + + FermionOperator('3^ 2', 0.017) + - FermionOperator('2^ 3', 1.99) + + FermionOperator('3^ 1^ 3 1', 0.09) + + FermionOperator('2^ 0^ 2 0', 0.126j) + + FermionOperator('4^ 2^ 4 2') + + FermionOperator('3^ 0^ 3 0') + ) operator_b = ( - FermionOperator('3^ 1', 0.7) + FermionOperator('1^ 3', -9.) + - FermionOperator('1^ 0^ 3 0', 0.1) - - FermionOperator('3^ 0^ 1 0', 0.11) + FermionOperator('3^ 2^ 3 2') + - FermionOperator('3^ 1^ 3 1', -1.37) + FermionOperator('4^ 2^ 4 2') + - FermionOperator('4^ 1^ 4 1') + FermionOperator('1^ 0^ 4 0', 16.7) + - FermionOperator('1^ 0^ 4 3', 1.67) + - FermionOperator('4^ 3^ 5 2', 1.789j) + - FermionOperator('6^ 5^ 4 1', -11.789j)) + FermionOperator('3^ 1', 0.7) + + FermionOperator('1^ 3', -9.0) + + FermionOperator('1^ 0^ 3 0', 0.1) + - FermionOperator('3^ 0^ 1 0', 0.11) + + FermionOperator('3^ 2^ 3 2') + + FermionOperator('3^ 1^ 3 1', -1.37) + + FermionOperator('4^ 2^ 4 2') + + FermionOperator('4^ 1^ 4 1') + + FermionOperator('1^ 0^ 4 0', 16.7) + + FermionOperator('1^ 0^ 4 3', 1.67) + + FermionOperator('4^ 3^ 5 2', 1.789j) + + FermionOperator('6^ 5^ 4 1', -11.789j) + ) reference = normal_ordered(commutator(operator_a, operator_b)) - result = commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b) + result = commutator_ordered_diagonal_coulomb_with_two_body_operator(operator_a, operator_b) diff = result - reference self.assertTrue(diff.isclose(FermionOperator.zero())) def test_nonstandard_second_arg(self): - operator_a = (FermionOperator('0^ 0', 0.3) + - FermionOperator('1^ 1', 0.1j) + - FermionOperator('2^ 0^ 2 0', -0.2) + - FermionOperator('2^ 1^ 2 1', -0.2j) + - FermionOperator('1^ 3') + FermionOperator('3^ 0') + - FermionOperator('4^ 4', -1.4j)) - - operator_b = (FermionOperator('4^ 1^ 3 0', 0.1) - - FermionOperator('3^ 0^ 1 0', 0.11)) - - reference = (FermionOperator('1^ 0^ 1 0', -0.11) + - FermionOperator('3^ 0^ 1 0', 0.011j) + - FermionOperator('3^ 0^ 3 0', 0.11) + - FermionOperator('3^ 2^ 0^ 2 1 0', -0.022j) + - FermionOperator('4^ 1^ 3 0', -0.03 - 0.13j) + - FermionOperator('4^ 2^ 1^ 3 2 0', -0.02 + 0.02j)) - - res = commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b) + operator_a = ( + FermionOperator('0^ 0', 0.3) + + FermionOperator('1^ 1', 0.1j) + + FermionOperator('2^ 0^ 2 0', -0.2) + + FermionOperator('2^ 1^ 2 1', -0.2j) + + FermionOperator('1^ 3') + + FermionOperator('3^ 0') + + FermionOperator('4^ 4', -1.4j) + ) + + operator_b = FermionOperator('4^ 1^ 3 0', 0.1) - FermionOperator('3^ 0^ 1 0', 0.11) + + reference = ( + FermionOperator('1^ 0^ 1 0', -0.11) + + FermionOperator('3^ 0^ 1 0', 0.011j) + + FermionOperator('3^ 0^ 3 0', 0.11) + + FermionOperator('3^ 2^ 0^ 2 1 0', -0.022j) + + FermionOperator('4^ 1^ 3 0', -0.03 - 0.13j) + + FermionOperator('4^ 2^ 1^ 3 2 0', -0.02 + 0.02j) + ) + + res = commutator_ordered_diagonal_coulomb_with_two_body_operator(operator_a, operator_b) self.assertTrue(res.isclose(reference)) @@ -83,13 +94,13 @@ def test_add_to_existing_result(self): operator_b = FermionOperator('0^ 2') commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b, prior_terms=prior_terms) + operator_a, operator_b, prior_terms=prior_terms + ) self.assertTrue(prior_terms.isclose(FermionOperator.zero())) def test_integration_jellium_hamiltonian_with_negation(self): - hamiltonian = normal_ordered( - jellium_model(Grid(2, 3, 1.), plane_wave=False)) + hamiltonian = normal_ordered(jellium_model(Grid(2, 3, 1.0), plane_wave=False)) part_a = FermionOperator.zero() part_b = FermionOperator.zero() @@ -104,13 +115,11 @@ def test_integration_jellium_hamiltonian_with_negation(self): add_to_a_or_b ^= 1 reference = normal_ordered(commutator(part_a, part_b)) - result = commutator_ordered_diagonal_coulomb_with_two_body_operator( - part_a, part_b) + result = commutator_ordered_diagonal_coulomb_with_two_body_operator(part_a, part_b) self.assertTrue(result.isclose(reference)) - negative = commutator_ordered_diagonal_coulomb_with_two_body_operator( - part_b, part_a) + negative = commutator_ordered_diagonal_coulomb_with_two_body_operator(part_b, part_a) result += negative self.assertTrue(result.isclose(FermionOperator.zero())) @@ -121,9 +130,9 @@ def test_no_warning_on_nonstandard_input_second_arg(self): operator_b = FermionOperator('4^ 3^ 4 1') reference = FermionOperator('4^ 3^ 2^ 4 2 1') - result = ( - commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b)) + result = commutator_ordered_diagonal_coulomb_with_two_body_operator( + operator_a, operator_b + ) self.assertFalse(w) @@ -136,13 +145,12 @@ def test_warning_on_bad_input_first_arg(self): operator_b = FermionOperator('3^ 2^ 3 2') reference = normal_ordered(commutator(operator_a, operator_b)) - result = ( - commutator_ordered_diagonal_coulomb_with_two_body_operator( - operator_a, operator_b)) + result = commutator_ordered_diagonal_coulomb_with_two_body_operator( + operator_a, operator_b + ) self.assertTrue(len(w) == 1) - self.assertIn('Defaulted to standard commutator evaluation', - str(w[-1].message)) + self.assertIn('Defaulted to standard commutator evaluation', str(w[-1].message)) # Result should still be correct in this case. diff = result - reference @@ -150,14 +158,15 @@ def test_warning_on_bad_input_first_arg(self): def test_integration_random_diagonal_coulomb_hamiltonian(self): hamiltonian1 = normal_ordered( - get_fermion_operator( - random_diagonal_coulomb_hamiltonian(n_qubits=7))) + get_fermion_operator(random_diagonal_coulomb_hamiltonian(n_qubits=7)) + ) hamiltonian2 = normal_ordered( - get_fermion_operator( - random_diagonal_coulomb_hamiltonian(n_qubits=7))) + get_fermion_operator(random_diagonal_coulomb_hamiltonian(n_qubits=7)) + ) reference = normal_ordered(commutator(hamiltonian1, hamiltonian2)) result = commutator_ordered_diagonal_coulomb_with_two_body_operator( - hamiltonian1, hamiltonian2) + hamiltonian1, hamiltonian2 + ) self.assertTrue(result.isclose(reference)) diff --git a/src/openfermion/transforms/opconversions/conversions.py b/src/openfermion/transforms/opconversions/conversions.py index 5702ac8fa..95f45b63c 100644 --- a/src/openfermion/transforms/opconversions/conversions.py +++ b/src/openfermion/transforms/opconversions/conversions.py @@ -14,15 +14,13 @@ import numpy import sympy -from openfermion.ops.operators import (QuadOperator, BosonOperator, - FermionOperator, MajoranaOperator) -from openfermion.ops.representations import (PolynomialTensor, - DiagonalCoulombHamiltonian) +from openfermion.ops.operators import QuadOperator, BosonOperator, FermionOperator, MajoranaOperator +from openfermion.ops.representations import PolynomialTensor, DiagonalCoulombHamiltonian from openfermion.utils.operator_utils import count_qubits -def get_quad_operator(operator, hbar=1.): +def get_quad_operator(operator, hbar=1.0): """Convert to QuadOperator. Args: @@ -40,14 +38,13 @@ def get_quad_operator(operator, hbar=1.): for term, coefficient in operator.terms.items(): tmp = QuadOperator('', coefficient) for i, d in term: - tmp *= (1./numpy.sqrt(2.*hbar)) \ - * (QuadOperator(((i, 'q'))) - + QuadOperator(((i, 'p')), 1j*(-1)**d)) + tmp *= (1.0 / numpy.sqrt(2.0 * hbar)) * ( + QuadOperator(((i, 'q'))) + QuadOperator(((i, 'p')), 1j * (-1) ** d) + ) quad_operator += tmp else: - raise TypeError("Only BosonOperator is currently " - "supported for get_quad_operator.") + raise TypeError("Only BosonOperator is currently " "supported for get_quad_operator.") return quad_operator @@ -62,12 +59,14 @@ def check_no_sympy(operator): """ for key in operator.terms: if isinstance(operator.terms[key], sympy.Expr): - raise TypeError('This conversion is currently not supported ' + - 'for operators with sympy expressions ' + - 'as coefficients') + raise TypeError( + 'This conversion is currently not supported ' + + 'for operators with sympy expressions ' + + 'as coefficients' + ) -def get_boson_operator(operator, hbar=1.): +def get_boson_operator(operator, hbar=1.0): """Convert to BosonOperator. Args: @@ -92,13 +91,11 @@ def get_boson_operator(operator, hbar=1.): coeff = -1j * numpy.sqrt(hbar / 2) sign = -1 - tmp *= coeff * (BosonOperator(((i, 0))) + BosonOperator( - ((i, 1)), sign)) + tmp *= coeff * (BosonOperator(((i, 0))) + BosonOperator(((i, 1)), sign)) boson_operator += tmp else: - raise TypeError("Only QuadOperator is currently " - "supported for get_boson_operator.") + raise TypeError("Only QuadOperator is currently " "supported for get_boson_operator.") return boson_operator @@ -116,8 +113,7 @@ def get_fermion_operator(operator): elif isinstance(operator, MajoranaOperator): return _majorana_operator_to_fermion_operator(operator) else: - raise TypeError('{} cannot be converted to FermionOperator'.format( - type(operator))) + raise TypeError('{} cannot be converted to FermionOperator'.format(type(operator))) def _polynomial_tensor_to_fermion_operator(operator): @@ -132,10 +128,10 @@ def _diagonal_coulomb_hamiltonian_to_fermion_operator(operator): n_qubits = count_qubits(operator) fermion_operator += FermionOperator((), operator.constant) for p, q in itertools.product(range(n_qubits), repeat=2): - fermion_operator += FermionOperator(((p, 1), (q, 0)), - operator.one_body[p, q]) - fermion_operator += FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), - operator.two_body[p, q]) + fermion_operator += FermionOperator(((p, 1), (q, 0)), operator.one_body[p, q]) + fermion_operator += FermionOperator( + ((p, 1), (p, 0), (q, 1), (q, 0)), operator.two_body[p, q] + ) return fermion_operator @@ -163,8 +159,8 @@ def _majorana_term_to_fermion_operator(term): def get_majorana_operator( - operator: Union[PolynomialTensor, DiagonalCoulombHamiltonian, - FermionOperator]) -> MajoranaOperator: + operator: Union[PolynomialTensor, DiagonalCoulombHamiltonian, FermionOperator] +) -> MajoranaOperator: """ Convert to MajoranaOperator. @@ -188,14 +184,11 @@ def get_majorana_operator( if isinstance(operator, FermionOperator): return _fermion_operator_to_majorana_operator(operator) elif isinstance(operator, (PolynomialTensor, DiagonalCoulombHamiltonian)): - return _fermion_operator_to_majorana_operator( - get_fermion_operator(operator)) - raise TypeError('{} cannot be converted to MajoranaOperator'.format( - type(operator))) + return _fermion_operator_to_majorana_operator(get_fermion_operator(operator)) + raise TypeError('{} cannot be converted to MajoranaOperator'.format(type(operator))) -def _fermion_operator_to_majorana_operator(fermion_operator: FermionOperator - ) -> MajoranaOperator: +def _fermion_operator_to_majorana_operator(fermion_operator: FermionOperator) -> MajoranaOperator: """ Convert FermionOperator to MajoranaOperator. diff --git a/src/openfermion/transforms/opconversions/conversions_test.py b/src/openfermion/transforms/opconversions/conversions_test.py index 3867c648f..5ef2c303f 100644 --- a/src/openfermion/transforms/opconversions/conversions_test.py +++ b/src/openfermion/transforms/opconversions/conversions_test.py @@ -15,21 +15,28 @@ import numpy import sympy -from openfermion.ops.operators import (QuadOperator, BosonOperator, - FermionOperator, MajoranaOperator, - QubitOperator) -from openfermion.transforms.repconversions.conversions import ( - get_diagonal_coulomb_hamiltonian) +from openfermion.ops.operators import ( + QuadOperator, + BosonOperator, + FermionOperator, + MajoranaOperator, + QubitOperator, +) +from openfermion.transforms.repconversions.conversions import get_diagonal_coulomb_hamiltonian from openfermion.transforms.opconversions.term_reordering import normal_ordered from openfermion.transforms.opconversions.conversions import ( - get_quad_operator, get_boson_operator, get_majorana_operator, - get_fermion_operator, check_no_sympy, - _fermion_operator_to_majorana_operator, _fermion_term_to_majorana_operator) + get_quad_operator, + get_boson_operator, + get_majorana_operator, + get_fermion_operator, + check_no_sympy, + _fermion_operator_to_majorana_operator, + _fermion_term_to_majorana_operator, +) class GetQuadOperatorTest(unittest.TestCase): - def setUp(self): self.hbar = 0.5 @@ -66,37 +73,53 @@ def test_two_mode(self): b = BosonOperator('0^ 2') q = get_quad_operator(b, hbar=self.hbar) expected = QuadOperator('q0') - 1j * QuadOperator('p0') - expected *= (QuadOperator('q2') + 1j * QuadOperator('p2')) + expected *= QuadOperator('q2') + 1j * QuadOperator('p2') expected /= 2 * self.hbar self.assertTrue(q == expected) def test_two_term(self): b = BosonOperator('0^ 0') + BosonOperator('0 0^') q = get_quad_operator(b, hbar=self.hbar) - expected = (QuadOperator('q0') - 1j*QuadOperator('p0')) \ - * (QuadOperator('q0') + 1j*QuadOperator('p0')) \ - + (QuadOperator('q0') + 1j*QuadOperator('p0')) \ - * (QuadOperator('q0') - 1j*QuadOperator('p0')) + expected = (QuadOperator('q0') - 1j * QuadOperator('p0')) * ( + QuadOperator('q0') + 1j * QuadOperator('p0') + ) + (QuadOperator('q0') + 1j * QuadOperator('p0')) * ( + QuadOperator('q0') - 1j * QuadOperator('p0') + ) expected /= 2 * self.hbar self.assertTrue(q == expected) def test_q_squared(self): - b = self.hbar * (BosonOperator('0^ 0^') + BosonOperator('0 0') + - BosonOperator('') + 2 * BosonOperator('0^ 0')) / 2 + b = ( + self.hbar + * ( + BosonOperator('0^ 0^') + + BosonOperator('0 0') + + BosonOperator('') + + 2 * BosonOperator('0^ 0') + ) + / 2 + ) q = normal_ordered(get_quad_operator(b, hbar=self.hbar), hbar=self.hbar) expected = QuadOperator('q0 q0') self.assertTrue(q == expected) def test_p_squared(self): - b = self.hbar * (-BosonOperator('1^ 1^') - BosonOperator('1 1') + - BosonOperator('') + 2 * BosonOperator('1^ 1')) / 2 + b = ( + self.hbar + * ( + -BosonOperator('1^ 1^') + - BosonOperator('1 1') + + BosonOperator('') + + 2 * BosonOperator('1^ 1') + ) + / 2 + ) q = normal_ordered(get_quad_operator(b, hbar=self.hbar), hbar=self.hbar) expected = QuadOperator('p1 p1') self.assertTrue(q == expected) class GetBosonOperatorTest(unittest.TestCase): - def setUp(self): self.hbar = 0.5 @@ -132,31 +155,48 @@ def test_p(self): def test_two_mode(self): q = QuadOperator('p2 q0') b = get_boson_operator(q, hbar=self.hbar) - expected = -1j*self.hbar/2 \ - * (BosonOperator('0') + BosonOperator('0^')) \ + expected = ( + -1j + * self.hbar + / 2 + * (BosonOperator('0') + BosonOperator('0^')) * (BosonOperator('2') - BosonOperator('2^')) + ) self.assertTrue(b == expected) def test_two_term(self): q = QuadOperator('p0 q0') + QuadOperator('q0 p0') b = get_boson_operator(q, hbar=self.hbar) - expected = -1j*self.hbar/2 \ - * ((BosonOperator('0') + BosonOperator('0^')) - * (BosonOperator('0') - BosonOperator('0^')) - + (BosonOperator('0') - BosonOperator('0^')) - * (BosonOperator('0') + BosonOperator('0^'))) + expected = ( + -1j + * self.hbar + / 2 + * ( + (BosonOperator('0') + BosonOperator('0^')) + * (BosonOperator('0') - BosonOperator('0^')) + + (BosonOperator('0') - BosonOperator('0^')) + * (BosonOperator('0') + BosonOperator('0^')) + ) + ) self.assertTrue(b == expected) def test_get_fermion_operator_majorana_operator(): a = MajoranaOperator((0, 3), 2.0) + MajoranaOperator((1, 2, 3)) op = get_fermion_operator(a) - expected_op = (-2j * (FermionOperator(((0, 0), (1, 0))) - FermionOperator( - ((0, 0), (1, 1))) + FermionOperator(((0, 1), (1, 0))) - FermionOperator( - ((0, 1), (1, 1)))) - 2 * FermionOperator( - ((0, 0), (1, 1), (1, 0))) + 2 * FermionOperator( - ((0, 1), (1, 1), (1, 0))) + FermionOperator( - (0, 0)) - FermionOperator((0, 1))) + expected_op = ( + -2j + * ( + FermionOperator(((0, 0), (1, 0))) + - FermionOperator(((0, 0), (1, 1))) + + FermionOperator(((0, 1), (1, 0))) + - FermionOperator(((0, 1), (1, 1))) + ) + - 2 * FermionOperator(((0, 0), (1, 1), (1, 0))) + + 2 * FermionOperator(((0, 1), (1, 1), (1, 0))) + + FermionOperator((0, 0)) + - FermionOperator((0, 1)) + ) assert normal_ordered(op) == normal_ordered(expected_op) @@ -179,34 +219,34 @@ def test_raises(self): def test_get_majorana_operator_fermion_operator(self): """Test conversion FermionOperator to MajoranaOperator.""" - fermion_op = (-2j * (FermionOperator( - ((0, 0), (1, 0))) - FermionOperator( - ((0, 0), (1, 1))) + FermionOperator( - ((0, 1), (1, 0))) - FermionOperator( - ((0, 1), (1, 1)))) - 2 * FermionOperator( - ((0, 0), (1, 1), (1, 0))) + 2 * FermionOperator( - ((0, 1), (1, 1), (1, 0))) + FermionOperator( - (0, 0)) - FermionOperator((0, 1))) + fermion_op = ( + -2j + * ( + FermionOperator(((0, 0), (1, 0))) + - FermionOperator(((0, 0), (1, 1))) + + FermionOperator(((0, 1), (1, 0))) + - FermionOperator(((0, 1), (1, 1))) + ) + - 2 * FermionOperator(((0, 0), (1, 1), (1, 0))) + + 2 * FermionOperator(((0, 1), (1, 1), (1, 0))) + + FermionOperator((0, 0)) + - FermionOperator((0, 1)) + ) majorana_op = get_majorana_operator(fermion_op) - expected_op = (MajoranaOperator((0, 3), 2.0) + MajoranaOperator( - (1, 2, 3))) + expected_op = MajoranaOperator((0, 3), 2.0) + MajoranaOperator((1, 2, 3)) self.assertTrue(majorana_op == expected_op) def test_get_majorana_operator_diagonalcoulomb(self): """Test get majorana from Diagonal Coulomb.""" - fermion_op = (FermionOperator('0^ 1', 1.0) + - FermionOperator('1^ 0', 1.0)) + fermion_op = FermionOperator('0^ 1', 1.0) + FermionOperator('1^ 0', 1.0) diagonal_ham = get_diagonal_coulomb_hamiltonian(fermion_op) - self.assertTrue( - get_majorana_operator(diagonal_ham) == get_majorana_operator( - fermion_op)) + self.assertTrue(get_majorana_operator(diagonal_ham) == get_majorana_operator(fermion_op)) class RaisesSympyExceptionTest(unittest.TestCase): - def test_raises_sympy_expression(self): operator = FermionOperator('0^', sympy.Symbol('x')) with self.assertRaises(TypeError): diff --git a/src/openfermion/transforms/opconversions/fenwick_tree.py b/src/openfermion/transforms/opconversions/fenwick_tree.py index 61363c37f..e491e31d1 100644 --- a/src/openfermion/transforms/opconversions/fenwick_tree.py +++ b/src/openfermion/transforms/opconversions/fenwick_tree.py @@ -14,6 +14,7 @@ class FenwickNode: """Fenwick Tree node.""" + parent = None children = None index = None @@ -53,6 +54,7 @@ class FenwickTree: a reference to the update set (U), the parity set (P) and the children set (F) sets of the Fenwick. """ + # Root node. root = None diff --git a/src/openfermion/transforms/opconversions/fenwick_tree_test.py b/src/openfermion/transforms/opconversions/fenwick_tree_test.py index 3dad1c2aa..85c209155 100644 --- a/src/openfermion/transforms/opconversions/fenwick_tree_test.py +++ b/src/openfermion/transforms/opconversions/fenwick_tree_test.py @@ -18,7 +18,6 @@ class FenwickTreeTest(unittest.TestCase): - def setUp(self): pass diff --git a/src/openfermion/transforms/opconversions/jordan_wigner.py b/src/openfermion/transforms/opconversions/jordan_wigner.py index 095bdd20f..60131e709 100644 --- a/src/openfermion/transforms/opconversions/jordan_wigner.py +++ b/src/openfermion/transforms/opconversions/jordan_wigner.py @@ -14,15 +14,13 @@ import numpy -from openfermion.ops.operators import (FermionOperator, MajoranaOperator, - QubitOperator) -from openfermion.ops.representations import (DiagonalCoulombHamiltonian, - InteractionOperator) +from openfermion.ops.operators import FermionOperator, MajoranaOperator, QubitOperator +from openfermion.ops.representations import DiagonalCoulombHamiltonian, InteractionOperator from openfermion.utils.operator_utils import count_qubits def jordan_wigner(operator): - r""" Apply the Jordan-Wigner transform to a FermionOperator, + r"""Apply the Jordan-Wigner transform to a FermionOperator, InteractionOperator, or DiagonalCoulombHamiltonian to convert to a QubitOperator. @@ -49,10 +47,12 @@ def jordan_wigner(operator): return _jordan_wigner_diagonal_coulomb_hamiltonian(operator) if isinstance(operator, InteractionOperator): return _jordan_wigner_interaction_op(operator) - raise TypeError("Operator must be a FermionOperator, " - "MajoranaOperator, " - "DiagonalCoulombHamiltonian, or " - "InteractionOperator.") + raise TypeError( + "Operator must be a FermionOperator, " + "MajoranaOperator, " + "DiagonalCoulombHamiltonian, or " + "InteractionOperator." + ) def _jordan_wigner_fermion_operator(operator): @@ -65,18 +65,17 @@ def _jordan_wigner_fermion_operator(operator): # Loop through operators, transform and multiply. for ladder_operator in term: if ladder_operator not in lookup_ladder_terms: - z_factors = tuple( - (index, 'Z') for index in range(ladder_operator[0])) - pauli_x_component = QubitOperator( - z_factors + ((ladder_operator[0], 'X'),), 0.5) + z_factors = tuple((index, 'Z') for index in range(ladder_operator[0])) + pauli_x_component = QubitOperator(z_factors + ((ladder_operator[0], 'X'),), 0.5) if ladder_operator[1]: pauli_y_component = QubitOperator( - z_factors + ((ladder_operator[0], 'Y'),), -0.5j) + z_factors + ((ladder_operator[0], 'Y'),), -0.5j + ) else: pauli_y_component = QubitOperator( - z_factors + ((ladder_operator[0], 'Y'),), 0.5j) - lookup_ladder_terms[ladder_operator] = (pauli_x_component + - pauli_y_component) + z_factors + ((ladder_operator[0], 'Y'),), 0.5j + ) + lookup_ladder_terms[ladder_operator] = pauli_x_component + pauli_y_component transformed_term *= lookup_ladder_terms[ladder_operator] transformed_operator += transformed_term return transformed_operator @@ -102,8 +101,8 @@ def _jordan_wigner_diagonal_coulomb_hamiltonian(operator): # Transform diagonal one-body terms for p in range(n_qubits): coefficient = operator.one_body[p, p] + operator.two_body[p, p] - qubit_operator += QubitOperator(((p, 'Z'),), -.5 * coefficient) - qubit_operator += QubitOperator((), .5 * coefficient) + qubit_operator += QubitOperator(((p, 'Z'),), -0.5 * coefficient) + qubit_operator += QubitOperator((), 0.5 * coefficient) # Transform other one-body terms and two-body terms for p, q in itertools.combinations(range(n_qubits), 2): @@ -111,21 +110,17 @@ def _jordan_wigner_diagonal_coulomb_hamiltonian(operator): real_part = numpy.real(operator.one_body[p, q]) imag_part = numpy.imag(operator.one_body[p, q]) parity_string = [(i, 'Z') for i in range(p + 1, q)] - qubit_operator += QubitOperator([(p, 'X')] + parity_string + [(q, 'X')], - .5 * real_part) - qubit_operator += QubitOperator([(p, 'Y')] + parity_string + [(q, 'Y')], - .5 * real_part) - qubit_operator += QubitOperator([(p, 'Y')] + parity_string + [(q, 'X')], - .5 * imag_part) - qubit_operator += QubitOperator([(p, 'X')] + parity_string + [(q, 'Y')], - -.5 * imag_part) + qubit_operator += QubitOperator([(p, 'X')] + parity_string + [(q, 'X')], 0.5 * real_part) + qubit_operator += QubitOperator([(p, 'Y')] + parity_string + [(q, 'Y')], 0.5 * real_part) + qubit_operator += QubitOperator([(p, 'Y')] + parity_string + [(q, 'X')], 0.5 * imag_part) + qubit_operator += QubitOperator([(p, 'X')] + parity_string + [(q, 'Y')], -0.5 * imag_part) # Two-body coefficient = operator.two_body[p, q] - qubit_operator += QubitOperator(((p, 'Z'), (q, 'Z')), .5 * coefficient) - qubit_operator += QubitOperator((p, 'Z'), -.5 * coefficient) - qubit_operator += QubitOperator((q, 'Z'), -.5 * coefficient) - qubit_operator += QubitOperator((), .5 * coefficient) + qubit_operator += QubitOperator(((p, 'Z'), (q, 'Z')), 0.5 * coefficient) + qubit_operator += QubitOperator((p, 'Z'), -0.5 * coefficient) + qubit_operator += QubitOperator((q, 'Z'), -0.5 * coefficient) + qubit_operator += QubitOperator((), 0.5 * coefficient) return qubit_operator @@ -157,41 +152,36 @@ def _jordan_wigner_interaction_op(iop, n_qubits=None): # Transform other one-body terms and "diagonal" two-body terms for p, q in itertools.combinations(range(n_qubits), 2): # One-body - coefficient = .5 * (iop[(p, 1), (q, 0)] + iop[(q, 1), - (p, 0)].conjugate()) + coefficient = 0.5 * (iop[(p, 1), (q, 0)] + iop[(q, 1), (p, 0)].conjugate()) qubit_operator += jordan_wigner_one_body(p, q, coefficient) # Two-body - coefficient = (iop[(p, 1), (q, 1), (p, 0), - (q, 0)] - iop[(p, 1), (q, 1), (q, 0), - (p, 0)] - iop[(q, 1), (p, 1), (p, 0), - (q, 0)] + iop[(q, 1), - (p, 1), - (q, 0), - (p, 0)]) + coefficient = ( + iop[(p, 1), (q, 1), (p, 0), (q, 0)] + - iop[(p, 1), (q, 1), (q, 0), (p, 0)] + - iop[(q, 1), (p, 1), (p, 0), (q, 0)] + + iop[(q, 1), (p, 1), (q, 0), (p, 0)] + ) qubit_operator += jordan_wigner_two_body(p, q, p, q, coefficient) # Transform the rest of the two-body terms - for (p, q), (r, s) in itertools.combinations( - itertools.combinations(range(n_qubits), 2), 2): - coefficient = 0.5 * (iop[(p, 1), (q, 1), (r, 0), - (s, 0)] + iop[(s, 1), (r, 1), (q, 0), - (p, 0)].conjugate() - - iop[(p, 1), (q, 1), (s, 0), - (r, 0)] - iop[(r, 1), (s, 1), (q, 0), - (p, 0)].conjugate() - - iop[(q, 1), (p, 1), (r, 0), - (s, 0)] - iop[(s, 1), (r, 1), (p, 0), - (q, 0)].conjugate() + - iop[(q, 1), (p, 1), (s, 0), - (r, 0)] + iop[(r, 1), (s, 1), (p, 0), - (q, 0)].conjugate()) + for (p, q), (r, s) in itertools.combinations(itertools.combinations(range(n_qubits), 2), 2): + coefficient = 0.5 * ( + iop[(p, 1), (q, 1), (r, 0), (s, 0)] + + iop[(s, 1), (r, 1), (q, 0), (p, 0)].conjugate() + - iop[(p, 1), (q, 1), (s, 0), (r, 0)] + - iop[(r, 1), (s, 1), (q, 0), (p, 0)].conjugate() + - iop[(q, 1), (p, 1), (r, 0), (s, 0)] + - iop[(s, 1), (r, 1), (p, 0), (q, 0)].conjugate() + + iop[(q, 1), (p, 1), (s, 0), (r, 0)] + + iop[(r, 1), (s, 1), (p, 0), (q, 0)].conjugate() + ) qubit_operator += jordan_wigner_two_body(p, q, r, s, coefficient) return qubit_operator -def jordan_wigner_one_body(p, q, coefficient=1.): +def jordan_wigner_one_body(p, q, coefficient=1.0): r"""Map the term a^\dagger_p a_q + h.c. to QubitOperator. Note that the diagonal terms are divided by a factor of 2 @@ -204,22 +194,24 @@ def jordan_wigner_one_body(p, q, coefficient=1.): p, q = q, p coefficient = coefficient.conjugate() parity_string = tuple((z, 'Z') for z in range(p + 1, q)) - for c, (op_a, op_b) in [(coefficient.real, 'XX'), - (coefficient.real, 'YY'), - (coefficient.imag, 'YX'), - (-coefficient.imag, 'XY')]: + for c, (op_a, op_b) in [ + (coefficient.real, 'XX'), + (coefficient.real, 'YY'), + (coefficient.imag, 'YX'), + (-coefficient.imag, 'XY'), + ]: operators = ((p, op_a),) + parity_string + ((q, op_b),) - qubit_operator += QubitOperator(operators, .5 * c) + qubit_operator += QubitOperator(operators, 0.5 * c) # Handle diagonal terms. else: - qubit_operator += QubitOperator((), .5 * coefficient) - qubit_operator += QubitOperator(((p, 'Z'),), -.5 * coefficient) + qubit_operator += QubitOperator((), 0.5 * coefficient) + qubit_operator += QubitOperator(((p, 'Z'),), -0.5 * coefficient) return qubit_operator -def jordan_wigner_two_body(p, q, r, s, coefficient=1.): +def jordan_wigner_two_body(p, q, r, s, coefficient=1.0): r"""Map the term a^\dagger_p a^\dagger_q a_r a_s + h.c. to QubitOperator. Note that the diagonal terms are divided by a factor of two @@ -240,19 +232,20 @@ def jordan_wigner_two_body(p, q, r, s, coefficient=1.): for ops in itertools.product('XY', repeat=4): # Get coefficients. if ops.count('X') % 2: - coeff = .125 * coefficient.imag + coeff = 0.125 * coefficient.imag if ''.join(ops) in ['XYXX', 'YXXX', 'YYXY', 'YYYX']: coeff *= -1 else: - coeff = .125 * coefficient.real + coeff = 0.125 * coefficient.real if ''.join(ops) not in ['XXYY', 'YYXX']: coeff *= -1 if not coeff: continue # Sort operators. - [(a, operator_a), (b, operator_b), (c, operator_c), - (d, operator_d)] = sorted(zip([p, q, r, s], ops)) + [(a, operator_a), (b, operator_b), (c, operator_c), (d, operator_d)] = sorted( + zip([p, q, r, s], ops) + ) # Compute operator strings. operators = ((a, operator_a),) @@ -267,7 +260,6 @@ def jordan_wigner_two_body(p, q, r, s, coefficient=1.): # Handle case of three unique indices. elif len(set([p, q, r, s])) == 3: - # Identify equal tensor factors. if p == r: if q > s: @@ -303,10 +295,12 @@ def jordan_wigner_two_body(p, q, r, s, coefficient=1.): # Get operators. parity_string = tuple((z, 'Z') for z in range(a + 1, b)) pauli_z = QubitOperator(((c, 'Z'),)) - for c, (op_a, op_b) in [(coefficient.real, 'XX'), - (coefficient.real, 'YY'), - (coefficient.imag, 'YX'), - (-coefficient.imag, 'XY')]: + for c, (op_a, op_b) in [ + (coefficient.real, 'XX'), + (coefficient.real, 'YY'), + (coefficient.imag, 'YX'), + (-coefficient.imag, 'XY'), + ]: operators = ((a, op_a),) + parity_string + ((b, op_b),) if not c: continue @@ -318,18 +312,16 @@ def jordan_wigner_two_body(p, q, r, s, coefficient=1.): # Handle case of two unique indices. elif len(set([p, q, r, s])) == 2: - # Get coefficient. if p == s: - coeff = -.25 * coefficient + coeff = -0.25 * coefficient else: - coeff = .25 * coefficient + coeff = 0.25 * coefficient # Add terms. qubit_operator -= QubitOperator((), coeff) qubit_operator += QubitOperator(((p, 'Z'),), coeff) qubit_operator += QubitOperator(((q, 'Z'),), coeff) - qubit_operator -= QubitOperator(((min(q, p), 'Z'), (max(q, p), 'Z')), - coeff) + qubit_operator -= QubitOperator(((min(q, p), 'Z'), (max(q, p), 'Z')), coeff) return qubit_operator diff --git a/src/openfermion/transforms/opconversions/jordan_wigner_test.py b/src/openfermion/transforms/opconversions/jordan_wigner_test.py index 70350d297..baf4864f3 100644 --- a/src/openfermion/transforms/opconversions/jordan_wigner_test.py +++ b/src/openfermion/transforms/opconversions/jordan_wigner_test.py @@ -20,25 +20,29 @@ from openfermion.config import DATA_DIRECTORY from openfermion.hamiltonians import fermi_hubbard, number_operator from openfermion.chem import MolecularData -from openfermion.ops.operators import (FermionOperator, MajoranaOperator, - QubitOperator) +from openfermion.ops.operators import FermionOperator, MajoranaOperator, QubitOperator from openfermion.ops.representations import InteractionOperator -from openfermion.transforms.opconversions import (get_fermion_operator, - reverse_jordan_wigner) +from openfermion.transforms.opconversions import get_fermion_operator, reverse_jordan_wigner from openfermion.transforms.repconversions import ( - get_diagonal_coulomb_hamiltonian, get_interaction_operator) + get_diagonal_coulomb_hamiltonian, + get_interaction_operator, +) from openfermion.utils import hermitian_conjugated from openfermion.transforms.opconversions import normal_ordered -from openfermion.testing.testing_utils import (random_interaction_operator, - random_quadratic_hamiltonian) +from openfermion.testing.testing_utils import ( + random_interaction_operator, + random_quadratic_hamiltonian, +) from openfermion.transforms.opconversions.jordan_wigner import ( - jordan_wigner, jordan_wigner_one_body, jordan_wigner_two_body, - _jordan_wigner_interaction_op) + jordan_wigner, + jordan_wigner_one_body, + jordan_wigner_two_body, + _jordan_wigner_interaction_op, +) class JordanWignerTransformTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 @@ -152,14 +156,10 @@ def test_transm_lower0(self): def test_transm_raise3lower0(self): # recall that creation gets -1j on Y and annihilation gets +1j on Y. term = jordan_wigner(FermionOperator(((3, 1), (0, 0)))) - self.assertEqual(term.terms[((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'))], - 0.25 * 1 * -1j) - self.assertEqual(term.terms[((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'))], - 0.25 * 1j * -1j) - self.assertEqual(term.terms[((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'))], - 0.25 * 1j * 1) - self.assertEqual(term.terms[((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'))], - 0.25 * 1 * 1) + self.assertEqual(term.terms[((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'Y'))], 0.25 * 1 * -1j) + self.assertEqual(term.terms[((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'Y'))], 0.25 * 1j * -1j) + self.assertEqual(term.terms[((0, 'Y'), (1, 'Z'), (2, 'Z'), (3, 'X'))], 0.25 * 1j * 1) + self.assertEqual(term.terms[((0, 'X'), (1, 'Z'), (2, 'Z'), (3, 'X'))], 0.25 * 1 * 1) def test_transm_number(self): n = number_operator(self.n_qubits, 3) @@ -213,34 +213,28 @@ def test_ccr_offsite_odd_aa(self): def test_ccr_onsite(self): c1 = FermionOperator(((1, 1),)) a1 = hermitian_conjugated(c1) - self.assertTrue( - normal_ordered(c1 * a1) == FermionOperator(()) - - normal_ordered(a1 * c1)) - self.assertTrue( - jordan_wigner(c1 * a1) == QubitOperator(()) - - jordan_wigner(a1 * c1)) + self.assertTrue(normal_ordered(c1 * a1) == FermionOperator(()) - normal_ordered(a1 * c1)) + self.assertTrue(jordan_wigner(c1 * a1) == QubitOperator(()) - jordan_wigner(a1 * c1)) def test_jordan_wigner_transm_op(self): n = number_operator(self.n_qubits) n_jw = jordan_wigner(n) self.assertEqual(self.n_qubits + 1, len(n_jw.terms)) - self.assertEqual(self.n_qubits / 2., n_jw.terms[()]) + self.assertEqual(self.n_qubits / 2.0, n_jw.terms[()]) for qubit in range(self.n_qubits): operators = ((qubit, 'Z'),) self.assertEqual(n_jw.terms[operators], -0.5) class InteractionOperatorsJWTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 - self.constant = 0. + self.constant = 0.0 self.one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) self.two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) - self.interaction_operator = InteractionOperator(self.constant, - self.one_body, - self.two_body) + (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float + ) + self.interaction_operator = InteractionOperator(self.constant, self.one_body, self.two_body) def test_consistency(self): """Test consistency with JW for FermionOperators.""" @@ -253,15 +247,12 @@ def test_consistency(self): self.assertEqual(op1, op2) # Interaction operator from molecule - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] basis = 'sto-3g' multiplicity = 1 filename = os.path.join(DATA_DIRECTORY, 'H1-Li1_sto-3g_singlet_1.45') - molecule = MolecularData(geometry, - basis, - multiplicity, - filename=filename) + molecule = MolecularData(geometry, basis, multiplicity, filename=filename) molecule.load() iop = molecule.get_molecular_hamiltonian() @@ -283,8 +274,7 @@ def testjordan_wigner_one_body(self): # Get correct qubit operator. fermion_term = FermionOperator(((p, 1), (q, 0)), coefficient) if p != q: - fermion_term += FermionOperator(((q, 1), (p, 0)), - coefficient.conjugate()) + fermion_term += FermionOperator(((q, 1), (p, 0)), coefficient.conjugate()) correct_op = jordan_wigner(fermion_term) self.assertTrue(test_operator == correct_op) @@ -300,37 +290,33 @@ def testjordan_wigner_two_body(self): test_operator = jordan_wigner_two_body(p, q, r, s, coefficient) # Get correct qubit operator. - fermion_term = FermionOperator(((p, 1), (q, 1), (r, 0), (s, 0)), - coefficient) + fermion_term = FermionOperator(((p, 1), (q, 1), (r, 0), (s, 0)), coefficient) if set([p, q]) != set([r, s]): fermion_term += FermionOperator( - ((s, 1), (r, 1), (q, 0), (p, 0)), coefficient.conjugate()) + ((s, 1), (r, 1), (q, 0), (p, 0)), coefficient.conjugate() + ) correct_op = jordan_wigner(fermion_term) - self.assertTrue(test_operator == correct_op, - str(test_operator - correct_op)) + self.assertTrue(test_operator == correct_op, str(test_operator - correct_op)) def test_jordan_wigner_twobody_interaction_op_allunique(self): test_op = FermionOperator('1^ 2^ 3 4') test_op += hermitian_conjugated(test_op) retransformed_test_op = reverse_jordan_wigner( - jordan_wigner(get_interaction_operator(test_op))) + jordan_wigner(get_interaction_operator(test_op)) + ) - self.assertTrue( - normal_ordered(retransformed_test_op) == normal_ordered(test_op)) + self.assertTrue(normal_ordered(retransformed_test_op) == normal_ordered(test_op)) def test_jordan_wigner_twobody_interaction_op_reversal_symmetric(self): test_op = FermionOperator('1^ 2^ 2 1') test_op += hermitian_conjugated(test_op) - self.assertTrue( - jordan_wigner(test_op) == jordan_wigner( - get_interaction_operator(test_op))) + self.assertTrue(jordan_wigner(test_op) == jordan_wigner(get_interaction_operator(test_op))) def test_jordan_wigner_interaction_op_too_few_n_qubits(self): with self.assertRaises(ValueError): - _jordan_wigner_interaction_op(self.interaction_operator, - self.n_qubits - 2) + _jordan_wigner_interaction_op(self.interaction_operator, self.n_qubits - 2) def test_jordan_wigner_interaction_op_with_zero_term(self): test_op = FermionOperator('1^ 2^ 3 4') @@ -339,98 +325,89 @@ def test_jordan_wigner_interaction_op_with_zero_term(self): interaction_op = get_interaction_operator(test_op) interaction_op.constant = 0.0 - retransformed_test_op = reverse_jordan_wigner( - jordan_wigner(interaction_op)) + retransformed_test_op = reverse_jordan_wigner(jordan_wigner(interaction_op)) - self.assertEqual(normal_ordered(retransformed_test_op), - normal_ordered(test_op)) + self.assertEqual(normal_ordered(retransformed_test_op), normal_ordered(test_op)) class GetInteractionOperatorTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 - self.constant = 0. + self.constant = 0.0 self.one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) self.two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float + ) def test_get_interaction_operator_identity(self): - interaction_operator = InteractionOperator(-2j, self.one_body, - self.two_body) + interaction_operator = InteractionOperator(-2j, self.one_body, self.two_body) qubit_operator = jordan_wigner(interaction_operator) self.assertTrue(qubit_operator == -2j * QubitOperator(())) self.assertEqual( interaction_operator, - get_interaction_operator(reverse_jordan_wigner(qubit_operator), - self.n_qubits)) + get_interaction_operator(reverse_jordan_wigner(qubit_operator), self.n_qubits), + ) def test_get_interaction_operator_one_body(self): - interaction_operator = get_interaction_operator(FermionOperator('2^ 2'), - self.n_qubits) + interaction_operator = get_interaction_operator(FermionOperator('2^ 2'), self.n_qubits) one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) - one_body[2, 2] = 1. - self.assertEqual(interaction_operator, - InteractionOperator(0.0, one_body, self.two_body)) + one_body[2, 2] = 1.0 + self.assertEqual(interaction_operator, InteractionOperator(0.0, one_body, self.two_body)) def test_get_interaction_operator_one_body_twoterm(self): interaction_operator = get_interaction_operator( - FermionOperator('2^ 3', -2j) + FermionOperator('3^ 2', 3j), - self.n_qubits) + FermionOperator('2^ 3', -2j) + FermionOperator('3^ 2', 3j), self.n_qubits + ) one_body = numpy.zeros((self.n_qubits, self.n_qubits), complex) one_body[2, 3] = -2j one_body[3, 2] = 3j - self.assertEqual(interaction_operator, - InteractionOperator(0.0, one_body, self.two_body)) + self.assertEqual(interaction_operator, InteractionOperator(0.0, one_body, self.two_body)) def test_get_interaction_operator_two_body(self): - interaction_operator = get_interaction_operator( - FermionOperator('2^ 2 3^ 4'), self.n_qubits) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) - two_body[3, 2, 4, 2] = -1. - self.assertEqual(interaction_operator, - InteractionOperator(0.0, self.one_body, two_body)) + interaction_operator = get_interaction_operator(FermionOperator('2^ 2 3^ 4'), self.n_qubits) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + two_body[3, 2, 4, 2] = -1.0 + self.assertEqual(interaction_operator, InteractionOperator(0.0, self.one_body, two_body)) def test_get_interaction_operator_two_body_distinct(self): - interaction_operator = get_interaction_operator( - FermionOperator('0^ 1^ 2 3'), self.n_qubits) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) - two_body[1, 0, 3, 2] = 1. - self.assertEqual(interaction_operator, - InteractionOperator(0.0, self.one_body, two_body)) + interaction_operator = get_interaction_operator(FermionOperator('0^ 1^ 2 3'), self.n_qubits) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + two_body[1, 0, 3, 2] = 1.0 + self.assertEqual(interaction_operator, InteractionOperator(0.0, self.one_body, two_body)) class JordanWignerDiagonalCoulombHamiltonianTest(unittest.TestCase): - def test_hubbard(self): x_dim = 4 y_dim = 5 - tunneling = 2. - coulomb = 3. - chemical_potential = 7. - magnetic_field = 11. + tunneling = 2.0 + coulomb = 3.0 + chemical_potential = 7.0 + magnetic_field = 11.0 periodic = False - hubbard_model = fermi_hubbard(x_dim, y_dim, tunneling, coulomb, - chemical_potential, magnetic_field, - periodic) + hubbard_model = fermi_hubbard( + x_dim, y_dim, tunneling, coulomb, chemical_potential, magnetic_field, periodic + ) self.assertTrue( - jordan_wigner(hubbard_model) == jordan_wigner( - get_diagonal_coulomb_hamiltonian(hubbard_model))) + jordan_wigner(hubbard_model) + == jordan_wigner(get_diagonal_coulomb_hamiltonian(hubbard_model)) + ) def test_random_quadratic(self): n_qubits = 5 quad_ham = random_quadratic_hamiltonian(n_qubits, True) ferm_op = get_fermion_operator(quad_ham) self.assertTrue( - jordan_wigner(ferm_op) == jordan_wigner( - get_diagonal_coulomb_hamiltonian(ferm_op))) + jordan_wigner(ferm_op) == jordan_wigner(get_diagonal_coulomb_hamiltonian(ferm_op)) + ) def test_jordan_wigner_majorana_op_consistent(): - op = (MajoranaOperator((1, 3, 4), 0.5) + MajoranaOperator( - (3, 7, 8, 9, 10, 12), 1.8) + MajoranaOperator((0, 4))) + op = ( + MajoranaOperator((1, 3, 4), 0.5) + + MajoranaOperator((3, 7, 8, 9, 10, 12), 1.8) + + MajoranaOperator((0, 4)) + ) assert jordan_wigner(op) == jordan_wigner(get_fermion_operator(op)) diff --git a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py index 14b23862b..8fdf07c6b 100644 --- a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py +++ b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum.py @@ -15,8 +15,9 @@ from openfermion.utils.operator_utils import count_qubits -def _qubit_operator_term_to_pauli_string(term: dict, qubits: Sequence[cirq.Qid] - ) -> cirq.PauliString: +def _qubit_operator_term_to_pauli_string( + term: dict, qubits: Sequence[cirq.Qid] +) -> cirq.PauliString: """ Convert term of QubitOperator to a PauliString. @@ -28,13 +29,12 @@ def _qubit_operator_term_to_pauli_string(term: dict, qubits: Sequence[cirq.Qid] """ ind_ops, coeff = term - return cirq.PauliString(dict((qubits[ind], op) for ind, op in ind_ops), - coeff) + return cirq.PauliString(dict((qubits[ind], op) for ind, op in ind_ops), coeff) -def qubit_operator_to_pauli_sum(operator: QubitOperator, - qubits: Optional[Sequence[cirq.Qid]] = None - ) -> cirq.PauliSum: +def qubit_operator_to_pauli_sum( + operator: QubitOperator, qubits: Optional[Sequence[cirq.Qid]] = None +) -> cirq.PauliSum: """ Convert QubitOperator to a sum of PauliString. diff --git a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py index 6abd368b2..f4e37eb7c 100644 --- a/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py +++ b/src/openfermion/transforms/opconversions/qubitoperator_to_paulisum_test.py @@ -15,17 +15,17 @@ import openfermion from openfermion.ops.operators import QubitOperator -from openfermion.transforms.opconversions import (qubit_operator_to_pauli_sum) +from openfermion.transforms.opconversions import qubit_operator_to_pauli_sum from openfermion.transforms.opconversions.qubitoperator_to_paulisum import ( - _qubit_operator_term_to_pauli_string) + _qubit_operator_term_to_pauli_string, +) def test_function_raises(): """Test function raises.""" operator = QubitOperator('X0 X1 X2 X3', 1.0) with pytest.raises(TypeError): - _qubit_operator_term_to_pauli_string(list(operator.terms.items())[0], - qubits=0.0) + _qubit_operator_term_to_pauli_string(list(operator.terms.items())[0], qubits=0.0) with pytest.raises(TypeError): qubit_operator_to_pauli_sum([5.0]) @@ -42,15 +42,17 @@ def test_identity(): """Test correct hanlding of Identity.""" identity_op = QubitOperator(' ', -0.5) pau_from_qop = _qubit_operator_term_to_pauli_string( - term=list(identity_op.terms.items())[0], qubits=cirq.LineQubit.range(2)) + term=list(identity_op.terms.items())[0], qubits=cirq.LineQubit.range(2) + ) pauli_str = cirq.PauliString() * (-0.5) assert pauli_str == pau_from_qop -@pytest.mark.parametrize('qubitop, state_binary', - [(QubitOperator('Z0 Z1', -1.0), '00'), - (QubitOperator('X0 Y1', 1.0), '10')]) +@pytest.mark.parametrize( + 'qubitop, state_binary', + [(QubitOperator('Z0 Z1', -1.0), '00'), (QubitOperator('X0 Y1', 1.0), '10')], +) def test_expectation_values(qubitop, state_binary): """Test PauliSum and QubitOperator expectation value.""" n_qubits = openfermion.count_qubits(qubitop) @@ -59,8 +61,8 @@ def test_expectation_values(qubitop, state_binary): qubit_map = {cirq.LineQubit(i): i for i in range(n_qubits)} pauli_str = _qubit_operator_term_to_pauli_string( - term=list(qubitop.terms.items())[0], - qubits=cirq.LineQubit.range(n_qubits)) + term=list(qubitop.terms.items())[0], qubits=cirq.LineQubit.range(n_qubits) + ) op_mat = openfermion.get_sparse_operator(qubitop, n_qubits) expct_qop = openfermion.expectation(op_mat, state) @@ -71,9 +73,11 @@ def test_expectation_values(qubitop, state_binary): @pytest.mark.parametrize( 'qubitop, state_binary', - [(QubitOperator('Z0 Z1 Z2 Z3', -1.0) + QubitOperator('X0 Y1 Y2 X3', 1.0), - '1100'), - (QubitOperator('X0 X3', -1.0) + QubitOperator('Y1 Y2', 1.0), '0000')]) + [ + (QubitOperator('Z0 Z1 Z2 Z3', -1.0) + QubitOperator('X0 Y1 Y2 X3', 1.0), '1100'), + (QubitOperator('X0 X3', -1.0) + QubitOperator('Y1 Y2', 1.0), '0000'), + ], +) def test_expectation_values_paulisum(qubitop, state_binary): """Test PauliSum and QubitOperator expectation value.""" n_qubits = openfermion.count_qubits(qubitop) diff --git a/src/openfermion/transforms/opconversions/remove_symmetry_qubits.py b/src/openfermion/transforms/opconversions/remove_symmetry_qubits.py index e914e758f..ebc8ce625 100644 --- a/src/openfermion/transforms/opconversions/remove_symmetry_qubits.py +++ b/src/openfermion/transforms/opconversions/remove_symmetry_qubits.py @@ -21,60 +21,57 @@ from openfermion.utils.indexing import up_then_down -def symmetry_conserving_bravyi_kitaev(fermion_hamiltonian, active_orbitals, - active_fermions): - """ Returns the qubit Hamiltonian for the fermionic Hamiltonian - supplied, with two qubits removed using conservation of electron - spin and number, as described in arXiv:1701.08213. - - Args: - fermion_hamiltonian: A fermionic hamiltonian obtained - using OpenFermion. An instance - of the FermionOperator class. - - active_orbitals: Int type object. The number of active orbitals - being considered for the system. - - active_fermions: Int type object. The number of active fermions - being considered for the system (note, this - is less than the number of electrons in a - molecule if some orbitals have been assumed - filled). - Returns: - qubit_hamiltonian: The qubit Hamiltonian corresponding to - the supplied fermionic Hamiltonian, with - two qubits removed using spin symmetries. - WARNING: - Reorders orbitals from the default even-odd ordering to all - spin-up orbitals, then all spin-down orbitals. - Raises: - ValueError if fermion_hamiltonian isn't of the type - FermionOperator, or active_orbitals isn't an integer, - or active_fermions isn't an integer. - - Notes: This function reorders the spin orbitals as all spin-up, then - all spin-down. It uses the OpenFermion bravyi_kitaev_tree - mapping, rather than the bravyi-kitaev mapping. - Caution advised when using with a Fermi-Hubbard Hamiltonian; - this technique correctly reduces the Hamiltonian only for the - lowest energy even and odd fermion number states, not states - with an arbitrary number of fermions. +def symmetry_conserving_bravyi_kitaev(fermion_hamiltonian, active_orbitals, active_fermions): + """Returns the qubit Hamiltonian for the fermionic Hamiltonian + supplied, with two qubits removed using conservation of electron + spin and number, as described in arXiv:1701.08213. + + Args: + fermion_hamiltonian: A fermionic hamiltonian obtained + using OpenFermion. An instance + of the FermionOperator class. + + active_orbitals: Int type object. The number of active orbitals + being considered for the system. + + active_fermions: Int type object. The number of active fermions + being considered for the system (note, this + is less than the number of electrons in a + molecule if some orbitals have been assumed + filled). + Returns: + qubit_hamiltonian: The qubit Hamiltonian corresponding to + the supplied fermionic Hamiltonian, with + two qubits removed using spin symmetries. + WARNING: + Reorders orbitals from the default even-odd ordering to all + spin-up orbitals, then all spin-down orbitals. + Raises: + ValueError if fermion_hamiltonian isn't of the type + FermionOperator, or active_orbitals isn't an integer, + or active_fermions isn't an integer. + + Notes: This function reorders the spin orbitals as all spin-up, then + all spin-down. It uses the OpenFermion bravyi_kitaev_tree + mapping, rather than the bravyi-kitaev mapping. + Caution advised when using with a Fermi-Hubbard Hamiltonian; + this technique correctly reduces the Hamiltonian only for the + lowest energy even and odd fermion number states, not states + with an arbitrary number of fermions. """ # Catch errors if inputs are of wrong type. if not isinstance(fermion_hamiltonian, FermionOperator): - raise ValueError( - "Supplied operator should be an instance of FermionOperator class") + raise ValueError("Supplied operator should be an instance of FermionOperator class") if not isinstance(active_orbitals, int): raise ValueError("Number of active orbitals should be an integer.") if not isinstance(active_fermions, int): raise ValueError("Number of active fermions should be an integer.") # Arrange spins up then down, then BK map to qubit Hamiltonian. - fermion_hamiltonian_reorder = reorder(fermion_hamiltonian, - up_then_down, - num_modes=active_orbitals) - qubit_hamiltonian = bravyi_kitaev_tree(fermion_hamiltonian_reorder, - n_qubits=active_orbitals) + fermion_hamiltonian_reorder = reorder( + fermion_hamiltonian, up_then_down, num_modes=active_orbitals + ) + qubit_hamiltonian = bravyi_kitaev_tree(fermion_hamiltonian_reorder, n_qubits=active_orbitals) qubit_hamiltonian.compress() # Allocates the parity factors for the orbitals as in arXiv:1704.05018. @@ -93,21 +90,19 @@ def symmetry_conserving_bravyi_kitaev(fermion_hamiltonian, active_orbitals, parity_middle_orb = 1 # Removes the final qubit, then the middle qubit. - qubit_hamiltonian = edit_hamiltonian_for_spin(qubit_hamiltonian, - active_orbitals, - parity_final_orb) - qubit_hamiltonian = edit_hamiltonian_for_spin(qubit_hamiltonian, - active_orbitals / 2, - parity_middle_orb) - qubit_hamiltonian = remove_indices(qubit_hamiltonian, - (active_orbitals / 2, active_orbitals)) + qubit_hamiltonian = edit_hamiltonian_for_spin( + qubit_hamiltonian, active_orbitals, parity_final_orb + ) + qubit_hamiltonian = edit_hamiltonian_for_spin( + qubit_hamiltonian, active_orbitals / 2, parity_middle_orb + ) + qubit_hamiltonian = remove_indices(qubit_hamiltonian, (active_orbitals / 2, active_orbitals)) return qubit_hamiltonian def edit_hamiltonian_for_spin(qubit_hamiltonian, spin_orbital, orbital_parity): - """ Removes the Z terms acting on the orbital from the Hamiltonian. - """ + """Removes the Z terms acting on the orbital from the Hamiltonian.""" new_qubit_dict = {} for term, coefficient in qubit_hamiltonian.terms.items(): # If Z acts on the specified orbital, precompute its effect and diff --git a/src/openfermion/transforms/opconversions/remove_symmetry_qubits_test.py b/src/openfermion/transforms/opconversions/remove_symmetry_qubits_test.py index 1e1e953e2..516f6fd09 100644 --- a/src/openfermion/transforms/opconversions/remove_symmetry_qubits_test.py +++ b/src/openfermion/transforms/opconversions/remove_symmetry_qubits_test.py @@ -20,20 +20,23 @@ from openfermion.chem import MolecularData from openfermion.transforms.opconversions import get_fermion_operator from openfermion.linalg.sparse_tools import ( - get_sparse_operator, jw_get_ground_state_at_particle_number) + get_sparse_operator, + jw_get_ground_state_at_particle_number, +) from openfermion.linalg import eigenspectrum from openfermion.ops.operators import FermionOperator from openfermion.transforms.opconversions.remove_symmetry_qubits import ( - symmetry_conserving_bravyi_kitaev) + symmetry_conserving_bravyi_kitaev, +) def LiH_sto3g(): - """ Generates the Hamiltonian for LiH in - the STO-3G basis, at a distance of - 1.45 A. + """Generates the Hamiltonian for LiH in + the STO-3G basis, at a distance of + 1.45 A. """ - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] molecule = MolecularData(geometry, 'sto-3g', 1, description="1.45") molecule.load() molecular_hamiltonian = molecule.get_molecular_hamiltonian() @@ -45,8 +48,7 @@ def LiH_sto3g(): def set_1D_hubbard(x_dim): - """ Returns a 1D Fermi-Hubbard Hamiltonian. - """ + """Returns a 1D Fermi-Hubbard Hamiltonian.""" y_dim = 1 tunneling = 1.0 coulomb = 1.0 @@ -57,16 +59,16 @@ def set_1D_hubbard(x_dim): num_orbitals = 2 * x_dim * y_dim - hubbard_model = fermi_hubbard(x_dim, y_dim, tunneling, coulomb, - chemical_potential, magnetic_field, periodic, - spinless) + hubbard_model = fermi_hubbard( + x_dim, y_dim, tunneling, coulomb, chemical_potential, magnetic_field, periodic, spinless + ) return hubbard_model, num_orbitals def number_of_qubits(qubit_hamiltonian, unreduced_orbitals): - """ Returns the number of qubits that a - qubit Hamiltonian acts upon. + """Returns the number of qubits that a + qubit Hamiltonian acts upon. """ max_orbital = 0 for i in range(unreduced_orbitals): @@ -79,7 +81,6 @@ def number_of_qubits(qubit_hamiltonian, unreduced_orbitals): class ReduceSymmetryQubitsTest(unittest.TestCase): - # Check whether fermionic and reduced qubit Hamiltonians # have the same energy for LiH def test_energy_reduce_symmetry_qubits(self): @@ -88,12 +89,11 @@ def test_energy_reduce_symmetry_qubits(self): lih_sto_hamil, lih_sto_numorb, lih_sto_numel = LiH_sto3g() # Use test function to reduce the qubits. - lih_sto_qbt = (symmetry_conserving_bravyi_kitaev( - lih_sto_hamil, lih_sto_numorb, lih_sto_numel)) + lih_sto_qbt = symmetry_conserving_bravyi_kitaev( + lih_sto_hamil, lih_sto_numorb, lih_sto_numel + ) - self.assertAlmostEqual( - eigenspectrum(lih_sto_qbt)[0], - eigenspectrum(lih_sto_hamil)[0]) + self.assertAlmostEqual(eigenspectrum(lih_sto_qbt)[0], eigenspectrum(lih_sto_hamil)[0]) # Check that the qubit Hamiltonian acts on two fewer qubits # for LiH. @@ -103,11 +103,11 @@ def test_orbnum_reduce_symmetry_qubits(self): lih_sto_hamil, lih_sto_numorb, lih_sto_numel = LiH_sto3g() # Use test function to reduce the qubits. - lih_sto_qbt = (symmetry_conserving_bravyi_kitaev( - lih_sto_hamil, lih_sto_numorb, lih_sto_numel)) + lih_sto_qbt = symmetry_conserving_bravyi_kitaev( + lih_sto_hamil, lih_sto_numorb, lih_sto_numel + ) - self.assertEqual(number_of_qubits(lih_sto_qbt, lih_sto_numorb), - lih_sto_numorb - 2) + self.assertEqual(number_of_qubits(lih_sto_qbt, lih_sto_numorb), lih_sto_numorb - 2) # Check ValueErrors arise correctly. def test_errors_reduce_symmetry_qubits(self): @@ -120,8 +120,7 @@ def test_errors_reduce_symmetry_qubits(self): with self.assertRaises(ValueError): symmetry_conserving_bravyi_kitaev(lih_sto_hamil, 1.5, lih_sto_numel) with self.assertRaises(ValueError): - symmetry_conserving_bravyi_kitaev(lih_sto_hamil, lih_sto_numorb, - 3.6) + symmetry_conserving_bravyi_kitaev(lih_sto_hamil, lih_sto_numorb, 3.6) # Check energy is the same for Fermi-Hubbard model. def test_hubbard_reduce_symmetry_qubits(self): @@ -131,12 +130,10 @@ def test_hubbard_reduce_symmetry_qubits(self): hub_hamil, n_orb = set_1D_hubbard(n_sites) # Use test function to reduce the qubits. - hub_qbt = (symmetry_conserving_bravyi_kitaev( - hub_hamil, n_orb, n_ferm)) + hub_qbt = symmetry_conserving_bravyi_kitaev(hub_hamil, n_orb, n_ferm) sparse_op = get_sparse_operator(hub_hamil) - ground_energy, _ = jw_get_ground_state_at_particle_number( - sparse_op, n_ferm) + ground_energy, _ = jw_get_ground_state_at_particle_number(sparse_op, n_ferm) self.assertAlmostEqual(eigenspectrum(hub_qbt)[0], ground_energy) @@ -146,9 +143,7 @@ def test_hubbard_reduce_symmetry_qubits(self): def test_single_operator(self): # Dummy operator acting only on 2 qubits of overall 4-qubit system op = FermionOperator("0^ 1^ 1 0") + FermionOperator("1^ 0^ 0 1") - trafo_op = symmetry_conserving_bravyi_kitaev(op, - active_fermions=2, - active_orbitals=4) + trafo_op = symmetry_conserving_bravyi_kitaev(op, active_fermions=2, active_orbitals=4) # Check via eigenspectrum -- needs to stay the same e_op = eigenspectrum(op) e_trafo = eigenspectrum(trafo_op) diff --git a/src/openfermion/transforms/opconversions/reverse_jordan_wigner.py b/src/openfermion/transforms/opconversions/reverse_jordan_wigner.py index 76461c1c4..fbd32012c 100644 --- a/src/openfermion/transforms/opconversions/reverse_jordan_wigner.py +++ b/src/openfermion/transforms/opconversions/reverse_jordan_wigner.py @@ -11,7 +11,7 @@ # limitations under the License. """Reverse Jordan-Wigner transform on QubitOperators.""" -from openfermion.ops.operators import (FermionOperator, QubitOperator) +from openfermion.ops.operators import FermionOperator, QubitOperator from openfermion.hamiltonians.special_operators import number_operator from openfermion.utils.operator_utils import count_qubits @@ -53,19 +53,19 @@ def reverse_jordan_wigner(qubit_operator, n_qubits=None): working_term = QubitOperator(term) pauli_operator = term[-1] while pauli_operator is not None: - # Handle Pauli Z. if pauli_operator[1] == 'Z': - transformed_pauli = FermionOperator( - ()) + number_operator(n_qubits, pauli_operator[0], -2.) + transformed_pauli = FermionOperator(()) + number_operator( + n_qubits, pauli_operator[0], -2.0 + ) # Handle Pauli X and Y. else: raising_term = FermionOperator(((pauli_operator[0], 1),)) lowering_term = FermionOperator(((pauli_operator[0], 0),)) if pauli_operator[1] == 'Y': - raising_term *= 1.j - lowering_term *= -1.j + raising_term *= 1.0j + lowering_term *= -1.0j transformed_pauli = raising_term + lowering_term @@ -75,7 +75,7 @@ def reverse_jordan_wigner(qubit_operator, n_qubits=None): working_term = z_term * working_term term_key = list(working_term.terms)[0] transformed_pauli *= working_term.terms[term_key] - working_term.terms[list(working_term.terms)[0]] = 1. + working_term.terms[list(working_term.terms)[0]] = 1.0 # Get next non-identity operator acting below 'working_qubit'. assert len(working_term.terms) == 1 diff --git a/src/openfermion/transforms/opconversions/reverse_jordan_wigner_test.py b/src/openfermion/transforms/opconversions/reverse_jordan_wigner_test.py index 2ace26d3e..88a419ed8 100644 --- a/src/openfermion/transforms/opconversions/reverse_jordan_wigner_test.py +++ b/src/openfermion/transforms/opconversions/reverse_jordan_wigner_test.py @@ -15,31 +15,27 @@ from openfermion.ops.operators import FermionOperator, QubitOperator from openfermion.transforms.opconversions import jordan_wigner, normal_ordered -from openfermion.transforms.opconversions.reverse_jordan_wigner import ( - reverse_jordan_wigner) +from openfermion.transforms.opconversions.reverse_jordan_wigner import reverse_jordan_wigner class ReverseJWTest(unittest.TestCase): - def setUp(self): self.coefficient = 0.5 self.operators = ((1, 'X'), (3, 'Y'), (8, 'Z')) self.term = QubitOperator(self.operators, self.coefficient) self.identity = QubitOperator(()) self.coefficient_a = 6.7j - self.coefficient_b = -88. + self.coefficient_b = -88.0 self.operators_a = ((3, 'Z'), (1, 'Y'), (4, 'Y')) self.operators_b = ((2, 'X'), (3, 'Y')) self.operator_a = QubitOperator(self.operators_a, self.coefficient_a) self.operator_b = QubitOperator(self.operators_b, self.coefficient_b) self.operator_ab = self.operator_a + self.operator_b self.qubit_operator = QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) - self.qubit_operator += QubitOperator(((1, 'Z'), (3, 'X'), (8, 'Z')), - 1.2) + self.qubit_operator += QubitOperator(((1, 'Z'), (3, 'X'), (8, 'Z')), 1.2) def test_identity_jwterm(self): - self.assertTrue( - FermionOperator(()) == reverse_jordan_wigner(QubitOperator(()))) + self.assertTrue(FermionOperator(()) == reverse_jordan_wigner(QubitOperator(()))) def test_x(self): pauli_x = QubitOperator(((2, 'X'),)) @@ -57,8 +53,7 @@ def test_z(self): pauli_z = QubitOperator(((2, 'Z'),)) transmed_z = reverse_jordan_wigner(pauli_z) - expected = (FermionOperator(()) + FermionOperator( - ((2, 1), (2, 0)), -2.)) + expected = FermionOperator(()) + FermionOperator(((2, 1), (2, 0)), -2.0) self.assertTrue(transmed_z == expected) retransmed_z = jordan_wigner(transmed_z) @@ -98,40 +93,36 @@ def test_term(self): self.assertTrue(self.term == retransmed_term) def test_xx(self): - xx = QubitOperator(((3, 'X'), (4, 'X')), 2.) + xx = QubitOperator(((3, 'X'), (4, 'X')), 2.0) transmed_xx = reverse_jordan_wigner(xx) retransmed_xx = jordan_wigner(transmed_xx) - expected1 = (FermionOperator(((3, 1),), 2.) - FermionOperator( - ((3, 0),), 2.)) - expected2 = (FermionOperator(((4, 1),), 1.) + FermionOperator( - ((4, 0),), 1.)) + expected1 = FermionOperator(((3, 1),), 2.0) - FermionOperator(((3, 0),), 2.0) + expected2 = FermionOperator(((4, 1),), 1.0) + FermionOperator(((4, 0),), 1.0) expected = expected1 * expected2 self.assertTrue(xx == retransmed_xx) self.assertTrue(normal_ordered(transmed_xx) == normal_ordered(expected)) def test_yy(self): - yy = QubitOperator(((2, 'Y'), (3, 'Y')), 2.) + yy = QubitOperator(((2, 'Y'), (3, 'Y')), 2.0) transmed_yy = reverse_jordan_wigner(yy) retransmed_yy = jordan_wigner(transmed_yy) - expected1 = -(FermionOperator(((2, 1),), 2.) + FermionOperator( - ((2, 0),), 2.)) - expected2 = (FermionOperator(((3, 1),)) - FermionOperator(((3, 0),))) + expected1 = -(FermionOperator(((2, 1),), 2.0) + FermionOperator(((2, 0),), 2.0)) + expected2 = FermionOperator(((3, 1),)) - FermionOperator(((3, 0),)) expected = expected1 * expected2 self.assertTrue(yy == retransmed_yy) self.assertTrue(normal_ordered(transmed_yy) == normal_ordered(expected)) def test_xy(self): - xy = QubitOperator(((4, 'X'), (5, 'Y')), -2.j) + xy = QubitOperator(((4, 'X'), (5, 'Y')), -2.0j) transmed_xy = reverse_jordan_wigner(xy) retransmed_xy = jordan_wigner(transmed_xy) - expected1 = -2j * (FermionOperator(((4, 1),), 1j) - FermionOperator( - ((4, 0),), 1j)) - expected2 = (FermionOperator(((5, 1),)) - FermionOperator(((5, 0),))) + expected1 = -2j * (FermionOperator(((4, 1),), 1j) - FermionOperator(((4, 0),), 1j)) + expected2 = FermionOperator(((5, 1),)) - FermionOperator(((5, 0),)) expected = expected1 * expected2 self.assertTrue(xy == retransmed_xy) @@ -142,10 +133,8 @@ def test_yx(self): transmed_yx = reverse_jordan_wigner(yx) retransmed_yx = jordan_wigner(transmed_yx) - expected1 = 1j * (FermionOperator(((0, 1),)) + FermionOperator( - ((0, 0),))) - expected2 = -0.5 * (FermionOperator(((1, 1),)) + FermionOperator( - ((1, 0),))) + expected1 = 1j * (FermionOperator(((0, 1),)) + FermionOperator(((0, 0),))) + expected2 = -0.5 * (FermionOperator(((1, 1),)) + FermionOperator(((1, 0),))) expected = expected1 * expected2 self.assertTrue(yx == retransmed_yx) diff --git a/src/openfermion/transforms/opconversions/term_reordering.py b/src/openfermion/transforms/opconversions/term_reordering.py index 2b235c7d6..ba9652a02 100644 --- a/src/openfermion/transforms/opconversions/term_reordering.py +++ b/src/openfermion/transforms/opconversions/term_reordering.py @@ -14,8 +14,7 @@ import itertools import numpy -from openfermion.ops.operators import (BosonOperator, FermionOperator, - QuadOperator) +from openfermion.ops.operators import BosonOperator, FermionOperator, QuadOperator from openfermion.ops.representations import InteractionOperator @@ -53,16 +52,14 @@ def chemist_ordered(fermion_operator): # Possibly add new one-body term. if term[1][0] == term[2][0]: new_one_body_term = (term[0], term[3]) - chemist_ordered_operator += FermionOperator( - new_one_body_term, coefficient) + chemist_ordered_operator += FermionOperator(new_one_body_term, coefficient) # Reorder two-body term. new_two_body_term = (term[0], term[2], term[1], term[3]) - chemist_ordered_operator += FermionOperator(new_two_body_term, - -coefficient) + chemist_ordered_operator += FermionOperator(new_two_body_term, -coefficient) return chemist_ordered_operator -def normal_ordered(operator, hbar=1.): +def normal_ordered(operator, hbar=1.0): r"""Compute and return the normal ordered form of a FermionOperator, BosonOperator, QuadOperator, or InteractionOperator. @@ -108,33 +105,44 @@ def normal_ordered(operator, hbar=1.): n_modes = operator.n_qubits one_body_tensor = operator.one_body_tensor.copy() two_body_tensor = numpy.zeros_like(operator.two_body_tensor) - quadratic_index_pairs = ( - (pq, pq) for pq in itertools.combinations(range(n_modes)[::-1], 2)) + quadratic_index_pairs = ((pq, pq) for pq in itertools.combinations(range(n_modes)[::-1], 2)) cubic_index_pairs = ( index_pair for p, q, r in itertools.combinations(range(n_modes)[::-1], 3) - for index_pair in [((p, q), (p, r)), ((p, r), ( - p, q)), ((p, q), (q, r)), ((q, r), - (p, q)), ((p, r), - (q, r)), ((q, r), (p, r))]) + for index_pair in [ + ((p, q), (p, r)), + ((p, r), (p, q)), + ((p, q), (q, r)), + ((q, r), (p, q)), + ((p, r), (q, r)), + ((q, r), (p, r)), + ] + ) quartic_index_pairs = ( index_pair for p, q, r, s in itertools.combinations(range(n_modes)[::-1], 4) - for index_pair in [((p, q), (r, s)), ((r, s), ( - p, q)), ((p, r), (q, s)), ((q, s), - (p, r)), ((p, s), - (q, r)), ((q, r), (p, s))]) - index_pairs = itertools.chain(quadratic_index_pairs, cubic_index_pairs, - quartic_index_pairs) + for index_pair in [ + ((p, q), (r, s)), + ((r, s), (p, q)), + ((p, r), (q, s)), + ((q, s), (p, r)), + ((p, s), (q, r)), + ((q, r), (p, s)), + ] + ) + index_pairs = itertools.chain(quadratic_index_pairs, cubic_index_pairs, quartic_index_pairs) for pq, rs in index_pairs: two_body_tensor[pq + rs] = sum( s * ss * operator.two_body_tensor[pq[::s] + rs[::ss]] - for s, ss in itertools.product([-1, 1], repeat=2)) + for s, ss in itertools.product([-1, 1], repeat=2) + ) return InteractionOperator(constant, one_body_tensor, two_body_tensor) else: - raise TypeError('Can only normal order FermionOperator, ' - 'BosonOperator, QuadOperator, or InteractionOperator.') + raise TypeError( + 'Can only normal order FermionOperator, ' + 'BosonOperator, QuadOperator, or InteractionOperator.' + ) for term, coefficient in operator.terms.items(): ordered_operator += order_fn(term, coefficient, **kwargs) @@ -198,15 +206,15 @@ def normal_ordered_ladder_term(term, coefficient, parity=-1): # Replace a a^\dagger with 1 + parity*a^\dagger a # if indices are the same. if right_operator[0] == left_operator[0]: - new_term = term[:(j - 1)] + term[(j + 1):] + new_term = term[: (j - 1)] + term[(j + 1) :] # Recursively add the processed new term. ordered_term += normal_ordered_ladder_term( - tuple(new_term), parity * coefficient, parity) + tuple(new_term), parity * coefficient, parity + ) # Handle case when operator type is the same. elif right_operator[1] == left_operator[1]: - # If same two Fermionic operators are repeated, # evaluate to zero. if parity == -1 and right_operator[0] == left_operator[0]: @@ -223,7 +231,7 @@ def normal_ordered_ladder_term(term, coefficient, parity=-1): return ordered_term -def normal_ordered_quad_term(term, coefficient, hbar=1.): +def normal_ordered_quad_term(term, coefficient, hbar=1.0): """Return a normal ordered QuadOperator corresponding to single term. Args: @@ -263,15 +271,15 @@ def normal_ordered_quad_term(term, coefficient, hbar=1.): # Replace p q with i hbar + q p # if indices are the same. if right_operator[0] == left_operator[0]: - new_term = term[:(j - 1)] + term[(j + 1)::] + new_term = term[: (j - 1)] + term[(j + 1) : :] # Recursively add the processed new term. ordered_term += normal_ordered_quad_term( - tuple(new_term), -coefficient * 1j * hbar) + tuple(new_term), -coefficient * 1j * hbar + ) # Handle case when operator type is the same. elif right_operator[1] == left_operator[1]: - # Swap if same type but lower index on left. if right_operator[0] > left_operator[0]: term[j - 1] = right_operator @@ -303,13 +311,9 @@ def reorder(operator, order_function, num_modes=None, reverse=False): """ if num_modes is None: - num_modes = max( - [factor[0] for term in operator.terms for factor in term]) + 1 + num_modes = max([factor[0] for term in operator.terms for factor in term]) + 1 - mode_map = { - mode_idx: order_function(mode_idx, num_modes) - for mode_idx in range(num_modes) - } + mode_map = {mode_idx: order_function(mode_idx, num_modes) for mode_idx in range(num_modes)} if reverse: mode_map = {val: key for key, val in mode_map.items()} diff --git a/src/openfermion/transforms/opconversions/term_reordering_test.py b/src/openfermion/transforms/opconversions/term_reordering_test.py index 649d0a83f..70cb6e2e1 100644 --- a/src/openfermion/transforms/opconversions/term_reordering_test.py +++ b/src/openfermion/transforms/opconversions/term_reordering_test.py @@ -16,32 +16,30 @@ import numpy from openfermion.hamiltonians import number_operator -from openfermion.ops.operators import (FermionOperator, BosonOperator, - QuadOperator) -from openfermion.transforms.opconversions import (jordan_wigner, - get_fermion_operator) +from openfermion.ops.operators import FermionOperator, BosonOperator, QuadOperator +from openfermion.transforms.opconversions import jordan_wigner, get_fermion_operator from openfermion.testing.testing_utils import random_interaction_operator from openfermion.utils import up_then_down from openfermion.transforms.opconversions.term_reordering import ( - normal_ordered, chemist_ordered, reorder) + normal_ordered, + chemist_ordered, + reorder, +) class ChemistOrderingTest(unittest.TestCase): - def test_convert_forward_back(self): n_qubits = 6 - random_operator = get_fermion_operator( - random_interaction_operator(n_qubits)) + random_operator = get_fermion_operator(random_interaction_operator(n_qubits)) chemist_operator = chemist_ordered(random_operator) normalized_chemist = normal_ordered(chemist_operator) difference = normalized_chemist - normal_ordered(random_operator) - self.assertAlmostEqual(0., difference.induced_norm()) + self.assertAlmostEqual(0.0, difference.induced_norm()) def test_exception(self): n_qubits = 6 - random_operator = get_fermion_operator( - random_interaction_operator(n_qubits)) + random_operator = get_fermion_operator(random_interaction_operator(n_qubits)) bad_term = ((2, 1), (3, 1)) random_operator += FermionOperator(bad_term) with self.assertRaises(TypeError): @@ -49,8 +47,7 @@ def test_exception(self): def test_form(self): n_qubits = 6 - random_operator = get_fermion_operator( - random_interaction_operator(n_qubits)) + random_operator = get_fermion_operator(random_interaction_operator(n_qubits)) chemist_operator = chemist_ordered(random_operator) for term, _ in chemist_operator.terms.items(): if len(term) == 2 or not len(term): @@ -65,16 +62,14 @@ def test_form(self): class TestNormalOrdering(unittest.TestCase): - def test_boson_single_term(self): op = BosonOperator('4 3 2 1') + BosonOperator('3 2') self.assertTrue(op == normal_ordered(op)) def test_boson_two_term(self): - op_b = BosonOperator(((2, 0), (4, 0), (2, 1)), 88.) + op_b = BosonOperator(((2, 0), (4, 0), (2, 1)), 88.0) normal_ordered_b = normal_ordered(op_b) - expected = (BosonOperator(((4, 0),), 88.) + BosonOperator( - ((2, 1), (4, 0), (2, 0)), 88.)) + expected = BosonOperator(((4, 0),), 88.0) + BosonOperator(((2, 1), (4, 0), (2, 0)), 88.0) self.assertTrue(normal_ordered_b == expected) def test_boson_number(self): @@ -98,8 +93,7 @@ def test_boson_offsite_reversed(self): def test_boson_multi(self): op = BosonOperator(((2, 0), (1, 1), (2, 1))) - expected = (BosonOperator(((2, 1), (1, 1), (2, 0))) + BosonOperator( - ((1, 1),))) + expected = BosonOperator(((2, 1), (1, 1), (2, 0))) + BosonOperator(((1, 1),)) self.assertTrue(expected == normal_ordered(op)) def test_boson_triple(self): @@ -116,10 +110,11 @@ def test_fermion_single_term(self): self.assertTrue(op == normal_ordered(op)) def test_fermion_two_term(self): - op_b = FermionOperator(((2, 0), (4, 0), (2, 1)), -88.) + op_b = FermionOperator(((2, 0), (4, 0), (2, 1)), -88.0) normal_ordered_b = normal_ordered(op_b) - expected = (FermionOperator(((4, 0),), 88.) + FermionOperator( - ((2, 1), (4, 0), (2, 0)), 88.)) + expected = FermionOperator(((4, 0),), 88.0) + FermionOperator( + ((2, 1), (4, 0), (2, 0)), 88.0 + ) self.assertTrue(normal_ordered_b == expected) def test_fermion_number(self): @@ -153,8 +148,7 @@ def test_fermion_double_create_separated(self): def test_fermion_multi(self): op = FermionOperator(((2, 0), (1, 1), (2, 1))) - expected = (-FermionOperator( - ((2, 1), (1, 1), (2, 0))) - FermionOperator(((1, 1),))) + expected = -FermionOperator(((2, 1), (1, 1), (2, 0))) - FermionOperator(((1, 1),)) self.assertTrue(expected == normal_ordered(op)) def test_fermion_triple(self): @@ -171,14 +165,13 @@ def test_quad_single_term(self): self.assertTrue(op == normal_ordered(op)) op = QuadOperator('q0 p0') - QuadOperator('p0 q0') - expected = QuadOperator('', 2.j) - self.assertTrue(expected == normal_ordered(op, hbar=2.)) + expected = QuadOperator('', 2.0j) + self.assertTrue(expected == normal_ordered(op, hbar=2.0)) def test_quad_two_term(self): - op_b = QuadOperator('p0 q0 p3', 88.) + op_b = QuadOperator('p0 q0 p3', 88.0) normal_ordered_b = normal_ordered(op_b, hbar=2) - expected = QuadOperator('p3', -88. * 2j) + QuadOperator( - 'q0 p0 p3', 88.0) + expected = QuadOperator('p3', -88.0 * 2j) + QuadOperator('q0 p0 p3', 88.0) self.assertTrue(normal_ordered_b == expected) def test_quad_offsite(self): @@ -200,8 +193,7 @@ def test_quad_triple(self): self.assertTrue(op_132 == normal_ordered(op_321)) def test_interaction_operator(self): - for n_orbitals, real, _ in itertools.product((1, 2, 5), (True, False), - range(5)): + for n_orbitals, real, _ in itertools.product((1, 2, 5), (True, False), range(5)): operator = random_interaction_operator(n_orbitals, real=real) normal_ordered_operator = normal_ordered(operator) expected_qubit_operator = jordan_wigner(operator) @@ -212,11 +204,11 @@ def test_interaction_operator(self): ones = numpy.ones((n_orbitals,) * 2) triu = numpy.triu(ones, 1) shape = (n_orbitals**2, 1) - mask = (triu.reshape(shape) * ones.reshape(shape[::-1]) + - ones.reshape(shape) * triu.reshape(shape[::-1])).reshape( - (n_orbitals,) * 4) - assert numpy.allclose(mask * two_body_tensor, - numpy.zeros((n_orbitals,) * 4)) + mask = ( + triu.reshape(shape) * ones.reshape(shape[::-1]) + + ones.reshape(shape) * triu.reshape(shape[::-1]) + ).reshape((n_orbitals,) * 4) + assert numpy.allclose(mask * two_body_tensor, numpy.zeros((n_orbitals,) * 4)) for term in normal_ordered_operator: order = len(term) // 2 left_term, right_term = term[:order], term[order:] @@ -231,39 +223,31 @@ def test_exceptions(self): class TestReorder(unittest.TestCase): - def test_reorder(self): - def shift_by_one(x, y): return (x + 1) % y operator = FermionOperator('1^ 2^ 3 4', -3.17) reordered = reorder(operator, shift_by_one) - self.assertEqual(reordered.terms, - {((2, 1), (3, 1), (4, 0), (0, 0)): -3.17}) + self.assertEqual(reordered.terms, {((2, 1), (3, 1), (4, 0), (0, 0)): -3.17}) reordered = reorder(operator, shift_by_one, reverse=True) - self.assertEqual(reordered.terms, - {((0, 1), (1, 1), (2, 0), (3, 0)): -3.17}) + self.assertEqual(reordered.terms, {((0, 1), (1, 1), (2, 0), (3, 0)): -3.17}) def test_reorder_boson(self): shift_by_one = lambda x, y: (x + 1) % y operator = BosonOperator('1^ 2^ 3 4', -3.17) reordered = reorder(operator, shift_by_one) - self.assertEqual(reordered.terms, - {((0, 0), (2, 1), (3, 1), (4, 0)): -3.17}) + self.assertEqual(reordered.terms, {((0, 0), (2, 1), (3, 1), (4, 0)): -3.17}) reordered = reorder(operator, shift_by_one, reverse=True) - self.assertEqual(reordered.terms, - {((0, 1), (1, 1), (2, 0), (3, 0)): -3.17}) + self.assertEqual(reordered.terms, {((0, 1), (1, 1), (2, 0), (3, 0)): -3.17}) def test_reorder_quad(self): shift_by_one = lambda x, y: (x + 1) % y operator = QuadOperator('q1 q2 p3 p4', -3.17) reordered = reorder(operator, shift_by_one) - self.assertEqual(reordered.terms, - {((0, 'p'), (2, 'q'), (3, 'q'), (4, 'p')): -3.17}) + self.assertEqual(reordered.terms, {((0, 'p'), (2, 'q'), (3, 'q'), (4, 'p')): -3.17}) reordered = reorder(operator, shift_by_one, reverse=True) - self.assertEqual(reordered.terms, - {((0, 'q'), (1, 'q'), (2, 'p'), (3, 'p')): -3.17}) + self.assertEqual(reordered.terms, {((0, 'q'), (1, 'q'), (2, 'p'), (3, 'p')): -3.17}) def test_up_then_down(self): for LadderOp in (FermionOperator, BosonOperator): diff --git a/src/openfermion/transforms/opconversions/verstraete_cirac.py b/src/openfermion/transforms/opconversions/verstraete_cirac.py index 5832247bc..d61bf98b2 100644 --- a/src/openfermion/transforms/opconversions/verstraete_cirac.py +++ b/src/openfermion/transforms/opconversions/verstraete_cirac.py @@ -18,11 +18,9 @@ from openfermion.hamiltonians.special_operators import majorana_operator -def verstraete_cirac_2d_square(operator, - x_dimension, - y_dimension, - add_auxiliary_hamiltonian=True, - snake=False): +def verstraete_cirac_2d_square( + operator, x_dimension, y_dimension, add_auxiliary_hamiltonian=True, snake=False +): """Apply the Verstraete-Cirac transform on a 2-d square lattice. Note that this transformation adds one auxiliary fermionic mode @@ -45,8 +43,7 @@ def verstraete_cirac_2d_square(operator, transformed_operator: A QubitOperator. """ if x_dimension % 2 != 0: - raise NotImplementedError('Currently only even x_dimension ' - 'is supported.') + raise NotImplementedError('Currently only even x_dimension ' 'is supported.') # Obtain the vertical edges of the snake ordering vert_edges = vertical_edges_snake(x_dimension, y_dimension) @@ -54,11 +51,10 @@ def verstraete_cirac_2d_square(operator, # Initialize a coefficient to scale the auxiliary Hamiltonian by. # The gap of the auxiliary Hamiltonian needs to be large enough to # to ensure the ground state of the original operator is preserved. - aux_ham_coefficient = 1. + aux_ham_coefficient = 1.0 transformed_operator = QubitOperator() for term in operator.terms: - indices = [ladder_operator[0] for ladder_operator in term] raise_or_lower = [ladder_operator[1] for ladder_operator in term] coefficient = operator.terms[term] @@ -66,8 +62,7 @@ def verstraete_cirac_2d_square(operator, # If the indices aren't in snake order, we need to convert them if not snake: indices = [ - lexicographic_index_to_snake_index(index, x_dimension, - y_dimension) + lexicographic_index_to_snake_index(index, x_dimension, y_dimension) for index in indices ] @@ -77,7 +72,8 @@ def verstraete_cirac_2d_square(operator, # Initialize the transformed term as a FermionOperator transformed_term = FermionOperator( - tuple(zip(transformed_indices, raise_or_lower)), coefficient) + tuple(zip(transformed_indices, raise_or_lower)), coefficient + ) # If necessary, multiply the transformed term by a stabilizer to # cancel out Jordan-Wigner strings @@ -95,8 +91,7 @@ def verstraete_cirac_2d_square(operator, bot_aux = expand_aux_index(bot) # Get the column that this edge is on - col, _ = snake_index_to_coordinates(top, x_dimension, - y_dimension) + col, _ = snake_index_to_coordinates(top, x_dimension, y_dimension) # Multiply by a stabilizer. If the column is even, the # stabilizer corresponds to an edge that points down; @@ -121,8 +116,7 @@ def verstraete_cirac_2d_square(operator, # Construct the auxiliary Hamiltonian aux_ham = FermionOperator() for i, j in aux_ham_graph.edges(): - aux_ham -= stabilizer_local_2d_square(i, j, x_dimension, - y_dimension) + aux_ham -= stabilizer_local_2d_square(i, j, x_dimension, y_dimension) # Add an identity term to ensure that the auxiliary Hamiltonian # has ground energy equal to zero @@ -142,7 +136,7 @@ def stabilizer(i, j): In the original paper, these are referred to as P_{ij}.""" c_i = majorana_operator((i, 0)) d_j = majorana_operator((j, 1)) - return 1.j * c_i * d_j + return 1.0j * c_i * d_j def stabilizer_local_2d_square(i, j, x_dimension, y_dimension): @@ -153,8 +147,9 @@ def stabilizer_local_2d_square(i, j, x_dimension, y_dimension): """ i_col, i_row = snake_index_to_coordinates(i, x_dimension, y_dimension) j_col, j_row = snake_index_to_coordinates(j, x_dimension, y_dimension) - if not (abs(i_row - j_row) == 1 and i_col == j_col or - abs(i_col - j_col) == 1 and i_row == j_row): + if not ( + abs(i_row - j_row) == 1 and i_col == j_col or abs(i_col - j_col) == 1 and i_row == j_row + ): raise ValueError("Vertices i and j are not adjacent") # Get the JWT indices in the combined system @@ -169,11 +164,11 @@ def stabilizer_local_2d_square(i, j, x_dimension, y_dimension): # Term is right-closed if i_col < x_dimension - 1: extra_term_top = expand_aux_index( - coordinates_to_snake_index(i_col + 1, top_row, x_dimension, - y_dimension)) + coordinates_to_snake_index(i_col + 1, top_row, x_dimension, y_dimension) + ) extra_term_bot = expand_aux_index( - coordinates_to_snake_index(i_col + 1, top_row + 1, - x_dimension, y_dimension)) + coordinates_to_snake_index(i_col + 1, top_row + 1, x_dimension, y_dimension) + ) if (i_col + 1) % 2 == 0: stab *= stabilizer(extra_term_top, extra_term_bot) else: @@ -182,11 +177,11 @@ def stabilizer_local_2d_square(i, j, x_dimension, y_dimension): # Term is left-closed if i_col > 0: extra_term_top = expand_aux_index( - coordinates_to_snake_index(i_col - 1, top_row, x_dimension, - y_dimension)) + coordinates_to_snake_index(i_col - 1, top_row, x_dimension, y_dimension) + ) extra_term_bot = expand_aux_index( - coordinates_to_snake_index(i_col - 1, top_row + 1, - x_dimension, y_dimension)) + coordinates_to_snake_index(i_col - 1, top_row + 1, x_dimension, y_dimension) + ) if (i_col - 1) % 2 == 0: stab *= stabilizer(extra_term_top, extra_term_bot) else: @@ -209,21 +204,21 @@ def auxiliary_graph_2d_square(x_dimension, y_dimension): graph.add_edge(k + 1, k) # Add bottom edge graph.add_edge( - coordinates_to_snake_index(k, y_dimension - 1, x_dimension, - y_dimension), - coordinates_to_snake_index(k + 1, y_dimension - 1, x_dimension, - y_dimension)) + coordinates_to_snake_index(k, y_dimension - 1, x_dimension, y_dimension), + coordinates_to_snake_index(k + 1, y_dimension - 1, x_dimension, y_dimension), + ) for l in range(y_dimension - 1): # Add edges between rows l and l + 1 # Add left edge graph.add_edge( coordinates_to_snake_index(k, l, x_dimension, y_dimension), - coordinates_to_snake_index(k, l + 1, x_dimension, y_dimension)) + coordinates_to_snake_index(k, l + 1, x_dimension, y_dimension), + ) # Add right edge graph.add_edge( - coordinates_to_snake_index(k + 1, l + 1, x_dimension, - y_dimension), - coordinates_to_snake_index(k + 1, l, x_dimension, y_dimension)) + coordinates_to_snake_index(k + 1, l + 1, x_dimension, y_dimension), + coordinates_to_snake_index(k + 1, l, x_dimension, y_dimension), + ) return graph diff --git a/src/openfermion/transforms/opconversions/verstraete_cirac_test.py b/src/openfermion/transforms/opconversions/verstraete_cirac_test.py index c73b2f0e9..b8c20a444 100644 --- a/src/openfermion/transforms/opconversions/verstraete_cirac_test.py +++ b/src/openfermion/transforms/opconversions/verstraete_cirac_test.py @@ -17,8 +17,10 @@ from openfermion.linalg import get_sparse_operator, get_ground_state from openfermion.transforms.opconversions import verstraete_cirac_2d_square from openfermion.transforms.opconversions.verstraete_cirac import ( - coordinates_to_snake_index, snake_index_to_coordinates, - stabilizer_local_2d_square) + coordinates_to_snake_index, + snake_index_to_coordinates, + stabilizer_local_2d_square, +) class VerstraeteCirac2dSquareGroundStateTest(unittest.TestCase): @@ -29,13 +31,15 @@ def setUp(self): self.y_dimension = 3 # Create a Hamiltonian with nearest-neighbor hopping terms - self.ferm_op = fermi_hubbard(self.x_dimension, self.y_dimension, 1., 0., - 0., 0., False, True) + self.ferm_op = fermi_hubbard( + self.x_dimension, self.y_dimension, 1.0, 0.0, 0.0, 0.0, False, True + ) # Get the ground energy and ground state self.ferm_op_sparse = get_sparse_operator(self.ferm_op) - self.ferm_op_ground_energy, self.ferm_op_ground_state = ( - get_ground_state(self.ferm_op_sparse)) + self.ferm_op_ground_energy, self.ferm_op_ground_state = get_ground_state( + self.ferm_op_sparse + ) # Transform the FermionOperator to a QubitOperator self.transformed_op = verstraete_cirac_2d_square( @@ -43,17 +47,18 @@ def setUp(self): self.x_dimension, self.y_dimension, add_auxiliary_hamiltonian=True, - snake=False) + snake=False, + ) # Get the ground energy and state of the transformed operator self.transformed_sparse = get_sparse_operator(self.transformed_op) - self.transformed_ground_energy, self.transformed_ground_state = ( - get_ground_state(self.transformed_sparse)) + self.transformed_ground_energy, self.transformed_ground_state = get_ground_state( + self.transformed_sparse + ) def test_ground_energy(self): """Test that the transformation preserves the ground energy.""" - self.assertAlmostEqual(self.transformed_ground_energy, - self.ferm_op_ground_energy) + self.assertAlmostEqual(self.transformed_ground_energy, self.ferm_op_ground_energy) class VerstraeteCirac2dSquareOperatorLocalityTest(unittest.TestCase): @@ -64,8 +69,9 @@ def setUp(self): self.y_dimension = 6 # Create a Hubbard Hamiltonian - self.ferm_op = fermi_hubbard(self.x_dimension, self.y_dimension, 1.0, - 4.0, 0.0, 0.0, False, True) + self.ferm_op = fermi_hubbard( + self.x_dimension, self.y_dimension, 1.0, 4.0, 0.0, 0.0, False, True + ) # Transform the FermionOperator to a QubitOperator without including # the auxiliary Hamiltonian @@ -74,7 +80,8 @@ def setUp(self): self.x_dimension, self.y_dimension, add_auxiliary_hamiltonian=False, - snake=False) + snake=False, + ) self.transformed_op_no_aux.compress() # Transform the FermionOperator to a QubitOperator, including @@ -84,7 +91,8 @@ def setUp(self): self.x_dimension, self.y_dimension, add_auxiliary_hamiltonian=True, - snake=False) + snake=False, + ) self.transformed_op_aux.compress() def test_operator_locality_no_aux(self): @@ -104,7 +112,7 @@ class ExceptionTest(unittest.TestCase): """Test that exceptions are raised correctly.""" def test_verstraete_cirac_2d_square(self): - ferm_op = fermi_hubbard(3, 2, 1., 0., spinless=True) + ferm_op = fermi_hubbard(3, 2, 1.0, 0.0, spinless=True) with self.assertRaises(NotImplementedError): _ = verstraete_cirac_2d_square(ferm_op, 3, 2) diff --git a/src/openfermion/transforms/repconversions/__init__.py b/src/openfermion/transforms/repconversions/__init__.py index 8a5b9399e..6ac0b1150 100644 --- a/src/openfermion/transforms/repconversions/__init__.py +++ b/src/openfermion/transforms/repconversions/__init__.py @@ -5,21 +5,11 @@ get_quadratic_hamiltonian, ) -from .fourier_transforms import ( - fourier_transform, - inverse_fourier_transform, -) +from .fourier_transforms import fourier_transform, inverse_fourier_transform -from .operator_tapering import ( - freeze_orbitals, - prune_unused_indices, -) +from .operator_tapering import freeze_orbitals, prune_unused_indices -from .qubit_operator_transforms import ( - project_onto_sector, - projection_error, - rotate_qubit_by_pauli, -) +from .qubit_operator_transforms import project_onto_sector, projection_error, rotate_qubit_by_pauli from .qubit_tapering_from_stabilizer import ( StabilizerError, @@ -30,8 +20,4 @@ fix_single_term, ) -from .weyl_ordering import ( - mccoy, - weyl_polynomial_quantization, - symmetric_ordering, -) +from .weyl_ordering import mccoy, weyl_polynomial_quantization, symmetric_ordering diff --git a/src/openfermion/transforms/repconversions/conversions.py b/src/openfermion/transforms/repconversions/conversions.py index 08ebe1267..c129ee469 100644 --- a/src/openfermion/transforms/repconversions/conversions.py +++ b/src/openfermion/transforms/repconversions/conversions.py @@ -14,13 +14,16 @@ from openfermion.config import EQ_TOLERANCE from openfermion.ops.operators import FermionOperator -from openfermion.ops.representations import (DiagonalCoulombHamiltonian, - InteractionOperator, - InteractionOperatorError) -from openfermion.transforms.opconversions import (check_no_sympy, - normal_ordered) +from openfermion.ops.representations import ( + DiagonalCoulombHamiltonian, + InteractionOperator, + InteractionOperatorError, +) +from openfermion.transforms.opconversions import check_no_sympy, normal_ordered from openfermion.ops.representations.quadratic_hamiltonian import ( - QuadraticHamiltonian, QuadraticHamiltonianError) + QuadraticHamiltonian, + QuadraticHamiltonianError, +) from openfermion.chem import MolecularData @@ -28,10 +31,9 @@ from openfermion.utils import operator_utils as op_utils -def get_quadratic_hamiltonian(fermion_operator, - chemical_potential=0., - n_qubits=None, - ignore_incompatible_terms=False): +def get_quadratic_hamiltonian( + fermion_operator, chemical_potential=0.0, n_qubits=None, ignore_incompatible_terms=False +): r"""Convert a quadratic fermionic operator to QuadraticHamiltonian. Args: @@ -71,7 +73,7 @@ def get_quadratic_hamiltonian(fermion_operator, # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) - constant = 0. + constant = 0.0 combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex) antisymmetric_part = numpy.zeros((n_qubits, n_qubits), complex) @@ -98,73 +100,72 @@ def get_quadratic_hamiltonian(fermion_operator, conjugate_term = ((p, 0), (q, 0)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( - 'FermionOperator does not map ' - 'to QuadraticHamiltonian (not Hermitian).') + 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).' + ) else: - matching_coefficient = -fermion_operator.terms[ - conjugate_term].conjugate() + matching_coefficient = -fermion_operator.terms[conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' - 'to QuadraticHamiltonian (not Hermitian).') - antisymmetric_part[p, q] += .5 * coefficient - antisymmetric_part[q, p] -= .5 * coefficient + 'to QuadraticHamiltonian (not Hermitian).' + ) + antisymmetric_part[p, q] += 0.5 * coefficient + antisymmetric_part[q, p] -= 0.5 * coefficient else: # ladder_type == [0, 0] # Need to check that the corresponding [1, 1] term is present conjugate_term = ((p, 1), (q, 1)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( - 'FermionOperator does not map ' - 'to QuadraticHamiltonian (not Hermitian).') + 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).' + ) else: - matching_coefficient = -fermion_operator.terms[ - conjugate_term].conjugate() + matching_coefficient = -fermion_operator.terms[conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' - 'to QuadraticHamiltonian (not Hermitian).') - antisymmetric_part[p, q] -= .5 * coefficient.conjugate() - antisymmetric_part[q, p] += .5 * coefficient.conjugate() + 'to QuadraticHamiltonian (not Hermitian).' + ) + antisymmetric_part[p, q] -= 0.5 * coefficient.conjugate() + antisymmetric_part[q, p] += 0.5 * coefficient.conjugate() elif not ignore_incompatible_terms: # Operator contains non-quadratic terms - raise QuadraticHamiltonianError('FermionOperator does not map ' - 'to QuadraticHamiltonian ' - '(contains non-quadratic terms).') + raise QuadraticHamiltonianError( + 'FermionOperator does not map ' + 'to QuadraticHamiltonian ' + '(contains non-quadratic terms).' + ) # Compute Hermitian part - hermitian_part = (combined_hermitian_part + - chemical_potential * numpy.eye(n_qubits)) + hermitian_part = combined_hermitian_part + chemical_potential * numpy.eye(n_qubits) # Check that the operator is Hermitian if not op_utils.is_hermitian(hermitian_part): raise QuadraticHamiltonianError( - 'FermionOperator does not map ' - 'to QuadraticHamiltonian (not Hermitian).') + 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).' + ) # Form QuadraticHamiltonian and return. discrepancy = numpy.max(numpy.abs(antisymmetric_part)) if discrepancy < EQ_TOLERANCE: # Hamiltonian conserves particle number quadratic_hamiltonian = QuadraticHamiltonian( - hermitian_part, - constant=constant, - chemical_potential=chemical_potential) + hermitian_part, constant=constant, chemical_potential=chemical_potential + ) else: # Hamiltonian does not conserve particle number - quadratic_hamiltonian = QuadraticHamiltonian(hermitian_part, - antisymmetric_part, - constant, - chemical_potential) + quadratic_hamiltonian = QuadraticHamiltonian( + hermitian_part, antisymmetric_part, constant, chemical_potential + ) return quadratic_hamiltonian -def get_diagonal_coulomb_hamiltonian(fermion_operator, - n_qubits=None, - ignore_incompatible_terms=False): +def get_diagonal_coulomb_hamiltonian( + fermion_operator, n_qubits=None, ignore_incompatible_terms=False +): r"""Convert a FermionOperator to a DiagonalCoulombHamiltonian. Args: @@ -190,7 +191,7 @@ def get_diagonal_coulomb_hamiltonian(fermion_operator, raise ValueError('Invalid number of qubits specified.') fermion_operator = normal_ordered(fermion_operator) - constant = 0. + constant = 0.0 one_body = numpy.zeros((n_qubits, n_qubits), complex) two_body = numpy.zeros((n_qubits, n_qubits), float) @@ -214,25 +215,30 @@ def get_diagonal_coulomb_hamiltonian(fermion_operator, if abs(numpy.imag(coefficient)) > EQ_TOLERANCE: raise ValueError( 'FermionOperator does not map to ' - 'DiagonalCoulombHamiltonian (not Hermitian).') + 'DiagonalCoulombHamiltonian (not Hermitian).' + ) coefficient = numpy.real(coefficient) - two_body[p, q] = -.5 * coefficient - two_body[q, p] = -.5 * coefficient + two_body[p, q] = -0.5 * coefficient + two_body[q, p] = -0.5 * coefficient elif not ignore_incompatible_terms: - raise ValueError('FermionOperator does not map to ' - 'DiagonalCoulombHamiltonian ' - '(contains terms with indices ' - '{}).'.format((p, q, r, s))) + raise ValueError( + 'FermionOperator does not map to ' + 'DiagonalCoulombHamiltonian ' + '(contains terms with indices ' + '{}).'.format((p, q, r, s)) + ) elif not ignore_incompatible_terms: - raise ValueError('FermionOperator does not map to ' - 'DiagonalCoulombHamiltonian (contains terms ' - 'with action {}.'.format(tuple(actions))) + raise ValueError( + 'FermionOperator does not map to ' + 'DiagonalCoulombHamiltonian (contains terms ' + 'with action {}.'.format(tuple(actions)) + ) # Check that the operator is Hermitian if not op_utils.is_hermitian(one_body): raise ValueError( - 'FermionOperator does not map to DiagonalCoulombHamiltonian ' - '(not Hermitian).') + 'FermionOperator does not map to DiagonalCoulombHamiltonian ' '(not Hermitian).' + ) return DiagonalCoulombHamiltonian(one_body, two_body, constant) @@ -269,7 +275,7 @@ def get_interaction_operator(fermion_operator, n_qubits=None): # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) - constant = 0. + constant = 0.0 one_body = numpy.zeros((n_qubits, n_qubits), complex) two_body = numpy.zeros((n_qubits, n_qubits, n_qubits, n_qubits), complex) @@ -292,8 +298,9 @@ def get_interaction_operator(fermion_operator, n_qubits=None): p, q = [operator[0] for operator in term] one_body[p, q] = coefficient else: - raise InteractionOperatorError('FermionOperator does not map ' - 'to InteractionOperator.') + raise InteractionOperatorError( + 'FermionOperator does not map ' 'to InteractionOperator.' + ) elif len(term) == 4: # Handle two-body terms. @@ -301,26 +308,30 @@ def get_interaction_operator(fermion_operator, n_qubits=None): p, q, r, s = [operator[0] for operator in term] two_body[p, q, r, s] = coefficient else: - raise InteractionOperatorError('FermionOperator does not map ' - 'to InteractionOperator.') + raise InteractionOperatorError( + 'FermionOperator does not map ' 'to InteractionOperator.' + ) else: # Handle non-molecular Hamiltonian. - raise InteractionOperatorError('FermionOperator does not map ' - 'to InteractionOperator.') + raise InteractionOperatorError( + 'FermionOperator does not map ' 'to InteractionOperator.' + ) # Form InteractionOperator and return. interaction_operator = InteractionOperator(constant, one_body, two_body) return interaction_operator -def get_molecular_data(interaction_operator, - geometry=None, - basis=None, - multiplicity=None, - n_electrons=None, - reduce_spin=True, - data_directory=None): +def get_molecular_data( + interaction_operator, + geometry=None, + basis=None, + multiplicity=None, + n_electrons=None, + reduce_spin=True, + data_directory=None, +): """Output a MolecularData object generated from an InteractionOperator Args: @@ -346,10 +357,9 @@ def get_molecular_data(interaction_operator, n_spin_orbitals = interaction_operator.n_qubits # Introduce bare molecular operator to fill - molecule = MolecularData(geometry=geometry, - basis=basis, - multiplicity=multiplicity, - data_directory=data_directory) + molecule = MolecularData( + geometry=geometry, basis=basis, multiplicity=multiplicity, data_directory=data_directory + ) molecule.nuclear_repulsion = interaction_operator.constant @@ -362,10 +372,11 @@ def get_molecular_data(interaction_operator, molecule.n_orbitals = len(reduction_indices) molecule.one_body_integrals = interaction_operator.one_body_tensor[ - numpy.ix_(reduction_indices, reduction_indices)] + numpy.ix_(reduction_indices, reduction_indices) + ] molecule.two_body_integrals = interaction_operator.two_body_tensor[ - numpy.ix_(reduction_indices, reduction_indices, reduction_indices, - reduction_indices)] + numpy.ix_(reduction_indices, reduction_indices, reduction_indices, reduction_indices) + ] # Fill in other metadata molecule.overlap_integrals = numpy.eye(molecule.n_orbitals) diff --git a/src/openfermion/transforms/repconversions/conversions_test.py b/src/openfermion/transforms/repconversions/conversions_test.py index d9db945ee..bddcfc5fc 100644 --- a/src/openfermion/transforms/repconversions/conversions_test.py +++ b/src/openfermion/transforms/repconversions/conversions_test.py @@ -15,10 +15,9 @@ from openfermion.chem import MolecularData from openfermion.config import EQ_TOLERANCE -from openfermion.ops.operators import (FermionOperator, QubitOperator) +from openfermion.ops.operators import FermionOperator, QubitOperator from openfermion.hamiltonians import fermi_hubbard -from openfermion.ops.representations import (InteractionOperatorError, - QuadraticHamiltonianError) +from openfermion.ops.representations import InteractionOperatorError, QuadraticHamiltonianError from openfermion.testing.testing_utils import random_quadratic_hamiltonian from openfermion.transforms.opconversions import get_fermion_operator from openfermion.transforms.opconversions.term_reordering import normal_ordered @@ -32,9 +31,8 @@ class GetInteractionOperatorTest(unittest.TestCase): - def test_get_molecular_operator(self): - coefficient = 3. + coefficient = 3.0 operators = ((2, 1), (3, 0), (0, 0), (3, 1)) op = FermionOperator(operators, coefficient) @@ -47,9 +45,7 @@ def test_get_molecular_operator(self): op *= 0.5 * EQ_TOLERANCE molecular_operator = get_interaction_operator(op) self.assertEqual(molecular_operator.constant, 0) - self.assertTrue( - numpy.allclose(molecular_operator.one_body_tensor, - numpy.zeros((2, 2)))) + self.assertTrue(numpy.allclose(molecular_operator.one_body_tensor, numpy.zeros((2, 2)))) def test_get_interaction_operator_bad_input(self): with self.assertRaises(TypeError): @@ -59,8 +55,7 @@ def test_get_interaction_operator_below_threshold(self): op = get_interaction_operator(FermionOperator('1^ 1', 0.0)) self.assertEqual(op.constant, 0) self.assertTrue(numpy.allclose(op.one_body_tensor, numpy.zeros((1, 1)))) - self.assertTrue( - numpy.allclose(op.two_body_tensor, numpy.zeros((1, 1, 1, 1)))) + self.assertTrue(numpy.allclose(op.two_body_tensor, numpy.zeros((1, 1, 1, 1)))) def test_get_interaction_operator_too_few_qubits(self): with self.assertRaises(ValueError): @@ -80,55 +75,58 @@ def test_get_interaction_operator_nonmolecular_term(self): def test_get_molecular_data(self): """Test conversion to MolecularData from InteractionOperator""" - coefficient = 3. + coefficient = 3.0 operators = ((2, 1), (3, 0), (0, 0), (3, 1)) op = FermionOperator(operators, coefficient) molecular_operator = get_interaction_operator(op) - molecule = get_molecular_data(molecular_operator, - geometry=[['H', [0, 0, 0]]], - basis='aug-cc-pvtz', - multiplicity=2, - n_electrons=1) + molecule = get_molecular_data( + molecular_operator, + geometry=[['H', [0, 0, 0]]], + basis='aug-cc-pvtz', + multiplicity=2, + n_electrons=1, + ) self.assertTrue(isinstance(molecule, MolecularData)) - molecule = get_molecular_data(molecular_operator, - geometry=[['H', [0, 0, 0]]], - basis='aug-cc-pvtz', - multiplicity=2, - n_electrons=1, - reduce_spin=False) + molecule = get_molecular_data( + molecular_operator, + geometry=[['H', [0, 0, 0]]], + basis='aug-cc-pvtz', + multiplicity=2, + n_electrons=1, + reduce_spin=False, + ) self.assertTrue(isinstance(molecule, MolecularData)) class GetQuadraticHamiltonianTest(unittest.TestCase): - def setUp(self): - self.hermitian_op = FermionOperator((), 1.) - self.hermitian_op += FermionOperator('1^ 1', 3.) - self.hermitian_op += FermionOperator('1^ 2', 3. + 4.j) - self.hermitian_op += FermionOperator('2^ 1', 3. - 4.j) - self.hermitian_op += FermionOperator('3^ 4^', 2. + 5.j) - self.hermitian_op += FermionOperator('4 3', 2. - 5.j) - - self.hermitian_op_pc = FermionOperator((), 1.) - self.hermitian_op_pc += FermionOperator('1^ 1', 3.) - self.hermitian_op_pc += FermionOperator('1^ 2', 3. + 4.j) - self.hermitian_op_pc += FermionOperator('2^ 1', 3. - 4.j) - self.hermitian_op_pc += FermionOperator('3^ 4', 2. + 5.j) - self.hermitian_op_pc += FermionOperator('4^ 3', 2. - 5.j) - - self.hermitian_op_bad_term = FermionOperator('1^ 1 2', 3.) - self.hermitian_op_bad_term += FermionOperator('2^ 1^ 1', 3.) + self.hermitian_op = FermionOperator((), 1.0) + self.hermitian_op += FermionOperator('1^ 1', 3.0) + self.hermitian_op += FermionOperator('1^ 2', 3.0 + 4.0j) + self.hermitian_op += FermionOperator('2^ 1', 3.0 - 4.0j) + self.hermitian_op += FermionOperator('3^ 4^', 2.0 + 5.0j) + self.hermitian_op += FermionOperator('4 3', 2.0 - 5.0j) + + self.hermitian_op_pc = FermionOperator((), 1.0) + self.hermitian_op_pc += FermionOperator('1^ 1', 3.0) + self.hermitian_op_pc += FermionOperator('1^ 2', 3.0 + 4.0j) + self.hermitian_op_pc += FermionOperator('2^ 1', 3.0 - 4.0j) + self.hermitian_op_pc += FermionOperator('3^ 4', 2.0 + 5.0j) + self.hermitian_op_pc += FermionOperator('4^ 3', 2.0 - 5.0j) + + self.hermitian_op_bad_term = FermionOperator('1^ 1 2', 3.0) + self.hermitian_op_bad_term += FermionOperator('2^ 1^ 1', 3.0) self.not_hermitian_1 = FermionOperator('2^ 0^') self.not_hermitian_2 = FermionOperator('3^ 0^') - self.not_hermitian_2 += FermionOperator('3 0', 3.) + self.not_hermitian_2 += FermionOperator('3 0', 3.0) self.not_hermitian_3 = FermionOperator('2 0') self.not_hermitian_4 = FermionOperator('4 0') - self.not_hermitian_4 += FermionOperator('4^ 0^', 3.) - self.not_hermitian_5 = FermionOperator('2^ 3', 3.) - self.not_hermitian_5 += FermionOperator('3^ 2', 2.) + self.not_hermitian_4 += FermionOperator('4^ 0^', 3.0) + self.not_hermitian_5 = FermionOperator('2^ 3', 3.0) + self.not_hermitian_5 += FermionOperator('3^ 2', 2.0) def test_get_quadratic_hamiltonian_hermitian(self): """Test properly formed quadratic Hamiltonians.""" @@ -139,8 +137,7 @@ def test_get_quadratic_hamiltonian_hermitian(self): self.assertTrue(normal_ordered(self.hermitian_op) == fermion_operator) # Non-particle-number-conserving chemical potential - quadratic_op = get_quadratic_hamiltonian(self.hermitian_op, - chemical_potential=3.) + quadratic_op = get_quadratic_hamiltonian(self.hermitian_op, chemical_potential=3.0) fermion_operator = get_fermion_operator(quadratic_op) fermion_operator = normal_ordered(fermion_operator) self.assertTrue(normal_ordered(self.hermitian_op) == fermion_operator) @@ -149,11 +146,10 @@ def test_get_quadratic_hamiltonian_hermitian(self): quadratic_op = get_quadratic_hamiltonian(self.hermitian_op_pc) fermion_operator = get_fermion_operator(quadratic_op) fermion_operator = normal_ordered(fermion_operator) - self.assertTrue( - normal_ordered(self.hermitian_op_pc) == fermion_operator) + self.assertTrue(normal_ordered(self.hermitian_op_pc) == fermion_operator) fop = FermionOperator('1^ 1') - fop *= 0.5E-8 + fop *= 0.5e-8 quad_op = get_quadratic_hamiltonian(fop) self.assertEqual(quad_op.constant, 0) @@ -191,60 +187,68 @@ def test_get_quadratic_hamiltonian_too_few_qubits(self): get_quadratic_hamiltonian(FermionOperator('3^ 2^'), n_qubits=3) def test_ignore_incompatible_terms(self): - - ferm_op = (FermionOperator('0^ 2') + FermionOperator('2^ 0') + - FermionOperator('1^ 0^ 2') + FermionOperator('1^ 0^ 2 1') + - FermionOperator('0^ 0 1^ 1') + FermionOperator('1^ 2^ 1 2')) - converted_op = get_quadratic_hamiltonian(ferm_op, - ignore_incompatible_terms=True) + ferm_op = ( + FermionOperator('0^ 2') + + FermionOperator('2^ 0') + + FermionOperator('1^ 0^ 2') + + FermionOperator('1^ 0^ 2 1') + + FermionOperator('0^ 0 1^ 1') + + FermionOperator('1^ 2^ 1 2') + ) + converted_op = get_quadratic_hamiltonian(ferm_op, ignore_incompatible_terms=True) self.assertTrue( - numpy.allclose(converted_op.hermitian_part, - numpy.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]))) + numpy.allclose( + converted_op.hermitian_part, numpy.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]) + ) + ) class GetDiagonalCoulombHamiltonianTest(unittest.TestCase): - def test_hubbard(self): x_dim = 4 y_dim = 5 - tunneling = 2. - coulomb = 3. - chemical_potential = 7. - magnetic_field = 11. + tunneling = 2.0 + coulomb = 3.0 + chemical_potential = 7.0 + magnetic_field = 11.0 periodic = False - hubbard_model = fermi_hubbard(x_dim, y_dim, tunneling, coulomb, - chemical_potential, magnetic_field, - periodic) + hubbard_model = fermi_hubbard( + x_dim, y_dim, tunneling, coulomb, chemical_potential, magnetic_field, periodic + ) self.assertTrue( - normal_ordered(hubbard_model) == normal_ordered( - get_fermion_operator( - get_diagonal_coulomb_hamiltonian(hubbard_model)))) + normal_ordered(hubbard_model) + == normal_ordered(get_fermion_operator(get_diagonal_coulomb_hamiltonian(hubbard_model))) + ) def test_random_quadratic(self): n_qubits = 5 quad_ham = random_quadratic_hamiltonian(n_qubits, True) ferm_op = get_fermion_operator(quad_ham) self.assertTrue( - normal_ordered(ferm_op) == normal_ordered( - get_fermion_operator(get_diagonal_coulomb_hamiltonian( - ferm_op)))) + normal_ordered(ferm_op) + == normal_ordered(get_fermion_operator(get_diagonal_coulomb_hamiltonian(ferm_op))) + ) def test_ignore_incompatible_terms(self): - - ferm_op = (FermionOperator('0^ 2') + FermionOperator('2^ 0') + - FermionOperator('1^ 0^ 2') + FermionOperator('1^ 0^ 2 1') + - FermionOperator('0^ 0 1^ 1') + FermionOperator('1^ 2^ 1 2')) - converted_op = get_diagonal_coulomb_hamiltonian( - ferm_op, ignore_incompatible_terms=True) + ferm_op = ( + FermionOperator('0^ 2') + + FermionOperator('2^ 0') + + FermionOperator('1^ 0^ 2') + + FermionOperator('1^ 0^ 2 1') + + FermionOperator('0^ 0 1^ 1') + + FermionOperator('1^ 2^ 1 2') + ) + converted_op = get_diagonal_coulomb_hamiltonian(ferm_op, ignore_incompatible_terms=True) self.assertTrue( - numpy.allclose(converted_op.one_body, - numpy.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]]))) + numpy.allclose(converted_op.one_body, numpy.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]])) + ) self.assertTrue( numpy.allclose( - converted_op.two_body, - numpy.array([[0, 0.5, 0], [0.5, 0, -0.5], [0, -0.5, 0]]))) + converted_op.two_body, numpy.array([[0, 0.5, 0], [0.5, 0, -0.5], [0, -0.5, 0]]) + ) + ) def test_exceptions(self): op1 = QubitOperator() @@ -252,7 +256,7 @@ def test_exceptions(self): op3 = FermionOperator('0^ 1^') op4 = FermionOperator('0^ 1^ 2^ 3') op5 = FermionOperator('0^ 3') - op6 = FermionOperator('0^ 0 1^ 1', 1.j) + op6 = FermionOperator('0^ 0 1^ 1', 1.0j) op7 = FermionOperator('0^ 1^ 2 3') with self.assertRaises(TypeError): _ = get_diagonal_coulomb_hamiltonian(op1) @@ -276,4 +280,4 @@ def test_threshold(self): fop = FermionOperator('1^ 1') fop *= 0.5 * EQ_TOLERANCE op = get_diagonal_coulomb_hamiltonian(fop) - self.assertEqual(op.constant, 0) \ No newline at end of file + self.assertEqual(op.constant, 0) diff --git a/src/openfermion/transforms/repconversions/fourier_transforms.py b/src/openfermion/transforms/repconversions/fourier_transforms.py index d0d7dd9de..a7f16b3e0 100644 --- a/src/openfermion/transforms/repconversions/fourier_transforms.py +++ b/src/openfermion/transforms/repconversions/fourier_transforms.py @@ -16,8 +16,7 @@ from openfermion.ops.operators import FermionOperator -def _fourier_transform_helper(hamiltonian, grid, spinless, phase_factor, - vec_func_1, vec_func_2): +def _fourier_transform_helper(hamiltonian, grid, spinless, phase_factor, vec_func_1, vec_func_2): hamiltonian_t = FermionOperator.zero() normalize_factor = numpy.sqrt(1.0 / float(grid.num_points)) @@ -35,8 +34,7 @@ def _fourier_transform_helper(hamiltonian, grid, spinless, phase_factor, if ladder_op_type == 1: exp_index *= -1.0 - element = FermionOperator(((orbital, ladder_op_type),), - numpy.exp(exp_index)) + element = FermionOperator(((orbital, ladder_op_type),), numpy.exp(exp_index)) new_basis += element new_basis *= normalize_factor @@ -66,12 +64,14 @@ def fourier_transform(hamiltonian, grid, spinless): Returns: FermionOperator: The fourier-transformed hamiltonian. """ - return _fourier_transform_helper(hamiltonian=hamiltonian, - grid=grid, - spinless=spinless, - phase_factor=+1, - vec_func_1=grid.momentum_vector, - vec_func_2=grid.position_vector) + return _fourier_transform_helper( + hamiltonian=hamiltonian, + grid=grid, + spinless=spinless, + phase_factor=+1, + vec_func_1=grid.momentum_vector, + vec_func_2=grid.position_vector, + ) def inverse_fourier_transform(hamiltonian, grid, spinless): @@ -92,9 +92,11 @@ def inverse_fourier_transform(hamiltonian, grid, spinless): Returns: FermionOperator: The inverse-fourier-transformed hamiltonian. """ - return _fourier_transform_helper(hamiltonian=hamiltonian, - grid=grid, - spinless=spinless, - phase_factor=-1, - vec_func_1=grid.position_vector, - vec_func_2=grid.momentum_vector) + return _fourier_transform_helper( + hamiltonian=hamiltonian, + grid=grid, + spinless=spinless, + phase_factor=-1, + vec_func_1=grid.position_vector, + vec_func_2=grid.momentum_vector, + ) diff --git a/src/openfermion/transforms/repconversions/fourier_transforms_test.py b/src/openfermion/transforms/repconversions/fourier_transforms_test.py index 88f1d5f5f..df3c7c81e 100644 --- a/src/openfermion/transforms/repconversions/fourier_transforms_test.py +++ b/src/openfermion/transforms/repconversions/fourier_transforms_test.py @@ -20,25 +20,23 @@ from openfermion.utils import is_hermitian from openfermion.transforms.repconversions.fourier_transforms import ( - fourier_transform, inverse_fourier_transform) + fourier_transform, + inverse_fourier_transform, +) class FourierTransformTest(unittest.TestCase): - def test_fourier_transform(self): for length in [2, 3]: grid = Grid(dimensions=1, scale=1.5, length=length) spinless_set = [True, False] geometry = [('H', (0.1,)), ('H', (0.5,))] for spinless in spinless_set: - h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, - True) - h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, - False) + h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, True) + h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) h_plane_wave_t = fourier_transform(h_plane_wave, grid, spinless) - self.assertEqual(normal_ordered(h_plane_wave_t), - normal_ordered(h_dual_basis)) + self.assertEqual(normal_ordered(h_plane_wave_t), normal_ordered(h_dual_basis)) # Verify that all 3 are Hermitian plane_wave_operator = get_sparse_operator(h_plane_wave) @@ -53,14 +51,10 @@ def test_inverse_fourier_transform_1d(self): spinless_set = [True, False] geometry = [('H', (0,)), ('H', (0.5,))] for spinless in spinless_set: - h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, - True) - h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, - False) - h_dual_basis_t = inverse_fourier_transform(h_dual_basis, grid, - spinless) - self.assertEqual(normal_ordered(h_dual_basis_t), - normal_ordered(h_plane_wave)) + h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, True) + h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) + h_dual_basis_t = inverse_fourier_transform(h_dual_basis, grid, spinless) + self.assertEqual(normal_ordered(h_dual_basis_t), normal_ordered(h_plane_wave)) def test_inverse_fourier_transform_2d(self): grid = Grid(dimensions=2, scale=1.5, length=3) @@ -69,5 +63,4 @@ def test_inverse_fourier_transform_2d(self): h_plane_wave = plane_wave_hamiltonian(grid, geometry, spinless, True) h_dual_basis = plane_wave_hamiltonian(grid, geometry, spinless, False) h_dual_basis_t = inverse_fourier_transform(h_dual_basis, grid, spinless) - self.assertEqual(normal_ordered(h_dual_basis_t), - normal_ordered(h_plane_wave)) \ No newline at end of file + self.assertEqual(normal_ordered(h_dual_basis_t), normal_ordered(h_plane_wave)) diff --git a/src/openfermion/transforms/repconversions/operator_tapering.py b/src/openfermion/transforms/repconversions/operator_tapering.py index edda67102..8b8cd0c06 100644 --- a/src/openfermion/transforms/repconversions/operator_tapering.py +++ b/src/openfermion/transforms/repconversions/operator_tapering.py @@ -13,9 +13,13 @@ import copy -from openfermion.ops.operators import (BosonOperator, FermionOperator, - MajoranaOperator, QuadOperator, - QubitOperator) +from openfermion.ops.operators import ( + BosonOperator, + FermionOperator, + MajoranaOperator, + QuadOperator, + QubitOperator, +) def freeze_orbitals(fermion_operator, occupied, unoccupied=None, prune=True): diff --git a/src/openfermion/transforms/repconversions/operator_tapering_test.py b/src/openfermion/transforms/repconversions/operator_tapering_test.py index 2897bf76d..60b89812c 100644 --- a/src/openfermion/transforms/repconversions/operator_tapering_test.py +++ b/src/openfermion/transforms/repconversions/operator_tapering_test.py @@ -14,11 +14,12 @@ import unittest from openfermion.ops.operators import FermionOperator, BosonOperator from openfermion.transforms.repconversions.operator_tapering import ( - freeze_orbitals, prune_unused_indices) + freeze_orbitals, + prune_unused_indices, +) class FreezeOrbitalsTest(unittest.TestCase): - def test_freeze_orbitals_nonvanishing(self): op = FermionOperator(((1, 1), (1, 0), (0, 1), (2, 0))) op_frozen = freeze_orbitals(op, [1]) @@ -32,10 +33,9 @@ def test_freeze_orbitals_vanishing(self): class PruneUnusedIndicesTest(unittest.TestCase): - def test_prune(self): for LadderOp in (FermionOperator, BosonOperator): op = LadderOp(((1, 1), (8, 1), (3, 0)), 0.5) op = prune_unused_indices(op) expected = LadderOp(((0, 1), (2, 1), (1, 0)), 0.5) - self.assertTrue(expected == op) \ No newline at end of file + self.assertTrue(expected == op) diff --git a/src/openfermion/transforms/repconversions/qubit_operator_transforms.py b/src/openfermion/transforms/repconversions/qubit_operator_transforms.py index 788f1b6d1..d5161abce 100644 --- a/src/openfermion/transforms/repconversions/qubit_operator_transforms.py +++ b/src/openfermion/transforms/repconversions/qubit_operator_transforms.py @@ -57,17 +57,17 @@ def project_onto_sector(operator, qubits, sectors): projected_operator = QubitOperator() for term, factor in operator.terms.items(): - # Any term containing X or Y on the removed # qubits has an expectation value of zero if [t for t in term if t[0] in qubits and t[1] in ['X', 'Y']]: continue - new_term = tuple((t[0] - len([q for q in qubits if q < t[0]]), - t[1]) for t in term if t[0] not in qubits) - new_factor =\ - factor * (-1)**(sum([sectors[qubits.index(t[0])] - for t in term if t[0] in qubits])) + new_term = tuple( + (t[0] - len([q for q in qubits if q < t[0]]), t[1]) for t in term if t[0] not in qubits + ) + new_factor = factor * (-1) ** ( + sum([sectors[qubits.index(t[0])] for t in term if t[0] in qubits]) + ) projected_operator += QubitOperator(new_term, new_factor) return projected_operator @@ -108,11 +108,10 @@ def projection_error(operator, qubits, sectors): error = 0 for term, factor in operator.terms.items(): - # Any term containing X or Y on the removed # qubits contributes to the error if [t for t in term if t[0] in qubits and t[1] in ['X', 'Y']]: - error += abs(factor)**2 + error += abs(factor) ** 2 return numpy.sqrt(error) @@ -143,15 +142,17 @@ def rotate_qubit_by_pauli(qop, pauli, angle): if type(qop) is not QubitOperator: raise TypeError('This can only rotate QubitOperators') - if (type(pauli) is not QubitOperator or len(pauli.terms) != 1 or - pvals[0] != 1): + if type(pauli) is not QubitOperator or len(pauli.terms) != 1 or pvals[0] != 1: raise TypeError('This can only rotate by Pauli operators') pqp = pauli * qop * pauli even_terms = 0.5 * (qop + pqp) odd_terms = 0.5 * (qop - pqp) - rotated_op = even_terms + numpy.cos(2 * angle) * odd_terms + \ - 1j * numpy.sin(2 * angle) * odd_terms * pauli + rotated_op = ( + even_terms + + numpy.cos(2 * angle) * odd_terms + + 1j * numpy.sin(2 * angle) * odd_terms * pauli + ) return rotated_op diff --git a/src/openfermion/transforms/repconversions/qubit_operator_transforms_test.py b/src/openfermion/transforms/repconversions/qubit_operator_transforms_test.py index 23a49f493..0f3c8931a 100644 --- a/src/openfermion/transforms/repconversions/qubit_operator_transforms_test.py +++ b/src/openfermion/transforms/repconversions/qubit_operator_transforms_test.py @@ -15,20 +15,21 @@ import numpy from openfermion.ops.operators import QubitOperator, FermionOperator -from openfermion.transforms.repconversions import (project_onto_sector, - projection_error, - rotate_qubit_by_pauli) +from openfermion.transforms.repconversions import ( + project_onto_sector, + projection_error, + rotate_qubit_by_pauli, +) from openfermion.utils import count_qubits class ProjectionTest(unittest.TestCase): - def setUp(self): pass def test_function_errors(self): """Test main function errors.""" - operator = (QubitOperator('Z0 X1', 1.0) + QubitOperator('X1', 2.0)) + operator = QubitOperator('Z0 X1', 1.0) + QubitOperator('X1', 2.0) sector1 = [0] sector2 = [1] qbt_list = [0] @@ -41,29 +42,19 @@ def test_function_errors(self): with self.assertRaises(TypeError): projection_error(operator=operator, qubits=0.0, sectors=sector2) with self.assertRaises(TypeError): - project_onto_sector(operator=operator, - qubits=qbt_list, - sectors=operator) + project_onto_sector(operator=operator, qubits=qbt_list, sectors=operator) with self.assertRaises(TypeError): - projection_error(operator=operator, - qubits=qbt_list, - sectors=operator) + projection_error(operator=operator, qubits=qbt_list, sectors=operator) with self.assertRaises(ValueError): - project_onto_sector(operator=operator, - qubits=[0, 1], - sectors=sector1) + project_onto_sector(operator=operator, qubits=[0, 1], sectors=sector1) with self.assertRaises(ValueError): projection_error(operator=operator, qubits=[0, 1], sectors=sector1) with self.assertRaises(ValueError): - project_onto_sector(operator=operator, - qubits=qbt_list, - sectors=[0, 0]) + project_onto_sector(operator=operator, qubits=qbt_list, sectors=[0, 0]) with self.assertRaises(ValueError): projection_error(operator=operator, qubits=qbt_list, sectors=[0, 0]) with self.assertRaises(ValueError): - project_onto_sector(operator=operator, - qubits=qbt_list, - sectors=[-1]) + project_onto_sector(operator=operator, qubits=qbt_list, sectors=[-1]) with self.assertRaises(ValueError): projection_error(operator=operator, qubits=qbt_list, sectors=[-1]) @@ -73,9 +64,7 @@ def test_projection(self): opstring2 = ((0, 'X'), (2, 'Z'), (3, 'Z')) operator = QubitOperator(opstring, coefficient) operator += QubitOperator(opstring2, coefficient) - new_operator = project_onto_sector(operator, - qubits=[2, 3], - sectors=[0, 1]) + new_operator = project_onto_sector(operator, qubits=[2, 3], sectors=[0, 1]) error = projection_error(operator, qubits=[2, 3], sectors=[0, 1]) self.assertEqual(count_qubits(new_operator), 2) self.assertEqual(error, 0) @@ -94,13 +83,11 @@ def test_projection_error(self): error = projection_error(operator, qubits=[1], sectors=[0]) self.assertEqual(count_qubits(new_operator), 3) self.assertTrue(((0, 'X'), (1, 'Z'), (2, 'Z')) in new_operator.terms) - self.assertEqual(new_operator.terms[((0, 'X'), (1, 'Z'), (2, 'Z'))], - 0.5) + self.assertEqual(new_operator.terms[((0, 'X'), (1, 'Z'), (2, 'Z'))], 0.5) self.assertEqual(error, 0.5) class UnitaryRotationsTest(unittest.TestCase): - def setup(self): pass diff --git a/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer.py b/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer.py index 73c9a26f3..db5cfa9af 100644 --- a/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer.py +++ b/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer.py @@ -93,8 +93,7 @@ def fix_single_term(term, position, fixed_op, other_op, stabilizer): term (QubitOperator): Updated term in a fiixed representation. """ pauli_tuple = list(term.terms)[0] - if (position, fixed_op) in pauli_tuple or (position, - other_op) in pauli_tuple: + if (position, fixed_op) in pauli_tuple or (position, other_op) in pauli_tuple: return term * stabilizer else: return term @@ -128,8 +127,7 @@ def _lookup_term(pauli_string, updated_terms_1, updated_terms_2): length = len(pauli_string) for x in numpy.arange(len(updated_terms_1)): - if (pauli_string == updated_terms_2[x] and - (length > len(list(updated_terms_1[x].terms)[0]))): + if pauli_string == updated_terms_2[x] and (length > len(list(updated_terms_1[x].terms)[0])): pauli_op = updated_terms_1[x] length = len(list(updated_terms_1[x].terms)[0]) return pauli_op @@ -188,29 +186,28 @@ def _reduce_terms(terms, stabilizer_list, manual_input, fixed_positions): new_terms = QubitOperator() for qubit_pauli in terms: - new_terms += fix_single_term(qubit_pauli, fixed_positions[i], - fixed_op, other_op, stabilizer_list[0]) + new_terms += fix_single_term( + qubit_pauli, fixed_positions[i], fixed_op, other_op, stabilizer_list[0] + ) updated_stabilizers = [] for update_stab in stabilizer_list[1:]: updated_stabilizers += [ - fix_single_term(update_stab, fixed_positions[i], fixed_op, - other_op, stabilizer_list[0]) + fix_single_term( + update_stab, fixed_positions[i], fixed_op, other_op, stabilizer_list[0] + ) ] # Update terms and stabilizer list. terms = new_terms stabilizer_list = updated_stabilizers - check_stabilizer_linearity(stabilizer_list, - msg='Linearly dependent stabilizers.') - check_commuting_stabilizers(stabilizer_list, - msg='Stabilizers anti-commute.') + check_stabilizer_linearity(stabilizer_list, msg='Linearly dependent stabilizers.') + check_commuting_stabilizers(stabilizer_list, msg='Stabilizers anti-commute.') return terms, fixed_positions -def _reduce_terms_keep_length(terms, stabilizer_list, manual_input, - fixed_positions): +def _reduce_terms_keep_length(terms, stabilizer_list, manual_input, fixed_positions): """ Perform the term reduction using stabilizer conditions. @@ -265,44 +262,44 @@ def _reduce_terms_keep_length(terms, stabilizer_list, manual_input, updated_stabilizers = [] for y in term_list: new_list += [ - fix_single_term(y, fixed_positions[i], fixed_op, other_op, - stabilizer_list[0]) + fix_single_term(y, fixed_positions[i], fixed_op, other_op, stabilizer_list[0]) ] for update_stab in stabilizer_list[1:]: updated_stabilizers += [ - fix_single_term(update_stab, fixed_positions[i], fixed_op, - other_op, stabilizer_list[0]) + fix_single_term( + update_stab, fixed_positions[i], fixed_op, other_op, stabilizer_list[0] + ) ] term_list = new_list stabilizer_list = updated_stabilizers - check_stabilizer_linearity(stabilizer_list, - msg='Linearly dependent stabilizers.') - check_commuting_stabilizers(stabilizer_list, - msg='Stabilizers anti-commute.') + check_stabilizer_linearity(stabilizer_list, msg='Linearly dependent stabilizers.') + check_commuting_stabilizers(stabilizer_list, msg='Stabilizers anti-commute.') new_terms = QubitOperator() for x, ent in enumerate(term_list): new_terms += ent * terms.terms[term_list_duplicate[x]] for x, ent in enumerate(term_list): - term_list_duplicate[x] = (QubitOperator(term_list_duplicate[x]) / - list(ent.terms.items())[0][1]) + term_list_duplicate[x] = ( + QubitOperator(term_list_duplicate[x]) / list(ent.terms.items())[0][1] + ) term_list[x] = list(ent.terms)[0] even_newer_terms = QubitOperator() for pauli_string, coefficient in new_terms.terms.items(): - even_newer_terms += coefficient * _lookup_term( - pauli_string, term_list_duplicate, term_list) + even_newer_terms += coefficient * _lookup_term(pauli_string, term_list_duplicate, term_list) return even_newer_terms, fixed_positions -def reduce_number_of_terms(operator, - stabilizers, - maintain_length=False, - output_fixed_positions=False, - manual_input=False, - fixed_positions=None): +def reduce_number_of_terms( + operator, + stabilizers, + maintain_length=False, + output_fixed_positions=False, + manual_input=False, + fixed_positions=None, +): r""" Reduce the number of Pauli strings of operator using stabilizers. @@ -368,10 +365,8 @@ def reduce_number_of_terms(operator, stabilizer_list = list(stabilizers) - check_stabilizer_linearity(stabilizer_list, - msg='Trivial stabilizer (identity).') - check_commuting_stabilizers(stabilizer_list, - msg='Stabilizer with complex coefficient.') + check_stabilizer_linearity(stabilizer_list, msg='Trivial stabilizer (identity).') + check_commuting_stabilizers(stabilizer_list, msg='Stabilizer with complex coefficient.') if manual_input: # Convert fixed_position into a list to allow any type of @@ -380,20 +375,21 @@ def reduce_number_of_terms(operator, raise TypeError('List of qubit positions required.') fixed_positions = list(fixed_positions) if len(fixed_positions) != len(stabilizer_list): - raise StabilizerError('The number of stabilizers must be equal ' + - 'to the number of qubits manually fixed.') + raise StabilizerError( + 'The number of stabilizers must be equal ' + + 'to the number of qubits manually fixed.' + ) if len(set(fixed_positions)) != len(stabilizer_list): raise StabilizerError('All qubit positions must be different.') if maintain_length: - (reduced_operator, - fixed_positions) = _reduce_terms_keep_length(operator, stabilizer_list, - manual_input, - fixed_positions) + (reduced_operator, fixed_positions) = _reduce_terms_keep_length( + operator, stabilizer_list, manual_input, fixed_positions + ) else: - (reduced_operator, - fixed_positions) = _reduce_terms(operator, stabilizer_list, - manual_input, fixed_positions) + (reduced_operator, fixed_positions) = _reduce_terms( + operator, stabilizer_list, manual_input, fixed_positions + ) if output_fixed_positions: return reduced_operator, fixed_positions @@ -401,11 +397,9 @@ def reduce_number_of_terms(operator, return reduced_operator -def taper_off_qubits(operator, - stabilizers, - manual_input=False, - fixed_positions=None, - output_tapered_positions=False): +def taper_off_qubits( + operator, stabilizers, manual_input=False, fixed_positions=None, output_tapered_positions=False +): r""" Remove qubits from given operator. @@ -456,13 +450,14 @@ def taper_off_qubits(operator, n_qbits = max(op_utils.count_qubits(operator), n_qbits_stabs) - (ham_to_update, - qbts_to_rm) = reduce_number_of_terms(operator, - stabilizers, - maintain_length=False, - manual_input=manual_input, - fixed_positions=fixed_positions, - output_fixed_positions=True) + (ham_to_update, qbts_to_rm) = reduce_number_of_terms( + operator, + stabilizers, + maintain_length=False, + manual_input=manual_input, + fixed_positions=fixed_positions, + output_fixed_positions=True, + ) # Gets a list of the order of the qubits after tapering qbit_order = list(numpy.arange(n_qbits - len(qbts_to_rm), dtype=int)) diff --git a/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer_test.py b/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer_test.py index c6d854dd9..d15a38c1a 100644 --- a/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer_test.py +++ b/src/openfermion/transforms/repconversions/qubit_tapering_from_stabilizer_test.py @@ -17,16 +17,21 @@ from openfermion.chem import MolecularData from openfermion.ops.operators import QubitOperator -from openfermion.transforms.opconversions import (jordan_wigner, - get_fermion_operator) +from openfermion.transforms.opconversions import jordan_wigner, get_fermion_operator from openfermion.linalg import eigenspectrum from openfermion.utils.operator_utils import count_qubits -from openfermion.transforms.repconversions.qubit_tapering_from_stabilizer \ - import (StabilizerError, check_commuting_stabilizers, - check_stabilizer_linearity, fix_single_term, _reduce_terms, - _reduce_terms_keep_length, _lookup_term, reduce_number_of_terms, - taper_off_qubits) +from openfermion.transforms.repconversions.qubit_tapering_from_stabilizer import ( + StabilizerError, + check_commuting_stabilizers, + check_stabilizer_linearity, + fix_single_term, + _reduce_terms, + _reduce_terms_keep_length, + _lookup_term, + reduce_number_of_terms, + taper_off_qubits, +) def lih_hamiltonian(): @@ -42,7 +47,7 @@ def lih_hamiltonian(): spectrum: List of energies. """ - geometry = [('Li', (0., 0., 0.)), ('H', (0., 0., 1.45))] + geometry = [('Li', (0.0, 0.0, 0.0)), ('H', (0.0, 0.0, 1.45))] active_space_start = 1 active_space_stop = 3 molecule = MolecularData(geometry, 'sto-3g', 1, description="1.45") @@ -50,7 +55,8 @@ def lih_hamiltonian(): molecular_hamiltonian = molecule.get_molecular_hamiltonian( occupied_indices=range(active_space_start), - active_indices=range(active_space_start, active_space_stop)) + active_indices=range(active_space_start, active_space_stop), + ) hamiltonian = get_fermion_operator(molecular_hamiltonian) spectrum = eigenspectrum(hamiltonian) @@ -72,86 +78,97 @@ def test_function_errors(self): with self.assertRaises(TypeError): reduce_number_of_terms(operator=qubit_hamiltonian, stabilizers=1) with self.assertRaises(TypeError): - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=stab1 + stab2, - manual_input=True, - fixed_positions=None) + reduce_number_of_terms( + operator=qubit_hamiltonian, + stabilizers=stab1 + stab2, + manual_input=True, + fixed_positions=None, + ) with self.assertRaises(StabilizerError): - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=stab1 + stab2, - manual_input=True, - fixed_positions=[1]) + reduce_number_of_terms( + operator=qubit_hamiltonian, + stabilizers=stab1 + stab2, + manual_input=True, + fixed_positions=[1], + ) with self.assertRaises(StabilizerError): - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=stab1 + stab2, - manual_input=True, - fixed_positions=[1, 1]) + reduce_number_of_terms( + operator=qubit_hamiltonian, + stabilizers=stab1 + stab2, + manual_input=True, + fixed_positions=[1, 1], + ) with self.assertRaises(StabilizerError): # Check Identity as stabilizer error. - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=(stab1 + - QubitOperator(' ', 1.0))) + reduce_number_of_terms( + operator=qubit_hamiltonian, stabilizers=(stab1 + QubitOperator(' ', 1.0)) + ) with self.assertRaises(StabilizerError): # Check complex coefficient stabilizer error. - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=(stab1 + - QubitOperator('Z0', 1.0j))) + reduce_number_of_terms( + operator=qubit_hamiltonian, stabilizers=(stab1 + QubitOperator('Z0', 1.0j)) + ) with self.assertRaises(StabilizerError): # Check linearly-dependent stabilizer error. reduce_number_of_terms( operator=qubit_hamiltonian, - stabilizers=(stab1 + QubitOperator('Z0 Z1 Z2 Z3', 1.0) + stab2)) + stabilizers=(stab1 + QubitOperator('Z0 Z1 Z2 Z3', 1.0) + stab2), + ) with self.assertRaises(StabilizerError): # Check anti-commuting stabilizer error. - reduce_number_of_terms(operator=qubit_hamiltonian, - stabilizers=(QubitOperator('X0', 1.0) + - QubitOperator('Y0', 1.0))) + reduce_number_of_terms( + operator=qubit_hamiltonian, + stabilizers=(QubitOperator('X0', 1.0) + QubitOperator('Y0', 1.0)), + ) with self.assertRaises(StabilizerError): # Check linearly-dependent stabilizer error. _reduce_terms( terms=qubit_hamiltonian, - stabilizer_list=list(stab1 + QubitOperator('Z0 Z1 Z2 Z3', 1.0) + - stab2), + stabilizer_list=list(stab1 + QubitOperator('Z0 Z1 Z2 Z3', 1.0) + stab2), manual_input=False, - fixed_positions=[]) + fixed_positions=[], + ) with self.assertRaises(StabilizerError): # Check complex coefficient stabilizer error. - _reduce_terms(terms=qubit_hamiltonian, - stabilizer_list=list(stab1 + - QubitOperator('Z0', 1.0j)), - manual_input=False, - fixed_positions=[]) + _reduce_terms( + terms=qubit_hamiltonian, + stabilizer_list=list(stab1 + QubitOperator('Z0', 1.0j)), + manual_input=False, + fixed_positions=[], + ) with self.assertRaises(StabilizerError): # Check linearly-dependent stabilizer error. par_qop = QubitOperator('Z0 Z1 Z2 Z3', 1.0) - _reduce_terms_keep_length(terms=qubit_hamiltonian, - stabilizer_list=[stab1, par_qop, stab2], - manual_input=False, - fixed_positions=[]) + _reduce_terms_keep_length( + terms=qubit_hamiltonian, + stabilizer_list=[stab1, par_qop, stab2], + manual_input=False, + fixed_positions=[], + ) with self.assertRaises(StabilizerError): # Check complex coefficient stabilizer error. aux_qop = QubitOperator('Z0', 1.0j) - _reduce_terms_keep_length(terms=qubit_hamiltonian, - stabilizer_list=[stab1, aux_qop], - manual_input=False, - fixed_positions=[]) + _reduce_terms_keep_length( + terms=qubit_hamiltonian, + stabilizer_list=[stab1, aux_qop], + manual_input=False, + fixed_positions=[], + ) with self.assertRaises(StabilizerError): # Test check_commuting_stabilizer function # Requires a list of QubitOperators one of which # has an imaginary term. - check_commuting_stabilizers(stabilizer_list=[ - QubitOperator('Z0 Z1', 1.0), - QubitOperator('X0', 1j) - ], - msg='This test fails.') + check_commuting_stabilizers( + stabilizer_list=[QubitOperator('Z0 Z1', 1.0), QubitOperator('X0', 1j)], + msg='This test fails.', + ) with self.assertRaises(StabilizerError): # Test check_stabilizer_linearity function. # Requires a list of QUbitOperators one of which is # the identity. check_stabilizer_linearity( - [QubitOperator('Z0 Z1', 1.0), - QubitOperator(' ', 1.0)], - msg='This test fails.') + [QubitOperator('Z0 Z1', 1.0), QubitOperator(' ', 1.0)], msg='This test fails.' + ) def test_fix_single_term(self): """Test fix_single_term function.""" @@ -184,8 +201,7 @@ def test_reduce_terms(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - red_eigenspectrum = eigenspectrum( - reduce_number_of_terms(qubit_hamiltonian, stab1 + stab2)) + red_eigenspectrum = eigenspectrum(reduce_number_of_terms(qubit_hamiltonian, stab1 + stab2)) self.assertAlmostEqual(spectrum[0], red_eigenspectrum[0]) @@ -197,9 +213,10 @@ def test_reduce_terms_manual_input(self): stab2 = QubitOperator('Z1 Z3', -1.0) red_eigenspectrum = eigenspectrum( - reduce_number_of_terms(qubit_hamiltonian, [stab1, stab2], - manual_input=True, - fixed_positions=[0, 1])) + reduce_number_of_terms( + qubit_hamiltonian, [stab1, stab2], manual_input=True, fixed_positions=[0, 1] + ) + ) self.assertAlmostEqual(spectrum[0], red_eigenspectrum[0]) @@ -211,9 +228,8 @@ def test_reduce_terms_maintain_length(self): stab2 = QubitOperator('Z1 Z3', -1.0) red_eigenspectrum = eigenspectrum( - reduce_number_of_terms(qubit_hamiltonian, - stab1 + stab2, - maintain_length=True)) + reduce_number_of_terms(qubit_hamiltonian, stab1 + stab2, maintain_length=True) + ) self.assertAlmostEqual(spectrum[0], red_eigenspectrum[0]) @@ -224,14 +240,12 @@ def test_reduce_terms_auxiliar_functions(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - red_ham1, _ = _reduce_terms(terms=qubit_ham, - stabilizer_list=[stab1, stab2], - manual_input=False, - fixed_positions=[]) - red_ham2, _ = _reduce_terms_keep_length(terms=qubit_ham, - stabilizer_list=[stab1, stab2], - manual_input=False, - fixed_positions=[]) + red_ham1, _ = _reduce_terms( + terms=qubit_ham, stabilizer_list=[stab1, stab2], manual_input=False, fixed_positions=[] + ) + red_ham2, _ = _reduce_terms_keep_length( + terms=qubit_ham, stabilizer_list=[stab1, stab2], manual_input=False, fixed_positions=[] + ) red_eigspct1 = eigenspectrum(red_ham1) red_eigspct2 = eigenspectrum(red_ham2) @@ -245,14 +259,18 @@ def test_reduce_terms_auxiliar_functions_manual_input(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - red_ham1, _ = _reduce_terms(terms=qubit_ham, - stabilizer_list=[stab1, stab2], - manual_input=True, - fixed_positions=[0, 1]) - red_ham2, _ = _reduce_terms_keep_length(terms=qubit_ham, - stabilizer_list=[stab1, stab2], - manual_input=True, - fixed_positions=[0, 1]) + red_ham1, _ = _reduce_terms( + terms=qubit_ham, + stabilizer_list=[stab1, stab2], + manual_input=True, + fixed_positions=[0, 1], + ) + red_ham2, _ = _reduce_terms_keep_length( + terms=qubit_ham, + stabilizer_list=[stab1, stab2], + manual_input=True, + fixed_positions=[0, 1], + ) red_eigspct1 = eigenspectrum(red_ham1) red_eigspct2 = eigenspectrum(red_ham2) @@ -266,10 +284,12 @@ def test_tapering_qubits_manual_input_false(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - tapered_hamiltonian = taper_off_qubits(operator=qubit_hamiltonian, - stabilizers=[stab1, stab2], - manual_input=False, - fixed_positions=[0, 3]) + tapered_hamiltonian = taper_off_qubits( + operator=qubit_hamiltonian, + stabilizers=[stab1, stab2], + manual_input=False, + fixed_positions=[0, 3], + ) tapered_spectrum = eigenspectrum(tapered_hamiltonian) self.assertAlmostEqual(spectrum[0], tapered_spectrum[0]) @@ -288,20 +308,19 @@ def test_tapering_qubits_manual_input(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - tapered_ham_0_3 = taper_off_qubits(qubit_hamiltonian, [stab1, stab2], - manual_input=True, - fixed_positions=[0, 3]) - tapered_ham_2_1 = taper_off_qubits(qubit_hamiltonian, [stab1, stab2], - manual_input=True, - fixed_positions=[2, 1]) + tapered_ham_0_3 = taper_off_qubits( + qubit_hamiltonian, [stab1, stab2], manual_input=True, fixed_positions=[0, 3] + ) + tapered_ham_2_1 = taper_off_qubits( + qubit_hamiltonian, [stab1, stab2], manual_input=True, fixed_positions=[2, 1] + ) tapered_spectrum_0_3 = eigenspectrum(tapered_ham_0_3) tapered_spectrum_2_1 = eigenspectrum(tapered_ham_2_1) self.assertAlmostEqual(spectrum[0], tapered_spectrum_0_3[0]) self.assertAlmostEqual(spectrum[0], tapered_spectrum_2_1[0]) - self.assertTrue( - numpy.allclose(tapered_spectrum_0_3, tapered_spectrum_2_1)) + self.assertTrue(numpy.allclose(tapered_spectrum_0_3, tapered_spectrum_2_1)) def test_tapering_qubits_remove_positions(self): """Test taper_off_qubits function using LiH Hamiltonian.""" @@ -310,12 +329,13 @@ def test_tapering_qubits_remove_positions(self): stab1 = QubitOperator('Z0 Z2', -1.0) stab2 = QubitOperator('Z1 Z3', -1.0) - (tapered_hamiltonian, - positions) = taper_off_qubits(operator=qubit_hamiltonian, - stabilizers=[stab1, stab2], - manual_input=True, - fixed_positions=[0, 3], - output_tapered_positions=True) + (tapered_hamiltonian, positions) = taper_off_qubits( + operator=qubit_hamiltonian, + stabilizers=[stab1, stab2], + manual_input=True, + fixed_positions=[0, 3], + output_tapered_positions=True, + ) tapered_spectrum = eigenspectrum(tapered_hamiltonian) @@ -346,4 +366,4 @@ def test_taper_x_stab(self): stab1 = QubitOperator('Y0 Y2', -1.0) tham = reduce_number_of_terms(hamiltonian, stab1, maintain_length=True) - self.assertTrue(hamiltonian == tham) \ No newline at end of file + self.assertTrue(hamiltonian == tham) diff --git a/src/openfermion/transforms/repconversions/weyl_ordering.py b/src/openfermion/transforms/repconversions/weyl_ordering.py index 3cca4d823..1df907d3c 100644 --- a/src/openfermion/transforms/repconversions/weyl_ordering.py +++ b/src/openfermion/transforms/repconversions/weyl_ordering.py @@ -17,7 +17,7 @@ def mccoy(mode, op_a, op_b, m, n): - """ Implement the McCoy formula on two operators of the + """Implement the McCoy formula on two operators of the form op_a^m op_b^n. Args: @@ -30,8 +30,7 @@ def mccoy(mode, op_a, op_b, m, n): new_op = dict() for r in range(0, n + 1): coeff = binom(n, r) / (2**n) - new_term = tuple([(mode, op_b)] * r + [(mode, op_a)] * m + - [(mode, op_b)] * (n - r)) + new_term = tuple([(mode, op_b)] * r + [(mode, op_a)] * m + [(mode, op_b)] * (n - r)) if new_term not in new_op: new_op[tuple(new_term)] = coeff else: @@ -40,7 +39,7 @@ def mccoy(mode, op_a, op_b, m, n): def weyl_polynomial_quantization(polynomial): - r""" Apply the Weyl quantization to a phase space polynomial. + r"""Apply the Weyl quantization to a phase space polynomial. The Weyl quantization is performed by applying McCoy's formula directly to a polynomial term of the form q^m p^n: @@ -107,7 +106,7 @@ def weyl_polynomial_quantization(polynomial): def symmetric_ordering(operator, ignore_coeff=True, ignore_identity=True): - """ Apply the symmetric ordering to a BosonOperator or QuadOperator. + """Apply the symmetric ordering to a BosonOperator or QuadOperator. The symmetric ordering is performed by applying McCoy's formula directly to polynomial terms of quadrature operators: diff --git a/src/openfermion/transforms/repconversions/weyl_ordering_test.py b/src/openfermion/transforms/repconversions/weyl_ordering_test.py index ad7cd1b45..5dac9b2f8 100644 --- a/src/openfermion/transforms/repconversions/weyl_ordering_test.py +++ b/src/openfermion/transforms/repconversions/weyl_ordering_test.py @@ -12,16 +12,17 @@ """Tests jordan_wigner.py.""" import unittest -from openfermion.ops.operators import (BosonOperator, FermionOperator, - QuadOperator) +from openfermion.ops.operators import BosonOperator, FermionOperator, QuadOperator from openfermion.utils import is_hermitian from openfermion.transforms.repconversions.weyl_ordering import ( - mccoy, symmetric_ordering, weyl_polynomial_quantization) + mccoy, + symmetric_ordering, + weyl_polynomial_quantization, +) class McCoyTest(unittest.TestCase): - def setUp(self): self.mode = 0 self.op_a = 'q' @@ -64,15 +65,11 @@ def test_mixed(self): m = 1 n = 1 res = mccoy(self.mode, self.op_a, self.op_b, m, n) - expected = { - ((0, self.op_a), (0, self.op_b)): 0.5, - ((0, self.op_b), (0, self.op_a)): 0.5 - } + expected = {((0, self.op_a), (0, self.op_b)): 0.5, ((0, self.op_b), (0, self.op_a)): 0.5} self.assertEqual(res, expected) class WeylQuantizationTest(unittest.TestCase): - def test_weyl_empty(self): res = weyl_polynomial_quantization('') self.assertTrue(res == QuadOperator.zero()) @@ -94,20 +91,17 @@ def test_weyl_two_term_same(self): def test_weyl_non_hermitian(self): res = weyl_polynomial_quantization('q0 p0') - expected = QuadOperator('q0 p0', 0.5) \ - + QuadOperator('p0 q0', 0.5) + expected = QuadOperator('q0 p0', 0.5) + QuadOperator('p0 q0', 0.5) self.assertTrue(res == expected) self.assertTrue(is_hermitian(res)) res = weyl_polynomial_quantization('q0^2 p0') - expected = QuadOperator('q0 q0 p0', 0.5) \ - + QuadOperator('p0 q0 q0', 0.5) + expected = QuadOperator('q0 q0 p0', 0.5) + QuadOperator('p0 q0 q0', 0.5) self.assertTrue(res == expected) self.assertTrue(is_hermitian(res)) class SymmetricOrderingTest(unittest.TestCase): - def test_invalid_op(self): op = FermionOperator() with self.assertRaises(TypeError): @@ -157,22 +151,19 @@ def test_symmetric_two_term_same(self): def test_symmetric_non_hermitian(self): op = BosonOperator('0^ 0') res = symmetric_ordering(op) - expected = BosonOperator('0^ 0', 0.5) \ - + BosonOperator('0 0^', 0.5) + expected = BosonOperator('0^ 0', 0.5) + BosonOperator('0 0^', 0.5) self.assertTrue(res == expected) self.assertTrue(is_hermitian(res)) op = BosonOperator('0^ 0', 0.5) res = symmetric_ordering(op, ignore_coeff=False) - expected = BosonOperator('0^ 0', 0.25) \ - + BosonOperator('0 0^', 0.25) + expected = BosonOperator('0^ 0', 0.25) + BosonOperator('0 0^', 0.25) self.assertTrue(res == expected) self.assertTrue(is_hermitian(res)) op = QuadOperator('q0 p0') res = symmetric_ordering(op) - expected = QuadOperator('q0 p0', 0.5) \ - + QuadOperator('p0 q0', 0.5) + expected = QuadOperator('q0 p0', 0.5) + QuadOperator('p0 q0', 0.5) self.assertTrue(res == expected) self.assertTrue(is_hermitian(res)) @@ -189,8 +180,7 @@ def test_symmetric_non_hermitian_order(self): self.assertTrue(is_hermitian(w2)) self.assertTrue(is_hermitian(w3)) - expected = QuadOperator('q0 q0 p0', 0.5) \ - + QuadOperator('p0 q0 q0', 0.5) + expected = QuadOperator('q0 q0 p0', 0.5) + QuadOperator('p0 q0 q0', 0.5) self.assertTrue(w1 == expected) self.assertTrue(w2 == expected) self.assertTrue(w3 == expected) @@ -199,7 +189,6 @@ def test_symmetric_coefficient(self): coeff = 0.5 + 0.6j op = QuadOperator('q0 p0', coeff) res = symmetric_ordering(op, ignore_coeff=False) - expected = QuadOperator('q0 p0', 0.5) \ - + QuadOperator('p0 q0', 0.5) + expected = QuadOperator('q0 p0', 0.5) + QuadOperator('p0 q0', 0.5) self.assertTrue(res == coeff * expected) self.assertFalse(is_hermitian(res)) diff --git a/src/openfermion/utils/__init__.py b/src/openfermion/utils/__init__.py index 34a6a27a9..5bec19680 100644 --- a/src/openfermion/utils/__init__.py +++ b/src/openfermion/utils/__init__.py @@ -12,19 +12,10 @@ from .bch_expansion import bch_expand -from .channel_state import ( - amplitude_damping_channel, - dephasing_channel, - depolarizing_channel, -) +from .channel_state import amplitude_damping_channel, dephasing_channel, depolarizing_channel # Imports out of alphabetical order to avoid circular dependency. -from .lattice import ( - HubbardSquareLattice, - HubbardLattice, - SpinPairs, - Spin, -) +from .lattice import HubbardSquareLattice, HubbardLattice, SpinPairs, Spin from .commutators import ( anticommutator, @@ -37,11 +28,7 @@ from .grid import Grid -from .indexing import ( - up_index, - down_index, - up_then_down, -) +from .indexing import up_index, down_index, up_then_down from .operator_utils import ( count_qubits, diff --git a/src/openfermion/utils/bch_expansion.py b/src/openfermion/utils/bch_expansion.py index b6dfdcfb2..8e75748ea 100644 --- a/src/openfermion/utils/bch_expansion.py +++ b/src/openfermion/utils/bch_expansion.py @@ -55,13 +55,13 @@ def _bch_expand_multiple_terms(*ops, **kwargs): if n_ops == 2: return _bch_expand_two_terms(ops[0], ops[1], order=order) else: - left_ops = ops[:n_ops // 2] - right_ops = ops[n_ops // 2:] - return _bch_expand_two_terms(_bch_expand_multiple_terms(*left_ops, - order=order), - _bch_expand_multiple_terms(*right_ops, - order=order), - order=order) + left_ops = ops[: n_ops // 2] + right_ops = ops[n_ops // 2 :] + return _bch_expand_two_terms( + _bch_expand_multiple_terms(*left_ops, order=order), + _bch_expand_multiple_terms(*right_ops, order=order), + order=order, + ) def _bch_expand_two_terms(x, y, order=6): @@ -121,9 +121,7 @@ def _generate_nested_commutator(order): coeff_list = [] for i in range(1, order + 1): - term_of_order_i = [ - list(x) for x in itertools.product(['0', '1'], repeat=i) - ] + term_of_order_i = [list(x) for x in itertools.product(['0', '1'], repeat=i)] # filter out trivially zero terms by checking if last two terms are # the same @@ -170,8 +168,7 @@ def _compute_coeff(split_bin_str): def cn(n): return _coeff_monomial(split_bin_str, n, len(split_bin_str)) - c = sum([(-1)**(n + 1) / float(n) * cn(n) - for n in range(num_block + 1, order + 1)]) + c = sum([(-1) ** (n + 1) / float(n) * cn(n) for n in range(num_block + 1, order + 1)]) return c / order @@ -190,24 +187,18 @@ class context: coeff = 0 def depth_first_search(split_bin_str, n, l, sol=None, cur_sum=0): - ''' Partition an integer value of n into l bins each with min 1 - ''' + '''Partition an integer value of n into l bins each with min 1''' sol = sol or [] cur_idx = len(sol) if cur_idx < l: m = len(split_bin_str[cur_idx]) n_avail = n - cur_sum for j in range(1, min(m, n_avail - (l - 1 - cur_idx)) + 1): - depth_first_search(split_bin_str, - n, - l, - sol=sol + [j], - cur_sum=cur_sum + j) + depth_first_search(split_bin_str, n, l, sol=sol + [j], cur_sum=cur_sum + j) elif cur_idx == l: if cur_sum == n: partition_list = sol - context.coeff += _coeff_monomial_with_partition( - split_bin_str, partition_list) + context.coeff += _coeff_monomial_with_partition(split_bin_str, partition_list) # start from the root depth_first_search(split_bin_str, n, l) @@ -234,11 +225,13 @@ def _coeff_for_non_descending_block(cnt_x, cnt_y, eta): ret = 0 for eta_x in range(1, eta): - ret += (_coeff_for_consectutive_op(cnt_x, eta_x) * - _coeff_for_consectutive_op(cnt_y, eta - eta_x)) + ret += _coeff_for_consectutive_op(cnt_x, eta_x) * _coeff_for_consectutive_op( + cnt_y, eta - eta_x + ) for eta_x in range(1, eta + 1): - ret += (_coeff_for_consectutive_op(cnt_x, eta_x) * - _coeff_for_consectutive_op(cnt_y, eta + 1 - eta_x)) + ret += _coeff_for_consectutive_op(cnt_x, eta_x) * _coeff_for_consectutive_op( + cnt_y, eta + 1 - eta_x + ) return ret @@ -249,6 +242,7 @@ def _coeff_for_consectutive_op(cnt_x, num_partition): """ ret = 0 for num_zero in range(num_partition): - ret += ((-1)**num_zero * (num_partition - num_zero)**cnt_x * - comb(num_partition, num_zero)) + ret += ( + (-1) ** num_zero * (num_partition - num_zero) ** cnt_x * comb(num_partition, num_zero) + ) return ret / float(factorial(cnt_x)) diff --git a/src/openfermion/utils/bch_expansion_test.py b/src/openfermion/utils/bch_expansion_test.py index e075fddd4..1b99a6e26 100644 --- a/src/openfermion/utils/bch_expansion_test.py +++ b/src/openfermion/utils/bch_expansion_test.py @@ -43,37 +43,30 @@ def bch_expand_baseline(x, y, order): # Second order. if order > 1: - z += commutator(x, y) / 2. + z += commutator(x, y) / 2.0 # Third order. if order > 2: - z += commutator(x, commutator(x, y)) / 12. - z += commutator(y, commutator(y, x)) / 12. + z += commutator(x, commutator(x, y)) / 12.0 + z += commutator(y, commutator(y, x)) / 12.0 # Fourth order. if order > 3: - z -= commutator(y, commutator(x, commutator(x, y))) / 24. + z -= commutator(y, commutator(x, commutator(x, y))) / 24.0 # Fifth order. if order > 4: - z -= commutator(y, commutator(y, commutator(y, commutator(y, - x)))) / 720. - z -= commutator(x, commutator(x, commutator(x, commutator(x, - y)))) / 720. - z += commutator(x, commutator(y, commutator(y, commutator(y, - x)))) / 360. - z += commutator(y, commutator(x, commutator(x, commutator(x, - y)))) / 360. - z += commutator(y, commutator(x, commutator(y, commutator(x, - y)))) / 120. - z += commutator(x, commutator(y, commutator(x, commutator(y, - x)))) / 120. + z -= commutator(y, commutator(y, commutator(y, commutator(y, x)))) / 720.0 + z -= commutator(x, commutator(x, commutator(x, commutator(x, y)))) / 720.0 + z += commutator(x, commutator(y, commutator(y, commutator(y, x)))) / 360.0 + z += commutator(y, commutator(x, commutator(x, commutator(x, y)))) / 360.0 + z += commutator(y, commutator(x, commutator(y, commutator(x, y)))) / 120.0 + z += commutator(x, commutator(y, commutator(x, commutator(y, x)))) / 120.0 return z class BCHTest(unittest.TestCase): - def setUp(self): """Initialize a few density matrices""" self.seed = [13579, 34628, 2888, 11111, 67917] @@ -94,10 +87,9 @@ def test_bch(self): self.assertAlmostEqual(norm(test - baseline), 0.0) test = bch_expand(x, y, z, order=self.test_order) - baseline = bch_expand_baseline(x, - bch_expand_baseline( - y, z, order=self.test_order), - order=self.test_order) + baseline = bch_expand_baseline( + x, bch_expand_baseline(y, z, order=self.test_order), order=self.test_order + ) self.assertAlmostEqual(norm(test - baseline), 0.0) def test_verification(self): diff --git a/src/openfermion/utils/channel_state.py b/src/openfermion/utils/channel_state.py index 9dd46c9d6..3b7869d1a 100644 --- a/src/openfermion/utils/channel_state.py +++ b/src/openfermion/utils/channel_state.py @@ -30,8 +30,7 @@ def _verify_channel_inputs(density_matrix, probability, target_qubit): """ n_qubits = int(log2(density_matrix.shape[0])) - if (len(density_matrix.shape) != 2 or - density_matrix.shape[0] != density_matrix.shape[1]): + if len(density_matrix.shape) != 2 or density_matrix.shape[0] != density_matrix.shape[1]: raise ValueError("Error in input of density matrix to channel.") if (probability < 0) or (probability > 1): raise ValueError("Channel probability must be between 0 and 1.") @@ -51,17 +50,18 @@ def _lift_operator(operator, n_qubits, target_qubit): new_operator(Sparse Operator): Operator representing the embedding in the full space. """ - new_operator = (reduce( + new_operator = reduce( kron, - chain((eye(2) for i in range(0, target_qubit)), [operator], - (eye(2) for i in range(target_qubit + 1, n_qubits))))) + chain( + (eye(2) for i in range(0, target_qubit)), + [operator], + (eye(2) for i in range(target_qubit + 1, n_qubits)), + ), + ) return new_operator -def amplitude_damping_channel(density_matrix, - probability, - target_qubit, - transpose=False): +def amplitude_damping_channel(density_matrix, probability, target_qubit, transpose=False): r"""Apply an amplitude damping channel Applies an amplitude damping channel with a given probability to the target @@ -82,27 +82,23 @@ def amplitude_damping_channel(density_matrix, n_qubits = int(log2(density_matrix.shape[0])) E0 = _lift_operator( - array([[1.0, 0.0], [0.0, sqrt(1.0 - probability)]], dtype=complex), - n_qubits, target_qubit) + array([[1.0, 0.0], [0.0, sqrt(1.0 - probability)]], dtype=complex), n_qubits, target_qubit + ) E1 = _lift_operator( - array([[0.0, sqrt(probability)], [0.0, 0.0]], dtype=complex), n_qubits, - target_qubit) + array([[0.0, sqrt(probability)], [0.0, 0.0]], dtype=complex), n_qubits, target_qubit + ) if transpose: E0 = E0.T E1 = E1.T - new_density_matrix = (dot(E0, dot(density_matrix, E0.T)) + - dot(E1, dot(density_matrix, E1.T))) + new_density_matrix = dot(E0, dot(density_matrix, E0.T)) + dot(E1, dot(density_matrix, E1.T)) return new_density_matrix -def dephasing_channel(density_matrix, - probability, - target_qubit, - transpose=False): +def dephasing_channel(density_matrix, probability, target_qubit, transpose=False): r"""Apply a dephasing channel Applies an amplitude damping channel with a given probability to the target @@ -122,26 +118,21 @@ def dephasing_channel(density_matrix, _verify_channel_inputs(density_matrix, probability, target_qubit) n_qubits = int(log2(density_matrix.shape[0])) - E0 = _lift_operator( - sqrt(1.0 - probability / 2.) * eye(2), n_qubits, target_qubit) + E0 = _lift_operator(sqrt(1.0 - probability / 2.0) * eye(2), n_qubits, target_qubit) E1 = _lift_operator( - sqrt(probability / 2.) * array([[1.0, 0.0], [1.0, -1.0]]), n_qubits, - target_qubit) + sqrt(probability / 2.0) * array([[1.0, 0.0], [1.0, -1.0]]), n_qubits, target_qubit + ) if transpose: E0 = E0.T E1 = E1.T - new_density_matrix = (dot(E0, dot(density_matrix, E0.T)) + - dot(E1, dot(density_matrix, E1.T))) + new_density_matrix = dot(E0, dot(density_matrix, E0.T)) + dot(E1, dot(density_matrix, E1.T)) return new_density_matrix -def depolarizing_channel(density_matrix, - probability, - target_qubit, - transpose=False): +def depolarizing_channel(density_matrix, probability, target_qubit, transpose=False): r"""Apply a depolarizing channel Applies an amplitude damping channel with a given probability to the target @@ -165,28 +156,30 @@ def depolarizing_channel(density_matrix, # Toggle depolarizing channel on all qubits if isinstance(target_qubit, str) and target_qubit.lower() == "all": dimension = density_matrix.shape[0] - new_density_matrix = ((1.0 - probability) * density_matrix + - probability * eye(dimension) / float(dimension)) + new_density_matrix = (1.0 - probability) * density_matrix + probability * eye( + dimension + ) / float(dimension) return new_density_matrix # For any other case, depolarize only the target qubit _verify_channel_inputs(density_matrix, probability, target_qubit) - E0 = _lift_operator( - sqrt(1.0 - probability) * eye(2), n_qubits, target_qubit) + E0 = _lift_operator(sqrt(1.0 - probability) * eye(2), n_qubits, target_qubit) E1 = _lift_operator( - sqrt(probability / 3.) * array([[0.0, 1.0], [1.0, 0.0]]), n_qubits, - target_qubit) + sqrt(probability / 3.0) * array([[0.0, 1.0], [1.0, 0.0]]), n_qubits, target_qubit + ) E2 = _lift_operator( - sqrt(probability / 3.) * array([[0.0, -1.0j], [1.0j, 0.0]]), n_qubits, - target_qubit) + sqrt(probability / 3.0) * array([[0.0, -1.0j], [1.0j, 0.0]]), n_qubits, target_qubit + ) E3 = _lift_operator( - sqrt(probability / 3.) * array([[1.0, 0.0], [0.0, -1.0]]), n_qubits, - target_qubit) - - new_density_matrix = (dot(E0, dot(density_matrix, E0)) + - dot(E1, dot(density_matrix, E1)) + - dot(E2, dot(density_matrix, E2)) + - dot(E3, dot(density_matrix, E3))) + sqrt(probability / 3.0) * array([[1.0, 0.0], [0.0, -1.0]]), n_qubits, target_qubit + ) + + new_density_matrix = ( + dot(E0, dot(density_matrix, E0)) + + dot(E1, dot(density_matrix, E1)) + + dot(E2, dot(density_matrix, E2)) + + dot(E3, dot(density_matrix, E3)) + ) return new_density_matrix diff --git a/src/openfermion/utils/channel_state_test.py b/src/openfermion/utils/channel_state_test.py index c861d1081..7e6b5a0e1 100644 --- a/src/openfermion/utils/channel_state_test.py +++ b/src/openfermion/utils/channel_state_test.py @@ -14,22 +14,17 @@ import numpy as np from scipy.linalg import norm -from .channel_state import ( - amplitude_damping_channel, - dephasing_channel, - depolarizing_channel, -) +from .channel_state import amplitude_damping_channel, dephasing_channel, depolarizing_channel class ChannelTest(unittest.TestCase): - def setUp(self): """Initialize a few density matrices""" zero_state = np.array([[1], [0]], dtype=complex) one_state = np.array([[0], [1]], dtype=complex) one_one_state = np.kron(one_state, one_state) zero_zero_state = np.kron(zero_state, zero_state) - cat_state = 1. / np.sqrt(2) * (zero_zero_state + one_one_state) + cat_state = 1.0 / np.sqrt(2) * (zero_zero_state + one_one_state) self.density_matrix = np.dot(one_one_state, one_one_state.T) self.cat_matrix = np.dot(cat_state, cat_state.T) @@ -38,108 +33,91 @@ def test_amplitude_damping(self): """Test amplitude damping on a simple qubit state""" # With probability 0 - test_density_matrix = (amplitude_damping_channel( - self.density_matrix, 0, 1)) - self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), - 0.0) - - test_density_matrix = (amplitude_damping_channel(self.density_matrix, - 0, - 1, - transpose=True)) - self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), - 0.0) + test_density_matrix = amplitude_damping_channel(self.density_matrix, 0, 1) + self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), 0.0) + + test_density_matrix = amplitude_damping_channel(self.density_matrix, 0, 1, transpose=True) + self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), 0.0) # With probability 1 correct_density_matrix = np.zeros((4, 4), dtype=complex) correct_density_matrix[2, 2] = 1 - test_density_matrix = (amplitude_damping_channel( - self.density_matrix, 1, 1)) + test_density_matrix = amplitude_damping_channel(self.density_matrix, 1, 1) - self.assertAlmostEqual( - norm(correct_density_matrix - test_density_matrix), 0.0) + self.assertAlmostEqual(norm(correct_density_matrix - test_density_matrix), 0.0) def test_dephasing(self): """Test dephasing on a simple qubit state""" # Check for identity on |11> state - test_density_matrix = (dephasing_channel(self.density_matrix, 1, 1)) - self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), - 0.0) - - test_density_matrix = (dephasing_channel(self.density_matrix, - 1, - 1, - transpose=True)) - - correct_matrix = np.array([[0., 0., 0., 0.], [0., 0., 0., 0.], - [0., 0., 0.5, -0.5], [0., 0., -0.5, 1.]]) + test_density_matrix = dephasing_channel(self.density_matrix, 1, 1) + self.assertAlmostEqual(norm(self.density_matrix - test_density_matrix), 0.0) + + test_density_matrix = dephasing_channel(self.density_matrix, 1, 1, transpose=True) + + correct_matrix = np.array( + [ + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.5, -0.5], + [0.0, 0.0, -0.5, 1.0], + ] + ) self.assertAlmostEqual(norm(correct_matrix - test_density_matrix), 0.0) # Check for correct action on cat state # With probability = 0 - test_density_matrix = (dephasing_channel(self.cat_matrix, 0, 1)) + test_density_matrix = dephasing_channel(self.cat_matrix, 0, 1) self.assertAlmostEqual(norm(self.cat_matrix - test_density_matrix), 0.0) # With probability = 1 - correct_matrix = np.array([[0.50, 0.25, 0.00, 0.00], - [0.25, 0.25, 0.00, -0.25], - [0.00, 0.00, 0.00, 0.00], - [0.00, -0.25, 0.00, 0.50]]) - test_density_matrix = (dephasing_channel(self.cat_matrix, 1, 1)) + correct_matrix = np.array( + [ + [0.50, 0.25, 0.00, 0.00], + [0.25, 0.25, 0.00, -0.25], + [0.00, 0.00, 0.00, 0.00], + [0.00, -0.25, 0.00, 0.50], + ] + ) + test_density_matrix = dephasing_channel(self.cat_matrix, 1, 1) self.assertAlmostEqual(norm(correct_matrix - test_density_matrix), 0.0) def test_depolarizing(self): """Test depolarizing on a simple qubit state""" # With probability = 0 - test_density_matrix = (depolarizing_channel(self.cat_matrix, 0, 1)) + test_density_matrix = depolarizing_channel(self.cat_matrix, 0, 1) self.assertAlmostEqual(norm(self.cat_matrix - test_density_matrix), 0.0) - test_density_matrix = (depolarizing_channel(self.cat_matrix, - 0, - 1, - transpose=True)) + test_density_matrix = depolarizing_channel(self.cat_matrix, 0, 1, transpose=True) self.assertAlmostEqual(norm(self.cat_matrix - test_density_matrix), 0.0) # With probability 1 on both qubits - correct_density_matrix = (np.array( - [[0.27777778, 0.00000000, 0.00000000, 0.05555556], - [0.00000000, 0.22222222, 0.00000000, 0.00000000], - [0.00000000, 0.00000000, 0.22222222, 0.00000000], - [0.05555556, 0.00000000, 0.00000000, 0.27777778]])) + correct_density_matrix = np.array( + [ + [0.27777778, 0.00000000, 0.00000000, 0.05555556], + [0.00000000, 0.22222222, 0.00000000, 0.00000000], + [0.00000000, 0.00000000, 0.22222222, 0.00000000], + [0.05555556, 0.00000000, 0.00000000, 0.27777778], + ] + ) - test_density_matrix = (depolarizing_channel(self.cat_matrix, 1, 0)) - test_density_matrix = (depolarizing_channel(test_density_matrix, 1, 1)) + test_density_matrix = depolarizing_channel(self.cat_matrix, 1, 0) + test_density_matrix = depolarizing_channel(test_density_matrix, 1, 1) - self.assertAlmostEqual(norm(correct_density_matrix - - test_density_matrix), - 0.0, - places=6) + self.assertAlmostEqual(norm(correct_density_matrix - test_density_matrix), 0.0, places=6) # Depolarizing channel should be self-adjoint - test_density_matrix = (depolarizing_channel(self.cat_matrix, - 1, - 0, - transpose=True)) - test_density_matrix = (depolarizing_channel(test_density_matrix, - 1, - 1, - transpose=True)) - - self.assertAlmostEqual(norm(correct_density_matrix - - test_density_matrix), - 0.0, - places=6) + test_density_matrix = depolarizing_channel(self.cat_matrix, 1, 0, transpose=True) + test_density_matrix = depolarizing_channel(test_density_matrix, 1, 1, transpose=True) + + self.assertAlmostEqual(norm(correct_density_matrix - test_density_matrix), 0.0, places=6) # With probability 1 for total depolarization correct_density_matrix = np.eye(4) / 4.0 - test_density_matrix = (depolarizing_channel(self.cat_matrix, 1, 'All')) - self.assertAlmostEqual(norm(correct_density_matrix - - test_density_matrix), - 0.0, - places=6) + test_density_matrix = depolarizing_channel(self.cat_matrix, 1, 'All') + self.assertAlmostEqual(norm(correct_density_matrix - test_density_matrix), 0.0, places=6) def test_verification(self): """Verify basic sanity checking on inputs""" diff --git a/src/openfermion/utils/commutators.py b/src/openfermion/utils/commutators.py index c0e049edb..a3ee7836b 100644 --- a/src/openfermion/utils/commutators.py +++ b/src/openfermion/utils/commutators.py @@ -14,8 +14,7 @@ import numpy from openfermion.ops.operators import FermionOperator -from openfermion.transforms.opconversions.term_reordering\ - import normal_ordered +from openfermion.transforms.opconversions.term_reordering import normal_ordered def commutator(operator_a, operator_b): @@ -64,13 +63,15 @@ def anticommutator(operator_a, operator_b): return result -def double_commutator(op1, - op2, - op3, - indices2=None, - indices3=None, - is_hopping_operator2=None, - is_hopping_operator3=None): +def double_commutator( + op1, + op2, + op3, + indices2=None, + indices3=None, + is_hopping_operator2=None, + is_hopping_operator3=None, +): """Return the double commutator [op1, [op2, op3]]. Args: @@ -88,7 +89,7 @@ def double_commutator(op1, indices3 = set(indices3) # Determine which indices both op2 and op3 act on. try: - intersection, = indices2.intersection(indices3) + (intersection,) = indices2.intersection(indices3) except ValueError: return FermionOperator.zero() @@ -98,13 +99,13 @@ def double_commutator(op1, indices3.remove(intersection) # Find the indices of the final output hopping operator. - index2, = indices2 - index3, = indices3 + (index2,) = indices2 + (index3,) = indices3 coeff2 = op2.terms[list(op2.terms)[0]] coeff3 = op3.terms[list(op3.terms)[0]] - commutator23 = (FermionOperator( - ((index2, 1), (index3, 0)), coeff2 * coeff3) + FermionOperator( - ((index3, 1), (index2, 0)), -coeff2 * coeff3)) + commutator23 = FermionOperator( + ((index2, 1), (index3, 0)), coeff2 * coeff3 + ) + FermionOperator(((index3, 1), (index2, 0)), -coeff2 * coeff3) else: commutator23 = normal_ordered(commutator(op2, op3)) @@ -112,13 +113,14 @@ def double_commutator(op1, def trivially_double_commutes_dual_basis_using_term_info( - indices_alpha=None, - indices_beta=None, - indices_alpha_prime=None, - is_hopping_operator_alpha=None, - is_hopping_operator_beta=None, - is_hopping_operator_alpha_prime=None, - jellium_only=False): + indices_alpha=None, + indices_beta=None, + indices_alpha_prime=None, + is_hopping_operator_alpha=None, + is_hopping_operator_beta=None, + is_hopping_operator_alpha_prime=None, + jellium_only=False, +): """Return whether [op_a, [op_b, op_a_prime]] is trivially zero. Assumes all the operators are FermionOperators from the dual basis @@ -159,9 +161,11 @@ def trivially_double_commutes_dual_basis_using_term_info( # form i^ i + j^ j or i^ j^ i j + c*(i^ i + j^ j), and not both # hopping operators) commute if they act on the same modes or if # there is no intersection. - if (jellium_only and (not is_hopping_operator_alpha_prime or - not is_hopping_operator_beta) and - len(indices_beta.intersection(indices_alpha_prime)) != 1): + if ( + jellium_only + and (not is_hopping_operator_alpha_prime or not is_hopping_operator_beta) + and len(indices_beta.intersection(indices_alpha_prime)) != 1 + ): return True # If the modes operator_alpha acts on are disjoint with the modes @@ -184,45 +188,44 @@ def trivially_commutes_dual_basis(term_a, term_b): Returns: Whether or not the commutator is trivially zero. """ - modes_acted_on_by_term_a, = term_a.terms.keys() - modes_acted_on_by_term_b, = term_b.terms.keys() + (modes_acted_on_by_term_a,) = term_a.terms.keys() + (modes_acted_on_by_term_b,) = term_b.terms.keys() - modes_touched_a = [ - modes_acted_on_by_term_a[0][0], modes_acted_on_by_term_a[1][0] - ] - modes_touched_b = [ - modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0] - ] + modes_touched_a = [modes_acted_on_by_term_a[0][0], modes_acted_on_by_term_a[1][0]] + modes_touched_b = [modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0]] # If there's no intersection between the modes term_a and term_b act # on, the commutator is zero. - if not (modes_touched_a[0] in modes_touched_b or - modes_touched_a[1] in modes_touched_b): + if not (modes_touched_a[0] in modes_touched_b or modes_touched_a[1] in modes_touched_b): return True # In the dual basis, possible number operators take the form # a^ a or a^ b^ a b. Number operators always commute trivially. term_a_is_number_operator = ( - modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_a[1][0] or - modes_acted_on_by_term_a[1][1]) + modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_a[1][0] + or modes_acted_on_by_term_a[1][1] + ) term_b_is_number_operator = ( - modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] or - modes_acted_on_by_term_b[1][1]) + modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] + or modes_acted_on_by_term_b[1][1] + ) if term_a_is_number_operator and term_b_is_number_operator: return True # If the first commutator's terms are both hopping, and both create # or annihilate the same mode, then the result is zero. if not (term_a_is_number_operator or term_b_is_number_operator): - if (modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_b[0][0] or - modes_acted_on_by_term_a[1][0] == modes_acted_on_by_term_b[1][0] - ): + if ( + modes_acted_on_by_term_a[0][0] == modes_acted_on_by_term_b[0][0] + or modes_acted_on_by_term_a[1][0] == modes_acted_on_by_term_b[1][0] + ): return True # If both terms act on the same operators and are not both hopping # operators, then they commute. - if ((term_a_is_number_operator or term_b_is_number_operator) and - set(modes_touched_a) == set(modes_touched_b)): + if (term_a_is_number_operator or term_b_is_number_operator) and set(modes_touched_a) == set( + modes_touched_b + ): return True return False @@ -245,27 +248,29 @@ def trivially_double_commutes_dual_basis(term_a, term_b, term_c): Whether or not the double commutator is trivially zero. """ # Determine the set of modes each term acts on. - modes_acted_on_by_term_b, = term_b.terms.keys() - modes_acted_on_by_term_c, = term_c.terms.keys() + (modes_acted_on_by_term_b,) = term_b.terms.keys() + (modes_acted_on_by_term_c,) = term_c.terms.keys() - modes_touched_c = [ - modes_acted_on_by_term_c[0][0], modes_acted_on_by_term_c[1][0] - ] + modes_touched_c = [modes_acted_on_by_term_c[0][0], modes_acted_on_by_term_c[1][0]] # If there's no intersection between the modes term_b and term_c act # on, the commutator is trivially zero. - if not (modes_acted_on_by_term_b[0][0] in modes_touched_c or - modes_acted_on_by_term_b[1][0] in modes_touched_c): + if not ( + modes_acted_on_by_term_b[0][0] in modes_touched_c + or modes_acted_on_by_term_b[1][0] in modes_touched_c + ): return True # In the dual_basis Hamiltonian, possible number operators take the # form a^ a or a^ b^ a b. Check for this. term_b_is_number_operator = ( - modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] or - modes_acted_on_by_term_b[1][1]) + modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_b[1][0] + or modes_acted_on_by_term_b[1][1] + ) term_c_is_number_operator = ( - modes_acted_on_by_term_c[0][0] == modes_acted_on_by_term_c[1][0] or - modes_acted_on_by_term_c[1][1]) + modes_acted_on_by_term_c[0][0] == modes_acted_on_by_term_c[1][0] + or modes_acted_on_by_term_c[1][1] + ) # Number operators always commute. if term_b_is_number_operator and term_c_is_number_operator: @@ -274,35 +279,38 @@ def trivially_double_commutes_dual_basis(term_a, term_b, term_c): # If the first commutator's terms are both hopping, and both create # or annihilate the same mode, then the result is zero. if not (term_b_is_number_operator or term_c_is_number_operator): - if (modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_c[0][0] or - modes_acted_on_by_term_b[1][0] == modes_acted_on_by_term_c[1][0] - ): + if ( + modes_acted_on_by_term_b[0][0] == modes_acted_on_by_term_c[0][0] + or modes_acted_on_by_term_b[1][0] == modes_acted_on_by_term_c[1][0] + ): return True # The modes term_a acts on are only needed if we reach this stage. - modes_acted_on_by_term_a, = term_a.terms.keys() - modes_touched_b = [ - modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0] - ] + (modes_acted_on_by_term_a,) = term_a.terms.keys() + modes_touched_b = [modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0]] modes_touched_bc = [ - modes_acted_on_by_term_b[0][0], modes_acted_on_by_term_b[1][0], - modes_acted_on_by_term_c[0][0], modes_acted_on_by_term_c[1][0] + modes_acted_on_by_term_b[0][0], + modes_acted_on_by_term_b[1][0], + modes_acted_on_by_term_c[0][0], + modes_acted_on_by_term_c[1][0], ] # If the term_a shares no indices with bc, the double commutator is zero. - if not (modes_acted_on_by_term_a[0][0] in modes_touched_bc or - modes_acted_on_by_term_a[1][0] in modes_touched_bc): + if not ( + modes_acted_on_by_term_a[0][0] in modes_touched_bc + or modes_acted_on_by_term_a[1][0] in modes_touched_bc + ): return True # If term_b and term_c are not both number operators and act on the # same modes, the commutator is zero. - if (sum(1 for i in modes_touched_b if i in modes_touched_c) > 1 and - (term_b_is_number_operator or term_c_is_number_operator)): + if sum(1 for i in modes_touched_b if i in modes_touched_c) > 1 and ( + term_b_is_number_operator or term_c_is_number_operator + ): return True # Create a list of all the creation and annihilations performed. - all_changes = (modes_acted_on_by_term_a + modes_acted_on_by_term_b + - modes_acted_on_by_term_c) + all_changes = modes_acted_on_by_term_a + modes_acted_on_by_term_b + modes_acted_on_by_term_c counts = {} for operator in all_changes: counts[operator[0]] = counts.get(operator[0], 0) + 2 * operator[1] - 1 diff --git a/src/openfermion/utils/commutators_test.py b/src/openfermion/utils/commutators_test.py index 75e97add9..83c92583a 100644 --- a/src/openfermion/utils/commutators_test.py +++ b/src/openfermion/utils/commutators_test.py @@ -14,35 +14,33 @@ import numpy -from openfermion.ops.operators import (FermionOperator, QubitOperator, - BosonOperator, QuadOperator) +from openfermion.ops.operators import FermionOperator, QubitOperator, BosonOperator, QuadOperator from openfermion.transforms import jordan_wigner from openfermion.utils import hermitian_conjugated from openfermion.transforms.opconversions import normal_ordered from openfermion.linalg.sparse_tools import pauli_matrix_map from openfermion.utils.commutators import ( - commutator, anticommutator, double_commutator, - trivially_commutes_dual_basis, trivially_double_commutes_dual_basis, - trivially_double_commutes_dual_basis_using_term_info) + commutator, + anticommutator, + double_commutator, + trivially_commutes_dual_basis, + trivially_double_commutes_dual_basis, + trivially_double_commutes_dual_basis_using_term_info, +) class CommutatorTest(unittest.TestCase): - def setUp(self): self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) - self.fermion_operator = self.fermion_term + hermitian_conjugated( - self.fermion_term) + self.fermion_operator = self.fermion_term + hermitian_conjugated(self.fermion_term) self.boson_term = BosonOperator('1^ 2^ 3 4', -3.17) - self.boson_operator = self.boson_term + hermitian_conjugated( - self.boson_term) + self.boson_operator = self.boson_term + hermitian_conjugated(self.boson_term) self.quad_term = QuadOperator('q0 p0 q1 p0 p0', -3.17) - self.quad_operator = self.quad_term + hermitian_conjugated( - self.quad_term) + self.quad_operator = self.quad_term + hermitian_conjugated(self.quad_term) self.qubit_operator = jordan_wigner(self.fermion_operator) def test_commutes_identity(self): - com = commutator(FermionOperator.identity(), - FermionOperator('2^ 3', 2.3)) + com = commutator(FermionOperator.identity(), FermionOperator('2^ 3', 2.3)) self.assertEqual(com, FermionOperator.zero()) com = commutator(BosonOperator.identity(), BosonOperator('2^ 3', 2.3)) @@ -99,12 +97,12 @@ def test_commutator_hopping_with_double_number_two_intersections(self): def test_commutator(self): operator_a = FermionOperator('') - self.assertEqual(FermionOperator.zero(), - commutator(operator_a, self.fermion_operator)) + self.assertEqual(FermionOperator.zero(), commutator(operator_a, self.fermion_operator)) operator_b = QubitOperator('X1 Y2') - self.assertEqual(commutator(self.qubit_operator, operator_b), - (self.qubit_operator * operator_b - - operator_b * self.qubit_operator)) + self.assertEqual( + commutator(self.qubit_operator, operator_b), + (self.qubit_operator * operator_b - operator_b * self.qubit_operator), + ) def test_canonical_boson_commutation_relations(self): op_1 = BosonOperator('3') @@ -128,23 +126,21 @@ def test_canonical_quad_commutation_relations(self): p2 = QuadOperator('p4') zero = QuadOperator() one = QuadOperator('') - hbar = 2. + hbar = 2.0 - self.assertTrue(1j * hbar * - one == normal_ordered(commutator(q1, p1), hbar)) + self.assertTrue(1j * hbar * one == normal_ordered(commutator(q1, p1), hbar)) self.assertTrue(zero == normal_ordered(commutator(q1, q2), hbar)) self.assertTrue(zero == normal_ordered(commutator(q1, p2), hbar)) self.assertTrue(zero == normal_ordered(commutator(p1, q2), hbar)) self.assertTrue(zero == normal_ordered(commutator(p1, p2), hbar)) - self.assertTrue(1j * hbar * - one == normal_ordered(commutator(q2, p2), hbar)) + self.assertTrue(1j * hbar * one == normal_ordered(commutator(q2, p2), hbar)) def test_ndarray_input(self): """Test when the inputs are numpy arrays.""" X = pauli_matrix_map['X'].toarray() Y = pauli_matrix_map['Y'].toarray() Z = pauli_matrix_map['Z'].toarray() - self.assertTrue(numpy.allclose(commutator(X, Y), 2.j * Z)) + self.assertTrue(numpy.allclose(commutator(X, Y), 2.0j * Z)) def test_commutator_operator_a_bad_type(self): with self.assertRaises(TypeError): @@ -160,7 +156,6 @@ def test_commutator_not_same_type(self): class AnticommutatorTest(unittest.TestCase): - def test_canonical_anticommutation_relations(self): op_1 = FermionOperator('3') op_1_dag = FermionOperator('3^') @@ -173,8 +168,7 @@ def test_canonical_anticommutation_relations(self): self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2))) self.assertEqual(zero, normal_ordered(anticommutator(op_1, op_2_dag))) self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2))) - self.assertEqual(zero, - normal_ordered(anticommutator(op_1_dag, op_2_dag))) + self.assertEqual(zero, normal_ordered(anticommutator(op_1_dag, op_2_dag))) self.assertEqual(one, normal_ordered(anticommutator(op_2, op_2_dag))) def test_ndarray_input(self): @@ -190,24 +184,23 @@ def test_anticommutator_not_same_type(self): class DoubleCommutatorTest(unittest.TestCase): - def test_double_commutator_no_intersection_with_union_of_second_two(self): - com = double_commutator(FermionOperator('4^ 3^ 6 5'), - FermionOperator('2^ 1 0'), - FermionOperator('0^')) + com = double_commutator( + FermionOperator('4^ 3^ 6 5'), FermionOperator('2^ 1 0'), FermionOperator('0^') + ) self.assertEqual(com, FermionOperator.zero()) def test_double_commutator_more_info_not_hopping(self): - com = double_commutator(FermionOperator('3^ 2'), - FermionOperator('2^ 3') + - FermionOperator('3^ 2'), - FermionOperator('4^ 2^ 4 2'), - indices2=set([2, 3]), - indices3=set([2, 4]), - is_hopping_operator2=True, - is_hopping_operator3=False) - self.assertEqual( - com, (FermionOperator('4^ 2^ 4 2') - FermionOperator('4^ 3^ 4 3'))) + com = double_commutator( + FermionOperator('3^ 2'), + FermionOperator('2^ 3') + FermionOperator('3^ 2'), + FermionOperator('4^ 2^ 4 2'), + indices2=set([2, 3]), + indices3=set([2, 4]), + is_hopping_operator2=True, + is_hopping_operator3=False, + ) + self.assertEqual(com, (FermionOperator('4^ 2^ 4 2') - FermionOperator('4^ 3^ 4 3'))) def test_double_commtator_more_info_both_hopping(self): com = double_commutator( @@ -217,13 +210,14 @@ def test_double_commtator_more_info_both_hopping(self): indices2=set([1, 2]), indices3=set([1, 3]), is_hopping_operator2=True, - is_hopping_operator3=True) - self.assertEqual(com, (FermionOperator('4^ 3^ 4 2', 2.73) + - FermionOperator('4^ 2^ 4 3', 2.73))) + is_hopping_operator3=True, + ) + self.assertEqual( + com, (FermionOperator('4^ 3^ 4 2', 2.73) + FermionOperator('4^ 2^ 4 3', 2.73)) + ) class TriviallyDoubleCommutesDualBasisUsingTermInfoTest(unittest.TestCase): - def test_number_operators_trivially_commute(self): self.assertTrue( trivially_double_commutes_dual_basis_using_term_info( @@ -233,7 +227,9 @@ def test_number_operators_trivially_commute(self): is_hopping_operator_alpha=False, is_hopping_operator_beta=False, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_left_hopping_operator_no_trivial_commutation(self): self.assertFalse( @@ -244,7 +240,9 @@ def test_left_hopping_operator_no_trivial_commutation(self): is_hopping_operator_alpha=True, is_hopping_operator_beta=True, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_right_hopping_operator_no_trivial_commutation(self): self.assertFalse( @@ -255,7 +253,9 @@ def test_right_hopping_operator_no_trivial_commutation(self): is_hopping_operator_alpha=True, is_hopping_operator_beta=False, is_hopping_operator_alpha_prime=True, - jellium_only=True)) + jellium_only=True, + ) + ) def test_alpha_is_hopping_operator_others_number_trivial_commutation(self): self.assertTrue( @@ -266,7 +266,9 @@ def test_alpha_is_hopping_operator_others_number_trivial_commutation(self): is_hopping_operator_alpha=True, is_hopping_operator_beta=False, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_no_intersection_in_first_commutator_trivially_commutes(self): self.assertTrue( @@ -277,7 +279,9 @@ def test_no_intersection_in_first_commutator_trivially_commutes(self): is_hopping_operator_alpha=True, is_hopping_operator_beta=True, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_double_intersection_in_first_commutator_trivially_commutes(self): self.assertTrue( @@ -288,7 +292,9 @@ def test_double_intersection_in_first_commutator_trivially_commutes(self): is_hopping_operator_alpha=True, is_hopping_operator_beta=True, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_single_intersection_in_first_commutator_nontrivial(self): self.assertFalse( @@ -299,7 +305,9 @@ def test_single_intersection_in_first_commutator_nontrivial(self): is_hopping_operator_alpha=False, is_hopping_operator_beta=True, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) def test_no_intersection_between_first_and_other_terms_is_trivial(self): self.assertTrue( @@ -310,167 +318,186 @@ def test_no_intersection_between_first_and_other_terms_is_trivial(self): is_hopping_operator_alpha=False, is_hopping_operator_beta=True, is_hopping_operator_alpha_prime=False, - jellium_only=True)) + jellium_only=True, + ) + ) class TriviallyCommutesDualBasisTest(unittest.TestCase): - def test_trivially_commutes_no_intersection(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), - FermionOperator('4^ 1'))) + trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), FermionOperator('4^ 1')) + ) def test_no_trivial_commute_with_intersection(self): self.assertFalse( - trivially_commutes_dual_basis(FermionOperator('2^ 1'), - FermionOperator('5^ 2^ 5 2'))) + trivially_commutes_dual_basis(FermionOperator('2^ 1'), FermionOperator('5^ 2^ 5 2')) + ) def test_trivially_commutes_both_single_number_operators(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('3^ 3'), - FermionOperator('3^ 3'))) + trivially_commutes_dual_basis(FermionOperator('3^ 3'), FermionOperator('3^ 3')) + ) def test_trivially_commutes_nonintersecting_single_number_operators(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('2^ 2'), - FermionOperator('3^ 3'))) + trivially_commutes_dual_basis(FermionOperator('2^ 2'), FermionOperator('3^ 3')) + ) def test_trivially_commutes_both_double_number_operators(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), - FermionOperator('3^ 1^ 3 1'))) + trivially_commutes_dual_basis( + FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 1^ 3 1') + ) + ) def test_trivially_commutes_one_double_number_operators(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), - FermionOperator('3^ 3'))) + trivially_commutes_dual_basis(FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 3')) + ) def test_no_trivial_commute_right_hopping_operator(self): self.assertFalse( - trivially_commutes_dual_basis(FermionOperator('3^ 1^ 3 1'), - FermionOperator('3^ 2'))) + trivially_commutes_dual_basis(FermionOperator('3^ 1^ 3 1'), FermionOperator('3^ 2')) + ) def test_no_trivial_commute_left_hopping_operator(self): self.assertFalse( - trivially_commutes_dual_basis(FermionOperator('3^ 2'), - FermionOperator('3^ 3'))) + trivially_commutes_dual_basis(FermionOperator('3^ 2'), FermionOperator('3^ 3')) + ) def test_trivially_commutes_both_hopping_create_same_mode(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('3^ 2'), - FermionOperator('3^ 1'))) + trivially_commutes_dual_basis(FermionOperator('3^ 2'), FermionOperator('3^ 1')) + ) def test_trivially_commutes_both_hopping_annihilate_same_mode(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('4^ 1'), - FermionOperator('3^ 1'))) + trivially_commutes_dual_basis(FermionOperator('4^ 1'), FermionOperator('3^ 1')) + ) def test_trivially_commutes_both_hopping_and_number_on_same_modes(self): self.assertTrue( - trivially_commutes_dual_basis(FermionOperator('4^ 1'), - FermionOperator('4^ 1^ 4 1'))) + trivially_commutes_dual_basis(FermionOperator('4^ 1'), FermionOperator('4^ 1^ 4 1')) + ) class TriviallyDoubleCommutesDualBasisTest(unittest.TestCase): - def test_trivially_double_commutes_no_intersection(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('3^ 4'), - FermionOperator('3^ 2^ 3 2'), - FermionOperator('4^ 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('3^ 4'), FermionOperator('3^ 2^ 3 2'), FermionOperator('4^ 1') + ) + ) def test_no_trivial_double_commute_with_intersection(self): self.assertFalse( - trivially_double_commutes_dual_basis(FermionOperator('4^ 2'), - FermionOperator('2^ 1'), - FermionOperator('5^ 2^ 5 2'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 2'), FermionOperator('2^ 1'), FermionOperator('5^ 2^ 5 2') + ) + ) def test_trivially_double_commutes_both_single_number_operators(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('3^ 3'), - FermionOperator('3^ 3'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('3^ 3'), FermionOperator('3^ 3') + ) + ) def test_trivially_double_commutes_nonintersecting_single_number_ops(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('3^ 2'), - FermionOperator('2^ 2'), - FermionOperator('3^ 3'))) + trivially_double_commutes_dual_basis( + FermionOperator('3^ 2'), FermionOperator('2^ 2'), FermionOperator('3^ 3') + ) + ) def test_trivially_double_commutes_both_double_number_operators(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('3^ 2^ 3 2'), - FermionOperator('3^ 1^ 3 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 1^ 3 1') + ) + ) def test_trivially_double_commutes_one_double_number_operators(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('3^ 2^ 3 2'), - FermionOperator('3^ 3'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('3^ 2^ 3 2'), FermionOperator('3^ 3') + ) + ) def test_no_trivial_double_commute_right_hopping_operator(self): self.assertFalse( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('3^ 1^ 3 1'), - FermionOperator('3^ 2'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('3^ 1^ 3 1'), FermionOperator('3^ 2') + ) + ) def test_no_trivial_double_commute_left_hopping_operator(self): self.assertFalse( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('3^ 2'), - FermionOperator('3^ 3'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('3^ 2'), FermionOperator('3^ 3') + ) + ) def test_trivially_double_commutes_both_hopping_create_same_mode(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('3^ 3'), - FermionOperator('3^ 2'), - FermionOperator('3^ 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('3^ 3'), FermionOperator('3^ 2'), FermionOperator('3^ 1') + ) + ) def test_trivially_double_commutes_both_hopping_annihilate_same_mode(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('1^ 1'), - FermionOperator('4^ 1'), - FermionOperator('3^ 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('1^ 1'), FermionOperator('4^ 1'), FermionOperator('3^ 1') + ) + ) def test_trivially_double_commutes_hopping_and_number_on_same_modes(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('4^ 3'), - FermionOperator('4^ 1'), - FermionOperator('4^ 1^ 4 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('4^ 3'), FermionOperator('4^ 1'), FermionOperator('4^ 1^ 4 1') + ) + ) def test_trivially_double_commutes_no_intersection_a_with_bc(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('3^ 1'), - FermionOperator('4^ 1^ 4 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1') + ) + ) def test_trivially_double_commutes_double_create_in_a_and_b(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('3^ 1'), - FermionOperator('4^ 1^ 4 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1') + ) + ) def test_trivially_double_commutes_double_annihilate_in_a_and_c(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('3^ 1'), - FermionOperator('4^ 1^ 4 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('3^ 1'), FermionOperator('4^ 1^ 4 1') + ) + ) def test_no_trivial_double_commute_double_annihilate_with_create(self): self.assertFalse( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('2^ 1'), - FermionOperator('4^ 2'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('2^ 1'), FermionOperator('4^ 2') + ) + ) def test_trivially_double_commutes_excess_create(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('5^ 5'), - FermionOperator('5^ 1'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('5^ 5'), FermionOperator('5^ 1') + ) + ) def test_trivially_double_commutes_excess_annihilate(self): self.assertTrue( - trivially_double_commutes_dual_basis(FermionOperator('5^ 2'), - FermionOperator('3^ 2'), - FermionOperator('2^ 2'))) + trivially_double_commutes_dual_basis( + FermionOperator('5^ 2'), FermionOperator('3^ 2'), FermionOperator('2^ 2') + ) + ) diff --git a/src/openfermion/utils/grid.py b/src/openfermion/utils/grid.py index f6ea4c15b..52d21d73b 100644 --- a/src/openfermion/utils/grid.py +++ b/src/openfermion/utils/grid.py @@ -60,16 +60,25 @@ def __init__(self, dimensions, length, scale): if not isinstance(dimensions, int) or dimensions <= 0: raise ValueError( 'dimensions must be a positive int but was {} {}'.format( - type(dimensions), repr(dimensions))) - if ((not isinstance(length, int) or length < 0) and - (not isinstance(length, tuple)) and (not isinstance(length, list))): - raise ValueError('length must be a non-negative int or tuple ' - 'but was {} {}'.format(type(length), repr(length))) - if ((not isinstance(scale, float) or not scale > 0) and - (not isinstance(scale, numpy.ndarray))): + type(dimensions), repr(dimensions) + ) + ) + if ( + (not isinstance(length, int) or length < 0) + and (not isinstance(length, tuple)) + and (not isinstance(length, list)) + ): + raise ValueError( + 'length must be a non-negative int or tuple ' + 'but was {} {}'.format(type(length), repr(length)) + ) + if (not isinstance(scale, float) or not scale > 0) and ( + not isinstance(scale, numpy.ndarray) + ): raise ValueError( 'scale must be a positive float or ndarray but was ' - '{} {}'.format(type(scale), repr(scale))) + '{} {}'.format(type(scale), repr(scale)) + ) self.dimensions = dimensions @@ -109,8 +118,7 @@ def all_points_indices(self): iterable[tuple[int]]: The index-coordinate tuple of each point in the grid. """ - return itertools.product( - *[range(self.length[i]) for i in range(self.dimensions)]) + return itertools.product(*[range(self.length[i]) for i in range(self.dimensions)]) def position_vector(self, position_indices): """Given grid point coordinate, return position vector with dimensions. @@ -126,16 +134,18 @@ def position_vector(self, position_indices): # Raise exceptions. if isinstance(position_indices, int): position_indices = [position_indices] - if not all(0 <= e < self.length[i] - for i, e in enumerate(position_indices)): + if not all(0 <= e < self.length[i] for i, e in enumerate(position_indices)): raise OrbitalSpecificationError( - 'Position indices must be integers in [0, grid_length).') + 'Position indices must be integers in [0, grid_length).' + ) # Compute position vector - vector = sum([ - (float(n - self.shifts[i]) / self.length[i]) * self.scale[:, i] - for i, n in enumerate(position_indices) - ]) + vector = sum( + [ + (float(n - self.shifts[i]) / self.length[i]) * self.scale[:, i] + for i, n in enumerate(position_indices) + ] + ) return vector def momentum_vector(self, momentum_indices, periodic=True): @@ -153,10 +163,10 @@ def momentum_vector(self, momentum_indices, periodic=True): # Raise exceptions. if isinstance(momentum_indices, int): momentum_indices = [momentum_indices] - if (not all(0 <= e < self.length[i] - for i, e in enumerate(momentum_indices))): + if not all(0 <= e < self.length[i] for i, e in enumerate(momentum_indices)): raise OrbitalSpecificationError( - 'Momentum indices must be integers in [0, grid_length).') + 'Momentum indices must be integers in [0, grid_length).' + ) # Compute momentum vector. momentum_ints = self.index_to_momentum_ints(momentum_indices) @@ -172,9 +182,7 @@ def index_to_momentum_ints(self, index): Integer momentum vector """ # Set baseline for grid between [-N//2, N//2] - momentum_int = [ - index[i] - self.shifts[i] for i in range(self.dimensions) - ] + momentum_int = [index[i] - self.shifts[i] for i in range(self.dimensions)] return numpy.array(momentum_int, dtype=int) @@ -207,12 +215,11 @@ def momentum_ints_to_value(self, momentum_ints, periodic=True): """ # Alias the higher momentum modes if periodic: - momentum_ints = self.index_to_momentum_ints( - self.momentum_ints_to_index(momentum_ints)) + momentum_ints = self.index_to_momentum_ints(self.momentum_ints_to_index(momentum_ints)) - momentum_vector = sum([ - n * self.reciprocal_scale[:, i] for i, n in enumerate(momentum_ints) - ]) + momentum_vector = sum( + [n * self.reciprocal_scale[:, i] for i, n in enumerate(momentum_ints)] + ) return momentum_vector def orbital_id(self, grid_coordinates, spin=None): @@ -237,16 +244,12 @@ def orbital_id(self, grid_coordinates, spin=None): # Loop through dimensions of coordinate tuple. tensor_factor = 0 for dimension, grid_coordinate in enumerate(grid_coordinates): - # Make sure coordinate is an integer in the correct bounds. - if (isinstance(grid_coordinate, int) and - grid_coordinate < self.length[dimension]): - tensor_factor += (grid_coordinate * - int(numpy.product(self.length[:dimension]))) + if isinstance(grid_coordinate, int) and grid_coordinate < self.length[dimension]: + tensor_factor += grid_coordinate * int(numpy.product(self.length[:dimension])) else: # Raise for invalid model. - raise OrbitalSpecificationError( - 'Invalid orbital coordinates provided.') + raise OrbitalSpecificationError('Invalid orbital coordinates provided.') # Account for spin and return. if spin is None: @@ -279,19 +282,19 @@ def grid_indices(self, qubit_id, spinless): # Get grid indices. grid_indices = [] for dimension in range(self.dimensions): - remainder = (orbital_id % - int(numpy.product(self.length[:dimension + 1]))) - grid_index = (remainder // - int(numpy.product(self.length[:dimension]))) + remainder = orbital_id % int(numpy.product(self.length[: dimension + 1])) + grid_index = remainder // int(numpy.product(self.length[:dimension])) grid_indices += [grid_index] return grid_indices def __eq__(self, other): if not isinstance(other, type(self)): return NotImplemented - return (self.dimensions == other.dimensions and - (self.scale == other.scale).all() and - self.length == other.length) + return ( + self.dimensions == other.dimensions + and (self.scale == other.scale).all() + and self.length == other.length + ) def __ne__(self, other): return not self == other diff --git a/src/openfermion/utils/grid_test.py b/src/openfermion/utils/grid_test.py index 69d1e7ef5..a1488f29f 100644 --- a/src/openfermion/utils/grid_test.py +++ b/src/openfermion/utils/grid_test.py @@ -19,9 +19,7 @@ class GridTest(unittest.TestCase): - def test_orbital_id(self): - # Test in 1D with spin. grid = Grid(dimensions=1, length=5, scale=1.0) input_coords = [0, 1, 2, 3, 4] @@ -45,55 +43,64 @@ def test_orbital_id(self): self.assertEqual(test_output, tensor_factors) def test_position_vector(self): - # Test in 1D. - grid = Grid(dimensions=1, length=4, scale=4.) - test_output = [ - grid.position_vector(i)[0] for i in range(grid.length[0]) - ] + grid = Grid(dimensions=1, length=4, scale=4.0) + test_output = [grid.position_vector(i)[0] for i in range(grid.length[0])] correct_output = [-2, -1, 0, 1] self.assertEqual(correct_output, test_output) # Test in 2D. - grid = Grid(dimensions=2, length=3, scale=3.) + grid = Grid(dimensions=2, length=3, scale=3.0) test_input = [] test_output = [] for i in range(3): for j in range(3): test_input += [(i, j)] test_output += [grid.position_vector((i, j))] - correct_output = numpy.array([[-1., -1.], [-1., 0.], [-1., 1.], - [0., -1.], [0., 0.], [0., 1.], [1., -1.], - [1., 0.], [1., 1.]]) - self.assertAlmostEqual(0., numpy.amax(test_output - correct_output)) + correct_output = numpy.array( + [ + [-1.0, -1.0], + [-1.0, 0.0], + [-1.0, 1.0], + [0.0, -1.0], + [0.0, 0.0], + [0.0, 1.0], + [1.0, -1.0], + [1.0, 0.0], + [1.0, 1.0], + ] + ) + self.assertAlmostEqual(0.0, numpy.amax(test_output - correct_output)) def test_momentum_vector(self): - grid = Grid(dimensions=1, length=3, scale=2. * numpy.pi) + grid = Grid(dimensions=1, length=3, scale=2.0 * numpy.pi) test_output = [grid.momentum_vector(i) for i in range(grid.length[0])] - correct_output = [-1., 0, 1.] + correct_output = [-1.0, 0, 1.0] self.assertEqual(correct_output, test_output) - grid = Grid(dimensions=1, length=2, scale=2. * numpy.pi) + grid = Grid(dimensions=1, length=2, scale=2.0 * numpy.pi) test_output = [grid.momentum_vector(i) for i in range(grid.length[0])] - correct_output = [-1., 0.] + correct_output = [-1.0, 0.0] self.assertEqual(correct_output, test_output) - grid = Grid(dimensions=1, length=11, scale=2. * numpy.pi) + grid = Grid(dimensions=1, length=11, scale=2.0 * numpy.pi) for i in range(grid.length[0]): - self.assertAlmostEqual(-grid.momentum_vector(i), - grid.momentum_vector(grid.length[0] - i - 1)) + self.assertAlmostEqual( + -grid.momentum_vector(i), grid.momentum_vector(grid.length[0] - i - 1) + ) # Test in 2D. - grid = Grid(dimensions=2, length=3, scale=2. * numpy.pi) + grid = Grid(dimensions=2, length=3, scale=2.0 * numpy.pi) test_input = [] test_output = [] for i in range(3): for j in range(3): test_input += [(i, j)] test_output += [grid.momentum_vector((i, j))] - correct_output = numpy.array([[-1, -1], [-1, 0], [-1, 1], [0, -1], - [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]]) - self.assertAlmostEqual(0., numpy.amax(test_output - correct_output)) + correct_output = numpy.array( + [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 0], [0, 1], [1, -1], [1, 0], [1, 1]] + ) + self.assertAlmostEqual(0.0, numpy.amax(test_output - correct_output)) def test_grid_indices(self): g1 = Grid(dimensions=2, length=4, scale=1.0) @@ -162,17 +169,10 @@ def test_properties(self): g = Grid(dimensions=2, length=3, scale=5.0) self.assertEqual(g.num_points, 9) self.assertEqual(g.volume_scale(), 25) - self.assertEqual(list(g.all_points_indices()), [ - (0, 0), - (0, 1), - (0, 2), - (1, 0), - (1, 1), - (1, 2), - (2, 0), - (2, 1), - (2, 2), - ]) + self.assertEqual( + list(g.all_points_indices()), + [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)], + ) def test_equality(self): eq = EqualsTester(self) diff --git a/src/openfermion/utils/indexing.py b/src/openfermion/utils/indexing.py index ca4481371..82f7f989a 100644 --- a/src/openfermion/utils/indexing.py +++ b/src/openfermion/utils/indexing.py @@ -38,7 +38,7 @@ def down_index(index): def up_then_down(mode_idx, num_modes): - """ up then down reordering, given the operator has the default even-odd + """up then down reordering, given the operator has the default even-odd ordering. Otherwise this function will reorder indices where all even indices now come before odd indices. @@ -54,7 +54,7 @@ def up_then_down(mode_idx, num_modes): Returns (int): reordered index of the mode. """ - halfway = int(numpy.ceil(num_modes / 2.)) + halfway = int(numpy.ceil(num_modes / 2.0)) if mode_idx % 2 == 0: return mode_idx // 2 diff --git a/src/openfermion/utils/lattice.py b/src/openfermion/utils/lattice.py index 624d1e760..848b76a13 100644 --- a/src/openfermion/utils/lattice.py +++ b/src/openfermion/utils/lattice.py @@ -103,12 +103,10 @@ def dof_index_offset(self, dof_index): def to_spin_orbital_index(self, site_index, dof_index, spin_index): """The index of the spin orbital.""" - return (self.site_index_offset(site_index) + - self.dof_index_offset(dof_index) + spin_index) + return self.site_index_offset(site_index) + self.dof_index_offset(dof_index) + spin_index def from_spin_orbital_index(self, spin_orbital_index): - site_index, offset = divmod(spin_orbital_index, - self.n_spin_orbitals_per_site) + site_index, offset = divmod(spin_orbital_index, self.n_spin_orbitals_per_site) dof_index, spin_index = divmod(offset, self.n_spin_values) return site_index, dof_index, spin_index @@ -123,9 +121,7 @@ def dof_indices(self): return range(self.n_dofs) def dof_pairs_iter(self, exclude_same=False): - return ((a, b) - for a in range(self.n_dofs) - for b in range(a + exclude_same, self.n_dofs)) + return ((a, b) for a in range(self.n_dofs) for b in range(a + exclude_same, self.n_dofs)) @property def spin_indices(self): @@ -133,28 +129,30 @@ def spin_indices(self): def spin_pairs_iter(self, spin_pairs=SpinPairs.ALL, ordered=True): if spin_pairs == SpinPairs.ALL: - return (itertools.product(self.spin_indices, repeat=2) - if ordered else itertools.combinations_with_replacement( - self.spin_indices, 2)) + return ( + itertools.product(self.spin_indices, repeat=2) + if ordered + else itertools.combinations_with_replacement(self.spin_indices, 2) + ) elif spin_pairs == SpinPairs.SAME: return ((s, s) for s in self.spin_indices) elif spin_pairs == SpinPairs.DIFF: - return (itertools.permutations(self.spin_indices, 2) if ordered else - itertools.combinations(self.spin_indices, 2)) - raise ValueError( - '{} not a valid SpinPairs specification.'.format(spin_pairs)) + return ( + itertools.permutations(self.spin_indices, 2) + if ordered + else itertools.combinations(self.spin_indices, 2) + ) + raise ValueError('{} not a valid SpinPairs specification.'.format(spin_pairs)) # validation def validate_edge_type(self, edge_type): if edge_type not in self.edge_types: - raise ValueError('{} not a valid edge type {}.'.format( - edge_type, self.edge_types)) + raise ValueError('{} not a valid edge type {}.'.format(edge_type, self.edge_types)) def validate_dof(self, dof, length=None): if not (0 <= dof < self.n_dofs): - raise ValueError('not (0 <= {} < n_dofs = {})'.format( - dof, self.n_dofs)) + raise ValueError('not (0 <= {} < n_dofs = {})'.format(dof, self.n_dofs)) def validate_dofs(self, dofs, length=None): for dof in dofs: @@ -174,12 +172,7 @@ class HubbardSquareLattice(HubbardLattice): * 'diagonal_neighbor' """ - def __init__(self, - x_dimension, - y_dimension, - n_dofs=1, - spinless=False, - periodic=True): + def __init__(self, x_dimension, y_dimension, n_dofs=1, spinless=False, periodic=True): """ Args: x_dimension (int): The width of the grid. @@ -212,8 +205,13 @@ def n_sites(self): @property def edge_types(self): - return ('onsite', 'neighbor', 'diagonal_neighbor', - 'horizontal_neighbor', 'vertical_neighbor') + return ( + 'onsite', + 'neighbor', + 'diagonal_neighbor', + 'horizontal_neighbor', + 'vertical_neighbor', + ) @property def onsite_edge_types(self): @@ -234,11 +232,17 @@ def site_pairs_iter(self, edge_type, ordered=True): def __repr__(self): return '{}({})'.format( - self.__class__.__name__, ', '.join( - (('x_dimension={}'.format(self.x_dimension)), - ('y_dimension={}'.format(self.y_dimension)), - ('n_dofs={}'.format(self.n_dofs)), ('spinless={}'.format( - self.spinless)), ('periodic={}'.format(self.periodic))))) + self.__class__.__name__, + ', '.join( + ( + ('x_dimension={}'.format(self.x_dimension)), + ('y_dimension={}'.format(self.y_dimension)), + ('n_dofs={}'.format(self.n_dofs)), + ('spinless={}'.format(self.spinless)), + ('periodic={}'.format(self.periodic)), + ) + ), + ) # site indexing @@ -254,45 +258,39 @@ def from_site_index(self, site_index): def n_horizontal_neighbor_pairs(self, ordered=True): """Number of horizontally neighboring (unordered) pairs of sites.""" - n_horizontal_edges_per_y = ( - self.x_dimension - (self.x_dimension <= 2 or not self.periodic)) - return (self.y_dimension * n_horizontal_edges_per_y * - (2 if ordered else 1)) + n_horizontal_edges_per_y = self.x_dimension - (self.x_dimension <= 2 or not self.periodic) + return self.y_dimension * n_horizontal_edges_per_y * (2 if ordered else 1) def n_vertical_neighbor_pairs(self, ordered=True): """Number of vertically neighboring (unordered) pairs of sites.""" - n_vertical_edges_per_x = (self.y_dimension - - (self.y_dimension <= 2 or not self.periodic)) - return (self.x_dimension * n_vertical_edges_per_x * - (2 if ordered else 1)) + n_vertical_edges_per_x = self.y_dimension - (self.y_dimension <= 2 or not self.periodic) + return self.x_dimension * n_vertical_edges_per_x * (2 if ordered else 1) def n_neighbor_pairs(self, ordered=True): """Number of neighboring (unordered) pairs of sites.""" - return (self.n_horizontal_neighbor_pairs(ordered) + - self.n_vertical_neighbor_pairs(ordered)) + return self.n_horizontal_neighbor_pairs(ordered) + self.n_vertical_neighbor_pairs(ordered) def neighbors_iter(self, ordered=True): - return itertools.chain(self.horizontal_neighbors_iter(ordered), - self.vertical_neighbors_iter(ordered)) + return itertools.chain( + self.horizontal_neighbors_iter(ordered), self.vertical_neighbors_iter(ordered) + ) def diagonal_neighbors_iter(self, ordered=True): - n_sites_per_y = (self.x_dimension - - (self.x_dimension <= 2 or not self.periodic)) - n_sites_per_x = (self.y_dimension - - (self.y_dimension <= 2 or not self.periodic)) + n_sites_per_y = self.x_dimension - (self.x_dimension <= 2 or not self.periodic) + n_sites_per_x = self.y_dimension - (self.y_dimension <= 2 or not self.periodic) for x in range(n_sites_per_y): for y in range(n_sites_per_x): for dy in (-1, 1): i = self.to_site_index((x, y)) - j = self.to_site_index(((x + 1) % self.x_dimension, - (y + dy) % self.y_dimension)) + j = self.to_site_index( + ((x + 1) % self.x_dimension, (y + dy) % self.y_dimension) + ) yield (i, j) if ordered: yield (j, i) def horizontal_neighbors_iter(self, ordered=True): - n_horizontal_edges_per_y = ( - self.x_dimension - (self.x_dimension <= 2 or not self.periodic)) + n_horizontal_edges_per_y = self.x_dimension - (self.x_dimension <= 2 or not self.periodic) for x in range(n_horizontal_edges_per_y): for y in range(self.y_dimension): i = self.to_site_index((x, y)) @@ -302,8 +300,7 @@ def horizontal_neighbors_iter(self, ordered=True): yield (j, i) def vertical_neighbors_iter(self, ordered=True): - n_vertical_edges_per_x = (self.y_dimension - - (self.y_dimension <= 2 or not self.periodic)) + n_vertical_edges_per_x = self.y_dimension - (self.y_dimension <= 2 or not self.periodic) for y in range(n_vertical_edges_per_x): for x in range(self.x_dimension): i = self.to_site_index((x, y)) @@ -321,13 +318,11 @@ def shape(self): def delta_mag(self, X, Y, by_index=False): """The distance between sites X and Y in each dimension.""" if by_index: - return self.delta_mag(self.from_site_index(X), - self.from_site_index(Y)) + return self.delta_mag(self.from_site_index(X), self.from_site_index(Y)) if self.periodic: return tuple( - min(abs((s * (x - y)) % d) - for s in (-1, 1)) - for d, x, y in zip(self.shape, X, Y)) + min(abs((s * (x - y)) % d) for s in (-1, 1)) for d, x, y in zip(self.shape, X, Y) + ) return tuple(abs(x - xx) for x, xx in zip(X, Y)) def manhattan_distance(self, X, Y, by_index=False): diff --git a/src/openfermion/utils/lattice_test.py b/src/openfermion/utils/lattice_test.py index 2335dff08..89f8b9bf7 100644 --- a/src/openfermion/utils/lattice_test.py +++ b/src/openfermion/utils/lattice_test.py @@ -15,7 +15,7 @@ import random import pytest -from openfermion.utils import (HubbardSquareLattice, SpinPairs, Spin) +from openfermion.utils import HubbardSquareLattice, SpinPairs, Spin def test_spin(): @@ -23,53 +23,57 @@ def test_spin(): assert tuple(lattice.spin_indices) == (Spin.UP, Spin.DOWN) -@pytest.mark.parametrize("x_dimension,y_dimension,n_dofs,spinless,periodic", - itertools.product(random.sample(range(3, 10), 3), - random.sample(range(3, 10), 3), - range(1, 4), (False, True), - (False, True))) -def test_hubbard_square_lattice(x_dimension, y_dimension, n_dofs, spinless, - periodic): - lattice = HubbardSquareLattice(x_dimension, - y_dimension, - n_dofs=n_dofs, - spinless=spinless, - periodic=periodic) +@pytest.mark.parametrize( + "x_dimension,y_dimension,n_dofs,spinless,periodic", + itertools.product( + random.sample(range(3, 10), 3), + random.sample(range(3, 10), 3), + range(1, 4), + (False, True), + (False, True), + ), +) +def test_hubbard_square_lattice(x_dimension, y_dimension, n_dofs, spinless, periodic): + lattice = HubbardSquareLattice( + x_dimension, y_dimension, n_dofs=n_dofs, spinless=spinless, periodic=periodic + ) n_spin_values = 2 - spinless - sites = tuple( - (x, y) - for y, x in itertools.product(range(y_dimension), range(x_dimension))) + sites = tuple((x, y) for y, x in itertools.product(range(y_dimension), range(x_dimension))) site_indices = tuple(lattice.to_site_index(site) for site in sites) - assert (sites == tuple( - lattice.from_site_index(site_index) for site_index in site_indices)) - assert (site_indices == tuple(lattice.site_indices) == tuple( - range(x_dimension * y_dimension))) + assert sites == tuple(lattice.from_site_index(site_index) for site_index in site_indices) + assert site_indices == tuple(lattice.site_indices) == tuple(range(x_dimension * y_dimension)) tuple( - itertools.product(range(x_dimension), range(y_dimension), range(n_dofs), - range(n_spin_values))) + itertools.product( + range(x_dimension), range(y_dimension), range(n_dofs), range(n_spin_values) + ) + ) spin_orbital_linear_indices = tuple( lattice.to_spin_orbital_index(*indices) - for indices in itertools.product(site_indices, range(n_dofs), - range(n_spin_values))) + for indices in itertools.product(site_indices, range(n_dofs), range(n_spin_values)) + ) assert spin_orbital_linear_indices == tuple(range(lattice.n_spin_orbitals)) for i, ii in zip(range(lattice.n_sites), lattice.site_pairs_iter('onsite')): assert ii == (i, i) - n_neighbor_pairs = 2 * ((x_dimension * (y_dimension - (not periodic))) + - ((x_dimension - (not periodic)) * y_dimension)) + n_neighbor_pairs = 2 * ( + (x_dimension * (y_dimension - (not periodic))) + + ((x_dimension - (not periodic)) * y_dimension) + ) neighbor_pairs = tuple(lattice.site_pairs_iter('neighbor')) - assert (2 * len(tuple(lattice.site_pairs_iter('neighbor', False))) == - len(neighbor_pairs) == n_neighbor_pairs) + assert ( + 2 * len(tuple(lattice.site_pairs_iter('neighbor', False))) + == len(neighbor_pairs) + == n_neighbor_pairs + ) for i, j in neighbor_pairs: assert sum(lattice.delta_mag(i, j, True)) == 1 assert lattice.manhattan_distance(i, j, True) == 1 - assert len(tuple(lattice.dof_pairs_iter(False))) == \ - n_dofs * (n_dofs + 1) / 2 + assert len(tuple(lattice.dof_pairs_iter(False))) == n_dofs * (n_dofs + 1) / 2 assert len(tuple(lattice.dof_pairs_iter(True))) == n_dofs * (n_dofs - 1) / 2 spin_pairs_all = tuple(lattice.spin_pairs_iter()) assert len(spin_pairs_all) == n_spin_values**2 @@ -106,8 +110,8 @@ def test_hubbard_square_lattice_dof_validation(n_dofs): def test_hubbard_square_lattice_edge_types(): lattice = HubbardSquareLattice(3, 3) assert sorted(lattice.edge_types) == sorted( - ('onsite', 'neighbor', 'diagonal_neighbor', 'vertical_neighbor', - 'horizontal_neighbor')) + ('onsite', 'neighbor', 'diagonal_neighbor', 'vertical_neighbor', 'horizontal_neighbor') + ) lattice.validate_edge_type('onsite') lattice.validate_edge_type('neighbor') lattice.validate_edge_type('diagonal_neighbor') @@ -124,29 +128,33 @@ def test_hubbard_square_lattice_1xd(d): for shape, periodic in itertools.product(((1, d), (d, 1)), (True, False)): lattice = HubbardSquareLattice(*shape, periodic=periodic) assert lattice.n_sites == d - assert (len(tuple(lattice.neighbors_iter())) == - 2 * len(tuple(lattice.neighbors_iter(False))) == 2 * - (d - (not periodic))) - assert (len(tuple(lattice.diagonal_neighbors_iter())) == len( - tuple(lattice.diagonal_neighbors_iter(False))) == 0) - assert (len(tuple(lattice.site_pairs_iter('diagonal_neighbor'))) == len( - tuple(lattice.diagonal_neighbors_iter(False))) == 0) - - -@pytest.mark.parametrize('x,y', - (random.sample(range(3, 10), 2) for _ in range(3))) + assert ( + len(tuple(lattice.neighbors_iter())) + == 2 * len(tuple(lattice.neighbors_iter(False))) + == 2 * (d - (not periodic)) + ) + assert ( + len(tuple(lattice.diagonal_neighbors_iter())) + == len(tuple(lattice.diagonal_neighbors_iter(False))) + == 0 + ) + assert ( + len(tuple(lattice.site_pairs_iter('diagonal_neighbor'))) + == len(tuple(lattice.diagonal_neighbors_iter(False))) + == 0 + ) + + +@pytest.mark.parametrize('x,y', (random.sample(range(3, 10), 2) for _ in range(3))) def test_hubbard_square_lattice_neighbors(x, y): for periodic in (True, False): lattice = HubbardSquareLattice(x, y, periodic=periodic) n_horizontal_neighbors = 2 * y * (x - (not periodic)) - assert (len(tuple( - lattice.horizontal_neighbors_iter())) == n_horizontal_neighbors) + assert len(tuple(lattice.horizontal_neighbors_iter())) == n_horizontal_neighbors n_vertical_neighbors = 2 * x * (y - (not periodic)) - assert (len(tuple( - lattice.vertical_neighbors_iter())) == n_vertical_neighbors) + assert len(tuple(lattice.vertical_neighbors_iter())) == n_vertical_neighbors n_diagonal_neighbors = 4 * (x - (not periodic)) * (y - (not periodic)) - assert (len(tuple( - lattice.diagonal_neighbors_iter())) == n_diagonal_neighbors) + assert len(tuple(lattice.diagonal_neighbors_iter())) == n_diagonal_neighbors @pytest.mark.parametrize('d', random.sample(range(3, 10), 3)) @@ -155,12 +163,17 @@ def test_hubbard_square_lattice_2xd(d): lattice = HubbardSquareLattice(*shape, periodic=periodic) assert lattice.n_sites == 2 * d n_neighbor_pairs = 2 * (2 * (d - (not periodic)) + d) - assert (len(tuple(lattice.neighbors_iter())) == 2 * - len(tuple(lattice.neighbors_iter(False))) == n_neighbor_pairs) + assert ( + len(tuple(lattice.neighbors_iter())) + == 2 * len(tuple(lattice.neighbors_iter(False))) + == n_neighbor_pairs + ) n_diagonal_neighbor_pairs = 4 * (d - (not periodic)) - assert (len(tuple(lattice.diagonal_neighbors_iter())) == - 2 * len(tuple(lattice.diagonal_neighbors_iter(False))) == - n_diagonal_neighbor_pairs) + assert ( + len(tuple(lattice.diagonal_neighbors_iter())) + == 2 * len(tuple(lattice.diagonal_neighbors_iter(False))) + == n_diagonal_neighbor_pairs + ) def test_hubbard_square_lattice_2x2(): @@ -173,13 +186,19 @@ def test_hubbard_square_lattice_2x2(): assert len(tuple(lattice.diagonal_neighbors_iter())) == 4 -@pytest.mark.parametrize('kwargs', [{ - 'x_dimension': random.randrange(1, 10), - 'y_dimension': random.randrange(1, 10), - 'n_dofs': random.randrange(1, 10), - 'spinless': random.choice((True, False)), - 'periodic': random.choice((True, False)) -} for _ in range(5)]) +@pytest.mark.parametrize( + 'kwargs', + [ + { + 'x_dimension': random.randrange(1, 10), + 'y_dimension': random.randrange(1, 10), + 'n_dofs': random.randrange(1, 10), + 'spinless': random.choice((True, False)), + 'periodic': random.choice((True, False)), + } + for _ in range(5) + ], +) def test_hubbard_square_lattice_repr(kwargs): lattice = HubbardSquareLattice(**kwargs) params = ('x_dimension', 'y_dimension', 'n_dofs', 'spinless', 'periodic') @@ -194,26 +213,31 @@ def test_spin_pairs_iter(): with pytest.raises(ValueError): spinful_lattice.spin_pairs_iter(10) - assert (tuple(spinful_lattice.spin_pairs_iter(SpinPairs.ALL, - True)) == ((0, 0), (0, 1), - (1, 0), (1, 1))) - assert (tuple(spinful_lattice.spin_pairs_iter(SpinPairs.ALL, - False)) == ((0, 0), (0, 1), - (1, 1))) - assert (tuple(spinful_lattice.spin_pairs_iter(SpinPairs.SAME, True)) == - tuple(spinful_lattice.spin_pairs_iter(SpinPairs.SAME, False)) == - ((0, 0), (1, 1))) - assert (tuple(spinful_lattice.spin_pairs_iter(SpinPairs.DIFF, - True)) == ((0, 1), (1, 0))) - assert (tuple(spinful_lattice.spin_pairs_iter(SpinPairs.DIFF, - False)) == ((0, 1),)) + assert tuple(spinful_lattice.spin_pairs_iter(SpinPairs.ALL, True)) == ( + (0, 0), + (0, 1), + (1, 0), + (1, 1), + ) + assert tuple(spinful_lattice.spin_pairs_iter(SpinPairs.ALL, False)) == ((0, 0), (0, 1), (1, 1)) + assert ( + tuple(spinful_lattice.spin_pairs_iter(SpinPairs.SAME, True)) + == tuple(spinful_lattice.spin_pairs_iter(SpinPairs.SAME, False)) + == ((0, 0), (1, 1)) + ) + assert tuple(spinful_lattice.spin_pairs_iter(SpinPairs.DIFF, True)) == ((0, 1), (1, 0)) + assert tuple(spinful_lattice.spin_pairs_iter(SpinPairs.DIFF, False)) == ((0, 1),) spinless_lattice = HubbardSquareLattice(3, 3, spinless=True) - assert (tuple(spinless_lattice.spin_pairs_iter(SpinPairs.ALL, True)) == - tuple(spinless_lattice.spin_pairs_iter(SpinPairs.ALL, False)) == - tuple(spinless_lattice.spin_pairs_iter(SpinPairs.SAME, True)) == - tuple(spinless_lattice.spin_pairs_iter(SpinPairs.SAME, False)) == - ((0, 0),)) - assert (tuple(spinless_lattice.spin_pairs_iter( - SpinPairs.DIFF, True)) == tuple( - spinless_lattice.spin_pairs_iter(SpinPairs.DIFF, False)) == tuple()) + assert ( + tuple(spinless_lattice.spin_pairs_iter(SpinPairs.ALL, True)) + == tuple(spinless_lattice.spin_pairs_iter(SpinPairs.ALL, False)) + == tuple(spinless_lattice.spin_pairs_iter(SpinPairs.SAME, True)) + == tuple(spinless_lattice.spin_pairs_iter(SpinPairs.SAME, False)) + == ((0, 0),) + ) + assert ( + tuple(spinless_lattice.spin_pairs_iter(SpinPairs.DIFF, True)) + == tuple(spinless_lattice.spin_pairs_iter(SpinPairs.DIFF, False)) + == tuple() + ) diff --git a/src/openfermion/utils/operator_utils.py b/src/openfermion/utils/operator_utils.py index 87543940d..82d2e345e 100644 --- a/src/openfermion/utils/operator_utils.py +++ b/src/openfermion/utils/operator_utils.py @@ -19,15 +19,21 @@ from scipy.sparse import spmatrix from openfermion.config import DATA_DIRECTORY, EQ_TOLERANCE -from openfermion.ops.operators import (BosonOperator, FermionOperator, - MajoranaOperator, QuadOperator, - QubitOperator, IsingOperator) -from openfermion.ops.representations import (PolynomialTensor, - DiagonalCoulombHamiltonian, - InteractionOperator, - InteractionRDM) -from openfermion.transforms.opconversions.term_reordering\ - import normal_ordered +from openfermion.ops.operators import ( + BosonOperator, + FermionOperator, + MajoranaOperator, + QuadOperator, + QubitOperator, + IsingOperator, +) +from openfermion.ops.representations import ( + PolynomialTensor, + DiagonalCoulombHamiltonian, + InteractionOperator, + InteractionRDM, +) +from openfermion.transforms.opconversions.term_reordering import normal_ordered class OperatorUtilsError(Exception): @@ -44,21 +50,20 @@ def hermitian_conjugated(operator): if isinstance(operator, FermionOperator): conjugate_operator = FermionOperator() for term, coefficient in operator.terms.items(): - conjugate_term = tuple([(tensor_factor, 1 - action) - for (tensor_factor, - action) in reversed(term)]) + conjugate_term = tuple( + [(tensor_factor, 1 - action) for (tensor_factor, action) in reversed(term)] + ) conjugate_operator.terms[conjugate_term] = coefficient.conjugate() # Handle BosonOperator elif isinstance(operator, BosonOperator): conjugate_operator = BosonOperator() for term, coefficient in operator.terms.items(): - conjugate_term = tuple([(tensor_factor, 1 - action) - for (tensor_factor, - action) in reversed(term)]) - # take into account that different indices commute conjugate_term = tuple( - sorted(conjugate_term, key=lambda factor: factor[0])) + [(tensor_factor, 1 - action) for (tensor_factor, action) in reversed(term)] + ) + # take into account that different indices commute + conjugate_term = tuple(sorted(conjugate_term, key=lambda factor: factor[0])) conjugate_operator.terms[conjugate_term] = coefficient.conjugate() # Handle QubitOperator @@ -73,20 +78,17 @@ def hermitian_conjugated(operator): for term, coefficient in operator.terms.items(): conjugate_term = reversed(term) # take into account that different indices commute - conjugate_term = tuple( - sorted(conjugate_term, key=lambda factor: factor[0])) + conjugate_term = tuple(sorted(conjugate_term, key=lambda factor: factor[0])) conjugate_operator.terms[conjugate_term] = coefficient.conjugate() # Handle InteractionOperator elif isinstance(operator, InteractionOperator): conjugate_constant = operator.constant.conjugate() - conjugate_one_body_tensor = hermitian_conjugated( - operator.one_body_tensor) - conjugate_two_body_tensor = hermitian_conjugated( - operator.two_body_tensor) - conjugate_operator = type(operator)(conjugate_constant, - conjugate_one_body_tensor, - conjugate_two_body_tensor) + conjugate_one_body_tensor = hermitian_conjugated(operator.one_body_tensor) + conjugate_two_body_tensor = hermitian_conjugated(operator.two_body_tensor) + conjugate_operator = type(operator)( + conjugate_constant, conjugate_one_body_tensor, conjugate_two_body_tensor + ) # Handle sparse matrix elif isinstance(operator, spmatrix): @@ -98,8 +100,10 @@ def hermitian_conjugated(operator): # Unsupported type else: - raise TypeError('Taking the hermitian conjugate of a {} is not ' - 'supported.'.format(type(operator).__name__)) + raise TypeError( + 'Taking the hermitian conjugate of a {} is not ' + 'supported.'.format(type(operator).__name__) + ) return conjugate_operator @@ -107,10 +111,8 @@ def hermitian_conjugated(operator): def is_hermitian(operator): """Test if operator is Hermitian.""" # Handle FermionOperator, BosonOperator, and InteractionOperator - if isinstance(operator, - (FermionOperator, BosonOperator, InteractionOperator)): - return (normal_ordered(operator) == normal_ordered( - hermitian_conjugated(operator))) + if isinstance(operator, (FermionOperator, BosonOperator, InteractionOperator)): + return normal_ordered(operator) == normal_ordered(hermitian_conjugated(operator)) # Handle QubitOperator and QuadOperator if isinstance(operator, (QubitOperator, QuadOperator)): @@ -119,7 +121,7 @@ def is_hermitian(operator): # Handle sparse matrix elif isinstance(operator, spmatrix): difference = operator - hermitian_conjugated(operator) - discrepancy = 0. + discrepancy = 0.0 if difference.nnz: discrepancy = max(abs(difference.data)) return discrepancy < EQ_TOLERANCE @@ -132,8 +134,10 @@ def is_hermitian(operator): # Unsupported type else: - raise TypeError('Checking whether a {} is hermitian is not ' - 'supported.'.format(type(operator).__name__)) + raise TypeError( + 'Checking whether a {} is hermitian is not ' + 'supported.'.format(type(operator).__name__) + ) def count_qubits(operator): @@ -208,14 +212,11 @@ def is_identity(operator): Raises: TypeError: Operator of invalid type. """ - if isinstance( - operator, - (QubitOperator, FermionOperator, BosonOperator, QuadOperator)): + if isinstance(operator, (QubitOperator, FermionOperator, BosonOperator, QuadOperator)): return list(operator.terms) == [()] raise TypeError('Operator of invalid type.') - def get_file_path(file_name, data_directory): """Compute file_path for the file that stores operator. @@ -305,11 +306,9 @@ def load_operator(file_name=None, data_directory=None, plain_text=False): return operator -def save_operator(operator, - file_name=None, - data_directory=None, - allow_overwrite=False, - plain_text=False): +def save_operator( + operator, file_name=None, data_directory=None, allow_overwrite=False, plain_text=False +): """Save FermionOperator or QubitOperator to file. Args: @@ -342,8 +341,9 @@ def save_operator(operator, elif isinstance(operator, QuadOperator): operator_type = "QuadOperator" elif isinstance(operator, (InteractionOperator, InteractionRDM)): - raise NotImplementedError('Not yet implemented for ' - 'InteractionOperator or InteractionRDM.') + raise NotImplementedError( + 'Not yet implemented for ' 'InteractionOperator or InteractionRDM.' + ) else: raise TypeError('Operator of invalid type.') @@ -357,6 +357,4 @@ def save_operator(operator, else: tm = operator.terms with open(file_path, 'wb') as f: - marshal.dump( - (operator_type, dict(zip(tm.keys(), map(complex, - tm.values())))), f) + marshal.dump((operator_type, dict(zip(tm.keys(), map(complex, tm.values())))), f) diff --git a/src/openfermion/utils/operator_utils_test.py b/src/openfermion/utils/operator_utils_test.py index c651261e4..95b7d32cb 100644 --- a/src/openfermion/utils/operator_utils_test.py +++ b/src/openfermion/utils/operator_utils_test.py @@ -25,31 +25,38 @@ from openfermion.config import DATA_DIRECTORY from openfermion.hamiltonians import fermi_hubbard -from openfermion.ops.operators import (FermionOperator, MajoranaOperator, - BosonOperator, QubitOperator, - QuadOperator, IsingOperator) +from openfermion.ops.operators import ( + FermionOperator, + MajoranaOperator, + BosonOperator, + QubitOperator, + QuadOperator, + IsingOperator, +) from openfermion.ops.representations import InteractionOperator -from openfermion.transforms.opconversions import (jordan_wigner, bravyi_kitaev) +from openfermion.transforms.opconversions import jordan_wigner, bravyi_kitaev from openfermion.transforms.repconversions import get_interaction_operator from openfermion.testing.testing_utils import random_interaction_operator -from openfermion.utils.operator_utils import (count_qubits, - hermitian_conjugated, is_identity, - save_operator, OperatorUtilsError, - is_hermitian, load_operator, - get_file_path) +from openfermion.utils.operator_utils import ( + count_qubits, + hermitian_conjugated, + is_identity, + save_operator, + OperatorUtilsError, + is_hermitian, + load_operator, + get_file_path, +) class OperatorUtilsTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 self.majorana_operator = MajoranaOperator((1, 4, 9)) self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) - self.fermion_operator = self.fermion_term + hermitian_conjugated( - self.fermion_term) + self.fermion_operator = self.fermion_term + hermitian_conjugated(self.fermion_term) self.qubit_operator = jordan_wigner(self.fermion_operator) - self.interaction_operator = get_interaction_operator( - self.fermion_operator) + self.interaction_operator = get_interaction_operator(self.fermion_operator) self.ising_operator = IsingOperator("[Z0] + [Z1] + [Z2] + [Z3] + [Z4]") def test_n_qubits_majorana_operator(self): @@ -78,25 +85,25 @@ def test_is_identity_unit_fermionoperator(self): self.assertTrue(is_identity(FermionOperator(()))) def test_is_identity_double_of_unit_fermionoperator(self): - self.assertTrue(is_identity(2. * FermionOperator(()))) + self.assertTrue(is_identity(2.0 * FermionOperator(()))) def test_is_identity_unit_bosonoperator(self): self.assertTrue(is_identity(BosonOperator(()))) def test_is_identity_double_of_unit_bosonoperator(self): - self.assertTrue(is_identity(2. * BosonOperator(()))) + self.assertTrue(is_identity(2.0 * BosonOperator(()))) def test_is_identity_unit_quadoperator(self): self.assertTrue(is_identity(QuadOperator(()))) def test_is_identity_double_of_unit_quadoperator(self): - self.assertTrue(is_identity(2. * QuadOperator(()))) + self.assertTrue(is_identity(2.0 * QuadOperator(()))) def test_is_identity_unit_qubitoperator(self): self.assertTrue(is_identity(QubitOperator(()))) def test_is_identity_double_of_unit_qubitoperator(self): - self.assertTrue(is_identity(QubitOperator((), 2.))) + self.assertTrue(is_identity(QubitOperator((), 2.0))) def test_not_is_identity_single_term_fermionoperator(self): self.assertFalse(is_identity(FermionOperator('1^'))) @@ -125,7 +132,6 @@ def test_is_identity_bad_type(self): class HermitianConjugatedTest(unittest.TestCase): - def test_hermitian_conjugated_qubit_op(self): """Test conjugating QubitOperators.""" op = QubitOperator() @@ -133,32 +139,34 @@ def test_hermitian_conjugated_qubit_op(self): correct_op = op self.assertEqual(op_hc, correct_op) - op = QubitOperator('X0 Y1', 2.) + op = QubitOperator('X0 Y1', 2.0) op_hc = hermitian_conjugated(op) correct_op = op self.assertEqual(op_hc, correct_op) - op = QubitOperator('X0 Y1', 2.j) + op = QubitOperator('X0 Y1', 2.0j) op_hc = hermitian_conjugated(op) - correct_op = QubitOperator('X0 Y1', -2.j) + correct_op = QubitOperator('X0 Y1', -2.0j) self.assertEqual(op_hc, correct_op) - op = QubitOperator('X0 Y1', 2.) + QubitOperator('Z4 X5 Y7', 3.j) + op = QubitOperator('X0 Y1', 2.0) + QubitOperator('Z4 X5 Y7', 3.0j) op_hc = hermitian_conjugated(op) - correct_op = (QubitOperator('X0 Y1', 2.) + - QubitOperator('Z4 X5 Y7', -3.j)) + correct_op = QubitOperator('X0 Y1', 2.0) + QubitOperator('Z4 X5 Y7', -3.0j) self.assertEqual(op_hc, correct_op) def test_hermitian_conjugated_qubit_op_consistency(self): """Some consistency checks for conjugating QubitOperators.""" - ferm_op = (FermionOperator('1^ 2') + FermionOperator('2 3 4') + - FermionOperator('2^ 7 9 11^')) + ferm_op = FermionOperator('1^ 2') + FermionOperator('2 3 4') + FermionOperator('2^ 7 9 11^') # Check that hermitian conjugation commutes with transforms - self.assertEqual(jordan_wigner(hermitian_conjugated(ferm_op)), - hermitian_conjugated(jordan_wigner(ferm_op))) - self.assertEqual(bravyi_kitaev(hermitian_conjugated(ferm_op)), - hermitian_conjugated(bravyi_kitaev(ferm_op))) + self.assertEqual( + jordan_wigner(hermitian_conjugated(ferm_op)), + hermitian_conjugated(jordan_wigner(ferm_op)), + ) + self.assertEqual( + bravyi_kitaev(hermitian_conjugated(ferm_op)), + hermitian_conjugated(bravyi_kitaev(ferm_op)), + ) def test_hermitian_conjugated_quad_op(self): """Test conjugating QuadOperator.""" @@ -167,26 +175,24 @@ def test_hermitian_conjugated_quad_op(self): correct_op = op self.assertTrue(op_hc == correct_op) - op = QuadOperator('q0 p1', 2.) + op = QuadOperator('q0 p1', 2.0) op_hc = hermitian_conjugated(op) correct_op = op self.assertTrue(op_hc == correct_op) - op = QuadOperator('q0 p1', 2.j) + op = QuadOperator('q0 p1', 2.0j) op_hc = hermitian_conjugated(op) - correct_op = QuadOperator('q0 p1', -2.j) + correct_op = QuadOperator('q0 p1', -2.0j) self.assertTrue(op_hc == correct_op) - op = QuadOperator('q0 p1', 2.) + QuadOperator('q4 q5 p7', 3.j) + op = QuadOperator('q0 p1', 2.0) + QuadOperator('q4 q5 p7', 3.0j) op_hc = hermitian_conjugated(op) - correct_op = (QuadOperator('q0 p1', 2.) + - QuadOperator('q4 q5 p7', -3.j)) + correct_op = QuadOperator('q0 p1', 2.0) + QuadOperator('q4 q5 p7', -3.0j) self.assertTrue(op_hc == correct_op) - op = QuadOperator('q0 p0 q1', 2.) + QuadOperator('q1 p1 p2', 3.j) + op = QuadOperator('q0 p0 q1', 2.0) + QuadOperator('q1 p1 p2', 3.0j) op_hc = hermitian_conjugated(op) - correct_op = (QuadOperator('p0 q0 q1', 2.) + - QuadOperator('p1 q1 p2', -3.j)) + correct_op = QuadOperator('p0 q0 q1', 2.0) + QuadOperator('p1 q1 p2', -3.0j) self.assertTrue(op_hc == correct_op) def test_hermitian_conjugate_empty(self): @@ -232,17 +238,33 @@ def test_hermitian_conjugate_notordered(self): self.assertEqual(op, op_hc) def test_hermitian_conjugate_semihermitian(self): - op = (FermionOperator() + 2j * FermionOperator('1^ 3') + - FermionOperator('3^ 1') * -2j + FermionOperator('2^ 2', 0.1j)) - op_hc = (FermionOperator() + FermionOperator('1^ 3', 2j) + - FermionOperator('3^ 1', -2j) + FermionOperator('2^ 2', -0.1j)) + op = ( + FermionOperator() + + 2j * FermionOperator('1^ 3') + + FermionOperator('3^ 1') * -2j + + FermionOperator('2^ 2', 0.1j) + ) + op_hc = ( + FermionOperator() + + FermionOperator('1^ 3', 2j) + + FermionOperator('3^ 1', -2j) + + FermionOperator('2^ 2', -0.1j) + ) op = hermitian_conjugated(op) self.assertEqual(op, op_hc) - op = (BosonOperator() + 2j * BosonOperator('1^ 3') + - BosonOperator('3^ 1') * -2j + BosonOperator('2^ 2', 0.1j)) - op_hc = (BosonOperator() + BosonOperator('1^ 3', 2j) + - BosonOperator('3^ 1', -2j) + BosonOperator('2^ 2', -0.1j)) + op = ( + BosonOperator() + + 2j * BosonOperator('1^ 3') + + BosonOperator('3^ 1') * -2j + + BosonOperator('2^ 2', 0.1j) + ) + op_hc = ( + BosonOperator() + + BosonOperator('1^ 3', 2j) + + BosonOperator('3^ 1', -2j) + + BosonOperator('2^ 2', -0.1j) + ) op = hermitian_conjugated(op) self.assertEqual(op, op_hc) @@ -280,16 +302,32 @@ def test_hermitian_conjugated_multiterm(self): self.assertEqual(op_hc, hermitian_conjugated(op)) def test_hermitian_conjugated_semihermitian(self): - op = (FermionOperator() + 2j * FermionOperator('1^ 3') + - FermionOperator('3^ 1') * -2j + FermionOperator('2^ 2', 0.1j)) - op_hc = (FermionOperator() + FermionOperator('1^ 3', 2j) + - FermionOperator('3^ 1', -2j) + FermionOperator('2^ 2', -0.1j)) + op = ( + FermionOperator() + + 2j * FermionOperator('1^ 3') + + FermionOperator('3^ 1') * -2j + + FermionOperator('2^ 2', 0.1j) + ) + op_hc = ( + FermionOperator() + + FermionOperator('1^ 3', 2j) + + FermionOperator('3^ 1', -2j) + + FermionOperator('2^ 2', -0.1j) + ) self.assertEqual(op_hc, hermitian_conjugated(op)) - op = (BosonOperator() + 2j * BosonOperator('1^ 3') + - BosonOperator('3^ 1') * -2j + BosonOperator('2^ 2', 0.1j)) - op_hc = (BosonOperator() + BosonOperator('1^ 3', 2j) + - BosonOperator('3^ 1', -2j) + BosonOperator('2^ 2', -0.1j)) + op = ( + BosonOperator() + + 2j * BosonOperator('1^ 3') + + BosonOperator('3^ 1') * -2j + + BosonOperator('2^ 2', 0.1j) + ) + op_hc = ( + BosonOperator() + + BosonOperator('1^ 3', 2j) + + BosonOperator('3^ 1', -2j) + + BosonOperator('2^ 2', -0.1j) + ) self.assertEqual(op_hc, hermitian_conjugated(op)) def test_hermitian_conjugated_interaction_operator(self): @@ -298,8 +336,7 @@ def test_hermitian_conjugated_interaction_operator(self): qubit_operator = jordan_wigner(operator) conjugate_operator = hermitian_conjugated(operator) conjugate_qubit_operator = jordan_wigner(conjugate_operator) - assert hermitian_conjugated(qubit_operator) == \ - conjugate_qubit_operator + assert hermitian_conjugated(qubit_operator) == conjugate_qubit_operator def test_exceptions(self): with self.assertRaises(TypeError): @@ -310,7 +347,6 @@ def test_exceptions(self): class IsHermitianTest(unittest.TestCase): - def test_fermion_operator_zero(self): op = FermionOperator() self.assertTrue(is_hermitian(op)) @@ -328,7 +364,7 @@ def test_fermion_operator_hermitian(self): op += FermionOperator('3^ 2 1^ 0') self.assertTrue(is_hermitian(op)) - op = fermi_hubbard(2, 2, 1., 1.) + op = fermi_hubbard(2, 2, 1.0, 1.0) self.assertTrue(is_hermitian(op)) def test_boson_operator_zero(self): @@ -381,12 +417,12 @@ def test_qubit_operator_identity(self): self.assertTrue(is_hermitian(op)) def test_qubit_operator_nonhermitian(self): - op = QubitOperator('X0 Y2 Z5', 1. + 2.j) + op = QubitOperator('X0 Y2 Z5', 1.0 + 2.0j) self.assertFalse(is_hermitian(op)) def test_qubit_operator_hermitian(self): - op = QubitOperator('X0 Y2 Z5', 1. + 2.j) - op += QubitOperator('X0 Y2 Z5', 1. - 2.j) + op = QubitOperator('X0 Y2 Z5', 1.0 + 2.0j) + op += QubitOperator('X0 Y2 Z5', 1.0 - 2.0j) self.assertTrue(is_hermitian(op)) def test_sparse_matrix_and_numpy_array_zero(self): @@ -409,7 +445,7 @@ def test_sparse_matrix_and_numpy_array_nonhermitian(self): def test_sparse_matrix_and_numpy_array_hermitian(self): op = numpy.arange(16, dtype=complex).reshape((4, 4)) - op += 1.j * op + op += 1.0j * op op += op.T.conj() self.assertTrue(is_hermitian(op)) op = csc_matrix(op) @@ -421,25 +457,20 @@ def test_exceptions(self): class SaveLoadOperatorTest(unittest.TestCase): - def setUp(self): self.n_qubits = 5 self.fermion_term = FermionOperator('1^ 2^ 3 4', -3.17) - self.fermion_operator = self.fermion_term + hermitian_conjugated( - self.fermion_term) + self.fermion_operator = self.fermion_term + hermitian_conjugated(self.fermion_term) self.boson_term = BosonOperator('1^ 2^ 3 4', -3.17) - self.boson_operator = self.boson_term + hermitian_conjugated( - self.boson_term) + self.boson_operator = self.boson_term + hermitian_conjugated(self.boson_term) self.quad_term = QuadOperator('q0 p0 q1 p0 p0', -3.17) - self.quad_operator = self.quad_term + hermitian_conjugated( - self.quad_term) + self.quad_operator = self.quad_term + hermitian_conjugated(self.quad_term) self.qubit_operator = jordan_wigner(self.fermion_operator) self.file_name = "test_file" self.bad_operator_filename = 'bad_file.data' bad_op = "A:\nB" - with open(os.path.join(DATA_DIRECTORY, self.bad_operator_filename), - 'w') as fid: + with open(os.path.join(DATA_DIRECTORY, self.bad_operator_filename), 'w') as fid: fid.write(bad_op) def tearDown(self): @@ -463,10 +494,11 @@ def test_raises_error_sympy(self): def test_save_and_load_fermion_operators(self): save_operator(self.fermion_operator, self.file_name) loaded_fermion_operator = load_operator(self.file_name) - self.assertEqual(self.fermion_operator, - loaded_fermion_operator, - msg=str(self.fermion_operator - - loaded_fermion_operator)) + self.assertEqual( + self.fermion_operator, + loaded_fermion_operator, + msg=str(self.fermion_operator - loaded_fermion_operator), + ) def test_save_and_load_fermion_operators_readably(self): save_operator(self.fermion_operator, self.file_name, plain_text=True) @@ -476,9 +508,11 @@ def test_save_and_load_fermion_operators_readably(self): def test_save_and_load_boson_operators(self): save_operator(self.boson_operator, self.file_name) loaded_boson_operator = load_operator(self.file_name) - self.assertEqual(self.boson_operator.terms, - loaded_boson_operator.terms, - msg=str(self.boson_operator - loaded_boson_operator)) + self.assertEqual( + self.boson_operator.terms, + loaded_boson_operator.terms, + msg=str(self.boson_operator - loaded_boson_operator), + ) def test_save_and_load_boson_operators_readably(self): save_operator(self.boson_operator, self.file_name, plain_text=True) @@ -514,10 +548,9 @@ def test_save_readably(self): file_path = os.path.join(DATA_DIRECTORY, self.file_name + '.data') with open(file_path, "r") as f: self.assertEqual( - f.read(), "\n".join([ - "FermionOperator:", "-3.17 [1^ 2^ 3 4] +", - "-3.17 [4^ 3^ 2 1]" - ])) + f.read(), + "\n".join(["FermionOperator:", "-3.17 [1^ 2^ 3 4] +", "-3.17 [4^ 3^ 2 1]"]), + ) def test_save_no_filename_operator_utils_error(self): with self.assertRaises(OperatorUtilsError): @@ -529,8 +562,7 @@ def test_basic_save(self): def test_save_interaction_operator_not_implemented(self): constant = 100.0 one_body = numpy.zeros((self.n_qubits, self.n_qubits), float) - two_body = numpy.zeros( - (self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) + two_body = numpy.zeros((self.n_qubits, self.n_qubits, self.n_qubits, self.n_qubits), float) one_body[1, 1] = 10.0 two_body[1, 2, 3, 4] = 12.0 interaction_operator = InteractionOperator(constant, one_body, two_body) @@ -545,15 +577,11 @@ def test_save_on_top_of_existing_operator_utils_error(self): def test_save_on_top_of_existing_operator_error_with_explicit_flag(self): save_operator(self.fermion_operator, self.file_name) with self.assertRaises(OperatorUtilsError): - save_operator(self.fermion_operator, - self.file_name, - allow_overwrite=False) + save_operator(self.fermion_operator, self.file_name, allow_overwrite=False) def test_overwrite_flag_save_on_top_of_existing_operator(self): save_operator(self.fermion_operator, self.file_name) - save_operator(self.fermion_operator, - self.file_name, - allow_overwrite=True) + save_operator(self.fermion_operator, self.file_name, allow_overwrite=True) fermion_operator = load_operator(self.file_name) self.assertEqual(fermion_operator, self.fermion_operator) @@ -568,7 +596,6 @@ def test_save_bad_type(self): class GetFileDirTest(unittest.TestCase): - def setUp(self): self.filename = 'foo' self.datadirname = 'data' @@ -576,9 +603,7 @@ def setUp(self): def test_file_load(self): """Test if file name is acquired correctly""" filepath = get_file_path(self.filename, None) - self.assertEqual(filepath, - DATA_DIRECTORY + '/' + self.filename + '.data') + self.assertEqual(filepath, DATA_DIRECTORY + '/' + self.filename + '.data') filepath = get_file_path(self.filename, self.datadirname) - self.assertEqual(filepath, - self.datadirname + '/' + self.filename + '.data') \ No newline at end of file + self.assertEqual(filepath, self.datadirname + '/' + self.filename + '.data') diff --git a/src/openfermion/utils/rdm_mapping_functions.py b/src/openfermion/utils/rdm_mapping_functions.py index 435e7c136..63d9100a3 100644 --- a/src/openfermion/utils/rdm_mapping_functions.py +++ b/src/openfermion/utils/rdm_mapping_functions.py @@ -52,12 +52,11 @@ def map_two_pdm_to_two_hole_dm(tpdm, opdm): ldim = opdm.shape[0] tqdm = numpy.zeros_like(tpdm) for p, q, r, s in product(range(ldim), repeat=4): - term1 = (opdm[p, s] * kronecker_delta(q, r) + - opdm[q, r] * kronecker_delta(p, s)) - term2 = -1 * (opdm[q, s] * kronecker_delta(p, r) + - opdm[p, r] * kronecker_delta(q, s)) - term3 = (kronecker_delta(q, s) * kronecker_delta(p, r) - - kronecker_delta(p, s) * kronecker_delta(q, r)) + term1 = opdm[p, s] * kronecker_delta(q, r) + opdm[q, r] * kronecker_delta(p, s) + term2 = -1 * (opdm[q, s] * kronecker_delta(p, r) + opdm[p, r] * kronecker_delta(q, s)) + term3 = kronecker_delta(q, s) * kronecker_delta(p, r) - kronecker_delta( + p, s + ) * kronecker_delta(q, r) tqdm[s, r, q, p] = tpdm[p, q, r, s] - term1 - term2 - term3 return tqdm @@ -81,12 +80,11 @@ def map_two_hole_dm_to_two_pdm(tqdm, opdm): ldim = opdm.shape[0] tpdm = numpy.zeros_like(tqdm) for p, q, r, s in product(range(ldim), repeat=4): - term1 = (opdm[p, s] * kronecker_delta(q, r) + - opdm[q, r] * kronecker_delta(p, s)) - term2 = -1 * (opdm[q, s] * kronecker_delta(p, r) + - opdm[p, r] * kronecker_delta(q, s)) - term3 = (kronecker_delta(q, s) * kronecker_delta(p, r) - - kronecker_delta(p, s) * kronecker_delta(q, r)) + term1 = opdm[p, s] * kronecker_delta(q, r) + opdm[q, r] * kronecker_delta(p, s) + term2 = -1 * (opdm[q, s] * kronecker_delta(p, r) + opdm[p, r] * kronecker_delta(q, s)) + term3 = kronecker_delta(q, s) * kronecker_delta(p, r) - kronecker_delta( + p, s + ) * kronecker_delta(q, r) tpdm[p, q, r, s] = tqdm[r, s, p, q] + term1 + term2 + term3 return tpdm @@ -160,8 +158,7 @@ def map_two_pdm_to_particle_hole_dm(tpdm, opdm): ldim = opdm.shape[0] phdm = numpy.zeros_like(tpdm) for p, q, r, s in product(range(ldim), repeat=4): - phdm[p, r, q, s] = (opdm[p, s] * kronecker_delta(q, r) - - tpdm[p, q, r, s]) + phdm[p, r, q, s] = opdm[p, s] * kronecker_delta(q, r) - tpdm[p, q, r, s] return phdm @@ -184,8 +181,7 @@ def map_particle_hole_dm_to_two_pdm(phdm, opdm): ldim = opdm.shape[0] tpdm = numpy.zeros_like(phdm) for p, q, r, s in product(range(ldim), repeat=4): - tpdm[p, q, r, s] = (opdm[p, s] * kronecker_delta(q, r) - - phdm[p, r, q, s]) + tpdm[p, q, r, s] = opdm[p, s] * kronecker_delta(q, r) - phdm[p, r, q, s] return tpdm @@ -205,5 +201,4 @@ def map_particle_hole_dm_to_one_pdm(phdm, num_particles, num_basis_functions): Returns: opdm (numpy.ndarray): the 1-RDM transformed from a 1-RDM. """ - return numpy.einsum('prrq', - phdm) / (num_basis_functions - num_particles + 1) + return numpy.einsum('prrq', phdm) / (num_basis_functions - num_particles + 1) diff --git a/src/openfermion/utils/rdm_mapping_functions_test.py b/src/openfermion/utils/rdm_mapping_functions_test.py index f729700d5..f64ebac52 100644 --- a/src/openfermion/utils/rdm_mapping_functions_test.py +++ b/src/openfermion/utils/rdm_mapping_functions_test.py @@ -18,44 +18,47 @@ from openfermion.config import DATA_DIRECTORY, THIS_DIRECTORY from openfermion.chem import MolecularData from openfermion.utils.rdm_mapping_functions import ( - kronecker_delta, map_two_pdm_to_two_hole_dm, map_two_pdm_to_one_pdm, - map_one_pdm_to_one_hole_dm, map_one_hole_dm_to_one_pdm, - map_two_pdm_to_particle_hole_dm, map_two_hole_dm_to_two_pdm, - map_two_hole_dm_to_one_hole_dm, map_particle_hole_dm_to_one_pdm, - map_particle_hole_dm_to_two_pdm) + kronecker_delta, + map_two_pdm_to_two_hole_dm, + map_two_pdm_to_one_pdm, + map_one_pdm_to_one_hole_dm, + map_one_hole_dm_to_one_pdm, + map_two_pdm_to_particle_hole_dm, + map_two_hole_dm_to_two_pdm, + map_two_hole_dm_to_one_hole_dm, + map_particle_hole_dm_to_one_pdm, + map_particle_hole_dm_to_two_pdm, +) class RDMMappingTest(unittest.TestCase): - def setUp(self): # load files and marginals from testing folder - tqdm_h2_sto3g = os.path.join(THIS_DIRECTORY, - 'testing/tqdm_H2_sto-3g_singlet_1.4.hdf5') + tqdm_h2_sto3g = os.path.join(THIS_DIRECTORY, 'testing/tqdm_H2_sto-3g_singlet_1.4.hdf5') with h5py.File(tqdm_h2_sto3g, 'r') as fid: self.tqdm_h2_sto3g = fid['tqdm'][...] - phdm_h2_sto3g = os.path.join(THIS_DIRECTORY, - 'testing/phdm_H2_sto-3g_singlet_1.4.hdf5') + phdm_h2_sto3g = os.path.join(THIS_DIRECTORY, 'testing/phdm_H2_sto-3g_singlet_1.4.hdf5') with h5py.File(phdm_h2_sto3g, 'r') as fid: self.phdm_h2_sto3g = fid['phdm'][...] - tqdm_h2_6_31g = os.path.join(THIS_DIRECTORY, - 'testing/tqdm_H2_6-31g_singlet_0.75.hdf5') + tqdm_h2_6_31g = os.path.join(THIS_DIRECTORY, 'testing/tqdm_H2_6-31g_singlet_0.75.hdf5') with h5py.File(tqdm_h2_6_31g, 'r') as fid: self.tqdm_h2_6_31g = fid['tqdm'][...] - phdm_h2_6_31g = os.path.join(THIS_DIRECTORY, - 'testing/phdm_H2_6-31g_singlet_0.75.hdf5') + phdm_h2_6_31g = os.path.join(THIS_DIRECTORY, 'testing/phdm_H2_6-31g_singlet_0.75.hdf5') with h5py.File(phdm_h2_6_31g, 'r') as fid: self.phdm_h2_6_31g = fid['phdm'][...] tqdm_lih_sto3g = os.path.join( - THIS_DIRECTORY, 'testing/tqdm_H1-Li1_sto-3g_singlet_1.45.hdf5') + THIS_DIRECTORY, 'testing/tqdm_H1-Li1_sto-3g_singlet_1.45.hdf5' + ) with h5py.File(tqdm_lih_sto3g, 'r') as fid: self.tqdm_lih_sto3g = fid['tqdm'][...] phdm_lih_sto3g = os.path.join( - THIS_DIRECTORY, 'testing/phdm_H1-Li1_sto-3g_singlet_1.45.hdf5') + THIS_DIRECTORY, 'testing/phdm_H1-Li1_sto-3g_singlet_1.45.hdf5' + ) with h5py.File(phdm_lih_sto3g, 'r') as fid: self.phdm_lih_sto3g = fid['phdm'][...] @@ -76,31 +79,23 @@ def test_kronecker_delta_nonunit_args(self): def test_tpdm_to_opdm(self): # for all files in datadirectory check if this map holds - for file in filter(lambda x: x.endswith(".hdf5"), - os.listdir(DATA_DIRECTORY)): - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, file)) - if (molecule.fci_one_rdm is not None and - molecule.fci_two_rdm is not None): - test_opdm = map_two_pdm_to_one_pdm(molecule.fci_two_rdm, - molecule.n_electrons) + for file in filter(lambda x: x.endswith(".hdf5"), os.listdir(DATA_DIRECTORY)): + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, file)) + if molecule.fci_one_rdm is not None and molecule.fci_two_rdm is not None: + test_opdm = map_two_pdm_to_one_pdm(molecule.fci_two_rdm, molecule.n_electrons) assert numpy.allclose(test_opdm, molecule.fci_one_rdm) def test_opdm_to_oqdm(self): - for file in filter(lambda x: x.endswith(".hdf5"), - os.listdir(DATA_DIRECTORY)): - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, file)) + for file in filter(lambda x: x.endswith(".hdf5"), os.listdir(DATA_DIRECTORY)): + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, file)) if molecule.fci_one_rdm is not None: test_oqdm = map_one_pdm_to_one_hole_dm(molecule.fci_one_rdm) true_oqdm = numpy.eye(molecule.n_qubits) - molecule.fci_one_rdm assert numpy.allclose(test_oqdm, true_oqdm) def test_oqdm_to_opdm(self): - for file in filter(lambda x: x.endswith(".hdf5"), - os.listdir(DATA_DIRECTORY)): - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, file)) + for file in filter(lambda x: x.endswith(".hdf5"), os.listdir(DATA_DIRECTORY)): + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, file)) if molecule.fci_one_rdm is not None: true_oqdm = numpy.eye(molecule.n_qubits) - molecule.fci_one_rdm test_opdm = map_one_hole_dm_to_one_pdm(true_oqdm) @@ -110,17 +105,16 @@ def test_tqdm_conversions_h2_631g(self): # construct the 2-hole-RDM for LiH the slow way # TODO: speed up this calculation by directly contracting from the wf. filename = "H2_6-31g_singlet_0.75.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_tqdm = self.tqdm_h2_6_31g - test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(true_tqdm, test_tqdm) true_oqdm = numpy.eye(molecule.n_qubits) - molecule.fci_one_rdm test_oqdm = map_two_hole_dm_to_one_hole_dm( - true_tqdm, molecule.n_qubits - molecule.n_electrons) + true_tqdm, molecule.n_qubits - molecule.n_electrons + ) assert numpy.allclose(true_oqdm, test_oqdm) test_tpdm = map_two_hole_dm_to_two_pdm(true_tqdm, molecule.fci_one_rdm) @@ -128,17 +122,16 @@ def test_tqdm_conversions_h2_631g(self): def test_tqdm_conversions_h2_sto3g(self): filename = "H2_sto-3g_singlet_1.4.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_tqdm = self.tqdm_h2_sto3g - test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(true_tqdm, test_tqdm) true_oqdm = numpy.eye(molecule.n_qubits) - molecule.fci_one_rdm test_oqdm = map_two_hole_dm_to_one_hole_dm( - true_tqdm, molecule.n_qubits - molecule.n_electrons) + true_tqdm, molecule.n_qubits - molecule.n_electrons + ) assert numpy.allclose(true_oqdm, test_oqdm) test_tpdm = map_two_hole_dm_to_two_pdm(true_tqdm, molecule.fci_one_rdm) @@ -146,17 +139,16 @@ def test_tqdm_conversions_h2_sto3g(self): def test_tqdm_conversions_lih_sto3g(self): filename = "H1-Li1_sto-3g_singlet_1.45.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_tqdm = self.tqdm_lih_sto3g - test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_tqdm = map_two_pdm_to_two_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(true_tqdm, test_tqdm) true_oqdm = numpy.eye(molecule.n_qubits) - molecule.fci_one_rdm test_oqdm = map_two_hole_dm_to_one_hole_dm( - true_tqdm, molecule.n_qubits - molecule.n_electrons) + true_tqdm, molecule.n_qubits - molecule.n_electrons + ) assert numpy.allclose(true_oqdm, test_oqdm) test_tpdm = map_two_hole_dm_to_two_pdm(true_tqdm, molecule.fci_one_rdm) @@ -164,57 +156,48 @@ def test_tqdm_conversions_lih_sto3g(self): def test_phdm_conversions_h2_631g(self): filename = "H2_6-31g_singlet_0.75.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_phdm = self.phdm_h2_6_31g - test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(test_phdm, true_phdm) - test_opdm = map_particle_hole_dm_to_one_pdm(true_phdm, - molecule.n_electrons, - molecule.n_qubits) + test_opdm = map_particle_hole_dm_to_one_pdm( + true_phdm, molecule.n_electrons, molecule.n_qubits + ) assert numpy.allclose(test_opdm, molecule.fci_one_rdm) - test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, - molecule.fci_one_rdm) + test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, molecule.fci_one_rdm) assert numpy.allclose(test_tpdm, molecule.fci_two_rdm) def test_phdm_conversions_h2_sto3g(self): filename = "H2_sto-3g_singlet_1.4.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_phdm = self.phdm_h2_sto3g - test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(test_phdm, true_phdm) - test_opdm = map_particle_hole_dm_to_one_pdm(true_phdm, - molecule.n_electrons, - molecule.n_qubits) + test_opdm = map_particle_hole_dm_to_one_pdm( + true_phdm, molecule.n_electrons, molecule.n_qubits + ) assert numpy.allclose(test_opdm, molecule.fci_one_rdm) - test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, - molecule.fci_one_rdm) + test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, molecule.fci_one_rdm) assert numpy.allclose(test_tpdm, molecule.fci_two_rdm) def test_phdm_conversions_lih_sto3g(self): filename = "H1-Li1_sto-3g_singlet_1.45.hdf5" - molecule = MolecularData( - filename=os.path.join(DATA_DIRECTORY, filename)) + molecule = MolecularData(filename=os.path.join(DATA_DIRECTORY, filename)) true_phdm = self.phdm_lih_sto3g - test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, - molecule.fci_one_rdm) + test_phdm = map_two_pdm_to_particle_hole_dm(molecule.fci_two_rdm, molecule.fci_one_rdm) assert numpy.allclose(test_phdm, true_phdm) - test_opdm = map_particle_hole_dm_to_one_pdm(true_phdm, - molecule.n_electrons, - molecule.n_qubits) + test_opdm = map_particle_hole_dm_to_one_pdm( + true_phdm, molecule.n_electrons, molecule.n_qubits + ) assert numpy.allclose(test_opdm, molecule.fci_one_rdm) - test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, - molecule.fci_one_rdm) + test_tpdm = map_particle_hole_dm_to_two_pdm(true_phdm, molecule.fci_one_rdm) assert numpy.allclose(test_tpdm, molecule.fci_two_rdm)