Skip to content

Commit f3aaa28

Browse files
mtfishmanclaude
andcommitted
Consolidate NDTensors serialization into one file organized by type
Merges NDTensors/src/serialization_types.jl into NDTensors/src/serialization.jl and reorders the file so each storage type's section contains: 1. The Serialized* struct definition (with docstring). 2. The serialized_type declaration. 3. The Base.convert overloads in both directions. Keeps the shared block-offset helpers near the top of the file since they're used by two sections (BlockSparse, DiagBlockSparse). serialization_types.jl is deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a5e803 commit f3aaa28

3 files changed

Lines changed: 165 additions & 165 deletions

File tree

NDTensors/src/NDTensors.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ include("empty/adapt.jl")
9090
#####################################
9191
# Serialization (schema types + backend-agnostic conversions, used by NDTensorsJLD2Ext)
9292
#
93-
include("serialization_types.jl")
9493
include("serialization.jl")
9594
# Mark the on-disk schema types and the `serialized_type` mapping as public API (per
9695
# Julia docs recommendation for backwards-compatible `public` declarations on pre-1.11

NDTensors/src/serialization.jl

Lines changed: 165 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
# Backend-agnostic serialization layer for NDTensors storage types.
22
#
3-
# Pairs each storage type with its [`SerializedX`](@ref) schema struct via
4-
# [`serialized_type`](@ref), and defines `Base.convert` overloads to do the value-level
5-
# transform in both directions. Serialization backends (today only `NDTensorsJLD2Ext`)
6-
# layer on top: they map their own writeas mechanism to `serialized_type`, and the
7-
# `Base.convert` overloads here handle the actual byte-shape transform.
3+
# Each section below defines, for one in-memory storage type:
4+
# 1. The [`SerializedX`](@ref) schema struct (the on-disk layout).
5+
# 2. The [`serialized_type`](@ref) declaration mapping the in-memory type to the
6+
# schema struct, used by serialization backends to bridge into their own writeas /
7+
# type-mapping mechanism.
8+
# 3. `Base.convert` overloads for the value-level transform in both directions.
9+
#
10+
# JLD2's default `wconvert` / `rconvert` already delegate to `Base.convert`, so backends
11+
# (today only `NDTensorsJLD2Ext`) only need to register a `JLD2.writeas` declaration
12+
# pointing at `serialized_type` — no JLD2-specific value-conversion code is required.
13+
#
14+
# Integer-width conventions for cross-language readability:
15+
# * `version::UInt32` matches `Base.VersionNumber`'s field width.
16+
# * Block-sparse layouts use `block_indices::Matrix{Int64}` shaped `(ndims, num_blocks)`,
17+
# following the COO sparse-tensor convention used by Apache Arrow, PyData Sparse, and
18+
# PyTorch's sparse format. The tensor rank is implicit in `size(block_indices, 1)`
19+
# and is preserved even when `num_blocks == 0` because HDF5 stores both matrix
20+
# dimensions.
821

922
"""
1023
NDTensors.serialized_type(::Type{T}) -> Type
@@ -19,21 +32,7 @@ NDTensors.serialized_type(Dense{Float64}) # => NDTensors.SerializedDense{Float6
1932
"""
2033
function serialized_type end
2134

22-
serialized_type(::Type{<:Dense{T}}) where {T} = SerializedDense{T}
23-
serialized_type(::Type{<:BlockSparse{T}}) where {T} = SerializedBlockSparse{T}
24-
serialized_type(::Type{<:NonuniformDiag{T}}) where {T} = SerializedDiag{T}
25-
serialized_type(::Type{<:UniformDiag{T}}) where {T} = SerializedUniformDiag{T}
26-
function serialized_type(::Type{<:NonuniformDiagBlockSparse{T}}) where {T}
27-
return SerializedDiagBlockSparse{T}
28-
end
29-
function serialized_type(::Type{<:UniformDiagBlockSparse{T}}) where {T}
30-
return SerializedUniformDiagBlockSparse{T}
31-
end
32-
serialized_type(::Type{<:EmptyStorage{T}}) where {T} = SerializedEmptyStorage{T}
33-
34-
# Shared block-offset (de)serialization helpers, used here for BlockSparse and
35-
# DiagBlockSparse. Block positions are written as the columns of a
36-
# `(ndims, num_blocks)` `Matrix{Int64}` (COO sparse-tensor convention).
35+
# Shared block-offset (de)serialization helpers, used by `BlockSparse` and `DiagBlockSparse`.
3736

