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