55# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
66
77mutable struct _CacheModel
8+ is_binary:: Bool
89 cache:: Vector{UInt8}
910 variable_type:: Vector{_VariableType}
1011 variable_primal:: Vector{Float64}
@@ -17,6 +18,7 @@ mutable struct _CacheModel
1718 sense:: MOI.OptimizationSense
1819 function _CacheModel ()
1920 return new (
21+ false ,
2022 zeros (UInt8, 64 ),
2123 _VariableType[],
2224 Float64[],
@@ -95,12 +97,18 @@ function _next_token(::Type{T}, io::IO, cache::Vector{UInt8}) where {T}
9597end
9698
9799function _next (:: Type{Float64} , io:: IO , model:: _CacheModel )
100+ if model. is_binary
101+ return read (io, Float64)
102+ end
98103 nnz = _next_token (Float64, io, model. cache)
99104 @assert nnz > 0
100105 return parse (Float64, String (model. cache[1 : nnz]))
101106end
102107
103108function _next (:: Type{Int} , io:: IO , model:: _CacheModel )
109+ if model. is_binary
110+ return convert (Int, read (io, Int32))
111+ end
104112 nnz = _next_token (Int, io, model. cache)
105113 @assert nnz > 0
106114 y = 0
@@ -112,13 +120,27 @@ function _next(::Type{Int}, io::IO, model::_CacheModel)
112120 return y
113121end
114122
123+ function _next (:: Type{Cchar} , io:: IO , model:: _CacheModel )
124+ if model. is_binary
125+ return read (io, Cchar)
126+ end
127+ byte = UInt8 (' ' )
128+ while byte == UInt8 (' ' )
129+ byte = read (io, UInt8)
130+ end
131+ return Cchar (byte)
132+ end
133+
115134"""
116- _read_til_newline(io::IO)
135+ _read_til_newline(io::IO, model::_CacheModel )
117136
118137This function reads until it finds a new line character. This is useful for
119138skipping comments.
120139"""
121- function _read_til_newline (io:: IO )
140+ function _read_til_newline (io:: IO , model:: _CacheModel )
141+ if model. is_binary
142+ return
143+ end
122144 while read (io, UInt8) != UInt8 (' \n ' )
123145 end
124146 return
@@ -131,12 +153,12 @@ function _parse_expr(io::IO, model::_CacheModel)
131153 char = Char (read (io, UInt8))
132154 if char == ' o'
133155 opcode = _next (Int, io, model)
134- _read_til_newline (io)
156+ _read_til_newline (io, model )
135157 arity, op_func = _AMPL_TO_JULIA[opcode]
136158 op_sym = Symbol (op_func)
137159 if arity == - 1
138160 arity = _next (Int, io, model)
139- _read_til_newline (io)
161+ _read_til_newline (io, model )
140162 if op_sym == :sum
141163 op_sym = :+
142164 elseif op_sym == :minimum
@@ -153,12 +175,12 @@ function _parse_expr(io::IO, model::_CacheModel)
153175 return parent
154176 elseif char == ' v'
155177 index = _next (Int, io, model)
156- _read_til_newline (io)
178+ _read_til_newline (io, model )
157179 return MOI. VariableIndex (index + 1 )
158180 else
159181 @assert char == ' n'
160182 ret = _next (Float64, io, model)
161- _read_til_newline (io)
183+ _read_til_newline (io, model )
162184 return ret
163185 end
164186end
@@ -270,13 +292,16 @@ function _parse_header(io::IO, model::_CacheModel)
270292 # Line 1
271293 # We don't support the binary format.
272294 byte = read (io, UInt8)
273- if byte != UInt8 (' g' )
295+ is_binary = false
296+ if byte == UInt8 (' b' )
297+ is_binary = true
298+ elseif byte != UInt8 (' g' )
274299 error (" Unable to parse NL file : unsupported mode $(Char (byte)) " )
275300 end
276301 # L1 has some magic bytes for AMPL internals (to quote David, "The numbers
277302 # on the first line matter to AMPL; for other uses, it is best simply to
278303 # supply the ones shown above.")
279- _read_til_newline (io)
304+ _read_til_newline (io, model )
280305 # Line 2
281306 # The number of variables
282307 n_var = _next (Int, io, model)
@@ -292,39 +317,39 @@ function _parse_header(io::IO, model::_CacheModel)
292317 # The number of logical constraints. This one is optional, so just read til
293318 # the end of the line.
294319 # @assert _next(Int, io, model) == 0
295- _read_til_newline (io)
320+ _read_til_newline (io, model )
296321 # Line 3
297322 # The number of nonlinear constraints
298323 @assert _next (Int, io, model) >= 0
299324 # The number of nonlinear objectives
300325 @assert 0 <= _next (Int, io, model) <= 1
301- _read_til_newline (io)
326+ _read_til_newline (io, model )
302327 # Line 4
303328 # The number of nonlinear network constraints
304329 @assert _next (Int, io, model) == 0
305330 # The number of linear network constraints
306331 @assert _next (Int, io, model) == 0
307- _read_til_newline (io)
332+ _read_til_newline (io, model )
308333 # Line 5
309334 # The number of nonlienar variables in constraints
310335 nlvc = _next (Int, io, model)
311336 # The number of nonlienar variables in objectives
312337 nlvo = _next (Int, io, model)
313338 # The number of nonlienar variables in constraints and objectives (both)
314339 nlvb = _next (Int, io, model)
315- _read_til_newline (io)
340+ _read_til_newline (io, model )
316341 # Line 6
317342 # The number of linear network variables
318343 @assert _next (Int, io, model) == 0
319344 # The number of user-defined functions
320345 @assert _next (Int, io, model) == 0
321346 # The number of "arith"
322347 # TODO (odow): I don't know what this is.
323- @assert _next (Int, io, model) == 0
348+ _next (Int, io, model)
324349 # The "flags" entry. This is mainly used for specifying that we want duals.
325350 # Ignore when reading.
326351 _next (Int, io, model)
327- _read_til_newline (io)
352+ _read_til_newline (io, model )
328353 # Line 7
329354 # Number of binary variables
330355 nbv = _next (Int, io, model)
@@ -336,25 +361,25 @@ function _parse_header(io::IO, model::_CacheModel)
336361 nlvci = _next (Int, io, model)
337362 # Number of integer variables in nonlinear objectives
338363 nlvoi = _next (Int, io, model)
339- _read_til_newline (io)
364+ _read_til_newline (io, model )
340365 # Line 8
341366 # Read the number of nonzeros in Jacobian and gradient, but don't do
342367 # anything with that information.
343368 @assert _next (Int, io, model) >= 0
344369 @assert _next (Int, io, model) >= 0
345- _read_til_newline (io)
370+ _read_til_newline (io, model )
346371 # Line 9
347372 # We don't support reading variable and constraint names, so just ignore
348373 # them
349- _read_til_newline (io)
374+ _read_til_newline (io, model )
350375 # Line 10
351376 # We don't support reading common subexpressions
352377 for _ in 1 : 5
353378 if _next (Int, io, model) > 0
354379 error (" Unable to parse NL file : we don't support common exprs" )
355380 end
356381 end
357- _read_til_newline (io)
382+ _read_til_newline (io, model )
358383 # ==========================================================================
359384 # Deal with the integrality of variables. This is quite complicated, so go
360385 # read the README in this folder.
@@ -387,6 +412,8 @@ function _parse_header(io::IO, model::_CacheModel)
387412 model. variable_type[offset] = types[i]
388413 end
389414 end
415+ # Delay setting is_binary until the end of the header section
416+ model. is_binary = is_binary
390417 return
391418end
392419
@@ -412,10 +439,20 @@ end
412439function _parse_section (io:: IO , :: Val{'S'} , model:: _CacheModel )
413440 k = _next (Int, io, model)
414441 n = _next (Int, io, model)
415- suffix = readline (io)
416- @warn (" Skipping suffix: `S$k $n$suffix `" )
442+ suffix_name = if model. is_binary
443+ len = _next (Int, io, model)
444+ String (read (io, len))
445+ else
446+ strip (readline (io))
447+ end
448+ @warn (" Skipping suffix: `S$k $n $suffix_name `" )
449+ # The “4” bit of k indicates whether the suffix is real (that is, double)
450+ # valued or integer valued: (k&4) != 0 --> real valued.
451+ T = ifelse (k & 4 != 0 , Float64, Int)
417452 for _ in 1 : n
418- _read_til_newline (io)
453+ _ = _next (Int, io, model)
454+ _ = _next (T, io, model)
455+ _read_til_newline (io, model)
419456 end
420457 return
421458end
440477
441478function _parse_section (io:: IO , :: Val{'C'} , model:: _CacheModel )
442479 index = _next (Int, io, model) + 1
443- _read_til_newline (io)
480+ _read_til_newline (io, model )
444481 expr = _force_expr (_parse_expr (io, model))
445482 current = model. constraints[index]
446483 if current == :()
@@ -460,7 +497,7 @@ function _parse_section(io::IO, ::Val{'O'}, model::_CacheModel)
460497 @assert sense == 0
461498 model. sense = MOI. MIN_SENSE
462499 end
463- _read_til_newline (io)
500+ _read_til_newline (io, model )
464501 expr = _force_expr (_parse_expr (io, model))
465502 if model. objective == :()
466503 model. objective = expr
@@ -472,95 +509,99 @@ end
472509
473510function _parse_section (io:: IO , :: Val{'x'} , model:: _CacheModel )
474511 index = _next (Int, io, model)
475- _read_til_newline (io)
512+ _read_til_newline (io, model )
476513 for _ in 1 : index
477514 xi = _next (Int, io, model) + 1
478515 v = _next (Float64, io, model)
479516 model. variable_primal[xi] = v
480- _read_til_newline (io)
517+ _read_til_newline (io, model )
481518 end
482519 return
483520end
484521
485522# TODO (odow): we don't read in dual starts.
486523function _parse_section (io:: IO , :: Val{'d'} , model:: _CacheModel )
487- index = _next (Int, io, model)
488- _read_til_newline (io)
489- for _ in 1 : index
490- _read_til_newline (io)
524+ n = _next (Int, io, model)
525+ _read_til_newline (io, model)
526+ for _ in 1 : n
527+ _ = _next (Int, io, model)
528+ _ = _next (Float64, io, model)
529+ _read_til_newline (io, model)
491530 end
492531 return
493532end
494533
495534function _parse_section (io:: IO , :: Val{'r'} , model:: _CacheModel )
496- _read_til_newline (io)
535+ _read_til_newline (io, model )
497536 for i in 1 : length (model. constraint_lower)
498- type = _next (Int , io, model)
499- if type == 0
537+ type = _next (Cchar , io, model)
538+ if type == Cchar ( ' 0 ' )
500539 model. constraint_lower[i] = _next (Float64, io, model)
501540 model. constraint_upper[i] = _next (Float64, io, model)
502- elseif type == 1
541+ elseif type == Cchar ( ' 1 ' )
503542 model. constraint_upper[i] = _next (Float64, io, model)
504- elseif type == 2
543+ elseif type == Cchar ( ' 2 ' )
505544 model. constraint_lower[i] = _next (Float64, io, model)
506- elseif type == 3
545+ elseif type == Cchar ( ' 3 ' )
507546 # Free constraint
508547 else
509- @assert type == 4
548+ @assert type == Cchar ( ' 4 ' )
510549 value = _next (Float64, io, model)
511550 model. constraint_lower[i] = value
512551 model. constraint_upper[i] = value
513552 end
514- _read_til_newline (io)
553+ _read_til_newline (io, model )
515554 end
516555 return
517556end
518557
519558function _parse_section (io:: IO , :: Val{'b'} , model:: _CacheModel )
520- _read_til_newline (io)
559+ _read_til_newline (io, model )
521560 for i in 1 : length (model. variable_lower)
522- type = _next (Int , io, model)
523- if type == 0
561+ type = _next (Cchar , io, model)
562+ if type == Cchar ( ' 0 ' )
524563 model. variable_lower[i] = _next (Float64, io, model)
525564 model. variable_upper[i] = _next (Float64, io, model)
526- elseif type == 1
565+ elseif type == Cchar ( ' 1 ' )
527566 model. variable_upper[i] = _next (Float64, io, model)
528- elseif type == 2
567+ elseif type == Cchar ( ' 2 ' )
529568 model. variable_lower[i] = _next (Float64, io, model)
530- elseif type == 3
569+ elseif type == Cchar ( ' 3 ' )
531570 # Free variable
532571 else
533- @assert type == 4
572+ @assert type == Cchar ( ' 4 ' )
534573 value = _next (Float64, io, model)
535574 model. variable_lower[i] = value
536575 model. variable_upper[i] = value
537576 end
538- _read_til_newline (io)
577+ _read_til_newline (io, model )
539578 end
540579 return
541580end
542581
543582# We ignore jacobian counts for now
544583function _parse_section (io:: IO , :: Val{'k'} , model:: _CacheModel )
545- _read_til_newline (io)
546- for _ in 2 : length (model. variable_lower)
547- _read_til_newline (io)
584+ n = _next (Int, io, model)
585+ _read_til_newline (io, model)
586+ for _ in 1 : n
587+ _ = _next (Int, io, model)
588+ _read_til_newline (io, model)
548589 end
549590 return
550591end
551592
552593function _parse_section (io:: IO , :: Val{'J'} , model:: _CacheModel )
553594 i = _next (Int, io, model) + 1
554595 nnz = _next (Int, io, model)
555- _read_til_newline (io)
596+ _read_til_newline (io, model )
556597 expr = Expr (:call , :+ )
557598 for _ in 1 : nnz
558599 x = _next (Int, io, model)
559600 c = _next (Float64, io, model)
560601 if ! iszero (c)
561602 push! (expr. args, Expr (:call , :* , c, MOI. VariableIndex (x + 1 )))
562603 end
563- _read_til_newline (io)
604+ _read_til_newline (io, model )
564605 end
565606 if length (expr. args) == 1
566607 # Linear part is just zeros
@@ -575,15 +616,15 @@ end
575616function _parse_section (io:: IO , :: Val{'G'} , model:: _CacheModel )
576617 i = _next (Int, io, model) + 1
577618 nnz = _next (Int, io, model)
578- _read_til_newline (io)
619+ _read_til_newline (io, model )
579620 expr = Expr (:call , :+ )
580621 for _ in 1 : nnz
581622 x = _next (Int, io, model)
582623 c = _next (Float64, io, model)
583624 if ! iszero (c)
584625 push! (expr. args, Expr (:call , :* , c, MOI. VariableIndex (x + 1 )))
585626 end
586- _read_til_newline (io)
627+ _read_til_newline (io, model )
587628 end
588629 if length (expr. args) == 1
589630 # Linear part is just zeros
0 commit comments