Skip to content

Commit b504fca

Browse files
jishnubclaude
andauthored
Define hash for the remaining spaces (#700)
* Define hash for the remaining spaces PR #698 fixed PointSpace and DiracSpace, but every other space in this package was in the same position: Space.jl:165 defines == as spacescompatible plus equal domains, while hash fell back to the identity-based default. Equal-valued spaces therefore hashed differently whenever they held a mutable field, or whenever the domains compared equal without being identical objects: julia> u1, u2 = UnionDomain([0..1, 2..3]), UnionDomain([2..3, 0..1]); julia> ConstantSpace(u1) == ConstantSpace(u2) true julia> hash(ConstantSpace(u1)) == hash(ConstantSpace(u2)) false Since == is a conjunction that includes domain(A) == domain(B), hashing the domain always agrees with ==, and this is all that is needed for the spaces that only compare domains: ZeroSpace, ConstantSpace, ContinuousSpace and SplineSpace. The order of a SplineSpace is part of the type, and it is mixed in so that splines of different orders over the same domain don't collide. The remaining spaces additionally hash whatever spacescompatible compares: the components for SumSpace, PiecewiseSpace, ArraySpace and TensorSpace, the space and the indices for SubSpace, and the underlying space for QuotientSpace, whose boundary conditions are not compared by == either. Two direct sums may hold their pieces in different containers and still be equal, so their components are hashed one at a time instead of hashing the container. Base hashes a range through its length, which it can't do for the infinite ranges that SubSpace often uses, so these are hashed through their first index and step instead. PiecewiseSegment overrides == to compare points, so it needs a matching hash as well; ContinuousSpace and SplineSpace hash it as their domain. Spaces that compare their domains do so with domainscompatible, which uses isapprox, so == is not transitive for them and no hash function can respect it exactly. Hashing the domain reproduces == for exactly equal domains, which is the case that matters for a Dict or a Set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ay3nR9CQtrrZj3r5LFu59c * Bump version from 0.9.37 to 0.9.38 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ay3nR9CQtrrZj3r5LFu59c --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a772275 commit b504fca

12 files changed

Lines changed: 129 additions & 2 deletions

File tree

‎Project.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "ApproxFunBase"
22
uuid = "fbd15aa5-315a-5a7d-a8a4-24992e37be05"
3-
version = "0.9.37"
3+
version = "0.9.38"
44

55
[deps]
66
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"

‎src/Domains/PiecewiseSegment.jl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function PiecewiseSegment(pcsin::AbstractVector{IT}) where IT<:IntervalOrSegment
2626
end
2727

2828
==(a::PiecewiseSegment,b::PiecewiseSegment) = a.points==b.points
29+
Base.hash(a::PiecewiseSegment, h::UInt) = hash(a.points, hash(PiecewiseSegment, h))
2930

3031
indomain(x, d::PiecewiseSegment) = any(Base.Fix1(in, x), components(d))
3132

‎src/Multivariate/TensorSpace.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ dimension(sp::TensorSpace) = mapreduce(dimension,*,sp.spaces)
329329
==(A::TensorSpace{<:NTuple{N,Space}}, B::TensorSpace{<:NTuple{N,Space}}) where {N} =
330330
factors(A) == factors(B)
331331

332+
Base.hash(A::TensorSpace, h::UInt) = hash(factors(A), hash(TensorSpace, h))
333+
332334
conversion_rule(a::TensorSpace{<:NTuple{2,Space}}, b::TensorSpace{<:NTuple{2,Space}}) =
333335
conversion_type(a.spaces[1],b.spaces[1]) ⊗ conversion_type(a.spaces[2],b.spaces[2])
334336

‎src/Space.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,8 @@ dimension(::ZeroSpace) = 0
624624

625625
spacescompatible(::ZeroSpace,::ZeroSpace) = true
626626

627+
Base.hash(a::ZeroSpace, h::UInt) = hash(domain(a), hash(ZeroSpace, h))
628+
627629
pick_maybe_nonambiguous_space(a::UnsetSpace, b::ZeroSpace) = a
628630
pick_maybe_nonambiguous_space(a::ZeroSpace, b::UnsetSpace) = b
629631

‎src/Spaces/ArraySpace.jl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ canonicalspace(AS::ArraySpace) = ArraySpace(canonicalspace.(AS.spaces))
182182
evaluate(f::AbstractVector,S::ArraySpace,x) = map(g->g(x),Fun(S,f))
183183

184184
==(A::ArraySpace, B::ArraySpace) = size(A) == size(B) && all(((x,y),) -> x==y, zip(A.spaces, B.spaces))
185+
Base.hash(A::ArraySpace, h::UInt) = hash(A.spaces, hash(ArraySpace, h))
185186

186187
## choosedomainspace
187188

‎src/Spaces/ConstantSpace.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ setdomain(f::Fun{CS},d::Domain) where {CS<:AnyDomain} = Number(f)*ones(d)
7676
canonicalspace(C::ConstantSpace) = C
7777
spacescompatible(a::ConstantSpace,b::ConstantSpace)=domainscompatible(a,b)
7878

79+
Base.hash(a::ConstantSpace, h::UInt) = hash(domain(a), hash(ConstantSpace, h))
80+
7981
ones(S::ConstantSpace) = Fun(S,fill(1.0,1))
8082
ones(S::Union{AnyDomain,UnsetSpace}) = ones(ConstantSpace())
8183
zeros(S::AnyDomain) = zeros(ConstantSpace())

‎src/Spaces/ContinuousSpace.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ isperiodic(C::ContinuousSpace) = isperiodic(domain(C))
1313

1414
spacescompatible(a::ContinuousSpace, b::ContinuousSpace) = domainscompatible(a,b)
1515

16+
Base.hash(a::ContinuousSpace, h::UInt) = hash(domain(a), hash(ContinuousSpace, h))
17+
1618
plan_transform(sp::ContinuousSpace, vals::AbstractVector) =
1719
TransformPlan{eltype(vals),typeof(sp),false,Nothing}(sp,nothing)
1820

‎src/Spaces/HeavisideSpace.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ convert(::Type{HeavisideSpace},d::AbstractVector) =
2121

2222
spacescompatible(a::SplineSpace{λ},b::SplineSpace{λ}) where {λ} = domainscompatible(a,b)
2323

24+
Base.hash(a::SplineSpace{λ}, h::UInt) where {λ} = hash(domain(a), hash(SplineSpace{λ}, h))
25+
2426

2527
function evaluate(c::AbstractVector{T}, s::HeavisideSpace{<:Real}, x::Real) where T
2628
# the loop below indexes p[k+1] for k up to length(c)

‎src/Spaces/QuotientSpace.jl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ domain(QS::QuotientSpace) = domain(QS.space)
2020
canonicalspace(QS::QuotientSpace) = QS.space
2121

2222
spacescompatible(a::QuotientSpace,b::QuotientSpace) = spacescompatible(a.space,b.space)
23+
# the boundary conditions are not compared by ==, so they are not hashed either
24+
Base.hash(a::QuotientSpace, h::UInt) = hash(a.space, hash(QuotientSpace, h))
2325
hasconversion(a::Space,b::QuotientSpace) = hasconversion(a,b.space)
2426
hasconversion(a::QuotientSpace,b::Space) = hasconversion(a.space,b)
2527
hasconversion(a::QuotientSpace,b::QuotientSpace) = hasconversion(a.space,b)

‎src/Spaces/SubSpace.jl‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ spacescompatible(S1::SubSpace{DS,IT,DD,RR},S2::SubSpace{DS,IT,DD,RR}) where {DS,
8484
==(S1::SubSpace{DS,IT,DD,RR},S2::SubSpace{DS,IT,DD,RR}) where {DS,IT,DD,RR} =
8585
S1.space == S2.space && S1.indexes == S2.indexes
8686

87+
_hashindexes(ind, h::UInt) = hash(ind, h)
88+
# Base hashes a range through its length, which it can't do if this is infinite,
89+
# so we hash the first index and the step in this case
90+
_hashindexes(ind::AbstractRange, h::UInt) =
91+
isinf(length(ind)) ? hash(first(ind), hash(step(ind), h)) : hash(ind, h)
92+
93+
Base.hash(S::SubSpace, h::UInt) = _hashindexes(S.indexes, hash(S.space, hash(SubSpace, h)))
94+
8795
canonicalspace(a::SubSpace) = a.space
8896

8997

0 commit comments

Comments
 (0)