amor 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e9946bfd005825741713196a3110508269ffa7a9
4
+ data.tar.gz: be143fbccb12ad889d1d38a7258e09aa72ca7000
5
+ SHA512:
6
+ metadata.gz: b531b2c9eef38a50945bec9c88b7bb2a49f7caeb6a2d53e3479fbb8ca6566d7079c837a2435247a86869a95b9833f4ca263cd03136171dc28e4deb81db2846bd
7
+ data.tar.gz: f46962ea18edfd428084acdfe9b9b4474cee93bf3d9f3afead1f03c7d94bb6b6a70155bb08175b62c0cfdeeeca6d608607de876ab2375d07fe1fdafc67f9587f
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in amor.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Florian Dahms
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # AMoR: Amazing Modelling with Ruby
2
+
3
+ The modelling language you will fall in love with.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'amor'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install amor
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/philosophus/amor/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/amor.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'amor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "amor"
8
+ spec.version = Amor::VERSION
9
+ spec.authors = ["Florian Dahms"]
10
+ spec.email = ["me@fdahms.com"]
11
+ spec.summary = "A versatile, yet simple modelling language for mathematical programming"
12
+ spec.description = "AMoR is a Ruby DSL for mathematical programming. It allows to simply define a mathematical program, but gives you all the features from Ruby for more complex projects"
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
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_runtime_dependency "require_all"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_development_dependency "rspec-mocks"
27
+ end
@@ -0,0 +1,14 @@
1
+ {
2
+ "folders":
3
+ [
4
+ {
5
+ "follow_symlinks": true,
6
+ "path": "."
7
+ }
8
+ ],
9
+ "settings": {
10
+ "tab_size": 2,
11
+ "translate_tabs_to_spaces": true,
12
+ "trim_trailing_white_space_on_save": true
13
+ }
14
+ }
data/bin/amor ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'amor'
4
+
5
+ Amor::Model.from_file(ARGV[0])
@@ -0,0 +1,37 @@
1
+ module Amor
2
+ class Constraint
3
+ attr_reader :lhs, :rhs, :relation
4
+
5
+ def initialize(lhs, relation, rhs)
6
+ @lhs = Expression.new(lhs)
7
+ @rhs = Expression.new(rhs)
8
+ @relation = relation
9
+ end
10
+
11
+ def lp_string
12
+ temp_lhs = (@lhs - @rhs).simplified
13
+ relation_string = case @relation
14
+ when :greater_equal
15
+ ">="
16
+ when :lesser_equal
17
+ "<="
18
+ else
19
+ "="
20
+ end
21
+
22
+ "#{temp_lhs.remove_constants.lp_string} #{relation_string} #{-temp_lhs.constant_factor}"
23
+ end
24
+
25
+ def to_s
26
+ relation_string = case @relation
27
+ when :greater_equal
28
+ ">="
29
+ when :lesser_equal
30
+ "<="
31
+ else
32
+ "=="
33
+ end
34
+ "#{@lhs} #{relation_string} #{@rhs}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ module Amor
2
+ class Expression
3
+
4
+ attr_reader :factors
5
+
6
+ def initialize(value)
7
+ if value.is_a? Array
8
+ @factors = value
9
+ elsif value.is_a? Expression
10
+ @factors = value.factors
11
+ elsif value.is_a? Variable
12
+ @factors = [[1, value]]
13
+ elsif value.is_a? Numeric
14
+ @factors = [[value, :constant]]
15
+ end
16
+ end
17
+
18
+ def +(value)
19
+ Expression.new(self.factors + Expression.new(value).factors)
20
+ end
21
+
22
+ def -(value)
23
+ self + -value
24
+ end
25
+
26
+ def -@
27
+ return Expression.new(self.factors.map{|factor| [-factor[0], factor[1]]})
28
+ end
29
+
30
+ def hash
31
+ @factors.hash
32
+ end
33
+
34
+ def eql? value
35
+ self.hash == value.hash
36
+ end
37
+
38
+ def == value
39
+ Constraint.new(self, :equal, value)
40
+ end
41
+
42
+ def <= value
43
+ Constraint.new(self, :lesser_equal, value)
44
+ end
45
+
46
+ def >= value
47
+ Constraint.new(self, :greater_equal, value)
48
+ end
49
+
50
+ def simplified
51
+ summed_scalars = Hash.new
52
+ @factors.each do |factor|
53
+ summed_scalars[factor[1]] = (summed_scalars[factor[1]] || 0) + factor[0]
54
+ end
55
+ Expression.new(summed_scalars.map{|var, scalar| [scalar, var]}.select{|factor| !factor[0].zero? })
56
+ end
57
+
58
+ def remove_constants
59
+ Expression.new(@factors.select{|factor| factor[1].is_a?(Variable)})
60
+ end
61
+
62
+ def constant_factor
63
+ @factors.select{|factor| !factor[1].is_a?(Variable)}.inject(0) {|m, factor| m + factor[0]}
64
+ end
65
+
66
+ def lp_string
67
+ result = ''
68
+ factor_strings = self.simplified.factors.each_with_index.map do |factor, i|
69
+ scalar = factor[0]
70
+ if scalar < 0
71
+ sign = '- '
72
+ scalar = -scalar
73
+ elsif i > 0
74
+ sign = '+ '
75
+ else
76
+ sign = ''
77
+ end
78
+
79
+ if factor[1].is_a? Variable
80
+ "#{sign}#{scalar} x#{factor[1].internal_index+1}"
81
+ else
82
+ "#{sign}#{scalar}"
83
+ end
84
+ end
85
+ return factor_strings.join(' ')
86
+ end
87
+
88
+ def to_s
89
+ factors.map{|factor| factor[1].is_a?(Variable) ? "#{factor[0]} #{factor[1]}" : "#{factor[0]}"}.join(" ")
90
+ end
91
+ end
92
+ end
data/lib/amor/model.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'amor/objective'
2
+
3
+ module Amor
4
+ class Model
5
+
6
+ attr_reader :objective, :constraints
7
+
8
+ def initialize
9
+ @variables = Array.new
10
+ @indices = Hash.new
11
+ end
12
+
13
+ # Return the variable for that index if already existing or a new one
14
+ def x(index)
15
+ @variables[@indices[index] ||= @indices.size] ||= Variable.new(self, index)
16
+ end
17
+
18
+ # Add a minimization objective
19
+ def min(expression)
20
+ @objective = Objective.new(:minimize, expression)
21
+ end
22
+
23
+ # Add a maximization objective
24
+ def max(expression)
25
+ @objective = Objective.new(:maximize, expression)
26
+ end
27
+
28
+ # Add a constraint
29
+ def st(constraint)
30
+ (@constraints ||= Array.new) << constraint
31
+ return constraint
32
+ end
33
+
34
+ # Create a model from a given string
35
+ def self.from_string(string)
36
+ model = Model.new
37
+ model.instance_eval(string)
38
+ return model
39
+ end
40
+
41
+ # Create a model from a file
42
+ def self.from_file(filename)
43
+ Model.from_string(File.read(filename))
44
+ end
45
+
46
+ def internal_index(index)
47
+ @indices[index]
48
+ end
49
+
50
+ def lp_string
51
+ result = @objective.lp_string
52
+ result << "\nSubject To\n"
53
+ result << @constraints.each_with_index.map do |constraint, i|
54
+ " c#{i+1}: #{constraint.lp_string}"
55
+ end.join("\n")
56
+ result << "\nEnd"
57
+ return result
58
+ end
59
+
60
+ def save_lp(filename)
61
+ File.open(filename, 'w') {|file| file.puts self.lp_string}
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ class Fixnum
2
+ old_multiplication = instance_method(:'*')
3
+ define_method(:'*') do |value|
4
+ if value.is_a? Amor::Variable
5
+ value * self
6
+ else
7
+ old_multiplication.bind(self).(value)
8
+ end
9
+ end
10
+
11
+ old_addition = instance_method(:'+')
12
+ define_method(:'+') do |value|
13
+ if value.is_a?(Amor::Expression) || value.is_a?(Amor::Variable)
14
+ Amor::Expression.new(self) + Amor::Expression.new(value)
15
+ else
16
+ old_addition.bind(self).(value)
17
+ end
18
+ end
19
+
20
+ old_subtraction = instance_method(:'-')
21
+ define_method(:'-') do |value|
22
+ if value.is_a?(Amor::Expression) || value.is_a?(Amor::Variable)
23
+ self + -value
24
+ else
25
+ old_subtraction.bind(self).(value)
26
+ end
27
+ end
28
+ end
29
+
30
+ class Float
31
+ old_multiplication = instance_method(:'*')
32
+ define_method(:'*') do |value|
33
+ if value.is_a? Amor::Variable
34
+ value * self
35
+ else
36
+ old_multiplication.bind(self).(value)
37
+ end
38
+ end
39
+
40
+ old_addition = instance_method(:'+')
41
+ define_method(:'+') do |value|
42
+ if value.is_a?(Amor::Expression) || value.is_a?(Amor::Variable)
43
+ Amor::Expression.new(self) + Amor::Expression.new(value)
44
+ else
45
+ old_addition.bind(self).(value)
46
+ end
47
+ end
48
+
49
+ old_subtraction = instance_method(:'-')
50
+ define_method(:'-') do |value|
51
+ if value.is_a?(Amor::Expression) || value.is_a?(Amor::Variable)
52
+ self + -value
53
+ else
54
+ old_subtraction.bind(self).(value)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ module Amor
2
+ class Objective
3
+
4
+ attr_reader :direction, :expression
5
+
6
+ def initialize(direction, expression)
7
+ @direction = direction
8
+ @expression = Expression.new(expression)
9
+ end
10
+
11
+ def lp_string
12
+ direction_string = (@direction == :maximize ? "Maximize" : "Minimize")
13
+ "#{direction_string}\n obj: #{@expression.lp_string}"
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ module Amor
2
+ class Variable
3
+
4
+ attr_reader :model, :index
5
+
6
+ def initialize(model, index)
7
+ @model = model
8
+ @index = index
9
+ end
10
+
11
+ def *(scalar)
12
+ Expression.new([[scalar, self]])
13
+ end
14
+
15
+ def +(value)
16
+ Expression.new(self) + Expression.new(value)
17
+ end
18
+
19
+ def -(value)
20
+ self + -value
21
+ end
22
+
23
+ def -@
24
+ Expression.new([[-1, self]])
25
+ end
26
+
27
+ def internal_index
28
+ @model.internal_index(self.index)
29
+ end
30
+
31
+ def == value
32
+ Constraint.new(self, :equal, value)
33
+ end
34
+
35
+ def <= value
36
+ Constraint.new(self, :lesser_equal, value)
37
+ end
38
+
39
+ def >= value
40
+ Constraint.new(self, :greater_equal, value)
41
+ end
42
+
43
+ def to_s
44
+ "x(#{index})"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Amor
2
+ VERSION = "0.0.2"
3
+ end
data/lib/amor.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'require_all'
2
+
3
+ require_all 'lib/amor'
@@ -0,0 +1,35 @@
1
+ require 'amor/constraint'
2
+
3
+ module Amor
4
+ describe Constraint do
5
+ describe '.new' do
6
+ before(:each) do
7
+ @lhs = Expression.new(4)
8
+ @rhs = Expression.new(8)
9
+ end
10
+
11
+ it 'returns a Constraint with the given left hand side' do
12
+ expect(Constraint.new(@lhs, :lesser_equal, @rhs).lhs).to eql(@lhs)
13
+ end
14
+
15
+ it 'returns a Constraint with the given right hand side' do
16
+ expect(Constraint.new(@lhs, :lesser_equal, @rhs).rhs).to eql(@rhs)
17
+ end
18
+
19
+ it 'returns a Constraint with the given relation' do
20
+ expect(Constraint.new(@lhs, :lesser_equal, @rhs).relation).to eq(:lesser_equal)
21
+ end
22
+ end
23
+
24
+ describe '#lp_string' do
25
+ it 'returns a LP format ready version of the constraint' do
26
+ model = Model.new
27
+ v1 = model.x(1)
28
+ v2 = model.x(2)
29
+ v3 = model.x(3)
30
+ expect(Constraint.new(Expression.new([[3, v1], [-2.0, v2], [2, :constant], [2.5, v3]]), :lesser_equal, Expression.new(5)).lp_string).to eq('3 x1 - 2.0 x2 + 2.5 x3 <= 3')
31
+ end
32
+ end
33
+
34
+ end
35
+ end