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.
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: