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

[weakdeps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3"

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

[compat]
Aqua = "0.8"
Base64 = "1"
Dates = "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"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
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", "Dates", "ForwardDiff", "JET", "Static", "Test"]
117 changes: 117 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,123 @@ 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.

## Comparing other types with infinity

With `Infinities.@archimedean` you can opt-in a non-`Real` type to compare with `∞`, `+∞`
and `-∞`, and support `+` and `-` with them. The name refers to
[Archimedean groups](https://en.wikipedia.org/wiki/Archimedean_group), in which adding any
positive value often enough exceeds every other value. Declare a type whose values lie on an
unbounded additive scale, i.e. it _models_ something unbounded, such as lengths or dates, and
which defines `isless`:

```julia
struct Meter
value::Rational{Int}
end
Base.isless(a::Meter, b::Meter) = isless(a.value, b.value)
Infinities.@archimedean Meter

-∞ < Meter(3//2) < ∞ # true
Meter(3//2) - ∞ === -∞ # true
```

A scale bounded on one side, i.e. something which is bounded on one side and unbounded on the
other, names its unbounded end, as in `@archimedean +∞ Kelvin` where positive temperatures can
go to infinity, but negative temperatures aren't a thing (neglecting thermodynamic temperature).
Cyclic types such as a time of day and orders without a fixed step such as strings do not
qualify. The docstring of `@archimedean` explains when a declaration is valid.

Loading Dates enables an extension that declares the periods, `Date` and `DateTime`:

```julia
using Infinities, Dates

Date(2026, 9, 27) < ∞ # true
Day(3) + ∞ === ∞ # true
```

`Dates.Time` is left out, because a time of day wraps at midnight.

## 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.
11 changes: 11 additions & 0 deletions ext/InfinitiesDatesExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module InfinitiesDatesExt

using Infinities: @archimedean
using Dates: Year, Quarter, Month, Week, Day, Hour, Minute, Second, Millisecond, Microsecond,
Nanosecond, Date, DateTime

# `Time` is left out, as it models a bounded quantity which wraps at midnight.
# Calendar periods are not comparable with fixed-duration periods.
@archimedean Year Quarter Month Week Day Hour Minute Second Millisecond Microsecond Nanosecond Date DateTime

end
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, OrderedInfinities
using Infinities: 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,)),
((:<, :<=, :>, :>=), (OrderedInfinities,), (StaticInteger, StaticFloat64)),
((:isless,), (AllRealInfinities, InfiniteCardinal, NotANumber), (StaticInteger, StaticFloat64)),
((:min, :max), (OrderedInfinities, NotANumber), (StaticInteger, 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