Skip to content

Commit

Permalink
Added code fences for syntax highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
Wouter Coppieters committed Mar 4, 2015
1 parent 4438c4b commit e4ff819
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 52 deletions.
81 changes: 58 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP libr

## Sample Code

```ruby
# maximize
# z = 10 * x + 6 * y + 4 * z
#
Expand Down Expand Up @@ -62,12 +63,13 @@ Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP libr
# Y_i.value == 67
# Z_i.value == 0
##

```

## Usage

#### Variables

```ruby
# Rulp variables are initialized as soon as they are needed so there is no
# need to initialize them.
# They follow a naming convention that defines their type.
Expand Down Expand Up @@ -110,7 +112,7 @@ Rulp is inspired by the ruby wrapper for the GLPK toolkit and the python LP libr
#<LV:0x007ffc4cc0d460 @name="Unit8">,
#<LV:0x007ffc4cc0cee8 @name="Unit9">,
#<LV:0x007ffc4cc0c970 @name="Unit10">]

```

### Variable Constraints

Expand All @@ -119,6 +121,7 @@ Be careful to use '==' and not '=' when expressing equality.
Constraints on a variable can only use numeric literals and not other variables.
Inter-variable constraints should be expressed as problem constrants. (Explained below.)

```ruby
X_i < 5
X_i.bounds
=> "X <= 5"
Expand All @@ -130,11 +133,13 @@ Inter-variable constraints should be expressed as problem constrants. (Explained
Y_f == 10
Y_f.bounds
=> "y = 10"
```

### Problem constraints

Constraints are added to a problem using the :[] syntax.

```ruby
problem = Rulp::Cbc Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i )

problem[
Expand All @@ -146,14 +151,17 @@ Constraints are added to a problem using the :[] syntax.
]
...
problem.solve
```

You can add multiple constraints at once by comma separating them as seen in the earlier examples:

```ruby
Rulp::Cbc Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
X_i + Y_i + Z_i <= 100,
10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
2 * X_i + 2 * Y_i + 6 * Z_i <= 300
]
```

### Solving or saving 'lp' files

Expand All @@ -165,61 +173,76 @@ such that the command `which [exec_name]` returns a path. (I.e they must be on y

Given a problem there are multiple ways to initiate a solver.

```ruby
@problem = Rulp::Cbc Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
X_i + Y_i + Z_i <= 100,
10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
2 * X_i + 2 * Y_i + 6 * Z_i <= 300
]
```

Default solver:

```ruby
@problem.solve
# this will use the solver specified in the environment variable 'SOLVER' by default.
# This can be 'scip', 'cbc', or 'glpk'. If no variable is given it uses scip as a default.
```

If you had a linear equation in a file named 'problem.rb' from the command line you could specify an alternate solver by executing:

```ruby
SOLVER=cbc ruby problem.rb
```

Explicit solver:

```ruby
@problem.scip
# Or
@problem.cbc
# Or
@problem.glpk
```

Or

```ruby
Rulp::Scip(@problem)
Rulp::Glpk(@problem)
Rulp::Cbc(@problem)

```

For debugging purposes you may wish to see the input and output files generated and consumed by Rulp.
To do this you can use the following extended syntax:

```ruby
def solve_with(type, open_definition=false, open_solution=false)...
```
The optional booleans will optionally call the 'open' utility to open the problem definition or the solution. (This utility is installed by default on a mac and will not work if the utility is not on your PATH)

```ruby
@problem.solve_with(SCIP, true, true)
```

#### Saving LP files.

You may not wish to use one of the RULP compatible but another solver that is able to read .lp files. (E.g CPLEX or Gurobi) but still want to use Rulp to generate your LP file. In this case you should use Rulp to output your lp problem description to a file of your choice. To do this simply use the following call

```ruby
@problem.save("/Users/johndoe/Desktop/myproblem.lp")
```

OR

```ruby
@problem.output("/Users/johndoe/Desktop/myproblem.lp")
```

You should also be able to call

```ruby
@problem.save

```
Without parameters to be prompted for a save location.

### Examples.
Expand All @@ -230,6 +253,8 @@ Rulp comes bundled with a 'rulp' executable which by default loads the rulp envi
(Preference for PRY). Once installed you should be able to simply execute 'rulp' to launch this rulp enabled REPL.
You can then play with and attempt LP and MIP problems straight from the command line.

```ruby

[1] pry(main)> 13 <= X_i <= 45 # Declare integer variable
=> X(i)[undefined]

Expand Down Expand Up @@ -286,7 +311,7 @@ You can then play with and attempt LP and MIP problems straight from the command

[10] pry(main)> (2 * X_i + 15 * Y_f).evaluate
=> 251.0

```

### A larger example
Here is a basic example of how Rulp can help you model problems with a large number of variables.
Expand All @@ -298,18 +323,28 @@ of how we could use Rulp to formulate this problem.
We decide to model each of these possible purchases as a binary variable (as we either purchase them or
we don't. We can't partially purchase one.)

# Generate the data randomly for this example.
costs, points = [*0..1000].map do |i|
[Purchase_b(i) * Random.rand(1.0..3.0), Purchase_b(i) * Random.rand(5.0..10.0)]
end.transpose.map(&:sum) #We sum the array of points and array of costs to create a Rulp expression

# And this is where the magic happens!. We ask rulp to maximise the number of points given
# the constraint that costs must be less than $55

Rulp::Max(points)[
costs < 55
].solve
=> 538.2125623353652 (# You will get a different value as data was generated randomly)

# Now how do we check which purchases were selected?
selected_purchases = [*0..1000].select{|i| Purchase_b(i).value }
```ruby
# Generate the data randomly for this example.
costs, points = [*0..1000].map do |i|
[Purchase_b(i) * Random.rand(1.0..3.0), Purchase_b(i) * Random.rand(5.0..10.0)]
end.transpose.map(&:sum) #We sum the array of points and array of costs to create a Rulp expression

# And this is where the magic happens!. We ask rulp to maximise the number of points given
# the constraint that costs must be less than $55

Rulp::Max(points)[
costs < 55
].solve
=> 538.2125623353652 (# You will get a different value as data was generated randomly)

# Now how do we check which purchases were selected?
selected_purchases = [*0..1000].select do |i|
Purchase_b(i).value
end.map(&Purchase_b)
=> [Purchase27(b)[true],
Purchase86(b)[true],
Purchase120(b)[true],
Purchase141(b)[true],
Purchase154(b)[true],
...
```
12 changes: 6 additions & 6 deletions lib/extensions/kernel_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ def method_missing(value, *args)
if (start <= "Z" && start >= "A")
case method_name[-1]
when "b"
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
return BV.send(method_name)
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)]
return BV.send(method_name, args)
when "i"
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
return IV.send(method_name)
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)]
return IV.send(method_name, args)
when "f"
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)] + args.join("_")
return LV.send(method_name)
method_name = method_name[0..(method_name[-2] == "_" ? -3 : -2)]
return LV.send(method_name, args)
end
end
old_method_missing(value, *args)
Expand Down
37 changes: 21 additions & 16 deletions lib/rulp/lv.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,49 @@
# These are constructed dynamically by using the special Capitalised variable declaration syntax.
##
class LV
attr_reader :name
attr_reader :name, :args
attr_accessor :lt, :lte, :gt, :gte, :value
include Rulp::Bounds
include Rulp::Initializers

def to_proc
->(index){ send(self.meth(index)) }
->(index){ send(self.meth, index) }
end

def meth(*args)
"#{self.name}#{args.join("_")}_#{self.suffix}"
def meth
"#{self.name}_#{self.suffix}"
end

def suffix
"f"
end

def self.method_missing(name, *args)
return self.definition( "#{name}#{args.join("_")}" )
return self.definition(name, args)
end

def self.const_missing(name)
return self.definition(name)
end

def self.definition(name)
self.class.send(:define_method, name){
defined = LV::names_table["#{name}"]
if defined && defined.class != self
raise StandardError.new("ERROR:\n#{name} was already defined as a variable of type #{defined.class}."+
"You are trying to redefine it as a variable of type #{self}")
elsif(!defined)
self.new(name)
def self.definition(name, args)
identifier = "#{name}#{args.join("_")}"
self.class.send(:define_method, identifier){|index=nil|
if index
self.definition(name, index)
else
defined
defined = LV::names_table["#{identifier}"]
if defined && defined.class != self
raise StandardError.new("ERROR:\n#{name} was already defined as a variable of type #{defined.class}."+
"You are trying to redefine it as a variable of type #{self}")
elsif(!defined)
self.new(name, args)
else
defined
end
end
}
return self.send(name) || self.new(name)
return self.send(identifier) || self.new(name, args)
end

def * (numeric)
Expand Down Expand Up @@ -73,7 +78,7 @@ def value
end

def inspect
"#{name}(#{suffix})[#{value || 'undefined'}]"
"#{name}#{args.join("-")}(#{suffix})[#{value || 'undefined'}]"
end
end

Expand Down
9 changes: 5 additions & 4 deletions lib/rulp/rulp_initializers.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module Rulp
module Initializers
def initialize(name)
raise StandardError.new("Variable with the name #{name} of a different type (#{LV::names_table[name].class}) already exists") if LV::names_table["#{name}"]
LV::names_table["#{name}"] = self
def initialize(name, args)
@name = name
@args = args
raise StandardError.new("Variable with the name #{self} of a different type (#{LV::names_table[self.to_s].class}) already exists") if LV::names_table[self.to_s]
LV::names_table[self.to_s] = self
end

def self.included(base)
Expand All @@ -21,7 +22,7 @@ def clear
end

def to_s
"#{self.name}"
"#{self.name}#{self.args.join("_")}"
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/solvers/cbc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def store_results(variables)
vars_by_name[cols[1].to_s] = cols[2].to_f
end
variables.each do |var|
var.value = vars_by_name[var.name.to_s].to_f
var.value = vars_by_name[var.to_s].to_f
end
return objective
end
Expand Down
2 changes: 1 addition & 1 deletion lib/solvers/scip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def store_results(variables)
vars_by_name[cols[0].to_s] = cols[1].to_f
end
variables.each do |var|
var.value = vars_by_name[var.name.to_s].to_f
var.value = vars_by_name[var.to_s].to_f
end

return objective
Expand Down
2 changes: 1 addition & 1 deletion rulp.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = 'rulp'
s.version = '0.0.5'
s.version = '0.0.6'
s.date = Date.today
s.summary = "Ruby Linear Programming"
s.description = "A simple Ruby LP description DSL"
Expand Down

0 comments on commit e4ff819

Please sign in to comment.