Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/src/numbers.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ In addition, the package supports different floating-point types underlying any

```@example examples
using DoubleFloats
seg = Segment{DoubleFloat}(-1, 1)
seg = Segment{Double64}(-1, 1)
seg(2//3)
```

Expand Down
9 changes: 6 additions & 3 deletions src/arcs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

Each `Arc` type is parameterized according to the common type of its complex input arguments.
"""
struct Arc{T} <: AbstractCurve{T}
circle::Circle{T}
struct Arc{T,Z<:AnyComplex{T}} <: AbstractCurve{T}
circle::Circle{T,Z}
start::T # specified as positive fraction of 1 ccw rotation from positive real
delta::T
Arc{T}(c, s, d) where {T} = new(convert_real_type(T, c), T(s), T(d))
Arc{T,Z}(c::Circle{T,Z}, s, d) where {T,Z} = new{T,Z}(c, T(s), T(d))
end
# Determine Z from the (possibly converted) circle.
Arc{T}(c, s, d) where {T} = _arc(convert_real_type(T, c), T(s), T(d))
_arc(c::Circle{T,Z}, s, d) where {T,Z} = Arc{T,Z}(c, s, d)

# Untyped constructors
"""
Expand Down
6 changes: 3 additions & 3 deletions src/circles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

Each `Circle` type is parameterized according to the common type of its complex input arguments.
"""
struct Circle{T} <: AbstractClosedCurve{T}
center::AnyComplex{T}
struct Circle{T,Z<:AnyComplex{T}} <: AbstractClosedCurve{T}
center::Z
radius::T
ccw::Bool
function Circle{T}(z, r, ccw=true) where {T}
Tz = complex(convert_real_type(T, z))
Tr = convert(T, r)
return new(Tz, Tr, ccw)
return new{T,typeof(Tz)}(Tz, Tr, ccw)
end
end
"""
Expand Down
26 changes: 15 additions & 11 deletions src/curves.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ isoutside(C::AbstractClosedCurve) = z -> isoutside(z, C)
"""
(type) Smooth curve defined by an explicit function of a real paramerter in [0,1].
"""
struct Curve{T} <: AbstractCurve{T}
point::Function
tangent::Function
struct Curve{T,F,G} <: AbstractCurve{T}
point::F
tangent::G
Curve{T}(p, q) where {T} = new{T,typeof(p),typeof(q)}(p, q)
end

"""
Expand Down Expand Up @@ -167,14 +168,17 @@ end
"""
(type) Smooth closed curve defined by an explicit function of a real paramerter in [0,1].
"""
struct ClosedCurve{T} <: AbstractClosedCurve{T}
curve::Curve{T}
function ClosedCurve{T}(c::Curve; tol=tolerance(T)) where T<:AbstractFloat
@assert isapprox(point(c, T(0)), point(c, T(1)); rtol=tol, atol=tol) "Curve does not close"
new(convert_real_type(T, c))
end
ClosedCurve{T}(f::Function, args...) where T = ClosedCurve{T}(Curve{T}(f, args...))
struct ClosedCurve{T,F,G} <: AbstractClosedCurve{T}
curve::Curve{T,F,G}
ClosedCurve{T,F,G}(c::Curve{T,F,G}) where {T,F,G} = new{T,F,G}(c)
end
function ClosedCurve{T}(c::Curve; tol=tolerance(T)) where T<:AbstractFloat
@assert isapprox(point(c, T(0)), point(c, T(1)); rtol=tol, atol=tol) "Curve does not close"
return _closedcurve(convert_real_type(T, c))
end
# Function barrier to capture the concrete point/tangent types.
_closedcurve(c::Curve{T,F,G}) where {T,F,G} = ClosedCurve{T,F,G}(c)
ClosedCurve{T}(f::Function, args...) where T = ClosedCurve{T}(Curve{T}(f, args...))

"""
ClosedCurve(f; tol=<default>)
Expand Down Expand Up @@ -204,7 +208,7 @@ Base.:-(C::ClosedCurve) = ClosedCurve(-C.curve)
Base.:*(C::ClosedCurve, z::Number) = ClosedCurve(C.curve * z)
Base.inv(C::ClosedCurve) = ClosedCurve(inv(C.curve))

