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

RimuQMC / Rimu.jl / 15992867340

01 Jul 2025 07:33AM UTC coverage: 94.311% (-0.3%) from 94.571%
15992867340

Pull #330

github

lch
Unified ModeMap Type (#330)

1. Renamed the type OccupiedModeMap to ModeMap and replaced all occurrences.
2. Added the occupied_mode_map function as a constructor for ModeMap.
3. Added the unoccupied_mode_map function as a constructor for FermiFS
and corresponding iterator type FermiUnoccupiedModes.
Pull Request #330: Unified ModeMap Type

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

7 existing lines in 3 files now uncovered.

7029 of 7453 relevant lines covered (94.31%)

11608138.38 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,257,183✔
56
end
57

58
function check_fermi_onr(onr, N, M)
26,872✔
59
    sum(onr) == N ||
26,873✔
60
        throw(ArgumentError("Invalid ONR: $N particles expected, $(sum(onr)) given."))
61
    length(onr) == M ||
26,871✔
62
        throw(ArgumentError("Invalid ONR: $M modes expected, $(length(onr)) given."))
63
    all(in((0, 1)), onr) ||
26,871✔
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}
26,748✔
83
    @boundscheck check_fermi_onr(onr, N, M)
26,748✔
84
    spl_type = select_int_type(M)
26,744✔
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)
26,744✔
90
    dense_sizeof = ceil(Int, M / 64)
26,744✔
91
    if dense_sizeof == 1 || dense_sizeof ≤ sparse_sizeof
26,744✔
92
        S = typeof(BitString{M}(0))
24,629✔
93
    else
94
        S = SortedParticleList{N,M,spl_type}
2,115✔
95
    end
96
    return FermiFS{N,M,S}(from_fermi_onr(S, onr))
26,918✔
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,110✔
129
Base.hash(a::FermiFS,  h::UInt) = hash(a.bs, h)
171,057,438✔
130
Base.:(==)(a::FermiFS, b::FermiFS) = a.bs == b.bs
1,000,172✔
131
num_occupied_modes(::FermiFS{N}) where {N} = N
1,044,430✔
132
occupied_modes(a::FermiFS{N,<:Any,S}) where {N,S} = FermiOccupiedModes{N,S}(a.bs)
10,981,831✔
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 [`onr`](@ref) but contains more information.
147

148
# Example
149

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

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

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

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

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

190
find_mode(a::FermiFS, i) = fermi_find_mode(a.bs, i)
17,451,326✔
191

192
function find_occupied_mode(a::FermiFS, i::Integer)
9,219,749✔
193
    for k in occupied_modes(a)
18,434,326✔
194
        i -= 1
17,085,605✔
195
        i == 0 && return k
17,086,246✔
196
    end
15,736,945✔
UNCOV
197
    return FermiFSIndex(0, 0, 0)
×
198
end
199

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

204
function excitation(a::FermiFS{N,M,S}, creations, destructions) where {N,M,S}
15,228,016✔
205
    new_bs, value = fermi_excitation(a.bs, creations, destructions)
27,972,402✔
206
    return FermiFS{N,M,S}(new_bs), value
15,228,808✔
207
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