Skip to content
Merged
6 changes: 6 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ makedocs(;
),
pages=[
"Home" => "index.md",
"Layers" => [
"Transformation" => "transformation.md",
"Arithmetic" => "arithmetic.md",
"Aggregation" => "aggregation.md",
"Comparison" => "comparison.md",
],
],
)

Expand Down
Empty file added docs/src/aggregation.md
Empty file.
Empty file added docs/src/arithmetic.md
Empty file.
Empty file added docs/src/comparison.md
Empty file.
Empty file added docs/src/transformation.md
Empty file.
12 changes: 11 additions & 1 deletion src/CompositionalNetworks.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
module CompositionalNetworks

# Write your package code here.
# Exports utilities
export lazy, lazy_param

# Include utils
include("utils.jl")

# Includes layers
include("transformation.jl")
include("arithmetic.jl")
include("aggregation.jl")
include("comparison.jl")

end
11 changes: 11 additions & 0 deletions src/aggregation.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
_sum(x::V)
Aggregate through `+` a vector into a single scalar.
"""
_sum(x::V) where {T <: Number, V <: AbstractVector{T}} = reduce(+, x)

"""
_count_positive(x::V)
Count the number of strictly positive elements of `x`.
"""
_count_positive(x::V) where {T <: Number, V <: AbstractVector{T}} = count(y -> y > 0.0, x)
11 changes: 11 additions & 0 deletions src/arithmetic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
_sum(x::W) where {T <: Number, V <: AbstractVector{T}, W <: AbstractVector{V}}
_prod(x::W) where {T <: Number, V <: AbstractVector{T}, W <: AbstractVector{V}}
Reduce `k = length(x)` vectors through sum/product to a single vector.
"""
function _sum(x::W) where {T <: Number, V <: AbstractVector{T}, W <: AbstractVector{V}}
return reduce((y, z) -> y .+ z, x)
end
function _prod(x::W) where {T <: Number, V <: AbstractVector{T}, W <: AbstractVector{V}}
return reduce((y, z) -> y .* z, x)
end
48 changes: 48 additions & 0 deletions src/comparison.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# doc in transformation.jl
_identity(x::T) where T <: Number = identity(x)

"""
_abs_diff_val_param(x::T, param::T)
Return the absolute difference between `x` and `param`.
"""
_abs_diff_val_param(x::T, param::T) where T <: Number = abs(x - param)

"""
_val_minus_param(x::T, param::T)
_param_minus_val(x::T, param::T)
Return the difference `x - param` (resp. `param - x`) if positive, `0.0` otherwise.
"""
_val_minus_param(x::T, param::T) where T <: Number = max(0.0, x - param)
_param_minus_val(x::T, param::T) where T <: Number = max(0.0, param - x)

"""
_euclidian_param(x::T, param::T, dom_size::T2)
_euclidian(x::T, dom_size::T2)
Compute an euclidian norm , possibly weigthed by `param`, on a scalar.
"""
function _euclidian_param(x::T, param::T, dom_size::T2) where {T <: Number, T2 <: Number}
return x == param ? 0.0 : (1.0 + abs(x - param) \ dom_size)
end
function _euclidian(x::T, dom_size::T2) where {T <: Number, T2 <: Number}
return _euclidian_param(x, zero(T), dom_size)
end

"""
_abs_diff_val_vars(x::T, vars::V)
Return the absolute difference between `x` and the number of variables.
"""
function _abs_diff_val_vars(x::T, vars::V) where {T <: Number, V <: AbstractVector{T}}
return abs(x - length(vars))
end

"""
_val_minus_vars(x::T, vars::V)
_vars_minus_val(x::T, vars::V)
Return the difference `x - length(vars)` (resp. `length(vars) - x`) if positive, `0.0` otherwise.
"""
function _val_minus_vars(x::T, vars::V) where {T <: Number, V <: AbstractVector{T}}
return _val_minus_param(x, length(vars))
end
function _vars_minus_val(x::T, vars::V) where {T <: Number, V <: AbstractVector{T}}
return _param_minus_val(x, length(vars))
end
115 changes: 115 additions & 0 deletions src/transformation.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
_identity(x::V) where {T <: Number,V <: AbstractVector{T}}
_identity(x::T) where T <: Number = identity(x)
Identity function. Already defined in Julia as `identity`, specialized for vectors and scalars.
"""
_identity(x::V) where {T <: Number,V <: AbstractVector{T}} = identity(x)

"""
_count_eq(i::Int, x::V)
_count_eq_right(i::Int, x::V)
_count_eq_left(i::Int, x::V)
Count the number of elements equal to `x[i]` (optionally to the right/left of `x[i]`). Extended method to vector `x::V` are generated.
"""
function _count_eq(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return count(y -> x[i] == y, x) - 1
end
function _count_eq_right(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_eq(1, @view x[i:end])
end
function _count_eq_left(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_eq(i, @view x[1:i])
end
# Generating vetorized versions
lazy(_count_eq, _count_eq_left, _count_eq_right)

"""
_count_greater(i::Int, x::V)
_count_lesser(i::Int, x::V)
_count_g_left(i::Int, x::V)
_count_l_left(i::Int, x::V)
_count_g_right(i::Int, x::V)
_count_l_right(i::Int, x::V)
Count the number of elements greater/lesser than `x[i]` (optionally to the left/right of `x[i]`). Extended method to vector with sig `(x::V)` are generated.
"""
function _count_greater(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return count(y -> x[i] < y, x)
end
function _count_lesser(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return count(y -> x[i] > y, x)
end
function _count_g_left(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_greater(i, @view x[1:i])
end
function _count_l_left(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_lesser(i, @view x[1:i])
end
function _count_g_right(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_greater(1, @view x[i:end])
end
function _count_l_right(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return _count_lesser(1, @view x[i:end])
end
# Generating vetorized versions
lazy(_count_greater, _count_g_left, _count_g_right)
lazy(_count_lesser, _count_l_left, _count_l_right)

"""
_count_eq_param(i::Int, x::V, param::T)
_count_l_param(i::Int, x::V, param::T)
_count_g_param(i::Int, x::V, param::T)
Count the number of elements equal to (resp. lesser/greater than) `x[i] + param`. Extended method to vector with sig `(x::V, param::T)` are generated.
"""
function _count_eq_param(i::Int, x::V, param::T) where {T <: Number,V <: AbstractVector{T}}
return count(y -> y == x[i] + param, x)
end
function _count_l_param(i::Int, x::V, param::T) where {T <: Number,V <: AbstractVector{T}}
return count(y -> y < x[i] + param, x)
end
function _count_g_param(i::Int, x::V, param::T) where {T <: Number,V <: AbstractVector{T}}
return count(y -> y > x[i] + param, x)
end
# Generating vetorized versions
lazy_param(_count_eq_param, _count_l_param, _count_g_param)

"""
_count_bounding_param(i::Int, x::V, param::T)
Count the number of elements bounded (not strictly) by `x[i]` and `x[i] + param`. An extended method to vector with sig `(x::V, param::T)` is generated.
"""
function _count_bounding_param(i::Int, x::V, param::T
) where {T <: Number,V <: AbstractVector{T}}
return count(y -> x[i] ≤ y ≤ x[i] + param, x)
end
# Generating vetorized versions
lazy_param(_count_bounding_param)

"""
_val_minus_param(i::Int, x::V, param::T)
_param_minus_val(i::Int, x::V, param::T)
Return the difference `x[i] - param` (resp. `param - x[i]`) if positive, `0.0` otherwise. Extended method to vector with sig `(x::V, param::T)` are generated.
"""
function _val_minus_param(i::Int, x::V, param::T
) where {T <: Number,V <: AbstractVector{T}}
return max(0, x[i] - param)
end
function _param_minus_val(i::Int, x::V, param::T
) where {T <: Number,V <: AbstractVector{T}}
return max(0, param - x[i])
end
# Generating vetorized versions
lazy_param(_val_minus_param, _param_minus_val)

"""
_contiguous_vals_minus(i::Int, x::V)
_contiguous_vals_minus_rev(i::Int, x::V)
Return the difference `x[i] - x[i + 1]` (resp. `x[i + 1] - x[i]`) if positive, `0.0` otherwise. Extended method to vector with sig `(x::V)` are generated.
"""
function _contiguous_vals_minus(i::Int, x::V) where {T <: Number,V <: AbstractVector{T}}
return length(x) == i ? 0 : _val_minus_param(i, x, x[i + 1])
end
function _contiguous_vals_minus_rev(i::Int, x::V
) where {T <: Number,V <: AbstractVector{T}}
return length(x) == i ? 0 : _param_minus_val(i, x, x[i + 1])
end
# Generating vetorized versions
lazy(_contiguous_vals_minus, _contiguous_vals_minus_rev)
20 changes: 20 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

"""
_map_tr(f, x)
_map_tr(f, x, param)
Return an anonymous function that applies `f` to all elements of `x`, with an optional parameter `param`.
"""
_map_tr(f, x) = ((g, y) -> map(i -> g(i, y), 1:length(y)))(f, x)
_map_tr(f, x, param) = ((g, y, p) -> map(i -> g(i, y, p), 1:length(y)))(f, x, param)

"""
lazy(funcs::Function...)
lazy_param(funcs::Function...)
Generate methods extended to a vector instead of one of its components. For `lazy` (resp. `lazy_param`) a function `f` should have the following signature: `f(i::Int, x::V)` (resp. `f(i::Int, x::V, param::T)`).
"""
function lazy(funcs::Function...)
foreach(f -> eval(:($f(x) = (y -> _map_tr($f, y))(x))), map(Symbol, funcs))
end
function lazy_param(funcs::Function...)
foreach(f -> eval(:($f(x, param) = (y -> _map_tr($f, y, param))(x))), map(Symbol, funcs))
end
154 changes: 154 additions & 0 deletions test/layers.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Transformation layer
data = [
[1, 5, 2, 4, 3] => 2,
[1, 2, 3, 2, 1] => 2,
]

# Test transformations without parameters
funcs = Dict(
ICN._identity => [
data[1].first,
data[2].first,
],
ICN._count_eq => [
[0, 0, 0, 0, 0],
[1, 1, 0, 1, 1],
],
ICN._count_eq_right => [
[0, 0, 0, 0, 0],
[1, 1, 0, 0, 0],
],
ICN._count_eq_left => [
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 1],
],
ICN._count_greater => [
[4, 0, 3, 1, 2],
[3, 1, 0, 1, 3],
],
ICN._count_lesser => [
[0, 4, 1, 3, 2],
[0, 2, 4, 2, 0],
],
ICN._count_g_left => [
[0, 0, 1, 1, 2],
[0, 0, 0, 1, 3],
],
ICN._count_l_left => [
[0, 1, 1, 2, 2],
[0, 1, 2, 1, 0],
],
ICN._count_g_right => [
[4, 0, 2, 0, 0],
[3, 1, 0, 0, 0],
],
ICN._count_l_right => [
[0, 3, 0, 1, 0],
[0, 1, 2, 1, 0],
],
ICN._contiguous_vals_minus => [
[0, 3, 0, 1, 0],
[0, 0, 1, 1, 0],
],
ICN._contiguous_vals_minus_rev => [
[4, 0, 2, 0, 0],
[1, 1, 0, 0, 0],
],
)

for (f, results) in funcs, (key, vals) in enumerate(data)
@test f(vals.first) == results[key]
end

# Test transformations with parameter
funcs_param = Dict(
ICN._count_eq_param => [
[1, 0, 1, 0, 1],
[1, 0, 0, 0, 1],
],
ICN._count_l_param => [
[2, 5, 3, 5, 4],
[4, 5, 5, 5, 4],
],
ICN._count_g_param => [
[2, 0, 1, 0, 0],
[0, 0, 0, 0, 0],
],
ICN._count_bounding_param => [
[3, 1, 3, 2, 3],
[5, 3, 1, 3, 5],
],
ICN._val_minus_param => [
[0, 3, 0, 2, 1],
[0, 0, 1, 0, 0],
],
ICN._param_minus_val => [
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 1],
],
)

for (f, results) in funcs_param, (key, vals) in enumerate(data)
@test f(vals.first, vals.second) == results[key]
end

# arithmetic layer
@test ICN._sum(map(p -> p.first, data)) == [2, 7, 5, 6, 4]
@test ICN._prod(map(p -> p.first, data)) == [1, 10, 6, 8, 3]

# aggregation layer
@test ICN._sum(data[1].first) == 15
@test ICN._sum(data[2].first) == 9

@test ICN._count_positive(data[1].first) == 5
@test ICN._count_positive(data[2].first) == 5
@test ICN._count_positive([1, 0, 1, 0, 1]) == 3

# Comparison layer
data = [3 => (1, 5), 5 => (10, 5)]

funcs = [
ICN._identity => [3, 5],
]

# test no param/vars
for (f, results) in funcs, (key, vals) in enumerate(data)
@test f(vals.first) == results[key]
end

funcs_param = [
ICN._abs_diff_val_param => [2, 5],
ICN._val_minus_param => [2, 0],
ICN._param_minus_val => [0, 5],
]

for (f, results) in funcs_param, (key, vals) in enumerate(data)
@test f(vals.first, vals.second[1]) == results[key]
end

funcs_vars = [
ICN._abs_diff_val_vars => [2, 0],
ICN._val_minus_vars => [0, 0],
ICN._vars_minus_val => [2, 0],
]

for (f, results) in funcs_vars, (key, vals) in enumerate(data)
@test f(vals.first, rand(1:10, vals.second[2])) == results[key]
end


funcs_param_dom = [
ICN._euclidian_param => [3.5, 2.0],
]

for (f, results) in funcs_param_dom, (key, vals) in enumerate(data)
@test f(vals.first, vals.second[1], vals.second[2]) ≈ results[key]
end

funcs_dom = [
ICN._euclidian => [8/3, 2.0],
]

for (f, results) in funcs_dom, (key, vals) in enumerate(data)
@test f(vals.first, vals.second[2]) ≈ results[key]
end
Loading