convert_real_type(T::Type{<:Real}, C::ClosedCurve{S}) where S = ClosedCurve{T}(C.point, C.tangent)
convert_real_type(T::Type{<:Real}, C::ClosedCurve{S}) where S = ClosedCurve{T}(convert_real_type(T, C.curve))
Base.promote_rule(::Type{<:ClosedCurve{T}}, ::Type{<:ClosedCurve{S}}) where {T,S} = ClosedCurve{promote_type(T,S)}

include("lines.jl")
Expand Down
10 changes: 5 additions & 5 deletions src/doublyconnected.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,18 @@ end

Region between two concentric circles.
"""
struct Annulus{T} <: AbstractDoublyConnectedRegion{T}
outer::Circle{T}
inner::Circle{T}
function Annulus{T}(outer::Circle{T}, inner::Circle{T}) where T
struct Annulus{T,Z<:AnyComplex{T}} <: AbstractDoublyConnectedRegion{T}
outer::Circle{T,Z}
inner::Circle{T,Z}
function Annulus{T}(outer::Circle{T,Z}, inner::Circle{T,Z}) where {T,Z}
@assert(outer.center ≈ inner.center)
if isinside(Inf, outer)
outer = reverse(outer)
end
if isoutside(Inf, inner)
inner = reverse(inner)
end
new(outer, inner)
new{T,Z}(outer, inner)
end
end

Expand Down
11 changes: 7 additions & 4 deletions src/lines.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

Each `Line` type is parameterized according to the common type of its complex input arguments.
"""
struct Line{T} <: AbstractClosedCurve{T}
base::Union{T,AnyComplex{T}}
direction::Union{T,AnyComplex{T}}
Line{T}(a, b) where {T} = new(convert_real_type(T, a), sign(convert_real_type(T, b - a)))
struct Line{T,Z<:Union{T,AnyComplex{T}}} <: AbstractClosedCurve{T}
base::Z
direction::Z
function Line{T}(a, b) where {T}
base, dir = promote(convert_real_type(T, a), sign(convert_real_type(T, b - a)))
new{T,typeof(base)}(base, dir)
end
end
"""
Line(a, b)
Expand Down
33 changes: 19 additions & 14 deletions src/paths.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,30 @@ function angles(P::AbstractPath{T}) where T
return θ
end

Base.conj(p::AbstractPath) = typeof(p)(conj.(curves(p)))
# Reconstruct a path of the same kind from a new set of curves. Uses only the
# real-type parameter so the curve-vector type parameter is recomputed (the new
# curves may differ in type from the originals).
rebuild(p::AbstractPath{T}) where {T} = (typeof(p).name.wrapper){T}

Base.reverse(p::AbstractPath) = typeof(p)(reverse(reverse.(curves(p))))
Base.conj(p::AbstractPath) = rebuild(p)(conj.(curves(p)))

Base.reverse(p::AbstractPath) = rebuild(p)(reverse(reverse.(curves(p))))

function Base.:+(p::AbstractPath{T}, z::Number) where T
return typeof(p)([c + convert_real_type(T, z) for c in curves(p)])
return rebuild(p)([c + convert_real_type(T, z) for c in curves(p)])
end
Base.:+(z::Number, p::AbstractPath) = p + z

Base.:-(p::AbstractPath) = typeof(p)([-c for c in curves(p)])
Base.:-(p::AbstractPath) = rebuild(p)([-c for c in curves(p)])
Base.:-(p::AbstractPath, z::Number) = p + (-z)
Base.:-(z::Number, p::AbstractPath) = (-p) + z

Base.:*(p::AbstractPath, z::Number) = typeof(p)([c * z for c in curves(p)])
Base.:*(z::Number, p::AbstractPath) = typeof(p)([z * c for c in curves(p)])
Base.:*(p::AbstractPath, z::Number) = rebuild(p)([c * z for c in curves(p)])
Base.:*(z::Number, p::AbstractPath) = rebuild(p)([z * c for c in curves(p)])

Base.:/(p::AbstractPath, z::Number) = typeof(p)([c / z for c in curves(p)])
Base.:/(p::AbstractPath, z::Number) = rebuild(p)([c / z for c in curves(p)])
Base.:/(z::Number, p::AbstractPath) = z * inv(p)
inv(p::AbstractPath) = typeof(p)([inv(c) for c in curves(p)])
inv(p::AbstractPath) = rebuild(p)([inv(c) for c in curves(p)])

"""
isapprox(P1::AbstractPath,R2::AbstractPath)
Expand Down Expand Up @@ -260,14 +265,14 @@ isoutside(P::AbstractClosedPath) = z -> isoutside(z, P)
(type) Path
Generic implementation of an `AbstractPath`.
"""
struct Path{T} <: AbstractPath{T}
curve::Vector{AbstractCurve{T}}
struct Path{T,V<:AbstractVector{<:AbstractCurve{T}}} <: AbstractPath{T}
curve::V
function Path{T}(c::AbstractVector{<:AbstractCurve{T}}; tol::Real=tolerance(T)) where {T}
n = length(c)
for k = 1:n-1
@assert isapprox(point(c[k], 1), point(c[k+1], 0), rtol=tol, atol=tol) "Curve endpoints do not match for pieces $(k) and $(k+1)"
end
new{T}(c)
new{T,typeof(c)}(c)
end
end
"""
Expand Down Expand Up @@ -302,13 +307,13 @@ end
(type) ClosedPath
Generic implementation of an `AbstractClosedPath`.
"""
struct ClosedPath{T} <: AbstractClosedPath{T}
curve::Vector{AbstractCurve{T}}
struct ClosedPath{T,V<:AbstractVector{<:AbstractCurve{T}}} <: AbstractClosedPath{T}
curve::V
function ClosedPath{T}(p::AbstractVector{<:AbstractCurve{T}}; tol=tolerance(T)) where {T}
q = Path{T}(p)
zi, zf = point(q, 0), point(q, length(q))
@assert isapprox(zi, zf, rtol=tol, atol=tol) || (isinf(zi) && isinf(zf)) "Path endpoints do not match"
new{T}(p)
new{T,typeof(p)}(p)
end
end
"""
Expand Down
31 changes: 16 additions & 15 deletions src/polygons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ side(p::AbstractCircularPolygon, args...) = curve(p, args...)

