opl 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|