Skip to content

Commit

Permalink
Merge pull request #17 from ryanjoneil/examples-and-docs-v0.2
Browse files Browse the repository at this point in the history
Adding UFL example. Fixing issue with constraints of the form "x <= y".
  • Loading branch information
ryanjoneil authored Jan 17, 2017
2 parents a9c349d + 83cd395 commit 5077974
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 5 deletions.
66 changes: 66 additions & 0 deletions examples/facility-location.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env ruby

require_relative '../lib/rams'

# Generic formulation for the Capacitated Facility Location Problem
# This example was converted from the ZIMPL examples.

FACILITIES = (:A..:D).to_a
CUSTOMERS = (1..9).to_a

# Costs for opening a facility
FIXED_COST = {A: 500, B: 600, C: 700, D: 800}

# Capacity of a facility at each site
CAPACITY = {A: 40, B: 55, C: 73, D: 90}

# Demand from each customer
DEMAND = {1 => 10, 2 => 14, 3 => 17, 4 => 8, 5 => 9, 6 => 12, 7 => 11, 8 => 15, 9 => 16}

# Transportation cost from each facility to each customer
TRANSPORTATION = {
A: {1 => 55, 2 => 4, 3 => 17, 4 => 33, 5 => 47, 6 => 98, 7 => 19, 8 => 10, 9 => 6},
B: {1 => 42, 2 => 12, 3 => 4, 4 => 23, 5 => 16, 6 => 78, 7 => 47, 8 => 9, 9 => 82},
C: {1 => 17, 2 => 34, 3 => 65, 4 => 25, 5 => 7, 6 => 67, 7 => 45, 8 => 13, 9 => 54},
D: {1 => 60, 2 => 8, 3 => 79, 4 => 24, 5 => 28, 6 => 19, 7 => 62, 8 => 18, 9 => 45}
}

m = RAMS::Model.new

# Fixed cost variables for opening facilities
y = FACILITIES.map { |f| [f, m.variable(type: :binary)] }.to_h

# Variables to represent facility/customer relations
x = FACILITIES.product(CUSTOMERS).map do |f, c|
[[f, c], m.variable(type: :binary)]
end.to_h

# Supply each customer from one facility
CUSTOMERS.each do |c|
m.constrain(FACILITIES.map { |f| x[[f, c]] }.reduce(:+) == 1)
end

# Using a facility incurs its fixed cost
FACILITIES.product(CUSTOMERS).map do |f, c|
m.constrain(x[[f, c]] <= y[f])
end

# Facilities cannot exceed their capacities
FACILITIES.each do |f|
m.constrain(CUSTOMERS.map { |c| DEMAND[c] * x[[f, c]] }.reduce(:+) <= CAPACITY[f])
end

# Minimize overall cost
m.sense = :min
m.objective = FACILITIES.map { |f| FIXED_COST[f] * y[f] }.reduce(:+) +
FACILITIES.product(CUSTOMERS).map { |f, c| TRANSPORTATION[f][c] * x[[f, c]] }.reduce(:+)

solution = m.solve

puts "status: #{solution.status}"
puts "total cost: #{solution.objective}"
FACILITIES.each do |f|
next unless solution[y[f]] > 0.5
puts "facility #{f}: #{CUSTOMERS.select { |c| solution[x[[f, c]]] > 0.5 }}"
end

2 changes: 1 addition & 1 deletion lib/rams/constraint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def name
end

def to_s
lhs_s = lhs.map { |v, c| "#{c >= 0 ? '+' : '-'} #{c} #{v.name} " }.join
lhs_s = lhs.map { |v, c| "#{c >= 0 ? '+ ' : ''}#{c} #{v.name} " }.join
sense_s = sense == :== ? '=' : sense.to_s
"#{name}: #{lhs_s}#{sense_s} #{rhs}"
end
Expand Down
6 changes: 3 additions & 3 deletions lib/rams/expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ def >=(other)
end

def to_s
vars_s = coefficients.map { |v, c| "#{c >= 0 ? '+' : '-'} #{c} #{v.name} " }.join
sign_s = constant >= 0 ? '+' : '-'
const_s = constant == 0 ? '' : "#{sign_s} #{constant}"
vars_s = coefficients.map { |v, c| "#{c >= 0 ? '+ ' : ''}#{c} #{v.name} " }.join
sign_s = constant >= 0 ? '+ ' : ''
const_s = constant == 0 ? '' : "#{sign_s}#{constant}"
vars_s + const_s
end

Expand Down
2 changes: 1 addition & 1 deletion rams.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |spec|
spec.name = 'rams'
spec.version = '0.1.3'
spec.version = '0.1.4'
spec.authors = ["Ryan J. O'Neil"]
spec.email = ['ryanjoneil@gmail.com']
spec.summary = 'Ruby Algebraic Modeling System'
Expand Down
11 changes: 11 additions & 0 deletions tests/test_expression.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ def test_add_expressions
assert_equal 11, e3.constant
end

def test_basic_substract_expression
x1 = RAMS::Variable.new
x2 = RAMS::Variable.new
e1 = x1 - x2
e2 = x2 - x1
assert_equal 1, e1.coefficients[x1]
assert_equal(-1, e1.coefficients[x2])
assert_equal(-1, e2.coefficients[x1])
assert_equal 1, e2.coefficients[x2]
end

def test_subtract_expressions
x1 = RAMS::Variable.new
x2 = RAMS::Variable.new
Expand Down
26 changes: 26 additions & 0 deletions tests/test_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ def test_unbounded
run_test_unbounded :scip
end

def test_impliation
run_test_implication :cbc
run_test_implication :cplex if ENV['RAMS_TEST_CPLEX']
run_test_implication :glpk
run_test_implication :scip
end

# rubocop:disable MethodLength
def run_test_simple(solver, args = [])
m = RAMS::Model.new
Expand Down Expand Up @@ -143,5 +150,24 @@ def run_test_unbounded(solver, args = [])

assert_includes [:unbounded, :undefined], solution.status
end

def run_test_implication(solver, args = [])
m = RAMS::Model.new
m.solver = solver
m.args = args

x1 = m.variable type: :binary
x2 = m.variable type: :binary
m.constrain(x1 + x2 <= 1)
m.constrain(x1 <= x2)

m.sense = :max
m.objective = 2 * x1 + x2
solution = m.solve

assert_equal :optimal, solution.status
assert_equal 0, solution[x1]
assert_equal 1, solution[x2]
end
end
# rubocop:enable ClassLength

0 comments on commit 5077974

Please sign in to comment.