Skip to content

Commit

Permalink
Serialization fixes (#4021)
Browse files Browse the repository at this point in the history
* Add SRow serialization

Co-authored-by: Antony Della Vecchia <[email protected]>

* Random serialization fixes

* Add `Oscar.@add_all_serialization_functions`

---------

Co-authored-by: Antony Della Vecchia <[email protected]>
  • Loading branch information
lgoettgens and antonydellavecchia authored Aug 19, 2024
1 parent 15be6ba commit a7f7564
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 15 deletions.
10 changes: 10 additions & 0 deletions docs/src/DeveloperDocumentation/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,16 @@ of a `FieldElem` will use the `save_type_params` / `load_type_params` from
`RingElem` since in both cases the only parameter needed for such types
is their parent.

### Import helper

When implementing the serialization of a new type in a module that is not
`Oscar` (e.g. in a submodule of `Oscar`) it is necessary to import the
a lot of helper functions (see the examples above).
To ease this process, the `@import_all_serialization_functions` macro can be used.
```@docs
Oscar.@import_all_serialization_functions
```

### Serializers

The code for the different types of serializers and their states is found in the
Expand Down
34 changes: 31 additions & 3 deletions src/Serialization/containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

@register_serialization_type Vector uses_params

const MatVecType{T} = Union{Matrix{T}, Vector{T}}
const MatVecType{T} = Union{Matrix{T}, Vector{T}, SRow{T}}

function save_type_params(s::SerializerState, obj::S) where {T, S <:MatVecType{T}}
save_data_dict(s) do
Expand Down Expand Up @@ -239,7 +239,7 @@ end
function load_object(s::DeserializerState, ::Type{<:Matrix}, params::Type)
load_node(s) do entries
if isempty(entries)
return zero_matrix(parent_type(params)(), 0, 0)
return Matrix{params}(undef, 0, 0)
end
len = length(entries)
m = reduce(vcat, [
Expand All @@ -252,7 +252,7 @@ end
function load_object(s::DeserializerState, ::Type{<:Matrix}, params::Tuple)
load_node(s) do entries
if isempty(entries)
return params[1][]
return Matrix{params[1]}(undef, 0, 0)
end

len = length(entries)
Expand Down Expand Up @@ -533,3 +533,31 @@ function load_object(s::DeserializerState, ::Type{<: Set}, params::Ring)
end
return Set{T}(loaded_entries)
end

################################################################################
# Sparse rows

@register_serialization_type SRow uses_params

function save_object(s::SerializerState, obj::SRow)
save_data_array(s) do
for (i, v) in collect(obj)
save_object(s, (i, v))
end
end
end

function load_object(s::DeserializerState, ::Type{<:SRow}, params::Ring)
pos = Int[]
entry_type = elem_type(params)
values = entry_type[]
load_array_node(s) do _
push!(pos, load_object(s, Int, 1))
if serialize_with_params(entry_type)
push!(values, load_object(s, entry_type, params, 2))
else
push!(values, load_object(s, entry_type, 2))
end
end
return sparse_row(params, pos, values)
end
70 changes: 58 additions & 12 deletions src/Serialization/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ end
const reverse_type_map = Dict{String, Type}()

function encode_type(::Type{T}) where T
error("Unsupported type '$T' for encoding. to add support see
https://docs.oscar-system.org/stable/DeveloperDocumentation/serialization/ \n")
error(
"""Unsupported type '$T' for encoding. To add support see
https://docs.oscar-system.org/stable/DeveloperDocumentation/serialization/
"""
)
end

function decode_type(s::DeserializerState)
Expand Down Expand Up @@ -295,8 +298,8 @@ serialize_with_params(::Type) = false
function register_serialization_type(ex::Any, str::String, uses_id::Bool, uses_params::Bool)
return esc(
quote
register_serialization_type($ex, $str)
encode_type(::Type{<:$ex}) = $str
Oscar.register_serialization_type($ex, $str)
Oscar.encode_type(::Type{<:$ex}) = $str
# There exist types where equality cannot be discerned from the serialization
# these types require an id so that equalities can be forced upon load.
# The ids are only necessary for parent types, checking for element type equality
Expand All @@ -310,18 +313,18 @@ function register_serialization_type(ex::Any, str::String, uses_id::Bool, uses_p
# Types like ZZ, QQ, and ZZ/nZZ do not require ids since there is no syntactic
# ambiguities in their encodings.

serialize_with_id(obj::T) where T <: $ex = $uses_id
serialize_with_id(T::Type{<:$ex}) = $uses_id
serialize_with_params(T::Type{<:$ex}) = $uses_params
Oscar.serialize_with_id(obj::T) where T <: $ex = $uses_id
Oscar.serialize_with_id(T::Type{<:$ex}) = $uses_id
Oscar.serialize_with_params(T::Type{<:$ex}) = $uses_params

# only extend serialize on non std julia types
if !($ex <: Union{Number, String, Bool, Symbol, Vector, Tuple, Matrix, NamedTuple, Dict, Set})
function serialize(s::AbstractSerializer, obj::T) where T <: $ex
serialize_type(s, T)
save(s.io, obj; serializer_type=IPCSerializer)
function Oscar.serialize(s::Oscar.AbstractSerializer, obj::T) where T <: $ex
Oscar.serialize_type(s, T)
Oscar.save(s.io, obj; serializer_type=Oscar.IPCSerializer)
end
function deserialize(s::AbstractSerializer, ::Type{<:$ex})
load(s.io; serializer_type=IPCSerializer)
function Oscar.deserialize(s::Oscar.AbstractSerializer, ::Type{<:$ex})
Oscar.load(s.io; serializer_type=Oscar.IPCSerializer)
end
end

Expand Down Expand Up @@ -365,6 +368,49 @@ macro register_serialization_type(ex::Any, args...)
return register_serialization_type(ex, str, uses_id, uses_params)
end


################################################################################
# Utility macro
"""
Oscar.@import_all_serialization_functions
This macro imports all serialization related functions that one may need for implementing
serialization for custom types from Oscar into the current module.
One can instead import the functions individually if needed but this macro is provided
for convenience.
"""
macro import_all_serialization_functions()
return quote
import Oscar:
load_object,
load_type_params,
save_object,
save_type_params

using Oscar:
@register_serialization_type,
DeserializerState,
SerializerState,
encode_type,
haskey,
load_array_node,
load_node,
load_params_node,
load_ref,
load_typed_object,
save_as_ref,
save_data_array,
save_data_basic,
save_data_dict,
save_data_json,
save_typed_object,
serialize_with_id,
serialize_with_params,
set_key
end
end


################################################################################
# Include serialization implementations for various types

Expand Down
5 changes: 5 additions & 0 deletions src/Serialization/serializers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ end

function end_dict_node(s::SerializerState)
write(s.io, "}")

if s.new_level_entry
# makes sure that entries after empty dicts add comma
s.new_level_entry = false
end
end

function begin_array_node(s::SerializerState)
Expand Down
1 change: 1 addition & 0 deletions test/Serialization/setup_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ if !isdefined(Main, :test_save_load_roundtrip)
save(filename, original)
Oscar.reset_global_serializer_state()
loaded = load(filename; params=params)
@test loaded isa T

# test schema
jsondict = JSON.parsefile(filename)
Expand Down

0 comments on commit a7f7564

Please sign in to comment.