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

Extend boolean functions for intervals to numbers #623

Closed
wants to merge 5 commits into from

Conversation

OlivierHnt
Copy link
Member

@OlivierHnt OlivierHnt commented Feb 1, 2024

This PR improves compatibility between Number and Interval by permitting most boolean functions to take numbers as an argument.

The general idea is that a boolean function defined for intervals, say foo(x::Interval) = ..., is extended to numbers by simply foo(x::Number) = foo(interval(x)).

This leads to some corner cases, eg isthin(Inf, Inf) and isthin(pi) return false.
Not sure if that should be the behaviour. The other possibility is of course to fall back to default Base functionalities: isthinzero(x::Number) = iszero(x) instead of isthinzero(x::Number) = isthinzero(interval(x)), etc.

@lbenet do you think this can help with JuliaDiff/TaylorSeries.jl#345? Perhaps then you won't have to define your own _isthinzero function.

@OlivierHnt
Copy link
Member Author

The behaviour of isthin, etc., should be consistent with inf, sup and bounds. Currently sup(Inf) == NaN because sup(x::Number) = sup(interval(x)).

Also

julia> inf(pi)
3.141592653589793

julia> sup(pi)
3.1415926535897936

@codecov-commenter
Copy link

codecov-commenter commented Feb 1, 2024

Codecov Report

Attention: 34 lines in your changes are missing coverage. Please review.

Comparison is base (7c46049) 83.46% compared to head (073c505) 82.24%.

Files Patch % Lines
src/intervals/interval_operations/boolean.jl 12.00% 22 Missing ⚠️
src/intervals/real_interface.jl 72.72% 12 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #623      +/-   ##
==========================================
- Coverage   83.46%   82.24%   -1.23%     
==========================================
  Files          25       25              
  Lines        2153     2185      +32     
==========================================
  Hits         1797     1797              
- Misses        356      388      +32     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lbenet
Copy link
Member

lbenet commented Feb 1, 2024

The general idea is that a boolean function defined for intervals, say foo(x::Interval) = ..., is extended to numbers by simply foo(x::Number) = foo(interval(x)).

This leads to some corner cases, eg isthin(Inf, Inf) and isthin(pi) return false. Not sure if that should be the behaviour. The other possibility is of course to fall back to default Base functionalities: isthinzero(x::Number) = iszero(x) instead of isthinzero(x::Number) = isthinzero(interval(x)), etc.

Thanks for this addition and bringing it up for discusion.

A subtlety of the current proposal is that in some cases we do create the proper interval that contains the real value intended (or typed), e.g. interval(pi) which is not thin, and in other we do not, e.g. interval(0.1). I understand why this is so internally (::Irrational vs ::Float64), which is leave to the user the responsability of what is being typed. Yet, the question is if we should systematically use the proper interval that contains the real number that is typed, or we should treat it as a numeric computational type?

In JuliaDiff/TaylorSeries.jl#345 I've been joggling with some internal functionality as a workaround (this was before #613). What I did was to define some internal function, say _isthinzero, which behaves as iszero always (Number), and for Intervals it corresponds to isthinzero. That way I am not incurring into type piracy --I think--, and allows me to have all Interval functionality as an external dependency. This approach is closer to the other possibility you mentioned above...

Something I noticed in JuliaDiff/TaylorSeries.jl#345 (and that I'm trying to solve) may help in the decision: In order to compute the Taylor series of certain functions (let us think of log), you first need to be able to compute the zero order term, which is the log of the constant term. For floating point numbers, log(x) throws an error for x<0, returns -Inf (which is useless) for x==0, and does what it should for positive x; so for floating points, it is enough to check iszero(x). Now, if x is an interval, the log computation involves checking the correct domain (an intersection), and that process may be an empty interval, or an interval that contains zero. The empty interval propagates, so I should check it and throw an error in that case, and if the interval contains zero, there will be a lot of -Infs, which are essentially useless. My point is, the iszero check returning false is not enough.

@OlivierHnt
Copy link
Member Author

Ah yes you have IntervalArithmetic as a weak dependancy. The way you do it with _isthinzero seems the right way.

In the example of log the effective check is x >= 0 (if one does not want to rely on Base throwing an error whenever x < 0). Btw this now works in this PR if x :: Interval provided that isthin(x) holds.

A subtlety of the current proposal is that in some cases we do create the proper interval that contains the real value intended (or typed), e.g. interval(pi) which is not thin, and in other we do not, e.g. interval(0.1). I understand why this is so internally (::Irrational vs ::Float64), which is leave to the user the responsability of what is being typed. Yet, the question is if we should systematically use the proper interval that contains the real number that is typed, or we should treat it as a numeric computational type?

