y_support 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39372bbe12e6643f53e78e2b4fd8666477554a67
4
- data.tar.gz: 9d1c8f648ab7369104ba1ca178fe6ca4abec4cf7
3
+ metadata.gz: f7b23faa0d465f3f3a15b4528d5fcfb0a32c6c7f
4
+ data.tar.gz: 3ef03c2bc2154ff8b552f388726daf8b7a82ab8c
5
5
  SHA512:
6
- metadata.gz: 7e3457d0f805683af3c83018bb7174b161868f614a77b90e7ecc022937ec9b4768fbbfc904717b97beb71dbe0f96296cac46fe60f52db06f89c80c9afa5ce755
7
- data.tar.gz: 8a57a47dcb40f4c286ffcdf3ad19c0ccd3cd6a58bb32f7d32861701c6907c4734db0eb6e72b6490afcf43e8c2797b0661fda1e7166ed15267736729d45fb06b8
6
+ metadata.gz: 53bcbc5db8d0cdedef378c15d87414d76f12a4d65b42274b9b224fd8d95c36620249057c6deac5aa4b03623b925dcdf75a88899975ccd4b46e33c54ef4419e21
7
+ data.tar.gz: 5f65f0d77dd1dd2b8808b40b9218b934287636ac5436a785c0ef465bbcbde96fb74815b8c3c9d365faa37d50b8cb2b0cfe0f151c81ea213bce3992ee159d09bd
@@ -0,0 +1,226 @@
1
+ #encoding: utf-8
2
+
3
+ require 'y_support'
4
+ require 'y_support/null_object'
5
+
6
+ # Rudiments of abstract algebra in Ruby.
7
+ #
8
+ # Some objects (users, mixins...) might ask from other classes to have things
9
+ # defined such as operators #+, #*, or to have a specific instance that behaves
10
+ # additive identity element (ie. zero), or multiplicative identity element (ie.
11
+ # one). The essence of some of these requirements is captured by the abstract
12
+ # algebraic notions of magma, monoid, ring, field...
13
+ #
14
+ # At the moment being, y_support/abstract_algebra does not aim as high as to
15
+ # comprehensively support monoids, rings, fields and the plethora of abstract
16
+ # algebraic stuff. Support is provided to such degree, as to make some practical
17
+ # problems tractable. (Our original concern was with Matrix instances filled
18
+ # with non-numeric objects, whose #+ operator was not coercible to work with
19
+ # numerics.)
20
+ #
21
+
22
+ # Adds abstract algebra concepts to Ruby.
23
+ #
24
+ module Algebra
25
+ # A Monoid requires:
26
+ #
27
+ # Closed and associative addition: #add method
28
+ # Additive identity element: #additive_identity
29
+ #
30
+ module Monoid
31
+ def self.included receiver
32
+ receiver.extend self::ClassMethods
33
+ end
34
+
35
+ def + summand; add summand end
36
+
37
+ module ClassMethods
38
+ def zero; additive_identity end
39
+ end
40
+ end
41
+
42
+ # A group is a monoid with additive inverse.
43
+ #
44
+ # additive inversion: #additive_inverse
45
+ #
46
+ module Group
47
+ include Monoid
48
+ def -@; additive_inverse end
49
+ def - subtrahend; add subtrahend.additive_inverse end
50
+ end
51
+
52
+ # A group that fulfills the condition of commutativity
53
+ #
54
+ # ( a.add b == b.add a ).
55
+ #
56
+ module AbelianGroup
57
+ include Group
58
+ end
59
+
60
+ # A ring is a commutative group with multiplication.
61
+ #
62
+ # multiplication: #multiply (associative, distributive)
63
+ # multiplicative identity element: #multiplicative_identity
64
+ #
65
+ module Ring
66
+ include AbelianGroup
67
+ def * multiplicand; multiply multiplicand end
68
+ def one; multiplicative_identity end
69
+ end
70
+
71
+ # A field is a ring that can do division.
72
+ #
73
+ module Field
74
+ include Ring
75
+ def inverse; multiplicative_inverse end
76
+ def / divisor; multiply divisor.multiplicative_inverse end
77
+ end
78
+ end
79
+
80
+
81
+ # Patching Integer with Algebra::Ring compliance methods.
82
+ #
83
+ class << Integer
84
+ def additive_identity; 0 end
85
+ alias zero additive_identity
86
+ def add( other ); self + other end
87
+ def additive_inverse; -self end
88
+ def multiply( other ); self * other end
89
+ def multiplicative_identity; 1 end
90
+ end
91
+
92
+ # Patching Float with Algebra::Field compliance methods.
93
+ #
94
+ class << Float
95
+ def additive_identity; 0.0 end
96
+ alias zero additive_identity
97
+ def add( other ); self + other end
98
+ def additive_inverse; -self end
99
+ def multiply( other ); self * other end
100
+ def multiplicative_identity; 1.0 end
101
+ alias one multiplicative_identity
102
+ def multiplicative_inverse; 1.0 / self end
103
+ end
104
+
105
+ # Patching Rational with Algebra::Field compliance methods.
106
+ #
107
+ class << Rational
108
+ def additive_identity; Rational 0, 1 end
109
+ alias zero additive_identity
110
+ def add( other ); self + other end
111
+ def additive_inverse; -self end
112
+ def multiply( other ); self * other end
113
+ def multiplicative_identity; Rational 1, 1 end
114
+ alias one multiplicative_identity
115
+ def multiplicative_inverse; Rational( 1, 1 ) / self end
116
+ end
117
+
118
+ # Patching Complex with #zero method.
119
+ #
120
+ class << Complex
121
+ def additive_identity; Complex 0.0, 0.0 end
122
+ alias zero additive_identity
123
+ def add( other ); self + other end
124
+ def additive_inverse; -self end
125
+ def multiply( other ); self * other end
126
+ def multiplicative_identity; Complex 1, 0 end
127
+ alias one multiplicative_identity
128
+ def multiplicative_inverse; Complex( 1, 0 ) / self end
129
+ end
130
+
131
+
132
+
133
+ # Wildcard zero, stronger than ordinary numeric literal 0.
134
+ #
135
+ ( WILDCARD_ZERO = NullObject.new ).instance_exec {
136
+ ɪ = self
137
+ singleton_class.class_exec { define_method :zero do ɪ end }
138
+ def * other; other.class.zero end
139
+ def / other
140
+ self unless other.zero?
141
+ fail ZeroDivisionError, "The divisor is zero! (#{other})"
142
+ end
143
+ def + other; other end
144
+ def - other; -other end
145
+ def coerce other; return other, other.class.zero end
146
+ def zero?; true end
147
+ def to_s; "∅" end
148
+ def inspect; to_s end
149
+ def to_f; 0.0 end
150
+ def to_i; 0 end
151
+ def == other
152
+ z = begin
153
+ other.class.zero
154
+ rescue NoMethodError
155
+ return false
156
+ end
157
+ other == z
158
+ end
159
+ }
160
+
161
+
162
+ # As a matter of fact, current version of the Matrix class (by Marc-Andre
163
+ # Lafortune) does not work with physical magnitudes. It is a feature of the
164
+ # physical magnitudes, that they do not allow themselves summed with plain
165
+ # numbers or incompatible magnitudes. But current version of Matrix class,
166
+ # upon matrix multiplication, performs needless addition of the matrix elements
167
+ # to literal numeric 0.
168
+ #
169
+ # The obvious solution is to patch Matrix class so that the needless addition
170
+ # to literal 0 is no longer performed.
171
+ #
172
+ # More systematically, abstract algebra is to be added to Ruby, and Matrix is
173
+ # to require that its elements comply with monoid, group, ring, field, depending
174
+ # on the operation one wants to do with such matrices.
175
+ #
176
+ class Matrix
177
+ # Matrix multiplication.
178
+ #
179
+ def * arg # arg is matrix or vector or number
180
+ case arg
181
+ when Numeric
182
+ rows = @rows.map { |row| row.map { |e| e * arg } }
183
+ return new_matrix rows, column_size
184
+ when Vector
185
+ arg = Matrix.column_vector arg
186
+ result = self * arg
187
+ return result.column 0
188
+ when Matrix
189
+ Matrix.Raise ErrDimensionMismatch if column_size != arg.row_size
190
+ if empty? then # if empty?, then reduce uses WILDCARD_ZERO
191
+ rows = Array.new row_size do |i|
192
+ Array.new arg.column_size do |j|
193
+ ( 0...column_size ).reduce WILDCARD_ZERO do |sum, c|
194
+ sum + arg[c, j] * self[i, c]
195
+ end
196
+ end
197
+ end
198
+ else # if non-empty, reduce proceeds without WILDCARD_ZERO
199
+ rows = Array.new row_size do |i|
200
+ Array.new arg.column_size do |j|
201
+ ( 0...column_size ).map { |c| arg[c, j] * self[i, c] }.reduce :+
202
+ end
203
+ end
204
+ end
205
+ return new_matrix( rows, arg.column_size )
206
+ when SY::Magnitude # newly added - multiplication by a magnitude
207
+ # I am not happy with this explicit switch on SY::Magnitude type here.
208
+ # Perhaps coerce should handle this?
209
+ rows = Array.new row_size do |i|
210
+ Array.new column_size do |j|
211
+ self[i, j] * arg
212
+ end
213
+ end
214
+ return self.class[ *rows ]
215
+ else
216
+ compat_1, compat_2 = arg.coerce self
217
+ return compat_1 * compat_2
218
+ end
219
+ end
220
+
221
+ # Creates a matrix of prescribed dimensions filled with wildcard zeros.
222
+ #
223
+ def Matrix.wildcard_zero r_count, c_count=r_count
224
+ build r_count, c_count do |r, c| WILDCARD_ZERO end
225
+ end
226
+ end
data/lib/y_support/all.rb CHANGED
@@ -8,6 +8,7 @@ require 'y_support/respond_to'
8
8
  require 'y_support/null_object'
