cassowary-ruby 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/strength.rb ADDED
@@ -0,0 +1,31 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class Strength
5
+ attr_accessor :name, :symbolic_weight
6
+
7
+ def initialize(name = nil, symbolic_weight = nil)
8
+ self.name = name
9
+ self.symbolic_weight = symbolic_weight
10
+ end
11
+
12
+ def required?
13
+ self == RequiredStrength
14
+ end
15
+
16
+ def inspect
17
+ "#{name}"
18
+ end
19
+
20
+ def each
21
+ [RequiredStrength, StrongStrength, MediumStrength, WeakStrength].each do |str|
22
+ yield str
23
+ end
24
+ end
25
+
26
+ RequiredStrength = new "required"
27
+ StrongStrength = new "strong", SymbolicWeight.new([1.0])
28
+ MediumStrength = new "medium", SymbolicWeight.new([0.0, 1.0])
29
+ WeakStrength = new "weak", SymbolicWeight.new([0.0, 0.0, 1.0])
30
+ end
31
+ end
@@ -0,0 +1,115 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class SymbolicWeight
5
+ include Enumerable
6
+ include Comparable
7
+
8
+ StrengthLevels = 3
9
+
10
+ def initialize(levels = {})
11
+ @levels = [0.0] * StrengthLevels
12
+ case levels
13
+ when Hash
14
+ levels.each_pair do |k, v|
15
+ self[k] = v
16
+ end
17
+ when Array
18
+ levels.each_with_index do |e, idx|
19
+ self[idx] = e
20
+ end
21
+ else
22
+ raise InternalError
23
+ end
24
+ end
25
+
26
+ def each(*args, &block)
27
+ @levels.each(*args, &block)
28
+ end
29
+
30
+ def [](idx)
31
+ @levels[idx]
32
+ end
33
+
34
+ def []=(idx, value)
35
+ @levels[idx] = value
36
+ end
37
+
38
+ def *(n)
39
+ raise InternalError unless n.is_a? Numeric
40
+ result = SymbolicWeight.new
41
+ each_with_index do |e, idx|
42
+ result[idx] = e * n
43
+ end
44
+ result
45
+ end
46
+
47
+ def /(n)
48
+ raise InternalError unless n.is_a? Numeric
49
+ result = SymbolicWeight.new
50
+ each_with_index do |e, idx|
51
+ result[idx] = e / n
52
+ end
53
+ result
54
+ end
55
+
56
+ def +(n)
57
+ raise InternalError unless n.is_a? SymbolicWeight
58
+ result = SymbolicWeight.new
59
+ each_with_index do |e, idx|
60
+ result[idx] = e + n[idx]
61
+ end
62
+ result
63
+ end
64
+
65
+ def -(n)
66
+ raise InternalError unless n.is_a? SymbolicWeight
67
+ result = SymbolicWeight.new
68
+ each_with_index do |e, idx|
69
+ result[idx] = e - n[idx]
70
+ end
71
+ result
72
+ end
73
+
74
+ def <=>(other)
75
+ raise InternalError unless other.is_a? SymbolicWeight
76
+ each_with_index do |e, idx|
77
+ return -1 if e < other[idx]
78
+ return 1 if e > other[idx]
79
+ end
80
+ 0
81
+ end
82
+
83
+ def cl_approx(s)
84
+ raise InternalError unless s.is_a? SymbolicWeight
85
+ each_with_index do |e, idx|
86
+ return false unless e.cl_approx(s[idx])
87
+ end
88
+ true
89
+ end
90
+
91
+ def cl_approx_zero
92
+ cl_approx Zero
93
+ end
94
+
95
+ def definitely_negative
96
+ epsilon = SimplexSolver::Epsilon
97
+ nepsilon = 0.0 - epsilon
98
+ each do |e|
99
+ return true if e < nepsilon
100
+ return false if e > epsilon
101
+ end
102
+ false
103
+ end
104
+
105
+ def symbolic_weight?
106
+ true
107
+ end
108
+
109
+ def inspect
110
+ "[" + @levels.join(",") + "]"
111
+ end
112
+
113
+ Zero = new([0.0] * StrengthLevels)
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ module Equalities
5
+ def cn_equal(expr, strength = Strength::RequiredStrength, weight = 1.0)
6
+ cn_equality(LinearEquation, self - expr, strength, weight)
7
+ end
8
+
9
+ def cn_geq(expr, strength = Strength::RequiredStrength, weight = 1.0)
10
+ cn_equality(LinearInequality, self - expr, strength, weight)
11
+ end
12
+
13
+ def cn_leq(expr, strength = Strength::RequiredStrength, weight = 1.0)
14
+ expr = expr.as_linear_expression if expr.is_a?(Numeric)
15
+ cn_equality(LinearInequality, expr - self, strength, weight)
16
+ end
17
+
18
+ private
19
+ def cn_equality(klass, expr, strength, weight)
20
+ cn = klass.new
21
+ cn.expression = expr
22
+ cn.strength = strength
23
+ cn.weight = weight
24
+ cn
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class AbstractVariable
5
+ attr_accessor :name
6
+
7
+ def initialize(hash = {})
8
+ self.name = hash[:name]
9
+ end
10
+
11
+ def dummy?
12
+ false
13
+ end
14
+
15
+ def external?
16
+ raise NotImplementedError, "my subclass should have implemented #external?"
17
+ end
18
+
19
+ def pivotable?
20
+ raise NotImplementedError, "my subclass should have implemented #pivotable?"
21
+ end
22
+
23
+ def restricted?
24
+ raise NotImplementedError, "my subclass should have implemented #restricted?"
25
+ end
26
+
27
+ def inspect
28
+ if name
29
+ "#{name}"
30
+ else
31
+ "<CV#0x#{object_id.to_s(16)}>"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class DummyVariable < AbstractVariable
5
+ def dummy?
6
+ true
7
+ end
8
+
9
+ def external?
10
+ false
11
+ end
12
+
13
+ def pivotable?
14
+ false
15
+ end
16
+
17
+ def restricted?
18
+ true
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class ObjectiveVariable < AbstractVariable
5
+ def external?
6
+ false
7
+ end
8
+
9
+ def pivotable?
10
+ false
11
+ end
12
+
13
+ def restricted?
14
+ false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class SlackVariable < AbstractVariable
5
+ def external?
6
+ false
7
+ end
8
+
9
+ def pivotable?
10
+ true
11
+ end
12
+
13
+ def restricted?
14
+ true
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ module Cassowary
4
+ class Variable < AbstractVariable
5
+ include Equalities
6
+
7
+ attr_accessor :value
8
+
9
+ def initialize(hash)
10
+ super
11
+ self.value = hash[:value]
12
+ end
13
+
14
+ def *(expr)
15
+ self.as_linear_expression * expr
16
+ end
17
+
18
+ def +(expr)
19
+ self.as_linear_expression + expr
20
+ end
21
+
22
+ def -(expr)
23
+ self.as_linear_expression - expr
24
+ end
25
+
26
+ def /(expr)
27
+ self.as_linear_expression / expr
28
+ end
29
+
30
+ def as_linear_expression
31
+ expr = LinearExpression.new
32
+ expr.terms[self] = 1.0
33
+ expr
34
+ end
35
+
36
+ def external?
37
+ true
38
+ end
39
+
40
+ def pivotable?
41
+ false
42
+ end
43
+
44
+ def restricted?
45
+ false
46
+ end
47
+
48
+ def -@
49
+ -1.0.as_linear_expression * self
50
+ end
51
+
52
+ def inspect
53
+ "#{super}[#{value.inspect}]"
54
+ end
55
+ end
56
+ end
data/lib/variables.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "variables/abstract_variable"
2
+ require "variables/dummy_variable"
3
+ require "variables/objective_variable"
4
+ require "variables/slack_variable"
5
+ require "variables/variable"
@@ -0,0 +1,22 @@
1
+ require File.expand_path("../test_helper", __FILE__)
2
+
3
+ class AbstractMethodsTest < Test::Unit::TestCase
4
+ def test_constraint
5
+ assert_raise NotImplementedError do
6
+ Cassowary::Constraint.new.expression
7
+ end
8
+ end
9
+
10
+ def test_abstract_variable
11
+ var = Cassowary::AbstractVariable.new
12
+ assert_raise NotImplementedError do
13
+ var.external?
14
+ end
15
+ assert_raise NotImplementedError do
16
+ var.pivotable?
17
+ end
18
+ assert_raise NotImplementedError do
19
+ var.restricted?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,184 @@
1
+ require File.expand_path("../test_helper", __FILE__)
2
+
3
+ class CassowaryTests < Test::Unit::TestCase
4
+ include Cassowary
5
+
6
+ def test_add_delete1
7
+ x = Variable.new(name: 'x')
8
+ solver = SimplexSolver.new
9
+ solver.add_constraint x.cn_equal(100.0, Strength::WeakStrength)
10
+ c10 = x.cn_leq 10.0
11
+ c20 = x.cn_leq 20.0
12
+ solver.add_constraint c10
13
+ solver.add_constraint c20
14
+ assert x.value.cl_approx(10.0)
15
+
16
+ solver.remove_constraint c10
17
+ assert x.value.cl_approx(20.0)
18
+
19
+ solver.remove_constraint c20
20
+ assert x.value.cl_approx(100.0)
21
+
22
+ c10again = x.cn_leq 10.0
23
+ solver.add_constraint c10
24
+ solver.add_constraint c10again
25
+ assert x.value.cl_approx(10.0)
26
+
27
+ solver.remove_constraint c10
28
+ assert x.value.cl_approx(10.0)
29
+
30
+ solver.remove_constraint c10again
31
+ assert x.value.cl_approx(100.0)
32
+ end
33
+
34
+ def test_add_delete2
35
+ x = Variable.new name: 'x'
36
+ y = Variable.new name: 'y'
37
+
38
+ solver = SimplexSolver.new
39
+ solver.add_constraint x.cn_equal(100.0, Strength::WeakStrength)
40
+ solver.add_constraint y.cn_equal(120.0, Strength::StrongStrength)
41
+
42
+ c10 = x.cn_leq(10.0)
43
+ c20 = x.cn_leq(20.0)
44
+ solver.add_constraint c10
45
+ solver.add_constraint c20
46
+ assert x.value.cl_approx(10.0)
47
+ assert y.value.cl_approx(120.0)
48
+
49
+ solver.remove_constraint c10
50
+ assert x.value.cl_approx 20.0
51
+ assert y.value.cl_approx 120.0
52
+
53
+ cxy = (x * 2).cn_equal y
54
+ solver.add_constraint cxy
55
+ assert x.value.cl_approx 20
56
+ assert y.value.cl_approx 40
57
+
58
+ solver.remove_constraint c20
59
+ assert x.value.cl_approx 60
60
+ assert y.value.cl_approx 120
61
+
62
+ solver.remove_constraint cxy
63
+ assert x.value.cl_approx 100
64
+ assert y.value.cl_approx 120
65
+
66
+ cxy2 = (x * 1.as_linear_expression).cn_equal(1000)
67
+ solver.add_constraint cxy2
68
+ assert x.value.cl_approx 1000
69
+ end
70
+
71
+ def test_add_delete3
72
+ x = Variable.new name: 'x'
73
+ solver = SimplexSolver.new
74
+ c1 = x.cn_equal 100, Strength::WeakStrength, 5
75
+ c2 = x.cn_equal 200, Strength::WeakStrength
76
+
77
+ solver.add_constraint c1
78
+ solver.add_constraint c2
79
+ assert x.value.cl_approx 100
80
+
81
+ solver.remove_constraint c1
82
+ assert x.value.cl_approx 200
83
+ end
84
+
85
+ def test_inconsistent1
86
+ x = Variable.new name: 'x'
87
+ solver = SimplexSolver.new
88
+ solver.add_constraint x.cn_equal 10
89
+ assert_raise RequiredFailure do
90
+ solver.add_constraint x.cn_equal 5
91
+ end
92
+ end
93
+
94
+ def test_inconsistent2
95
+ x = Variable.new name: 'x'
96
+ solver = SimplexSolver.new
97
+ solver.add_constraint x.cn_geq 10
98
+ assert_raise RequiredFailure do
99
+ solver.add_constraint x.cn_leq 5
100
+ end
101
+ end
102
+
103
+ def test_stay1
104
+ x = Variable.new name: 'x', value: 20
105
+ solver = SimplexSolver.new
106
+
107
+ solver.add_stay x, Strength::WeakStrength
108
+ assert x.value.cl_approx 20
109
+ end
110
+
111
+ def test_two_solutions
112
+ x = Variable.new name: 'x'
113
+ y = Variable.new name: 'y'
114
+
115
+ solver = SimplexSolver.new
116
+ solver.add_constraint x.cn_leq y
117
+ solver.add_constraint y.cn_equal x + 3
118
+ solver.add_constraint x.cn_equal 10, Strength::WeakStrength
119
+ solver.add_constraint y.cn_equal 10, Strength::WeakStrength
120
+
121
+ assert(x.value.cl_approx(10) && y.value.cl_approx(13) ||
122
+ x.value.cl_approx(7) && y.value.cl_approx(10))
123
+ end
124
+
125
+ def test_weighted1
126
+ x = Variable.new name: 'x'
127
+ solver = SimplexSolver.new
128
+
129
+ c15 = x.cn_equal 15, Strength::WeakStrength
130
+ c20 = x.cn_equal 20, Strength::WeakStrength, 2
131
+
132
+ solver.add_constraint c15
133
+ assert x.value.cl_approx 15
134
+
135
+ solver.add_constraint c20
136
+ assert x.value.cl_approx 20
137
+
138
+ solver.remove_constraint c20
139
+ assert x.value.cl_approx 15
140
+ end
141
+
142
+ def test_edit1
143
+ x = Variable.new name: 'x', value: 20
144
+ y = Variable.new name: 'y', value: 30
145
+
146
+ solver = SimplexSolver.new
147
+ solver.add_stay x, Strength::WeakStrength
148
+ solver.add_constraint x.cn_geq 10
149
+ solver.add_constraint x.cn_leq 100
150
+ solver.add_constraint x.cn_equal y * 2
151
+ assert x.value.cl_approx 20
152
+ assert y.value.cl_approx 10
153
+
154
+ solver.add_edit_var y, Strength::StrongStrength
155
+ solver.begin_edit
156
+ solver.suggest_value y, 35
157
+ solver.resolve
158
+ assert x.value.cl_approx 70
159
+ assert y.value.cl_approx 35
160
+
161
+ solver.suggest_value y, 80
162
+ solver.resolve
163
+ assert x.value.cl_approx 100
164
+ assert y.value.cl_approx 50
165
+
166
+ solver.suggest_value y, 25
167
+ solver.resolve
168
+ assert x.value.cl_approx 50
169
+ assert y.value.cl_approx 25
170
+
171
+ solver.end_edit
172
+ assert x.value.cl_approx 50
173
+ assert y.value.cl_approx 25
174
+
175
+ solver.add_edit_var x, Strength::StrongStrength
176
+ solver.begin_edit
177
+ solver.suggest_value x, 44.0
178
+ solver.resolve
179
+ assert x.value.cl_approx 44
180
+ assert y.value.cl_approx 22
181
+
182
+ solver.end_edit
183
+ end
184
+ end
data/test/test_ext.rb ADDED
@@ -0,0 +1,38 @@
1
+ require File.expand_path("../test_helper", __FILE__)
2
+
3
+ class ExtTests < Test::Unit::TestCase
4
+ def test_float_approx_zero
5
+ assert !1.1.cl_approx_zero
6
+ assert 0.0.cl_approx_zero
7
+ assert 0.1e-8.cl_approx_zero
8
+ end
9
+
10
+ def test_float_approx
11
+ assert 1.1.cl_approx 1.1
12
+ end
13
+
14
+ def test_float_negative
15
+ assert -1.1.definitely_negative
16
+ assert -0.1e-5.definitely_negative
17
+ assert ! -0.1e-8.definitely_negative
18
+ end
19
+
20
+ def test_numeric_as_linear_expression
21
+ expr = 1.as_linear_expression
22
+ assert expr.terms.empty?
23
+ assert expr.constant == 1.to_f
24
+
25
+ expr = 1.1.as_linear_expression
26
+ assert expr.terms.empty?
27
+ assert expr.constant == 1.1
28
+ end
29
+
30
+ def test_object_approx
31
+ assert "foo".cl_approx "foo"
32
+ assert ! Object.new.cl_approx(Object.new)
33
+ end
34
+
35
+ def test_object_symbolic_weight
36
+ assert !Object.new.symbolic_weight?
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ if ENV["coverage"]
4
+ require "simplecov"
5
+ SimpleCov.start do
6
+ add_filter "/test/"
7
+ minimum_coverage 90
8
+ end
9
+ end
10
+
11
+ require "test/unit"
12
+ require "cassowary"
@@ -0,0 +1,33 @@
1
+ require File.expand_path("../test_helper", __FILE__)
2
+
3
+ class VariablesTests < Test::Unit::TestCase
4
+ def test_operations
5
+ x = Cassowary::Variable.new name: 'x', value: 20
6
+ expr = x / 10
7
+ assert expr.constant == 0
8
+ assert expr.terms[x] == 0.1
9
+
10
+ expr = x * 10
11
+ assert expr.terms[x] == 10
12
+
13
+ expr = x - 10
14
+ assert expr.constant == -10
15
+
16
+ expr = x + 10
17
+ assert expr.constant == 10
18
+
19
+ expr = -x
20
+ assert expr.terms[x] == -1
21
+ end
22
+
23
+ def test_inspect
24
+ x = Cassowary::Variable.new name: 'x', value: 21.1
25
+ assert_equal "x[21.1]", x.inspect
26
+
27
+ x = Cassowary::Variable.new name: 'x'
28
+ assert_equal "x[nil]", x.inspect
29
+
30
+ x = Cassowary::SlackVariable.new
31
+ assert_equal "<CV#0x" + x.object_id.to_s(16) + ">", x.inspect
32
+ end
33
+ end