Absolutely.
When I re-defined inf and sup to convert the input to an Interval first, I do not think that I took into account what would happen for an Irrational.
Mhmm from our own docstring for isthin, having isthin(pi) == false is kind of contradictory. So we could consider this a bug (and thus also change the behaviour of inf and sup), or alter the docstring.

@lbenet
Copy link
Member

lbenet commented Feb 2, 2024

n the example of log the effective check is x >= 0 (if one does not want to rely on Base throwing an error whenever x < 0). Btw this now works in this PR if x :: Interval provided that isthin(x) holds.

Thanks for the remark. I'll give it a try and provide some feedback

@lbenet
Copy link
Member

lbenet commented Feb 2, 2024

When I re-defined inf and sup to convert the input to an Interval first, I do not think that I took into account what would happen for an Irrational.
Mhmm from our own docstring for isthin, having isthin(pi) == false is kind of contradictory. So we could consider this a bug (and thus also change the behaviour of inf and sup), or alter the docstring.

Just to make it clear: I am not against that interval(pi) returns the correct interval that contains the (mathematically) real pi; I accept the fact that interval(0.1) returns a thin interval (which does not contain the actual 1//10); my point is that interval behaves slightly (but deterministically) inconsistent, depending on the (Julia) type of its entries.
Once stated this, I think that isthin(interval(pi)) correctly returns false, while isthin(interval(0.1)) or isthin(interval(1)) correctly return true; in my opinion there is no bug.

@OlivierHnt
Copy link
Member Author

Thanks for the remark. I'll give it a try and provide some feedback

Oh I was just mentioning this by the way.
I do not think it will fit your needs since whenever x is not thin, you will get an error... which is very impractical but provide a slightly better support for some safe generic code.

Just to make it clear: I am not against that interval(pi) returns the correct interval that contains the (mathematically) real pi; I accept the fact that interval(0.1) returns a thin interval (which does not contain the actual 1//10); my point is that interval behaves slightly (but deterministically) inconsistent, depending on the (Julia) type of its entries.
Once stated this, I think that isthin(interval(pi)) correctly returns false, while isthin(interval(0.1)) or isthin(interval(1)) correctly return true; in my opinion there is no bug.

Indeed. Now it's just a matter of deciding whether isthin(pi) should return false or true 🤷‍♂️ (and also for isthin(Inf), but this one is more of a corner case)

@lbenet
Copy link
Member

lbenet commented Feb 2, 2024

Using this branch, I get the following:

julia> x = interval(0.5,1)
Interval{Float64}(0.5, 1.0, com)

julia> x >= 0
ERROR: ArgumentError: `==` is only supported for thin intervals. See instead `isequal_interval`
...

It seems to me that, using directly the comparison (>=, or something alike, as I do for Julia Numbers), due to the error, I wouldn't be able to approximate log(x) as a Taylor series for the interval x. (This is part of the reason I check if log(x) is an empty interval, in which case because it includes the intersect_interval operation which is precisely the filter I need.

@OlivierHnt
Copy link
Member Author

OlivierHnt commented Feb 3, 2024

Yea that's what I was referring to in my previous comment.

Somehow I can wrap my head around isthin(pi) being false, since an Irrational can never be an exact numerical value.
But this is puzzling

julia> isunbounded(Inf)
┌ Warning: invalid interval, NaI is returned
└ @ IntervalArithmetic ~/.julia/dev/IntervalArithmetic/src/intervals/construction.jl:417
false

One good thing is that we have a warning message... And it's also not wrong in the sense that Inf does not correspond to an unbounded interval (this is flavour dependant), but in practice one may expect isunbounded to be equivalent to isinf for numbers.

@lbenet
Copy link
Member

lbenet commented Feb 3, 2024

Somehow I can wrap my head around isthin(pi) being false, since an Irrational can never be an exact numerical value.

I agree!

But this is puzzling

julia> isunbounded(Inf)
┌ Warning: invalid interval, NaI is returned
└ @ IntervalArithmetic ~/.julia/dev/IntervalArithmetic/src/intervals/construction.jl:417
false

One good thing is that we have a warning message... And it's also not wrong in the sense that Inf does not correspond to an unbounded interval (this is flavour dependant), but in practice one may expect isunbounded to be equivalent to isinf for numbers.

This is indeed puzzling, but ok in my opinion; I think we are consistent with the fact that interval(Inf) corresponds to NaI; I also think this is in also consistent with the standard, isn't it?

@lbenet
Copy link
Member

lbenet commented Feb 3, 2024

Regarding JuliaDiff/TaylorSeries.jl#345, I am not exploiting the proposed functionality, and the error messages, while ok, are also frustrating... Yet, I do need something related to #624, perhaps in the way you propose with setdecoration.

@OlivierHnt OlivierHnt closed this Feb 18, 2024
@OlivierHnt OlivierHnt deleted the bool branch February 21, 2024 13:23
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