Skip to content
This repository was archived by the owner on Apr 21, 2022. It is now read-only.

Commit a8b6900

Browse files
dourouc05odow
authored andcommitted
Add more compression formats (#90)
* Move most of the code from jump-dev/JuMP.jl#1982. * Refactor to use a dictionary storing all the variance within compression codecs. * Same refactoring for model types. * Start restructuring compression format handling. * Finalise implementation based on types. * Add documentation for AbstractCompressionScheme. * Clean up code. * Add more tests. * Comment out XZ. * Remove dependency for commented out code. * Comment out XZ.
1 parent cc7e817 commit a8b6900

File tree

5 files changed

+152
-42
lines changed

5 files changed

+152
-42
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ authors = ["Oscar Dowson <[email protected]"]
44
version = "0.2.2"
55

66
[deps]
7+
CodecBzip2 = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd"
78
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
89
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
910
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"

src/MOF/MOF.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ Validate that the MOF file `filename` conforms to the MOF JSON schema. Returns
9090
`nothing` if the file is valid, otherwise throws an error describing why the
9191
file is not valid.
9292
"""
93-
function validate(filename::String)
94-
MathOptFormat.gzip_open(filename, "r") do io
93+
function validate(filename::String; compression::MathOptFormat.AbstractCompressionScheme=MathOptFormat.AutomaticCompression())
94+
compression = MathOptFormat._automatic_compression(filename, compression)
95+
MathOptFormat._compressed_open(filename, "r", compression) do io
9596
validate(io)
9697
end
9798
return

src/MathOptFormat.jl

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ module MathOptFormat
33
import MathOptInterface
44
const MOI = MathOptInterface
55

6+
import CodecBzip2
7+
# import CodecXz
68
import CodecZlib
79

10+
include("compression.jl")
811
include("CBF/CBF.jl")
912
include("LP/LP.jl")
1013
include("MOF/MOF.jl")
@@ -133,36 +136,51 @@ function create_unique_variable_names(
133136
end
134137
end
135138

136-
function gzip_open(f::Function, filename::String, mode::String)
137-
if endswith(filename, ".gz")
138-
if mode == "r"
139-
open(CodecZlib.GzipDecompressorStream, filename, mode) do io
140-
f(io)
141-
end
142-
elseif mode == "w"
143-
open(CodecZlib.GzipCompressorStream, filename, mode) do io
144-
f(io)
139+
"""
140+
List of accepted export formats. `AUTOMATIC_FILE_FORMAT` corresponds to
141+
a detection from the file name, only based on the extension (regardless of
142+
compression format).
143+
"""
144+
@enum(FileFormat, FORMAT_CBF, FORMAT_LP, FORMAT_MOF, FORMAT_MPS, AUTOMATIC_FILE_FORMAT)
145+
146+
const _file_formats = Dict{FileFormat, Tuple{String, Any}}(
147+
# ENUMERATED VALUE => extension, model type
148+
FORMAT_CBF => (".cbf", CBF.Model),
149+
FORMAT_LP => (".lp", LP.Model),
150+
FORMAT_MOF => (".mof.json", MOF.Model),
151+
FORMAT_MPS => (".mps", MPS.Model)
152+
)
153+
154+
function _filename_to_format(filename::String)
155+
for compr_ext in ["", ".bz2", ".gz", ".xz"]
156+
for (type, format) in _file_formats
157+
if endswith(filename, "$(format[1])$(compr_ext)")
158+
return type
145159
end
146-
else
147-
throw(ArgumentError("Mode must be \"r\" or \"w\""))
148160
end
149-
else
150-
return open(f, filename, mode)
151161
end
162+
163+
error("File type of $(filename) not recognized by MathOptFormat.jl.")
164+
end
165+
166+
function _filename_to_model(filename::String)
167+
return _file_formats[_filename_to_format(filename)][2]()
152168
end
153169

154170
const MATH_OPT_FORMATS = Union{
155171
CBF.InnerModel, LP.InnerModel, MOF.Model, MPS.InnerModel
156172
}
157173

158-
function MOI.write_to_file(model::MATH_OPT_FORMATS, filename::String)
159-
gzip_open(filename, "w") do io
174+
function MOI.write_to_file(model::MATH_OPT_FORMATS, filename::String; compression::AbstractCompressionScheme=AutomaticCompression())
175+
compression = _automatic_compression(filename, compression)
176+
_compressed_open(filename, "w", compression) do io
160177
MOI.write_to_file(model, io)
161178
end
162179
end
163180

164-
function MOI.read_from_file(model::MATH_OPT_FORMATS, filename::String)
165-
gzip_open(filename, "r") do io
181+
function MOI.read_from_file(model::MATH_OPT_FORMATS, filename::String; compression::AbstractCompressionScheme=AutomaticCompression())
182+
compression = _automatic_compression(filename, compression)
183+
_compressed_open(filename, "r", compression) do io
166184
MOI.read_from_file(model, io)
167185
end
168186
end
@@ -173,19 +191,9 @@ end
173191
Create a MOI model by reading `filename`. Type of the returned model depends on
174192
the extension of `filename`.
175193
"""
176-
function read_from_file(filename::String)
177-
model = if endswith(filename, ".mof.json.gz") || endswith(filename, ".mof.json")
178-
MOF.Model()
179-
elseif endswith(filename, ".cbf.gz") || endswith(filename, ".cbf")
180-
CBF.Model()
181-
elseif endswith(filename, ".mps.gz") || endswith(filename, ".mps")
182-
MPS.Model()
183-
elseif endswith(filename, ".lp.gz") || endswith(filename, ".lp")
184-
LP.Model()
185-
else
186-
error("File-type of $(filename) not supported by MathOptFormat.jl.")
187-
end
188-
MOI.read_from_file(model, filename)
194+
function read_from_file(filename::String; compression::AbstractCompressionScheme=AutomaticCompression())
195+
model = _filename_to_model(filename)
196+
MOI.read_from_file(model, filename, compression=compression)
189197
return model
190198
end
191199

src/compression.jl

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
function error_mode(mode::String)
2+
throw(ArgumentError("For dealing with compressed data, mode must be \"r\" or \"w\"; $mode given"))
3+
end
4+
5+
"""
6+
abstract type AbstractCompressionScheme end
7+
8+
Base type to implement a new compression scheme for MathOptFormat. To do so,
9+
create a concrete subtype (e.g., named after the compression scheme) and
10+
implement `open(f::Function, filename::String, mode::String, ::YourScheme)`.
11+
"""
12+
abstract type AbstractCompressionScheme end
13+
14+
struct AutomaticCompression <: AbstractCompressionScheme end
15+
# No open() implementation, this would not make sense (flag to indicate that _filename_to_compression should be called).
16+
17+
struct NoCompression <: AbstractCompressionScheme end
18+
function _compressed_open(
19+
f::Function, filename::String, mode::String, ::NoCompression
20+
)
21+
return Base.open(f, filename, mode)
22+
end
23+
24+
struct Gzip <: AbstractCompressionScheme end
25+
function _compressed_open(
26+
f::Function, filename::String, mode::String, ::Gzip
27+
)
28+
return if mode == "w"
29+
Base.open(f, CodecZlib.GzipCompressorStream, filename, mode)
30+
elseif mode == "r"
31+
Base.open(f, CodecZlib.GzipDecompressorStream, filename, mode)
32+
else
33+
error_mode(mode)
34+
end
35+
end
36+
37+
struct Bzip2 <: AbstractCompressionScheme end
38+
function _compressed_open(
39+
f::Function, filename::String, mode::String, ::Bzip2
40+
)
41+
if mode == "w"
42+
Base.open(f, CodecBzip2.Bzip2CompressorStream, filename, mode)
43+
elseif mode == "r"
44+
Base.open(f, CodecBzip2.Bzip2DecompressorStream, filename, mode)
45+
else
46+
error_mode(mode)
47+
end
48+
end
49+
50+
# struct Xz <: AbstractCompressionScheme end
51+
# function _compressed_open(
52+
# f::Function, filename::String, mode::String, ::Xz
53+
# )
54+
# return if mode == "w"
55+
# Base.open(f, CodecXz.XzDecompressorStream, filename, mode)
56+
# elseif mode == "r"
57+
# Base.open(f, CodecXz.XzCompressorStream, filename, mode)
58+
# else
59+
# error_mode(mode)
60+
# end
61+
# end
62+
63+
function _automatic_compression(filename::String, compression::AbstractCompressionScheme)
64+
if compression == AutomaticCompression()
65+
return _filename_to_compression(filename)
66+
end
67+
return compression
68+
end
69+
70+
function _filename_to_compression(filename::String)
71+
if endswith(filename, ".bz2")
72+
return Bzip2()
73+
elseif endswith(filename, ".gz")
74+
return Gzip()
75+
# elseif endswith(filename, ".xz")
76+
# return Xz()
77+
else
78+
return NoCompression()
79+
end
80+
end

test/runtests.jl

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,35 @@ const MOIU = MOI.Utilities
2727
end
2828
end
2929

30-
@testset "Calling gzip_open" begin
31-
@test_throws ArgumentError MathOptFormat.gzip_open((x) -> nothing,
32-
"dummy.gz", "a")
33-
@test_throws ArgumentError MathOptFormat.gzip_open((x) -> nothing,
34-
"dummy.gz", "r+")
35-
@test_throws ArgumentError MathOptFormat.gzip_open((x) -> nothing,
36-
"dummy.gz", "w+")
37-
@test_throws ArgumentError MathOptFormat.gzip_open((x) -> nothing,
38-
"dummy.gz", "a+")
39-
30+
@testset "Calling MOF._compressed_open" begin
31+
for cs in [MathOptFormat.Bzip2(), MathOptFormat.Gzip()] # MathOptFormat.Xz()
32+
@test_throws ArgumentError MathOptFormat._compressed_open((x) -> nothing,
33+
"dummy.gz", "a", cs)
34+
@test_throws ArgumentError MathOptFormat._compressed_open((x) -> nothing,
35+
"dummy.gz", "r+", cs)
36+
@test_throws ArgumentError MathOptFormat._compressed_open((x) -> nothing,
37+
"dummy.gz", "w+", cs)
38+
@test_throws ArgumentError MathOptFormat._compressed_open((x) -> nothing,
39+
"dummy.gz", "a+", cs)
40+
end
41+
end
42+
43+
@testset "Provided compression schemes" begin
44+
file_to_read = joinpath(@__DIR__, "MPS", "free_integer.mps")
45+
m = MathOptFormat.read_from_file(file_to_read)
46+
47+
@testset "Automatic detection from extension" begin
48+
MOI.write_to_file(m, file_to_read * ".garbage")
49+
for ext in ["", ".bz2", ".gz"] # ".xz"
50+
MOI.write_to_file(m, file_to_read * ext)
51+
MathOptFormat.read_from_file(file_to_read * ext)
52+
end
53+
54+
# Clean up
55+
sleep(1.0) # Allow time for unlink to happen.
56+
for ext in ["", ".garbage", ".bz2", ".gz"] # ".xz"
57+
rm(file_to_read * ext, force = true)
58+
end
59+
end
4060
end
4161
end

0 commit comments

Comments
 (0)