From 83cd395c25db5cf8690409fa652c141c8cbd0dfd Mon Sep 17 00:00:00 2001 From: "Ryan J. O'Neil" Date: Mon, 16 Jan 2017 23:49:52 -0500 Subject: [PATCH] Adding UFL example. Fixing issue with constraints of the form "x <= y". --- examples/facility-location.rb | 66 +++++++++++++++++++++++++++++++++++ lib/rams/constraint.rb | 2 +- lib/rams/expression.rb | 6 ++-- rams.gemspec | 2 +- tests/test_expression.rb | 11 ++++++ tests/test_model.rb | 26 ++++++++++++++ 6 files changed, 108 insertions(+), 5 deletions(-) create mode 100755 examples/facility-location.rb diff --git a/examples/facility-location.rb b/examples/facility-location.rb new file mode 100755 index 0000000..2ed32b1 --- /dev/null +++ b/examples/facility-location.rb @@ -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 + diff --git a/lib/rams/constraint.rb b/lib/rams/constraint.rb index f69a3be..bf225bd 100644 --- a/lib/rams/constraint.rb +++ b/lib/rams/constraint.rb @@ -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 diff --git a/lib/rams/expression.rb b/lib/rams/expression.rb index bcbcb15..33e69ae 100644 --- a/lib/rams/expression.rb +++ b/lib/rams/expression.rb @@ -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 diff --git a/rams.gemspec b/rams.gemspec index d77573a..79e9123 100644 --- a/rams.gemspec +++ b/rams.gemspec @@ -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' diff --git a/tests/test_expression.rb b/tests/test_expression.rb index 0dc9e64..e6a2933 100644 --- a/tests/test_expression.rb +++ b/tests/test_expression.rb @@ -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 diff --git a/tests/test_model.rb b/tests/test_model.rb index f5651ab..68c5547 100644 --- a/tests/test_model.rb +++ b/tests/test_model.rb @@ -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 @@ -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