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 +4 -4
- data/lib/y_support/abstract_algebra.rb +226 -0
- data/lib/y_support/all.rb +1 -0
- data/lib/y_support/version.rb +1 -1
- data/test/abstract_algebra_test.rb +145 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7b23faa0d465f3f3a15b4528d5fcfb0a32c6c7f
|
4
|
+
data.tar.gz: 3ef03c2bc2154ff8b552f388726daf8b7a82ab8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/lib/y_support/version.rb
CHANGED
@@ -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.
|
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
|