-
Notifications
You must be signed in to change notification settings - Fork 181
Expand file tree
/
Copy pathmain.jl
More file actions
1071 lines (922 loc) · 33.2 KB
/
Copy pathmain.jl
File metadata and controls
1071 lines (922 loc) · 33.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module Serialization
using ..Oscar
using UUIDs
import JSON
import TranscodingStreams # for gzip
import CodecZlib # for gzip
using ..Oscar: _grading,
FreeAssociativeAlgebraIdeal,
IdealGens,
LaurentMPolyIdeal,
MPolyAnyMap,
MPolyLocalizedRingHom,
MPolyQuoLocalizedRingHom,
NormalToricVarietyType,
Orderings,
PhylogeneticTree,
pm_object,
PolyhedralObject,
scalar_types,
VERSION_NUMBER,
_pmdata_for_oscar
using ..Oscar: is_terse, Lowercase, pretty, terse, Indent, Dedent
using Distributed: RemoteChannel
# This type should not be exported and should be before serializers
const BasicTypeUnion = Union{String, QQFieldElem, Symbol,
Number, ZZRingElem, TropicalSemiringElem}
include("serializers.jl")
const type_key = :_type
################################################################################
# Meta Data
@Base.kwdef struct MetaData
author_orcid::Union{String, Nothing} = nothing
name::Union{String, Nothing} = nothing
description::Union{String, Nothing} = nothing
end
# FIXME: this function is exported but undocumented
function metadata(;args...)
return MetaData(;args...)
end
# FIXME: this function is exported but undocumented
function read_metadata(filename::String)
open(filename) do io
obj = JSON.lazy(io)
println(JSON.json(JSON.parse(obj[:meta]), 2))
end
end
################################################################################
# Serialization info
function serialization_version_info(obj::AbstractDict{Symbol, Any})
ns = obj[:_ns]
version_info = ns[:Oscar][2]
return version_number(version_info)
end
function version_number(v_number::String)
return VersionNumber(v_number)
end
# needed for older versions
function version_number(dict::AbstractDict)
return VersionNumber(dict[:major], dict[:minor], dict[:patch])
end
const oscar_serialization_version = Ref{Dict{Symbol, Any}}()
function get_oscar_serialization_version()
if isassigned(oscar_serialization_version)
return oscar_serialization_version[]
end
if Oscar.is_dev
next_version = "$(VERSION_NUMBER.major).$(VERSION_NUMBER.minor).$(VERSION_NUMBER.patch)"
n_upgrades = count(x -> startswith(x, next_version) && endswith(x, ".jl"),
readdir(joinpath(@__DIR__, "Upgrades")))
commit_hash = get(Oscar._get_oscar_git_info(), :commit, "unknown")
version_info = iszero(n_upgrades) ? "$VERSION_NUMBER-$commit_hash" : "$VERSION_NUMBER-$n_upgrades-$commit_hash"
result = Dict{Symbol, Any}(
:Oscar => ["https://github.com/oscar-system/Oscar.jl", version_info]
)
else
result = Dict{Symbol, Any}(
:Oscar => ["https://github.com/oscar-system/Oscar.jl", VERSION_NUMBER]
)
end
return oscar_serialization_version[] = result
end
################################################################################
# Type attribute map
const type_attr_map = Dict{String, Vector{Symbol}}()
attrs_list(T::Type) = get(type_attr_map, encode_type(T), Symbol[])
with_attrs(s::T) where T <: Union{DeserializerState, SerializerState} = s.with_attrs
################################################################################
# (De|En)coding types
# parameters of type should not matter here
const reverse_type_map = Dict{String, Union{Dict{String, Type}, 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/
"""
)
end
function decode_type(s::String)
return get(reverse_type_map, s) do
error("unsupported type '$s' for decoding")
end
end
function decode_type(s::DeserializerState)
if s.obj isa String
uuid = tryparse(UUID, s.obj)
if !isnothing(uuid)
if isnothing(s.refs)
return typeof(global_serializer_state.id_to_obj[uuid])
end
id = s.obj
s.obj = s.refs[Symbol(id)]
T = decode_type(s)
s.obj = id
return T
end
obj = decode_type(s.obj)
obj isa AbstractDict && return obj["default"]
return obj
end
if type_key in keys(s.obj)
return load_node(s, type_key) do _
obj = decode_type(s)
obj isa AbstractDict && return obj["default"]
return obj
end
end
if :name in keys(s.obj)
if :_instance in keys(s.obj)
return get(reverse_type_map[s.obj[:name]], s.obj[:_instance]) do
unsupported_instance = s.obj[:_instance]
error("unsupported instance '$unsupported_instance' for decoding")
end
else
return load_node(s, :name) do _
obj = decode_type(s)
obj isa AbstractDict && return obj["default"]
return obj
end
end
end
end
################################################################################
# TypeAndParams Struct
struct TypeAndParams{T, S}
type::Type{T}
params::S
function TypeAndParams(T::Type, params)
if Base.issingletontype(T)
return new{T, Nothing}(T, nothing)
else
return new{T, typeof(params)}(T, params)
end
end
end
TypeAndParams(T::Type, args::Pair...) = TypeAndParams(T, args)
params(tp::TypeAndParams) = tp.params
type(tp::TypeAndParams) = tp.type
type_and_params(obj::T) where T = TypeAndParams(T, nothing)
function Base.show(io::IO, tp::TypeAndParams{T, Tuple}) where T
if is_terse(io)
print(io, "Type parameters for $T")
else
io = pretty(io)
print(io, "Type parameters for $T")
for param in params(tp)
println(io, "")
print(terse(io), Lowercase(), param)
end
end
end
function Base.show(io::IO, tp::TypeAndParams{T, S}) where {T, S}
if is_terse(io)
print(io, "Type parameters for $T")
else
io = pretty(io)
print(io, "Type parameters for $T ")
print(terse(io), Lowercase(), params(tp))
end
end
# ATTENTION
# We need to distinguish between data with a globally defined normal form and data where such a normal form depends on some parameters.
# In particular, this does NOT ONLY depend on the type; see, e.g., FqField.
################################################################################
# High level
function save_object(s::SerializerState, x::Any, key::Symbol)
set_key(s, key)
save_object(s, x)
end
function save_json(s::SerializerState, x::Any)
save_data_json(s, x)
end
function save_json(s::SerializerState, x::Any, key::Symbol)
set_key(s, key)
save_json(s, x)
end
function save_header(s::SerializerState, h::Dict{Symbol, Any}, key::Symbol)
save_data_dict(s, key) do
for (k, v) in h
save_object(s, v, k)
end
end
end
function save_typed_object(s::SerializerState, x::T) where T
if Base.issingletontype(T)
save_object(s, encode_type(T), type_key)
else
save_type_and_params(s, x, type_key)
save_object(s, x, :data)
end
if with_attrs(s)
attrs = attrs_list(T)
!isempty(attrs) && save_attrs(s, x)
end
end
function save_typed_object(s::SerializerState, x::T, key::Symbol) where T
set_key(s, key)
if serialize_with_id(x)
# key should already be set before function call
ref = save_as_ref(s, x)
save_object(s, ref)
else
save_data_dict(s) do
save_typed_object(s, x)
end
end
end
################################################################################
# (save | load) TypeAndParams
function save_type_and_params(s::SerializerState, obj::Any, key::Symbol)
set_key(s, key)
save_type_and_params(s, obj)
end
function save_type_and_params(s::SerializerState, obj::T) where T
save_type_and_params(s, type_and_params(obj))
end
function save_type_and_params(s::SerializerState, tp::TypeAndParams)
save_data_dict(s) do
T = type(tp)
type_encoding = encode_type(T)
if reverse_type_map[type_encoding] isa Dict
save_object(s, convert_type_to_string(T), :_instance)
end
save_object(s, type_encoding, :name)
# params(tp) isa TypeAndParams if the type isa container type
if params(tp) isa TypeAndParams
save_type_and_params(s, params(tp), :params)
else
save_typed_object(s, params(tp), :params)
end
end
end
function save_type_and_params(s::SerializerState,
::TypeAndParams{T, Nothing}) where T
type_encoding = encode_type(T)
if reverse_type_map[type_encoding] isa Dict
save_data_dict(s) do
save_object(s, type_encoding, :name)
save_object(s, convert_type_to_string(T), :_instance)
end
else
save_object(s, type_encoding)
end
end
function save_type_and_params(s::SerializerState,
tp::TypeAndParams{<:TypeAndParams, <:Tuple{Vararg{Pair}}})
for param in params(tp)
save_type_and_params(s, param.second, Symbol(param.first))
end
end
function save_type_and_params(s::SerializerState,
tp::TypeAndParams{<:TypeAndParams, <:Tuple})
save_data_array(s) do
for param in params(tp)
save_type_and_params(s, param)
end
end
end
function save_type_and_params(s::SerializerState,
tp::TypeAndParams{T, <:Tuple{Vararg{Pair}}}) where T
save_data_dict(s) do
save_object(s, encode_type(T), :name)
save_data_dict(s, :params) do
for param in params(tp)
if param.second isa Type
save_object(s, encode_type(param.second), Symbol(param.first))
elseif !(param.second isa TypeAndParams)
if param.second isa Tuple
save_data_array(s, Symbol(param.first)) do
for entry in param.second
if serialize_with_id(entry)
save_object(s, save_as_ref(s, entry))
else
save_data_dict(s) do
save_typed_object(s, entry)
end
end
end
end
else
save_typed_object(s, param.second, Symbol(param.first))
end
else
save_type_and_params(s, param.second, Symbol(param.first))
end
end
end
end
end
function load_type_and_params(s::DeserializerState, T::Type, key::Symbol)
load_node(s, key) do _
load_type_and_params(s, T)
end
end
function load_type_array_params(s::DeserializerState)
load_array_node(s) do obj
T = decode_type(s)
if obj isa String
!isnothing(tryparse(UUID, s.obj)) && return load_ref(s)
return T
end
return load_type_and_params(s, T)[2]
end
end
function load_type_and_params(s::DeserializerState, T::Type)
if s.obj isa String
if !isnothing(tryparse(UUID, s.obj))
return T, load_ref(s)
end
return T, nothing
end
if haskey(s, :params)
load_node(s, :params) do obj
if obj isa JSON3.Array || obj isa Vector
params = load_type_array_params(s)
elseif obj isa String || haskey(s, :params)
U = decode_type(s)
if Base.issingletontype(U)
params = U()
else
params = load_type_and_params(s, U)[2]
end
# handle cases where type_and_params is a dict of params
elseif !haskey(obj, type_key)
params = Dict{Symbol, Any}()
for (k, _) in obj
params[k] = load_node(s, k) do obj
if obj isa JSON3.Array || obj isa Vector
return load_type_array_params(s)
end
U = decode_type(s)
if obj isa String && isnothing(tryparse(UUID, obj))
return U
end
return load_type_and_params(s, U)[2]
end
end
else
params = load_typed_object(s)
end
# all types where the type T should be updated with a subtype i.e. T -> T{U}
# need to implement their own method, see for example containers
return T, params
end
elseif haskey(s, :_instance)
T, nothing
else
return T, load_typed_object(s)
end
end
function load_typed_object(s::DeserializerState, key::Symbol; override_params::Any = nothing)
load_node(s, key) do _
load_typed_object(s; override_params=override_params)
end
end
# The load mechanism first checks if the type needs to load necessary
# parameters before loading its data, if so a type tree is traversed
function load_typed_object(s::DeserializerState; override_params::Any = nothing)
T = decode_type(s)
if !isnothing(override_params)
T, _ = load_type_and_params(s, T, type_key)
params = override_params
else
s.obj isa String && !isnothing(tryparse(UUID, s.obj)) && return load_ref(s)
T, params = load_type_and_params(s, T, type_key)
end
Base.issingletontype(T) && return T()
obj = load_node(s, :data) do _
return load_object(s, T, params)
end
load_attrs(s, obj)
return obj
end
function load_object(s::DeserializerState, T::Type, key::Union{Symbol, Int})
load_node(s, key) do _
load_object(s, T)
end
end
function load_object(s::DeserializerState, T::Type, params::S,
key::Union{Symbol, Int}) where S
load_node(s, key) do _
load_object(s, T, params)
end
end
load_object(s::DeserializerState, T::Type, ::Nothing) = load_object(s, T)
################################################################################
# serializing attributes
function save_attrs(s::SerializerState, obj::T) where T
if any(attr -> has_attribute(obj, attr), attrs_list(T))
save_data_dict(s, :attrs) do
for attr in attrs_list(T)
has_attribute(obj, attr) && save_typed_object(s, get_attribute(obj, attr), attr)
end
end
end
end
function load_attrs(s::DeserializerState, obj::T) where T
!with_attrs(s) && return
haskey(s, :attrs) && load_node(s, :attrs) do d
for attr in keys(d)
set_attribute!(obj, attr, load_typed_object(s, attr))
end
end
end
################################################################################
# Type Registration
function register_serialization_type(@nospecialize(T::Type), str::String, default::Bool)
if haskey(reverse_type_map, str)
init = reverse_type_map[str]
# promote the value to a dictionary if necessary
if init isa Type
init = Dict{String, Type}(convert_type_to_string(init) => init)
end
reverse_type_map[str] = merge(Dict{String, Type}(convert_type_to_string(T) => T), init)
elseif default
reverse_type_map[str] = Dict{String, Type}(convert_type_to_string(T) => T)
else
reverse_type_map[str] = T
end
if default
if haskey(reverse_type_map[str], "default")
S = reverse_type_map[str]["default"]
error("Attempting to overwrite registered default type $S with $T")
end
reverse_type_map[str]["default"] = T
end
end
function register_attr_list(@nospecialize(T::Type),
attrs::Union{Vector{Symbol}, Nothing})
if !isnothing(attrs)
serialize_with_id(T) || error("Only types that are stored as references can store attributes")
type_attr_map[encode_type(T)] = attrs
end
end
import Serialization.serialize
import Serialization.deserialize
import Serialization.serialize_type
import Distributed.AbstractSerializer
# add these here so that the proper errors are thrown
# when the type hasn't been registered
serialize_with_id(::Type) = false
serialize_with_id(obj::Any) = false
function register_serialization_type(ex::Any, str::String, uses_id::Bool, attrs::Any, default::Bool)
return esc(
quote
Oscar.Serialization.register_serialization_type($ex, $str, $default)
Oscar.Serialization.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
# can be done once the parents are known to be equal.
# For example two serializations of QQ[x] require ids to check for equality.
# Although they're isomorphic rings, they may want to be treated as separate
# This is done since other software might not use symbols in their serialization of QQ[x].
# Which will then still allow for the distinction between QQ[x] and QQ[y], i.e.
# whenever there is a possibility (amongst any software system) that the objects
# cannot be distinguish on a syntactic level we use ids.
# Types like ZZ, QQ, and ZZ/nZZ do not require ids since there is no syntactic
# ambiguities in their encodings.
Oscar.Serialization.serialize_with_id(obj::T) where T <: $ex = $uses_id
Oscar.Serialization.serialize_with_id(T::Type{<:$ex}) = $uses_id
# add list of possible attributes to save for a given type to a global dict
Oscar.Serialization.register_attr_list($ex, $attrs)
# only extend serialize on non std julia types
if !($ex <: Union{Number, String, Bool, Symbol, Vector, Tuple, Matrix, NamedTuple, Dict, Set, Array, Nothing})
function Oscar.Serialization.serialize(s::Oscar.Serialization.AbstractSerializer, obj::T) where T <: $ex
Oscar.Serialization.serialize_type(s, T)
Oscar.Serialization.save(s.io, obj; serializer=Oscar.Serialization.IPCSerializer())
end
function Oscar.Serialization.deserialize(s::Oscar.Serialization.AbstractSerializer, T::Type{<:$ex})
Oscar.Serialization.load(s.io; serializer=Oscar.Serialization.IPCSerializer())
end
end
end)
end
"""
@register_serialization_type NewType "String Representation of type" uses_id default [:attr1, :attr2]
`@register_serialization_type` is a macro to ensure that the string we generate
matches exactly the expression passed as first argument, and does not change
in unexpected ways when import/export statements are adjusted.
Passing a string argument will override how the type is stored as a string.
When setting `uses_id` the object will be stored as a reference and
will be referred to throughout the serialization sessions using a `UUID`.
This should typically only be used for types that do not have a fixed
normal form for example `PolyRing` and `MPolyRing`.
When registering a type with a "String representation of type" that is shared amongst more than one type, use the `default` flag to declare which type should be loaded by default.
All other types with the same "String representation of type" are stored with an additional `_instance` key describing their type.
```
{
"_ns": {
"Oscar": [
"https://github.com/oscar-system/Oscar.jl",
"1.8.0"
]
},
"_type": "FiniteField",
"data": "2",
"id": "7dc83bd8-4f11-4d30-9e4d-d96629c9a8f4"
}
```
is loaded as a `FqField`
where as
```
{
"_ns": {
"Oscar": [
"https://github.com/oscar-system/Oscar.jl",
"1.8.0"
]
},
"_type": {
"_instance": "fpField",
"name": "FiniteField"
},
"data": "2"
}
```
is loaded as a `fpField`.
Passing a vector of symbols that correspond to attributes of type
indicates which attributes will be serialized when using save with `with_attrs=true`.
"""
macro register_serialization_type(ex::Any, args...)
uses_id = false
str = nothing
attrs = nothing
default = false
for el in args
if el isa String
str = el
elseif el == :uses_id
uses_id = true
elseif el == :default
default = true
else
attrs = el
end
end
if str === nothing
# here we use string since on an expression.
# this choice means we should write types without the namespace in front
# when registering, and in convert_type_to_string
str = string(ex)
end
return register_serialization_type(ex, str, uses_id, attrs, default)
end
################################################################################
# Include serialization implementations for various types
include("basic_types.jl")
include("containers.jl")
include("PolyhedralGeometry.jl")
include("Combinatorics.jl")
include("Fields.jl")
include("ToricGeometry.jl")
include("Rings.jl")
include("MPolyMap.jl")
include("Algebras.jl")
include("polymake.jl")
include("TropicalGeometry.jl")
include("QuadForm.jl")
include("GAP.jl")
include("Groups.jl")
include("LieTheory.jl")
include("Upgrades/main.jl")
include("parallel.jl")
################################################################################
# Interacting with IO streams and files
"""
save(io::IO, obj::Any; metadata::MetaData=nothing, with_attrs::Bool=true, serializer::OscarSerializer=JSONSerializer())
save(filename::String, obj::Any; metadata::MetaData=nothing, with_attrs::Bool=true, compression::Symbol=:none, pretty_print::Bool=false, serializer::OscarSerializer=JSONSerializer())
Save an object `obj` to the given io stream respectively to the file `filename`.
When used with `with_attrs=true` the object will save its attributes along with
all the attributes of the types used in the object's struct. The attributes that
will be saved are defined during type registration, see
[`@register_serialization_type`](@ref).
The optional `serializer` argument controls the output format and layout.
The default `JSONSerializer` writes a single `.mrdi` file. Other serializers
such as `MultiFileRefSerializer` (multi-file prefix-based) and `LPSerializer` (external LP
file for linear programs) are available. See the
[serialization documentation](@ref serialization) for details and examples.
Setting the optional argument `compression` will compress the file using the
given compression method. The `filename` must have the appropriate file
extension for the chosen compression method.
Currently, only `:none` (default) and `:gzip` are supported.
The `pretty_print` optional argument can be used similar to the standard [JSON](https://juliaio.github.io/JSON.jl/stable/writing/#Pretty-Printing) functionality.
See [`load`](@ref).
# Examples
```jldoctest; setup=:(current=pwd(); cd(mktempdir())), teardown=:(cd(current))
julia> meta = metadata(author_orcid="0000-0000-0000-0042", name="42", description="The meaning of life, the universe and everything")
Oscar.Serialization.MetaData("0000-0000-0000-0042", "42", "The meaning of life, the universe and everything")
julia> save("fourtitwo.mrdi", 42; metadata=meta);
julia> read_metadata("fourtitwo.mrdi")
{
"author_orcid": "0000-0000-0000-0042",
"name": "42",
"description": "The meaning of life, the universe and everything"
}
julia> load("fourtitwo.mrdi")
42
```
"""
function save(io::IO, obj::T; metadata::Union{MetaData, Nothing}=nothing,
with_attrs::Bool=true,
pretty_print::Bool=false,
serializer::OscarSerializer = JSONSerializer()) where T
if pretty_print
io = pretty(io)
end
s = serializer_open(io, serializer, with_attrs, pretty_print)
save_data_dict(s) do
# write out the namespace first
save_header(s, get_oscar_serialization_version(), :_ns)
save_typed_object(s, obj)
if serialize_with_id(T)
ref = get(global_serializer_state.obj_to_id, obj, nothing)
if isnothing(ref)
ref = global_serializer_state.obj_to_id[obj] = uuid4()
global_serializer_state.id_to_obj[ref] = obj
end
save_object(s, string(ref), :id)
end
handle_refs(s)
if !isnothing(metadata)
save_json(s, JSON.json(metadata), :meta)
end
end
serializer_close(s)
return nothing
end
function save(filename::String, obj::Any;
compression::Symbol=:none,
serializer::OscarSerializer=JSONSerializer(),
kwargs...)
if serializer isa MultiFileRefSerializer
prefix = if endswith(filename, ".mrdi.gz")
chopsuffix(filename, ".mrdi.gz")
elseif endswith(filename, ".mrdi")
chopsuffix(filename, ".mrdi")
else
filename
end
main_ext = compression == :gzip ? ".mrdi.gz" : ".mrdi"
main_file = prefix * main_ext
prefix_dir = isempty(dirname(prefix)) ? pwd() : dirname(prefix)
inner_serializer = MultiFileRefSerializer(prefix, compression)
temp_file = tempname(prefix_dir)
if compression == :gzip
open(CodecZlib.GzipCompressorStream, temp_file, "w") do file
save(file, obj; serializer=inner_serializer, kwargs...)
end
else
open(temp_file, "w") do file
save(file, obj; serializer=inner_serializer, kwargs...)
end
end
Base.Filesystem.rename(temp_file, main_file)
return nothing
end
temp_file = tempname(dirname(abspath(filename)))
if compression == :none
open(temp_file, "w") do file
save(file, obj; serializer=serializer, kwargs...)
end
elseif compression == :gzip
@req endswith(filename, ".gz") "For gzip compression the filename should end with .gz"
open(CodecZlib.GzipCompressorStream, temp_file, "w") do file
save(file, obj; serializer=serializer, kwargs...)
end
else
error("Unsupported compression method: $compression")
end
Base.Filesystem.rename(temp_file, filename) # atomic "multi process safe"
return nothing
end
"""
load(io::IO; params::Any = nothing, type::Any = nothing, with_attrs::Bool=true, serializer::OscarSerializer=JSONSerializer())
load(filename::String; params::Any = nothing, type::Any = nothing, with_attrs::Bool=true, serializer::OscarSerializer=JSONSerializer())
Load the object stored in the given io stream respectively in the file `filename`.
If `params` is specified, then the root object of the loaded data will attempt a
load using these parameters. In the case of rings this results in setting its
parent, or in the case of a container of ring types such as `Vector` or `Tuple`,
the parent of the entries will be set using their `params`.
If a type `T` is given then attempt to load the root object of the data being
loaded with this type; if this fails, an error is thrown.
If `with_attrs=true` the object will be loaded with attributes available from
the file (or serialized data).
The optional `serializer` argument must match the one used when saving. Pass the
same serializer instance (e.g. `MultiFileRefSerializer()` or `LPSerializer(basepath)`)
that was used with `save`. See the
[serialization documentation](@ref serialization) for details and examples.
If the file was created with `compression=:gzip` and the filename ends in `.gz`,
the file will be decompressed on-the-fly automatically.
See [`save`](@ref).
# Examples
```jldoctest; setup=:(current=pwd(); cd(mktempdir())), teardown=:(cd(current))
julia> save("fourtitwo.mrdi", 42);
julia> load("fourtitwo.mrdi")
42
julia> load("fourtitwo.mrdi"; type=Int64)
42
julia> R, x = QQ[:x]
(Univariate polynomial ring in x over QQ, x)
julia> p = x^2 - x + 1
x^2 - x + 1
julia> save("p.mrdi", p)
julia> p_loaded = load("p.mrdi", params=R)
x^2 - x + 1
julia> parent(p_loaded) === R
true
julia> save("p_v.mrdi", [p, p])
julia> loaded_p_v = load("p_v.mrdi", params=R)
2-element Vector{QQPolyRingElem}:
x^2 - x + 1
x^2 - x + 1
julia> parent(loaded_p_v[1]) === parent(loaded_p_v[2]) === R
true
```
"""
function load(io::IO; params::Any = nothing, type::Any = nothing,
serializer::OscarSerializer=JSONSerializer(), with_attrs::Bool=true)
s = deserializer_open(io, serializer, with_attrs)
if haskey(s.obj, :id)
id = s.obj[:id]
if haskey(global_serializer_state.id_to_obj, UUID(id))
return global_serializer_state.id_to_obj[UUID(id)]
end
end
# handle different namespaces
polymake_obj = load_node(s) do d
@req :_ns in keys(d) "Namespace is missing"
load_node(s, :_ns) do _ns
if :polymake in keys(_ns)
return load_from_polymake(Dict(d))
end
end
end
if !isnothing(polymake_obj)
return polymake_obj
end
load_node(s, :_ns) do _ns
@req haskey(_ns, :Oscar) "Not an Oscar object"
end
# deal with upgrades
file_version = load_node(s) do obj
serialization_version_info(obj)
end
# A file needs upgrading iff some upgrade script targets a version newer than the
# file's effective version. Gate on the newest upgrade script rather than on
# VERSION_NUMBER: for DEV builds the file's version string carries a commit hash
# (e.g. "1.8.0-DEV-<hash>"), and comparing that against VERSION_NUMBER orders the two
# by hash characters rather than by upgrade state, which can wrongly skip upgrades.
if effective_upgrade_version(file_version) < version(last(upgrade_scripts))
# we need a mutable dictionary
jsondict = copy(s.obj)
jsondict = upgrade(file_version, jsondict)
jsondict_str = JSON.json(jsondict)
s = deserializer_open(IOBuffer(jsondict_str),
serializer,
with_attrs)
end
try
if params isa TypeAndParams
params = _convert_override_params(params)
end
if type !== nothing
# Decode the stored type, and compare it to the type `T` supplied by the caller.
# If they are identical, just proceed. If not, then we assume that either
# `T` is concrete, in which case `T <: U` should hold; or else `U` is
# concrete, and `U <: T` should hold.
#
# This check should maybe change to a check on the whole type tree?
U = load_node(s, type_key) do _
decode_type(s)
end
U <: type || U >: type || error("Type in file doesn't match target type: $(dict[type_key]) not a subtype of $type")
Base.issingletontype(type) && return type()
if isnothing(params)
_, params = load_node(s, type_key) do _
load_type_and_params(s, U)
end
end
load_node(s, :data) do _
loaded = load_object(s, type, params)
end
else
loaded = load_typed_object(s; override_params=params)
end
if :id in keys(s.obj)
load_node(s, :id) do id
global_serializer_state.obj_to_id[loaded] = UUID(id)
global_serializer_state.id_to_obj[UUID(id)] = loaded
end
end
return loaded
catch e
if VersionNumber(replace(string(file_version), r"DEV.+" => "DEV")) > VERSION_NUMBER
@warn """
Attempted loading file stored with Oscar version $file_version
using Oscar version $VERSION_NUMBER
"""
end
if contains(string(file_version), "DEV")
commit = split(string(file_version), "-")[end]
@warn "Attempted loading file stored using a DEV version with commit $commit"
end
rethrow(e)
end
end
function load(filename::String; serializer::OscarSerializer=JSONSerializer(), kwargs...)
if serializer isa MultiFileRefSerializer
if endswith(filename, ".mrdi.gz")
main_file = filename
prefix = chopsuffix(filename, ".mrdi.gz")
elseif endswith(filename, ".mrdi")
main_file = filename
prefix = chopsuffix(filename, ".mrdi")
else
prefix = filename
main_gz = prefix * ".mrdi.gz"
main_file = isfile(main_gz) ? main_gz : prefix * ".mrdi"
end
compression = endswith(main_file, ".gz") ? :gzip : :none
if compression == :gzip
open(CodecZlib.GzipDecompressorStream, main_file) do file
return load(file; serializer=MultiFileRefSerializer(prefix, compression), kwargs...)
end
else
open(main_file) do file
return load(file; serializer=MultiFileRefSerializer(prefix), kwargs...)
end
end
elseif endswith(filename, ".gz")
open(CodecZlib.GzipDecompressorStream, filename) do file
return load(file; serializer=serializer, kwargs...)
end
else
open(filename) do file
return load(file; serializer=serializer, kwargs...)
end
end
end
_convert_override_params(tp::TypeAndParams{T, S}) where {T, S} = _convert_override_params(params(tp))
_convert_override_params(tp::TypeAndParams{T, <:Tuple{Vararg{Pair}}}) where T = Dict(_convert_override_params(params(tp)))
_convert_override_params(tp::TypeAndParams{T, S}) where {T <: MatVecType, S} = _convert_override_params(params(tp))