Skip to content

Commit

Permalink
Vacuum constructors for OccupationNumberFS (#308)
Browse files Browse the repository at this point in the history
# Vacuum constructors for `OccupationNumberFS`

## New features
- Convenience constructors for vacuum `OccupationNumberFS` 

## Bug fixes
- Fix error when trying to construct an `OccupationNumberFS` from
`BoseFS` with zero particles.

---------

Co-authored-by: jamie-tay <jamiet96@gmail.com>
  • Loading branch information
joachimbrand and jamie-tay authored Feb 6, 2025
1 parent 0be5a04 commit 3d7a7f6
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 32 deletions.
12 changes: 8 additions & 4 deletions src/BitStringAddresses/bosefs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ automatically based on the properties of the address.
particles in `bs` is equal to `N`.
* [`@fs_str`](@ref): Addresses are sometimes printed in a compact manner. This
representation can also be used as a constructor. See the last example below.
representation can also be used as a constructor. See the examples below.
# Examples
```jldoctest
julia> BoseFS{6,5}(0, 1, 2, 3, 0)
BoseFS{6,5}(0, 1, 2, 3, 0)
julia> BoseFS([abs(i - 3) ≤ 1 ? i - 1 : 0 for i in 1:5])
julia> BoseFS(abs(i - 3) ≤ 1 ? i - 1 : 0 for i in 1:5)
BoseFS{6,5}(0, 1, 2, 3, 0)
julia> BoseFS(5, 2 => 1, 3 => 2, 4 => 3)
Expand Down Expand Up @@ -97,7 +97,8 @@ function BoseFS{N,M}(onr::Union{AbstractArray{<:Integer},NTuple{M,<:Integer}}) w
end
return BoseFS{N,M,S}(from_bose_onr(S, onr))
end
function BoseFS(onr::Union{AbstractArray,Tuple})
function BoseFS(onr) # single argument constructor
onr = Tuple(onr)
M = length(onr)
N = sum(onr)
return BoseFS{N,M}(onr)
Expand All @@ -106,10 +107,13 @@ BoseFS(vals::Integer...) = BoseFS(vals) # specify occupation numbers
BoseFS(val::Integer) = BoseFS((val,)) # single mode address
BoseFS{N,M}(vals::Integer...) where {N,M} = BoseFS{N,M}(vals)

# Sparse constructors
BoseFS(M::Integer, pairs::Pair...) = BoseFS(M, pairs)
BoseFS(M::Integer, pairs) = BoseFS(sparse_to_onr(M, pairs))
BoseFS{N,M}(pairs::Pair...) where {N,M} = BoseFS{N,M}(pairs)
BoseFS{N,M}(pairs) where {N,M} = BoseFS{N,M}(sparse_to_onr(M, pairs))
BoseFS(pairs::Pair...) = throw(ArgumentError("number of modes must be provided"))


function print_address(io::IO, b::BoseFS{N,M}; compact=false) where {N,M}
if compact && b.bs isa SortedParticleList
Expand Down Expand Up @@ -259,7 +263,7 @@ end
Compute the new address of a hopping event for the Hubbard model. Returns the new
address and the square root of product of occupation numbers of the involved modes
multiplied by a term consistent with boundary condition as the `value`.
multiplied by a term consistent with boundary condition as the `value`.
The following boundary conditions are supported:
* `:periodic`: hopping over the boundary gives does not change the `value`.
Expand Down
6 changes: 4 additions & 2 deletions src/BitStringAddresses/fermifs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ chosen automatically based on the properties of the address.
particles in `bs` is equal to `N`, or whether each mode only contains one particle.
* [`@fs_str`](@ref): Addresses are sometimes printed in a compact manner. This
representation can also be used as a constructor. See the last example below.
representation can also be used as a constructor. See the examples below.
# Examples
Expand Down Expand Up @@ -95,7 +95,8 @@ function FermiFS{N,M}(onr::Union{AbstractArray{<:Integer},NTuple{M,<:Integer}})
end
return FermiFS{N,M,S}(from_fermi_onr(S, onr))
end
function FermiFS(onr::Union{AbstractArray,Tuple})
function FermiFS(onr)
onr = Tuple(onr)
M = length(onr)
N = sum(onr)
return FermiFS{N,M}(onr)
Expand All @@ -109,6 +110,7 @@ FermiFS(M::Integer, pairs::Pair...) = FermiFS(M, pairs)
FermiFS(M::Integer, pairs) = FermiFS(sparse_to_onr(M, pairs))
FermiFS{N,M}(pairs::Vararg{Pair,N}) where {N,M} = FermiFS{N,M}(pairs)
FermiFS{N,M}(pairs) where {N,M} = FermiFS{N,M}(sparse_to_onr(M, pairs))
FermiFS(pairs::Pair...) = throw(ArgumentError("number of modes must be provided"))

function print_address(io::IO, f::FermiFS{N,M}; compact=false) where {N,M}
if compact && f.bs isa SortedParticleList
Expand Down
75 changes: 60 additions & 15 deletions src/BitStringAddresses/occupationnumberfs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
OccupationNumberFS{M,T} <: SingleComponentFockAddress
Address type that stores the occupation numbers of a single component bosonic Fock state
with `M` modes. The occupation numbers must fit into the type `T <: Unsigned`. The number of
particles is runtime data, and can be retrieved with `num_particles(address)`.
particles is runtime data, and can be retrieved with [`num_particles(address)`](@ref).
# Constructors
- `OccupationNumberFS(val::Integer...)`: Construct from occupation numbers. Must be
< 256 to fit into `UInt8`.
- `OccupationNumberFS(M, pairs::Pair...)`: Construct from a sparse representation with
`M` modes and pairs of mode index and occupation number.
- `OccupationNumberFS{[M,T]}(onr)`: Construct from collection `onr` with `M` occupation
numbers with type `T`. If unspecified, the type `T` of the occupation numbers is inferred
from the type of the arguments.
numbers with optional type `T`. `onr` may be a tuple, an array, or a generator.
- `OccupationNumberFS{M[,T]}()`: Construct a vacuum state with `M` modes. If `T` is
unspecified, `UInt8` is used.
- `OccupationNumberFS(fs::BoseFS)`: Construct from [`BoseFS`](@ref).
- With shortform macro [`@fs_str`](@ref). Specify the number of
- With short form macro [`@fs_str`](@ref). Specify the number of
significant bits in braces. See example below.
# Examples
Expand All @@ -24,36 +27,78 @@ true
julia> num_particles(ofs)
6
julia> OccupationNumberFS{5}() # vacuum state with 5 modes
OccupationNumberFS{5, UInt8}(0, 0, 0, 0, 0)
julia> OccupationNumberFS(i for i in 1:3) # use list comprehension
OccupationNumberFS{3, UInt8}(1, 2, 3)
julia> OccupationNumberFS(4, 1=>2, 3=>4) # sparse constructor
OccupationNumberFS{4, UInt8}(2, 0, 4, 0)
julia> OccupationNumberFS(5, i=>i^2 for i in 2:4) # sparse with list comprehension
OccupationNumberFS{5, UInt8}(0, 4, 9, 16, 0)
```
"""
struct OccupationNumberFS{M,T<:Unsigned} <: SingleComponentFockAddress{missing,M}
onr::SVector{M,T}

function OccupationNumberFS{M,T}(args...) where {M,T<:Unsigned}
return new(SVector{M,T}(args...))
end
end

function OccupationNumberFS{M,T}(args...) where {M,T}
return OccupationNumberFS(SVector{M,T}(args...))
function OccupationNumberFS(sv::SVector{M,T}) where {M,T<:Unsigned}
return OccupationNumberFS{M,T}(sv)
end

function OccupationNumberFS(args...)
sv = SVector(args...)
all(isinteger, sv) || throw(ArgumentError("all arguments must be integers"))
all(x -> x 0, sv) || throw(ArgumentError("all arguments must be non-negative"))
all(x -> x < 256, sv) || throw(ArgumentError("arguments don't fit in a byte, specify type"))
return OccupationNumberFS(SVector{length(sv),UInt8}(args...))
function OccupationNumberFS(arg)
t = Tuple(arg)
return OccupationNumberFS{length(t)}(t)
end

function OccupationNumberFS(args::Number...)
return OccupationNumberFS(SVector(args))
end
OccupationNumberFS(arg::Number) = OccupationNumberFS{1}(tuple(arg)) # to resolve ambiguity
function OccupationNumberFS{M}(args::Number...) where {M}
return OccupationNumberFS{M}(SVector{M}(args))
end

function OccupationNumberFS{M}(args...) where M
sv = SVector{M}(args...)
t = Tuple(args...)
if all(x -> isa(x, Pair), t)
return OccupationNumberFS{M}(t)
end
sv = SVector{M}(t)
all(isinteger, sv) || throw(ArgumentError("all arguments must be integers"))
all(x -> x 0, sv) || throw(ArgumentError("all arguments must be non-negative"))
all(x -> x < 256, sv) || throw(ArgumentError("arguments don't fit in a byte, specify type"))
return OccupationNumberFS(SVector{M,UInt8}(args...))
return OccupationNumberFS{M,UInt8}(args...)
end

function OccupationNumberFS(fs::BoseFS{N,M}) where {N,M}
return OccupationNumberFS{M,select_int_type(N)}(onr(fs))
end

# convenience constructors for vacuum state
function OccupationNumberFS{M,T}() where {M,T<:Unsigned}
return OccupationNumberFS(SVector{M,T}(zero(T) for _ in 1:M))
end
OccupationNumberFS{M}() where {M} = OccupationNumberFS{M,UInt8}()

# Sparse constructors
OccupationNumberFS(M::Number, pairs::Pair...) = OccupationNumberFS(Int(M), pairs)
OccupationNumberFS(M::Number, pairs) = OccupationNumberFS(sparse_to_onr(Int(M), pairs))
OccupationNumberFS{M}(pairs::Pair...) where {M} = OccupationNumberFS{M}(pairs)

function OccupationNumberFS{M}(pairs::NTuple{<:Any,Pair}) where {M}
OccupationNumberFS{M}(sparse_to_onr(M, pairs))
end

OccupationNumberFS(pairs::Pair...) = throw(ArgumentError("number of modes must be provided"))

function print_address(io::IO, ofs::OccupationNumberFS{M,T}; compact=false) where {M,T}
if compact
BITS = sizeof(T) * 8
Expand All @@ -68,13 +113,13 @@ Base.reverse(ofs::OccupationNumberFS) = OccupationNumberFS(reverse(ofs.onr))
onr(ofs::OccupationNumberFS) = ofs.onr
function Base.isless(a::OccupationNumberFS{M}, b::OccupationNumberFS{M}) where {M}
# equivalent to `isless(reverse(a.onr), reverse(b.onr))`
# reversing the order here to make it consistent with BoseFS
i = length(a.onr)
while i > 1 && a.onr[i] == b.onr[i]
i -= 1
end
return isless(a.onr[i], b.onr[i])
end
# reversing the order here to make it consistent with BoseFS
Base.:(==)(a::OccupationNumberFS, b::OccupationNumberFS) = a.onr == b.onr
Base.hash(ofs::OccupationNumberFS, h::UInt) = hash(ofs.onr, h)

Expand Down
18 changes: 9 additions & 9 deletions src/BitStringAddresses/sortedparticlelist.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""
select_int_type(M)
select_int_type(n)
Select unsigned integer type that can hold values up to `M`.
Select unsigned integer type that can hold values up to `n`.
"""
function select_int_type(M)
if M 0
throw(ArgumentError("`M` must be positive!"))
elseif M typemax(UInt8)
function select_int_type(n)
if n < 0
throw(ArgumentError("`n` must be a non-negative integer!"))
elseif n typemax(UInt8)
return UInt8
elseif M typemax(UInt16)
elseif n typemax(UInt16)
return UInt16
elseif M typemax(UInt32)
elseif n typemax(UInt32)
return UInt32
else
return UInt64
Expand All @@ -20,7 +20,7 @@ end
"""
SortedParticleList{N,M,T<:Unsigned}
Type for storing sparse fock states. Stores the mode number of each particle as an entry
Type for storing sparse Fock states. Stores the mode number of each particle as an entry
with only its mode stored. The entries are always kept sorted.
Iterating over `SortedParticleList`s yields occupied modes as a tuple of occupation number,
Expand Down
20 changes: 18 additions & 2 deletions test/BitStringAddresses.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ end

@test_throws ArgumentError BoseFS(10, 11 => 1)
@test_throws ArgumentError BoseFS(10, 10 => -1)
@test_throws MethodError BoseFS(10 => 1)
@test_throws ArgumentError BoseFS(10 => 1)
end
@testset "onr" begin
middle_full_onr = onr(middle_full)
Expand Down Expand Up @@ -461,7 +461,9 @@ end
end

@testset "OccupationNumberFS with multiple arguments" begin
@test isa(OccupationNumberFS(1, 2, 3), OccupationNumberFS{3, UInt8})
@test OccupationNumberFS(i for i in 1:3) == OccupationNumberFS(1, 2, 3)
@test isa(OccupationNumberFS{3,UInt32}(i for i in 1:3), OccupationNumberFS{3,UInt32})
@test isa(OccupationNumberFS(1, 2, 3), OccupationNumberFS{3,UInt8})
@test_throws ArgumentError OccupationNumberFS(1.1, 2, 3)
@test_throws ArgumentError OccupationNumberFS(-1, 2, 3)
@test_throws ArgumentError OccupationNumberFS(1, 2, 300)
Expand All @@ -479,6 +481,20 @@ end
@test isa(OccupationNumberFS(fs), OccupationNumberFS{2, UInt8})
fs = BoseFS(1, 333)
@test isa(OccupationNumberFS(fs), OccupationNumberFS{2,UInt16})
fs = BoseFS(0, 0)
@test isa(OccupationNumberFS(fs), OccupationNumberFS{2,UInt8})
end

@testset "OccupationNumberFS with sparse constructor" begin
@test OccupationNumberFS(2, 2=>4) == OccupationNumberFS(0, 4)
@test OccupationNumberFS{2}(2 => 4) == OccupationNumberFS(2, 2 => 4)
@test OccupationNumberFS(5, i => i + 1 for i in 1:3) ==
OccupationNumberFS{5}(i => i + 1 for i in 1:3) ==
OccupationNumberFS{5}(Tuple(i => i + 1 for i in 1:3)) ==
OccupationNumberFS(5, 1 => 2, 2 => 3, 3 => 4) ==
OccupationNumberFS{5,UInt8}(2, 3, 4, 0, 0)
@test OccupationNumberFS{5}(i => i^2 for i in 1:5) ==
OccupationNumberFS(5, i => i^2 for i in 1:5)
end

@testset "Printing and parsing OccupationNumberFS" begin
Expand Down

0 comments on commit 3d7a7f6

Please sign in to comment.