A factor maps from the cartesion product of its dimensions's supports to a Float64
.
Factor
represent dimensions with a Dimensions
datatype
using Factors
using DataFrames
Julia
is column-major, and so are potentials: the first dimension varies over to the first axis (column), the second dimension varies over the second axis (rows) etc ...
DataFrame(Factor(1:6, :X=>3, :Y=>2))
X | Y | potential | |
---|---|---|---|
1 | 1 | 1 | 1.0 |
2 | 2 | 1 | 2.0 |
3 | 3 | 1 | 3.0 |
4 | 1 | 2 | 4.0 |
5 | 2 | 2 | 5.0 |
6 | 3 | 2 | 6.0 |
Factor
s can be initialized with explicitly created Dimensions
:
c = Dimension(:C, 3)
s = Dimension(:S, 10:2:18)
Factor([c, s], rand(3, 5))
Factor([c, s], rand(15)) # reshapes automatically
Factor(c, [2, 0, 16])
3 instantiations: C: 1:3
Julia
will also convert any T <: AbstractVector
to a Dimension
:
Factor([1 4; 2 5; 3 6], :X=> 3:5, :Y=> ['a', 'b'])
Factor(1:100, :X=>10, :Y=>2, :Z=>5) # reshape automatically
100 instantiations: X: 1:10 Y: 1:2 Z: 1:5
Or assume the i
-th Dimension
is 1:size(potential, i)
:
Factor(:X, [31, 33, 58])
Factor([:X, :Y], rand(20, 16))
320 instantiations: X: 1:20 Y: 1:16
Factor
s can also have uniform values (the default is zero), or be uninitialized:
Factor(c) # all 0
Factor(c, 31) # all 31
Factor([c, s], nothing) # unitialized
Factor([c, s], 16)
Factor(Dict(:X=>14, :Y=>['Γ', 'Δ'], :Z=>'a':2:'z'))
Factor(Dict(:X=>14, :Y=>['Γ', 'Δ'], :Z=>'a':2:'z'), nothing)
Factor(:A=>10, :B=>3:20)
Factor(16, :A=>10, :B=>3:20)
180 instantiations: A: 1:10 B: 3:20 (18)
A Factor
can also be zero-dimensional (for weird edge cases, and marginalization):
Factor(2016)
1 instantiation: 2016.0
A Factor
s scope is its dimensions:
ft = Factor([:X, :Y], rand(20, 16))
scope(ft)
2-element Array{Factors.Dimension,1}: X: 1:20 Y: 1:16
names(ft)
2-element Array{Symbol,1}: :X :Y
Factor
s act as AbstractArray
s in many cases
similar(ft) # unitialized potential
320 instantiations: X: 1:20 Y: 1:16
size(ft, :X)
20
length(ft)
320
ndims(ft)
2
push(ft, Dimension(:J, 4))
1280 instantiations: X: 1:20 Y: 1:16 J: 1:4
An Assignment
(or Pair
s) can index into a Factor
:
ft = Factor([1 4; 2 5; 3 6], :X=> 2:4, :Y=>['a', 'b'])
ft[:X=>[3, 2], :Y=>'a']
2 instantiations: X: [3,2] (2)
ft[Assignment(:X=>[3, 2], :Y=>'a')] = [20, 16]
ft.potential
3×2 Array{Float64,2}: 16.0 4.0 20.0 5.0 3.0 6.0
Besides overloading sub2ind
and ind2sub
, functions to convert from and Assignment
s and assignment tuples are provided:
at2sub(ft, 3, 'b')
(2,2)
sub2at(ft, 1, 1)
(2,'a')
at2a(ft, 2, 'a')
Dict{Symbol,Any} with 2 entries: :X => 2 :Y => 'a'
a2at(ft, :Y=>'b', :X=>2)
(2,'b')
a2sub(ft, :Y=>'b', :X=>2)
(1,2)
sub2a(ft, 2, 2)
Dict{Symbol,Any} with 2 entries: :X => 3 :Y => 'b'
Iterating over a factor returns assignment tuples
for t in Factor(:X=>'α':'γ', :Y=>["waldo", "carmen"], :Z=>3)
println(t)
end
('α',"waldo",1) ('β',"waldo",1) ('γ',"waldo",1) ('α',"carmen",1) ('β',"carmen",1) ('γ',"carmen",1) ('α',"waldo",2) ('β',"waldo",2) ('γ',"waldo",2) ('α',"carmen",2) ('β',"carmen",2) ('γ',"carmen",2) ('α',"waldo",3) ('β',"waldo",3) ('γ',"waldo",3) ('α',"carmen",3) ('β',"carmen",3)
pattern
returns the sequence of a indices a Dimension
will take in Factor
across all indicies
c = Dimension(:C, 2:4)
s = Dimension(:S, 'a':2:'h')
ft = Factor([c, s])
pattern(ft)
12×2 Array{Int64,2}: 1 1 2 1 3 1 1 2 2 2 3 2 1 3 2 3 3 3 1 4 2 4 3 4
pattern_states
returns the sequence of states
pattern_states(ft, :C)
12×1 Array{Int64,2}: 2 3 4 2 3 4 2 3 4 2 3 4
This, of course, can be changed by permuting the dimensions:
permutedims!(ft, [2, 1])
pattern(ft)
12×2 Array{Int64,2}: 1 1 2 1 3 1 4 1 1 2 2 2 3 2 4 2 1 3 2 3 3 3 4 3
map!(x -> x + 10, ft)
ft.potential
4×3 Array{Float64,2}: 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0
Common functions have already been defined
log(ft)
abs(ft)
sin(ft)
12 instantiations: S: 'a':2:'g' (4) C: 2:4 (3)
As have other not-so-mappy ones
randn!(ft)
rand!(ft)
fill!(ft, 2016)
ft.potential
4×3 Array{Float64,2}: 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0 2016.0
Operations can be broadcast along dimensions:
ft = Factor(1:6, :X=> 2:4, :Y=>['a', 'b'])
DataFrame(broadcast(*, ft, :Y, [100, 0.01]))
X | Y | potential | |
---|---|---|---|
1 | 2 | a | 100.0 |
2 | 3 | a | 200.0 |
3 | 4 | a | 300.0 |
4 | 2 | b | 0.04 |
5 | 3 | b | 0.05 |
6 | 4 | b | 0.06 |
Dimensions can be reduced. Convience functions are provded for the following:
sum
prod
maximum
minimum
DataFrame(prod(ft, :X))
Y | potential | |
---|---|---|
1 | a | 6.0 |
2 | b | 120.0 |
Z(ft) # purpously reminiscent of a partition function
1 instantiation: 21.0
Marginalization sums out all but:
ft = Factor(:W=>2, :X=>3, :Y=>2, :Z=>3)
marginalize(ft, :W)
marginalize(ft, [:W, :Y])
4 instantiations: W: 1:2 Y: 1:2
Factors can be joined through join
or by multiplying (adding, etc.) them:
ft1 = Factor(collect(1:9), :A=>3, :B=>3)
ft2 = Factor(10, :B=>3, :C=>2)
DataFrame(ft1 * ft2)
A | B | C | potential | |
---|---|---|---|---|
1 | 1 | 1 | 1 | 10.0 |
2 | 2 | 1 | 1 | 20.0 |
3 | 3 | 1 | 1 | 30.0 |
4 | 1 | 2 | 1 | 40.0 |
5 | 2 | 2 | 1 | 50.0 |
6 | 3 | 2 | 1 | 60.0 |
7 | 1 | 3 | 1 | 70.0 |
8 | 2 | 3 | 1 | 80.0 |
9 | 3 | 3 | 1 | 90.0 |
10 | 1 | 1 | 2 | 10.0 |
11 | 2 | 1 | 2 | 20.0 |
12 | 3 | 1 | 2 | 30.0 |
13 | 1 | 2 | 2 | 40.0 |
14 | 2 | 2 | 2 | 50.0 |
15 | 3 | 2 | 2 | 60.0 |
16 | 1 | 3 | 2 | 70.0 |
17 | 2 | 3 | 2 | 80.0 |
18 | 3 | 3 | 2 | 90.0 |
By default, negatives are allowed in factors:
Factor(:X, [-2016, 4])
2 instantiations: X: 1:2
This can be changed to raise a warning or to throw an error
set_negative_mode(NegativeWarn)
Factor(:X, [1, 1]) - Factor(:X, [2, 2])
WARNING: potential has negative values
2 instantiations: X: 1:2
set_negative_mode(NegativeError)
log(Factor(:X, [1E-2, 1//4]))
ArgumentError: potential has negative values in _check_negatives(::Array{Float64,1}, ::Factors.NegativeMode{:error}) at C:\Users\Hamza El-Saawy\.julia\v0.5\Factors\src\negatives.jl:34 in Factors.Factor{1}(::Array{Factors.Dimension,1}, ::Array{Float64,1}) at C:\Users\Hamza El-Saawy\.julia\v0.5\Factors\src\factors_main.jl:23 in Factors.Factor{N}(::Array{Factors.Dimension,1}, ::Array{Float64,1}) at C:\Users\Hamza El-Saawy\.julia\v0.5\Factors\src\factors_main.jl:50 in log(::Factors.Factor{1}) at C:\Users\Hamza El-Saawy\.julia\v0.5\Factors\src\factors_map.jl:122
set_negative_mode(NegativeIgnore)
Factors.NegativeMode{:error}()
The core unit are dimensions, which are names (Symbol
) with countably-finite supports (<: AbstractVector
).
ds = map(s -> Dimension(:X, s), [["bob", "waldo", "superman"], ('a', 'α'), 'a':2:'z', 10:3:40, 2:15, 1:4, 16, []])
8-element Array{Any,1}: X: String["bob","waldo","superman"] (3) X: ['a','α'] (2) X: 'a':2:'y' (13) X: 10:3:40 (11) X: 2:15 (14) X: 1:4 X: 1:16 X: Any[] (0)
map(eltype, ds)
8-element Array{Any,1}: String Char Char Int64 Int64 Int64 Int64 Any
map(spttype, ds)
8-element Array{Any,1}: Array{String,1} Array{Char,1} StepRange{Char,Int64} StepRange{Int64,Int64} UnitRange{Int64} Base.OneTo{Int64} Base.OneTo{Int64} Array{Any,1}
x = Dimension(:X, 'α':'ω')
for v in x
print(v, " ")
end
α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω
x[2]
'β'
indexin('β', x)
2
(i, d) = update(x, ['α', 'ψ', 'ζ', 'δ'])
([1,24,6,4],X: ['α','ψ','ζ','δ'] (4))
Equality for dimensions is by their support:
Dimension(:X, [1, 2, 3]) == Dimension(:X, 1:1:3) == Dimension(:X, 1:3) == Dimension(:X, 3)
true
Comparisons use the position of elements in a dimension
o = Dimension(:X, [3, 16, -2])
o .< -2 # here, 3 & 16 are less than -2
3-element BitArray{1}: true true false
# 3, 16, and -2 are all ≥ 3
o .≥ 3
3-element BitArray{1}: true true true