///|
/// A deterministic linearly spaced sample.
pub fn linspace(
minimum : Double,
maximum : Double,
count : Int,
) -> Array[Double] {
let result : Array[Double] = []
let n = count.max(0)
if n == 0 {
result
} else if n == 1 {
result.push(minimum)
result
} else {
for i in 0.. Array[Double] {
let low = natural_log(minimum.max(1.0e-12))
let high = natural_log(maximum.max(1.0e-12))
linspace(low, high, count).map(fn(x) { @math.exp(x) })
}
///|
/// Midpoints of an ordered sample grid.
pub fn midpoints(values : ArrayView[Double]) -> Array[Double] {
let result : Array[Double] = []
if values.length() >= 2 {
for i in 0..<(values.length() - 1) {
result.push((values[i] + values[i + 1]) / 2.0)
}
}
result
}
///|
/// Difference between adjacent samples.
pub fn adjacent_differences(values : ArrayView[Double]) -> Array[Double] {
let result : Array[Double] = []
if values.length() >= 2 {
for i in 0..<(values.length() - 1) {
result.push(values[i + 1] - values[i])
}
}
result
}
///|
/// Trapezoid area for tabulated data.
pub fn tabulated_trapezoid(
x : ArrayView[Double],
y : ArrayView[Double],
) -> Double {
let count = x.length().min(y.length())
if count < 2 {
0.0
} else {
for i = 1, area = 0.0; i < count; i = i + 1 {
continue i + 1, area + (x[i] - x[i - 1]) * (y[i] + y[i - 1]) / 2.0
} nobreak {
area
}
}
}
///|
/// Weighted mean of a tabulated signal.
pub fn weighted_mean(
values : ArrayView[Double],
weights : ArrayView[Double],
) -> Double {
let count = values.length().min(weights.length())
let total_weight = for i = 0, total = 0.0; i < count; i = i + 1 {
continue i + 1, total + weights[i]
} nobreak {
total
}
if total_weight.abs() <= 1.0e-12 {
0.0
} else {
let weighted = for i = 0, total = 0.0; i < count; i = i + 1 {
continue i + 1, total + values[i] * weights[i]
} nobreak {
total
}
weighted / total_weight
}
}
///|
/// Root-mean-square of a signal.
pub fn root_mean_square(values : ArrayView[Double]) -> Double {
if values.length() == 0 {
0.0
} else {
let total = values.fold(init=0.0, fn(acc, value) { acc + value * value })
(total / Double::from_int(values.length())).sqrt()
}
}
///|
/// Mean absolute error between two equal-length signals.
pub fn mean_absolute_error(
first : ArrayView[Double],
second : ArrayView[Double],
) -> Double {
let count = first.length().min(second.length())
if count == 0 {
0.0
} else {
let total = for i = 0, acc = 0.0; i < count; i = i + 1 {
continue i + 1, acc + (first[i] - second[i]).abs()
} nobreak {
acc
}
total / Double::from_int(count)
}
}
///|
/// Maximum absolute error between two signals.
pub fn maximum_absolute_error(
first : ArrayView[Double],
second : ArrayView[Double],
) -> Double {
let count = first.length().min(second.length())
for i = 0, error = 0.0; i < count; i = i + 1 {
continue i + 1, error.max((first[i] - second[i]).abs())
} nobreak {
error
}
}
///|
/// Normalize a signal to a target range.
pub fn normalize_to_range(
values : ArrayView[Double],
minimum : Double,
maximum : Double,
) -> Array[Double] {
let low = values.fold(init=1.0e30, fn(acc, x) { acc.min(x) })
let high = values.fold(init=-1.0e30, fn(acc, x) { acc.max(x) })
let span = (high - low).max(1.0e-12)
values.map(fn(x) { minimum + (x - low) / span * (maximum - minimum) })
}
///|
/// Clip a sampled signal to a physical range.
pub fn clip_values(
values : ArrayView[Double],
minimum : Double,
maximum : Double,
) -> Array[Double] {
values.map(fn(x) {
x.clamp(min=minimum.min(maximum), max=maximum.max(minimum))
})
}
///|
/// First-order low-pass filter for deterministic sampled data.
pub fn low_pass(values : ArrayView[Double], alpha : Double) -> Array[Double] {
let result : Array[Double] = []
if values.length() == 0 {
result
} else {
let weight = alpha.clamp(min=0.0, max=1.0)
let mut state = values[0]
result.push(state)
for value in values[1:] {
state = state + weight * (value - state)
result.push(state)
}
result
}
}
///|
/// Compute a discrete first difference.
pub fn first_difference(
values : ArrayView[Double],
step : Double,
) -> Array[Double] {
let result : Array[Double] = []
if values.length() >= 2 {
for i in 0..<(values.length() - 1) {
result.push((values[i + 1] - values[i]) / step.max(1.0e-12))
}
}
result
}
///|
/// Detect a sign change between adjacent samples.
pub fn sign_change_count(values : ArrayView[Double]) -> Int {
if values.length() < 2 {
0
} else {
for i = 1, count = 0; i < values.length(); i = i + 1 {
let changed = (values[i - 1] < 0.0 && values[i] >= 0.0) ||
(values[i - 1] >= 0.0 && values[i] < 0.0)
continue i + 1, if changed { count + 1 } else { count }
} nobreak {
count
}
}
}
///|
/// Return the index of the largest sampled value.
pub fn argmax(values : ArrayView[Double]) -> Int? {
if values.length() == 0 {
None
} else {
let mut index = 0
for i in 1.. values[index] {
index = i
}
}
Some(index)
}
}
///|
/// Return the index of the smallest sampled value.
pub fn argmin(values : ArrayView[Double]) -> Int? {
if values.length() == 0 {
None
} else {
let mut index = 0
for i in 1..