Conventions

We use $σ$ to represent a binary digit, its subtitle usually refers to the position of a given binary digit inside a number (bit string).

In computing, bit numbering (or sometimes bit endianness) is the convention used to identify the bit positions in a binary number or a container of such a value. The bit number starts with zero and is incremented by one for each subsequent bit position. See also Bit numbering((Bit endianness)).

There are two different representation orders of a bit string:

• Least significant bit 0 bit numbering
• Most significant bit 0 bit numbering

LSB 0 bit numbering

This follows the order of BitArray or other array representation of bits, e.g

For number 0b011101 (29)

$\sigma_1=1, \sigma_2=0, \sigma_3=1, \sigma_4=1, \sigma_5=1, \sigma_6=0$

MSB 0 bit numbering

This follows the order of binary literal 0bxxxx, e.g

For number 0b011101 (29)

$\sigma_1=0, \sigma_2=1, \sigma_3=1, \sigma_4=1, \sigma_5=0, \sigma_6=1$

Integer Representations

We use an Int type to store bit-wise (spin) configurations, e.g. 0b011101 (29) represents the configuration

$\sigma_1=1, \sigma_2=0, \sigma_3=1, \sigma_4=1, \sigma_5=1, \sigma_6=0$

so we annotate the configurations $\vec σ$ with integer $b$ by $b = \sum\limits_i 2^{i-1}σ_i$. e.g. we can use a number 28 to represent bit configuration 0b11100

julia> bdistance(0b11100, 0b10101) == 2  # Hamming distance
true

julia> bit_length(0b11100) == 5
true

In BitBasis, we also provide a more readable way to define these kind of objects, which is called the bit string literal, most of the integer operations and BitBasis functions are overloaded for the bit string literal.

We can switch between binary and digital representations with

• bitarray(integers, nbits), transform integers to bistrings of type BitArray.
• packabits(bitstring), transform bitstrings to integers.
• baddrs(integer), get the locations of nonzero qubits.
julia> bitarray(4, 5)
5-element BitArray{1}:
false
false
true
false
false

julia> bitarray([4, 5, 6], 5)
5×3 BitArray{2}:
false   true  false
false  false   true
true   true   true
false  false  false
false  false  false

julia> packbits([1, 1, 0])
3

julia> bitarray([4, 5, 6], 5) |> packbits;

A curried version of the above function is also provided. See also bitarray.

Bit String Literal

bit strings are literals for bits, it provides better view on binary basis. you could use @bit_str, which looks like the following

julia> bit"101" * 2
1010 ₍₂₎

julia> bcat(bit"101" for i in 1:10)
101101101101101101101101101101 ₍₂₎

julia> repeat(bit"101", 2)
101101 ₍₂₎

julia> bit"1101"
0

to define a bit string with length. bit"10101" is equivalent to 0b10101 on both performance and functionality but it store the length of given bits statically. The bit string literal offers a more readable syntax for these kind of objects.

Besides bit literal, you can convert a string or an integer to bit literal by bit, e.g

julia> BitStr{5}(0b00101)
ERROR: MethodError: no method matching BitStr{5,T} where T(::UInt8)
Closest candidates are:
BitStr{5,T} where T(::T<:Number) where T<:Number at boot.jl:741
BitStr{5,T} where T(!Matched::Float16) where T<:Integer at float.jl:71
BitStr{5,T} where T(!Matched::Complex) where T<:Real at complex.jl:37
...

Bit Manipulations julia> readbit(0b11100, 2, 3) == 0b10  # read the 2nd and 3rd bits as x₃x₂
true

julia> baddrs(0b11100) == [3,4,5]  # locations of one bits
true

Masking technic provides faster binary operations, to generate a mask with specific position masked, e.g. we want to mask qubits 1, 3, 4

0x0d

┌ Warning: bit(b; len) is deprecated, use BitStr{len}(b) instead.
│   caller = ip:0x0
└ @ Core :-1
ERROR: MethodError: no method matching BitStr{4,T} where T(::UInt8)
Closest candidates are:
BitStr{4,T} where T(::T<:Number) where T<:Number at boot.jl:741
BitStr{4,T} where T(!Matched::Float16) where T<:Integer at float.jl:71
BitStr{4,T} where T(!Matched::Complex) where T<:Real at complex.jl:37
...

allone and anyone

with this mask (masked positions are colored light blue), we have julia> allone(0b1011, mask) == false # true if all masked positions are 1
true

julia> anyone(0b1011, mask) == true # true if any masked positions is 1
true

ismatch julia> ismatch(0b1011, mask, 0b1001) == true  # true if masked part matches 0b1001
true

flip ┌ Warning: bit(b; len) is deprecated, use BitStr{len}(b) instead.
│   caller = ip:0x0
└ @ Core :-1
ERROR: MethodError: no method matching BitStr{4,T} where T(::UInt8)
Closest candidates are:
BitStr{4,T} where T(::T<:Number) where T<:Number at boot.jl:741
BitStr{4,T} where T(!Matched::Float16) where T<:Integer at float.jl:71
BitStr{4,T} where T(!Matched::Complex) where T<:Real at complex.jl:37
...

setbit julia> setbit(0b1011, 0b1100) == 0b1111 # set masked positions 1
true

swapbits julia> swapbits(0b1011, 0b1100) == 0b0111  # swap masked positions
true

neg

julia> neg(0b1011, 2) == 0b1000
true

btruncate and breflect julia> btruncate(0b1011, 2) == 0b0011  # only the first two qubits are retained
true

breflect julia> breflect(4, 0b1011) == 0b1101  # reflect little end and big end
┌ Warning: breflect(nbits::Int, b::Integer) is deprecated, use breflect(b; nbits=nbits) instead.
│   caller = top-level scope at none:0
└ @ Core none:0
true

For more detailed bitwise operations, see manual page BitBasis.

In phase estimation and HHL algorithms, one need to read out qubits as integer or float point numbers. A register can be read out in different ways, like

• bint, the integer itself
• bint_r, the integer with bits small-big end reflected.
• bfloat, the float point number $0.σ₁σ₂ \cdots σ_n$.
• bfloat_r, the float point number $0.σ_n \cdots σ₂σ₁$. julia> bint(0b010101)
0x15

julia> bint_r(0b010101, nbits=6)
0x2a

julia> bfloat(0b010101)
0.65625

julia> bfloat_r(0b010101, nbits=6);

Notice the functions with _r as postfix always require nbits as an additional input parameter to help reading, which is regarded as less natural way of expressing numbers.

Iterating over Bases

Counting from 0 is very natural way of iterating quantum registers, very pity for Julia

julia> itr = basis(4)
0:15

julia> collect(itr)
16-element Array{Int64,1}:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

itercontrol is a complicated API, but it plays an fundamental role in high performance quantum simulation of Yao. It is used for iterating over basis in controlled way, its interface looks like

julia> for each in itercontrol(7, [1, 3, 4, 7], (1, 0, 1, 0))
end
0001001
0001011
0011001
0011011
0101001
0101011
0111001
0111011

Reordering Basis

We store the wave function as $v[b+1] := \langle b|\psi\rangle$. We are able to reorder the basis as

julia> v = onehot(5, 0b11100)  # the one hot vector representation of given bits
32-element Array{Float64,1}:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
⋮
0.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0

julia> reorder(v, (3,2,1,5,4)) ≈ onehot(5, 0b11001)
true

julia> invorder(v) ≈ onehot(5, 0b00111)  # breflect for each basis
true