Source code for qugradlab.hilbert_spaces.fermionic._fermionic_hilbert_spaces
1from typing import Optional, Iterable, Union
2
3import numpy as np
4
5from qugrad import HilbertSpace
6
7from ._hamming_weight_operations import only_low_lieing_states_occupied,\
8 sub_hamming_weight_is, \
9 any_sub_hamming_weight_is, \
10 all_constant_hamming_weight
11from .. import QuditSpace
12from .._get_digit import get_digit
13
14
[docs]
15class FermionSpace(HilbertSpace):
16 """
17 Represents a Fermionic Fock space.
18 """
19
20 _n_single_particle_states: int
21 "The number of single particle states a fermion can take on"
22
[docs]
23 def __init__(self, n_single_particle_states: int):
24 """
25 Initialises a :class:`FermionSpace`
26
27 Parameters
28 ----------
29 n_single_particle_states: int
30 The number of single particle states a fermion can take on
31 """
32 self._n_single_particle_states = n_single_particle_states
33 super().__init__(np.arange(2**n_single_particle_states))
34 @property
35 def n_single_particle_states(self) -> int:
36 "The number of single particle states a fermion can take on"
37 return self._n_single_particle_states
38 @staticmethod
39 def _labels(digits: Iterable[str]) -> str:
40 """
41 Generates a strings that represent the state specified by the `digits`.
42
43 Parameters
44 ----------
45 digits : Iterable[str]
46 The digits representing the state
47
48 Returns
49 -------
50 str
51 The label for the specified state.
52 """
53 return f"|{''.join(digits)}⟩"
[docs]
54 def labels(self,
55 states: Optional[Union[int, list[int]]] = None
56 ) -> Union[str, list[str]]:
57 """
58 Generates a string (list of strings) that represent the state(s).
59
60 Parameters
61 ----------
62 states : int | list[int], optional
63 The state(s) to label. If ``None`` then the labels for all states in
64 :attr:`basis` are returned. By default ``None``.
65
66 Returns
67 -------
68 str | list[str]
69 The label(s) for the specified states.
70 """
71 if states is None: states = self.basis
72 digits = get_digit(states,
73 2,
74 np.arange(self._n_single_particle_states)
75 ).astype(str)
76 if isinstance(states, int):
77 return self._labels(digits)
78 return [self._labels(d) for d in digits]
79
[docs]
80class FixedParticleFermionSpace(FermionSpace):
81 """
82 Represents a Fermionic Fock space constrained to have a fixed particle
83 number.
84 """
85
86 _n_particles: int
87 "The number of particles"
88
[docs]
89 def __init__(self, n_single_particle_states: int, n_particles: int):
90 """
91 Initialises a :class:`FixedParticleFermionSpace`
92
93 Parameters
94 ----------
95 n_single_particle_states: int
96 The number of single particle states a fermion can take on
97 n_particles : int
98 The number of particles
99 """
100 self._n_single_particle_states = n_single_particle_states
101 self._n_particles = n_particles
102 basis = all_constant_hamming_weight(n_single_particle_states,
103 n_particles)
104 HilbertSpace.__init__(self, list(basis))
105 @property
106 def n_particles(self) -> int:
107 "The number of particles"
108 return self._n_particles
109
[docs]
110class FermionQuditSpace(FixedParticleFermionSpace, QuditSpace):
111 """
112 A :class:`FixedParticleFermionSpace` with a computational structure. The
113 Hilbert space is split into the tensor product of sites (qudits), with each
114 site hosting a specified number of levels. The computational subspace
115 consists of the single occupation states that only have particles occupying
116 the lowest two levels.
117 """
118
119 _sites: int
120 "The number of sites (qudits)"
121
122 _levels_per_site: int
123 "The number of states per site (qudit)"
124
[docs]
125 def __init__(self,
126 sites: int,
127 levels_per_site: int,
128 n_particles: int):
129 """
130 Initialises a :class:`FermionQuditSpace`.
131
132 Parameters
133 ----------
134 sites : int
135 The number of sites (qudits)
136 levels_per_site : int
137 The number of states per site (qudit)
138 n_particles : int
139 The number of particles
140 """
141 self._sites = sites
142 self._levels_per_site = levels_per_site
143 FixedParticleFermionSpace.__init__(self,
144 sites * levels_per_site,
145 n_particles)
146 @property
147 def sites(self) -> int:
148 "The number of sites (qudits)"
149 return self._sites
150 @property
151 def levels_per_site(self) -> int:
152 "The number of states per site (qudit)"
153 return self._levels_per_site
[docs]
154 def computational_projector(self) -> np.ndarray[bool]:
155 """
156 Generates a boolean filter for the computation basis states in
157 :attr:`basis`. The computational subspace consists of the single
158 occupation states that only have particles occupying the lowest two
159 levels.
160
161 Returns
162 -------
163 NDArray[Shape[:attr:`dim`], bool]
164 A boolean filter for the computation basis states in :attr:`basis`.
165 """
166 return only_low_lieing_states_occupied(2,
167 self.levels_per_site,
168 self._sites,
169 self.basis) \
170 & self.single_occupation_states()
[docs]
171 def single_occupation_states(self) -> np.ndarray[bool]:
172 """
173 Returns a boolean array indicating whether each of the :attr:`basis`
174 states is a single occupation state (each site has exactly one
175 particle).
176
177 Returns
178 -------
179 NDArray[Shape[:attr:`dim`], bool]
180 A boolean array indicating whether each of the :attr:`basis`
181 states is a single occupation state.
182 """
183 return sub_hamming_weight_is(1,
184 self.levels_per_site,
185 self._sites,
186 self.basis)
[docs]
187 def n_occupation_states(self, occupation: int) -> np.ndarray[bool]:
188 """
189 Returns a boolean array indicating whether each of the :attr:`basis`
190 states has at most the specified occupation.
191
192 Parameters
193 ----------
194 occupation : int
195 The occupation to check the :attr:`basis` states for.
196
197 Returns
198 -------
199 NDArray[Shape[:attr:`dim`], bool]
200 A boolean array indicating whether each of the :attr:`basis`
201 states has at most the specified occupation.
202
203 Note
204 ----
205 :meth:`single_occupation_states` is only equivalent to
206 ``n_occupation_states(1)`` when :attr:`n_particles` is greater than or
207 equal to :attr:`sites`.
208 """
209 projector = any_sub_hamming_weight_is(occupation,
210 self.levels_per_site,
211 self._sites,
212 self.basis)
213 for n in range(occupation+1,self.n_particles+1):
214 projector &= np.logical_not(
215 any_sub_hamming_weight_is(n,
216 self.levels_per_site,
217 self._sites,
218 self.basis))
219 return projector