sy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,102 @@
1
+ #encoding: utf-8
2
+
3
+ # Adds abstract algebra concepts to Ruby.
4
+ #
5
+ module Algebra
6
+ # A Monoid requires:
7
+ #
8
+ # Closed and associative addition: #add method
9
+ # Additive identity element: #additive_identity
10
+ #
11
+ module Monoid
12
+ def + summand; add summand end
13
+ def zero; additive_identity end
14
+ end
15
+
16
+ # A group is a monoid with additive inverse.
17
+ #
18
+ # additive inversion: #additive_inverse
19
+ #
20
+ module Group
21
+ include Monoid
22
+ def -@; additive_inverse end
23
+ def - subtrahend; add subtrahend.additive_inverse end
24
+ end
25
+
26
+ # A group that fulfills the condition of commutativity
27
+ #
28
+ # ( a.add b == b.add a ).
29
+ #
30
+ module AbelianGroup
31
+ include Group
32
+ end
33
+
34
+ # A ring is a commutative group with multiplication.
35
+ #
36
+ # multiplication: #multiply (associative, distributive)
37
+ # multiplicative identity element: #multiplicative_identity
38
+ #
39
+ module Ring
40
+ include AbelianGroup
41
+ def * multiplicand; multiply multiplicand end
42
+ def one; multiplicative_identity end
43
+ end
44
+
45
+ # A field is a ring that can do division.
46
+ #
47
+ module Field
48
+ include Ring
49
+ def inverse; multiplicative_inverse end
50
+ def / divisor; multiply divisor.multiplicative_inverse end
51
+ end
52
+ end
53
+
54
+ # Patching Integer with Algebra::Ring compliance methods.
55
+ #
56
+ class << Integer
57
+ def additive_identity; 0 end
58
+ alias zero additive_identity
59
+ def add( other ); self + other end
60
+ def additive_inverse; -self end
61
+ def multiply( other ); self * other end
62
+ def multiplicative_identity; 1 end
63
+ end
64
+
65
+ # Patching Float with Algebra::Field compliance methods.
66
+ #
67
+ class << Float
68
+ def additive_identity; 0.0 end
69
+ alias zero additive_identity
70
+ def add( other ); self + other end
71
+ def additive_inverse; -self end
72
+ def multiply( other ); self * other end
73
+ def multiplicative_identity; 1.0 end
74
+ alias one multiplicative_identity
75
+ def multiplicative_inverse; 1.0 / self end
76
+ end
77
+
78
+ # Patching Rational with Algebra::Field compliance methods.
79
+ #
80
+ class << Rational
81
+ def additive_identity; Rational 0, 1 end
82
+ alias zero additive_identity
83
+ def add( other ); self + other end
84
+ def additive_inverse; -self end
85
+ def multiply( other ); self * other end
86
+ def multiplicative_identity; Rational 1, 1 end
87
+ alias one multiplicative_identity
88
+ def multiplicative_inverse; Rational( 1, 1 ) / self end
89
+ end
90
+
91
+ # Patching Complex with #zero method.
92
+ #
93
+ class << Complex
94
+ def additive_identity; Complex 0.0, 0.0 end
95
+ alias zero additive_identity
96
+ def add( other ); self + other end
97
+ def additive_inverse; -self end
98
+ def multiply( other ); self * other end
99
+ def multiplicative_identity; Complex 1, 0 end
100
+ alias one multiplicative_identity
101
+ def multiplicative_inverse; Complex( 1, 0 ) / self end
102
+ end
@@ -0,0 +1,219 @@
1
+ #encoding: utf-8
2
+
3
+ # Composition of quantities.
4
+ #
5
+ class SY::Composition < Hash
6
+ # Simplification rules for quantity combinations.
7
+ #
8
+ SR = SIMPLIFICATION_RULES = []
9
+
10
+ # SY::Amount and SY::Amount.relative are disposable:
11
+ SR << -> ꜧ {
12
+ ꜧ.reject! { |qnt, _| qnt == SY::Amount || qnt == SY::Amount.relative }
13
+ }
14
+ # Relative quantities of the composition are absolutized:
15
+ SR << -> ꜧ {
16
+ ꜧ.select { |qnt, _| qnt.relative? }.each { |qnt, exp|
17
+ ꜧ.delete qnt
18
+ ꜧ.update qnt.absolute => exp
19
+ }
20
+ }
21
+ # Any quantities with exponent zero can be deleted:
22
+ SR << -> ꜧ {
23
+ ꜧ.reject! { |_, exp| exp == 0 }
24
+ }
25
+ #
26
+ SR << -> ꜧ {
27
+ begin
28
+ q1, q2, q3 = SY::MoleAmount, SY::LitreVolume, SY::Molarity
29
+ rescue NameError
30
+ return ꜧ
31
+ end
32
+ # transform = -> e1, e2 {
33
+ # return e1, e2 unless e1 != 0 && e2 != 0 && ( e1 <=> 0 ) != ( e2 <=> 0 )
34
+ # n = e1 <=> 0
35
+ # return e1 - n, e2 + n
36
+ # }
37
+ # e1 = ꜧ.delete q1
38
+ # e2 = ꜧ.delete q2
39
+ # ꜧ.merge! q1 => e1, q2 => e2
40
+ # else
41
+ # ꜧ.update q1 => e1, q2 => e2
42
+ # end
43
+ e1 = ꜧ.delete q1
44
+ e2 = ꜧ.delete q2
45
+ if e1 && e2 && e1 > 0 && e2 < 0 then
46
+ e1 -= 1
47
+ e2 += 1
48
+ e3 = ꜧ.delete q3
49
+ ꜧ.update q3 => ( e3 ? e3 + 1 : 1 )
50
+ end
51
+ ꜧ.update q1 => e1 if e1 && e1 != 0
52
+ ꜧ.update q2 => e2 if e2 && e2 != 0
53
+ return ꜧ
54
+ }
55
+
56
+ class << self
57
+ def singular quantity
58
+ self[ SY.Quantity( quantity ) => 1 ]
59
+ end
60
+
61
+ def empty
62
+ self[]
63
+ end
64
+ end
65
+
66
+ # Cache for quantity construction.
67
+ #
68
+ QUANTITY_TABLE = Hash.new { |ꜧ, args|
69
+ if args.keys.include? [:name] || args.keys.include?( :ɴ ) then
70
+ ɴ = args.delete( :name ) || args.delete( :ɴ ) # won't cache name
71
+ ꜧ[args].tap { |ɪ| ɪ.name = ɴ } # recursion
72
+ elsif args.keys.include? :mapping then
73
+ ᴍ = args.delete( :mapping ) # won't cache mapping
74
+ ꜧ[args].tap { |ɪ| ɪ.set_mapping ᴍ } # recursion
75
+ elsif args.keys.include? :relative then
76
+ ʀ = args.delete( :relative ) ? true : false # won't cache :relative
77
+ ꜧ[args].send ʀ ? :relative : :absolute # recursion
78
+ else
79
+ cᴍ = SY::Composition[ args ].simplify
80
+ ꜧ[args] = if cᴍ != args then ꜧ[ cᴍ ] # recursion while #simplify
81
+ else x = cᴍ.expand # we'll try to #expand now
82
+ if x != cᴍ then ꜧ[ x ] # recursion while #expand
83
+ else
84
+ if x.empty? then # use std. ∅ quantity
85
+ SY.Dimension( :∅ ).standard_quantity
86
+ elsif x.singular? then
87
+ x.first[0] # unwrap the quantity
88
+ else # create new quantity
89
+ SY::Quantity.new composition: x
90
+ end
91
+ end
92
+ end
93
+ end
94
+ }
95
+
96
+ # Singular compositions consist of only one quantity.
97
+ #
98
+ def singular?
99
+ size == 1 && first[1] == 1
100
+ end
101
+
102
+ # Atomic compositions are singular compositions, whose quantity dimension
103
+ # is a base dimension.
104
+ #
105
+ def atomic?
106
+ singular? && first[0].dimension.base?
107
+ end
108
+
109
+ # Returns a new instance with same hash.
110
+ #
111
+ def +@
112
+ self.class[ self ]
113
+ end
114
+
115
+ # Negates hash exponents.
116
+ #
117
+ def -@
118
+ self.class[ self.with_values do |v| -v end ]
119
+ end
120
+
121
+ # Merges two compositions.
122
+ #
123
+ def + other
124
+ self.class[ self.merge( other ) { |_, v1, v2| v1 + v2 } ]
125
+ end
126
+
127
+ # Subtracts two compositions.
128
+ #
129
+ def - other
130
+ self + -other
131
+ end
132
+
133
+ # Multiplication by a number.
134
+ #
135
+ def * number
136
+ self.class[ self.with_values do |v| v * number end ]
137
+ end
138
+
139
+ # Division by a number.
140
+ #
141
+ def / number
142
+ self.class[ self.with_values do |val|
143
+ raise TErr, "Compositions with rational exponents " +
144
+ "not implemented!" if val % number != 0
145
+ val / number
146
+ end ]
147
+ end
148
+
149
+ # Simplifies a quantity hash by applying simplification rules.
150
+ #
151
+ def simplify
152
+ ꜧ = self.to_hash
153
+ puts "simplifying #{ꜧ}" if SY::DEBUG
154
+ SIMPLIFICATION_RULES.each { |rule| rule.( ꜧ ) }
155
+ self.class[ ꜧ ].tap { |_| puts "result is #{_}" if SY::DEBUG }
156
+ end
157
+
158
+ # Returns the quantity appropriate to this composition.
159
+ #
160
+ def to_quantity args={}
161
+ # All the work is delegated to the quantity table:
162
+ QUANTITY_TABLE[ args.merge( self ) ]
163
+ end
164
+
165
+ # Directly (without attempts to simplify) creates a new quantity from self.
166
+ # If self is empty or singular, SY::Amount, resp. the singular quantity
167
+ # in question is returned.
168
+ #
169
+ def new_quantity args={}
170
+ SY::Quantity.new args.merge( composition: self )
171
+ end
172
+
173
+ # Dimension of a quantity composition is the sum of its dimensions.
174
+ #
175
+ def dimension
176
+ map { |qnt, exp| qnt.dimension * exp }.reduce SY::Dimension.zero, :+
177
+ end
178
+
179
+ # Infers the mapping of the composition's quantity. (Mapping
180
+ # defines mapping of a quantity to the standard quantity of its dimension.)
181
+ #
182
+ def infer_mapping
183
+ puts "#infer_mapping; hash is #{self}" if SY::DEBUG
184
+ map do |qnt, exp|
185
+ qnt.standardish? ? SY::Mapping.identity :
186
+ qnt.mapping_to( qnt.standard ) ** exp
187
+ end.reduce( SY::Mapping.identity, :* )
188
+ end
189
+
190
+ # Simple composition is one that is either empty or singular.
191
+ #
192
+ def simple?
193
+ empty? or singular?
194
+ end
195
+
196
+ # Whether it is possible to expand this
197
+ def irreducible?
198
+ all? { |qnt, exp| qnt.irreducible? }
199
+ end
200
+
201
+ # Try to simplify the composition by decomposing its quantities.
202
+ #
203
+ def expand
204
+ return self if irreducible?
205
+ puts "#expand: #{self} not irreducible" if SY::DEBUG
206
+ self.class[ reduce( self.class.empty ) { |cᴍ, pair|
207
+ qnt, exp = pair
208
+ puts "#expand: qnt: #{qnt}, exp: #{exp}" if SY::DEBUG
209
+ puts "cᴍ is #{cᴍ}" if SY::DEBUG
210
+ ( cᴍ + if qnt.irreducible? then
211
+ self.class.singular( qnt ) * exp
212
+ else
213
+ qnt.composition * exp
214
+ end.tap { |x| puts "Adding #{x}." if SY::DEBUG }
215
+ ).tap { |x| puts "Result is #{x}." if SY::DEBUG }
216
+ } ]
217
+ .tap{ |rslt| puts "#expand: result is #{rslt}" if SY::DEBUG }
218
+ end
219
+ end # class SY::Composition
@@ -0,0 +1,182 @@
1
+ #encoding: utf-8
2
+
3
+ # This class represents physical dimension of a metrological quantity.
4
+ #
5
+ class SY::Dimension
6
+ # Let's set up an instance variable (of the class) that will hold standard
7
+ # quantities of selected dimensions:
8
+ #
9
+ @standard_quantities ||= Hash.new { |ꜧ, missing_key|
10
+ if missing_key.is_a? SY::Dimension then
11
+ # Create a new quantity of that dimension:
12
+ ꜧ[missing_key] = SY::Quantity.of missing_key
13
+ else
14
+ # Otherwise, let SY.Dimension constructor judge:
15
+ ꜧ[ SY.Dimension missing_key ]
16
+ end
17
+ }
18
+
19
+ class << self
20
+ alias __new__ new
21
+ attr_reader :standard_quantities
22
+
23
+ # The #new constructor of SY::Dimension has been changed, so that the
24
+ # same instance is returned, if that dimension has already been created.
25
+ #
26
+ def new dim={}
27
+ ꜧ = case dim
28
+ when Hash then dim
29
+ when self then return dim # it is a dimension instance
30
+ else # we'll treat dimension_specification as SPS
31
+ SY::BASE_DIMENSIONS.parse_sps( dim )
32
+ end
33
+ # Zeros by default:
34
+ ꜧ.default! Hash[ SY::BASE_DIMENSIONS.base_symbols.map { |ß| [ß, 0] } ]
35
+ # Now we can see whether the instance of this dimension already exists.
36
+ return instances.find { |inst| inst.to_hash == ꜧ } ||
37
+ __new__( ꜧ ).tap { |inst| instances << inst }
38
+ end
39
+
40
+ # Presents class-owned instances (array).
41
+ #
42
+ def instances
43
+ return @instances ||= []
44
+ end
45
+
46
+ # Base dimension constructor. Base dimension symbol is expeced as argument.
47
+ #
48
+ def base symbol
49
+ raise AErr, "Unknown base dimension: #{symbol}" unless
50
+ SY::BASE_DIMENSIONS.base_symbols.include? symbol
51
+ return new( symbol => 1 )
52
+ end
53
+ alias basic base
54
+
55
+ # Constructor for zero dimension ("dimensionless").
56
+ #
57
+ def zero; new end
58
+ end
59
+
60
+ attr_accessor *SY::BASE_DIMENSIONS.base_symbols
61
+
62
+ # Dimension can be initialized either by supplying a hash
63
+ # (such as Dimension.new L: 1, T: -2) or by supplying a SPS
64
+ # (superscripted product string), such as Dimension.new( "L.T⁻²" ).
65
+ # It is also possible to supply a Dimension instance, which will
66
+ # result in a new Dimension instance equal to the supplied one.
67
+ #
68
+ def initialize hash
69
+ SY::BASE_DIMENSIONS.base_symbols.each { |ß|
70
+ instance_variable_set "@#{ß}", hash[ß]
71
+ }
72
+ end
73
+
74
+ # #[] method provides access to the dimension components, such as
75
+ # d = Dimension.new( L: 1, T: -2 ), d[:T] #=> -2
76
+ #
77
+ def [] ß
78
+ return send ß if SY::BASE_DIMENSIONS.letters.include? ß
79
+ raise AErr, "Unknown basic dimension: #{ß}"
80
+ end
81
+
82
+ #Two dimensions are equal, if their exponents are equal.
83
+ #
84
+ def == other
85
+ to_a == other.to_a
86
+ end
87
+
88
+ # Dimension arithmetic: addition. (+, -, *, /).
89
+ #
90
+ def + other
91
+ ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
92
+ [ l, self.send( l ) + other.send( l ) ]
93
+ end ]
94
+ end
95
+
96
+ # Dimension arithmetic: subtraction.
97
+ #
98
+ def - other
99
+ ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
100
+ [ l, self.send( l ) - other.send( l ) ]
101
+ end ]
102
+ end
103
+
104
+ # Dimension arithmetic: multiplication by a number.
105
+ #
106
+ def * number
107
+ ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
108
+ [ l, self.send( l ) * number ]
109
+ end ]
110
+ end
111
+
112
+ # Dimension arithmetic: division by a number.
113
+ #
114
+ def / number
115
+ ç.new Hash[ SY::BASE_DIMENSIONS.base_symbols.map do |l|
116
+ exp = send l
117
+ raise TErr, "Dimensions with rational exponents " +
118
+ "not implemented!" if exp % number != 0
119
+ [ l, exp / number ]
120
+ end ]
121
+ end
122
+
123
+ # Conversion to an array (of exponents of in the order of the
124
+ # basic dimension letters).
125
+ #
126
+ def to_a
127
+ SY::BASE_DIMENSIONS.base_symbols.map { |l| self.send l }
128
+ end
129
+
130
+ # Conversion to a hash (eg. { L: 1, M: 0, T: -2, Q: 0, Θ: 0 } ).⁻³
131
+ #
132
+ def to_hash
133
+ SY::BASE_DIMENSIONS.base_symbols.each_with_object Hash.new do |l, ꜧ|
134
+ ꜧ[ l ] = self.send( l )
135
+ end
136
+ end
137
+
138
+ # True if the dimension is zero ("dimensionless"), otherwise false.
139
+ #
140
+ def zero?
141
+ to_a.all? { |e| e.zero? }
142
+ end
143
+
144
+ # True if the dimension is basic, otherwise false.
145
+ #
146
+ def base?
147
+ to_a.count( 1 ) == 1 &&
148
+ to_a.count( 0 ) == SY::BASE_DIMENSIONS.base_symbols.size - 1
149
+ end
150
+ alias basic? base?
151
+
152
+ # Converts the dimension into its SPS.
153
+ #
154
+ def to_s
155
+ sps = SY::SPS.( SY::BASE_DIMENSIONS.base_symbols, to_a )
156
+ return sps == "" ? "∅" : sps
157
+ end
158
+
159
+ # Produces the inspect string of the dimension.
160
+ #
161
+ def inspect
162
+ "#<Dimension: #{self} >"
163
+ end
164
+
165
+ # Returns dimension's standard quantity.
166
+ #
167
+ def standard_quantity
168
+ self.class.standard_quantities[ self ]
169
+ end
170
+
171
+ # Returns default quantity composition for this dimension.
172
+ #
173
+ def to_composition
174
+ SY::Composition[ SY::BASE_DIMENSIONS.base_symbols
175
+ .map { |l| self.class.base l }
176
+ .map { |dim| self.class.standard_quantities[ dim ] }
177
+ .map { |qnt| qnt.absolute }
178
+ .zip( to_a ).delete_if { |qnt, exp| exp.zero? } ]
179
+ end
180
+
181
+ delegate :standard_unit, to: :standard_quantity
182
+ end # class SY::Dimension