lp_select 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lp_select.gemspec
4
+ gemspec
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1,96 @@
1
+ # LpSelect
2
+
3
+
4
+ This gem provides both an FFI interface to the lp_solve library and a simplified interface
5
+ designed to pick from a set of choices to satisfy constraints (LPSelect).
6
+
7
+ lp_solve is a Mixed Integer Linear Programming (MILP) solver. It solves pure linear, (mixed) integer/binary,
8
+ semi-cont and special ordered sets (SOS) models. lp_solve is written in ANSI C and can be compiled on many
9
+ different platforms like Linux and WINDOWS.
10
+
11
+ The lp_solve library is included and dynamically loaded. The gem will look for the library in the
12
+ lib/binaries folder, and searches for the first library it can load in this order:
13
+
14
+ liblpsolve55.dylib
15
+ liblpsolve55.dylib-ppc
16
+ liblpsolve55.dylib.x86-64
17
+ liblpsolve55.so
18
+ liblpsolve55.so-ux64
19
+ lpsolve55.dll
20
+
21
+
22
+ You can download and compile the liblpsolve library from http://sourceforge.net/projects/lpsolve/
23
+
24
+ LPSelect is designed for selecting a set of things that satisfies as many constraints as possible.
25
+ The lp_select_test.rb test is a contrived example showing how it can be used to select a fruit salad
26
+ at the lowest cost that satisfies the most people. The price of each fruit is a weight and the objective
27
+ is to minimize the cost (ie weight) of the selected fruits. Each person's choices is added as a
28
+ constraint row.
29
+
30
+
31
+ ## Installation
32
+
33
+ Add this line to your application's Gemfile:
34
+
35
+ gem 'lp_select'
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install lp_select
44
+
45
+
46
+ ## Usage
47
+
48
+ Current usage is focused on a handful of known problems to be solved, but it is easily extensible.
49
+
50
+ The lp_solve library uses 1 indexed arrays.
51
+
52
+ There is more example usage in the LPSelect library and tests
53
+
54
+ ```ruby
55
+ # Make a three row five column equation
56
+ @lp = LPSolve::make_lp(3, 5)
57
+
58
+ # Set some column names
59
+ LPSolve::set_col_name(@lp, 1, "fred")
60
+ LPSolve::set_col_name(@lp, 2, "bob")
61
+ # Add a constraint and a row name, the API expects a 1 indexed array
62
+ constraint_vars = [0, 0, 1]
63
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
64
+ p.write_array_of_double(constraint_vars)
65
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
66
+ end
67
+ LPSolve::set_row_name(@lp, 1, "onlyBob")
68
+
69
+ # Set the objective function and minimize it
70
+ constraint_vars = [1.0, 3.0]
71
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
72
+ p.write_array_of_double(constraint_vars)
73
+ LPSolve::set_obj_fn(@lp, p)
74
+ end
75
+ LPSolve::set_minim(@lp)
76
+
77
+ # Solve it and retreive the result
78
+ LPSolve::solve(@lp)
79
+ @objective = LPSolve::get_objective(@lp)
80
+
81
+ ```
82
+
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Pull Request
91
+
92
+
93
+ ## Contributors
94
+ * James Prior
95
+ * Jake Sower
96
+ * Mattias Ekberg
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/*_test.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,9 @@
1
+ require "lp_select/version"
2
+
3
+ require 'ffi'
4
+ require 'yaml'
5
+
6
+ module LpSelect
7
+ require 'lp_select/lp_solve.rb'
8
+ require 'lp_select/lp_select.rb'
9
+ end
@@ -0,0 +1,239 @@
1
+
2
+ # -----------------------------------------------
3
+ #
4
+ # This class relies on the lpsolve library hooks, and
5
+ # serves as an interface for solving specialized
6
+ # selection problems. It uses binary variables
7
+ # with assigned names to determine what is selected
8
+ # -----------------------------------------------
9
+
10
+ include LPSolve
11
+
12
+ class LPSelect
13
+
14
+ attr_reader :vars, :lp, :model_name, :results, :objective, :constraints, :objective_row
15
+ CONSTRAINT_ROW = {:name => "", :vars => [], :op => nil, :target => nil} #our own little format
16
+
17
+
18
+ # Create a new selection equation, either by passing a list of variables
19
+ # corresponding to applicants, or by passing a YAML file or string that
20
+ # represents an equation serialized by this same model.
21
+ #
22
+ # Optionally it can load a file from disk in the lp format and solve it,
23
+ # although many of the utilities for inspecting the equation and serializing
24
+ # it will be non-functional
25
+ def initialize(opts = {})
26
+
27
+ # The objective is the overall equation to solve, in our case, the minimum
28
+ # sum of each applicants rank
29
+ @objective_row = {:direction => "min", :weights => {} }
30
+ @constraints = [] #This will be filled with dups of CONSTRAINT_ROW
31
+ @var_struct = nil
32
+
33
+ if opts[:filename] && File::exists?(opts[:filename])
34
+ load_from_file(opts[:filename])
35
+ elsif opts[:vars]
36
+ raise "Invalid option, vars must be an array" unless opts[:vars].is_a? Array
37
+ create_new(opts[:vars])
38
+ elsif opts[:yaml]
39
+ load_from_yaml(opts[:yaml])
40
+ else
41
+ raise "Must pass :filename, :yaml, or :vars"
42
+ end
43
+
44
+ @model_name = Time.now().strftime('%Y%m%d%H%M')
45
+ LPSolve::set_lp_name(@lp, @model_name)
46
+
47
+ LPSolve::set_verbose(@lp, LPSolve::SEVERE )
48
+ end
49
+
50
+ def create_new(varnames)
51
+ self.vars = varnames
52
+ cols = @vars.length
53
+ @lp = LPSolve::make_lp(0, cols)
54
+ 1.upto(cols) do |cnum|
55
+ cname = varnames[cnum-1] #For every column get the column name and index (NOT zero indexed)
56
+ LPSolve::set_binary(@lp, cnum, 1) #Define the column to be binary
57
+ LPSolve::set_col_name(@lp, cnum, cname.to_s.dup) #Set the name to what we passed
58
+ end
59
+ end
60
+
61
+ def load_from_file(filename)
62
+ @lp = LPSolve::read_LP(filename, LPSolve::SEVERE, "")
63
+
64
+ loc_lp = LPSolve::copy_lp(@lp) #We make a copy to avoid advancing internal pointers.
65
+ existing_vars = []
66
+ 1.upto(get_Ncolumns) do |col|
67
+ existing_vars << LPSolve::get_origcol_name(loc_lp, col).to_s
68
+ end
69
+ self.vars = existing_vars
70
+ end
71
+
72
+ # Weights should be a hash with variable names as keys, and
73
+ # individual multipliers as the values. Eg, { "v1" => 10 }
74
+ # would return a row like +10 v1
75
+ def set_objective(weights, direction = :min)
76
+ weights = weights.inject({}) do |options, (key, value)|
77
+ options[key.to_sym] = value
78
+ options
79
+ end
80
+
81
+ obj_fn = [1.0] # placeholder for zero indexed array
82
+ @vars.each do |var|
83
+ weight = weights[var] || 0.0
84
+ obj_fn << weight
85
+ end
86
+
87
+ FFI::MemoryPointer.new(:double, obj_fn.size) do |p|
88
+ p.write_array_of_double(obj_fn)
89
+ LPSolve::set_obj_fn(@lp, p)
90
+ end
91
+
92
+ if direction == :max
93
+ LPSolve::set_maxim(@lp)
94
+ else
95
+ LPSolve::set_minim(@lp)
96
+ end
97
+
98
+ @objective_row = {:direction => direction, :weights => weights }
99
+
100
+ end
101
+
102
+ def solve
103
+
104
+ #Important step.
105
+ solution = LPSolve::solve(@lp)
106
+
107
+ if solution == LPSolve::OPTIMAL || LPSolve::SUBOPTIMAL
108
+ #--------------------
109
+ # Get the values
110
+ #--------------------
111
+ @results = {}
112
+
113
+ retvals = []
114
+ FFI::MemoryPointer.new(:double, @vars.size) do |p|
115
+ if LPSolve::get_variables(@lp, p)
116
+ retvals = p.get_array_of_double(0, @vars.size)
117
+ @vars.each_with_index do |c, idx|
118
+ @results[c] = retvals[idx]
119
+ end
120
+ end
121
+ end
122
+
123
+ #--------------------
124
+ # Set the objective (eg, final sum)
125
+ #--------------------
126
+ @objective = LPSolve::get_objective(@lp)
127
+ end
128
+ return solution
129
+
130
+ end
131
+
132
+
133
+ # This piece is what's used to ensure that the function selects the right amounts from
134
+ # the right categories. For example, to select at least one applicant from Maine,
135
+ # we could add a constraint saying that the sum of all applicant variables
136
+ # representing the applicants from Maine must be greater than or equal to 1.
137
+ def add_constraint(rowdef = CONSTRAINT_ROW)
138
+
139
+ raise "You must specify at least one variable to add a constraint" if rowdef[:vars].length == 0
140
+ raise "You must specify an operation" if rowdef[:op].nil?
141
+ raise "You must specify a target (ie, right hand side)" if rowdef[:target].nil?
142
+ raise "Target must be a number" unless rowdef[:target].is_a?(Float) || rowdef[:target].is_a?(Integer)
143
+
144
+ varnames = rowdef[:vars].map! {|v| v.to_sym}
145
+
146
+ #The API expects a 1 indexed array, so start with an empty item in row_constraints[0]
147
+ row_constraints = [0.0]
148
+ @vars.each do |v|
149
+ # Since we're only interested in binary columns, putting in a 1 or a zero is sufficient
150
+ # to either add them to the constraint or not
151
+ row_constraints << (varnames.include?(v) ? 1.0 : 0.0)
152
+ end
153
+
154
+ FFI::MemoryPointer.new(:double, row_constraints.size) do |p|
155
+ p.write_array_of_double(row_constraints)
156
+ LPSolve::add_constraint(@lp, p, rowdef[:op], rowdef[:target].to_f)
157
+ end
158
+
159
+ # Every row has a name, and it's helpful if it suggests something about the constraint,
160
+ # eg maine or minorities
161
+ if rowdef[:name] == ""
162
+ rowdef[:name] = "R#{@constraints.length+1}"
163
+ end
164
+ @constraints << rowdef
165
+ LPSolve::set_row_name(@lp, @constraints.length, rowdef[:name])
166
+ end
167
+
168
+ def to_file(filename)
169
+ loc_lp = LPSolve::copy_lp(@lp)
170
+ valid = LPSolve::write_lp(loc_lp, filename)
171
+ raise "Could not write to #{filename}" unless valid
172
+ end
173
+
174
+ def to_yaml
175
+ @complete = {}
176
+ @complete["vars"] = @vars
177
+ @complete["model_name"] = @model_name
178
+ @complete["constraints"] = @constraints
179
+ @complete["objective"] = @objective_row unless @objective_row[:weights].empty?
180
+ @complete.to_yaml
181
+ end
182
+
183
+ def load_from_yaml(yaml)
184
+ @complete = YAML::load(yaml)
185
+ raise "could not load from yaml" unless @complete
186
+ create_new(@complete["vars"])
187
+ LPSolve::set_lp_name(@lp, @complete["model_name"])
188
+ @complete["constraints"].each do |c|
189
+ add_constraint(c)
190
+ end
191
+
192
+ if @complete['objective']
193
+ set_objective(@complete['objective'][:weights], @complete['objective'][:direction])
194
+ end
195
+ end
196
+
197
+ def to_lp_format
198
+ lp_text = "/* #{@model_name} */\n\n"
199
+
200
+ lp_text << "/* Objective function */\n"
201
+ lp_text << "#{@objective_row[:direction]}:"
202
+ weights = @objective_row[:weights]
203
+ weights.keys.sort_by{|s| s.to_s}.map{|key| [key, weights[key]] }.each do |var,constant|
204
+ sign = constant > 0 ? "+" : "-"
205
+ if constant.abs == 1
206
+ lp_text << " #{sign}#{var}"
207
+ else
208
+ lp_text << " #{sign}#{constant} #{var}"
209
+ end
210
+ end
211
+ lp_text << ";\n\n"
212
+
213
+ lp_text << "/* Constraints */\n"
214
+ @constraints.each do |row|
215
+ row_vars = row[:vars].map{|x| "+#{x}"}.sort.join(" ")
216
+ row_op = LPSolve.op_to_s(row[:op])
217
+ lp_text << "#{row[:name]}: #{row_vars} #{row_op} #{row[:target]}; \n\n"
218
+ end
219
+
220
+ lp_text << "\n/* Varible Bounds */\n"
221
+ @vars.each do |v|
222
+ lp_text << "#{v} <= 1;\n"
223
+ end
224
+
225
+ lp_text << "\n/* Variable Definitions */\n"
226
+ lp_text << "int #{@vars.join(", ")};"
227
+ return lp_text
228
+ end
229
+
230
+ private
231
+
232
+ def vars=(new_vars)
233
+ @vars = new_vars.collect(&:to_sym)
234
+ end
235
+
236
+ def get_Ncolumns
237
+ @n_columns ||= LPSolve::get_Ncolumns(@lp)
238
+ end
239
+ end
@@ -0,0 +1,154 @@
1
+ # -----------------------------------------------
2
+ #
3
+ # This module uses Ruby's DL library to dynamically load
4
+ # the liblpsolve library, which is a linear equation solver.
5
+ #
6
+ # The sourceforge summary says:
7
+ # Mixed Integer Linear Programming (MILP) solver lp_solve solves pure linear,
8
+ # (mixed) integer/binary, semi-cont and special ordered sets (SOS) models.
9
+ # lp_solve is written in ANSI C and can be compiled on many different platforms
10
+ # like Linux and WINDOWS
11
+ #
12
+ # Read more at http://sourceforge.net/projects/lpsolve/
13
+ #
14
+ # This module mostly serves to load the file and attach to the
15
+ # API functions
16
+ #
17
+ # -----------------------------------------------
18
+
19
+ module LPSolve
20
+ extend FFI::Library
21
+ base = File.expand_path(File.join( File.dirname(__FILE__), "binaries") )
22
+ err = nil
23
+ ["liblpsolve55.so", "liblpsolve55.so-ux64", "liblpsolve55.dylib", "liblpsolve55.dylib.x86-64", "liblpsolve55.dylib-ppc", "lpsolve55.dll"].each do |lib|
24
+ begin
25
+ err = nil
26
+ ffi_lib File.join(base, lib)
27
+ break
28
+ rescue LoadError => e
29
+ err = e
30
+ end
31
+ end
32
+ raise "Could not find suitable liblpsolve55 library #{err}" unless err.nil?
33
+
34
+ #Constants used when assigning constraints
35
+ LE = 1 # <=
36
+ EQ = 3 # ==
37
+ GE = 2 # >=
38
+
39
+ def op_to_s(op_int)
40
+ case op_int
41
+ when LPSelect::GE then ">="
42
+ when LPSelect::LE then "<="
43
+ when LPSelect::EQ then "=="
44
+ end
45
+ end
46
+
47
+ #Constants used for verbosity
48
+ CRITICAL = 1
49
+ SEVERE = 2
50
+ IMPORTANT = 3
51
+ NORMAL = 4
52
+ DETAILED = 5
53
+ FULL = 6
54
+
55
+ #Constants used for solve results
56
+ NOMEMORY = -2
57
+ OPTIMAL = 0
58
+ SUBOPTIMAL = 1
59
+ INFEASIBLE = 2
60
+ UNBOUNDED = 3
61
+ DEGENERATE = 4
62
+ NUMFAILURE = 5
63
+ USERABORT = 6
64
+ TIMEOUT = 7
65
+ PRESOLVED = 9
66
+ PROCFAIL = 10
67
+ PROCBREAK = 11
68
+ FEASFOUND = 12
69
+ NOFEASFOUND = 13
70
+
71
+ typedef :double, :REAL
72
+ typedef :long, :lprec
73
+
74
+
75
+ # All of the function signatures here come from
76
+ # http://lpsolve.sourceforge.net/5.5/lp_solveAPIreference.htm
77
+
78
+ # void lp_solve_version(int *majorversion, int *minorversion, int *release, int *build)
79
+ attach_function :lp_solve_version, [:pointer, :pointer, :pointer, :pointer], :void
80
+
81
+ # lprec *read_LP(char *filename, int verbose, char *lp_name)
82
+ attach_function :read_LP, [:string, :int, :string], :pointer
83
+
84
+ # int solve(lprec *lp)
85
+ attach_function :solve, [:pointer], :int
86
+
87
+ # unsigned char get_variables(lprec *lp, REAL *var);
88
+ attach_function :get_variables, [:pointer, :pointer], :char
89
+
90
+ # int get_Ncolumns(lprec *lp);
91
+ attach_function :get_Ncolumns, [:pointer], :int
92
+
93
+ # char *get_col_name(lprec *lp, int column);
94
+ attach_function :get_col_name, [:pointer, :int], :string
95
+
96
+ # char *get_origcol_name(lprec *lp, int column);
97
+ attach_function :get_origcol_name, [:pointer, :int], :string
98
+
99
+ # lprec *copy_lp(lprec *lp);
100
+ attach_function :copy_lp, [:pointer], :pointer
101
+
102
+ # void print_lp(lprec *lp);
103
+ attach_function :print_lp, [:pointer], :void
104
+
105
+ # lprec *make_lp(int rows, int columns);
106
+ attach_function :make_lp, [:int, :int], :pointer
107
+
108
+ # void delete_lp(lprec *lp);
109
+ attach_function :delete_lp, [:pointer], :void
110
+
111
+ # unsigned char set_binary(lprec *lp, int column, unsigned char must_be_bin);
112
+ attach_function :set_binary, [:pointer, :int, :char], :char
113
+
114
+ # unsigned char set_col_name(lprec *lp, int column, char *new_name);
115
+ attach_function :set_col_name, [:pointer, :int, :string], :char
116
+
117
+ # unsigned char set_obj_fn(lprec *lp, REAL *row);
118
+ attach_function :set_obj_fn, [:pointer, :pointer], :char
119
+
120
+ # unsigned char add_constraint(lprec *lp, REAL *row, int constr_type, REAL rh);
121
+ attach_function :add_constraint, [:pointer, :pointer, :int, :REAL], :char
122
+
123
+ # unsigned char set_row_name(lprec *lp, int row, char *new_name);
124
+ attach_function :set_row_name, [:pointer, :int, :string], :char
125
+
126
+ # REAL get_objective(lprec *lp);
127
+ attach_function :get_objective, [:pointer], :REAL
128
+
129
+ # void set_verbose(lprec *lp, int verbose);
130
+ attach_function :set_verbose, [:pointer, :int], :void
131
+
132
+ # unsigned char set_lp_name(lprec *lp, char *lpname);
133
+ attach_function :set_lp_name, [:pointer, :string], :char
134
+
135
+ # unsigned char write_lp(lprec *lp, char *filename);
136
+ attach_function :write_lp, [:pointer, :string], :char
137
+
138
+ # void set_maxim(lprec *lp);
139
+ attach_function :set_maxim, [:pointer], :void
140
+
141
+ # void set_minim(lprec *lp);
142
+ attach_function :set_minim, [:pointer], :void
143
+
144
+ def self.version
145
+ maj_ptr = FFI::MemoryPointer.new(:pointer, 1)
146
+ min_ptr = FFI::MemoryPointer.new(:pointer, 1)
147
+ rel_ptr = FFI::MemoryPointer.new(:pointer, 1)
148
+ bld_ptr = FFI::MemoryPointer.new(:pointer, 1)
149
+ LPSolve::lp_solve_version(maj_ptr, min_ptr, rel_ptr, bld_ptr)
150
+
151
+ "#{maj_ptr.read_int}.#{min_ptr.read_int}.#{rel_ptr.read_int} build #{bld_ptr.read_int}"
152
+ end
153
+
154
+ end
@@ -0,0 +1,4 @@
1
+ module LpSelect
2
+ # See http://semver.org/
3
+ VERSION = "1.0.0"
4
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lp_select/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "lp_select"
8
+ spec.version = LpSelect::VERSION
9
+ spec.authors = ["Jake Sower", "James Prior"]
10
+ spec.email = ["j.sower@asee.org", "j.prior@asee.org"]
11
+ spec.description = %q{Ruby bindings for LPSolve}
12
+ spec.summary = %q{Ruby bindings for LPSolve}
13
+ spec.homepage = "https://github.com/asee/lp_select"
14
+ spec.license = "LGPL"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "ffi"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
@@ -0,0 +1,6 @@
1
+ -x1 -x2;
2
+ /* or min: x1 + x2; */
3
+ x1 >= 1;
4
+ x2 >= 1;
5
+ x1 + x2 >= 2;
6
+ int x1;
@@ -0,0 +1,186 @@
1
+ require 'test_helper'
2
+
3
+ class LpSelectTest < Test::Unit::TestCase
4
+ def setup
5
+ @fruits = {
6
+ "apple" => 1,
7
+ "bananna" => 0.3,
8
+ "pear" => 1.4,
9
+ "strawberry" => 3,
10
+ "pineapple" => 5,
11
+ "watermelon" => 4.3,
12
+ "grapes" => 2.4,
13
+ "orange" => 0.89,
14
+ "kiwi" => 3.49,
15
+ "mango" => 4.45,
16
+ "cherry" => 6,
17
+ "blueberry" => 5.50
18
+ }
19
+
20
+ @lprec = LPSelect.new(:vars => @fruits.keys)
21
+ end
22
+
23
+ def test_set_objective
24
+ objective_row = {}
25
+ assert_nothing_raised do
26
+ @lprec.set_objective(@fruits, :min) # min is the default
27
+ objective_row = @lprec.objective_row
28
+ end
29
+ assert_equal :min, objective_row[:direction]
30
+ assert_equal @fruits.length, objective_row[:weights].length
31
+ end
32
+
33
+ def test_add_constraints
34
+ # Alice likes berries, she gets at least one
35
+ @lprec.add_constraint({:name => "alice_picks", :vars => ["blueberry", "strawberry"], :op => LPSelect::GE, :target => 1})
36
+ assert_not_nil @lprec.constraints.detect{|x| x[:name] == 'alice_picks'}
37
+
38
+ # Bob likes tropical fruit, but he shouldn't get any more than two
39
+ @lprec.add_constraint({:name => "bob_picks", :vars => ["kiwi", "mango", "pineapple"], :op => LPSelect::LE, :target => 2})
40
+ assert_not_nil @lprec.constraints.detect{|x| x[:name] == 'bob_picks'}
41
+
42
+ # Carol is a toddler and gets to pick only two things.
43
+ @lprec.add_constraint({:name => "carol_picks", :vars => ["apple", "bananna", "grapes"], :op => LPSelect::EQ, :target => 2})
44
+ assert_not_nil @lprec.constraints.detect{|x| x[:name] == 'carol_picks'}
45
+
46
+ # Dan is an omnivore and we like him enough that he should get at least three.
47
+ @lprec.add_constraint({:name => "don_picks", :vars => @fruits.keys, :op => LPSelect::GE, :target => 3})
48
+ assert_not_nil @lprec.constraints.detect{|x| x[:name] == 'don_picks'}
49
+
50
+ assert_equal 4, @lprec.constraints.count
51
+ end
52
+
53
+ def test_yaml_serialize
54
+ @lprec.set_objective(@fruits, :min) # min is the default
55
+
56
+ # Alice likes berries, she gets at least one
57
+ @lprec.add_constraint({:name => "alice_picks", :vars => ["blueberry", "strawberry"], :op => LPSelect::GE, :target => 1})
58
+
59
+ # Bob likes tropical fruit, but he shouldn't get any more than two
60
+ @lprec.add_constraint({:name => "bob_picks", :vars => ["kiwi", "mango", "pineapple"], :op => LPSelect::LE, :target => 2})
61
+
62
+ # Carol is a toddler and gets to pick only two things.
63
+ @lprec.add_constraint({:name => "carol_picks", :vars => ["apple", "bananna", "grapes"], :op => LPSelect::EQ, :target => 2})
64
+
65
+ # Dan is an omnivore and we like him enough that he should get at least three.
66
+ @lprec.add_constraint({:name => "don_picks", :vars => @fruits.keys, :op => LPSelect::GE, :target => 3})
67
+
68
+ # etc...
69
+
70
+ yaml_source = nil
71
+ assert_nothing_raised do
72
+ yaml_source = @lprec.to_yaml
73
+ end
74
+ assert_not_nil yaml_source
75
+
76
+ alt = LPSelect.new(:yaml => yaml_source)
77
+ assert_equal alt.objective_row, @lprec.objective_row
78
+ assert_equal alt.vars, @lprec.vars
79
+ assert_equal alt.constraints, @lprec.constraints
80
+ end
81
+
82
+ def test_lp_format_serialize
83
+ @lprec.set_objective(@fruits, :min) # min is the default
84
+
85
+ # Alice likes berries, she gets at least one
86
+ @lprec.add_constraint({:name => "alice_picks", :vars => ["blueberry", "strawberry"], :op => LPSelect::GE, :target => 1})
87
+
88
+ # Bob likes tropical fruit, but he shouldn't get any more than two
89
+ @lprec.add_constraint({:name => "bob_picks", :vars => ["kiwi", "mango", "pineapple"], :op => LPSelect::LE, :target => 2})
90
+
91
+ # Carol is a toddler and gets to pick only two things.
92
+ @lprec.add_constraint({:name => "carol_picks", :vars => ["apple", "bananna", "grapes"], :op => LPSelect::EQ, :target => 2})
93
+
94
+ # Dan is an omnivore and we like him enough that he should get at least three.
95
+ @lprec.add_constraint({:name => "don_picks", :vars => @fruits.keys, :op => LPSelect::GE, :target => 3})
96
+
97
+ # etc...
98
+ begin
99
+ destination = Tempfile.new("lp_select_test")
100
+ lp_source = nil
101
+ assert_nothing_raised do
102
+ lp_source = @lprec.to_lp_format
103
+ @lprec.to_file(destination.path)
104
+ end
105
+
106
+ assert_not_nil lp_source
107
+
108
+ @lprec.constraints.each do |c|
109
+ assert lp_source.include?(c[:name]), "Expected the source to include #{c[:name]}"
110
+ end
111
+
112
+ @fruits.keys.each do |name|
113
+ assert lp_source.include?(name), "Expected the source to include #{name}"
114
+ end
115
+
116
+ assert File.size(destination.path) > 0
117
+
118
+ alt = LPSelect.new(:filename => destination.path)
119
+ assert_equal alt.vars, @lprec.vars
120
+
121
+ ensure
122
+ destination.unlink
123
+ end
124
+ end
125
+
126
+ def test_should_solve_with_min_price
127
+ @lprec.set_objective(@fruits, :min) # min is the default
128
+
129
+ # Alice likes berries, she gets at least one, strawberry is expected since it is the cheapest
130
+ @lprec.add_constraint({:name => "alice_picks", :vars => ["blueberry", "strawberry"], :op => LPSelect::GE, :target => 1})
131
+
132
+ # Bob likes tropical fruit, but he shouldn't get any more than two. Since we're minimizing we expect none of these
133
+ @lprec.add_constraint({:name => "bob_picks", :vars => ["kiwi", "mango", "pineapple"], :op => LPSelect::LE, :target => 2})
134
+
135
+ # Carol is a toddler and gets to pick only two things, the two cheapest
136
+ @lprec.add_constraint({:name => "carol_picks", :vars => ["apple", "bananna", "grapes"], :op => LPSelect::EQ, :target => 2})
137
+
138
+ # Dan is an omnivore and we like him enough that he should get at least three, so three total picks
139
+ @lprec.add_constraint({:name => "don_picks", :vars => @fruits.keys, :op => LPSelect::GE, :target => 3})
140
+
141
+ status = nil
142
+ assert_nothing_raised do
143
+ status = @lprec.solve
144
+ end
145
+
146
+ assert_not_nil status
147
+ assert_equal LPSolve::OPTIMAL, status
148
+
149
+ assert_not_nil @lprec.results
150
+ selected_fruits = @lprec.results.collect{|k,v| v == 1.0 ? k : nil}.flatten.compact
151
+ assert_equal 4.3, @lprec.objective
152
+ assert_equal [:apple, :bananna, :strawberry], selected_fruits
153
+ end
154
+
155
+ def test_should_solve_with_max_price
156
+ @lprec.set_objective(@fruits, :max)
157
+
158
+ # Alice likes berries, she gets at least one. Both should show up since we are maximizing
159
+ @lprec.add_constraint({:name => "alice_picks", :vars => ["blueberry", "strawberry"], :op => LPSelect::GE, :target => 1})
160
+
161
+ # Bob likes tropical fruit, but he shouldn't get any more than two - mango and pineapple since they are the most expensive
162
+ @lprec.add_constraint({:name => "bob_picks", :vars => ["kiwi", "mango", "pineapple"], :op => LPSelect::LE, :target => 2})
163
+
164
+ # Carol is a toddler and gets to pick only two things. Apple and grapes since they are the most expensive, but no bananas
165
+ @lprec.add_constraint({:name => "carol_picks", :vars => ["apple", "bananna", "grapes"], :op => LPSelect::EQ, :target => 2})
166
+
167
+ # Dan is an omnivore and we like him enough that he should get at least three. - so everythign else here
168
+ @lprec.add_constraint({:name => "don_picks", :vars => @fruits.keys, :op => LPSelect::GE, :target => 3})
169
+
170
+ expected_fruits = @fruits.keys.collect(&:to_sym) - [:bananna, :kiwi]
171
+
172
+ status = nil
173
+ assert_nothing_raised do
174
+ status = @lprec.solve
175
+ end
176
+
177
+ assert_not_nil status
178
+ assert_equal LPSolve::OPTIMAL, status
179
+
180
+ assert_not_nil @lprec.results
181
+ selected_fruits = @lprec.results.collect{|k,v| v == 1.0 ? k : nil}.flatten.compact
182
+ assert_equal 33.94, @lprec.objective
183
+ assert_equal expected_fruits.collect(&:to_s).sort, selected_fruits.collect(&:to_s).sort
184
+ end
185
+
186
+ end
@@ -0,0 +1,251 @@
1
+ require 'test_helper'
2
+
3
+ class LpSolveTest < Test::Unit::TestCase
4
+
5
+ # void lp_solve_version(int *majorversion, int *minorversion, int *release, int *build)
6
+ def test_lp_solve_version
7
+ assert LPSolve.version.include?("5.5.0 build ")
8
+ end
9
+
10
+ # lprec *make_lp(int rows, int columns);
11
+ def test_make_lp
12
+ assert_nothing_raised do
13
+ @lp = LPSolve::make_lp(0, 0)
14
+ end
15
+ assert_not_nil @lp
16
+ end
17
+
18
+ # unsigned char set_binary(lprec *lp, int column, unsigned char must_be_bin);
19
+ def test_set_binary
20
+ @lp = LPSolve::make_lp(0, 1)
21
+ assert_nothing_raised do
22
+ LPSolve::set_binary(@lp, 1, 1) #Define the column to be binary
23
+ end
24
+ end
25
+
26
+ # unsigned char set_col_name(lprec *lp, int column, char *new_name);
27
+ def test_set_col_name
28
+ @lp = LPSolve::make_lp(0, 1)
29
+ assert_nothing_raised do
30
+ LPSolve::set_col_name(@lp, 1, "fred")
31
+ end
32
+ end
33
+
34
+ # unsigned char set_lp_name(lprec *lp, char *lpname);
35
+ def test_set_lp_name
36
+ @lp = LPSolve::make_lp(0, 0)
37
+ assert_nothing_raised do
38
+ LPSolve::set_lp_name(@lp, "Hi mom")
39
+ end
40
+ end
41
+
42
+ # void set_verbose(lprec *lp, int verbose);
43
+ def test_set_verbose
44
+ @lp = LPSolve::make_lp(0, 0)
45
+ assert_nothing_raised do
46
+ LPSolve::set_verbose(@lp, LPSolve::SEVERE )
47
+ end
48
+ end
49
+
50
+ # lprec *copy_lp(lprec *lp);
51
+ def test_copy_lp
52
+ @lp = LPSolve::make_lp(0, 0)
53
+ assert_nothing_raised do
54
+ LPSolve::copy_lp(@lp)
55
+ end
56
+ end
57
+
58
+ # lprec *read_LP(char *filename, int verbose, char *lp_name)
59
+ def test_read_lp
60
+ filename = File.expand_path("../lp_format_sample.txt", __FILE__)
61
+ assert_nothing_raised do
62
+ @lp = LPSolve::read_LP(filename, LPSolve::SEVERE, "")
63
+ end
64
+ end
65
+
66
+ # char *get_origcol_name(lprec *lp, int column);
67
+ def test_get_origcol_name
68
+ @lp = LPSolve::make_lp(0, 1)
69
+ LPSolve::set_col_name(@lp, 1, "fred")
70
+
71
+ colname = LPSolve::get_origcol_name(@lp, 1)
72
+ assert_equal "fred", colname.to_s
73
+ end
74
+
75
+ # void set_maxim(lprec *lp);
76
+ def test_set_maxim
77
+ @lp = LPSolve::make_lp(0, 0)
78
+ assert_nothing_raised do
79
+ LPSolve::set_maxim(@lp)
80
+ end
81
+ end
82
+
83
+ # void set_minim(lprec *lp);
84
+ def test_set_minim
85
+ @lp = LPSolve::make_lp(0, 0)
86
+ assert_nothing_raised do
87
+ LPSolve::set_minim(@lp)
88
+ end
89
+ end
90
+
91
+ # unsigned char write_lp(lprec *lp, char *filename);
92
+ def test_write_lp
93
+ destination = Tempfile.new("lp_solve_test")
94
+ begin
95
+ @lp = LPSolve::make_lp(0, 1)
96
+ LPSolve::set_col_name(@lp, 1, "fred")
97
+
98
+ assert_nothing_raised do
99
+ LPSolve::write_lp(@lp, destination.path)
100
+ end
101
+ assert File.size(destination.path) > 0
102
+ ensure
103
+ destination.unlink
104
+ end
105
+ end
106
+
107
+ # int get_Ncolumns(lprec *lp);
108
+ def test_get_Ncolumns
109
+ @lp = LPSolve::make_lp(0, 3)
110
+ ncols = LPSolve::get_Ncolumns(@lp)
111
+ assert_equal 3, ncols
112
+ end
113
+
114
+ # char *get_col_name(lprec *lp, int column);
115
+ def test_get_col_name
116
+ @lp = LPSolve::make_lp(0, 1)
117
+ LPSolve::set_col_name(@lp, 1, "fred")
118
+
119
+ colname = LPSolve::get_origcol_name(@lp, 1)
120
+ assert_equal "fred", colname.to_s
121
+ end
122
+
123
+ # unsigned char add_constraint(lprec *lp, REAL *row, int constr_type, REAL rh);
124
+ def test_add_constraint
125
+ @lp = LPSolve::make_lp(0, 2)
126
+
127
+ #The API expects a 1 indexed array
128
+ constraint_vars = [0, 0, 1]
129
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
130
+ p.write_array_of_double(constraint_vars)
131
+ assert_nothing_raised do
132
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
133
+ end
134
+ end
135
+ end
136
+
137
+ # unsigned char set_row_name(lprec *lp, int row, char *new_name);
138
+ def test_set_row_name
139
+ @lp = LPSolve::make_lp(0, 2)
140
+ LPSolve::set_col_name(@lp, 1, "fred")
141
+ LPSolve::set_col_name(@lp, 2, "bob")
142
+
143
+ #The API expects a 1 indexed array
144
+ constraint_vars = [0, 0, 1]
145
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
146
+ p.write_array_of_double(constraint_vars)
147
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
148
+ end
149
+
150
+ assert_nothing_raised do
151
+ LPSolve::set_row_name(@lp, 1, "onlyBob")
152
+ end
153
+ end
154
+
155
+ # unsigned char set_obj_fn(lprec *lp, REAL *row);
156
+ def test_set_obj_fn
157
+ @lp = LPSolve::make_lp(0, 1)
158
+
159
+ #The API expects a 1 indexed array
160
+ constraint_vars = [1.0, 3.0]
161
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
162
+ p.write_array_of_double(constraint_vars)
163
+ assert_nothing_raised do
164
+ LPSolve::set_obj_fn(@lp, p)
165
+ end
166
+ end
167
+ end
168
+
169
+
170
+ # void delete_lp(lprec *lp);
171
+ def test_delete_lp
172
+ @lp = LPSolve::make_lp(0, 1)
173
+ assert_nothing_raised do
174
+ LPSolve::delete_lp(@lp)
175
+ end
176
+ end
177
+
178
+ # int solve(lprec *lp)
179
+ def test_solve
180
+ @lp = LPSolve::make_lp(0, 2)
181
+ LPSolve::set_verbose(@lp, LPSolve::SEVERE )
182
+ LPSolve::set_col_name(@lp, 1, "fred")
183
+ LPSolve::set_col_name(@lp, 2, "bob")
184
+
185
+ #The API expects a 1 indexed array
186
+ constraint_vars = [0, 0, 1]
187
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
188
+ p.write_array_of_double(constraint_vars)
189
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
190
+ end
191
+
192
+ LPSolve::set_minim(@lp)
193
+
194
+ assert_nothing_raised do
195
+ solution = LPSolve::solve(@lp)
196
+ end
197
+
198
+ end
199
+
200
+ # REAL get_objective(lprec *lp);
201
+ def test_get_objective
202
+ @lp = LPSolve::make_lp(0, 2)
203
+ LPSolve::set_verbose(@lp, LPSolve::SEVERE )
204
+ LPSolve::set_col_name(@lp, 1, "fred")
205
+ LPSolve::set_col_name(@lp, 2, "bob")
206
+
207
+ #The API expects a 1 indexed array
208
+ constraint_vars = [0, 0, 1]
209
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
210
+ p.write_array_of_double(constraint_vars)
211
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
212
+ end
213
+
214
+ LPSolve::set_minim(@lp)
215
+ solution = LPSolve::solve(@lp)
216
+
217
+ assert_nothing_raised do
218
+ objective = LPSolve::get_objective(@lp)
219
+ assert_not_nil objective
220
+ end
221
+ end
222
+
223
+ # unsigned char get_variables(lprec *lp, REAL *var);
224
+ def test_get_variables
225
+ @lp = LPSolve::make_lp(0, 2)
226
+ LPSolve::set_verbose(@lp, LPSolve::SEVERE )
227
+ LPSolve::set_col_name(@lp, 1, "fred")
228
+ LPSolve::set_col_name(@lp, 2, "bob")
229
+
230
+ #The API expects a 1 indexed array
231
+ constraint_vars = [0, 0, 1]
232
+ FFI::MemoryPointer.new(:double, constraint_vars.size) do |p|
233
+ p.write_array_of_double(constraint_vars)
234
+ LPSolve::add_constraint(@lp, p, LPSelect::EQ, 1.0.to_f)
235
+ end
236
+
237
+
238
+ LPSolve::set_minim(@lp)
239
+ solution = LPSolve::solve(@lp)
240
+
241
+ retvals = []
242
+ FFI::MemoryPointer.new(:double, 2) do |p|
243
+ assert_nothing_raised do
244
+ err = LPSolve::get_variables(@lp, p)
245
+ end
246
+ retvals = p.get_array_of_double(0,2)
247
+ end
248
+ assert_not_nil retvals[0]
249
+ assert_equal 1.0, retvals[1]
250
+ end
251
+ end
@@ -0,0 +1,9 @@
1
+
2
+ require 'lp_select'
3
+
4
+ require 'test/unit'
5
+ require 'tempfile'
6
+
7
+ class Test::Unit::TestCase
8
+ end
9
+
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lp_select
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jake Sower
9
+ - James Prior
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2015-07-27 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: bundler
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '1.3'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '1.3'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ description: Ruby bindings for LPSolve
64
+ email:
65
+ - j.sower@asee.org
66
+ - j.prior@asee.org
67
+ executables: []
68
+ extensions: []
69
+ extra_rdoc_files: []
70
+ files:
71
+ - .gitignore
72
+ - Gemfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - lib/lp_select.rb
77
+ - lib/lp_select/binaries/liblpsolve55.dylib
78
+ - lib/lp_select/binaries/liblpsolve55.dylib-ppc
79
+ - lib/lp_select/binaries/liblpsolve55.dylib.x86-64
80
+ - lib/lp_select/binaries/liblpsolve55.so
81
+ - lib/lp_select/binaries/liblpsolve55.so-ux64
82
+ - lib/lp_select/binaries/lpsolve55.dll
83
+ - lib/lp_select/lp_select.rb
84
+ - lib/lp_select/lp_solve.rb
85
+ - lib/lp_select/version.rb
86
+ - lp_select.gemspec
87
+ - test/lp_format_sample.txt
88
+ - test/lp_select_test.rb
89
+ - test/lp_solve_test.rb
90
+ - test/test_helper.rb
91
+ homepage: https://github.com/asee/lp_select
92
+ licenses:
93
+ - LGPL
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 1.8.23
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Ruby bindings for LPSolve
116
+ test_files:
117
+ - test/lp_format_sample.txt
118
+ - test/lp_select_test.rb
119
+ - test/lp_solve_test.rb
120
+ - test/test_helper.rb
121
+ has_rdoc: