opl 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/opl.rb +126 -32
- metadata +1 -1
data/lib/opl.rb
CHANGED
@@ -4,21 +4,27 @@ require "rglpk"
|
|
4
4
|
#1.0
|
5
5
|
|
6
6
|
#2.0
|
7
|
-
#
|
8
|
-
|
9
|
-
#
|
10
|
-
#within a constraint or index
|
11
|
-
#e.g. sum(i in (1..3), x[i-1])
|
7
|
+
#data arrays
|
8
|
+
#setting variable values in constraints
|
9
|
+
#and then using that variable value in further constraints
|
12
10
|
#a matrix representation of the solution if using
|
13
11
|
#sub notation
|
12
|
+
#summing of variables
|
13
|
+
#e.g. x1 + x1 <= 3
|
14
|
+
#object structure
|
14
15
|
|
15
16
|
#3.0
|
17
|
+
#will have to figure out "<" and ">"
|
16
18
|
#make sure extreme cases of foralls and sums
|
17
19
|
#are handled
|
18
20
|
#multiple level sub notation e.g. x[1][[3]]
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
|
22
|
+
#4.0
|
23
|
+
#absolute value: abs()
|
24
|
+
#if --> then statements
|
25
|
+
#or statements
|
26
|
+
#piecewise statements
|
27
|
+
|
22
28
|
#write as module
|
23
29
|
|
24
30
|
def sides(equation)
|
@@ -233,9 +239,9 @@ end
|
|
233
239
|
def coefficients(equation)#parameter is one side of the equation
|
234
240
|
equation = add_ones(equation)
|
235
241
|
if equation[0]=="-"
|
236
|
-
equation.scan(/[+-]\d+/)
|
242
|
+
equation.scan(/[+-][\d\.]+/)
|
237
243
|
else
|
238
|
-
("#"+equation).scan(/[#+-]\d+/).map{|e|e.gsub("#","+")}
|
244
|
+
("#"+equation).scan(/[#+-][\d\.]+/).map{|e|e.gsub("#","+")}
|
239
245
|
end
|
240
246
|
end
|
241
247
|
|
@@ -249,6 +255,9 @@ class LinearProgram
|
|
249
255
|
attr_accessor :constraints
|
250
256
|
attr_accessor :rows
|
251
257
|
attr_accessor :solution
|
258
|
+
attr_accessor :formatted_constraints
|
259
|
+
attr_accessor :rglpk_object
|
260
|
+
attr_accessor :solver
|
252
261
|
|
253
262
|
def initialize(objective, constraints)
|
254
263
|
@objective = objective
|
@@ -287,10 +296,12 @@ end
|
|
287
296
|
class VariableCoefficientPair
|
288
297
|
attr_accessor :variable
|
289
298
|
attr_accessor :coefficient
|
299
|
+
attr_accessor :variable_type
|
290
300
|
|
291
|
-
def initialize(variable, coefficient)
|
301
|
+
def initialize(variable, coefficient, variable_type=1)
|
292
302
|
@variable = variable
|
293
303
|
@coefficient = coefficient
|
304
|
+
@variable_type = variable_type
|
294
305
|
end
|
295
306
|
end
|
296
307
|
|
@@ -310,18 +321,18 @@ def get_constants(text)
|
|
310
321
|
text = text.gsub(" ","")
|
311
322
|
text = text+"#"
|
312
323
|
cs = []
|
313
|
-
potential_constants = text.scan(
|
324
|
+
potential_constants = text.scan(/[\d\.]+[^a-z^\[^\]^\d^\.^\)]/)
|
314
325
|
#potential_constants = text.scan(/\d+[^a-z^\[^\]^\d]/)
|
315
326
|
constants = potential_constants.find_all{|c|![*('a'..'z'),*('A'..'Z')].include?(text[text.index(c)-1])}
|
316
327
|
constants.each do |constant|
|
317
|
-
c = constant.scan(
|
328
|
+
c = constant.scan(/[\d\.]+/)[0]
|
318
329
|
index = text.index(constant)
|
319
330
|
if index == 0
|
320
331
|
c = "+"+c
|
321
332
|
else
|
322
333
|
c = text.scan(/[\-\+]#{constant}/)[0]
|
323
334
|
end
|
324
|
-
cs << c.scan(/[\-\+]\d+/)[0]
|
335
|
+
cs << c.scan(/[\-\+][\d\.]+/)[0]
|
325
336
|
end
|
326
337
|
return({:formatted => cs, :unformatted => constants})
|
327
338
|
end
|
@@ -332,9 +343,16 @@ def put_constants_on_rhs(text)
|
|
332
343
|
text = text.gsub(" ","")
|
333
344
|
s = sides(text)
|
334
345
|
constants_results = get_constants(s[:lhs])
|
335
|
-
constants =
|
346
|
+
constants = []
|
347
|
+
constants_results[:formatted].each_index do |i|
|
348
|
+
formatted_constant = constants_results[:formatted][i]
|
349
|
+
unformatted_constant = constants_results[:unformatted][i]
|
350
|
+
unless unformatted_constant.include?("*")
|
351
|
+
constants << formatted_constant
|
352
|
+
end
|
353
|
+
end
|
336
354
|
unless constants.empty?
|
337
|
-
sum = constants.map{|cc|cc.
|
355
|
+
sum = constants.map{|cc|cc.to_f}.inject("+").to_s
|
338
356
|
if sum.include?("-")
|
339
357
|
sum = sum.gsub("-","+")
|
340
358
|
else
|
@@ -358,7 +376,12 @@ end
|
|
358
376
|
def sum_constants(text)
|
359
377
|
#in: "100+ 10-3"
|
360
378
|
#out: "107"
|
361
|
-
get_constants(text)[:formatted]
|
379
|
+
constants = get_constants(text)[:formatted]
|
380
|
+
if constants.to_s.include?(".")
|
381
|
+
constants.map{|c|c.to_f}.inject("+").to_s
|
382
|
+
else
|
383
|
+
constants.map{|c|c.to_i}.inject("+").to_s
|
384
|
+
end
|
362
385
|
end
|
363
386
|
|
364
387
|
def sub_rhs_with_summed_constants(constraint)
|
@@ -370,11 +393,26 @@ def get_coefficient_variable_pairs(text)
|
|
370
393
|
text.scan(/\d*[\*]*[a-z]\[*\d*\]*/)
|
371
394
|
end
|
372
395
|
|
396
|
+
def operator(constraint)
|
397
|
+
if constraint.include?(">=")
|
398
|
+
">="
|
399
|
+
elsif constraint.include?("<=")
|
400
|
+
"<="
|
401
|
+
elsif constraint.include?(">")
|
402
|
+
">"
|
403
|
+
elsif constraint.include?("<")
|
404
|
+
"<"
|
405
|
+
elsif constraint.include?("=")
|
406
|
+
"="
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
373
410
|
def put_variables_on_lhs(text)
|
374
411
|
#in: "x + y - x[3] <= 3z + 2x[2] - 10"
|
375
412
|
#out: "x + y - x[3] - 3z - 2x[2] <= -10"
|
376
413
|
text = text.gsub(" ", "")
|
377
414
|
s = sides(text.gsub(" ",""))
|
415
|
+
oper = operator(text)
|
378
416
|
rhs = s[:rhs]
|
379
417
|
lhs = s[:lhs]
|
380
418
|
coefficient_variable_pairs = get_coefficient_variable_pairs(rhs)
|
@@ -396,12 +434,13 @@ def put_variables_on_lhs(text)
|
|
396
434
|
end
|
397
435
|
end
|
398
436
|
new_lhs = lhs+add_to_left.join("")
|
399
|
-
text = text.gsub(lhs, new_lhs)
|
437
|
+
text = text.gsub(lhs+oper, new_lhs+oper)
|
400
438
|
new_rhs = rhs
|
401
439
|
remove_from_right.each do |rfr|
|
402
440
|
new_rhs = new_rhs.gsub(rfr, "")
|
403
441
|
end
|
404
|
-
|
442
|
+
new_rhs = "0" if new_rhs == ""
|
443
|
+
text = text.gsub(oper+rhs, oper+new_rhs)
|
405
444
|
return(text)
|
406
445
|
end
|
407
446
|
|
@@ -419,15 +458,54 @@ def split_equals_a(constraints)
|
|
419
458
|
end.flatten
|
420
459
|
end
|
421
460
|
|
422
|
-
def
|
461
|
+
def sum_indices(constraint)
|
462
|
+
pieces_to_sub = constraint.scan(/[a-z]\[\d[\d\+\-]+\]/)
|
463
|
+
pieces_to_sub.each do |piece|
|
464
|
+
characters_to_sum = piece.scan(/[\d\+\-]+/)[0]
|
465
|
+
index_sum = sum_constants(characters_to_sum)
|
466
|
+
new_piece = piece.gsub(characters_to_sum, index_sum)
|
467
|
+
constraint = constraint.gsub(piece, new_piece)
|
468
|
+
end
|
469
|
+
return(constraint)
|
470
|
+
end
|
471
|
+
|
472
|
+
def produce_variable_type_hash(variable_types, all_variables)
|
473
|
+
#in: ["BOOLEAN: x, y", "INTEGER: z"]
|
474
|
+
#out: {:x => 3, :y => 3, :z => 2}
|
475
|
+
variable_type_hash = {}
|
476
|
+
variable_types.each do |vt|
|
477
|
+
type = vt.gsub(" ","").split(":")[0]
|
478
|
+
if type == "BOOLEAN"
|
479
|
+
type_number = 3
|
480
|
+
elsif type == "INTEGER"
|
481
|
+
type_number = 2
|
482
|
+
end
|
483
|
+
variables = vt.split(":")[1].gsub(" ","").split(",")
|
484
|
+
variables.each do |root_var|
|
485
|
+
all_variables_with_root = all_variables.find_all{|var|var.include?("[") && var.split("[")[0]==root_var}+[root_var]
|
486
|
+
all_variables_with_root.each do |var|
|
487
|
+
variable_type_hash[var.to_sym] = type_number
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
variable_type_hash
|
492
|
+
end
|
493
|
+
|
494
|
+
def subject_to(constraints, variable_types=[])
|
423
495
|
constraints = constraints.flatten
|
424
496
|
constraints = split_equals_a(constraints)
|
425
497
|
constraints = constraints.map do |constraint|
|
426
498
|
sub_forall(constraint)
|
427
499
|
end.flatten
|
500
|
+
constraints = constraints.map do |constraint|
|
501
|
+
sum_indices(constraint)
|
502
|
+
end
|
428
503
|
constraints = constraints.map do |constraint|
|
429
504
|
sub_sum(constraint)
|
430
505
|
end
|
506
|
+
constraints = constraints.map do |constraint|
|
507
|
+
sum_indices(constraint)
|
508
|
+
end
|
431
509
|
constraints = constraints.map do |constraint|
|
432
510
|
put_constants_on_rhs(constraint)
|
433
511
|
end
|
@@ -438,6 +516,7 @@ def subject_to(constraints)
|
|
438
516
|
sub_rhs_with_summed_constants(constraint)
|
439
517
|
end
|
440
518
|
all_vars = get_all_vars(constraints)
|
519
|
+
variable_type_hash = produce_variable_type_hash(variable_types, all_vars)
|
441
520
|
rows = []
|
442
521
|
constraints.each do |constraint|
|
443
522
|
negate = false
|
@@ -449,13 +528,13 @@ def subject_to(constraints)
|
|
449
528
|
upper_bound = value.split("<=")[1]
|
450
529
|
elsif value.include?(">=")
|
451
530
|
negate = true
|
452
|
-
bound = value.split(">=")[1].
|
531
|
+
bound = value.split(">=")[1].to_f
|
453
532
|
upper_bound = (bound*-1).to_s
|
454
533
|
elsif value.include?("<")
|
455
|
-
upper_bound = (value.split("<")[1]).
|
534
|
+
upper_bound = (value.split("<")[1]).to_f - 1
|
456
535
|
elsif value.include?(">")
|
457
536
|
negate = true
|
458
|
-
bound = (value.split(">")[1]
|
537
|
+
bound = (value.split(">")[1]).to_f + 1
|
459
538
|
upper_bound = (bound*-1).to_s
|
460
539
|
end
|
461
540
|
coefs = coefficients(sides(value)[:lhs])
|
@@ -478,7 +557,8 @@ def subject_to(constraints)
|
|
478
557
|
pairs = []
|
479
558
|
all_vars.each do |var|
|
480
559
|
coef = coefs[vars.index(var)]
|
481
|
-
|
560
|
+
variable_type = variable_type_hash[var.to_sym] || 1
|
561
|
+
pairs << VariableCoefficientPair.new(var, coef, variable_type)
|
482
562
|
end
|
483
563
|
row.variable_coefficient_pairs = pairs
|
484
564
|
rows << row
|
@@ -519,15 +599,20 @@ def optimize(optimization, objective, rows_c)
|
|
519
599
|
rows[i].set_bounds(Rglpk::GLP_UP, 0.0, row.upper_bound) unless row.upper_bound.nil?
|
520
600
|
rows[i].set_bounds(Rglpk::GLP_LO, 0.0, row.lower_bound) unless row.lower_bound.nil?
|
521
601
|
end
|
522
|
-
vars = rows_c.first.variable_coefficient_pairs
|
602
|
+
vars = rows_c.first.variable_coefficient_pairs
|
523
603
|
cols = p.add_cols(vars.size)
|
604
|
+
solver = "simplex"
|
524
605
|
vars.each_index do |i|
|
525
|
-
column_name = vars[i]
|
606
|
+
column_name = vars[i].variable
|
526
607
|
cols[i].name = column_name
|
527
608
|
cols[i].set_bounds(Rglpk::GLP_LO, 0.0, 0.0)
|
609
|
+
cols[i].kind = vars[i].variable_type#boolean, integer, etc.
|
610
|
+
if vars[i].variable_type != 1
|
611
|
+
solver = "mip"
|
612
|
+
end
|
528
613
|
end
|
529
614
|
all_vars = rows_c.first.variable_coefficient_pairs.map{|vcp|vcp.variable}
|
530
|
-
obj_coefficients = coefficients(objective.gsub(" ","")).map{|c|c.
|
615
|
+
obj_coefficients = coefficients(objective.gsub(" ","")).map{|c|c.to_f}
|
531
616
|
obj_vars = variables(objective.gsub(" ",""))
|
532
617
|
all_obj_coefficients = []
|
533
618
|
all_vars.each do |var|
|
@@ -536,13 +621,22 @@ def optimize(optimization, objective, rows_c)
|
|
536
621
|
all_obj_coefficients << coef
|
537
622
|
end
|
538
623
|
p.obj.coefs = all_obj_coefficients
|
539
|
-
p.set_matrix(rows_c.map{|row|row.variable_coefficient_pairs.map{|vcp|vcp.coefficient.
|
540
|
-
p.simplex
|
541
|
-
lp.objective.optimized_value = p.obj.get + objective_addition.to_f
|
624
|
+
p.set_matrix(rows_c.map{|row|row.variable_coefficient_pairs.map{|vcp|vcp.coefficient.to_f}}.flatten)
|
542
625
|
answer = Hash.new()
|
543
|
-
|
544
|
-
|
626
|
+
p.simplex
|
627
|
+
if solver == "simplex"
|
628
|
+
lp.objective.optimized_value = p.obj.get + objective_addition.to_f
|
629
|
+
cols.each do |c|
|
630
|
+
answer[c.name] = c.get_prim.to_s
|
631
|
+
end
|
632
|
+
elsif solver == "mip"
|
633
|
+
p.mip
|
634
|
+
lp.objective.optimized_value = p.obj.mip + objective_addition.to_f
|
635
|
+
cols.each do |c|
|
636
|
+
answer[c.name] = c.mip_val.to_s
|
637
|
+
end
|
545
638
|
end
|
546
639
|
lp.solution = answer
|
640
|
+
lp.rglpk_object = p
|
547
641
|
lp
|
548
642
|
end
|