ruby-cbc 0.3.10 → 0.3.12

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Build Status](https://travis-ci.org/gverger/ruby-cbc.svg?branch=master)](https://travis-ci.org/gverger/ruby-cbc)
1
2
  # Ruby-Cbc
2
3
 
3
4
  This gem is using Cbc, an Integer Linear Programming Library, to provide optimization problems solving
@@ -120,7 +121,7 @@ a1 * x1 + a2 * x2 + ... + an * xn == C
120
121
 
121
122
  With Ruby-Cbc you can write
122
123
  ```ruby
123
- 2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5
124
+ 2 * (2 + 5 * x) + 4 * 5 + 1 == 1 + 4 * 5 * y
124
125
  ```
125
126
  The (in)equation must still be a **linear** (in)equation, you cannot multiply two variables !
126
127
 
@@ -1,21 +1,21 @@
1
1
  require "ruby-cbc/version"
2
- require 'cbc-wrapper'
2
+ require "cbc-wrapper"
3
3
 
4
4
  module Cbc
5
5
  end
6
6
 
7
7
  files = %w(
8
- conflict_solver
9
- model
10
- problem
11
- version
12
- ilp/constant
13
- ilp/constraint
14
- ilp/objective
15
- ilp/term
16
- ilp/term_array
17
- ilp/var
18
- utils/compressed_row_storage
8
+ conflict_solver
9
+ model
10
+ problem
11
+ version
12
+ ilp/constant
13
+ ilp/constraint
14
+ ilp/objective
15
+ ilp/term
16
+ ilp/term_array
17
+ ilp/var
18
+ utils/compressed_row_storage
19
19
  )
20
20
 
21
21
  files.each do |file|
@@ -1,7 +1,5 @@
1
1
  module Cbc
2
-
3
2
  class ConflictSolver
4
-
5
3
  def initialize(problem)
6
4
  # clone the model minus the objective
7
5
  @model = Model.new
@@ -12,7 +10,7 @@ module Cbc
12
10
 
13
11
  def find_conflict
14
12
  crs = Util::CompressedRowStorage.from_model(@model)
15
- continuous = is_continuous_conflict?(crs)
13
+ continuous = continuous_conflict?(crs)
16
14
  unless continuous
17
15
  p = Problem.from_compressed_row_storage(crs, continuous: false)
18
16
  return [] unless infeasible?(p)
@@ -57,12 +55,13 @@ module Cbc
57
55
  crs.model.constraints[0, conflict_set_size]
58
56
  end
59
57
 
60
- def is_continuous_conflict?(crs)
58
+ def continuous_conflict?(crs)
61
59
  problem = Problem.from_compressed_row_storage(crs, continuous: true)
62
60
  infeasible?(problem)
63
61
  end
64
62
 
65
- private
63
+ private
64
+
66
65
  # finds the first constraint from constraints that makes the problem infeasible
67
66
  def first_failing(conflict_set_size, crs, continuous: false, max_iterations: nil)
68
67
  min_idx = conflict_set_size
@@ -2,35 +2,40 @@ module Ilp
2
2
  class Constant
3
3
  attr_accessor :value
4
4
  def initialize(value)
5
- raise ArgumentError, 'Argument is not numeric' unless value.is_a? Numeric
5
+ raise ArgumentError, "Argument is not numeric" unless value.is_a? Numeric
6
6
  @value = value
7
7
  end
8
- def <= (term)
9
- term >= value
8
+
9
+ def <=(other)
10
+ other >= value
10
11
  end
11
- def <(term)
12
- term > value
12
+
13
+ def <(other)
14
+ other > value
13
15
  end
14
- def >=(term)
15
- term <= value
16
+
17
+ def >=(other)
18
+ other <= value
16
19
  end
17
- def > (term)
18
- term < value
20
+
21
+ def >(other)
22
+ other < value
19
23
  end
20
- def ==(term)
21
- term == value
24
+
25
+ def ==(other)
26
+ other == value
22
27
  end
23
28
 
24
- def *(term)
25
- term * value
29
+ def *(other)
30
+ other * value
26
31
  end
27
-
28
- def +(term)
29
- term + value
32
+
33
+ def +(other)
34
+ other + value
30
35
  end
31
36
 
32
- def -(term)
33
- -1 * term + value
37
+ def -(other)
38
+ -1 * other + value
34
39
  end
35
40
 
36
41
  def to_s
@@ -1,6 +1,5 @@
1
1
  module Ilp
2
2
  class Constraint
3
-
4
3
  LESS_OR_EQ = :less_or_eq
5
4
  GREATER_OR_EQ = :greater_or_eq
6
5
  EQUALS = :equals
@@ -15,26 +14,22 @@ module Ilp
15
14
  end
16
15
 
17
16
  def vars
18
- terms.vars.uniq
17
+ terms.vars
19
18
  end
20
19
 
21
20
  def to_function_s
22
- "#{function_name || 'constraint'}(#{vars.map(&:name).join(', ')})"
21
+ "#{function_name || 'constraint'}(#{vars.map!(&:name).join(', ')})"
23
22
  end
24
23
 
24
+ SIGN_TO_STRING = {
25
+ LESS_OR_EQ => "<=",
26
+ GREATER_OR_EQ => ">=",
27
+ EQUALS => "=="
28
+ }
29
+
25
30
  def to_s
26
- case @type
27
- when LESS_OR_EQ
28
- sign = '<='
29
- when GREATER_OR_EQ
30
- sign = '>='
31
- when EQUALS
32
- sign = '='
33
- else
34
- sign = '??'
35
- end
36
- "#{@terms.to_s} #{sign} #{@bound}"
31
+ sign = SIGN_TO_STRING(@type) || "??"
32
+ "#{@terms} #{sign} #{@bound}"
37
33
  end
38
-
39
34
  end
40
35
  end
@@ -1,6 +1,5 @@
1
1
  module Ilp
2
2
  class Objective
3
-
4
3
  MINIMIZE = :min
5
4
  MAXIMIZE = :max
6
5
 
@@ -10,17 +9,12 @@ module Ilp
10
9
  @terms = Ilp::Term.new(@terms) if @terms.is_a? Ilp::Var
11
10
  @terms = Ilp::TermArray.new([@terms]) if @terms.is_a? Ilp::Term
12
11
  @terms.normalize!
13
- cste = @terms.send(:pop_constant)
14
- puts "Removing constant [#{cste}] in objective" if cste != 0
12
+ @terms.send(:pop_constant)
15
13
  @objective_function = objective_function
16
14
  end
17
15
 
18
16
  def to_s
19
- str = ""
20
- str << (@objective_function == :max ? "Maximize" : "Minimize")
21
- str << "\n "
22
- str << terms.to_s
17
+ "#{(@objective_function == :max ? 'Maximize' : 'Minimize')}\n #{terms}"
23
18
  end
24
-
25
19
  end
26
20
  end
@@ -1,6 +1,5 @@
1
1
  module Ilp
2
2
  class Term
3
-
4
3
  attr_accessor :mult, :var
5
4
 
6
5
  def initialize(var, mult = 1)
@@ -8,30 +7,29 @@ module Ilp
8
7
  @var = var
9
8
  end
10
9
 
11
- def +(vars)
12
- Ilp::TermArray.new([self]) + vars
10
+ def +(other)
11
+ Ilp::TermArray.new([self]) + other
13
12
  end
14
13
 
15
- def -(vars)
16
- Ilp::TermArray.new([self]) - vars
14
+ def -(other)
15
+ Ilp::TermArray.new([self]) - other
17
16
  end
18
17
 
19
- def ==(vars)
20
- Ilp::TermArray.new([self]) == vars
18
+ def ==(other)
19
+ Ilp::TermArray.new([self]) == other
21
20
  end
22
21
 
23
- def <=(vars)
24
- Ilp::TermArray.new([self]) <= vars
22
+ def <=(other)
23
+ Ilp::TermArray.new([self]) <= other
25
24
  end
26
25
 
27
- def >=(vars)
28
- Ilp::TermArray.new([self]) >= vars
26
+ def >=(other)
27
+ Ilp::TermArray.new([self]) >= other
29
28
  end
30
29
 
31
-
32
- def *(mult)
33
- raise ArgumentError, 'Argument is not numeric' unless mult.is_a? Numeric
34
- Ilp::Term.new(@var, @mult * mult)
30
+ def *(other)
31
+ raise ArgumentError, "Argument is not numeric" unless other.is_a? Numeric
32
+ Ilp::Term.new(@var, @mult * other)
35
33
  end
36
34
 
37
35
  def coerce(num)
@@ -1,86 +1,87 @@
1
1
  module Ilp
2
2
  class TermArray
3
- include Enumerable
3
+ extend Forwardable
4
4
 
5
5
  attr_accessor :terms
6
+ def_delegators :@terms, :map, :each, :size
6
7
 
7
8
  def initialize(terms)
8
9
  @terms = terms
9
10
  end
10
11
 
11
- def +(vars)
12
+ def +(other)
12
13
  new_terms = terms.dup
13
- if vars.is_a? Numeric
14
- new_terms << vars
15
- elsif vars.is_a? Ilp::Var
16
- new_terms << Ilp::Term.new(vars)
17
- elsif vars.is_a? Ilp::Term
18
- new_terms << vars
19
- elsif vars.is_a? Ilp::TermArray
20
- new_terms.concat(vars.terms)
14
+ case other
15
+ when Numeric
16
+ new_terms << other
17
+ when Ilp::Var
18
+ new_terms << Ilp::Term.new(other)
19
+ when Ilp::Term
20
+ new_terms << other
21
+ when Ilp::TermArray
22
+ new_terms.concat(other.terms)
21
23
  else
22
- raise ArgumentError, "Argument is not allowed: #{vars} of type #{vars.class}"
24
+ raise ArgumentError, "Argument is not allowed: #{other} of type #{other.class}"
23
25
  end
24
26
  TermArray.new(new_terms)
25
27
  end
26
28
 
27
- def -(vars)
28
- self + -1 * vars
29
+ def -(other)
30
+ self + -1 * other
29
31
  end
30
32
 
31
- def *(mult)
32
- raise ArgumentError, 'Argument is not numeric' unless mult.is_a? Numeric
33
- new_terms = terms.map { |term| term * mult }
33
+ def *(other)
34
+ raise ArgumentError, 'Argument is not numeric' unless other.is_a? Numeric
35
+ new_terms = terms.map { |term| term * other }
34
36
  TermArray.new(new_terms)
35
37
  end
36
38
 
37
39
  # cste + nb * var + nb * var...
38
40
  def normalize!
39
- constant = @terms.select{ |t| t.is_a? Numeric }.reduce(:+)
40
- hterms = @terms.select{ |t| t.is_a? Ilp::Term }.group_by(&:var)
41
- @terms = []
42
- constant ||= 0
43
- @terms << constant
44
- reduced = hterms.map do |v, ts|
45
- if ts.count == 1
46
- ts.first
47
- else
48
- ts.reduce(Ilp::Term.new(v, 0)) { |v1, v2| v1.mult += v2.mult; v1 }
41
+ constant = 0
42
+ hterms = {}
43
+ @terms.each do |term|
44
+ case term
45
+ when Numeric
46
+ constant += term
47
+ when Ilp::Term
48
+ v = term.var
49
+ hterms[v] ||= Ilp::Term.new(v, 0)
50
+ hterms[v].mult += term.mult
49
51
  end
50
- end.reject { |term| term.mult == 0 }
51
- @terms.concat reduced
52
+ end
53
+ reduced = hterms.map { |_, term| term unless term.mult.zero? }
54
+ reduced.compact!
55
+ @terms = [constant].concat reduced
52
56
  self
53
57
  end
54
58
 
55
- def <=(value)
56
- Ilp::Constraint.new(self, Ilp::Constraint::LESS_OR_EQ, value)
59
+ def <=(other)
60
+ Ilp::Constraint.new(self, Ilp::Constraint::LESS_OR_EQ, other)
57
61
  end
58
62
 
59
- def >=(value)
60
- Ilp::Constraint.new(self, Ilp::Constraint::GREATER_OR_EQ, value)
63
+ def >=(other)
64
+ Ilp::Constraint.new(self, Ilp::Constraint::GREATER_OR_EQ, other)
61
65
  end
62
66
 
63
- def ==(value)
64
- Ilp::Constraint.new(self, Ilp::Constraint::EQUALS, value)
67
+ def ==(other)
68
+ Ilp::Constraint.new(self, Ilp::Constraint::EQUALS, other)
65
69
  end
66
70
 
67
71
  def coerce(value)
68
72
  [Ilp::Constant.new(value), self]
69
73
  end
70
74
 
71
- def each(&block)
72
- @terms.each(&block)
73
- end
74
-
75
75
  def to_s
76
- @terms.map(&:to_s).join(' ')
76
+ @terms.map(&:to_s).join(" ")
77
77
  end
78
78
 
79
79
  def vars
80
80
  @terms.map(&:var)
81
81
  end
82
82
 
83
- private
83
+ private
84
+
84
85
  # Must be normalized!
85
86
  def pop_constant
86
87
  terms.slice!(0)
@@ -9,7 +9,7 @@ module Ilp
9
9
  def initialize(name: nil, kind: INTEGER_KIND, lower_bound: nil, upper_bound: nil)
10
10
  @kind = kind
11
11
  @name = name
12
- @name = ('a'..'z').to_a.shuffle[0,8].join if name.nil?
12
+ @name = ("a".."z").to_a.sample(8).join if name.nil?
13
13
  @lower_bound = lower_bound
14
14
  @upper_bound = upper_bound
15
15
  end
@@ -23,32 +23,32 @@ module Ilp
23
23
  @lower_bound..@upper_bound
24
24
  end
25
25
 
26
- def +(vars)
27
- Ilp::Term.new(self) + vars
26
+ def +(other)
27
+ Ilp::Term.new(self) + other
28
28
  end
29
29
 
30
- def -(vars)
31
- Ilp::Term.new(self) - vars
30
+ def -(other)
31
+ Ilp::Term.new(self) - other
32
32
  end
33
33
 
34
34
  def -@
35
35
  Ilp::Term.new(self, -1)
36
36
  end
37
37
 
38
- def *(mult)
39
- Ilp::Term.new(self) * mult
38
+ def *(other)
39
+ Ilp::Term.new(self) * other
40
40
  end
41
41
 
42
- def ==(vars)
43
- Ilp::Term.new(self) == vars
42
+ def ==(other)
43
+ Ilp::Term.new(self) == other
44
44
  end
45
45
 
46
- def <=(vars)
47
- Ilp::Term.new(self) <= vars
46
+ def <=(other)
47
+ Ilp::Term.new(self) <= other
48
48
  end
49
49
 
50
- def >=(vars)
51
- Ilp::Term.new(self) >= vars
50
+ def >=(other)
51
+ Ilp::Term.new(self) >= other
52
52
  end
53
53
 
54
54
  def coerce(num)
@@ -56,7 +56,7 @@ module Ilp
56
56
  end
57
57
 
58
58
  def to_s
59
- name
59
+ name.to_s
60
60
  end
61
61
  end
62
62
  end