rulp 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjEyMDI0ZDM1NzVhNWE3NmJkYTk5Yzg3ODQyMjRiMmYxOWFkMDdkZQ==
5
+ data.tar.gz: !binary |-
6
+ YzE0NmE1YzFhNDg1YTI4YjUzNTc3YmY2NzA4NTI3MDE1NGE2MzI2NA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ M2ZhODczODZkYmU4ZGQ0N2RmZGM4MzZiN2ZmYjc3NjhkZjAyOTE0MjE0MWNj
10
+ ZTI1MWJkYzA5YWI0NmQyMjE1NmJjNTZhMGUxYjIyNDA4NzhjZDQzZTBkNzNi
11
+ ZjE3ZjQwZjEyNDI4YWNmYTkwMDk5NzZkYjliYTIxMTczMGJlODY=
12
+ data.tar.gz: !binary |-
13
+ ODhjYmYwOGY1N2E0MDc2OTlmYjBjZTRjN2VmMDk5NjVkOGIyMDM3NDA4ZTU2
14
+ YWE0MWU3ODIwY2VhZmI4NWU0OTcwMDg0ZmJiMzIzNTk2OGIwZDQ2ZTUwMDFh
15
+ ZTIwM2EwZDVmYWMyZjdkYzgwZDZiYWQyYmJmN2Y5ZjYzMjdmNWQ=
data/bin/rulp ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/rulp'
3
+ begin
4
+ load Gem.bin_path('pry', 'pry', ">= 0.9")
5
+ rescue
6
+ require 'irb'
7
+ ARGV.clear # otherwise all script parameters get passed to IRB
8
+ IRB.start
9
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def sum
3
+ self.inject(:+)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ require_relative './array_extensions'
2
+ require_relative './kernel_extensions'
3
+ require_relative './object_extensions'
@@ -0,0 +1,82 @@
1
+ ##
2
+ # Kernel extension to allow numbered LP variables to be initialised dynamically using the following
3
+ # syntax.
4
+ #
5
+ # [Capitalized_varname][lp var type suffix]( integer )
6
+ #
7
+ # This is similar to the syntax defined in the object extensions but allows for numbered
8
+ # suffixes to quickly generate ranges of similar variables.
9
+ #
10
+ # Where lp var type suffix is either _b for binary, _i for integer, or _f for float.
11
+ # I.e
12
+ #
13
+ # Rating_i(5) is the equivalent of Rating_5 (type integer)
14
+ # Is_happy_b(2) is the equivalent of Is_happy_2 (type binary/boolean)
15
+ # ...
16
+ ##
17
+ module Kernel
18
+ alias_method :old_method_missing, :method_missing
19
+ def method_missing(value, *args)
20
+ method_name = "#{value}" rescue ""
21
+ start = method_name[0]
22
+ if (start <= "Z" && start >= "A")
23
+ case method_name[-1]
24
+ when "b"
25
+ method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
26
+ return BV.send(method_name)
27
+ when "i"
28
+ method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
29
+ return IV.send(method_name)
30
+ when "f"
31
+ method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
32
+ return LV.send(method_name)
33
+ end
34
+ end
35
+ old_method_missing(value, *args)
36
+ end
37
+
38
+ def _profile
39
+ start = Time.now
40
+ return_value = yield
41
+ return return_value, Time.now - start
42
+ end
43
+ end
44
+
45
+
46
+ module Kernel
47
+ ##
48
+ # Adds assertion capabilities to ruby.
49
+ # The assert function will raise an error if the inner block returns false.
50
+ # The error will contain the file, line number and source line of the failing assertion.
51
+ ##
52
+ class AssertionException < Exception; end
53
+
54
+ ##
55
+ # Ensure the SCRIPT_LINES global variable exists so that we can access the source of the failed assertion
56
+ ##
57
+ ::SCRIPT_LINES__ = {} unless defined? ::SCRIPT_LINES__
58
+
59
+ ##
60
+ # If assertion returns false we return a new assertion exception with the failing file and line,
61
+ # and attempt to return the failed source if accessible.
62
+ ##
63
+ def assert(truthy=false)
64
+ unless truthy || (block_given? && yield)
65
+ file, line = caller[0].split(":")
66
+ error_message = "Assertion Failed! < #{file}:#{line}:#{ SCRIPT_LINES__[file][line.to_i - 1][0..-2] rescue ''} >"
67
+ raise AssertionException.new(error_message)
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ def given
74
+ @dummy ||= begin
75
+ dummy = {}
76
+ class << dummy
77
+ def [](*args)
78
+ end
79
+ end
80
+ dummy
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ ##
2
+ # Object extension to allow numbered LP variables to be initialised dynamically using the following
3
+ # syntax.
4
+ #
5
+ # [Capitalized_varname][lp var type suffix]
6
+ #
7
+ # Where lp var type suffix is either _b for binary, _i for integer, or _f for float.
8
+ # I.e
9
+ #
10
+ # Rating_i is the equivalent of Rating (type integer)
11
+ # Is_happy_b is the equivalent of Is_happy (type binary/boolean)
12
+ ##
13
+ class << Object
14
+ def const_missing(value)
15
+ method_name = "#{value}" rescue ""
16
+ if (("A".."Z").include?(method_name[0]))
17
+ if(method_name.end_with?("b"))
18
+ BV.send(method_name[0..(method_name[-2] == "_" ? -3 : -2)])
19
+ elsif(method_name.end_with?("i"))
20
+ IV.send(method_name[0..(method_name[-2] == "_" ? -3 : -2)])
21
+ elsif(method_name.end_with?("f"))
22
+ LV.send(method_name[0..(method_name[-2] == "_" ? -3 : -2)])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,57 @@
1
+ ##
2
+ # Basic logger module.
3
+ # Allows logging in the format
4
+ #
5
+ # "This is a string".log(:debug)
6
+ # "Oh no!".log(:error)
7
+ #
8
+ # Log level is set as follows.
9
+ # Rulp::Logger::level = :debug
10
+ #
11
+ ##
12
+ module Rulp
13
+ module Logger
14
+ DEBUG = 5
15
+ INFO = 4
16
+ WARN = 3
17
+ ERROR = 2
18
+ OFF = 1
19
+
20
+ LEVELS = {
21
+ debug: DEBUG,
22
+ info: INFO,
23
+ warn: WARN,
24
+ error: ERROR,
25
+ off: OFF
26
+ }
27
+
28
+ def self.level=(value)
29
+ raise Exception.new("#{value} is not a valid log level") unless LEVELS[value]
30
+ @@level = value
31
+ end
32
+
33
+ def self.level
34
+ @@level || :info
35
+ end
36
+
37
+ def self.log(level, message)
38
+ if(LEVELS[level].to_i <= LEVELS[self.level])
39
+ puts("[#{level}] #{message}")
40
+ end
41
+ end
42
+
43
+ self.level = :info
44
+
45
+ class ::String
46
+ def log(level)
47
+ Logger::log(level, self)
48
+ end
49
+ end
50
+
51
+ class ::Array
52
+ def log(level, sep="\n")
53
+ Logger::log(level, self.join("#{sep}[#{level}] "))
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/rulp.rb ADDED
@@ -0,0 +1,2 @@
1
+ STDOUT.sync = true
2
+ require_relative 'rulp/rulp'
@@ -0,0 +1,17 @@
1
+ ##
2
+ # An LP Expression constraint. A mathematical expression of which the result
3
+ # must be constrained in some way.
4
+ ##
5
+ class Constraint
6
+ def initialize(*constraint_expression)
7
+ @expressions , @constraint_op, @value = constraint_expression
8
+ end
9
+
10
+ def variables
11
+ @expressions.variables
12
+ end
13
+
14
+ def to_s
15
+ return "#{@expressions} #{@constraint_op == :== ? "=" : @constraint_op} #{@value}"
16
+ end
17
+ end
@@ -0,0 +1,112 @@
1
+ ##
2
+ # An LP Expression. A mathematical expression.
3
+ # Can be a constraint or can be the objective function of a LP or MIP problem.
4
+ ##
5
+ class Expressions
6
+ attr_accessor :expressions
7
+ def initialize(expressions)
8
+ @expressions = expressions
9
+ end
10
+
11
+ def +(other)
12
+ return Expressions.new(@expressions + [other])
13
+ end
14
+
15
+ def to_s
16
+ @expressions.map{|expression|
17
+ [expression.operand < 0 ? " " : " + ", expression.to_s]
18
+ }.flatten[1..-1].join("")
19
+ end
20
+
21
+ def variables
22
+ @expressions.map(&:variable)
23
+ end
24
+
25
+ [:==, :<, :<=, :>, :>=].each do |constraint_type|
26
+ define_method(constraint_type){|value|
27
+ Constraint.new(self, constraint_type, value)
28
+ }
29
+ end
30
+
31
+ def -@
32
+ -self.expressions[0]
33
+ self
34
+ end
35
+
36
+ def -(other)
37
+ other = -other
38
+ self + other
39
+ end
40
+
41
+ def +(other)
42
+ Expressions.new(self.expressions + Expressions[other].expressions)
43
+ end
44
+
45
+ def self.[](value)
46
+ return Expressions.new([Fragment.new(value, 1)]) if value.kind_of?(LV)
47
+ return Expressions.new([value]) if value.kind_of?(Fragment)
48
+ return value if value.kind_of?(Expressions)
49
+ end
50
+
51
+ def evaluate
52
+ self.expressions.map(&:evaluate).inject(:+)
53
+ end
54
+ end
55
+
56
+ ##
57
+ # An expression fragment. An expression can consist of many fragments.
58
+ ##
59
+ class Fragment
60
+ attr_accessor :lv, :operand
61
+
62
+ def initialize(lv, operand)
63
+ @lv = lv
64
+ @operand = operand
65
+ end
66
+
67
+ def +(other)
68
+ return Expressions.new([self] + Expressions[other].expressions)
69
+ end
70
+
71
+ def -(other)
72
+ self.+(-other)
73
+ end
74
+
75
+ def *(value)
76
+ Fragment.new(@lv, @operand * value)
77
+ end
78
+
79
+ def evaluate
80
+ if [TrueClass,FalseClass].include? @lv.value.class
81
+ @operand * (@lv.value ? 1 : 0)
82
+ else
83
+ @operand * @lv.value
84
+ end
85
+ end
86
+
87
+ def -@
88
+ @operand = -@operand
89
+ self
90
+ end
91
+
92
+ def variable
93
+ @lv
94
+ end
95
+
96
+ [:==, :<, :<=, :>, :>=].each do |constraint_type|
97
+ define_method(constraint_type){|value|
98
+ Constraint.new(Expressions.new(self), constraint_type, value)
99
+ }
100
+ end
101
+
102
+ def to_s
103
+ case @operand
104
+ when -1
105
+ "-#{@lv}"
106
+ when 1
107
+ "#{@lv}"
108
+ else
109
+ "#{@operand} #{@lv}"
110
+ end
111
+ end
112
+ end
data/lib/rulp/lv.rb ADDED
@@ -0,0 +1,74 @@
1
+ ##
2
+ # An LP Variable. Used as arguments in LP Expressions.
3
+ # The subtypes BV and IV represent Binary and Integer variables.
4
+ # These are constructed dynamically by using the special Capitalised variable declaration syntax.
5
+ ##
6
+ class LV
7
+ attr_reader :name
8
+ attr_accessor :lt, :lte, :gt, :gte, :value
9
+ include Rulp::Bounds
10
+ include Rulp::Initializers
11
+
12
+ def to_proc
13
+ ->(index){ send(self.meth(index)) }
14
+ end
15
+
16
+ def meth(index)
17
+ "#{self.name}#{index}_#{self.suffix}"
18
+ end
19
+
20
+ def suffix
21
+ "f"
22
+ end
23
+
24
+ def self.method_missing(name, *args)
25
+ return self.definition( "#{name}#{args.join("_")}" )
26
+ end
27
+
28
+ def self.const_missing(name)
29
+ return self.definition(name)
30
+ end
31
+
32
+ def self.definition(name)
33
+ self.class.send(:define_method, name){
34
+ LV::names_table["#{name}#{self}"] if LV::names_table["#{name}#{self}"].class == self
35
+ }
36
+ return self.send(name) || self.new(name)
37
+ end
38
+
39
+ def * (numeric)
40
+ self.nocoerce
41
+ Expressions.new([Fragment.new(self, numeric)])
42
+ end
43
+
44
+ def -@
45
+ return self * -1
46
+ end
47
+
48
+ def -(other)
49
+ self + (-other)
50
+ end
51
+
52
+ def + (expressions)
53
+ Expressions[self] + Expressions[expressions]
54
+ end
55
+
56
+ def value
57
+ if self.class == BV
58
+ return @value.round(2) == 1
59
+ else
60
+ @value
61
+ end
62
+ end
63
+ end
64
+
65
+ class BV < LV;
66
+ def suffix
67
+ "b"
68
+ end
69
+ end
70
+ class IV < LV;
71
+ def suffix
72
+ "i"
73
+ end
74
+ end
data/lib/rulp/rulp.rb ADDED
@@ -0,0 +1,134 @@
1
+ require_relative "rulp_bounds"
2
+ require_relative "rulp_initializers"
3
+ require_relative "lv"
4
+ require_relative "constraint"
5
+ require_relative "expression"
6
+
7
+ require_relative "../solvers/solvers"
8
+ require_relative "../extensions/extensions"
9
+ require_relative "../helpers/log"
10
+
11
+ require 'set'
12
+
13
+ GLPK = "glpsol"
14
+ SCIP = "scip"
15
+ CBC = "cbc"
16
+
17
+ module Rulp
18
+ attr_accessor :expressions
19
+ MIN = "Minimize"
20
+ MAX = "Maximize"
21
+
22
+ GLPK = ::GLPK
23
+ SCIP = ::SCIP
24
+ CBC = ::CBC
25
+
26
+ SOLVERS = {
27
+ "glpsol" => Glpk,
28
+ "scip" => Scip,
29
+ "cbc" => Cbc
30
+ }
31
+
32
+ def self.Glpk(lp)
33
+ lp.solve_with(GLPK)
34
+ end
35
+
36
+ def self.Cbc(lp)
37
+ lp.solve_with(CBC)
38
+ end
39
+
40
+ def self.Scip(lp)
41
+ lp.solve_with(SCIP)
42
+ end
43
+
44
+ def self.Max(objective_expression)
45
+ "Creating maximization problem".log :info
46
+ Problem.new(Rulp::MAX, objective_expression)
47
+ end
48
+
49
+ def self.Min(objective_expression)
50
+ "Creating minimization problem".log :info
51
+ Problem.new(Rulp::MIN, objective_expression)
52
+ end
53
+
54
+ class Problem
55
+ def initialize(objective, objective_expression)
56
+ @objective = objective
57
+ @variables = Set.new
58
+ @objective_expression = objective_expression.kind_of?(LV) ? 1 * objective_expression : objective_expression
59
+ @variables.merge(@objective_expression.variables)
60
+ @constraints = []
61
+ end
62
+
63
+ def [](*constraints)
64
+ @constraints.concat(constraints)
65
+ @variables.merge(constraints.map(&:variables).flatten)
66
+ self
67
+ end
68
+
69
+ def constraints
70
+ @constraints.map.with_index{|constraint, i|
71
+ " c#{i}: #{constraint}"
72
+ }.join("\n")
73
+ end
74
+
75
+ def integers
76
+ ints = @variables.select{|x| x.kind_of?(IV) }.join(" ")
77
+ return "\nGeneral\n #{ints}" if(ints.length > 0)
78
+ end
79
+
80
+ def bits
81
+ bits = @variables.select{|x| x.kind_of?(BV) }.join(" ")
82
+ return "\nBinary\n #{bits}" if(bits.length > 0)
83
+ end
84
+
85
+ def bounds
86
+ @variables.map{|var|
87
+ next unless var.bounds_str
88
+ " #{var.bounds_str}"
89
+ }.compact.join("\n")
90
+ end
91
+
92
+ def get_output_filename
93
+ "/tmp/rulp-#{Random.rand(0..1000)}.lp"
94
+ end
95
+
96
+ def output(filename)
97
+ IO.write(filename, self)
98
+ end
99
+
100
+ def solve_with(type, open_definition=false, open_solution=false)
101
+ filename = get_output_filename
102
+ solver = SOLVERS[type].new(filename)
103
+
104
+ "Writing problem".log(:info)
105
+ IO.write(filename, self)
106
+
107
+ `open #{filename}` if open_definition
108
+
109
+ "Solving problem".log(:info)
110
+ _, time = _profile{ solver.solve(open_solution) }
111
+
112
+ "Solver took #{time}".log(:info)
113
+
114
+ "Parsing result".log(:info)
115
+ solver.store_results(@variables)
116
+
117
+ result = @objective_expression.evaluate
118
+
119
+ "Objective: #{result}\n#{@variables.map{|v|[v.name, "=", v.value].join(' ') if v.value}.compact.join("\n")}".log(:debug)
120
+ return result
121
+ end
122
+
123
+ def to_s
124
+ "\
125
+ #{@objective}
126
+ obj: #{@objective_expression}
127
+ Subject to
128
+ #{constraints}
129
+ Bounds
130
+ #{bounds}#{integers}#{bits}
131
+ End"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,65 @@
1
+ module Rulp
2
+ module Bounds
3
+
4
+ attr_accessor :const
5
+
6
+ DIRS = {">" => {value: "gt=", equality: "gte="}, "<" => {value: "lt=", equality: "lte="}}
7
+ DIRS_REVERSED = {">" => DIRS["<"], "<" => DIRS[">"]}
8
+
9
+ def relative_constraint direction, value, equality=false
10
+ direction = coerced? ? DIRS_REVERSED[direction] : DIRS[direction]
11
+ self.const = false
12
+ self.send(direction[:value], value)
13
+ self.send(direction[:equality], equality)
14
+ return self
15
+ end
16
+
17
+ def nocoerce
18
+ @@coerced = false
19
+ end
20
+
21
+ def coerced?
22
+ was_coerced = @@coerced rescue nil
23
+ @@coerced = false
24
+ return was_coerced
25
+ end
26
+
27
+ def >(val)
28
+ relative_constraint(">", val)
29
+ end
30
+
31
+ def >=(val)
32
+ relative_constraint(">", val, true)
33
+ end
34
+
35
+ def <(val)
36
+ relative_constraint("<", val)
37
+ end
38
+
39
+ def <=(val)
40
+ relative_constraint("<", val, true)
41
+ end
42
+
43
+ def ==(val)
44
+ self.const = val
45
+ end
46
+
47
+ def coerce(something)
48
+ @@coerced = true
49
+ return self, something
50
+ end
51
+
52
+ def bounds_str
53
+ return nil if !(self.gt || self.lt || self.const)
54
+ return "#{self.name} = #{self.const}" if self.const
55
+
56
+ [
57
+ self.gt,
58
+ self.gt ? "<=" : nil,
59
+ self.name,
60
+ self.lt ? "<=" : nil,
61
+ self.lt
62
+ ].compact.join(" ")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,24 @@
1
+ module Rulp
2
+ module Initializers
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
6
+ @name = name
7
+ end
8
+
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ def names_table
15
+ @@names ||= {}
16
+ end
17
+ end
18
+
19
+ def to_s
20
+ "#{self.name}"
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,24 @@
1
+ class Cbc < Solver
2
+ def solve(open_solution=false)
3
+ `#{executable} #{@filename} branch solution #{@outfile}`
4
+ `open #{@outfile}` if open_solution
5
+ end
6
+
7
+ def executable
8
+ :cbc
9
+ end
10
+
11
+ def store_results(variables)
12
+ rows = IO.read(@outfile).split("\n")
13
+ objective = rows[0].split(/\s+/)[-1].to_f
14
+ vars_by_name = {}
15
+ rows[1..-1].each do |row|
16
+ cols = row.strip.split(/\s+/)
17
+ vars_by_name[cols[1].to_s] = cols[2].to_f
18
+ end
19
+ variables.each do |var|
20
+ var.value = vars_by_name[var.name.to_s].to_f
21
+ end
22
+ return objective
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ class Glpk < Solver
2
+ def solve(open_solution=false)
3
+ `#{executable} --lp #{@filename} --write #{@outfile}`
4
+ `open #{@outfile}` if open_solution
5
+ end
6
+
7
+ def executable
8
+ :glpsol
9
+ end
10
+
11
+ def store_results(variables)
12
+ rows = IO.read(@outfile).split("\n")
13
+ variables.zip(rows[-variables.count..-1]).each do |var, row|
14
+ cols = row.split(" ")
15
+ var.value = cols[[1, cols.count - 1].min].to_f
16
+ end
17
+ return rows[1].split(" ")[-1]
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ class Scip < Solver
2
+ def solve(open_solution=false)
3
+ `#{executable} -f #{@filename} > #{@outfile}`
4
+ `open #{@outfile}` if open_solution
5
+ end
6
+
7
+ def executable
8
+ :scip
9
+ end
10
+
11
+ def store_results(variables)
12
+ results = IO.read(@outfile)
13
+ start = results.sub(/.*?primal solution:.*?=+/m, "")
14
+ stripped = start.sub(/Statistics.+/m, "").strip
15
+ rows = stripped.split("\n")
16
+
17
+ objective = rows[0].split(/\s+/)[-1].to_f
18
+
19
+ vars_by_name = {}
20
+ rows[1..-1].each do |row|
21
+ cols = row.strip.split(/\s+/)
22
+ vars_by_name[cols[0].to_s] = cols[1].to_f
23
+ end
24
+ variables.each do |var|
25
+ var.value = vars_by_name[var.name.to_s].to_f
26
+ end
27
+
28
+ return objective
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ class Solver
2
+ def initialize(filename)
3
+ @filename = filename
4
+ @outfile = "/tmp/#{executable}-output.txt"
5
+ raise Exception.new("Couldn't find solver #{executable}!") if `which #{executable}`.length == 0
6
+ @solver_exists = true
7
+ end
8
+
9
+ def store_results(variables)
10
+ puts "Not yet implemented"
11
+ end
12
+
13
+ def solver_exists?
14
+ @solver_exists || false
15
+ end
16
+ end
17
+
18
+ require_relative 'cbc'
19
+ require_relative 'scip'
20
+ require_relative 'glpk'
@@ -0,0 +1,44 @@
1
+ require_relative 'test_helper'
2
+ ##
3
+ #
4
+ # Given 50 items of varying prices
5
+ # Get the minimal sum of 10 items that equals at least $15 dollars
6
+ #
7
+ ##
8
+ class BooleanTest < Minitest::Test
9
+ def setup
10
+ @items = 30.times.map(&Shop_Item_b)
11
+ items_count = @items.sum
12
+ items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum
13
+
14
+ @problem =
15
+ Rulp::Min( items_costs ) [
16
+ items_count >= 10,
17
+ items_costs >= 15
18
+ ]
19
+ end
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
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ gem "minitest"
2
+
3
+ require_relative "../lib/rulp"
4
+ require "minitest/autorun"
5
+
6
+ Rulp::Logger::level = :off
@@ -0,0 +1,50 @@
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 SimpleTest < Minitest::Test
15
+ def setup
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 ) [
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_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
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rulp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Wouter Coppieters
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple Ruby LP description DSL
14
+ email: wc@pico.net.nz
15
+ executables:
16
+ - rulp
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/rulp
21
+ - lib/extensions/array_extensions.rb
22
+ - lib/extensions/extensions.rb
23
+ - lib/extensions/kernel_extensions.rb
24
+ - lib/extensions/object_extensions.rb
25
+ - lib/helpers/log.rb
26
+ - lib/rulp.rb
27
+ - lib/rulp/constraint.rb
28
+ - lib/rulp/expression.rb
29
+ - lib/rulp/lv.rb
30
+ - lib/rulp/rulp.rb
31
+ - lib/rulp/rulp_bounds.rb
32
+ - lib/rulp/rulp_initializers.rb
33
+ - lib/solvers/cbc.rb
34
+ - lib/solvers/glpk.rb
35
+ - lib/solvers/scip.rb
36
+ - lib/solvers/solvers.rb
37
+ - test/test_boolean.rb
38
+ - test/test_helper.rb
39
+ - test/test_simple.rb
40
+ homepage:
41
+ licenses: []
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 2.3.0
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Ruby Linear Programming
63
+ test_files:
64
+ - test/test_boolean.rb
65
+ - test/test_helper.rb
66
+ - test/test_simple.rb
67
+ has_rdoc: