opl 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/opl.rb +126 -32
  2. 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
- #summing of variables
8
- #e.g. x1 + x1 <= 3
9
- #need to be able to handle arithmetic operations
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
- #float coefficients and constants (does rglpk even handle this?)
20
- #will have to figure out "<" and ">"
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(/\d+[^a-z^\[^\]^\d^\.^\)]/)
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(/\d+/)[0]
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 = constants_results[:formatted]
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.to_i}.inject("+").to_s
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].map{|c|c.to_i}.inject("+").to_s
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
- text = text.gsub(rhs, new_rhs)
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 subject_to(constraints)
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].to_i
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]).to_i - 1
534
+ upper_bound = (value.split("<")[1]).to_f - 1
456
535
  elsif value.include?(">")
457
536
  negate = true
458
- bound = (value.split(">")[1].to_i).to_i + 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
- pairs << VariableCoefficientPair.new(var, coef)
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.map{|vcp|vcp.variable}
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.to_i}
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.to_i}}.flatten)
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
- cols.each do |c|
544
- answer[c.name] = c.get_prim.to_s
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: