rulp 0.0.2 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjEyMDI0ZDM1NzVhNWE3NmJkYTk5Yzg3ODQyMjRiMmYxOWFkMDdkZQ==
4
+ Yzk4NWFlMmYwMGQ0ZDFjZTUxMmEwNzlkNDAwZmQ4MDU3YWY2Zjg5Mg==
5
5
  data.tar.gz: !binary |-
6
- YzE0NmE1YzFhNDg1YTI4YjUzNTc3YmY2NzA4NTI3MDE1NGE2MzI2NA==
6
+ MDI3Y2E5MjdhNTY2M2FiZDBhZGMzZTg4MzU2ZDRiMzgyOGFmZTIzMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- M2ZhODczODZkYmU4ZGQ0N2RmZGM4MzZiN2ZmYjc3NjhkZjAyOTE0MjE0MWNj
10
- ZTI1MWJkYzA5YWI0NmQyMjE1NmJjNTZhMGUxYjIyNDA4NzhjZDQzZTBkNzNi
11
- ZjE3ZjQwZjEyNDI4YWNmYTkwMDk5NzZkYjliYTIxMTczMGJlODY=
9
+ YTg0OTJlNjhiMGJkNjdlM2U1ZTQ0ZGMyMTkwMTc0ZmI5NjliZjIzM2M3NjE1
10
+ N2VlNTBiNDBjZmIwOTA5MWI2OGEzZjhkYzYyMmFkOTkzNWM5YTg5OWUzNTc3
11
+ ZmUwY2U0ODM0ZmJhNTdiNjY2M2JjZDNhNzI0MTJmZDllMDY4Y2Q=
12
12
  data.tar.gz: !binary |-
13
- ODhjYmYwOGY1N2E0MDc2OTlmYjBjZTRjN2VmMDk5NjVkOGIyMDM3NDA4ZTU2
14
- YWE0MWU3ODIwY2VhZmI4NWU0OTcwMDg0ZmJiMzIzNTk2OGIwZDQ2ZTUwMDFh
15
- ZTIwM2EwZDVmYWMyZjdkYzgwZDZiYWQyYmJmN2Y5ZjYzMjdmNWQ=
13
+ MzU4YjQxNzkzNTRlMmU0NzMyNTIyNGQ0NmE1ODM1YzU0NmYyYzFiZmM2YzI1
14
+ YmI2MTFkNWZmMzJjZWFmZjY0ZTAzODVmNTEwZTk5MDlkYjBhMmZhNzg1NzZm
15
+ NzFhNTllMTM1MTIwNmJmMDk3MGYyNTRmZGY5ZGMyM2U5YWI2ZjA=
@@ -1,3 +1,5 @@
1
1
  require_relative './array_extensions'
2
2
  require_relative './kernel_extensions'
3
- require_relative './object_extensions'
3
+ require_relative './object_extensions'
4
+ require_relative './file_extensions'
5
+ require_relative './os_extensions'
@@ -0,0 +1,9 @@
1
+ def choose_file
2
+ case os
3
+ when :macosx
4
+ command = "osascript -e 'set the_file to choose file name with prompt \"Select an output file\"' -e 'set the result to POSIX path of the_file'"
5
+ File.absolute_path(`#{command}`.strip)
6
+ else
7
+ File.absolute_path(gets)
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ def os
2
+ @os ||= (
3
+ require "rbconfig"
4
+ host_os = RbConfig::CONFIG['host_os'].downcase
5
+
6
+ case host_os
7
+ when /linux/
8
+ :linux
9
+ when /darwin|mac os/
10
+ :macosx
11
+ when /mswin|msys|mingw32/
12
+ :windows
13
+ when /cygwin/
14
+ :cygwin
15
+ when /solaris|sunos/
16
+ :solaris
17
+ when /bsd/
18
+ :bsd
19
+ when /aix/
20
+ :aix
21
+ else
22
+ raise Error, "unknown os: #{host_os.inspect}"
23
+ end
24
+ )
25
+ end
data/lib/helpers/log.rb CHANGED
@@ -26,7 +26,7 @@ module Rulp
26
26
  }
27
27
 
28
28
  def self.level=(value)
29
- raise Exception.new("#{value} is not a valid log level") unless LEVELS[value]
29
+ raise StandardError.new("#{value} is not a valid log level") unless LEVELS[value]
30
30
  @@level = value
31
31
  end
32
32
 
data/lib/rulp.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  STDOUT.sync = true
2
- require_relative 'rulp/rulp'
2
+ require_relative 'rulp/rulp'
@@ -12,6 +12,19 @@ class Constraint
12
12
  end
13
13
 
14
14
  def to_s
15
- return "#{@expressions} #{@constraint_op == :== ? "=" : @constraint_op} #{@value}"
15
+ return "#{@expressions} #{constraint_op} #{@value}"
16
+ end
17
+
18
+ def constraint_op
19
+ case "#{@constraint_op}"
20
+ when "=="
21
+ "="
22
+ when "<"
23
+ "<="
24
+ when ">"
25
+ ">="
26
+ else
27
+ @constraint_op
28
+ end
16
29
  end
17
30
  end
data/lib/rulp/lv.rb CHANGED
@@ -13,8 +13,8 @@ class LV
13
13
  ->(index){ send(self.meth(index)) }
14
14
  end
15
15
 
16
- def meth(index)
17
- "#{self.name}#{index}_#{self.suffix}"
16
+ def meth(*args)
17
+ "#{self.name}#{args.join("_")}_#{self.suffix}"
18
18
  end
19
19
 
20
20
  def suffix
@@ -31,7 +31,15 @@ class LV
31
31
 
32
32
  def self.definition(name)
33
33
  self.class.send(:define_method, name){
34
- LV::names_table["#{name}#{self}"] if LV::names_table["#{name}#{self}"].class == self
34
+ defined = LV::names_table["#{name}"]
35
+ if defined && defined.class != self
36
+ raise StandardError.new("ERROR:\n#{name} was already defined as a variable of type #{defined.class}."+
37
+ "You are trying to redefine it as a variable of type #{self}")
38
+ elsif(!defined)
39
+ self.new(name)
40
+ else
41
+ defined
42
+ end
35
43
  }
36
44
  return self.send(name) || self.new(name)
37
45
  end
@@ -54,12 +62,19 @@ class LV
54
62
  end
55
63
 
56
64
  def value
65
+ return nil unless @value
57
66
  if self.class == BV
58
67
  return @value.round(2) == 1
68
+ elsif self.class == IV
69
+ return @value
59
70
  else
60
71
  @value
61
72
  end
62
73
  end
74
+
75
+ def inspect
76
+ "#{name}(#{suffix})[#{value || 'undefined'}]"
77
+ end
63
78
  end
64
79
 
65
80
  class BV < LV;
data/lib/rulp/rulp.rb CHANGED
@@ -24,21 +24,21 @@ module Rulp
24
24
  CBC = ::CBC
25
25
 
26
26
  SOLVERS = {
27
- "glpsol" => Glpk,
28
- "scip" => Scip,
29
- "cbc" => Cbc
27
+ GLPK => Glpk,
28
+ SCIP => Scip,
29
+ CBC => Cbc
30
30
  }
31
31
 
32
32
  def self.Glpk(lp)
33
- lp.solve_with(GLPK)
33
+ lp.solve_with(GLPK) rescue nil
34
34
  end
35
35
 
36
36
  def self.Cbc(lp)
37
- lp.solve_with(CBC)
37
+ lp.solve_with(CBC) rescue nil
38
38
  end
39
39
 
40
40
  def self.Scip(lp)
41
- lp.solve_with(SCIP)
41
+ lp.solve_with(SCIP) rescue nil
42
42
  end
43
43
 
44
44
  def self.Max(objective_expression)
@@ -51,6 +51,12 @@ module Rulp
51
51
  Problem.new(Rulp::MIN, objective_expression)
52
52
  end
53
53
 
54
+ def self.solver_exists?(solver_name)
55
+ solver = solver_name[0].upcase + solver_name[1..-1].downcase
56
+ solver_class = ObjectSpace.const_defined?(solver) && ObjectSpace.const_get(solver)
57
+ solver_class.exists? if(solver_class)
58
+ end
59
+
54
60
  class Problem
55
61
  def initialize(objective, objective_expression)
56
62
  @objective = objective
@@ -66,10 +72,32 @@ module Rulp
66
72
  self
67
73
  end
68
74
 
75
+ def solve
76
+ Rulp.send(self.solver, self)
77
+ end
78
+
79
+ def method_missing(method_name)
80
+ self.call(method_name)
81
+ end
82
+
83
+ def solver(solver=nil)
84
+ solver = solver || ENV["SOLVER"] || "Scip"
85
+ solver = solver[0].upcase + solver[1..-1].downcase
86
+ end
87
+
88
+ def call(using=nil)
89
+ Rulp.send(self.solver(using), self)
90
+ end
91
+
69
92
  def constraints
70
- @constraints.map.with_index{|constraint, i|
93
+ constraints_str = @constraints.map.with_index{|constraint, i|
71
94
  " c#{i}: #{constraint}"
72
- }.join("\n")
95
+ }.join("\n").strip
96
+ if constraints_str.empty?
97
+ "0 #{@variables.first} = 0"
98
+ else
99
+ " #{constraints_str}"
100
+ end
73
101
  end
74
102
 
75
103
  def integers
@@ -84,8 +112,8 @@ module Rulp
84
112
 
85
113
  def bounds
86
114
  @variables.map{|var|
87
- next unless var.bounds_str
88
- " #{var.bounds_str}"
115
+ next unless var.bounds
116
+ " #{var.bounds}"
89
117
  }.compact.join("\n")
90
118
  end
91
119
 
@@ -93,7 +121,7 @@ module Rulp
93
121
  "/tmp/rulp-#{Random.rand(0..1000)}.lp"
94
122
  end
95
123
 
96
- def output(filename)
124
+ def output(filename=choose_file)
97
125
  IO.write(filename, self)
98
126
  end
99
127
 
@@ -120,15 +148,22 @@ module Rulp
120
148
  return result
121
149
  end
122
150
 
151
+ def inspect
152
+ to_s
153
+ end
154
+
123
155
  def to_s
124
- "\
125
- #{@objective}
126
- obj: #{@objective_expression}
127
- Subject to
128
- #{constraints}
129
- Bounds
130
- #{bounds}#{integers}#{bits}
131
- End"
156
+ %Q(
157
+ #{' '*0}#{@objective}
158
+ #{' '*0} obj: #{@objective_expression}
159
+ #{' '*0}Subject to
160
+ #{' '*0}#{constraints}
161
+ #{' '*0}Bounds
162
+ #{' '*0}#{bounds}#{integers}#{bits}
163
+ #{' '*0}End
164
+ )
132
165
  end
166
+
167
+ alias_method :save, :output
133
168
  end
134
169
  end
@@ -49,7 +49,7 @@ module Rulp
49
49
  return self, something
50
50
  end
51
51
 
52
- def bounds_str
52
+ def bounds
53
53
  return nil if !(self.gt || self.lt || self.const)
54
54
  return "#{self.name} = #{self.const}" if self.const
55
55
 
@@ -1,8 +1,8 @@
1
1
  module Rulp
2
2
  module Initializers
3
3
  def initialize(name)
4
- raise Exception.new("Variable with the name #{name} of a different type (#{LV::names_table[name].class}) already exists") if LV::names_table[name]
5
- LV::names_table["#{name}#{self.class}"] = self
4
+ raise StandardError.new("Variable with the name #{name} of a different type (#{LV::names_table[name].class}) already exists") if LV::names_table["#{name}"]
5
+ LV::names_table["#{name}"] = self
6
6
  @name = name
7
7
  end
8
8
 
@@ -14,6 +14,10 @@ module Rulp
14
14
  def names_table
15
15
  @@names ||= {}
16
16
  end
17
+
18
+ def clear
19
+ @@names = {}
20
+ end
17
21
  end
18
22
 
19
23
  def to_s
data/lib/solvers/cbc.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  class Cbc < Solver
2
2
  def solve(open_solution=false)
3
- `#{executable} #{@filename} branch solution #{@outfile}`
3
+ system("#{executable} #{@filename} branch solution #{@outfile}")
4
4
  `open #{@outfile}` if open_solution
5
5
  end
6
6
 
7
- def executable
7
+ def self.executable
8
8
  :cbc
9
9
  end
10
10
 
data/lib/solvers/glpk.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  class Glpk < Solver
2
2
  def solve(open_solution=false)
3
- `#{executable} --lp #{@filename} --write #{@outfile}`
3
+ system("#{executable} --lp #{@filename} --write #{@outfile}")
4
4
  `open #{@outfile}` if open_solution
5
5
  end
6
6
 
7
- def executable
7
+ def self.executable
8
8
  :glpsol
9
9
  end
10
10
 
data/lib/solvers/scip.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  class Scip < Solver
2
- def solve(open_solution=false)
3
- `#{executable} -f #{@filename} > #{@outfile}`
4
- `open #{@outfile}` if open_solution
5
- end
6
2
 
7
- def executable
3
+ def self.executable
8
4
  :scip
9
5
  end
10
6
 
7
+ def solve(open_solution=false)
8
+ system("rm #{@outfile}; #{executable} -f #{@filename} -l #{@outfile}")
9
+ `open #{@outfile}` if open_solution
10
+ end
11
+
11
12
  def store_results(variables)
12
13
  results = IO.read(@outfile)
13
14
  start = results.sub(/.*?primal solution:.*?=+/m, "")
@@ -2,7 +2,7 @@ class Solver
2
2
  def initialize(filename)
3
3
  @filename = filename
4
4
  @outfile = "/tmp/#{executable}-output.txt"
5
- raise Exception.new("Couldn't find solver #{executable}!") if `which #{executable}`.length == 0
5
+ raise StandardError.new("Couldn't find solver #{executable}!") if `which #{executable}`.length == 0
6
6
  @solver_exists = true
7
7
  end
8
8
 
@@ -10,9 +10,17 @@ class Solver
10
10
  puts "Not yet implemented"
11
11
  end
12
12
 
13
+ def executable
14
+ self.class.executable
15
+ end
16
+
13
17
  def solver_exists?
14
18
  @solver_exists || false
15
19
  end
20
+
21
+ def self.exists?
22
+ return `which #{self.executable}`.length != 0
23
+ end
16
24
  end
17
25
 
18
26
  require_relative 'cbc'
