///|
fn unique_indices(indices : Array[Int], size : Int) -> Unit raise CodaError {
let seen = Array::make(size, false)
for i in indices {
if i < 0 || i >= size {
raise Shape("feature index outside composition")
}
if seen[i] {
raise Invalid("repeated feature index")
}
seen[i] = true
}
}
///|
pub fn subcomposition(
x : Array[Double],
indices : Array[Int],
) -> Array[Double] raise CodaError {
let c = closure(x)
vector_size(indices.length())
unique_indices(indices, c.length())
closure(indices.map(i => c[i]))
}
///|
/// Groups must form a disjoint, exhaustive partition. This is NOT an isometry.
pub fn amalgamate(
x : Array[Double],
groups : Array[Array[Int]],
) -> Array[Double] raise CodaError {
let c = closure(x)
vector_size(groups.length())
let seen = Array::make(c.length(), false)
let result = []
for group in groups {
if group.length() == 0 {
raise Shape("empty amalgamation group")
}
let mut total = 0.0
for i in group {
if i < 0 || i >= c.length() {
raise Shape("amalgamation index out of bounds")
}
if seen[i] {
raise Invalid("amalgamation groups overlap")
}
seen[i] = true
total += c[i]
}
result.push(total)
}
for found in seen {
if !found {
raise Invalid("amalgamation omits a feature")
}
}
closure(result)
}
///|
/// Restore feature order after a permutation (not after feature removal).
pub fn permute(
x : Array[Double],
order : Array[Int],
) -> Array[Double] raise CodaError {
if x.length() != order.length() {
raise Shape("permutation size mismatch")
}
unique_indices(order, x.length())
let c = closure(x)
order.map(i => c[i])
}