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

RimuQMC / Rimu.jl / 18362588080

09 Oct 2025 01:20AM UTC coverage: 94.215% (+0.1%) from 94.072%
18362588080

push

github

web-flow
Fix documentation for IsDynamicSemistochastic (#359)

This updates the documentation to reflect the new behaviour of
`rel_spawning_threshold` and `abs_spawning_threshold` in
`IsDynamicSemistochastic`.

7329 of 7779 relevant lines covered (94.22%)

13140384.12 hits per line

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

85.64
/src/BitStringAddresses/fockaddress.jl
1
# AbstractFockAddress is defined in Interfaces/abstractfockaddress.jl, so we can use it here.
2
# So are num_particles, num_modes, num_components.
3

4
"""
5
    SingleComponentFockAddress{N,M} <: AbstractFockAddress{N,M}
6

7
A type representing a single component Fock state with `N` particles and `M` modes.
8

9
Implemented subtypes: [`BoseFS`](@ref), [`FermiFS`](@ref).
10

11
# Supported functionality
12

13
* [`find_mode`](@ref)
14
* [`find_occupied_mode`](@ref)
15
* [`num_occupied_modes`](@ref)
16
* [`occupied_modes`](@ref): Lazy iterator.
17
* [`occupied_mode_map`](@ref): `AbstractVector` with eager construction.
18
* [`excitation`](@ref): Create a new address.
19
* [`BoseFSIndex`](@ref) and [`FermiFSIndex`](@ref) for indexing.
20

21
See also [`CompositeFS`](@ref), [`AbstractFockAddress`](@ref).
22
"""
23
abstract type SingleComponentFockAddress{N,M} <: AbstractFockAddress{N,M} end
24

25
Interfaces.num_components(::Type{<:SingleComponentFockAddress}) = 1
×
26

27
"""
28
    occupation_number_representation(fs::SingleComponentFockAddress)
29
    onr(fs::SingleComponentFockAddress)
30

31
Compute and return the occupation number representation of the Fock state `fs` as an
32
`SVector{M}`, where `M` is the number of modes.
33
"""
34
onr
35

36
"""
37
    find_mode(::SingleComponentFockAddress, i[, occ])
38

39
Find the `i`-th mode in address. Returns [`BoseFSIndex`](@ref) for [`BoseFS`](@ref), and
40
[`FermiFSIndex`](@ref) for [`FermiFS`](@ref). Can work on a tuple of modes. Does not check
41
bounds. Setting `occ` to the result of [`occupied_mode_map`](@ref) can increase the
42
performance for [`BoseFS`](@ref).
43

44
```jldoctest
45
julia> find_mode(BoseFS(1, 0, 2), 2)
46
BoseFSIndex(occnum=0, mode=2, offset=2)
47

48
julia> find_mode(FermiFS(1, 1, 1, 0), (2,3))
49
(FermiFSIndex(occnum=1, mode=2, offset=1), FermiFSIndex(occnum=1, mode=3, offset=2))
50
```
51

52
See [`SingleComponentFockAddress`](@ref).
53
"""
54
find_mode
55

56
"""
57
    find_occupied_mode(::SingleComponentFockAddress, k)
58
    find_occupied_mode(::BoseFS, k, [n])
59

60
Find the `k`-th occupied mode in address (with at least `n` particles).
61
Returns [`BoseFSIndex`](@ref) for [`BoseFS`](@ref), and [`FermiFSIndex`](@ref) for
62
[`FermiFS`](@ref). When unsuccessful it returns a zero index.
63

64
# Example
65

66
```jldoctest
67
julia> find_occupied_mode(FermiFS(1, 1, 1, 0), 2)
68
FermiFSIndex(occnum=1, mode=2, offset=1)
69

70
julia> find_occupied_mode(BoseFS(1, 0, 2), 1)
71
BoseFSIndex(occnum=1, mode=1, offset=0)
72

73
julia> find_occupied_mode(BoseFS(1, 0, 2), 1, 2)
74
BoseFSIndex(occnum=2, mode=3, offset=3)
75
```
76

77
See also [`occupied_modes`](@ref), [`occupied_mode_map`](@ref),
78
[`SingleComponentFockAddress`](@ref).
79
"""
80
function find_occupied_mode(b::SingleComponentFockAddress, index::Integer, n=1)
912✔
81
    mode_iterator = occupied_modes(b)
559,416,277✔
82
    T = eltype(mode_iterator)
216,096,226✔
83
    for (occnum, mode, offset) in mode_iterator
432,191,980✔
84
        index -= occnum ≥ n
343,323,945✔
85
        if index == 0
343,323,945✔
86
            return T(occnum, mode, offset)
216,096,226✔
87
        end
88
    end
254,455,488✔
89
    return T(0, 0, 0)
×
90
end
91

92
"""
93
    num_occupied_modes(::SingleComponentFockAddress)
94

95
Get the number of occupied modes in address. Equivalent to
96
`length(`[`occupied_modes`](@ref)`(address))`, or the number of non-zeros in its ONR
97
representation.
98

99
# Example
100

101
```jldoctest
102
julia> num_occupied_modes(BoseFS((1, 0, 2)))
103
2
104
julia> num_occupied_modes(FermiFS((1, 1, 1, 0)))
105
3
106
```
107

108
See [`SingleComponentFockAddress`](@ref).
109
"""
110
num_occupied_modes
111

112
"""
113
    occupied_modes(::SingleComponentFockAddress)
114

115
Return a lazy iterator over all occupied modes in an address. Iterates over
116
[`BoseFSIndex`](@ref)s for [`BoseFS`](@ref), and over [`FermiFSIndex`](@ref)s for
117
[`FermiFS`](@ref). See [`occupied_mode_map`](@ref) for an eager version.
118

119
# Example
120

121
```jldoctest
122
julia> b = BoseFS((1,5,0,4));
123

124
julia> foreach(println, occupied_modes(b))
125
BoseFSIndex(occnum=1, mode=1, offset=0)
126
BoseFSIndex(occnum=5, mode=2, offset=2)
127
BoseFSIndex(occnum=4, mode=4, offset=9)
128
```
129

130
```jldoctest
131
julia> f = FermiFS((1,1,0,1,0,0,1));
132

133
julia> foreach(println, occupied_modes(f))
134
FermiFSIndex(occnum=1, mode=1, offset=0)
135
FermiFSIndex(occnum=1, mode=2, offset=1)
136
FermiFSIndex(occnum=1, mode=4, offset=3)
137
FermiFSIndex(occnum=1, mode=7, offset=6)
138
```
139
See also [`find_occupied_mode`](@ref),
140
[`SingleComponentFockAddress`](@ref).
141
"""
142
occupied_modes
143

144
"""
145
    unoccupied_modes(::FermiFS)
146

147
Return a lazy iterator over all unoccupied modes in an Fermi type address. Iterates over
148
over [`FermiFSIndex`](@ref)s for [`FermiFS`](@ref).
149
See [`unoccupied_mode_map`](@ref) for an eager version.
150

151
# Example
152

153
```jldoctest
154
julia> f = FermiFS((1,1,0,1,0,0,1));
155

156
julia> foreach(println, unoccupied_modes(f))
157
FermiFSIndex(occnum=0, mode=3, offset=2)
158
FermiFSIndex(occnum=0, mode=5, offset=4)
159
FermiFSIndex(occnum=0, mode=6, offset=5)
160
```
161
See also [`find_occupied_mode`](@ref), [`occupied_modes`](@ref).
162
"""
163
unoccupied_modes
164

165
"""
166
    excitation(addr::SingleComponentFockAddress, creations::NTuple, destructions::NTuple)
167

168
Generate an excitation on address `addr` by applying `creations` and `destructions`, which
169
are tuples of the appropriate address indices (i.e. [`BoseFSIndex`](@ref) for bosons, or
170
[`FermiFSIndex`](@ref) for fermions).
171

172
```math
173
a^†_{c_1} a^†_{c_2} \\ldots a_{d_1} a_{d_2} \\ldots |\\mathrm{addr}\\rangle \\to
174
α|\\mathrm{naddr}\\rangle
175
```
176

177
Returns the new address `naddr` and the factor `α`. The value of `α` is given by the square
178
root of the product of mode occupations before destruction and after creation. If the
179
excitation is illegal, returns an arbitrary address and the value `0.0`.
180

181
# Example
182

183
```jldoctest
184
julia> f = FermiFS(1,1,0,0,1,1,1,1)
185
FermiFS{6,8}(1, 1, 0, 0, 1, 1, 1, 1)
186

187
julia> i, j, k, l = find_mode(f, (3,4,2,5))
188
(FermiFSIndex(occnum=0, mode=3, offset=2), FermiFSIndex(occnum=0, mode=4, offset=3), FermiFSIndex(occnum=1, mode=2, offset=1), FermiFSIndex(occnum=1, mode=5, offset=4))
189

190
julia> excitation(f, (i,j), (k,l))
191
(FermiFS{6,8}(1, 0, 1, 1, 0, 1, 1, 1), -1.0)
192
```
193

194
See [`SingleComponentFockAddress`](@ref).
195
"""
196
excitation
197

198

199
"""
200
    ModeMap <: AbstractVector
201

202
A unified storage structure for indices of `SingleComponentFockAddress`.
203
It stores the FSIndex of corresponding address as an `AbstractVector` compatible with
204
[`excitation`](@ref) - [`BoseFSIndex`](@ref) or [`FermiFSIndex`](@ref).
205

206
This struct is not intended to be constructed directly. Use [`occupied_mode_map`](@ref) or
207
[`unoccupied_mode_map`](@ref) to obtain an instance.
208

209
See also [`SingleComponentFockAddress`](@ref).
210
"""
211
struct ModeMap{N,T} <: AbstractVector{T}
212
    indices::SVector{N,T} # N = min(N, M)
98,770,555✔
213
    length::Int
214
end
215

216
Base.eltype(::ModeMap{N,T}) where {N,T} = T
187,130✔
217

218
Base.@deprecate OccupiedModeMap(addr) occupied_mode_map(addr)
219

220

221
"""
222
    occupied_mode_map(addr) <: AbstractVector
223

224
Get a map of occupied modes in address as an `AbstractVector` of indices compatible with
225
[`excitation`](@ref) - [`BoseFSIndex`](@ref) or [`FermiFSIndex`](@ref).
226

227
`occupied_mode_map(addr)[i]` contains the index for the `i`-th occupied mode.
228
This is useful because repeatedly looking for occupied modes with
229
[`find_occupied_mode`](@ref) can be time-consuming.
230
`occupied_mode_map(addr)` is an eager version of the iterator returned by
231
[`occupied_modes`](@ref). It is similar to [`onr`](@ref) but contains more information.
232

233
# Example
234

235
```jldoctest
236
julia> b = BoseFS(10, 0, 0, 0, 2, 0, 1)
237
BoseFS{13,7}(10, 0, 0, 0, 2, 0, 1)
238

239
julia> mb = occupied_mode_map(b)
240
3-element Rimu.BitStringAddresses.ModeMap{7, BoseFSIndex}:
241
 BoseFSIndex(occnum=10, mode=1, offset=0)
242
 BoseFSIndex(occnum=2, mode=5, offset=14)
243
 BoseFSIndex(occnum=1, mode=7, offset=18)
244

245
julia> f = FermiFS(1,1,1,1,0,0,1,0,0)
246
FermiFS{5,9}(1, 1, 1, 1, 0, 0, 1, 0, 0)
247

248
julia> mf = occupied_mode_map(f)
249
5-element Rimu.BitStringAddresses.ModeMap{5, FermiFSIndex}:
250
 FermiFSIndex(occnum=1, mode=1, offset=0)
251
 FermiFSIndex(occnum=1, mode=2, offset=1)
252
 FermiFSIndex(occnum=1, mode=3, offset=2)
253
 FermiFSIndex(occnum=1, mode=4, offset=3)
254
 FermiFSIndex(occnum=1, mode=7, offset=6)
255

256
julia> mf == collect(occupied_modes(f))
257
true
258

259
julia> dot(mf, mb)
260
11
261

262
julia> dot(mf, 1:20)
263
17
264
```
265
See also [`dot`](@ref Main.Hamiltonians.dot), [`unoccupied_mode_map`](@ref),
266
[`SingleComponentFockAddress`](@ref).
267
"""
268
function occupied_mode_map(addr::SingleComponentFockAddress{N,M}) where {N,M}
353✔
269
    modes = occupied_modes(addr)
98,770,481✔
270
    T = eltype(modes)
98,770,481✔
271
    # There are at most N occupied modes. This could be also @generated for cases where N ≫ M
272
    L = ismissing(N) ? M : min(N, M)
98,770,481✔
273
    indices = MVector{L,T}(undef)
98,770,481✔
274
    i = 0
98,770,481✔
275
    for index in modes
197,541,004✔
276
        i += 1
410,297,235✔
277
        @inbounds indices[i] = index
410,297,235✔
278
    end
721,824,355✔
279
    return ModeMap(SVector(indices), i)
98,770,481✔
280
end
281

282
Base.size(om::ModeMap) = (om.length,)
482,997,141✔
283
function Base.getindex(om::ModeMap, i)
×
284
    @boundscheck 1 ≤ i ≤ om.length || throw(BoundsError(om, i))
269,808,841✔
285
    return om.indices[i]
269,808,841✔
286
end
287

288
"""
289
    abstract type ModeIterator
290

291
Iterator over modes with `eltype` [`BoseFSIndex`](@ref) or
292
[`FermiFSIndex`](@ref). A subtype of this should be returned when calling
293
[`occupied_modes`](@ref) on a Fock state.
294
"""
295
abstract type ModeIterator end
296

297
"""
298
    dot(map::ModeMap, vec::AbstractVector)
299
    dot(map1::ModeMap, map2::ModeMap)
300
Dot product extracting mode occupation numbers from an [`ModeMap`](@ref) similar
301
to [`onr`](@ref).
302

303
```jldoctest
304
julia> b = BoseFS(10, 0, 0, 0, 2, 0, 1)
305
BoseFS{13,7}(10, 0, 0, 0, 2, 0, 1)
306

307
julia> mb = occupied_mode_map(b)
308
3-element Rimu.BitStringAddresses.ModeMap{7, BoseFSIndex}:
309
 BoseFSIndex(occnum=10, mode=1, offset=0)
310
 BoseFSIndex(occnum=2, mode=5, offset=14)
311
 BoseFSIndex(occnum=1, mode=7, offset=18)
312

313
julia> dot(mb, 1:7)
314
27
315

316
julia> mb⋅(1:7) == onr(b)⋅(1:7)
317
true
318
```
319
See also [`SingleComponentFockAddress`](@ref).
320
"""
321
function LinearAlgebra.dot(map::ModeMap, vec::AbstractVector)
51✔
322
    value = zero(eltype(vec))
24,950,003✔
323
    for index in map
49,900,006✔
324
        value += vec[index.mode] * index.occnum
127,345,363✔
325
    end
229,740,723✔
326
    return value
24,950,003✔
327
end
328
LinearAlgebra.dot(vec::AbstractVector, map::ModeMap) = dot(map, vec)
127,311,471✔
329

330
# Defined for consistency. Could also be used to compute cross-component interactions in
331
# real space.
332
function LinearAlgebra.dot(map1::ModeMap, map2::ModeMap)
1✔
333
    i = j = 1
609,640✔
334
    value = 0
609,640✔
335
    while i ≤ length(map1) && j ≤ length(map2)
2,380,962✔
336
        index1 = map1[i]
1,771,322✔
337
        index2 = map2[j]
1,771,322✔
338
        if index1.mode == index2.mode
1,771,322✔
339
            value += index1.occnum * index2.occnum
429,246✔
340
            i += 1
429,246✔
341
            j += 1
429,246✔
342
        elseif index1.mode < index2.mode
1,342,076✔
343
            i += 1
791,375✔
344
        else
345
            j += 1
550,701✔
346
        end
347
    end
1,771,322✔
348
    return value
609,640✔
349
end
350

351
"""
352
    parse_address(str)
353

354
Parse the compact representation of a Fock state address.
355
"""
356
function parse_address(str)
304✔
357
    # CompositeFS
358
    m = match(r"⊗", str)
304✔
359
    if !isnothing(m)
311✔
360
        if !isnothing(match(r"[↓⇅]", str))
9✔
361
            throw(ArgumentError("invalid fock state format \"$str\""))
2✔
362
        else
363
            return CompositeFS(map(parse_address, split(str, r" *⊗ *"))...)
5✔
364
        end
365
    end
366
    # FermiFS2C
367
    m = match(r"[↓⇅]", str)
297✔
368
    if !isnothing(m)
320✔
369
        m = match(r"\|([↑↓⇅⋅ ]+)⟩", str)
23✔
370
        if isnothing(m)
46✔
371
            throw(ArgumentError("invalid fock state format \"$str\""))
×
372
        else
373
            chars = filter(!=(' '), Vector{Char}(m.captures[1]))
46✔
374
            f1 = FermiFS((chars .== '↑') .| (chars .== '⇅'))
23✔
375
            f2 = FermiFS((chars .== '↓') .| (chars .== '⇅'))
23✔
376
            return CompositeFS(f1, f2)
23✔
377
        end
378
    end
379
    # Sparse BoseFS
380
    m = match(r"\|b *([0-9]+): *([ 0-9]+)⟩", str)
274✔
381
    if !isnothing(m)
286✔
382
        particles = parse.(Int, filter(!isempty, split(m.captures[2], r" +")))
12✔
383
        return BoseFS(parse(Int, m.captures[1]), zip(particles, fill(1, length(particles))))
12✔
384
    end
385
    # Sparse FermiFS
386
    m = match(r"\|f *([0-9]+): *([ 0-9]+)⟩", str)
262✔
387
    if !isnothing(m)
284✔
388
        particles = parse.(Int, filter(!isempty, split(m.captures[2], r" +")))
22✔
389
        return FermiFS(parse(Int, m.captures[1]), zip(particles, fill(1, length(particles))))
22✔
390
    end
391
    # OccupationNumberFS
392
    m = match(r"\|([ 0-9]+)⟩{[0-9]*}", str)
240✔
393
    if !isnothing(m)
253✔
394
        m2 = match(r"{([0-9]+)}", str)
13✔
395
        if isnothing(m2) # empty braces defaults to UInt8
24✔
396
            BITS = 8
2✔
397
        else
398
            BITS = parse(Int, m2.captures[1])
11✔
399
        end
400
        T = if BITS ≤ 8
13✔
401
            UInt8
8✔
402
        elseif BITS ≤ 16
5✔
403
            UInt16
1✔
404
        elseif BITS ≤ 32
4✔
405
            UInt32
1✔
406
        elseif BITS ≤ 64
3✔
407
            UInt64
1✔
408
        elseif BITS ≤ 128
2✔
409
            UInt128
1✔
410
        else
411
            throw(ArgumentError("invalid Fock state format \"$str\""))
22✔
412
        end
413
        t = Tuple(parse.(T, split(m.captures[1], r" +")))
12✔
414
        return OccupationNumberFS(SVector(t))
12✔
415
    end
416
    m = match(r"\|([ 0-9]+)⟩{", str) # anything else that has a curly brace
227✔
417
    if !isnothing(m)
228✔
418
        throw(ArgumentError("invalid Fock state format \"$str\""))
1✔
419
    end
420

421
    # BoseFS
422
    m = match(r"\|([ 0-9]+)⟩", str)
226✔
423
    if !isnothing(m)
335✔
424
        return BoseFS(parse.(Int, split(m.captures[1], r" +")))
109✔
425
    end
426
    # Single FermiFS
427
    m = match(r"\|([ ⋅↑]+)⟩", str)
117✔
428
    if !isnothing(m)
234✔
429
        chars = filter(!=(' '), Vector{Char}(m.captures[1]))
234✔
430
        return FermiFS(chars .== '↑')
117✔
431
    end
432
    throw(ArgumentError("invalid Fock state format \"$str\""))
×
433
end
434

435
"""
436
    fs"\$(string)"
437

438
Parse the compact representation of a Fock state.
439
Useful for copying the printout from a vector to the REPL.
440

441
# Example
442

443
```jldoctest
444
julia> DVec(BoseFS{3,4}(0, 1, 2, 0) => 1)
445
DVec{BoseFS{3, 4, BitString{6, 1, UInt8}},Int64} with 1 entry, style = IsStochasticInteger{Int64}()
446
  fs"|0 1 2 0⟩" => 1
447

448
julia> fs"|0 1 2 0⟩" => 1 # Copied from above printout
449
BoseFS{3,4}(0, 1, 2, 0) => 1
450

451
julia> fs"|1 2 3⟩⊗|0 1 0⟩" # composite bosonic Fock state
452
CompositeFS(
453
  BoseFS{6,3}(1, 2, 3),
454
  BoseFS{1,3}(0, 1, 0),
455
)
456

457
julia> fs"|↑↓↑⟩" # construct a fermionic Fock state
458
CompositeFS(
459
  FermiFS{2,3}(1, 0, 1),
460
  FermiFS{1,3}(0, 1, 0),
461
)
462

463
julia> s = fs"|0 1 2 0⟩{}" # constructing OccupationNumberFS with default UInt8 container
464
OccupationNumberFS{4, UInt8}(0, 1, 2, 0)
465

466
julia> [s] # prints out with the signifcant number of bits specified in braces
467
1-element Vector{OccupationNumberFS{4, UInt8}}:
468
 fs"|0 1 2 0⟩{8}"
469
```
470

471
See also [`FermiFS`](@ref), [`BoseFS`](@ref), [`CompositeFS`](@ref), [`FermiFS2C`](@ref),
472
[`OccupationNumberFS`](@ref).
473
"""
474
macro fs_str(str)
99✔
475
    return parse_address(str)
99✔
476
end
477

478
"""
479
    print_address(io::IO, address)
480

481
Print the `address` to `io`. If `get(io, :compact, false) == true`, the printed form should
482
be parsable by [`parse_address`](@ref).
483

484
This function is used to implement `Base.show` for [`AbstractFockAddress`](@ref).
485
"""
486
print_address
487

488
function Base.show(io::IO, add::AbstractFockAddress)
777✔
489
    if get(io, :typeinfo, nothing) == typeof(add) || get(io, :compact, false)
2,559✔
490
        print(io, "fs\"")
711✔
491
        print_address(io, add; compact=true)
711✔
492
        print(io, "\"")
711✔
493
    else
494
        print_address(io, add; compact=false)
617✔
495
    end
496
end
497

498
function onr_sparse_string(o)
499
    ps = map(p -> p[1] => p[2], Iterators.filter(p -> !iszero(p[2]), enumerate(o)))
19,362✔
500
    return join(ps, ", ")
90✔
501
end
502

503
###
504
### Boson stuff
505
###
506
"""
507
    BoseFSIndex
508

509
Struct used for indexing and performing [`excitation`](@ref)s on a [`BoseFS`](@ref).
510

511
## Fields:
512

513
* `occnum`: the occupation number.
514
* `mode`: the index of the mode.
515
* `offset`: the position of the mode in the address. This is the bit offset of the mode when
516
 the address is represented by a bitstring, and the position in the list when it is
517
 represented by `SortedParticleList`.
518

519
"""
520
Base.@kwdef struct BoseFSIndex<:FieldVector{3,Int}
2✔
521
    occnum::Int
1,572,194,610✔
522
    mode::Int
523
    offset::Int
524
end
525

526
function Base.show(io::IO, i::BoseFSIndex)
527
    @unpack occnum, mode, offset = i
54✔
528
    print(io, "BoseFSIndex(occnum=$occnum, mode=$mode, offset=$offset)")
54✔
529
end
530
Base.show(io::IO, ::MIME"text/plain", i::BoseFSIndex) = show(io, i)
9✔
531

532
"""
533
    BoseOccupiedModes{C,S<:BoseFS}
534

535
Iterator for occupied modes in [`BoseFS`](@ref). The definition of `iterate` is dispatched
536
on the storage type.
537

538
See [`occupied_modes`](@ref).
539

540
Defining `Base.length` and `Base.iterate` for this struct is a part of the interface for an
541
underlying storage format used by [`BoseFS`](@ref).
542
"""
543
struct BoseOccupiedModes{N,M,S} <: ModeIterator
544
    storage::S
805,157,523✔
545
end
546
Base.eltype(::BoseOccupiedModes) = BoseFSIndex
313,667,388✔
547

548
# Apply destruction operator to BoseFSIndex.
549
@inline _destroy(d, index::BoseFSIndex) = @set index.occnum -= (d.mode == index.mode)
953,599,484✔
550
@inline _destroy(d) = Base.Fix1(_destroy, d)
366,274,185✔
551
# Apply creation operator to BoseFSIndex.
552
@inline _create(c, index::BoseFSIndex) = @set index.occnum += (c.mode == index.mode)
245,460,092✔
553
@inline _create(c) = Base.Fix1(_create, c)
708,139,392✔
554

555
"""
556
    bose_excitation_value(
557
        creations::NTuple{_,BoseFSIndex}, destructions::NTuple{_,::BoseFSIndex}
558
    ) -> Int
559

560
Compute the squared value of an excitation from indices. Starts by applying all destruction
561
operators, and then applying all creation operators. The operators must be given in reverse
562
order. Will return 0 if move is illegal.
563
"""
564
@inline bose_excitation_value(::Tuple{}, ::Tuple{}) = 1
×
565
@inline function bose_excitation_value((c, cs...)::NTuple{<:Any,BoseFSIndex}, ::Tuple{})
566
    return bose_excitation_value(map(_create(c), cs), ()) * (c.occnum + 1)
708,139,392✔
567
end
568
@inline function bose_excitation_value(
569
    creations::NTuple{<:Any,BoseFSIndex}, (d, ds...)::NTuple{<:Any,BoseFSIndex}
570
)
571
    return bose_excitation_value(map(_destroy(d), creations), map(_destroy(d), ds)) * d.occnum
708,139,392✔
572
end
573

574
"""
575
    from_bose_onr(::Type{B}, onr::AbstractArray) -> B
576

577
Convert array `onr` to type `B`. It is safe to assume `onr` contains a valid
578
occupation-number representation array. The checks are preformed in the [`BoseFS`](@ref)
579
constructor.
580

581
This function is a part of the interface for an underlying storage format used by
582
[`BoseFS`](@ref).
583
"""
584
from_bose_onr
585

586
"""
587
    to_bose_onr(bs::B) -> SVector
588

589
Convert `bs` to a static vector in the occupation number representation format.
590

591
This function is a part of the interface for an underlying storage format used by
592
[`BoseFS`](@ref).
593
"""
594
to_bose_onr
595

596
"""
597
    bose_excitation(
598
        bs::B, creations::NTuple{N,BoseFSIndex}, destructions::NTuple{N,BoseFSIndex}
599
    ) -> Tuple{B,Float64}
600

601
Perform excitation as if `bs` was a bosonic address. See also
602
[`bose_excitation_value`](@ref).
603

604
This function is a part of the interface for an underlying storage format used by
605
[`BoseFS`](@ref).
606
"""
607
bose_excitation
608

609
"""
610
    bose_num_occupied_modes(bs::B)
611

612
Return the number of occupied modes, if `bs` represents a bosonic address.
613

614
This function is a part of the interface for an underlying storage format used by
615
[`BoseFS`](@ref).
616
"""
617
bose_num_occupied_modes
618

619
###
620
### Fermion stuff
621
###
622
"""
623
    FermiFSIndex
624

625
Struct used for indexing and performing [`excitation`](@ref)s on a [`FermiFS`](@ref).
626

627
## Fields:
628

629
* `occnum`: the occupation number.
630
* `mode`: the index of the mode.
631
* `offset`: the position of the mode in the address. This is `mode - 1` when the address is
632
  represented by a bitstring, and the position in the list when using `SortedParticleList`.
633

634
"""
635
Base.@kwdef struct FermiFSIndex<:FieldVector{3,Int}
636
    occnum::Int
57,815,815✔
637
    mode::Int
638
    offset::Int
639
end
640

641
function Base.show(io::IO, i::FermiFSIndex)
642
    @unpack occnum, mode, offset = i
35✔
643
    print(io, "FermiFSIndex(occnum=$occnum, mode=$mode, offset=$offset)")
35✔
644
end
645
Base.show(io::IO, ::MIME"text/plain", i::FermiFSIndex) = show(io, i)
8✔
646

647
"""
648
    FermiOccupiedModes{N,S<:BitString}
649

650
Iterator over occupied modes in address. `N` is the number of fermions. See [`occupied_modes`](@ref).
651
"""
652
struct FermiOccupiedModes{N,S} <: ModeIterator
653
    storage::S
2,040,930✔
654
end
655

656
Base.length(::FermiOccupiedModes{N}) where {N} = N
53✔
657
Base.eltype(::FermiOccupiedModes) = FermiFSIndex
1,199,328✔
658

659
"""
660
    FermiUnoccupiedModes{N}
661

662
Iterator over unoccupied modes in address. `N` is the number of unoccupied orbitals. See [`unoccupied_modes`](@ref).
663
"""
664
struct FermiUnoccupiedModes{N,S} <: ModeIterator
665
    storage::S
139✔
666
end
667
Base.length(::FermiUnoccupiedModes{N}) where {N} = N
64✔
668
Base.eltype(::FermiUnoccupiedModes) = FermiFSIndex
78✔
669

670
"""
671
    from_fermi_onr(::Type{B}, onr) -> B
672

673
Convert array `onr` to type `B`. It is safe to assume `onr` contains a valid
674
occupation-number representation array. The checks are preformed in the [`FermiFS`](@ref)
675
constructor.
676

677
This function is a part of the interface for an underlying storage format used by
678
[`FermiFS`](@ref).
679
"""
680
from_fermi_onr
681

682
"""
683
    fermi_find_mode(bs::B, i::Integer) -> FermiFSIndex
684

685
Find `i`-th mode in `bs` if `bs` is a fermionic address. Should return an appropriately
686
formatted [`FermiFSIndex`](@ref).
687

688
This function is a part of the interface for an underlying storage format used by
689
[`FermiFS`](@ref).
690
"""
691
fermi_find_mode
692

693
"""
694
    fermi_excitation(
695
        bs::B, creations::NTuple{N,FermiFSIndex}, destructions::NTuple{N,FermiFSIndex}
696
    ) -> Tuple{B,Float64}
697

698
Perform excitation as if `bs` was a fermionic address.
699

700
This function is a part of the interface for an underlying storage format used by
701
[`FermiFS`](@ref).
702
"""
703
fermi_excitation
704

705
###
706
### General
707
###
708
function LinearAlgebra.dot(occ_a::ModeIterator, occ_b::ModeIterator)
×
709
    (n_a, i_a, _), st_a = iterate(occ_a)
×
710
    (n_b, i_b, _), st_b = iterate(occ_b)
×
711

712
    acc = 0
×
713
    while true
×
714
        if i_a > i_b
×
715
            # b is behind and needs to do a step
716
            iter = iterate(occ_b, st_b)
×
717
            isnothing(iter) && return acc
×
718
            (n_b, i_b, _), st_b = iter
×
719
        elseif i_a < i_b
×
720
            # a is behind and needs to do a step
721
            iter = iterate(occ_a, st_a)
×
722
            isnothing(iter) && return acc
×
723
            (n_a, i_a, _), st_a = iter
×
724
        else
725
            # a and b are at the same position
726
            acc += n_a * n_b
×
727
            # now both need to do a step
728
            iter = iterate(occ_a, st_a)
×
729
            isnothing(iter) && return acc
×
730
            (n_a, i_a, _), st_a = iter
×
731
            iter = iterate(occ_b, st_b)
×
732
            isnothing(iter) && return acc
×
733
            (n_b, i_b, _), st_b = iter
×
734
        end
735
    end
×
736
end
737

738
function sparse_to_onr(M, pairs)
641✔
739
    onr = spzeros(Int, M)
643✔
740
    for (k, v) in pairs
681✔
741
        v ≥ 0 || throw(ArgumentError("Invalid pair `$k=>$v`: particle number negative"))
2,038✔
742
        0 < k ≤ M || throw(ArgumentError("Invalid pair `$k => $v`: key of of range `1:$M`"))
2,036✔
743
        onr[k] += v
2,032✔
744
    end
2,466✔
745
    return onr
638✔
746
end
747

748
"""
749
    OccupiedPairsMap(addr::SingleComponentFockAddress) <: AbstractVector
750

751
Get a map of all distinct pairs of indices in `addr`. Pairs involving
752
multiply-occupied modes are counted once, (including self-pairing).
753
This is useful for cases where identifying pairs of particles for eg.
754
interactions is not well-defined or efficient to do on the fly.
755
This is an eager iterator whose elements are a tuple of particle indices that
756
can be given to `excitation`
757

758
# Example
759

760
```jldoctest
761
julia> addr = BoseFS(10, 0, 0, 0, 2, 0, 1)
762
BoseFS{13,7}(10, 0, 0, 0, 2, 0, 1)
763

764
julia> pairs = OccupiedPairsMap(addr)
765
5-element OccupiedPairsMap{78, Tuple{BoseFSIndex, BoseFSIndex}}:
766
 (BoseFSIndex(occnum=10, mode=1, offset=0), BoseFSIndex(occnum=10, mode=1, offset=0))
767
 (BoseFSIndex(occnum=2, mode=5, offset=14), BoseFSIndex(occnum=2, mode=5, offset=14))
768
 (BoseFSIndex(occnum=2, mode=5, offset=14), BoseFSIndex(occnum=10, mode=1, offset=0))
769
 (BoseFSIndex(occnum=1, mode=7, offset=18), BoseFSIndex(occnum=10, mode=1, offset=0))
770
 (BoseFSIndex(occnum=1, mode=7, offset=18), BoseFSIndex(occnum=2, mode=5, offset=14))
771

772
julia> excitation(addr, pairs[2], pairs[4])
773
(BoseFS{13,7}(9, 0, 0, 0, 4, 0, 0), 10.954451150103322)
774
```
775

776
See also [`occupied_mode_map`](@ref).
777
"""
778
struct OccupiedPairsMap{N,T} <: AbstractVector{T}
779
    pairs::SVector{N,T}
66✔
780
    length::Int
781
end
782

783
function OccupiedPairsMap(addr::SingleComponentFockAddress{N}) where {N}
66✔
784
    omm = occupied_mode_map(addr)
137✔
785
    T = eltype(omm)
66✔
786
    P = N * (N - 1) ÷ 2
66✔
787
    pairs = MVector{P,Tuple{T,T}}(undef)
66✔
788
    a = 0
66✔
789
    for i in eachindex(omm)
66✔
790
        p_i = omm[i]
137✔
791
        if p_i.occnum > 1
137✔
792
            a += 1
46✔
793
            @inbounds pairs[a] = (p_i, p_i)
46✔
794
        end
795
        for j in 1:i-1
137✔
796
            p_j = omm[j]
93✔
797
            a += 1
93✔
798
            @inbounds pairs[a] = (p_i, p_j)
93✔
799
        end
115✔
800
    end
208✔
801

802
    return OccupiedPairsMap(SVector(pairs), a)
66✔
803
end
804

805
Base.size(opm::OccupiedPairsMap) = (opm.length,)
1,348✔
806
function Base.getindex(opm::OccupiedPairsMap, i)
2✔
807
    @boundscheck 1 ≤ i ≤ opm.length || throw(BoundsError(opm, i))
2,251✔
808
    return opm.pairs[i]
2,251✔
809
end
810

811
struct LazyEachMode{M,A<:SingleComponentFockAddress{<:Any,M},I} <: AbstractVector{I}
812
    address::A
813

814
    function LazyEachMode(addr::SingleComponentFockAddress)
815
        I = typeof(find_mode(addr, 1))
146✔
816
        return new{num_modes(addr),typeof(addr),I}(addr)
146✔
817
    end
818
end
819

820
Base.size(::LazyEachMode{M}) where {M} = (M,)
292✔
821
Base.getindex(em::LazyEachMode, i) = find_mode(em.address, i)
15,060✔
822
Base.show(io::IO, em::LazyEachMode) = print(io, "each_mode($(em.address))")
×
823

824
"""
825
    each_mode(address::SingleComponentFockAddress)
826

827
Return an iterator that iterates over each mode in an address. Iterates value of either
828
[`BoseFSIndex`](@ref) or [`FermiFSIndex`](@ref).
829
"""
830
function each_mode(addr::SingleComponentFockAddress)
146✔
831
    return LazyEachMode(addr)
146✔
832
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

© 2025 Coveralls, Inc