9
9
  require 'y_support/inert_recorder'
10
10
  require 'y_support/local_object'
11
+ require 'y_support/abstract_algebra'
11
12
  require 'y_support/misc'
12
13
 
13
14
 
@@ -1,3 +1,3 @@
1
1
  module YSupport
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -0,0 +1,145 @@
1
+ #! /usr/bin/ruby
2
+ #encoding: utf-8
3
+
4
+ require 'test/unit'
5
+ require 'shoulda'
6
+
7
+ class AbstractAlgebraTest < Test::Unit::TestCase
8
+ context "Algebra" do
9
+ setup do
10
+ require 'y_support/abstract_algebra'
11
+ # Define the stupidest monoid:
12
+ @monoid = Class.new { include Algebra::Monoid } # make some class
13
+ zero = @monoid.new # call arbitrary instance zero
14
+ @monoid.class_exec {
15
+ # Define the stupidest #add method.
16
+ define_method :add do |other|
17
+ if self == zero then other
18
+ elsif other == zero then self
19
+ else self.class.addition_table[[self, other]] end
20
+ end
21
+ # Define the stupidest addition table.
22
+ instance_variable_set :@addition_table,
23
+ Hash.new { |ꜧ, k|
24
+ ꜧ[k] = if k[0].object_id <= k[1].object_id
25
+ new # just make up an instance
26
+ else
27
+ ꜧ[k[1], k[0]] # swap operands
28
+ end
29
+ }
30
+ }
31
+ # And refine the @monoid's singleton class.
32
+ @monoid.singleton_class.class_exec { attr_reader :addition_table }
33
+ @monoid.define_singleton_method :additive_identity do zero end
34
+ end
35
+
36
+ should "have working Monoid" do
37
+ m = @monoid.random # choose an instance
38
+
39
+ # #== method
40
+ assert m == m
41
+
42
+ # closure
43
+ # (not tested)
44
+
45
+ # associativity
46
+ n, o = @monoid.random, @monoid.random
47
+ assert ( m + n ) + o == m + ( n + o )
48
+
49
+ # identity element
50
+ assert m + @monoid.zero == m
51
+ assert @monoid.zero + m == m
52
+ end
53
+
54
+ should "have working Group" do
55
+ g = @group.random
56
+
57
+ # (monoid properties not tested)
58
+
59
+ # inverse element
60
+ assert g + (-g) == @group.zero
61
+ assert (-g) + g == @group.zero
62
+ end
63
+
64
+ should "define AbelianGroup" do
65
+ ag = @abelian_group.random
66
+
67
+ # (group properties not tested)
68
+
69
+ # commutativity
70
+ bh = @abelian_group.random
71
+ assert ag + bh == bh + ag
72
+ end
73
+
74
+ should "define Ring" do
75
+ r = @ring.random
76
+
77
+ # (abelian group properties with respect to addition not tested)
78
+
79
+ # (multiplication closure not tested)
80
+
81
+ # multiplication associativity
82
+ s, t = @ring.random, @ring.random
83
+ assert r * ( s * t ) == ( r * s ) * t
84
+
85
+ # multiplication identity
86
+ mi = @ring.one
87
+ assert r * mi == r
88
+ assert mi * r == r
89
+
90
+ # distributivity
91
+ assert r * ( s + t ) == ( r * s ) + ( s * t )
92
+ end
93
+
94
+ should "define Field" do
95
+ f = @field.random
96
+
97
+ # (ring properties not tested)
98
+
99
+ # multiplicative inverse
100
+ mi = @ring.multiplicative_identity
101
+ assert f * f.multiplicative_inverse == mi
102
+ assert f.multiplicative_inverse * f == mi
103
+ end
104
+ end
105
+
106
+ context "numerics" do
107
+ setup do
108
+ require 'y_support/abstract_algebra'
109
+ end
110
+
111
+ should "have patched Integer" do
112
+ assert Integer.zero.equal? 0
113
+ end
114
+
115
+ should "have patched Float" do
116
+ assert Float.zero.equal? 0.0
117
+ end
118
+
119
+ should "have patched Rational" do
120
+ assert Rational.zero.equal? Rational( 0, 0 )
121
+ end
122
+
123
+ should "have patched Complex" do
124
+ assert Complex.zero.equal? Complex( 0, 0 )
125
+ end
126
+ end
127
+
128
+ context "Matrix" do
129
+ setup do
130
+ require 'y_support/abstract_algebra'
131
+ end
132
+
133
+ should "have Matrix.wildcard_zero public instance method" do
134
+ # FIXME
135
+ end
136
+
137
+ should "be able to perform #* with nonnumerics in the matrix" do
138
+ # FIXME
139
+ end
140
+
141
+ should "numeric matrix multiplication still be working normally" do
142
+ # FIXME
143
+ end
144
+ end # context Matrix
145
+ end # class AbstractAlgebraTest
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: y_support
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - boris
@@ -51,6 +51,7 @@ files:
51
51
  - README.md
52
52
  - Rakefile
53
53
  - lib/y_support.rb
54
+ - lib/y_support/abstract_algebra.rb
54
55
  - lib/y_support/all.rb
55
56
  - lib/y_support/core_ext.rb
56
57
  - lib/y_support/core_ext/array.rb
@@ -91,6 +92,7 @@ files:
91
92
  - lib/y_support/typing/object/typing.rb
92
93
  - lib/y_support/unicode.rb
93
94
  - lib/y_support/version.rb
95
+ - test/abstract_algebra_test.rb
94
96
  - test/inert_recorder_test.rb
95
97
  - test/local_object_test.rb
96
98
  - test/misc_test.rb
@@ -126,6 +128,7 @@ specification_version: 4
126
128
  summary: LocalObject, RespondTo, InertRecorder, NullObject, NameMagic, core extensions,
127
129
  typing etc.
128
130
  test_files:
131
+ - test/abstract_algebra_test.rb
129
132
  - test/inert_recorder_test.rb
130
133
  - test/local_object_test.rb
131
134
  - test/misc_test.rb