function reverse(p::AbstractCircularPolygon)
@assert sum(isinf.(vertices(p))) < 2 "Reversal is not a propoer polygon"
typeof(p)(reverse(reverse.(sides(p))))
rebuild(p)(reverse(reverse.(sides(p))))
end

function winding(p::AbstractCircularPolygon, z::Number)
Expand Down Expand Up @@ -69,13 +69,13 @@ end
(type) CircularPolygon
Type for closed paths consisting entirely of arcs, segments, and rays.
"""
struct CircularPolygon{T} <: AbstractCircularPolygon{T}
path::ClosedPath{T}
function CircularPolygon{T}(p::ClosedPath{T}) where {T}
struct CircularPolygon{T,V<:AbstractVector{<:AbstractCurve{T}}} <: AbstractCircularPolygon{T}
path::ClosedPath{T,V}
function CircularPolygon{T}(p::ClosedPath{T,V}) where {T,V}
# Continuity and closure have been checked to make a closed path
valid = isa.(curves(p), Union{Arc, Segment, Ray})
@assert all(valid) "All sides must be an Arc, Segment, or Ray"
new(p)
new{T,V}(p)
end
function CircularPolygon{T}(p::AbstractVector{<:AbstractCurve{T}}; kw...) where {T}
return CircularPolygon{T}(ClosedPath(p; kw...))
Expand Down Expand Up @@ -121,13 +121,13 @@ ispositive(p::CircularPolygon) = winding(1 / p, 0) < 0
(type) Polygon
Type for closed paths consisting entirely of segments and rays.
"""
struct Polygon{T} <: AbstractPolygon{T}
path::ClosedPath{T}
function Polygon{T}(p::AbstractClosedPath{T}) where {T}
struct Polygon{T,V<:AbstractVector{<:AbstractCurve{T}}} <: AbstractPolygon{T}
path::ClosedPath{T,V}
function Polygon{T}(p::ClosedPath{T,V}) where {T,V}
# Assumes continuity and closure have been checked previously
valid = isa.(curves(p), Union{Segment{T}, Ray{T}})
@assert all(valid) "All sides must be a Segment or Ray of the same base type"
new(p)
new{T,V}(p)
end
function Polygon{T}(p::PathLike{T}; kw...) where {T}
Polygon{T}(ClosedPath{T}(p; kw...))
Expand Down Expand Up @@ -294,20 +294,21 @@ ispositive(p::Polygon) = sum(angles(p) / π .- 1) < 0