3837
function _serialize_blockoffsets(storage, ::Val{N}) where {N}
3938
boffs = blockoffsets(storage)
@@ -56,7 +55,24 @@ function _deserialize_blockoffsets(s, ::Val{N}) where {N}
5655
return boffs
5756
end
5857

59-
# Dense
58+
# --- Dense ---
59+
60+
"""
61+
SerializedDense{T}
62+
63+
On-disk schema for `Dense{T}` storage. Version 1.
64+
65+
Fields:
66+
67+
- `version::UInt32`
68+
- `data::Vector{T}`
69+
"""
70+
struct SerializedDense{T}
71+
version::UInt32
72+
data::Vector{T}
73+
end
74+
75+
serialized_type(::Type{<:Dense{T}}) where {T} = SerializedDense{T}
6076

6177
function Base.convert(::Type{SerializedDense{T}}, d::Dense{T}) where {T}
6278
return SerializedDense{T}(UInt32(1), convert(Vector{T}, data(d)))
@@ -68,7 +84,30 @@ function Base.convert(::Type{S}, s::SerializedDense) where {T, S <: Dense{T}}
6884
return S(s.data)
6985
end
7086

71-
# BlockSparse
87+
# --- BlockSparse ---
88+
89+
"""
90+
SerializedBlockSparse{T}
91+
92+
On-disk schema for `BlockSparse{T}` storage. Version 1.
93+
94+
Fields:
95+
96+
- `version::UInt32`
97+
- `data::Vector{T}` — flat element buffer.
98+
- `block_indices::Matrix{Int64}` — shape `(ndims, num_blocks)`, each column a block
99+
position (COO convention; tensor rank is `size(block_indices, 1)`).
100+
- `block_offsets::Vector{Int64}` — length `num_blocks`, the offset into `data` for each
101+
block.
102+
"""
103+
struct SerializedBlockSparse{T}
104+
version::UInt32
105+
data::Vector{T}
106+
block_indices::Matrix{Int64}
107+
block_offsets::Vector{Int64}
108+
end
109+
110+
serialized_type(::Type{<:BlockSparse{T}}) where {T} = SerializedBlockSparse{T}
72111

73112
function Base.convert(
74113
::Type{SerializedBlockSparse{T}}, bs::BlockSparse{T, <:Any, N}
@@ -87,7 +126,24 @@ function Base.convert(::Type{S}, s::SerializedBlockSparse) where {T, S <: BlockS
87126
return BlockSparse(s.data, _deserialize_blockoffsets(s, Val(N)))::S
88127
end
89128

90-
# Diag (nonuniform)
129+
# --- Diag (nonuniform) ---
130+
131+
"""
132+
SerializedDiag{T}
133+
134+
On-disk schema for non-uniform `Diag{T}` storage. Version 1.
135+
136+
Fields:
137+
138+
- `version::UInt32`
139+
- `data::Vector{T}` — the diagonal entries.
140+
"""
141+
struct SerializedDiag{T}
142+
version::UInt32
143+
data::Vector{T}
144+
end
145+
146+
serialized_type(::Type{<:NonuniformDiag{T}}) where {T} = SerializedDiag{T}
91147

92148
function Base.convert(::Type{SerializedDiag{T}}, d::NonuniformDiag{T}) where {T}
93149
return SerializedDiag{T}(UInt32(1), convert(Vector{T}, data(d)))
@@ -100,7 +156,24 @@ function Base.convert(::Type{S}, s::SerializedDiag) where {T, S <: NonuniformDia
100156
return Diag{T}(s.data)::S
101157
end
102158

103-
# Diag (uniform)
159+
# --- Diag (uniform) ---
160+
161+
"""
162+
SerializedUniformDiag{T}
163+
164+
On-disk schema for uniform `Diag{T}` storage (all diagonal entries equal). Version 1.
165+
166+
Fields:
167+
168+
- `version::UInt32`
169+
- `value::T` — the shared diagonal value.
170+
"""
171+
struct SerializedUniformDiag{T}
172+
version::UInt32
173+
value::T
174+
end
175+
176+
serialized_type(::Type{<:UniformDiag{T}}) where {T} = SerializedUniformDiag{T}
104177

105178
function Base.convert(::Type{SerializedUniformDiag{T}}, d::UniformDiag{T}) where {T}
106179
return SerializedUniformDiag{T}(UInt32(1), convert(T, data(d)))
@@ -113,7 +186,31 @@ function Base.convert(::Type{S}, s::SerializedUniformDiag) where {T, S <: Unifor
113186
return Diag(s.value)::S
114187
end
115188

116-
# DiagBlockSparse (nonuniform)
189+
# --- DiagBlockSparse (nonuniform) ---
190+
191+
"""
192+
SerializedDiagBlockSparse{T}
193+
194+
On-disk schema for non-uniform `DiagBlockSparse{T}` storage. Version 1. Layout matches
195+
[`SerializedBlockSparse`](@ref).
196+
197+
Fields:
198+
199+
- `version::UInt32`
200+
- `data::Vector{T}`
201+
- `block_indices::Matrix{Int64}` shape `(ndims, num_blocks)`
202+
- `block_offsets::Vector{Int64}` length `num_blocks`
203+
"""
204+
struct SerializedDiagBlockSparse{T}
205+
version::UInt32
206+
data::Vector{T}
207+
block_indices::Matrix{Int64}
208+
block_offsets::Vector{Int64}
209+
end
210+
211+
function serialized_type(::Type{<:NonuniformDiagBlockSparse{T}}) where {T}
212+
return SerializedDiagBlockSparse{T}
213+
end
117214

118215
function Base.convert(
119216
::Type{SerializedDiagBlockSparse{T}},
@@ -135,7 +232,30 @@ function Base.convert(
135232
return DiagBlockSparse(s.data, _deserialize_blockoffsets(s, Val(N)))::S
136233
end
137234

138-
# DiagBlockSparse (uniform)
235+
# --- DiagBlockSparse (uniform) ---
236+
237+
"""
238+
SerializedUniformDiagBlockSparse{T}
239+
240+
On-disk schema for uniform `DiagBlockSparse{T}` storage. Version 1.
241+
242+
Fields:
243+
244+
- `version::UInt32`
245+
- `value::T` — the shared diagonal value.
246+
- `block_indices::Matrix{Int64}` shape `(ndims, num_blocks)`
247+
- `block_offsets::Vector{Int64}` length `num_blocks`
248+
"""
249+
struct SerializedUniformDiagBlockSparse{T}
250+
version::UInt32
251+
value::T
252+
block_indices::Matrix{Int64}
253+
block_offsets::Vector{Int64}
254+
end
255+
256+
function serialized_type(::Type{<:UniformDiagBlockSparse{T}}) where {T}
257+
return SerializedUniformDiagBlockSparse{T}
258+
end
139259

140260
function Base.convert(
141261
::Type{SerializedUniformDiagBlockSparse{T}},
@@ -157,14 +277,26 @@ function Base.convert(
157277
return DiagBlockSparse(s.value, _deserialize_blockoffsets(s, Val(N)))::S
158278
end
159279

160-
# EmptyStorage
280+
# --- EmptyStorage ---
161281

162-
function Base.convert(
163-
::Type{SerializedEmptyStorage{T}}, ::EmptyStorage{T}
164-
) where {T}
165-
return SerializedEmptyStorage{T}(UInt32(1))
282+
"""
283+
SerializedEmptyStorage{T}
284+
285+
On-disk schema for `EmptyStorage{T}`. Version 1. The element type `T` is carried in the
286+
parametric type name on disk, so no data field is needed.
287+
288+
Fields:
289+
290+
- `version::UInt32`
291+
"""
292+
struct SerializedEmptyStorage{T}
293+
version::UInt32
166294
end
167295

168-
function Base.convert(::Type{S}, ::SerializedEmptyStorage) where {T, S <: EmptyStorage{T}}
169-
return S()
296+
serialized_type(::Type{<:EmptyStorage{T}}) where {T} = SerializedEmptyStorage{T}
297+
298+
function Base.convert(::Type{SerializedEmptyStorage{T}}, ::EmptyStorage{T}) where {T}
299+
return SerializedEmptyStorage{T}(UInt32(1))
170300
end
301+
302+
Base.convert(::Type{S}, ::SerializedEmptyStorage) where {T, S <: EmptyStorage{T}} = S()

0 commit comments

Comments
 (0)