Skip to content
Draft
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
14 changes: 13 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@ uuid = "e1ba4f0e-776d-440f-acd9-e1d2e9742647"
authors = ["Sheehan Olver <solver@mac.com>"]
version = "0.1.13"

[weakdeps]
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"

[extensions]
InfinitiesForwardDiffExt = "ForwardDiff"
InfinitiesStaticExt = "Static"

[compat]
Aqua = "0.8"
Base64 = "1"
ForwardDiff = "1"
JET = "0.9, 0.10, 0.11, 0.12"
Static = "1.4"
Test = "1"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "Base64", "JET", "Test"]
test = ["Aqua", "Base64", "ForwardDiff", "JET", "Static", "Test"]
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,86 @@ This Julia package is used to represent infinities, including:

Note that we subtype based on interfaces, rather than strict mathematical definitions. For example, `ℵ₀ isa Integer` as `Integer` is often used to represent the size of a set or vector. Similarly, `∞ isa Real`.

## Extending `RealInfinity`

To add another representation of positive or negative infinity, subtype `RealInfinity`
and define `Base.signbit`: return `false` for positive infinity and `true` for negative infinity.

```julia
struct SignedInfinity <: RealInfinity
negative::Bool
end
Base.signbit(inf::SignedInfinity) = inf.negative

SignedInfinity(true) == -∞ # true
SignedInfinity(true)^2 === +∞ # true
```

Read the sign from your representation in `signbit`. Do not define it as `x < 0`,
because the inherited comparisons call `signbit` and would cause infinite recursion.
You do not need to implement arithmetic, comparisons, floating-point conversion, or
hashing: your subtype inherits these operations from `RealInfinity`. Any additional
fields you store are ignored when comparing or hashing values. Calling `zero` on your
type or an instance returns `0.0`. Calling `one` or `oneunit` returns `1.0`.

A subtype may represent only one sign. Results need not retain its concrete type or
metadata: negation and powers may return `PositiveInfinity` or `NegativeInfinity`.
Constructors and representation-preserving conversions are the subtype's responsibility.
Specialize standard Base operations when representation preservation is needed.

## Static.jl integration

