def test_solver_squared_error_in_the_underdetermined_case(self): x = np.array([0., 4., 5.]) y = np.array([0., 0., 2.]) w = np.ones_like(x) knots = np.array([1., 2., 3.]) # The x=1 and x=3 knots affect the squared error, so their y-values are # determined by the system. However, the x=2 knot has no effect, so it can # take on an infinite range of values. # The expected curve is [(1., 0), (2., unknown), (3., 1.)]. expected_error = 2.0 nonmono_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) _, nonmono_error = nonmono_solver.solve(knots) self.assertAlmostEqual(expected_error, nonmono_error) mono_up_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) _, mono_up_error = mono_up_solver.solve(knots) self.assertAlmostEqual(expected_error, mono_up_error) # The mono-down case is fully constrained, with best curve y = mean = 2/3. mono_down_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, max_slope=0) _, mono_down_error = mono_down_solver.solve(knots) self.assertAlmostEqual(8. / 3, mono_down_error)
def test_bitonic_solver_suffers_when_peak_or_direction_is_wrong(self): np.random.seed(58444) x = np.sort(np.random.uniform(size=100)) y = (0.5 - x)**2 # y is a concave-up parabole with its minimum at x=0.6. w = np.random.uniform(size=100) knots = [.1, .3, .5, .7, .9] non_mono_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) concave_down_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, bitonic_peak=0.5, bitonic_concave_down=True) peak_too_low_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, bitonic_peak=0.2, bitonic_concave_down=False) peak_too_high_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, bitonic_peak=0.8, bitonic_concave_down=False) non_mono_y, non_mono_error = non_mono_solver.solve(knots) concave_down_y, concave_down_error = concave_down_solver.solve(knots) peak_too_low_y, peak_too_low_error = peak_too_low_solver.solve(knots) peak_too_high_y, peak_too_high_error = peak_too_high_solver.solve( knots) # The bitonic restrictions prevent us from learning the good solution. self.assert_notallclose(non_mono_y, concave_down_y) self.assert_notallclose(non_mono_y, peak_too_low_y) self.assert_notallclose(non_mono_y, peak_too_high_y) self.assertNotAlmostEqual(concave_down_error, non_mono_error) self.assertNotAlmostEqual(peak_too_low_error, non_mono_error) self.assertNotAlmostEqual(peak_too_high_error, non_mono_error)
def test_mono_solver_returns_mono_solution(self): np.random.seed(58443) x = np.sort(np.random.uniform(size=10)) y = np.random.normal(scale=.5, size=10) w = np.random.uniform(size=10) knots = np.sort(np.random.choice(x, size=4, replace=False)) mono_up_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) mono_down_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, max_slope=0) mono_up_y, _ = mono_up_solver.solve(knots) self.assert_increasing(mono_up_y) mono_down_y, _ = mono_down_solver.solve(knots) self.assert_decreasing(mono_down_y)
def test_mono_down_equals_flipped_mono_up(self): # Fitting monoup should be equivalent to flipping y and fitting monodown. np.random.seed(58442) x = np.sort(np.random.uniform(size=100)) y = x**2 + np.random.normal(scale=.2, size=100) w = np.random.uniform(size=100) mono_up_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) mono_down_solver = fitter._WeightedLeastSquaresPWLSolver( x, -y, w, max_slope=0) knots = [.2, .5, .8, .9] mono_up_y, mono_up_error = mono_up_solver.solve(knots) mono_down_y, mono_down_error = mono_down_solver.solve(knots) self.assertAlmostEqual(mono_up_error, mono_down_error) self.assert_allclose(mono_up_y, -mono_down_y)
def test_mono_solver_same_as_non_mono_for_mono_task(self): # The monotonicity restriction shouldn't matter if the natural fit is # monotonic. np.random.seed(58444) x = np.sort(np.random.uniform(size=100)) y = x**2 w = np.random.uniform(size=100) knots = [.2, .5, .8, .9] mono_up_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) non_mono_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) mono_up_y, mono_up_error = mono_up_solver.solve(knots) non_mono_y, non_mono_error = non_mono_solver.solve(knots) np.testing.assert_array_equal(non_mono_y, mono_up_y) self.assertAlmostEqual(mono_up_error, non_mono_error)
def test_knots_between_xs(self): x = np.array([0., 1., 2., 3., 4., 5., 6., 7.]) y = np.array([0., 0., 0., 1., 2., 3., 4., 4.]) w = np.ones_like(x) knots = [2.5, 3.5, 4.5, 5.5] # This problem has a perfect monotone increasing solution, which the solver # should find whether it's set to mono-up or non-mono. solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) solution, residue = solver.solve(knots) self.assert_allclose(solution, [0, 2, 2, 4]) self.assertAlmostEqual(residue, 0) solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) solution, residue = solver.solve(knots) self.assert_allclose(list(solution), [0, 2, 2, 4]) self.assertAlmostEqual(residue, 0)
def test_solver_works_without_bounding_knots(self): x = np.array([0., 1., 2., 3., 4., 5., 6., 7.]) y = np.array([0., 0., 0., 1., 2., 3., 4., 4.]) w = np.ones_like(x) knots = [2., 3., 5., 6.] # This problem has a perfect monotone increasing solution, which the solver # should find whether it's set to mono-up or non-mono. solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) solution, error = solver.solve(knots) self.assert_allclose(solution, [0, 1, 3, 4]) self.assertAlmostEqual(error, 0) solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) solution, error = solver.solve(knots) self.assert_allclose(solution, [0, 1, 3, 4]) self.assertAlmostEqual(error, 0)
def test_min_and_max_slope(self): x = np.array([0., 1., 2., 3., 4.]) y = np.array([0., 2., 0., -2, 0.]) w = np.ones_like(x) knots = x slope_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, min_slope=-1.0, max_slope=1.0) knot_ys, _ = slope_solver.solve(knots) self.assert_allclose(knot_ys, [0, 1, 0, -1, 0])
def test_max_slope_smooths_out_spike(self): x = np.array([0., 2., 4., 6., 8., 10.]) y = np.array([0., 0., 0., 6., 6., 6.]) w = np.ones_like(x) knots = x max_slope_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, max_slope=1.0) max_slope_y, _ = max_slope_solver.solve(knots) self.assert_allclose(max_slope_y, [0, 0, 2, 4, 6, 6])
def test_reports_correct_error(self): np.random.seed(58440) x = np.sort(np.random.uniform(size=100)) y = x**2 + np.random.normal(scale=.2, size=100) w = np.random.uniform(size=100) knots = [.2, .5, .8, .9] solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) knot_ys, reported_error = solver.solve(knots) points = list(zip(knots, knot_ys)) true_error = _curve_error_on_points(x, y, w, pwlcurve.PWLCurve(points)) self.assertAlmostEqual(true_error, reported_error) mono_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w, min_slope=0) knot_ys, reported_error = mono_solver.solve(knots) points = list(zip(knots, knot_ys)) true_error = _curve_error_on_points(x, y, w, pwlcurve.PWLCurve(points)) self.assertAlmostEqual(true_error, reported_error)
def test_bitonic_solver_same_as_non_mono_for_bitonic_task(self): # The bitonic restriction shouldn't matter if the natural fit is bitonic. np.random.seed(58444) x = np.sort(np.random.uniform(size=100)) y = (0.6 - x)**2 # y is a concave-up parabole with its minimum at x=0.6. w = np.random.uniform(size=100) knots = [.2, .5, .7, .9] concave_up_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, bitonic_peak=0.6, bitonic_concave_down=False) non_mono_solver = fitter._WeightedLeastSquaresPWLSolver(x, y, w) concave_up_y, concave_up_error = concave_up_solver.solve(knots) non_mono_y, non_mono_error = non_mono_solver.solve(knots) # The problem is bitonic concave up, so the concave up solution matches the # non-mono. np.testing.assert_array_equal(non_mono_y, concave_up_y) self.assertAlmostEqual(concave_up_error, non_mono_error)
def test_sloped_solver_returns_sloped_solution(self): np.random.seed(58443) x = np.sort(np.random.uniform(size=10)) y = np.random.normal(scale=.5, size=10) w = np.random.uniform(size=10) knots = np.sort(np.random.choice(x, size=4, replace=False)) min_slope_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, min_slope=5.) max_slope_solver = fitter._WeightedLeastSquaresPWLSolver( x, y, w, max_slope=-5.) min_slope_y, _ = min_slope_solver.solve(knots) slopes = (min_slope_y[1:] - min_slope_y[:1]) / (knots[1:] - knots[:-1]) for slope in slopes: self.assertGreaterEqual(slope, 5) max_slope_y, _ = max_slope_solver.solve(knots) slopes = (max_slope_y[1:] - max_slope_y[:1]) / (knots[1:] - knots[:-1]) for slope in slopes: self.assertLessEqual(slope, -5)