rulp 0.0.2

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