-
Notifications
You must be signed in to change notification settings - Fork 92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Binary export extension #678
Comments
In my opinion, if you want to reliably serialize/deserialize arbitrary Julia object then the best is to
Trying to keep backwards compatibility by writing these cusom serialization functions sounds kind of annoying to me. |
I'd imagine to write as a sane default the It is very likely that I can describe a Lagrange interpolation by a triple of |
I think this would be a nice feature! From my point of view, (or perhaps as a starting point), the current issue is to save the constraint handler when used with anonymous functions. Are there any nice solutions for this? (xref my question/attempt JuliaIO/JLD2.jl#288 (comment)). |
I'm not entirely sure why that is, because you would provide a custom serialization that is valid for a single Ferrite version (and in best case across versions if they don't break the structs) together with a bundled Manifest and Project toml entry of the current environment
I think this issue is at the end of a possible implementation. One should start with a |
We might want to think about recommendations for users regarding IO / solution iteration and put them into the docs, possibly with more examples. Related issues in the ecosystem are are Ferrite-FEM/FerriteViz.jl#1 and #828 . |
What do you think about a standardized output for HDF5 for all types where it is possible? julia> h5open("blah.h5", "w") do h5f
h5f["grid/cells"] = grid.cells
end |
Not necessary standardized, but at least recommendations or a guideline. |
But why not standardize things that are possible julia> h5open("blah.h5", "w") do h5f
h5f["grid/cells"] = grid.cells
h5f["grid/nodes"] = grid.nodes
for (facesetname,faceset) in grid.facesets
h5f["grid/facesets/$facesetname"] = collect(faceset)
end
end Seems weird to me that every user has to write something like this in order to export a grid to HDF5 |
I think this is Pandora's box. What is the format that covers all possible use cases? The code snippet you shows already just works for our internal standard serial grid, but not for the p4est grid (information about the trees is lost) and also not for the distributed grid. If we have multiple solutions on a grid with the same dof handler, how is this handled (e.g. for time series) and so on. If I have multiple grids, this also already breaks down. This is very closely related to the struggle in Ferrite-FEM/FerriteViz.jl#1 which just tried to solve this abstraction layer for a single thing. If you have a solution to this I am happy to help on the implementation anyway. |
Just to be clear for others. What I would like to see is something like this julia> h5open("blah.h5", "w") do h5f
h5f["grid"] = grid
end
ERROR: Type Array does not have a definite size.
Stacktrace: which is not possible since HDF5.jl needs julia> function Base.setindex!(parent::HDF5.File, val::Grid, path::Union{AbstractString,Nothing}; pv...)
parent["grid/cells"] = grid.cells
parent["grid/nodes"] = grid.nodes
for (facesetname,faceset) in grid.facesets
parent["grid/facesets/$facesetname"] = collect(faceset)
end
end
julia> h5open("blah.h5", "w") do h5f
h5f["grid"] = grid
end
Grid{2, Quadrilateral, Float64} with 400 Quadrilateral cells and 441 nodes In order to read the things back in you get now a julia> h5open("blah.h5", "r") do h5f
read(h5f["grid"])
end
Dict{String, Any} with 3 entries:
"nodes" => NamedTuple{(:x,), Tuple{NamedTuple{(:data,), Tuple{NamedTuple{(Symbol("1"), Symbol("2")), Tuple{Float64, Float64}}}}}}[(x = (data = (var"1" = -1.…
"cells" => NamedTuple{(:nodes,), Tuple{NamedTuple{(Symbol("1"), Symbol("2"), Symbol("3"), Symbol("4")), NTuple{4, Int64}}}}[(nodes = (var"1" = 1, var"2" = 2…
"facesets" => Dict{String, Any}("left"=>NamedTuple{(:idx,), Tuple{NamedTuple{(Symbol("1"), Symbol("2")), Tuple{Int64, Int64}}}}[(idx = (var"1" = 1, var"2" = 4)… which is again somewhat inconvenient and one would ideally do the same as for a
Then people can save in whatever order/form they want, just a small convenience layer (which isn't even visible/noticeable while using it) in order to make life easier and share code that would be duplicated anyways |
In general, I think having some binary exchange format for Ferrite data would be nice! Just to mention an alternative approach, which can be done from outside struct FerriteHDF{G<:AbstractGrid,F<:HDF5.File}
grid::G
h5f::F
end
function FerriteHDF(name::String, grid::AbstractGrid)
h5f = open(name, "w")
h5f ["grid/cells"] = grid.cells
h5f ["grid/nodes"] = grid.nodes
for (facesetname,faceset) in grid.facesets
h5f["grid/facesets/$facesetname"] = collect(faceset)
end
return FerriteHDF(grid, h5f)
end
Base.close(io::FerriteHDF) = close(io.h5f)
# Could also define the do-block, append modes and other conveniences as required....
# And define e.g. `Ferrite.write_celldata(::FerriteHDF, data)`
# Would be even more general if the same was done for the dofhandler instead of the grid... IMO, doing something like this in an external package could be nice - and if it becomes well-adopted we can consider bringing it in as a weak dep via HDF5.jl (slightly tricky with the type, but I guess that type could be defined outside the extension) Similarly (as previously discussed) could be done for JLD2.jl Currently, there isn't any interface for reading this back in, but one could define function FerriteHDF5(path::String, G::Type{<:AbstractGrid})
h5f = h5open(path, "r")
grid = dict_to_grid(G, read(h5f["grid"]))
return FerriteHDF5(grid, h5f)
end
# Similar to above, a do-construct can be defined as well.
# The following methods could then be defined in Ferrite if we have an importer functionality,
# but initially could be defined by the package defining FerriteHDF5...
read_celldata(::FerriteHDF5, key) (Of course, I'm biased here since I have #692 in my head) |
I'm not sure, but why should this be type piracy if we own the type |
From inside Ferrite it is perfectly fine! (I was referring to doing this in an external package) |
I think the choice for having this supported by Ferrite is more that one has to commit to one backend as maintaining multiple might be too much to promise. HDF5 might certainly be a nice choice! Out of interest, why choosing this over JLD2 as was discussed above? Or would this somehow work naturally with JLD2 since that is hdf5-based? |
I received some reviewer comments in a funding proposal that something more established like HDF5 should be used instead of JLD2 (due to its "known problems"). The "known problems" weren't specified, but I think there is some consensus that HDF5 is somewhat reliable and established. Aside that, jld2 leaks quite severely memory (not sure if HDF5 has the same problem) |
Thanks - that is interesting to get from a reviewer (even if vague) At least the current problem with the circular dependency for the In the future, I plan to overhaul the IO part of FerriteProblems (which currently uses JLD2), and after that, I might have better views/opinions. But it would be great if there already is a solution to tap into then:) |
Do you happen to know if JLD2 allows for the same |
Don't know, but doubt it since JLD2.jl doesn't have HDF5.jl as dep. JLD.jl does, maybe it works for that one? But also not sure if this design was causing JLD2.jl to be created as the replacement. |
I meant instead of julia> function Base.setindex!(parent::HDF5.File, val::Grid, path::Union{AbstractString,Nothing}; pv...)
parent["grid/cells"] = grid.cells
parent["grid/nodes"] = grid.nodes
for (facesetname,faceset) in grid.facesets
parent["grid/facesets/$facesetname"] = collect(faceset)
end
end
julia> h5open("blah.h5", "w") do h5f
h5f["grid"] = grid
end this julia> function Base.setindex!(parent::JLD2.File, val::Grid, path::Union{AbstractString,Nothing}; pv...)
parent["grid/cells"] = grid.cells
parent["grid/nodes"] = grid.nodes
for (facesetname,faceset) in grid.facesets
parent["grid/facesets/$facesetname"] = collect(faceset)
end
end
julia> jldopen("blah.jld2", "w") do jld
jld["grid"] = grid
end |
The recommended way to do this would be to define a custom serialization: https://juliaio.github.io/JLD2.jl/dev/advanced/
I have not encountered that before. Please file an issue, if you can trigger this. |
I think I was thinking more about the latter case, because the datastructure
It's quite hard to come up with a reproducer, it happens for me only in simulations that compute for days. Also julia threads error for me in that case after closing the REPL. I hope I can come up with a reproducer at some point. |
Or to put it differently: I can think of a lot of cases, where I don't want to have an explicit bijective mapping from storage type to (actual user) julia type. Sometimes I just want to dump data (and not being able to reconstruct the type from which I gathered the data) and read it out as such ( |
@koehlerson
Caveat: JLD2 stores the type signature of the EDIT: |
I'd love to have some intermediate dispatch
I was hoping that |
This sounds like a really nice idea. One of the things that limits flexibility in JLD2 is that it tries to be type stable to be fast. Building efficient memory layouts for structures also requires quite some knowledge about the types.. Further discussion might be more appropriate in JLD2. |
I use JLD2 for saving my simulation results. The two most important pieces that I save are solution vectors and a
DofHandler
. The only annoying part is if structs change they can't be reloaded that easily. However, JLD2 offers a lot of API to cover that by e.g. custom serialization and type remappingWhat do you think about providing a binary export extension for e.g. JLD2 where we specify a custom serialization for the
DofHandler
?The text was updated successfully, but these errors were encountered: