/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use euclid::approxeq::ApproxEq; use style::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuilder}; fn get_linear_keyword_equivalent() -> PiecewiseLinearFunction { PiecewiseLinearFunctionBuilder::default().build() } #[test] fn linear_keyword_equivalent_in_bound() { let function = get_linear_keyword_equivalent(); assert!(function.at(0.).approx_eq(&0.)); assert!(function.at(0.5).approx_eq(&0.5)); assert!(function.at(1.0).approx_eq(&1.0)); } #[test] fn linear_keyword_equivalent_out_of_bounds() { let function = get_linear_keyword_equivalent(); assert!(function.at(-0.1).approx_eq(&-0.1)); assert!(function.at(1.1).approx_eq(&1.1)); } fn get_const_function() -> PiecewiseLinearFunction { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(CONST_VALUE as f32, None); builder.build() } const CONST_VALUE: f32 = 0.2; #[test] fn const_function() { let function = get_const_function(); assert!(function.at(0.).approx_eq(&CONST_VALUE)); assert!(function.at(0.5).approx_eq(&CONST_VALUE)); assert!(function.at(1.0).approx_eq(&CONST_VALUE)); } #[test] fn const_function_out_of_bounds() { let function = get_const_function(); assert!(function.at(-0.1).approx_eq(&CONST_VALUE)); assert!(function.at(1.1).approx_eq(&CONST_VALUE)); } #[test] fn implied_input_spacing() { let explicit_spacing = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(1.0, Some(1.0)); builder.build() }; let implied_spacing = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(1.0, None); builder.build() }; assert!(implied_spacing.at(0.).approx_eq(&explicit_spacing.at(0.))); assert!(implied_spacing.at(0.5).approx_eq(&explicit_spacing.at(0.5))); assert!(implied_spacing.at(1.0).approx_eq(&explicit_spacing.at(1.0))); } #[test] fn interpolation() { let interpolate = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(0.7, None); builder.push(1.0, None); builder.build() }; assert!(interpolate.at(0.1).approx_eq(&0.14)); assert!(interpolate.at(0.25).approx_eq(&0.35)); assert!(interpolate.at(0.45).approx_eq(&0.63)); assert!(interpolate.at(0.7).approx_eq(&0.82)); assert!(interpolate.at(0.75).approx_eq(&0.85)); assert!(interpolate.at(0.95).approx_eq(&0.97)); } #[test] fn implied_multiple_input_spacing() { let multiple_implied = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(0.8, None); builder.push(0.6, None); builder.push(0.4, None); builder.push(0.5, Some(0.4)); builder.push(0.1, None); builder.push(0.9, None); builder.push(1.0, None); builder.build() }; assert!(multiple_implied.at(0.1).approx_eq(&0.8)); assert!(multiple_implied.at(0.2).approx_eq(&0.6)); assert!(multiple_implied.at(0.3).approx_eq(&0.4)); assert!(multiple_implied.at(0.4).approx_eq(&0.5)); assert!(multiple_implied.at(0.6).approx_eq(&0.1)); assert!(multiple_implied.at(0.8).approx_eq(&0.9)); assert!(multiple_implied.at(1.).approx_eq(&1.)); } #[test] fn nonzero_edge_values() { let nonzero_edges = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.1, Some(0.0)); builder.push(0.7, Some(1.0)); builder.build() }; assert!(nonzero_edges.at(0.).approx_eq(&0.1)); assert!(nonzero_edges.at(0.5).approx_eq(&0.4)); assert!(nonzero_edges.at(1.0).approx_eq(&0.7)); } #[test] fn out_of_bounds_extrapolate() { // General case: extrapolate from the edges' slope let oob_extend = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(0.7, None); builder.push(1.0, None); builder.build() }; assert!(oob_extend.at(-0.25).approx_eq(&-0.35)); assert!(oob_extend.at(1.25).approx_eq(&1.15)); } #[test] fn out_of_bounds_flat() { // Repeated endpoints: flat extrapolation out-of-bounds let oob_flat = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.0, Some(0.0)); builder.push(0.7, None); builder.push(1.0, Some(1.0)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(oob_flat.at(0.0).approx_eq(&oob_flat.at(-0.25))); assert!(oob_flat.at(1.0).approx_eq(&oob_flat.at(1.25))); } #[test] fn flat_region() { let flat = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.5, Some(0.25)); builder.push(0.5, Some(0.7)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(flat.at(0.125).approx_eq(&0.25)); assert!(flat.at(0.5).approx_eq(&0.5)); assert!(flat.at(0.85).approx_eq(&0.75)); } #[test] fn step() { let step = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.0, Some(0.5)); builder.push(1.0, Some(0.5)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(step.at(0.25).approx_eq(&0.0)); // At the discontinuity, take the right hand side value assert!(step.at(0.5).approx_eq(&1.0)); assert!(step.at(0.75).approx_eq(&1.0)); } #[test] fn step_multiple_conflicting() { let step = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.0, Some(0.5)); builder.push(0.75, Some(0.5)); builder.push(0.75, Some(0.5)); builder.push(1.0, Some(0.5)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(step.at(0.25).approx_eq(&0.0)); assert!(step.at(0.5).approx_eq(&1.0)); assert!(step.at(0.75).approx_eq(&1.0)); } #[test] fn always_monotonic() { let monotonic = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.3, Some(0.5)); builder.push(0.4, Some(0.4)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(monotonic.at(0.25).approx_eq(&0.15)); // A discontinuity at x = 0.5 from y = 0.3 to 0.4 assert!(monotonic.at(0.5).approx_eq(&0.4)); assert!(monotonic.at(0.6).approx_eq(&0.52)); } #[test] fn always_monotonic_flat() { let monotonic = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.2, Some(0.2)); builder.push(0.4, Some(0.1)); builder.push(0.4, Some(0.15)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(monotonic.at(0.2).approx_eq(&0.4)); // A discontinuity at x = 0.2 from y = 0.2 to 0.4 assert!(monotonic.at(0.3).approx_eq(&0.475)); } #[test] fn always_monotonic_flat_backwards() { let monotonic = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.2, Some(0.2)); builder.push(0.3, Some(0.3)); builder.push(0.3, Some(0.2)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(monotonic.at(0.2).approx_eq(&0.2)); assert!(monotonic.at(0.3).approx_eq(&0.3)); assert!(monotonic.at(0.4).approx_eq(&0.4)); } #[test] fn input_out_of_bounds() { let oob = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(-0.5)); builder.push(1.0, Some(1.5)); builder.build() }; assert!(oob.at(-0.5).approx_eq(&0.0)); assert!(oob.at(0.0).approx_eq(&0.25)); assert!(oob.at(0.5).approx_eq(&0.5)); assert!(oob.at(1.0).approx_eq(&0.75)); assert!(oob.at(1.5).approx_eq(&1.0)); } #[test] fn invalid_builder_input() { let built_from_invalid = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(f32::NEG_INFINITY)); builder.push(0.7, Some(f32::NAN)); builder.push(1.0, Some(f32::INFINITY)); builder.build() }; let equivalent = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(0.7, None); builder.push(1.0, None); builder.build() }; assert!(built_from_invalid.at(0.0).approx_eq(&equivalent.at(0.0))); assert!(built_from_invalid.at(0.25).approx_eq(&equivalent.at(0.25))); assert!(built_from_invalid.at(0.5).approx_eq(&equivalent.at(0.5))); assert!(built_from_invalid.at(0.75).approx_eq(&equivalent.at(0.75))); assert!(built_from_invalid.at(1.0).approx_eq(&equivalent.at(1.0))); } #[test] fn input_domain_not_complete() { let not_covered = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.2, Some(0.2)); builder.push(0.8, Some(0.8)); builder.build() }; assert!(not_covered.at(0.0).approx_eq(&0.0)); assert!(not_covered.at(0.5).approx_eq(&0.5)); assert!(not_covered.at(1.0).approx_eq(&1.0)); } #[test] fn input_second_negative() { let function = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, None); builder.push(0.0, Some(-0.1)); builder.push(0.3, Some(-0.05)); builder.push(0.5, None); builder.push(0.2, Some(0.6)); builder.push(1.0, None); builder.build() }; let equivalent = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(0.0, Some(0.0)); builder.push(0.3, Some(0.0)); builder.push(0.5, Some(0.3)); builder.push(0.2, Some(0.6)); builder.push(1.0, Some(1.0)); builder.build() }; assert!(function.at(-0.1).approx_eq(&equivalent.at(-0.1))); assert!(function.at(0.0).approx_eq(&equivalent.at(0.0))); assert!(function.at(0.3).approx_eq(&equivalent.at(0.3))); assert!(function.at(0.6).approx_eq(&equivalent.at(0.6))); assert!(function.at(1.0).approx_eq(&equivalent.at(1.0))); } #[test] fn input_second_last_above_1() { let function = { let mut builder = PiecewiseLinearFunctionBuilder::default(); builder.push(0.0, Some(0.0)); builder.push(1.0, Some(2.0)); builder.push(1.0, None); builder.build() }; assert!(function.at(-0.5).approx_eq(&-0.25)); assert!(function.at(0.0).approx_eq(&0.0)); assert!(function.at(0.5).approx_eq(&0.25)); assert!(function.at(1.0).approx_eq(&0.5)); assert!(function.at(1.5).approx_eq(&0.75)); assert!(function.at(2.0).approx_eq(&1.0)); assert!(function.at(3.0).approx_eq(&1.0)); }