Source code for netket.experimental.hilbert.spin_orbital_fermions
# Copyright 2022 The NetKet Authors - All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Optional, List, Union
from collections.abc import Iterable
import numpy as np
from fractions import Fraction
from netket.hilbert.fock import Fock
from netket.hilbert.tensor_hilbert import TensorHilbert
from netket.hilbert.homogeneous import HomogeneousHilbert
class SpinOrbitalFermions(HomogeneousHilbert):
r"""
Hilbert space for 2nd quantization fermions with spin `s` distributed among
`n_orbital` orbitals.
The number of fermions can be fixed globally or fixed on a per spin projection.
Note:
This class is simply a convenient wrapper that creates a Fock or TensorHilbert
of Fock spaces with occupation numbers 0 or 1.
It is mainly useful to avoid needing to specify the n_max=1 each time, and adds
convenient functions such as _get_index and _spin_index, which allow one to
index the correct TensorHilbert corresponding to the right spin projection.
"""
[docs] def __init__(
self,
n_orbitals: int,
s: float = None,
n_fermions: Optional[Union[int, List[int]]] = None,
):
r"""
Constructs the hilbert space for spin-`s` fermions on `n_orbitals`.
Samples of this hilbert space represent occupation numbers (0,1) of the
orbitals. The number of fermions may be fixed to `n_fermions`.
If the spin is different from 0 or None, n_fermions can also be a list to fix
the number of fermions per spin component.
Using this class, one can generate a tensor product of fermionic hilbert spaces
that distinguish particles with different spin.
Args:
n_orbitals: number of orbitals we store occupation numbers for. If the
number of fermions per spin is conserved, the different spin
configurations are not counted as orbitals and are handled differently.
s: spin of the fermions.
n_fermions: (optional) fixed number of fermions per spin (conserved). In the
case n_fermions is an int, the total number of fermions is fixed, while
for lists, the number of fermions per spin component is fixed.
Returns:
A SpinOrbitalFermions object
"""
if s is None:
total_size = n_orbitals
else:
spin_size = round(2 * s + 1)
total_size = n_orbitals * spin_size
if n_fermions is None:
hilbert = Fock(n_max=1, N=total_size)
elif isinstance(n_fermions, int):
hilbert = Fock(n_max=1, N=total_size, n_particles=n_fermions)
else:
if not isinstance(n_fermions, Iterable):
raise TypeError(
f"n_fermions (whose type is {type(n_fermions)}) "
"must be None, an integer, or an iterable of integers"
)
if s is None:
raise TypeError(
"n_fermions cannot be a sequence if no spin is specified."
)
if len(n_fermions) != spin_size:
raise ValueError(
"list of number of fermions must equal number of spin components"
)
spin_hilberts = [
Fock(n_max=1, N=n_orbitals, n_particles=Nf) for Nf in n_fermions
]
hilbert = TensorHilbert(*spin_hilberts)
self._fock = hilbert
"""Internal representation of this Hilbert space (Fock or TensorHilbert)."""
# local states are the occupation numbers (0, 1)
local_states = np.array((0.0, 1.0))
# we use the constraints from the Fock spaces, and override is_constrained later
super().__init__(local_states, N=total_size, constraint_fn=None)
self._s = s
self.n_fermions = n_fermions
self._is_constrained = n_fermions is not None
self.n_orbitals = n_orbitals
# we copy the respective functions, independent of what hilbert space they are
self._numbers_to_states = self._fock._numbers_to_states
self._states_to_numbers = self._fock._states_to_numbers
def __repr__(self):
_str = f"SpinOrbitalFermions(n_orbitals={self.n_orbitals}"
if self.n_fermions is not None:
_str += f", n_fermions={self.n_fermions}"
if self.spin is not None:
_str += f", s={Fraction(self.spin)}"
_str += ")"
return _str
@property
def spin(self) -> float:
"""Returns the spin of the fermions"""
return self._s
@property
def size(self) -> int:
"""Size of the hilbert space. In case the fermions have spin `s`, the size is
(2*s+1)*n_orbitals"""
return self._fock.size
@property
def _attrs(self):
return (self.spin, self.n_fermions, self.n_orbitals)
@property
def is_constrained(self):
return self._is_constrained
@property
def is_finite(self) -> bool:
return self._fock.is_finite
@property
def n_states(self) -> int:
return self._fock.n_states
@property
def _n_spin_states(self) -> int:
"""return the number of spin projections"""
if self.spin is None:
raise Exception(
"cannot request number of spin states for spinless fermions"
)
return round(2 * self.spin + 1)
def _spin_index(self, sz: float) -> int:
"""return the index of the Fock block corresponding to the sz projection"""
if self.spin is None:
if sz is not None or not np.isclose(sz, 0):
raise Exception("cannot request spin index of spinless fermions")
return 0
else:
return round(sz + self.spin)
def _get_index(self, orb: int, sz: float = None):
"""go from (site, spin_projection) indices to index in the hilbert space"""
spin_idx = self._spin_index(sz)
return spin_idx * self.n_orbitals + orb