## Special polygon types and constructors

struct Rectangle{T} <: AbstractPolygon{T}
center::AnyComplex{T}
struct Rectangle{T,Z<:AnyComplex{T},V<:AbstractVector{<:AbstractCurve{T}}} <: AbstractPolygon{T}
center::Z
radii::SVector{2,T}
rotation::T # angle about the center point
polygon::Polygon{T}
polygon::Polygon{T,V}
Rectangle(center::T, args...) where {T<:Real} = Rectangle(complex(center), args...)
function Rectangle(center::AnyComplex{S}=0.0, radii::AbstractVector{T}=[-1.0, 1.0], rotation::U=0) where {S<:Real,T<:Real,U<:Real}
function Rectangle(center::AnyComplex{S}=0.0, radii::AbstractVector{T}=[1.0, 1.0], rotation::U=0) where {S<:Real,T<:Real,U<:Real}
# rotation is in radians
@assert (length(radii) == 2) && all(radii .>= 0)
R = promote_type(S, T, U)
c = complex(convert_real_type(R, center))
ρ = cis(convert(R, rotation))
z = @. center + ρ * complex(R(radii[1]) * [-1, 1, 1, -1], R(radii[2]) * [-1, -1, 1, 1])
z = @. c + ρ * complex(R(radii[1]) * [-1, 1, 1, -1], R(radii[2]) * [-1, -1, 1, 1])
p = Polygon(z)
new{R}(center, radii, rotation, p)
new{R,typeof(c),typeof(curves(p))}(c, radii, rotation, p)
end
end

Expand Down
11 changes: 6 additions & 5 deletions src/rays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
(type) Ray{T<:AnyComplex} in the complex plane
Each `Ray` type is parameterized according to the common type of its complex input arguments.
"""
struct Ray{T} <: AbstractCurve{T}
base::AnyComplex{T}
struct Ray{T,Z<:AnyComplex{T}} <: AbstractCurve{T}
base::Z
angle::T
reverse::Bool
function Ray{T}(a, d, rev=false) where {T}
new(complex(convert_real_type(T, a)), mod2pi(T(d)), rev)
base = complex(convert_real_type(T, a))
new{T,typeof(base)}(base, mod2pi(T(d)), rev)
end
end

Expand Down Expand Up @@ -38,9 +39,9 @@ function point(R::Ray{T}, t::Real) where {T}
if t == 0
R.base
elseif t == 1
T(Inf)
typeof(R.base)(Inf)
else
R.base + t / (1 - t) * cis(R.angle)
R.base + typeof(R.base)(t / (1 - t) * cis(R.angle))
end
end

Expand Down
9 changes: 5 additions & 4 deletions src/segments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

Each `Segment` type is parameterized according to the common type of its complex input arguments.
"""
struct Segment{T} <: AbstractCurve{T}
za::Union{T,AnyComplex{T}}
zb::Union{T,AnyComplex{T}}
struct Segment{T,Z<:Union{T,AnyComplex{T}}} <: AbstractCurve{T}
za::Z
zb::Z
function Segment{T}(a, b) where {T}
@assert isfinite(a) && isfinite(b)
new(convert_real_type(T, a), convert_real_type(T, b))
za, zb = promote(convert_real_type(T, a), convert_real_type(T, b))
new{T,typeof(za)}(za, zb)
end
end

Expand Down
8 changes: 4 additions & 4 deletions src/simplyconnected.jl
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function isapprox(
end

# disks
const Disk{T} = InteriorSimplyConnectedRegion{T, Circle{T}}
const Disk{T} = InteriorSimplyConnectedRegion{T, <:Circle{T}}
"""
disk(C::Circle)
Construct the disk interior to `C`.
Expand All @@ -133,7 +133,7 @@ show(io::IO, R::Disk) = print(io, "Disk")
# COV_EXCL_STOP

# quads
const Quad{T} = InteriorSimplyConnectedRegion{T, Rectangle{T}}
const Quad{T} = InteriorSimplyConnectedRegion{T, <:Rectangle{T}}
"""
quad(R::Rectangle)
Construct the rectangle interior to `R`.
Expand All @@ -148,7 +148,7 @@ end
# COV_EXCL_STOP

# half-planes
const Halfplane{T} = SimplyConnectedRegion{T, Line{T}}
const Halfplane{T} = SimplyConnectedRegion{T, <:Line{T}}
"""
halfplane(L::Line)
Construct the half-plane to the left of `L`.
Expand All @@ -174,4 +174,4 @@ end
(type) PolygonalRegion
Representation of a simply connected region bounded by a polygon.
"""
const PolygonalRegion{T} = SimplyConnectedRegion{T, Polygon{T}}
const PolygonalRegion{T} = SimplyConnectedRegion{T, <:Polygon{T}}
22 changes: 22 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,28 @@ end
@test arclength(Shapes.spiral(3, 1)) ≈ 172.17394523
end

# `point` and the tangent/normal accessors must infer a concrete complex return
# type. `@inferred` throws if inference fails or widens to an abstract type, so
# these guard against regressions in the curve/path type parameterization.
# Note: only homogeneous (uniform-coordinate) paths/polygons are stable; a
# genuinely heterogeneous path has element type `AbstractCurve` and returns `Any`
# by design, so such cases are intentionally excluded here.
@testset "Type stability using $T" for T in (Float64, BigFloat)
o = complex(T(0))
leaves = (Circle(o, T(1)), Arc(complex(T(1)), 1im + o, -1 + o),
Segment(o, 1im + o), Line(o, 1 + 1im + o), Ray(o, T(1)))
for C in leaves, f in (point, tangent, unittangent, unitnormal)
@test (@inferred f(C, T(3) / 10)) isa Complex{T}
end
poly = Polygon([o, 1 + o, 1 + 1im + o, 1im + o])
@test (@inferred point(poly, T(7) / 4)) isa Complex{T}
@test (@inferred point(Rectangle(o, T[1, 1]), T(1) / 2)) isa Complex{T}
upath = Path([Segment(o, 1 + o), Segment(1 + o, 1 + 1im + o)])
@test (@inferred point(upath, T(1) / 2)) isa Complex{T}
cpath = ClosedPath([Segment(o, 1 + o), Segment(1 + o, 1im + o), Segment(1im + o, o)])
@test (@inferred point(cpath, T(3) / 2)) isa Complex{T}
end

@testset "Utilities" begin
@test isempty(CR.realroots(1, 2, 2))
@test length(CR.realroots(1, -4, 4)) == 1
Expand Down
Loading