• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RimuQMC / Rimu.jl / 16110004339

07 Jul 2025 06:53AM UTC coverage: 94.311% (-0.3%) from 94.571%
16110004339

Pull #330

github

web-flow
Apply suggestions from code review

Co-authored-by: mtsch <matijacufar@gmail.com>
Pull Request #330: Unified ModeMap Type

74 of 97 new or added lines in 16 files covered. (76.29%)

1 existing line in 1 file now uncovered.

7029 of 7453 relevant lines covered (94.31%)

11618857.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.34
/src/BitStringAddresses/fermifs.jl
1
"""
2
    FermiFS{N,M,S} <: SingleComponentFockAddress
3

4
Address type that represents a Fock state of `N` fermions of the same spin in `M` modes by
5
wrapping a [`BitString`](@ref), or a [`SortedParticleList`](@ref). Which is wrapped is
6
chosen automatically based on the properties of the address.
7

8
# Constructors
9

10
* `FermiFS{[N,M]}(val::Integer...)`: Create `FermiFS{N,M}` from occupation numbers. This is
11
  type-stable if the number of modes `M` and the number of particles `N` are provided.
12
  Otherwise, `M` and `N` are inferred from the arguments.
13

14
* `FermiFS{[N,M]}(onr)`: Create `FermiFS{N,M}`  from occupation number representation, see
15
  [`onr`](@ref). This is efficient if `N` and `M` are provided, and `onr` is a
16
  statically-sized collection, such as a `Tuple{M}` or `SVector{M}`.
17

18
* `FermiFS{[N,M]}([M, ]pairs...)`: Provide the number of modes `M` and pairs of the form
19
  `mode => 1`. If `M` is provided as a type parameter, it should not be provided as the
20
  first argument.  Useful for creating sparse addresses. `pairs` can be multiple arguments
21
  or an iterator of pairs.
22

23
* `FermiFS{N,M,S}(bs::S)`: Unsafe constructor. Does not check whether the number of
24
  particles in `bs` is equal to `N`, or whether each mode only contains one particle.
25

26
* [`@fs_str`](@ref): Addresses are sometimes printed in a compact manner. This
27
  representation can also be used as a constructor. See the examples below.
28

29
# Examples
30

31
```jldoctest
32
julia> FermiFS{3,5}(0, 1, 1, 1, 0)
33
FermiFS{3,5}(0, 1, 1, 1, 0)
34

35
julia> FermiFS([abs(i - 3) ≤ 1 for i in 1:5])
36
FermiFS{3,5}(0, 1, 1, 1, 0)
37

38
julia> FermiFS(5, 2 => 1, 3 => 1, 4 => 1)
39
FermiFS{3,5}(0, 1, 1, 1, 0)
40

41
julia> FermiFS{3,5}(i => 1 for i in 2:4)
42
FermiFS{3,5}(0, 1, 1, 1, 0)
43

44
julia> fs"|⋅↑↑↑⋅⟩"
45
FermiFS{3,5}(0, 1, 1, 1, 0)
46

47
julia> fs"|f 5: 2 3 4⟩"
48
FermiFS{3,5}(0, 1, 1, 1, 0)
49
```
50

51
See also: [`SingleComponentFockAddress`](@ref), [`BoseFS`](@ref), [`CompositeFS`](@ref),
52
[`FermiFS2C`](@ref), [`BitString`](@ref), [`OccupationNumberFS`](@ref).
53
"""
54
struct FermiFS{N,M,S} <: SingleComponentFockAddress{N,M}
55
    bs::S
15,253,707✔
56
end
57

58
function check_fermi_onr(onr, N, M)
25,976✔
59
    sum(onr) == N ||
25,977✔
60
        throw(ArgumentError("Invalid ONR: $N particles expected, $(sum(onr)) given."))
61
    length(onr) == M ||
25,975✔
62
        throw(ArgumentError("Invalid ONR: $M modes expected, $(length(onr)) given."))
63
    all(in((0, 1)), onr) ||