@@ -0,0 +1,58 @@
1
+ require_relative 'test_helper'
2
+
3
+ class BasicSuite < Minitest::Test
4
+
5
+ def test_single_binary_var
6
+ each_solver do |solver|
7
+ assert_equal X_b.value, nil
8
+
9
+ # The minimal value for a single binary variable is 0
10
+ Rulp::Min(X_b).(solver)
11
+ assert_equal X_b.value, false
12
+
13
+ # The maximal value for a single binary variable is 1
14
+ Rulp::Max(X_b).(solver)
15
+ assert_equal X_b.value, true
16
+
17
+ # If we set an upper bound this is respected by the solver
18
+ Rulp::Max(X_b)[1 * X_b <= 0].(solver)
19
+ assert_equal X_b.value, false
20
+
21
+ # If we set a lower bound this is respected by the solver
22
+ Rulp::Min(X_b)[1 * X_b >= 1].(solver)
23
+ assert_equal X_b.value, true
24
+ end
25
+ end
26
+
27
+ def test_single_integer_var
28
+ each_solver do |solver|
29
+ assert_equal X_i.value, nil
30
+
31
+ given[ -35 <= X_i <= 35 ]
32
+
33
+ # Integer variables respect integer bounds
34
+ Rulp::Min(X_i).(solver)
35
+ assert_equal X_i.value, -35
36
+
37
+ # Integer variables respect integer bounds
38
+ Rulp::Max(X_i).(solver)
39
+ assert_equal X_i.value, 35
40
+ end
41
+ end
42
+
43
+ def test_single_general_var
44
+ each_solver do |solver|
45
+ assert_equal X_f.value, nil
46
+
47
+ given[ -345.4321 <= X_f <= 345.4321 ]
48
+
49
+ # Integer variables respect integer bounds
50
+ Rulp::Min(X_f).(solver)
51
+ assert_equal X_f.value, -345.4321
52
+
53
+ # Integer variables respect integer bounds
54
+ Rulp::Max(X_f).(solver)
55
+ assert_equal X_f.value, 345.4321
56
+ end
57
+ end
58
+ end
data/test/test_boolean.rb CHANGED
@@ -9,36 +9,23 @@ class BooleanTest < Minitest::Test
9
9
  def setup
10
10
  @items = 30.times.map(&Shop_Item_b)
11
11
  items_count = @items.sum
12
- items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum
12
+ @items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum
13
13
 
14
14
  @problem =
15
- Rulp::Min( items_costs ) [
15
+ Rulp::Min( @items_costs ) [
16
16
  items_count >= 10,
17
- items_costs >= 15
17
+ @items_costs >= 15
18
18
  ]
19
19
  end
20
20
 
21
- def test_scip
22
- solution = Rulp::Scip @problem
23
- selected = @items.select(&:value)
24
- assert_equal selected.length, 10
25
- assert_operator solution.round(2), :>=, 15
26
- assert_operator solution, :<=, 25
27
- end
28
-
29
- def test_cbc
30
- solution = Rulp::Cbc @problem
31
- selected = @items.select(&:value)
32
- assert_equal selected.length, 10
33
- assert_operator solution.round(2), :>=, 15
34
- assert_operator solution, :<=, 25
35
- end
36
-
37
- def test_glpk
38
- solution = Rulp::Glpk @problem
39
- selected = @items.select(&:value)
40
- assert_equal selected.length, 10
41
- assert_operator solution.round(2), :>=, 15
42
- assert_operator solution, :<=, 25
21
+ def test_simple
22
+ each_solver do |solver|
23
+ setup
24
+ @problem.send(solver)
25
+ selected = @items.select(&:value)
26
+ assert_equal selected.length, 10
27
+ assert_operator @items_costs.evaluate.round(2), :>=, 15
28
+ assert_operator @items_costs.evaluate, :<=, 25
29
+ end
43
30
  end
44
31
  end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,18 @@
1
- gem "minitest"
2
-
3
1
  require_relative "../lib/rulp"
2
+
3
+ gem "minitest"
4
4
  require "minitest/autorun"
5
5
 
6
6
  Rulp::Logger::level = :off
7
+
8
+
9
+ def each_solver
10
+ [:scip, :cbc, :glpk, :gurobi].each do |solver|
11
+ LV::clear
12
+ if Rulp::solver_exists?(solver)
13
+ yield(solver)
14
+ else
15
+ puts "Couldn't find solver #{solver}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'test_helper'
2
+ # maximize
3
+ # z = 10 * x + 6 * y + 4 * z
4
+ #
5
+ # subject to
6
+ # p: x + y + z <= 100
7
+ # q: 10 * x + 4 * y + 5 * z <= 600
8
+ # r: 2 * x + 2 * y + 6 * z <= 300
9
+ #
10
+ # where all variables are non-negative integers
11
+ # x >= 0, y >= 0, z >= 0
12
+ #
13
+
14
+ class SaveToFile < Minitest::Test
15
+ def setup
16
+ given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ]
17
+ @objective = 10 * X_i + 6 * Y_i + 4 * Z_i
18
+ @problem = Rulp::Max( @objective ) [
19
+ X_i + Y_i + Z_i <= 100,
20
+ 10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
21
+ 2 * X_i + 2 * Y_i + 6 * Z_i <= 300
22
+ ]
23
+ end
24
+
25
+ def test_save
26
+ sample_output_filename = @problem.get_output_filename
27
+ @problem.save(sample_output_filename)
28
+ assert_equal(IO.read(sample_output_filename), "#{@problem}")
29
+ assert_operator "#{@problem}".length, :>=, 100
30
+ end
31
+ end
data/test/test_simple.rb CHANGED
@@ -14,37 +14,22 @@ require_relative 'test_helper'
14
14
  class SimpleTest < Minitest::Test
15
15
  def setup
16
16
  given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ]
17
- @problem =
18
- Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
17
+ @objective = 10 * X_i + 6 * Y_i + 4 * Z_i
18
+ @problem = Rulp::Max( @objective ) [
19
19
  X_i + Y_i + Z_i <= 100,
20
20
  10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
21
21
  2 * X_i + 2 * Y_i + 6 * Z_i <= 300
22
22
  ]
23
23
  end
24
24
 
25
- def test_scip
26
- solution = Rulp::Scip @problem
27
- assert_equal X_i.value, 33
28
- assert_equal Y_i.value, 67
29
- assert_equal Z_i.value, 0
30
- assert_equal solution , 732
31
-
32
- end
33
-
34
- def test_cbc
35
- solution = Rulp::Cbc @problem
36
- assert_equal X_i.value, 33
37
- assert_equal Y_i.value, 67
38
- assert_equal Z_i.value, 0
39
- assert_equal solution , 732
40
-
41
- end
42
-
43
- def test_glpk
44
- solution = Rulp::Glpk @problem
45
- assert_equal X_i.value, 33
46
- assert_equal Y_i.value, 67
47
- assert_equal Z_i.value, 0
48
- assert_equal solution , 732
25
+ def test_simple
26
+ each_solver do |solver|
27
+ setup
28
+ @problem.send(solver)
29
+ assert_equal X_i.value, 33
30
+ assert_equal Y_i.value, 67
31
+ assert_equal Z_i.value, 0
32
+ assert_equal @objective.evaluate , 732
33
+ end
49
34
  end
50
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rulp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-18 00:00:00.000000000 Z
11
+ date: 2015-03-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple Ruby LP description DSL
14
14
  email: wc@pico.net.nz
@@ -20,8 +20,10 @@ files:
20
20
  - bin/rulp
21
21
  - lib/extensions/array_extensions.rb
22
22
  - lib/extensions/extensions.rb
23
+ - lib/extensions/file_extensions.rb
23
24
  - lib/extensions/kernel_extensions.rb
24
25
  - lib/extensions/object_extensions.rb
26
+ - lib/extensions/os_extensions.rb
25
27
  - lib/helpers/log.rb
26
28
  - lib/rulp.rb
27
29
  - lib/rulp/constraint.rb
@@ -34,8 +36,10 @@ files:
34
36
  - lib/solvers/glpk.rb
35
37
  - lib/solvers/scip.rb
36
38
  - lib/solvers/solvers.rb
39
+ - test/test_basic_suite.rb
37
40
  - test/test_boolean.rb
38
41
  - test/test_helper.rb
42
+ - test/test_save_to_file.rb
39
43
  - test/test_simple.rb
40
44
  homepage:
41
45
  licenses: []
@@ -61,7 +65,9 @@ signing_key:
61
65
  specification_version: 4
62
66
  summary: Ruby Linear Programming
63
67
  test_files:
68
+ - test/test_basic_suite.rb
64
69
  - test/test_boolean.rb
65
70
  - test/test_helper.rb
71
+ - test/test_save_to_file.rb
66
72
  - test/test_simple.rb
67
73
  has_rdoc: