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

Convert eltype using specific types #137

Open
jishnub opened this issue Jan 21, 2023 · 7 comments
Open

Convert eltype using specific types #137

jishnub opened this issue Jan 21, 2023 · 7 comments

Comments

@jishnub
Copy link
Contributor

jishnub commented Jan 21, 2023

The conversion to float works, so ideally, conversions to specific element types should work as well:

julia> float(0..1)
0.0..1.0

julia> Float64(0..1)
ERROR: MethodError: no method matching Float64(::IntervalSets.ClosedInterval{Int64})

Closest candidates are:
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number}
   @ Base char.jl:50
  (::Type{T})(::Base.TwicePrecision) where T<:Number
   @ Base twiceprecision.jl:266
  (::Type{T})(::Complex) where T<:Real
   @ Base complex.jl:44
  ...

Stacktrace:
 [1] top-level scope
   @ REPL[67]:1
@hyrodium
Copy link
Collaborator

We will not support Float64(0..1) because every method of Float64 should be a constructor that returns Float64.
(Same issue on Unitful.jl: PainterQubits/Unitful.jl#358)

I think it would be nice to have a method float(::Type{<:AbstractFloat}, x) in Base. After adding the method in Base, we can add the method float(::Type{<:AbstractFloat}, ::Interval) in this package.

@daanhb
Copy link
Contributor

daanhb commented Jan 21, 2023

There are other possible ways of expressing this (all of which work for ranges): Float64.(0..1), convert(Domain{Float64}, 0..1) or map(Float64, 0..1).

@dlfivefifty
Copy link
Member

I really like Float64.(0..1)! Note this is the only one that has a Set counterpart:

julia> x = Set([1,2,3])
Set{Int64} with 3 elements:
  2
  3
  1

julia> float(x)
ERROR: MethodError: no method matching AbstractFloat(::Set{Int64})
Closest candidates are:
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number} at char.jl:50
  (::Type{T})(::Base.TwicePrecision) where T<:Number at twiceprecision.jl:266
  (::Type{T})(::Complex) where T<:Real at complex.jl:44
  ...
Stacktrace:
 [1] float(x::Set{Int64})
   @ Base ./float.jl:269
 [2] top-level scope
   @ REPL[2]:1

julia> Float64.(x)
3-element Vector{Float64}:
 2.0
 3.0
 1.0

julia> map(Float64, x)
ERROR: map is not defined on sets
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] map(f::Type, #unused#::Set{Int64})
   @ Base ./abstractarray.jl:2964
 [3] top-level scope
   @ REPL[4]:1

@jishnub
Copy link
Contributor Author

jishnub commented Jan 21, 2023

I agree that Float64.(0..1) and map(Float64, 0..1) seem like a natural way to express this operation, and these are what I had tried initially, but netiher seem to work:

julia> Float64.(0..1)
ERROR: MethodError: no method matching size(::IntervalSets.ClosedInterval{Int64})

Closest candidates are:
  size(::Union{LinearAlgebra.QR, LinearAlgebra.QRCompactWY, LinearAlgebra.QRPivoted})
   @ LinearAlgebra ~/packages/julias/julia-1.9/share/julia/stdlib/v1.9/LinearAlgebra/src/qr.jl:581
  size(::Union{LinearAlgebra.QR, LinearAlgebra.QRCompactWY, LinearAlgebra.QRPivoted}, ::Integer)
   @ LinearAlgebra ~/packages/julias/julia-1.9/share/julia/stdlib/v1.9/LinearAlgebra/src/qr.jl:580
  size(::Union{LinearAlgebra.QRCompactWYQ, LinearAlgebra.QRPackedQ})
   @ LinearAlgebra ~/packages/julias/julia-1.9/share/julia/stdlib/v1.9/LinearAlgebra/src/qr.jl:584
  ...

Stacktrace:
 [1] axes
   @ ./abstractarray.jl:98 [inlined]
 [2] combine_axes(A::IntervalSets.ClosedInterval{Int64})
   @ Base.Broadcast ./broadcast.jl:513
 [3] instantiate
   @ ./broadcast.jl:294 [inlined]
 [4] materialize(bc::Base.Broadcast.Broadcasted{DomainSets.DomainSetStyle, Nothing, Type{Float64}, Tuple{IntervalSets.ClosedInterval{Int64}}})
   @ Base.Broadcast ./broadcast.jl:873
 [5] top-level scope
   @ REPL[2]:1

julia> map(Float64, 0..1)
ERROR: MethodError: no method matching iterate(::IntervalSets.ClosedInterval{Int64})

Closest candidates are:
  iterate(::Union{LinRange, StepRangeLen})
   @ Base range.jl:880
  iterate(::Union{LinRange, StepRangeLen}, ::Integer)
   @ Base range.jl:880
  iterate(::T) where T<:Union{Base.KeySet{<:Any, <:Dict}, Base.ValueIterator{<:Dict}}
   @ Base dict.jl:698
  ...

Stacktrace:
 [1] iterate
   @ ./generator.jl:44 [inlined]
 [2] grow_to!(dest::Vector{Float64}, itr::Base.Generator{IntervalSets.ClosedInterval{Int64}, Type{Float64}})
   @ Base ./array.jl:855
 [3] collect(itr::Base.Generator{IntervalSets.ClosedInterval{Int64}, Type{Float64}})
   @ Base ./array.jl:779
 [4] map(f::Type, A::IntervalSets.ClosedInterval{Int64})
   @ Base ./abstractarray.jl:3283
 [5] top-level scope
   @ REPL[3]:1

This is what had brought me to Float64(0..1) after discovering float(::Interval), although that's perhaps not analogous, as float evidently works on arrays as well. The convert suggestion is the one that works at present. Perhaps the other routes could also be made to work?

@hyrodium
Copy link
Collaborator

There was a discussion on broadcasting: #55

@aplavin
Copy link
Contributor

aplavin commented Jan 22, 2023

Another common mathematical operation that makes total and unambiguous sense for intervals (and other domains, btw) is adding/stripping Unitful units.

Here is a pirating snippet that I use for interactive work with unitful intervals:

Base.:*(i::Interval, u::Unitful.Units) = @modify(x -> x*u, x |> Properties())
Unitful.ustrip(x::AbstractInterval) = @modify(ustrip, x |> Properties())
Unitful.ustrip(u::Unitful.Units, x::AbstractInterval) = @modify(f -> ustrip(u, f), x |> Properties())

It allows stuff like i = (1..2)u"m", ustrip(i), ustrip(u"cm", i).
Would be great if something similar was added to IntervalSets itself!

Also, deg2rad and rad2deg.

@daanhb
Copy link
Contributor

daanhb commented Jan 22, 2023

Hmm, the broadcast and map syntax would need special support which isn't there, but I was expecting the convert syntax to work for units. It does not:

julia> using IntervalSets, Unitful

julia> T = typeof(1.0u"s")
Quantity{Float64, 𝐓, Unitful.FreeUnits{(s,), 𝐓, nothing}}

julia> convert(Domain{T}, 1..2)
ERROR: DimensionError: s and 1 are not dimensionally compatible.

The reason is a (valid!) difference between conversion and constructor syntax of unit types:

julia> convert(T, 1)
ERROR: DimensionError: s and 1 are not dimensionally compatible.

julia> T(1)
1.0 s

Given this behaviour, I think it is valid that the conversion to an interval fails. But T.(1..2) or map(T, 1..2) could be made to work (for any T), and this example might be motivation to do so.

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

No branches or pull requests

5 participants