25,975✔
64
        throw(ArgumentError("Invalid ONR: may only contain 0s and 1s."))
65
end
66

67
function FermiFS{N,M,S}(onr::Union{SVector{M},MVector{M},NTuple{M}}) where {N,M,S}
124✔
68
    @boundscheck begin
124✔
69
        check_fermi_onr(onr, N, M)
124✔
70
        if S <: BitString
124✔
71
            M == num_bits(S) || throw(ArgumentError(
122✔
72
                "invalid ONR: $B-bit BitString does not fit $M modes"
73
            ))
74
        elseif S <: SortedParticleList
2✔
75
            N == num_particles(S) && M == num_modes(S) || throw(ArgumentError(
2✔
76
                "invalid ONR: $S does not fit $N particles in $M modes"
77
            ))
78
        end
79
    end
80
    return FermiFS{N,M,S}(from_fermi_onr(S, onr))
128✔
81
end
82
function FermiFS{N,M}(onr::Union{AbstractArray{<:Integer},NTuple{M,<:Integer}}) where {N,M}
25,852✔
83
    @boundscheck check_fermi_onr(onr, N, M)
25,852✔
84
    spl_type = select_int_type(M)
25,848✔
85
    # Pick smaller address type, but prefer dense.
86
    # Alway pick dense if it fits into one chunk.
87

88
    # Compute the size of container in words
89
    sparse_sizeof = ceil(Int, N * sizeof(spl_type) / 8)
25,848✔
90
    dense_sizeof = ceil(Int, M / 64)
25,848✔
91
    if dense_sizeof == 1 || dense_sizeof ≤ sparse_sizeof
25,848✔
92
        S = typeof(BitString{M}(0))
23,776✔
93
    else
94
        S = SortedParticleList{N,M,spl_type}
2,072✔
95
    end
96
    return FermiFS{N,M,S}(from_fermi_onr(S, onr))
26,022✔
97
end
98
function FermiFS(onr)
523✔
99
    onr = Tuple(onr)
681✔
100
    M = length(onr)
523✔
101
    N = sum(onr)
9,663✔
102
    return FermiFS{N,M}(onr)
688✔
103
end
104
FermiFS(vals::Integer...) = FermiFS(vals) # list occupation numbers
60✔
105
FermiFS(val::Integer) = FermiFS((val,)) # single mode
3✔
106
FermiFS{N,M}(vals::Integer...) where {N,M} = FermiFS{N,M}(vals)
118✔
107

108
# Sparse constructors
109
FermiFS(M::Integer, pairs::Pair...) = FermiFS(M, pairs)
7✔
110
FermiFS(M::Integer, pairs) = FermiFS(sparse_to_onr(M, pairs))
35✔
111
FermiFS{N,M}(pairs::Vararg{Pair,N}) where {N,M} = FermiFS{N,M}(pairs)
21✔
112
FermiFS{N,M}(pairs) where {N,M} = FermiFS{N,M}(sparse_to_onr(M, pairs))
23✔
113
FermiFS(pairs::Pair...) = throw(ArgumentError("number of modes must be provided"))
×
114

115
function print_address(io::IO, f::FermiFS{N,M}; compact=false) where {N,M}
928✔
116
    if compact && f.bs isa SortedParticleList
464✔
117
        print(io, "|f ", M, ": ", join(Int.(f.bs.storage), ' '), "⟩")
20✔
118
    elseif compact
444✔
119
        print(io, "|", join(map(o -> o == 0 ? '⋅' : '↑', onr(f))), "⟩")
6,754✔
120
    elseif f.bs isa SortedParticleList
323✔
121
        print(io, "FermiFS{$N,$M}(", onr_sparse_string(onr(f)), ")")
50✔
122
    else
123
        print(io, "FermiFS{$N,$M}", tuple(onr(f)...))
273✔
124
    end
125
end
126

127
Base.bitstring(a::FermiFS) = bitstring(a.bs)
×
128
Base.isless(a::FermiFS, b::FermiFS) = isless(a.bs, b.bs)
10,107✔
129
Base.hash(a::FermiFS,  h::UInt) = hash(a.bs, h)
170,066,512✔
130
Base.:(==)(a::FermiFS, b::FermiFS) = a.bs == b.bs
1,000,177✔
131
num_occupied_modes(::FermiFS{N}) where {N} = N
1,043,167✔
132
occupied_modes(a::FermiFS{N,<:Any,S}) where {N,S} = FermiOccupiedModes{N,S}(a.bs)
10,976,370✔
133

134
num_unoccupied_modes(::FermiFS{N,M}) where {N,M} = M - N
1✔
135
unoccupied_modes(a::FermiFS{N,<:Any,S}) where {N,S} = FermiUnoccupiedModes{N,S}(~a.bs)
3✔
136

137
"""
138
    unoccupied_mode_map(addr::FermiFS) <: AbstractVector
139
    
140
Get a map of unoccupied modes in [`FermiFS`](@ref) address as an `AbstractVector`
141
of indices compatible with [`excitation`](@ref).
142

143
`unoccupied_mode_map(addr)[i]` contains the index for the `i`-th unoccupied mode.
144
This is useful because unoccupied modes is required in some cases.
145
`unoccupied_mode_map(addr)` is an eager version of the iterator returned by
146
[`unoccupied_modes`](@ref). It is similar to 
147
[`onr`](@ref) but contains more information.
148

149
# Example
150

151
```jldoctest
152
julia> f = FermiFS(1,1,0,0)
153
FermiFS{2,4}(1, 1, 0, 0)
154

155
julia> mf = unoccupied_mode_map(f)
156
2-element Rimu.BitStringAddresses.ModeMap{2, FermiFSIndex}:
157
 FermiFSIndex(occnum=0, mode=3, offset=2)
158
 FermiFSIndex(occnum=0, mode=4, offset=3)
159
 
160
julia> mf == collect(unoccupied_modes(f))
161
true
162

163
```
164
See also [`occupied_mode_map`](@ref).
165
"""
166
function unoccupied_mode_map(addr::FermiFS{N,M}) where {N,M}
1✔
167
    modes = unoccupied_modes(addr)
1✔
168
    T = eltype(modes)
1✔
169
    L = num_unoccupied_modes(addr)
1✔
170
    indices = MVector{L,T}(undef)
1✔
171
    i = 0
1✔
172
    for index in modes
2✔
173
        i += 1
2✔
174
        @inbounds indices[i] = index
2✔
175
    end
3✔
176
    return ModeMap(SVector(indices), i)
1✔
177
end
178

179
function near_uniform(::Type{FermiFS{N,M}}) where {N,M}
30✔
180
    return FermiFS([fill(1, N); fill(0, M - N)])
30✔
181
end
182

183
@inline function onr(a::FermiFS{<:Any,M}) where {M}
625,131✔
184
    result = zero(MVector{M,Int32})
625,131✔
185
    @inbounds for (_, mode) in occupied_modes(a)
1,004,314✔
186
        result[mode] = 1
29,402,199✔
187
    end
36,534,706✔
188
    return SVector(result)
625,131✔
189
end
190

191
find_mode(a::FermiFS, i) = fermi_find_mode(a.bs, i)
17,446,230✔
192

193
function find_occupied_mode(a::FermiFS, i::Integer)
9,215,928✔
194
    for k in occupied_modes(a)
18,424,729✔
195
        i -= 1
17,078,796✔
196
        i == 0 && return k
17,078,169✔
197
    end
15,732,670✔
UNCOV
198
    return FermiFSIndex(0, 0, 0)
×
199
end
200

201
function Base.reverse(f::FermiFS)
1,428✔
202
    return typeof(f)(reverse(f.bs))
1,428✔
203
end
204

205
function excitation(a::FermiFS{N,M,S}, creations, destructions) where {N,M,S}
15,224,016✔
206
    new_bs, value = fermi_excitation(a.bs, creations, destructions)
27,962,634✔
207
    return FermiFS{N,M,S}(new_bs), value
15,226,500✔
208
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc