Module: SY::Magnitude

Includes:
Comparable, ExpressibleInUnits
Defined in:
lib/sy/magnitude.rb

Overview

This module defines common assets of a magnitude – be it absolute (number of unit objects), or relative (magnitude difference).

Constant Summary

Constants included from ExpressibleInUnits

ExpressibleInUnits::COLLISION_WARNING, ExpressibleInUnits::REDEFINE_WARNING, ExpressibleInUnits::RecursionError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ExpressibleInUnits

exponentiation_string, find_unit, included, included_in, known_units, method_family, #method_missing, prefix_method_string, #respond_to_missing?, unit_namespace

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class SY::ExpressibleInUnits

Instance Attribute Details

#amountObject (readonly) Also known as: in_standard_unit

Returns the value of attribute amount.



55
56
57
# File 'lib/sy/magnitude.rb', line 55

def amount
  @amount
end

#quantityObject (readonly)

Returns the value of attribute quantity.



55
56
57
# File 'lib/sy/magnitude.rb', line 55

def quantity
  @quantity
end

Class Method Details

.absolute(of: nil, amount: nil) ⇒ Object

Constructs an absolute magnitude of a given quantity.



10
11
12
# File 'lib/sy/magnitude.rb', line 10

def absolute( of: nil, amount: nil )
  of.absolute.magnitude( amount )
end

.difference(of: nil, amount: nil) ⇒ Object

Constructs a relative magnitude of a given quantity.



16
17
18
# File 'lib/sy/magnitude.rb', line 16

def difference( of: nil, amount: nil )
  of.relative.magnitude( amount )
end

.of(quantity, amount: nil) ⇒ Object

Constructs a magnitude of a given quantity.



22
23
24
# File 'lib/sy/magnitude.rb', line 22

def of( quantity, amount: nil )
  quantity.magnitude( amount )
end

.one(of: nil) ⇒ Object

Magnitude 1 of a given quantity.



34
35
36
# File 'lib/sy/magnitude.rb', line 34

def one( of: nil )
  absolute of: of, amount: 1
end

.zero(of: nil) ⇒ Object

Zero magnitude of a given quantity.



28
29
30
# File 'lib/sy/magnitude.rb', line 28

def zero( of: nil )
  absolute of: of, amount: 0
end

Instance Method Details

#%(m2) ⇒ Object

Percent operator (remainder after division)



177
178
179
180
181
# File 'lib/sy/magnitude.rb', line 177

def % m2
  return magnitude amount % m2.amount if quantity == m2.quantity
  return self % m2.( quantity ) if quantity.coerces? m2.quantity
  apply_through_coerce :%, m2
end

#*(m2) ⇒ Object

Multiplication.



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sy/magnitude.rb', line 128

def * m2
  case m2
  when Numeric then
    magnitude amount * m2
  # when SY::ZERO then
  #   return magnitude 0
  when Matrix then
    m2.map { |e| self * e }
  else
    ( quantity * m2.quantity ).magnitude( amount * m2.amount )
  end
end

#**(exp) ⇒ Object

Exponentiation.



158
159
160
161
162
163
164
165
166
167
# File 'lib/sy/magnitude.rb', line 158

def ** exp
  case exp
  when SY::Magnitude then
    raise SY::DimensionError, "Exponent must have zero dimension! " +
      "(exp given)" unless exp.dimension.zero?
    ( quantity ** exp.amount ).magnitude( amount ** exp.amount )
  else
    ( quantity ** exp ).magnitude( amount ** exp )
  end
end

#+(m2) ⇒ Object

Addition.



112
113
114
115
116
# File 'lib/sy/magnitude.rb', line 112

def + m2
  return magnitude amount + m2.amount if quantity == m2.quantity
  return self + m2.( quantity ) if quantity.coerces? m2.quantity
  apply_through_coerce :+, m2
end

#+@Object

Reframes the magnitude into its relative quantity.



88
89
90
# File 'lib/sy/magnitude.rb', line 88

def +@
  quantity.relative.magnitude( amount )
end

#-(m2) ⇒ Object

Subtraction.



120
121
122
123
124
# File 'lib/sy/magnitude.rb', line 120

def - m2
  return magnitude amount - m2.amount if quantity == m2.quantity
  return self - m2.( quantity ) if quantity.coerces? m2.quantity
  apply_through_coerce :-, m2
end

#-@Object

Reframes the magnitude into its relative quantity, with negative amount.



94
95
96
# File 'lib/sy/magnitude.rb', line 94

def -@
  quantity.relative.magnitude( -amount )
end

#/(m2) ⇒ Object

Division.



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/sy/magnitude.rb', line 143

def / m2
  case m2
  when Numeric then
    magnitude amount / m2
  # when SY::ZERO then
  #   raise ZeroDivisionError, "Attempt to divide #{self} by #{SY::ZERO}."
  when Matrix then
    amount / m2 * quantity.magnitude( 1 )
  else
    ( quantity / m2.quantity ).magnitude( amount / m2.amount )
  end
end

#<=>(m2) ⇒ Object

Three-way comparison operator of magnitudes.



49
50
51
52
53
# File 'lib/sy/magnitude.rb', line 49

def <=> m2
  return amount <=> m2.amount if quantity == m2.quantity
  return self <=> m2.( quantity ) if quantity.coerces? m2.quantity
  apply_through_coerce :<=>, m2
end

#absObject

Absolute value of a magnitude (no reframe).



100
101
102
# File 'lib/sy/magnitude.rb', line 100

def abs
  magnitude amount.abs
end

#absoluteObject

Computes absolute value and reframes into the absolute quantity.



76
77
78
# File 'lib/sy/magnitude.rb', line 76

def absolute
  quantity.absolute.magnitude( amount.abs )
end

#call(q2) ⇒ Object

Reframes a magnitude into a relative version of a given quantity. (If absolute quantity is supplied as an argument, its relative colleague is used to reframe.)



229
230
231
232
233
234
# File 'lib/sy/magnitude.rb', line 229

def call q2
  case q2
  when SY::Quantity then q2.relative.read self
  when SY::Unit then q2.quantity.relative.read self
  else raise TypeError, "Unable to reframe into a #{q2.class}!" end
end

#coerce(m2) ⇒ Object

Type coercion for magnitudes.



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/sy/magnitude.rb', line 185

def coerce m2
  if m2.is_a? Numeric then
    return SY::Amount.relative.magnitude( m2 ), self
  elsif m2.is_a? Matrix then
    return m2 * SY::UNIT, self
  elsif quantity.coerces? m2.quantity then
    return m2.( quantity ), self
  else
    raise TypeError, "#{self} cannot be coerced into a #{m2.class}!"
  end
end

#eql?(other) ⇒ Boolean

Same magnitudes and same (#eql) quantities.

Returns:

  • (Boolean)


171
172
173
# File 'lib/sy/magnitude.rb', line 171

def eql? other
  quantity == other.quantity && amount == other.amount
end

#in(m2) ⇒ Object

Gives the magnitude as a plain number in multiples of another magnitude, supplied as argument. The quantities must match.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/sy/magnitude.rb', line 200

def in m2
  case m2
  when Symbol, String then
    begin
      self.in eval( "1.#{m2}" ).aT_kind_of SY::Magnitude # digest it
    rescue TypeError
      raise TypeError, "Evaluating 1.#{m2} does not result in a magnitude; " +
        "method collision with another library?"
    end
  when SY::Magnitude then
    quantity.measure( of: m2.quantity ).w.( amount ) / m2.amount
  else
    raise TypeError, "Unexpected type for Magnitude#in method! (#{m2.class})"
  end
end

#inspectObject

Inspect string of the magnitude



438
439
440
441
# File 'lib/sy/magnitude.rb', line 438

def inspect
  puts "inspect called on a magnitude of quantity #{quantity}" if SY::DEBUG
  "#<#{çς}: #{self} >"
end

#negative?Boolean

True if amount is negative. Implicitly false for absolute quantities.

Returns:

  • (Boolean)


238
239
240
# File 'lib/sy/magnitude.rb', line 238

def negative?
  amount < 0
end

#nonnegative?Boolean

Opposite of #negative?. Implicitly true for absolute quantities.

Returns:

  • (Boolean)


244
245
246
# File 'lib/sy/magnitude.rb', line 244

def nonnegative?
  amount >= 0
end

#reframe(q2) ⇒ Object

Reframes a magnitude into a different quantity. Dimension must match.



218
219
220
221
222
223
# File 'lib/sy/magnitude.rb', line 218

def reframe q2
  case q2
  when SY::Quantity then q2.read self
  when SY::Unit then q2.quantity.read self
  else raise TypeError, "Unable to reframe into a #{q2.class}!" end
end

#relativeObject

Reframes into the relative quantity.



82
83
84
# File 'lib/sy/magnitude.rb', line 82

def relative
  quantity.relative.magnitude( amount )
end

#round(*args) ⇒ Object

Rounded value of a Magnitude: A new magnitude with rounded amount.



106
107
108
# File 'lib/sy/magnitude.rb', line 106

def round *args
  magnitude amount.round( *args )
end

#to_magnitudeObject

Without arguments, it returns a new magnitude equal to self. If argument is given, it is treated as factor, by which the amount is to be muliplied.



446
447
448
# File 'lib/sy/magnitude.rb', line 446

def to_magnitude
  magnitude( amount )
end

#to_s(unit = quantity.units.first || quantity.standard_unit, number_format = default_amount_format) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/sy/magnitude.rb', line 267

def to_s( unit=quantity.units.first || quantity.standard_unit,
          number_format=default_amount_format ) # FIXME: TUTO JE TO KUREVSTVO TU SA TA JEDNOTKA KONSTRUUJE
  puts "#to_s called on a magnitude of quantity #{quantity}" if SY::DEBUG
  # step 1: produce pairs [number, unit_presentation],
  #         where unit_presentation is an array of triples
  #         [prefix, unit, exponent], which together give the
  #         correct dimension for this magnitude, and correct
  #         factor so that number * factor == self.amount
  # step 2: define a goodness function for them
  # step 3: define a satisfaction criterion
  # step 4: maximize this goodness function until the satisfaction
  #         criterion is met
  # step 5: interpolate the string from the chosen choice
  
  # so, let's start doing it
  # how do we produce the first choice?
  # if the standard unit for this quantity is named, we'll start with it
  
  # let's say that the abbreviation of this std. unit is Uu, so the first
  # choices will be:
  # 
  #                        amount.Uu
  #                        (amount * 1000).µUu
  #                        (amount / 1000).kUu
  #                        (amount * 1_000_000).nUu
  #                        (amount / 1_000_000).MUu
  #                        ...
  #                        
  # (let's say we'll use only short prefixes)
  #
  # which one do we use?
  # That depends. For example, CelsiusTemperature is never rendered with
  # SI prefixes, so their cost should be +Infinity
  # 
  # Cost of the number could be eg.:
  #
  #          style:                cost:
  #          3.141                 0
  #          31.41, 314.1          1
  #          0.3141                2
  #          3141.0                3
  #          0.03141               4
  #          31410.0               5n
  #          0.003141              6
  #          ...
  #          
  # Default cost of prefixes could be eg.
  #
  #          unit representation:  cost:
  #          U                     0
  #          dU                    +Infinity
  #          cU                    +Infinity
  #          mU                    1
  #          dkU                   +Infinity
  #          hU                    +Infinity
  #          kU                    1
  #          µU                    2
  #          MU                    2
  #          nU                    3
  #          GU                    3
  #          pU                    4
  #          TU                    4
  #          fU                    5
  #          PU                    5
  #          aU                    6
  #          EU                    6
  #
  # Cost of exponents could be eg. their absolute value, and +1 for minus sign
  #
  # Same unit with two different prefixes may never be used (cost +Infinity)
  #
  # Afterwards, there should be cost of inconsistency. This could be implemented
  # eg. as computing the first 10 possibilities for amount: 1 and giving them
  # bonuses -20, -15, -11, -8, -6, -5, -4, -3, -2, -1. That would further reduce the variability of the
  # unit representations.
  #
  # Commenting again upon default cost of prefixes, prefixes before second:
  #
  #          prefix:               cost:
  #          s                     0
  #          ms                    4
  #          ns                    5
  #          ps                    6
  #          fs                    7
  #          as                    9
  #          ks                    +Infinity
  #          Ms                    +Infinity
  #          ...
  #
  # Prefixes before metre
  #
  #          prefix:               cost:
  #          m                     0
  #          mm                    2
  #          µm                    2
  #          nm                    3
  #          km                    3
  #          Mm                    +Infinity
  #          ...
  #          

  # number, unit_presentation = choice

  # return "#{amount} of #{quantity}"

  begin
    
    un = unit.short || unit.name
    
    if un then
      number = self.in unit
      number_ς = number_format % number
      
      prefix = ''
      exp = 1
      # unit_presentation = prefix, unit, exp
      
      unit_ς = SY::SPS.( [ "#{prefix}#{unit.short}" ], [ exp ] )
      
      [ number_ς, unit_ς ].join '.'
    else
      number = amount
      # otherwise, use units of component quantities
       = quantity.composition.to_hash
      symbols, exponents = .each_with_object Hash.new do |pair, memo|
        qnt, exp = pair
        if qnt.standard_unit.name
          std_unit = qnt.standard_unit
          memo[ std_unit.short || std_unit.name ] = exp
        else
          m = qnt.magnitude( 1 ).to_s
          memo[ m[2..-1] ] = exp
          number = m[0].to_i * number
        end
      end.to_a.transpose
      # assemble SPS
      unit_ς = SY::SPS.( symbols, exponents )
      # interpolate
      number_ς = number_format % number
      return number_ς if unit_ς == '' || unit_ς == 'unit'
      [ number_ς, unit_ς ].join '.'
    end
    
  rescue
    fail
    number_ς = number_format % amount
    [ number_ς, "unit[#{quantity}]" ].join '.'
  end
end