///|
/// Measured properties of a timing curve over a normalized interval.
pub struct CurveStatistics {
minimum : Double
maximum : Double
mean : Double
integral : Double
peak_velocity : Double
peak_acceleration : Double
total_variation : Double
overshoot : Double
undershoot : Double
} derive(Debug)
///|
pub fn CurveStatistics::minimum(self : CurveStatistics) -> Double {
self.minimum
}
///|
pub fn CurveStatistics::maximum(self : CurveStatistics) -> Double {
self.maximum
}
///|
pub fn CurveStatistics::mean(self : CurveStatistics) -> Double {
self.mean
}
///|
pub fn CurveStatistics::integral(self : CurveStatistics) -> Double {
self.integral
}
///|
pub fn CurveStatistics::peak_velocity(self : CurveStatistics) -> Double {
self.peak_velocity
}
///|
pub fn CurveStatistics::peak_acceleration(self : CurveStatistics) -> Double {
self.peak_acceleration
}
///|
pub fn CurveStatistics::total_variation(self : CurveStatistics) -> Double {
self.total_variation
}
///|
pub fn CurveStatistics::overshoot(self : CurveStatistics) -> Double {
self.overshoot
}
///|
pub fn CurveStatistics::undershoot(self : CurveStatistics) -> Double {
self.undershoot
}
///|
pub fn analyze_curve(curve : Curve, samples : Int) -> CurveStatistics {
let count = samples.max(4)
let step = 1.0 / count.to_double()
let first = curve.apply(0.0)
let mut minimum = first
let mut maximum = first
let mut integral = 0.0
let mut variation = 0.0
let mut peak_velocity = 0.0
let mut peak_acceleration = 0.0
let mut previous = first
let mut previous_velocity = 0.0
for index in 1..<=count {
let current_time = index.to_double() * step
let current = curve.apply(current_time)
let velocity = (current - previous) / step
let acceleration = (velocity - previous_velocity) / step
minimum = minimum.min(current)
maximum = maximum.max(current)
integral = integral + (previous + current) * step * 0.5
variation = variation + (current - previous).abs()
peak_velocity = peak_velocity.max(velocity.abs())
peak_acceleration = peak_acceleration.max(acceleration.abs())
previous = current
previous_velocity = velocity
}
{
minimum,
maximum,
mean: integral,
integral,
peak_velocity,
peak_acceleration,
total_variation: variation,
overshoot: (maximum - 1.0).max(0.0),
undershoot: (0.0 - minimum).max(0.0),
}
}
///|
pub fn CurveStatistics::monotonic(self : CurveStatistics) -> Bool {
self.total_variation <= (self.maximum - self.minimum).abs() + 0.000001
}
///|
pub fn CurveStatistics::range(self : CurveStatistics) -> Double {
self.maximum - self.minimum
}
///|
pub fn CurveStatistics::normalized_overshoot(self : CurveStatistics) -> Double {
let range = self.range().abs()
if range == 0.0 {
0.0
} else {
self.overshoot / range
}
}
///|
pub struct CurveComparison {
mean_absolute_error : Double
root_mean_square_error : Double
maximum_error : Double
area_error : Double
correlation : Double
samples : Int
} derive(Debug)
///|
pub fn CurveComparison::mean_absolute_error(self : CurveComparison) -> Double {
self.mean_absolute_error
}
///|
pub fn CurveComparison::root_mean_square_error(
self : CurveComparison,
) -> Double {
self.root_mean_square_error
}
///|
pub fn CurveComparison::maximum_error(self : CurveComparison) -> Double {
self.maximum_error
}
///|
pub fn CurveComparison::area_error(self : CurveComparison) -> Double {
self.area_error
}
///|
pub fn CurveComparison::correlation(self : CurveComparison) -> Double {
self.correlation
}
///|
pub fn CurveComparison::samples(self : CurveComparison) -> Int {
self.samples
}
///|
pub fn compare_curves(
first : Curve,
second : Curve,
samples : Int,
) -> CurveComparison {
let count = samples.max(2)
let step = 1.0 / (count - 1).to_double()
let mut absolute = 0.0
let mut square = 0.0
let mut maximum = 0.0
let mut area = 0.0
let mut first_mean = 0.0
let mut second_mean = 0.0
for index in 0.. 0 {
let prior_time = (index - 1).to_double() * step
area = area +
(
(first.apply(prior_time) - second.apply(prior_time)).abs() +
difference.abs()
) *
step *
0.5
}
first_mean = first_mean + left
second_mean = second_mean + right
}
first_mean = first_mean / count.to_double()
second_mean = second_mean / count.to_double()
let mut covariance = 0.0
let mut first_variance = 0.0
let mut second_variance = 0.0
for index in 0.. Array[CurveFeature] {
[
FeatureStart,
FeatureEnd,
FeaturePeakVelocity,
FeaturePeakAcceleration,
FeatureFirstOvershoot,
FeatureSettlingTime,
]
}
///|
pub fn curve_feature_value(
curve : Curve,
feature : CurveFeature,
samples : Int,
) -> Double {
let count = samples.max(4)
let stats = analyze_curve(curve, count)
match feature {
FeatureStart => curve.apply(0.0)
FeatureEnd => curve.apply(1.0)
FeaturePeakVelocity => stats.peak_velocity
FeaturePeakAcceleration => stats.peak_acceleration
FeatureFirstOvershoot => stats.overshoot
FeatureSettlingTime => {
let mut result = 1.0
for index in 0..<=count {
let time = index.to_double() / count.to_double()
if (curve.apply(time) - 1.0).abs() <= 0.01 && result == 1.0 {
result = time
}
}
result
}
}
}
///|
pub struct CurveEnvelope {
lower : Array[Double]
upper : Array[Double]
samples : Int
} derive(Debug)
///|
pub fn build_envelope(curves : Array[Curve], samples : Int) -> CurveEnvelope {
let count = samples.max(2)
let lower : Array[Double] = []
let upper : Array[Double] = []
for index in 0.. Array[Double] {
self.lower.copy()
}
///|
pub fn CurveEnvelope::upper(self : CurveEnvelope) -> Array[Double] {
self.upper.copy()
}
///|
pub fn CurveEnvelope::samples(self : CurveEnvelope) -> Int {
self.samples
}
///|
pub fn CurveEnvelope::contains(self : CurveEnvelope, curve : Curve) -> Bool {
for index in 0.. self.upper[index] + 0.000001 {
return false
}
}
true
}
///|
pub fn CurveEnvelope::width_at(self : CurveEnvelope, time : Double) -> Double {
let position = clamp01(time) * (self.samples - 1).to_double()
let index = @math.floor(position).to_int().min(self.samples - 2)
let ratio = position - index.to_double()
let lower = self.lower[index] +
(self.lower[index + 1] - self.lower[index]) * ratio
let upper = self.upper[index] +
(self.upper[index + 1] - self.upper[index]) * ratio
upper - lower
}
///|
pub fn estimate_settling_time(
curve : Curve,
tolerance : Double,
samples : Int,
) -> Double {
let count = samples.max(2)
let allowed = tolerance.abs()
let mut latest = 0.0
for index in 0.. allowed {
latest = time
}
}
if latest >= 1.0 {
1.0
} else {
latest
}
}
///|
pub fn estimate_zero_crossings(curve : Curve, samples : Int) -> Int {
let count = samples.max(2)
let mut previous = curve.apply(0.0)
let mut crossings = 0
for index in 1..= 0.0) || (previous >= 0.0 && current < 0.0) {
crossings = crossings + 1
}
previous = current
}
crossings
}