Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement piecewise function #689

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const RealIntervalType{T} = Union{BareInterval{T},Interval{T}}

#

include("piecewise.jl")
export Constant, Piecewise

#

include("display.jl")
export setdisplay

Expand Down
51 changes: 51 additions & 0 deletions src/piecewise.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
struct Constant{T}
value::T
end

(constant::Constant)(::Any) = constant.value
(constant::Constant)(::Interval) = interval(constant.value)

struct Piecewise{T <: Tuple}
pieces::T
continuous::Bool
end

Piecewise(pairs... ; continuous = false) = Piecewise(pairs, continuous)

domain(piecewise::Piecewise) = reduce(hull, subdomains(piecewise))
subdomains(piecewise::Piecewise) = first.(piecewise.pieces)

intersecting(X::Interval, Y::Interval) = !isempty_interval(intersect_interval(X, Y))

function (piecewise::Piecewise)(X::Interval)
intersections = intersecting.(X, subdomains(piecewise))
dom = domain(piecewise)
!intersecting(X, dom) && return emptyinterval()
if !issubset_interval(X, dom)
dec = trv
elseif count(intersections) > 1
if piecewise.continuous
dec = dac
else
dec = def
end
else
dec = com
end

used_pieces = filter(piece -> intersecting(X, piece[1]), piecewise.pieces)
outputs = map(used_pieces) do (region, f)
S = IntervalArithmetic.setdecoration(intersect_interval(X, region), decoration(X))
return f(S)
end

dec = min(dec, minimum(decoration.(outputs)))
return IntervalArithmetic.setdecoration(reduce(hull, outputs), dec)
end

function (piecewise::Piecewise)(x::Real)
for (region, f) in piecewise.pieces
in_interval(x, region) && return f(x)
Copy link
Member

@dpsanders dpsanders Nov 28, 2024

Choose a reason for hiding this comment

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

This doesn't seem completely right: you need to return the union of f(x) over all the pieces that x overlaps with, I think.

Copy link
Member

Choose a reason for hiding this comment

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

e.g. the line

    return reduce(hull, f(intersect_interval(X, region)) for (region, f) ∈ piecewise.pieces)

in my original implementation in the issue you limked to.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This part is the implementation for non-interval real only, so I think it is fine, as it would be a bit awkward to return an interval for non-interval input.

As @OlivierHnt mentioned, this implementation allows ill-defined functions, but I don't think the answer is the interval-ification of everything.

end
throw(DomainError("piecewise function was called with $x which is outside of its domain $(domain(piecewise))"))
end
45 changes: 45 additions & 0 deletions test/interval_tests/piecewise.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@testset "Step function" begin
step = Piecewise(
interval(-Inf, 0) => Constant(0),
interval(0, Inf) => Constant(1)
)

@test step(-1) == 0
@test step(100) == 1
@test isequal_interval(step(interval(-3.2, -2.1)), interval(0))
@test decoration(step(interval(-3.33))) == com
@test isequal_interval(step(interval(2.3, 3.4)), interval(1))
@test decoration(step(interval(4.44))) == com
@test isequal_interval(step(interval(-22.2, 33.3)), interval(0, 1))
@test decoration(step(interval(-11, 11))) == def
end

@testset "abs" begin
myabs = Piecewise(
interval(-Inf, 0) => x -> -x,
interval(0, Inf) => identity ;
continuous = true
)

@test myabs(-1) == 1
@test myabs(100) == 100
@test isequal_interval(myabs(interval(-3.2, -2.1)), interval(2.1, 3.2))
@test decoration(myabs(interval(-3.33))) == com
@test isequal_interval(myabs(interval(2.3, 3.4)), interval(2.3, 3.4))
@test decoration(myabs(interval(4.444))) == com
@test isequal_interval(myabs(interval(-22.2, 33.3)), interval(0, 33.3))
@test decoration(myabs(interval(-11, 11))) == dac
end

@testset "Out of domain" begin
window = Piecewise(
interval(-π, π) => x -> 1/2 * (cos(x) + 1)
)

@test_throws DomainError window(123)
@test isequal_interval(window(interval(0, π)), interval(0, 1))
@test decoration(window(interval(-π, 0))) == com
@test isequal_interval(window(interval(-10, 10)), interval(0, 1))
@test decoration(window(interval(-10, 10))) == trv
@test isempty_interval(window(interval(100, 1000)))
end
Loading