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

Project plan #2

Open
4 of 6 tasks
dlfivefifty opened this issue Aug 12, 2020 · 33 comments
Open
4 of 6 tasks

Project plan #2

dlfivefifty opened this issue Aug 12, 2020 · 33 comments

Comments

@dlfivefifty
Copy link
Member

dlfivefifty commented Aug 12, 2020

  1. Infinity <: Real (replaces Infinity.Infinite)
  2. InfiniteCardinal{K} <: Integer (InfiniteCardinal{0} replaces InfiniteArrays.Infinity and InfiniteCardinal{K} replaces ContinuumArrays.AlephInfinity{K})
  3. RealInfinity <: Real
  4. ComplexInfinity <: Number for angled infinity
  5. InfiniteTime <: TimeType
  6. InfExtended*

I'll plan to do 1--4 by copying over the code from InfiniteArrays.jl.

@dlfivefifty
Copy link
Member Author

dlfivefifty commented Aug 12, 2020

@cjdoris For consistency in naming we should choose one of the following:

  1. RealInfinity and ComplexInfinity
  2. SignedInfinity and AngledInfinity

In the previous proposal it mixes terms. Which of the two do you prefer?

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

I prefer ComplexInfinity over AngledInfinity because then it's clear it really is meant to be a complex number, and also there are many kinds of angles.

But then if we go with 1 then logically we should also have PositiveRealInfinity (or UnsignedRealInfinity or URealInfinity) instead of Infinity.

@dlfivefifty
Copy link
Member Author

I think just Infinity is fine for PositiveRealInfinity, as it will be the one with as the shorthand. The README can make this clear.

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

I'm OK with that.

@dlfivefifty
Copy link
Member Author

Hmm, Note that ComplexInfinity{Bool} is actual the same as RealInfinity... Does this change anything design wise?

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Does it? What does the type parameter mean?

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

I was thinking about it earlier, and I definitely think that ComplexInfinity{T} should represent the number z * ∞ by the non-zero complex number z :: Complex{T} and not by the angle. Firstly, this is more consistent with the existing Complex type, which uses Cartesian coordinates. Secondly, this doesn't get in the way of using Gaussian integers Complex{Int} or infinite Gaussian integers ComplexInfinity{Int} and InfExtendedComplex{Int}.

@dlfivefifty
Copy link
Member Author

Does it? What does the type parameter mean?

See

struct ComplexInfinity{T<:Real} <: Number

I currently represent it by the angle, but stored as signbit, that is π * a.signbit. The reason I did this is so that ±∞ are exactly representable. But of course they are also exactly representable if we store z.

But I don't think I actually use InfiniteArrays.OrientedInfinity for anything important, so we can change it to your suggestion.

@JeffreySarnoff
Copy link
Member

Projective Infinity (the infinity of the Riemann sphere)

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Yes please. As a general rule I think x*∞ should be represented exactly if possible, whatever the type of x. On the whole this might mean storing x exactly, but for reals a sign bit suffices.

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Projective Infinity (the infinity of the Riemann sphere)

There's already https://github.com/scheinerman/RiemannComplexNumbers.jl

No reason we couldn't add projective infinities in general though, if there's demand.

@dlfivefifty
Copy link
Member Author

Hmm, I think what we really want are union types that are subtypes of Real or Number. In other words, we wish we could do:

const RiemannSphere{T} = Union{Complex{T}, ComplexInfinity{T}}

but we cannot since Union is not <: Number.

But what do you think about this:

struct IntegerUnion{TYPES<:Union} <: Integer
    types::TYPES
end
struct RealUnion{TYPES<:Union} <: Real
    types::TYPES
end
struct ComplexUnion{TYPES<:Union} <: Number
     types::TYPES
end

const ExtendedInt = IntegerUnion{Union{Int,RealInfinity}}
const ExtendedComplex{T} = ComplexUnion{Union{Complex{T},ComplexInfinity{T}}}
const RiemannSphere{T} = ComplexUnion{Union{Complex{T},Infinity}}

@dlfivefifty
Copy link
Member Author

Sorry, I was wrong:

julia> Union{Real,Complex} <: Number
true

So actually, my new proposal is have no special types for extended numbers and just do:

const ExtendedInt = Union{Int,RealInfinity}
const ExtendedComplex{T} = Union{Complex{T},ComplexInfinity{T}}
const RiemannSphere{T} = Union{Complex{T},Infinity}

What if any draw back would there be?

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Type instability and promotion craziness! This is what InfExtendedReal{Int} solves, by being a single concrete type.

@dlfivefifty
Copy link
Member Author

Small union types do not suffer from type instability: this is the whole point of Union{T,Missing}.

@JeffreySarnoff
Copy link
Member

I have done something similar.

  • Performant logic requires this sort of an exhaustive approach (afaik).
  • could be streamlined some with macros and @eval loops
using Base.Checked

abstract type IntegerInfinity end
struct PosIntInfinity <: IntegerInfinity end
struct NegIntInfinity <: IntegerInfinity end

const IntInfinity = Union{PosIntInfinity, NegIntInfinity}
const ExtendedInt = Union{Int, IntInfinity}

const PosIntInf = PosIntInfinity()
const NegIntInf = NegIntInfinity()

const InfInt = IntInfinity()
const ExtendedInt = Union{Int, IntInfinity}

function Base.(*)(x::Int, y::Int)::ExtendedInt
    z, ovf = mul_with_overflow(x, y)
    return ovf ? (signbit(x) === signbit(y) ? PosInfInt : NegInfInt) : z
end
function Base.(*)(x::Int, y::PosIntInfinity)::IntInfinity
    signbit(x) ? NegIntInf : PosIntInf
end
function Base.(*)(x::Int, y::NegIntInfinity)::IntInfinity
    signbit(x) ? PosIntInf : NegIntInf
end
function Base.(*)(x::PosIntInfinity y::Int)::IntInfinity
    signbit(y) ? NegIntInf : PosIntInf
end
function Base.(*)(x::NegIntInfinity, y::Int)::IntInfinity
    signbit(y) ? PosIntInf : NegIntInf
end
Base.(")(x::PosIntInfinity, y::PosIntInfinty)::PosIntInfinity = PosIntInf
Base.(")(x::PosIntInfinity, y::NegIntInfinty)::NegIntInfinty  = NegIntInf
Base.(")(x::NegIntInfinity, y::PosIntInfinty)::NegIntInfinty  = NegIntInf
Base.(")(x::NegIntInfinity, y::NegIntInfinty)::PosIntInfinty  = PosIntInf

@dlfivefifty
Copy link
Member Author

Perhaps we should nail down Infinity first, say as v0.1, and we can think about extended reals for v0.2? That way we can do some testing if a union type suffices.

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Small union types do not suffer from type instability: this is the whole point of Union{T,Missing}.

That relies on the specifics of the Julia compiler, and the notion of "small union". If I have a x :: Vector{Union{Int,RealInfinity}} then map(Vector, x) is a Vector{Union{Vector{Int}, Vector{RealInfinity}}}. Is that still type-stable and fast? Is it what the user really wanted? The current API for numbers is that binary operations are type-stable and promote to a common type.

@dlfivefifty
Copy link
Member Author

Your example doesn't make any sense since Vector(5) and Vector(∞) are not defined.

Your logic applies also to Missing... if its good enough for Missing isn't it good enough for us? In other words, to make a convincing argument you would have to point to a specific weakness in Union{T,Missing} that is important enough to justify the extra type.

@dlfivefifty
Copy link
Member Author

To hammer down the point, a working example does type inferrence with union types:

julia> @inferred(map(something, Union{Int,Nothing}[1,2]))
2-element Array{Int64,1}:
 1
 2

@dlfivefifty
Copy link
Member Author

Or an example more close to home:

julia> closeint(::Infinity) = typemax(Int)
closeint (generic function with 1 method)

julia> closeint(x::Int) = x
closeint (generic function with 2 methods)

julia> @inferred(map(closeint, [1,5,∞]))
3-element Array{Int64,1}:
                   1
                   5
 9223372036854775807

That relies on the specifics of the Julia compiler

No it does not, as the behaviour for Missing and Nothing is documented and advocated as a good design

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

julia> map(a->[a], Union{Int,Infinity}[1,Infinity()])
2-element Array{Array{T,1} where T,1}:
 [1]
 [Infinity()]

julia> @inferred ans[1]
ERROR: return type Array{Int64,1} does not match inferred return type Array{T,1} where T
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] top-level scope at REPL[13]:1

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

The above is exactly the sort of thing that someone doing numerical computing shouldn't have to think about: the existing API for number types is that they promote to a common concrete type when mixed together.

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Also [1, Infinity()] is a Vector{Real} and 1:Infinity() doesn't work at all.

I suppose one fix would be to have a promotion rule T<:Real + Infinity = Union{T,Infinity}, but that wouldn't fix the earlier issue.

@dlfivefifty
Copy link
Member Author

Hmm, with the right overrides we can get things to work:

julia> Base.typejoin(::Type{Vector{Infinity}}, ::Type{Vector{Int}}) = Vector{Union{Int,Infinity}}

julia> Base.typejoin(::Type{Vector{Int}}, ::Type{Vector{Infinity}}) = Vector{Union{Int,Infinity}}

julia> Base.promote_rule(::Type{Int}, ::Type{Infinity}) = Union{Int,Infinity}

julia> map(a->[a], [1,Infinity()])
2-element Array{Array{Union{Infinity, Int64},1},1}:
 [1]
 [∞]

Though you are right it might be necessary to just have custom types, e.g., the last line is not type-inferred.

@dlfivefifty
Copy link
Member Author

PS:

julia> map(a -> [a], [1,missing])
2-element Array{Array{T,1} where T,1}:
 [1]
 [missing]

So it seems like a missing case in the compiler.

@JeffreySarnoff
Copy link
Member

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Hmm, with the right overrides we can get things to work:

My point was not specific to Vector, but applies to any type that has a numeric type parameter. Also, you really shouldn't overload typejoin, it has a very precise meaning that your definitions break.

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Arithmetic of ComplexInfiniity

I get File Not Found. <<<< see below for a pdf version >>>>

But the name reminds me to say that RiemannSphere{T} = Union{Complex{T}, Infinity} doesn't make conceptual sense because Infinity() is the positive real infinity, which is not the same thing as projective infinity. So if anything it should be RiemannSphere{T} = Union{Complex{T}, ProjectiveInfinity}.

@dlfivefifty
Copy link
Member Author

Also, you really shouldn't overload typejoin

Agreed, you won the argument and these should be special types (at least until Julia v2.0 comes along and improves Missing support even more)

@cjdoris
Copy link

cjdoris commented Aug 12, 2020

Glad I won you around :)

What's happening in v2.0? Where do I go to find out such things?

@dlfivefifty
Copy link
Member Author

What's happening in v2.0? Where do I go to find out such things?

Was just being facetious...but perhaps they can be convinced to improve the type inference. (It might be pretty easy actually with an improved Generator.)

@JeffreySarnoff
Copy link
Member

JeffreySarnoff commented Aug 12, 2020

Arithmetic of ComplexInfinity
(Wolfram web page as pdf, zoom in to read)

@putianyi889 putianyi889 mentioned this issue Mar 29, 2023
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

3 participants