y_support 1.0.0 → 1.1.0

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.
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