diff --git a/.travis.yml b/.travis.yml index 84b4b01..1c457f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,14 +3,16 @@ os: - linux - osx julia: - - 0.6 - 0.7 - 1.0 - nightly +addons: + apt: + packages: + - hdf5-tools +before_script: + - if [ $TRAVIS_OS_NAME = osx ]; then brew install gcc; brew link --overwrite gcc; brew install hdf5; fi notifications: email: false -script: - - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia -e 'Pkg.clone(pwd()); Pkg.build("MAT"); Pkg.test("MAT"; coverage=true)' after_success: - julia -e 'cd(Pkg.dir("MAT")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' diff --git a/REQUIRE b/REQUIRE index 264fa34..c4668ba 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,5 @@ -julia 0.6 -HDF5 +julia 0.7 +HDF5 0.10.3 BufferedStreams 0.2.0 Libz Compat 1.0 diff --git a/src/MAT.jl b/src/MAT.jl index 5b55d2a..46fe2cf 100644 --- a/src/MAT.jl +++ b/src/MAT.jl @@ -22,11 +22,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -VERSION >= v"0.4.0-dev+6521" && __precompile__() - module MAT -using HDF5, Compat +using HDF5, Compat, SparseArrays include("MAT_HDF5.jl") include("MAT_v5.jl") @@ -143,7 +141,7 @@ end Write a dictionary containing variable names as keys and values as values to a Matlab file, opening and closing it automatically. """ -function matwrite(filename::AbstractString, dict::AbstractDict{S, T}) where {S,T} +function matwrite(filename::AbstractString, dict::AbstractDict{S, T}) where {S, T} file = matopen(filename, "w") try for (k, v) in dict diff --git a/src/MAT_HDF5.jl b/src/MAT_HDF5.jl index 03070f7..a3ea212 100644 --- a/src/MAT_HDF5.jl +++ b/src/MAT_HDF5.jl @@ -28,9 +28,7 @@ module MAT_HDF5 -using HDF5 -using Compat -using Compat.SparseArrays +using HDF5, SparseArrays import Base: read, write, close import HDF5: names, exists, HDF5ReferenceObj, HDF5BitsKind @@ -47,7 +45,7 @@ mutable struct MatlabHDF5File <: HDF5.DataFile function MatlabHDF5File(plain, toclose::Bool=true, writeheader::Bool=false, refcounter::Int=0) f = new(plain, toclose, writeheader, refcounter) if toclose - finalizer(f, close) + finalizer(close, f) end f end @@ -64,7 +62,11 @@ function close(f::MatlabHDF5File) if f.writeheader magic = zeros(UInt8, 512) identifier = "MATLAB 7.3 MAT-file" # minimal but sufficient - magic[1:length(identifier)] = Vector{UInt8}(identifier) + GC.@preserve magic identifier begin + magicptr = pointer(magic) + idptr = pointer(identifier) + unsafe_copyto!(magicptr, idptr, length(identifier)) + end magic[126] = 0x02 magic[127] = 0x49 magic[128] = 0x4d @@ -117,26 +119,18 @@ const sparse_attr_matlab = "MATLAB_sparse" const int_decode_attr_matlab = "MATLAB_int_decode" ### Reading -function read_complex(dtype::HDF5Datatype, dset::HDF5Dataset, ::Type{Array{T}}) where {T} +function read_complex(dtype::HDF5Datatype, dset::HDF5Dataset, ::Type{Array{T}}) where T if !check_datatype_complex(dtype) close(dtype) error("Unrecognized compound data type when reading ", name(dset)) end memtype = build_datatype_complex(T) sz = size(dset) - dbuf = Array{T}(2, sz...) st = sizeof(T) - buf = reinterpret(UInt8, dbuf, (2 * st, sz...)) - HDF5.h5d_read(dset.id, memtype.id, HDF5.H5S_ALL, HDF5.H5S_ALL, HDF5.H5P_DEFAULT, buf) + buf = Array{Complex{T}}(undef, sz) + HDF5.h5d_read(dset.id, memtype.id, HDF5.H5S_ALL, HDF5.H5S_ALL, HDF5.H5P_DEFAULT, vec(buf)) - if T == Float32 - d = reinterpret(Complex64, dbuf, sz) - elseif T == Float64 - d = reinterpret(Complex128, dbuf, sz) - else - d = slicedim(dbuf, 1, 1) + im * slicedim(dbuf, 1, 2) - end - length(d) == 1 ? d[1] : d + size(buf) == (1,) ? buf[1] : buf end function m_read(dset::HDF5Dataset) @@ -156,7 +150,7 @@ function m_read(dset::HDF5Dataset) end else T = mattype == "canonical empty" ? Union{} : str2eltype_matlab[mattype] - return Array{T}(dims...) + return Array{T}(undef, dims...) end end @@ -165,7 +159,7 @@ function m_read(dset::HDF5Dataset) if mattype == "cell" # Cell arrays, represented as an array of refs refs = read(dset, Array{HDF5ReferenceObj}) - out = Array{Any}(size(refs)) + out = Array{Any}(undef, size(refs)) f = file(dset) for i = 1:length(refs) dset = f[refs[i]] @@ -300,12 +294,12 @@ elseif length(s) > 63 end toarray(x::Array) = x -toarray(x::Array{Bool}) = reinterpret(UInt8, x) +toarray(x::Array{Bool}) = reinterpret.(UInt8, x) toarray(x::Bool) = UInt8[x] toarray(x) = [x] # Write the MATLAB type string for dset -m_writetypeattr(dset, ::Type{Complex{T}}) where {T} = m_writetypeattr(dset, T) +m_writetypeattr(dset, ::Type{Complex{T}}) where T = m_writetypeattr(dset, T) function m_writetypeattr(dset, T) if !haskey(type2str_matlab, T) error("Type ", T, " is not (yet) supported") @@ -320,7 +314,7 @@ function m_writetypeattr(dset, T) end # Writes an empty scalar or array -function m_writeempty(parent::HDF5Parent, name::String, data::Array) +function m_writeempty(parent::HDF5Parent, name::String, data::AbstractArray) adata = [size(data)...] dset, dtype = d_create(parent, name, adata) try @@ -334,7 +328,7 @@ function m_writeempty(parent::HDF5Parent, name::String, data::Array) end # Write an array to a dataset in a MATLAB file, returning the dataset -function m_writearray(parent::HDF5Parent, name::String, adata::Array{T}) where {T<:HDF5BitsOrBool} +function m_writearray(parent::HDF5Parent, name::String, adata::AbstractArray{T}) where {T<:HDF5BitsOrBool} dset, dtype = d_create(parent, name, adata) try HDF5.writearray(dset, dtype.id, adata) @@ -346,14 +340,14 @@ function m_writearray(parent::HDF5Parent, name::String, adata::Array{T}) where { close(dtype) end end -function m_writearray(parent::HDF5Parent, name::String, adata::Array{Complex{T}}) where {T<:HDF5BitsOrBool} +function m_writearray(parent::HDF5Parent, name::String, adata::AbstractArray{Complex{T}}) where {T<:HDF5BitsOrBool} dtype = build_datatype_complex(T) try stype = dataspace(adata) obj_id = HDF5.h5d_create(parent.id, name, dtype.id, stype.id) dset = HDF5Dataset(obj_id, file(parent)) try - arr = reinterpret(T, adata, tuple(2, size(adata)...)) + arr = reshape(reinterpret(T, adata), tuple(2, size(adata)...)) HDF5.writearray(dset, dtype.id, arr) catch e close(dset) @@ -382,16 +376,16 @@ function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data:: end # Write sparse arrays -function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data::SparseMatrixCSC{T}) where {T} +function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data::SparseMatrixCSC{T}) where T g = g_create(parent, name) try m_writetypeattr(g, T) a_write(g, sparse_attr_matlab, UInt64(size(data, 1))) if !isempty(data.nzval) close(m_writearray(g, "data", toarray(data.nzval))) - close(m_writearray(g, "ir", add!(isa(data.rowval, Vector{UInt64}) ? copy(data.rowval) : convert(Vector{UInt64}, data.rowval), reinterpret(UInt64, convert(Int64, -1))))) + close(m_writearray(g, "ir", add!(isa(data.rowval, Vector{UInt64}) ? copy(data.rowval) : convert(Vector{UInt64}, data.rowval), typemax(UInt64)))) end - close(m_writearray(g, "jc", add!(isa(data.colptr, Vector{UInt64}) ? copy(data.colptr) : convert(Vector{UInt64}, data.colptr), reinterpret(UInt64, convert(Int64, -1))))) + close(m_writearray(g, "jc", add!(isa(data.colptr, Vector{UInt64}) ? copy(data.colptr) : convert(Vector{UInt64}, data.colptr), typemax(UInt64)))) finally close(g) end @@ -435,7 +429,7 @@ function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, str::A end # Write cell arrays -function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data::Array{T}) where {T} +function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data::Array{T}) where T pathrefs = "/#refs#" fid = file(parent) local g @@ -466,7 +460,7 @@ function m_write(mfile::MatlabHDF5File, parent::HDF5Parent, name::String, data:: close(a) end # Write the items to the reference group - refs = Array{HDF5ReferenceObj}(size(data)) + refs = Array{HDF5ReferenceObj}(undef, size(data)) for i = 1:length(data) mfile.refcounter += 1 itemname = string(mfile.refcounter) @@ -492,7 +486,7 @@ end # Check that keys are valid for a struct, and convert them to an array of ASCIIStrings function check_struct_keys(k::Vector) - asckeys = Vector{String}(length(k)) + asckeys = Vector{String}(undef, length(k)) for i = 1:length(k) key = k[i] if !isa(key, AbstractString) @@ -542,7 +536,7 @@ end ## Type conversion operations ## -struct MatlabString; end +struct MatlabString end const str2type_matlab = Dict( "canonical empty" => nothing, @@ -600,9 +594,9 @@ function read(obj::HDF5Object, ::Type{MatlabString}) data = reshape(data, sz[2:end]) end if ndims(data) == 1 - return convert(String, convert(Vector{Char}, data)) + return String(convert(Vector{Char}, data)) elseif ndims(data) == 2 - return datap = String[rstrip(convert(String, convert(Vector{Char}, vec(data[i, :])))) for i = 1:size(data, 1)] + return datap = String[rstrip(String(convert(Vector{Char}, vec(data[i, :])))) for i = 1:size(data, 1)] else return data end @@ -611,9 +605,19 @@ function read(obj::HDF5Object, ::Type{Bool}) tf = read(obj, UInt8) tf > 0 end + +# copied from HDF5.jl/src/HDF5.jl#L1306 function read(obj::HDF5Object, ::Type{Array{Bool}}) - tf = read(obj, Array{UInt8}) - reinterpret(Bool, tf) + if HDF5.isnull(obj) + return Bool[] + end + + dims = size(obj) + tf = Array{Bool}(undef, dims) + + HDF5.readarray(obj, HDF5.hdf5_type_id(UInt8), reinterpret(UInt8, tf)) + + return tf end ## Utilities for handling complex numbers diff --git a/src/MAT_v5.jl b/src/MAT_v5.jl index 7e1de50..a9f72c1 100644 --- a/src/MAT_v5.jl +++ b/src/MAT_v5.jl @@ -26,17 +26,12 @@ # http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf module MAT_v5 -using Libz, BufferedStreams, HDF5, Compat +using Libz, BufferedStreams, HDF5, SparseArrays import Base: read, write, close import HDF5: names, exists -if VERSION < v"0.6.0-dev.1632" - round_uint8(data) = round(UInt8, data) - complex_array(a, b) = complex(a, b) -else - round_uint8(data) = round.(UInt8, data) - complex_array(a, b) = complex.(a, b) -end +round_uint8(data) = round.(UInt8, data) +complex_array(a, b) = complex.(a, b) mutable struct Matlabv5File <: HDF5.DataFile ios::IOStream @@ -84,9 +79,9 @@ const CONVERT_TYPES = Type[ Union{}, Float64, Float32, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64] -read_bswap(f::IO, swap_bytes::Bool, ::Type{T}) where {T} = +read_bswap(f::IO, swap_bytes::Bool, ::Type{T}) where T = swap_bytes ? bswap(read(f, T)) : read(f, T) -function read_bswap(f::IO, swap_bytes::Bool, ::Type{T}, dim::Union{Int, Tuple{Vararg{Int}}}) where {T} +function read_bswap(f::IO, swap_bytes::Bool, ::Type{T}, dim::Union{Int, Tuple{Vararg{Int}}}) where T d = read!(f, Array{T}(undef, dim)) if swap_bytes for i = 1:length(d) @@ -95,6 +90,15 @@ function read_bswap(f::IO, swap_bytes::Bool, ::Type{T}, dim::Union{Int, Tuple{Va end d end +function read_bswap(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T + readbytes!(f, reinterpret(UInt8, d)) + if swap_bytes + for i = 1:length(d) + @inbounds d[i] = bswap(d[i]) + end + end + d +end skip_padding(f::IO, nbytes::Int, hbytes::Int) = if nbytes % hbytes != 0 skip(f, hbytes-(nbytes % hbytes)) @@ -114,7 +118,7 @@ function read_header(f::IO, swap_bytes::Bool) end # Read data element as a vector of a given type -function read_element(f::IO, swap_bytes::Bool, ::Type{T}) where {T} +function read_element(f::IO, swap_bytes::Bool, ::Type{T}) where T (dtype, nbytes, hbytes) = read_header(f, swap_bytes) data = read_bswap(f, swap_bytes, T, Int(div(nbytes, sizeof(T)))) skip_padding(f, nbytes, hbytes) @@ -133,7 +137,7 @@ end # Read data element as encoded type with given dimensions, converting # to another type if necessary and collapsing one-element matrices to # scalars -function read_data(f::IO, swap_bytes::Bool, ::Type{T}, dimensions::Vector{Int32}) where {T} +function read_data(f::IO, swap_bytes::Bool, ::Type{T}, dimensions::Vector{Int32}) where T (dtype, nbytes, hbytes) = read_header(f, swap_bytes) read_type = READ_TYPES[dtype] @@ -150,9 +154,27 @@ function read_data(f::IO, swap_bytes::Bool, ::Type{T}, dimensions::Vector{Int32} read_array ? convert(Array{T}, data) : convert(T, data) end +function read_data(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T + (dtype, nbytes, hbytes) = read_header(f, swap_bytes) + read_type = READ_TYPES[dtype] + dimensions = size(d) + + read_array = any(dimensions .!= 1) + if sizeof(read_type)*prod(dimensions) != nbytes + error("Invalid element length") + end + if read_array + data = read_bswap(f, swap_bytes, reinterpret(read_type, d)) + else + data = read_bswap(f, swap_bytes, read_type) + end + skip_padding(f, nbytes, hbytes) + + read_array ? d : convert(T, data) +end function read_cell(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) - data = Array{Any}(convert(Vector{Int}, dimensions)...) + data = Array{Any}(undef, convert(Vector{Int}, dimensions)...) for i = 1:length(data) (ignored_name, data[i]) = read_matrix(f, swap_bytes) end @@ -168,11 +190,11 @@ function read_struct(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, is_obje n_fields = div(length(field_names), field_length) # Get field names as strings - field_name_strings = Vector{String}(n_fields) + field_name_strings = Vector{String}(undef, n_fields) n_el = prod(dimensions) for i = 1:n_fields sname = field_names[(i-1)*field_length+1:i*field_length] - index = findfirst(sname, 0) + index = something(findfirst(iszero, sname), 0) field_name_strings[i] = String(index == 0 ? sname : sname[1:index-1]) end @@ -190,7 +212,7 @@ function read_struct(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, is_obje else # Read multiple structs into a dict of arrays for field_name in field_name_strings - data[field_name] = Array{Any}(dimensions...) + data[field_name] = Array{Any}(undef, dimensions...) end for i = 1:n_el for field_name in field_name_strings @@ -240,11 +262,7 @@ function read_sparse(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, flags:: SparseMatrixCSC(m, n, jc, ir, pr) end -if VERSION >= v"0.4.0-dev+1039" - truncate_to_uint8(x) = x % UInt8 -else - truncate_to_uint8(x) = convert(UInt8, x) -end +truncate_to_uint8(x) = x % UInt8 function read_string(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) (dtype, nbytes, hbytes) = read_header(f, swap_bytes) @@ -257,7 +275,7 @@ function read_string(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}) if dimensions[1] <= 1 data = String(chars) else - data = Vector{String}(dimensions[1]) + data = Vector{String}(undef, dimensions[1]) for i = 1:dimensions[1] data[i] = rstrip(String(chars[i:dimensions[1]:end])) end @@ -310,7 +328,7 @@ function read_matrix(f::IO, swap_bytes::Bool) # a = {[], [], []} # then MATLAB does not save the empty cells as zero-byte matrices. To avoid # surprises, we produce an empty array in both cases. - return ("", Matrix{Union{}}(0, 0)) + return ("", Matrix{Union{}}(undef, 0, 0)) end flags = read_element(f, swap_bytes, UInt32) @@ -329,11 +347,14 @@ function read_matrix(f::IO, swap_bytes::Bool) data = read_string(f, swap_bytes, dimensions) else convert_type = CONVERT_TYPES[class] - data = read_data(f, swap_bytes, convert_type, dimensions) if (flags[1] & (1 << 11)) != 0 # complex + data = read_data(f, swap_bytes, convert_type, dimensions) data = complex_array(data, read_data(f, swap_bytes, convert_type, dimensions)) elseif (flags[1] & (1 << 9)) != 0 # logical - data = reinterpret(Bool, round_uint8(data)) + d = Array{Bool}(undef, dimensions...) + data = read_data(f, swap_bytes, d) + else + data = read_data(f, swap_bytes, convert_type, dimensions) end end diff --git a/test/read.jl b/test/read.jl index c60f706..6e00483 100644 --- a/test/read.jl +++ b/test/read.jl @@ -1,4 +1,4 @@ -using MAT, Base.Test +using MAT, Test function check(filename, result) matfile = matopen(filename) @@ -45,7 +45,7 @@ end global format for _format in ["v6", "v7", "v7.3"] - format = _format + global format = _format cd(joinpath(dirname(@__FILE__), format)) result = Dict( @@ -72,7 +72,7 @@ for _format in ["v6", "v7", "v7.3"] end result = Dict( - "imaginary" => Complex128[1 -1 1+im 1-im -1+im -1-im im] + "imaginary" => ComplexF64[1 -1 1+im 1-im -1+im -1-im im] ) check("complex.mat", result) @@ -89,7 +89,7 @@ for _format in ["v6", "v7", "v7.3"] "a1x2" => [1.0 2.0], "a2x1" => zeros(2, 1)+[1.0, 2.0], "a2x2" => [1.0 3.0; 4.0 2.0], - "a2x2x2" => cat(3, [1.0 3.0; 4.0 2.0], [1.0 2.0; 3.0 4.0]), + "a2x2x2" => cat([1.0 3.0; 4.0 2.0], [1.0 2.0; 3.0 4.0]; dims=3), "empty" => zeros(0, 0), "string" => "string" ) @@ -117,7 +117,7 @@ for _format in ["v6", "v7", "v7.3"] true false false false true false true false false - ] + ] ) check("logical.mat", result) @@ -127,8 +127,8 @@ for _format in ["v6", "v7", "v7.3"] check("empty_cells.mat", result) result = Dict( - "sparse_empty" => sparse(Matrix{Float64}(0, 0)), - "sparse_eye" => speye(20), + "sparse_empty" => sparse(Matrix{Float64}(undef, 0, 0)), + "sparse_eye" => sparse(1.0I, 20, 20), "sparse_logical" => SparseMatrixCSC{Bool,Int64}(5, 5, [1:6;], [1:5;], fill(true, 5)), "sparse_random" => sparse([0 6. 0; 8. 0 1.; 0 0 9.]), "sparse_complex" => sparse([0 6. 0; 8. 0 1.; 0 0 9.]*(1. + 1.0im)), diff --git a/test/runtests.jl b/test/runtests.jl index f3aa8ae..5b9bec1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,2 +1,4 @@ +using SparseArrays, LinearAlgebra + include("read.jl") include("write.jl") diff --git a/test/write.jl b/test/write.jl index 226390c..cb994cf 100644 --- a/test/write.jl +++ b/test/write.jl @@ -33,10 +33,12 @@ test_write(Dict( )) test_write(Dict( - "Complex128" => [1.0 -1.0 1.0+1.0im 1.0-1.0im -1.0+1.0im -1.0-1.0im 1.0im], + "ComplexInt" => Complex{Int}[1 -1 1+1im 1-1im -1+1im -1-1im 1im], + "ComplexF32" => ComplexF32[1.0 -1.0 1.0+1.0im 1.0-1.0im -1.0+1.0im -1.0-1.0im 1.0im], + "ComplexF64" => [1.0 -1.0 1.0+1.0im 1.0-1.0im -1.0+1.0im -1.0-1.0im 1.0im], "ComplexPair" => [1 2-3im 4+5im] )) -test_write(Dict("Complex128" => 1.0im, "ComplexPair" => 2-3im)) +test_write(Dict("ComplexF64" => 1.0im, "ComplexPair" => 2-3im)) test_write(Dict( "simple_string" => "the quick brown fox", @@ -50,7 +52,7 @@ test_write(Dict( "a1x2" => [1.0 2.0], "a2x1" => zeros(2, 1)+[1.0, 2.0], "a2x2" => [1.0 3.0; 4.0 2.0], - "a2x2x2" => cat(3, [1.0 3.0; 4.0 2.0], [1.0 2.0; 3.0 4.0]), + "a2x2x2" => cat([1.0 3.0; 4.0 2.0], [1.0 2.0; 3.0 4.0], dims=3), "empty" => zeros(0, 0), "string" => "string" )) @@ -69,8 +71,8 @@ test_write(Dict( )) test_write(Dict( - "sparse_empty" => sparse(Matrix{Float64}(0, 0)), - "sparse_eye" => speye(20), + "sparse_empty" => sparse(Matrix{Float64}(undef, 0, 0)), + "sparse_eye" => sparse(1.0I, 20, 20), "sparse_logical" => SparseMatrixCSC{Bool,Int64}(5, 5, [1:6;], [1:5;], fill(true, 5)), "sparse_random" => sparse([0 6. 0; 8. 0 1.; 0 0 9.]), "sparse_complex" => sparse([0 6. 0; 8. 0 1.; 0 0 9.]*(1. + 1.0im)), @@ -81,7 +83,7 @@ test_write(Dict( @test_throws ErrorException test_write(Dict("another invalid key" => "invalid characters")) @test_throws ErrorException test_write(Dict("yetanotherinvalidkeyyetanotherinvalidkeyyetanotherinvalidkeyyetanotherinvalidkey" => "too long")) -type TestCompositeKind +struct TestCompositeKind field1::AbstractString end fid = matopen(tmpfile, "w") @@ -100,9 +102,9 @@ close(fid) using DataStructures sd = SortedDict(Dict( "uint16" => UInt16(1), - "Complex128" => [1.0 -1.0 1.0+1.0im 1.0-1.0im -1.0+1.0im -1.0-1.0im 1.0im], + "ComplexF64" => [1.0 -1.0 1.0+1.0im 1.0-1.0im -1.0+1.0im -1.0-1.0im 1.0im], "simple_string" => "the quick brown fox", "a1x2" => [1.0 2.0], - "sparse_empty" => sparse(Matrix{Float64}(0, 0)) + "sparse_empty" => sparse(Matrix{Float64}(undef, 0, 0)) )) test_write(sd)