Loading [Static.jl](https://github.com/SciML/Static.jl) enables an optional package extension.
The values `∞`, `+∞`, `-∞`, `InfiniteCardinal{k}()`, and `NotANumber()` are already
fully determined by their types, so they need no separate static representation:

```julia
using Infinities, Static

static(∞) === ∞ # true
is_static(typeof(-∞)) === True() # true
known(typeof(ℵ₀)) === ℵ₀ # true
Static.lt(static(2), ∞) === True() # true
static(2) + ℵ₀ === ℵ₀ # true
```

Mixed arithmetic and comparisons use the same rules as the corresponding ordinary
numbers, including undefined results and NaN propagation. Base operations retain their
ordinary return types: `one(∞)` is `1` and `isinf(∞)` is `true`. Use `static` on a result
or Static.jl's comparison functions when a static result type is needed.

`ComplexInfinity` stores its direction as a value, not in its type. Its arithmetic
accepts static operands, but `is_static(ComplexInfinity)` is `False()` and
`static(im*∞)` is unsupported.

## ForwardDiff.jl integration

Loading [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) enables an optional
extension for mixed `+`, `-`, `*`, `/`, `mod`, `rem`, `min`, `max`, and comparisons
of dual numbers with `∞`, `+∞`, `-∞`, and `NotANumber()`. Primal values retain
Infinities' scalar results and exceptions, without converting infinities to floats.
Derivative rules use the same scalar arithmetic, so a zero tangent multiplied by an
infinity becomes `NotANumber()`. Bounded `mod` and `rem` preserve the dividend's
tangents, while an undefined remainder has undefined tangents.

```julia
using Infinities, ForwardDiff

ForwardDiff.derivative(input -> input * ∞, 2.0) # +∞
ForwardDiff.derivative(input -> input + ∞, 2.0) # 1.0
ForwardDiff.derivative(input -> input / ∞, 2.0) # 0.0
```

`Dual(∞)` preserves the infinity. Explicitly requesting a floating scalar type, such
as `Dual{Nothing, Float64}(∞)`, converts it. Mixed symbolic results can use `Real`
as the dual's scalar parameter, with a corresponding loss of type specialization.

This is not general support for arbitrary symbolic differentiation: ForwardDiff
operations such as unary negation can reject heterogeneous symbolic partials, and
powers remain subject to the existing scalar and ForwardDiff limitations. There is
no floating-point fallback. The extension does not define mixed dual-number operations
for `InfiniteCardinal` or `ComplexInfinity`.

## Similar packages

This package is meant to eventually replace [Infinity.jl](https://github.com/cjdoris/Infinity.jl) and the definitions of `∞` in [InfiniteArrays.jl](https://github.com/JuliaArrays/InfiniteArrays.jl). We do not yet support Infinity.jl's notions of `InfExtendedReal` but we hope to add this soon.
51 changes: 51 additions & 0 deletions ext/InfinitiesForwardDiffExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module InfinitiesForwardDiffExt

using Infinities: Infinity, RealInfinity, NotANumber
import ForwardDiff: Dual, Partials, value, partials

function symbolic_dual(::Dual{Tag}, primal, tangents::NTuple{N, Any}) where {Tag, N}
Dual{Tag, Real, N}(primal, Partials{N, Real}(tangents))
end

for Typ in (Infinity, RealInfinity, NotANumber)
for op in (:^, :min, :max, :(==), :isequal, :<, :<=, :isless)
@eval Base.$op(dual::Dual, inf::$Typ) = invoke($op, Tuple{Dual, Real}, dual, inf)
@eval Base.$op(inf::$Typ, dual::Dual) = invoke($op, Tuple{Real, Dual}, inf, dual)
end

for (op, forward, reverse) in ((:+, :tangent, :tangent),
(:-, :tangent, :(-tangent)), (:*, :(tangent * inf), :(tangent * inf)),
(:/, :(tangent / inf), :(-(primal / value(dual)) * tangent)))
@eval function Base.$op(dual::Dual, inf::$Typ)
primal = $op(value(dual), inf)
symbolic_dual(dual, primal, map(tangent -> $forward, Tuple(partials(dual))))
end
@eval function Base.$op(inf::$Typ, dual::Dual)
primal = $op(inf, value(dual))
symbolic_dual(dual, primal, map(tangent -> $reverse, Tuple(partials(dual))))
end
end

for op in (:mod, :rem)
@eval function Base.$op(dual::Dual, inf::$Typ)
primal = $op(value(dual), inf)
symbolic_dual(dual, primal, map(tangent -> isnan(primal) ? NotANumber() : tangent, Tuple(partials(dual))))
end
@eval Base.$op(inf::$Typ, dual::Dual) =
symbolic_dual(dual, $op(inf, value(dual)), map(_ -> NotANumber(), Tuple(partials(dual))))
end

@eval begin
Dual(inf::$Typ) = Dual{Nothing}(inf, ())
Dual{Tag}(inf::$Typ) where {Tag} = Dual{Tag}(inf, ())
Dual{Tag, Value}(inf::$Typ) where {Tag, Value} = Dual{Tag, Value, 0}(inf)
Dual{Tag, Value, N}(inf::$Typ) where {Tag, Value, N} =
Dual{Tag, Value, N}(convert(Value, inf), zero(Partials{N, Value}))
Dual{Tag}(inf::$Typ, tangents::Partials{N, Value}) where {Tag, N, Value} =
Dual{Tag, Real, N}(inf, Partials{N, Real}(Tuple(tangents)))
Dual{Tag}(inf::Value, tangents::Partials{N, Value}) where {Tag, N, Value<:$Typ} =
Dual{Tag, Real, N}(inf, Partials{N, Real}(Tuple(tangents)))
end
end

end
37 changes: 37 additions & 0 deletions ext/InfinitiesStaticExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module InfinitiesStaticExt

using Infinities: Infinity, PositiveInfinity, NegativeInfinity, InfiniteCardinal, NotANumber
using Infinities: AllInfinities, AllRealInfinities, IntegerInfinities, ComplexInfinity, RealInfinity
using Static: Static, dynamic, StaticNumber, StaticInteger, StaticFloat64, StaticInt, True

for Typ in (Infinity, PositiveInfinity, NegativeInfinity, NotANumber)
@eval begin
Static.static(x::$Typ) = x
Static.is_static(::Type{$Typ}) = True()
Static.known(::Type{$Typ}) = $Typ()
end
end

Static.static(x::InfiniteCardinal) = x
Static.is_static(::Type{InfiniteCardinal{N}}) where {N} = True()
Static.known(::Type{InfiniteCardinal{N}}) where {N} = InfiniteCardinal{N}()

for (ops, Types, StaticTypes) in (
((:+, :-, :*, :/, :(==)), (AllInfinities,), (StaticNumber,)),
((:isequal,), (NotANumber,), (StaticNumber,)),
((:div, :fld, :cld, :divrem), (IntegerInfinities,), (StaticNumber,)),
((:<, :<=, :>, :>=), (AllInfinities,), (StaticInteger, StaticFloat64)),
((:isless,), (AllRealInfinities, InfiniteCardinal, NotANumber), (StaticInteger, StaticFloat64)),
((:min, :max), (AllInfinities, NotANumber), (StaticInteger,)),
((:min, :max), (IntegerInfinities, ComplexInfinity, NotANumber), (StaticFloat64,)),
((:mod,), (IntegerInfinities, NotANumber), (StaticNumber,)),
((:rem,), (IntegerInfinities, NotANumber), (StaticInteger, StaticFloat64)),
((:*,), (InfiniteCardinal,), (StaticInt{0},)),
), op in ops, Typ in Types, StaticTyp in StaticTypes
@eval Base.$op(x::$Typ, y::$StaticTyp) = $op(x, dynamic(y))
@eval Base.$op(x::$StaticTyp, y::$Typ) = $op(dynamic(x), y)
end

Base.:^(x::RealInfinity, y::StaticNumber) = x^dynamic(y)

end
Loading
Loading