ruby-cbc 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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