Skip to content

Conversation

@jd-lara
Copy link
Member

@jd-lara jd-lara commented Nov 17, 2025

Implement make_convex and make_concave functions using the Pool Adjacent Violators Algorithm (PAVA) for isotonic regression. This provides an optimal O(n) solution for finding the closest convex/concave approximation to non-convex PiecewiseLinearData and PiecewiseStepData.

New functions:

  • isotonic_regression / antitonic_regression: Core PAVA implementation
  • make_convex / make_concave: Convert piecewise data to convex/concave form
  • convexity_violations: Find indices where convexity is violated
  • convexity_gap: Measure the maximum convexity violation
  • approximation_error: Compute error between original and approximated data

Features:

  • Multiple weighting schemes (:uniform, :length, custom)
  • Multiple anchor options for PiecewiseLinearData (:first, :last, :centroid)
  • Support for L1, L2, and Linf error metrics

Implement make_convex and make_concave functions using the Pool Adjacent
Violators Algorithm (PAVA) for isotonic regression. This provides an
optimal O(n) solution for finding the closest convex/concave approximation
to non-convex PiecewiseLinearData and PiecewiseStepData.

New functions:
- isotonic_regression / antitonic_regression: Core PAVA implementation
- make_convex / make_concave: Convert piecewise data to convex/concave form
- convexity_violations: Find indices where convexity is violated
- convexity_gap: Measure the maximum convexity violation
- approximation_error: Compute error between original and approximated data

Features:
- Multiple weighting schemes (:uniform, :length, custom)
- Multiple anchor options for PiecewiseLinearData (:first, :last, :centroid)
- Support for L1, L2, and Linf error metrics
Remove antitonic_regression, make_concave for PiecewiseStepData and
PiecewiseLinearData, and related tests. The library only needs
conversion from concave to convex (make_convex), not the reverse.
return _get_x_lengths(x_coords)
elseif weights isa Vector{Float64}
length(weights) == n_segments ||
throw(ArgumentError("Custom weights must have length $n_segments, got $(length(weights))"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
throw(ArgumentError("Custom weights must have length $n_segments, got $(length(weights))"))
throw(
ArgumentError(
"Custom weights must have length $n_segments, got $(length(weights))",
),
)

approximated = IS.PiecewiseStepData([0.0, 1.0, 2.0, 3.0], [7.5, 7.5, 15.0])

# L2 error with uniform weights
err_l2 = IS.approximation_error(original, approximated; metric = :L2, weights = :uniform)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
err_l2 = IS.approximation_error(original, approximated; metric = :L2, weights = :uniform)
err_l2 =
IS.approximation_error(original, approximated; metric = :L2, weights = :uniform)

@test err_l2 expected_l2

# L1 error
err_l1 = IS.approximation_error(original, approximated; metric = :L1, weights = :uniform)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
err_l1 = IS.approximation_error(original, approximated; metric = :L1, weights = :uniform)
err_l1 =
IS.approximation_error(original, approximated; metric = :L1, weights = :uniform)

@test err_l1 expected_l1

# Linf error
err_linf = IS.approximation_error(original, approximated; metric = :Linf, weights = :uniform)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
err_linf = IS.approximation_error(original, approximated; metric = :Linf, weights = :uniform)
err_linf =
IS.approximation_error(original, approximated; metric = :Linf, weights = :uniform)

@test err expected

# Test invalid metric
@test_throws ArgumentError IS.approximation_error(original, approximated; metric = :invalid)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
@test_throws ArgumentError IS.approximation_error(original, approximated; metric = :invalid)
@test_throws ArgumentError IS.approximation_error(
original,
approximated;
metric = :invalid,
)

Refactor approximation_error functions to use Julia's standard
LinearAlgebra functions instead of manually implementing norms:

- L2 norm: Use norm() with weighted scaling
- L1 norm: Use dot() for weighted sum
- L∞ norm: Use norm(diff, Inf)

This makes the code more idiomatic and leverages optimized
implementations from Julia's standard library.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants