Arithmetic with scales and aspects¶
Unit composition is often a useful way to express measured quantities, and is supported in m-layer-concept.
Note
There is a widely-held belief that units of measurement can be generated by multiplying other units together. This is a misunderstanding. Another widely-held belief is that the rules of quantity calculus can be applied to measurement data expressed in any format. This is also a misunderstanding.
Unit arithmetic¶
Unit multiplication is sensible when the unit names, or symbols, are treated as variables for conversion factors. For example, a speed expressed as 50 km/h, may be converted to 13.89 m/s by knowing that kg = 1000 m and h = 3600 s. The unit names, km/h and m/s, actually encode the dimensions of speed, L/T; so, they describe a formula for a unit conversion factor. However, software struggles with situations where the dimensions of a compound unit are associated with several different units of measurement (e.g., the fact that the name kg.m2.s-2 may be considered as a unit for torque or as a unit for work or energy). In such cases, there are often a number of alternative names for the compound unit, but more information is needed to determine which names apply (e.g., energy would be expressed in J, but torque in N.m).
Compound scales¶
Speed¶
Speed provides a simple example of how m-layer-concept handles compound units.
We first import the package and declare scales for length and time
>>> from m_layer import *
>>> m = Scale( ('ml_si_m_ratio', 17771593641054934856197983478245767638) )
>>> s = Scale( ('ml_si_s_ratio', 276296348539283398608930897564542275037) )
A compound unit for speed can then be generated and used in an expression
>>> m_s = m/s
>>> print( m_s )
m/(s)
>>> v = expr(1.5, m/s )
>>> print( v )
1.5 m/(s)
Alternative compound units for speed can be created using different scales for length and time
>>> ft = Scale( ('ml_foot_ratio', 150280610960339969789551668292960104920) )
>>> minute = Scale( ('ml_si_minute_ratio', 219754916679293138667106941253484129447 ) )
>>> ft_min = ft/minute
>>> print( ft_min )
ft/(min)
Conversion is possible, but the arithmetic expressions defining the compound scales must have exactly the same form
>>> print( v.convert( ft_min ) )
295.2756 ft/(min)
Note, the compound-scale objects shown above are not automatically resolved to a single M-layer scale for speed. However, when a corresponding scale is available, a compound scale can be converted. In this example, there is an m-layer-concept scale defined for speed, so conversion can be carried out
>>> m_per_s = Scale( ("ml_si_m.s-1_ratio",294302032761295079611018815406253236800) )
>>> print( repr(v) )
Expression(1.5,m/(s))
>>> print( v.convert(m_per_s) )
1.5 m.s-1
Energy or moment of force¶
The special unit name joule is used for energy in the SI, and the compound name newton-metre is the recommended unit for moment of force. Nevertheless, there is a systematic unit, the kilogram-metre-squared-per-second-squared (kg.m2.s-2), which is a valid alternative to both these units.
Note
For convenience, we use the term ‘systematic’ for a unit name that is composed from products of powers of base unit names (or prefixed base units), or symbols. For example, kilogram metre squared per second squared (kg.m2.s-2) is systematic. This terminology helps to describe how the m-layer-concept resolves compound scales to individual scales.
A compound scale for kg.m2.s-2 can be created and used to express data
>>> kg = Scale( ('ml_si_kg_ratio', 12782167041499057092439851237297548539) )
>>> m = Scale( ('ml_si_m_ratio', 17771593641054934856197983478245767638) )
>>> s = Scale( ('ml_si_s_ratio', 276296348539283398608930897564542275037) )
>>> kg_mm_ss = kg*m**2/s**2
>>> print(kg_mm_ss)
kg.m^2/(s^2)
>>> w = expr(10.1,kg_mm_ss)
>>> print( w )
10.1 kg.m^2/(s^2)
However, no aspect is specified, so this expression does not distinguish between energy and moment of force.
By declaring the aspects
>>> energy = Aspect( ("ml_energy", 12139911566084412692636353460656684046) )
>>> moment = Aspect( ("ml_moment_of_force", 313648474034040825357489751369673453388) )
and the scales
>>> J = Scale( ("ml_si_J_ratio",165050666678496469850612022016789737781) )
>>> N_m = Scale( ("ml_si_N.m_ratio",185449807049376763233547052617606721423) )
it is possible to cast the systematic unit to one that is quantity-specific, such as
>>> print( w.cast( ScaleAspect(J,energy) ) )
10.1 J
or
>>> print( w.cast( ScaleAspect(N_m,moment) ) )
10.1 N.m
Systematic compound scales¶
A CompoundScale has a systematic property that returns a CompoundSystematic associated with an expression
>>> print( kg_mm_ss.systematic )
{ SI(0, 0, 1, 0, 0, 0, 0) : [-2], SI(0, 1, 0, 0, 0, 0, 0) : [2], SI(1, 0, 0, 0, 0, 0, 0) : [1] }
The CompoundSystematic is a collection of Systematic objects – one for every scale – each with an associated exponent.
A compound object can be reduced to a single Systematic (combining the dimensions and their exponents)
>>> print( kg_mm_ss.systematic.simplify )
SI(1, 2, -2, 0, 0, 0, 0)
If a scale is not coherent in the unit system, there will also be a factor relating the unit to the corresponding coherent unit. For example, using the unit nanometre
>>> nm = Scale( ("ml_si_nm_ratio", 257091757625055920788370123828667027186) )
>>> kg_nmnm_ss = kg*nm**2/s**2
>>> print( kg_nmnm_ss.systematic )
{ SI(0, 0, 1, 0, 0, 0, 0) : [-2], 1E-09*SI(0, 1, 0, 0, 0, 0, 0) : [2], SI(1, 0, 0, 0, 0, 0, 0) : [1] }
The prefix nano is associated with the metre scale, but this association is lost when the CompoundSystematic is simplified
>>> print( kg_nmnm_ss.systematic.simplify )
1E-18*SI(1, 2, -2, 0, 0, 0, 0)
Compound scale identifiers¶
The CompoundScale class has a uid property that returns a CompoundUID associated with an expression. This encapsulates the identifiers of individual scales and their exponents. CompoundUID objects may be compared for for equality.
>>> print( kg_mm_ss.uid )
{ ['ml_si_s_ratio', 276296348539283398608930897564542275037] : [-2], ['ml_si_m_ratio', 17771593641054934856197983478245767638] : [2], ['ml_si_kg_ratio', 12782167041499057092439851237297548539] : [1] }
Ratios that form a scale of dimension one¶
When a compound unit is equivalent to a multiple or submultiple of the SI unit one, It is considered good practice to explicitly retain unit names in the numerator and denominator (e.g., mm/m instead of 1E-3). The m-layer-concept software supports this. For example,
>>> V = Scale( ("ml_si_V_ratio",324370471112617696659965827203196197232) )
>>> mV = Scale( ("ml_si_mV_ratio",198003412807998624987043120288110344365) )
>>> mV_V = mV/V
>>> print( mV_V )
mV/(V)
>>> nV = Scale( ("ml_si_nV_ratio",2467209754778232353783778251664853474) )
>>> pV = Scale( ("ml_si_pV_ratio",82044477201173066720472034767008183292) )
>>> pV_nV = pV/nV
>>> print( pV_nV )
pV/(nV)
The UIDs of these compound scales retain information about the different scales
>>> print( mV_V.uid )
{ ['ml_si_V_ratio', 324370471112617696659965827203196197232] : [-1], ['ml_si_mV_ratio', 198003412807998624987043120288110344365] : [1] }
>>> print( pV_nV.uid )
{ ['ml_si_nV_ratio', 2467209754778232353783778251664853474] : [-1], ['ml_si_pV_ratio', 82044477201173066720472034767008183292] : [1] }
and compound systematic objects also encode scale differences
>>> print( mV_V.systematic )
{ SI(1, 2, -3, -1, 0, 0, 0) : [-1], 1/1000*SI(1, 2, -3, -1, 0, 0, 0) : [1] }
>>> print( pV_nV.systematic )
{ 1E-09*SI(1, 2, -3, -1, 0, 0, 0) : [-1], 1E-12*SI(1, 2, -3, -1, 0, 0, 0) : [1] }
Nevertheless, the compound scales are commensurate (have the same system dimensional exponents)
>>> print( mV_V.systematic.commensurate( pV_nV.systematic ) )
True
and they become indistinguishable when simplified
>>> print( mV_V.systematic.simplify )
1/1000*SI(0, 0, 0, 0, 0, 0, 0)
>>> print( pV_nV.systematic.simplify )
1/1000*SI(0, 0, 0, 0, 0, 0, 0)
>>> print( mV_V.systematic.simplify == pV_nV.systematic.simplify )
True
The compound scale volt-per-volt can also be used. However, two Scale objects must be created to retain the numerator and denominator scales (otherwise cancellation will occur, see Compound scales and base-unit simplification)
>>> V1 = Scale( ("ml_si_V_ratio",324370471112617696659965827203196197232) )
>>> V2 = Scale( ("ml_si_V_ratio",324370471112617696659965827203196197232) )
>>> V_V = V1/V2
>>> print( V_V.uid )
{ ['ml_si_V_ratio', 324370471112617696659965827203196197232] : [1, -1] }
>>> print( V_V.systematic )
{ SI(1, 2, -3, -1, 0, 0, 0) : [1, -1] }
>>> print( V_V.systematic.simplify )
SI(0, 0, 0, 0, 0, 0, 0)
Compound scales and base-unit simplification¶
One of the difficulties that arises when using system dimensions, is that arithmetic cancellation of terms with the same dimension may occur.
For instance, we may wish to express a horizontal velocity gradient with respect to height (e.g., a rate of change in wind speed with altitude). Suitable units are metres per second per metre. A compound scale for this can be declared
>>> m = Scale( ('ml_si_m_ratio', 17771593641054934856197983478245767638) )
>>> s = Scale( ('ml_si_s_ratio', 276296348539283398608930897564542275037) )
>>> m_s = m/s
>>> m_s_m = m_s/m
However, the m-layer-concept software recognises that reference is made to the same metre Scale object twice, because metre appears in both the numerator and denominator. The default behaviour is to allow this common factor to be cancelled
>>> print( m_s_m.uid )
{ ['ml_si_s_ratio', 276296348539283398608930897564542275037] : [-1] }
Nevertheless, the distinction between units of elevation and horizontal length can be made. This requires a second instance of the metre Scale to be created. The software recognises that different objects are involved, and that they have distinct roles in the expression. It also recognises that they are associated with the same M-layer scale. The CompoundUID now shows two exponents associated with the metre Scale, which indicates that the metre appears in the numerator and denominator of the compound unit.
>>> m_height = Scale( ('ml_si_m_ratio',17771593641054934856197983478245767638) )
>>> m_s_m = m_s/m_height
>>> print( m_s_m.uid )
{ ['ml_si_m_ratio', 17771593641054934856197983478245767638] : [1, -1], ['ml_si_s_ratio', 276296348539283398608930897564542275037] : [-1] }
Similarly, when distinct Scale object are used, the CompoundSystematic captures two exponents associated with the length dimension
>>> print( m_s_m.systematic )
{ SI(0, 1, 0, 0, 0, 0, 0) : [1, -1], SI(0, 0, 1, 0, 0, 0, 0) : [-1] }
The compound systematic object can be simplified, which cancels references to the metre Scale
>>> print( m_s_m.systematic.simplify )
SI(0, 0, -1, 0, 0, 0, 0)
Note
The M-layer register does not hold compound-scale records. The software works with compound-scale expressions that encapsulate individual M-layer scales. To convert from one compound scale to another, expressions will be matched, term by term, which requires the expressions involved to have exactly the same arithmetic form.
Conversion from a compound-scale expression to a single-scale expression is also possible. A single M-layer scale will be identified using unit dimensions. Individual scales must belong to the same unit system, so they have dimensions in that system. The compound-scale dimensions are evaluated and used to look up the M-layer registry for a corresponding scale designated as systematic.
Compound scale-aspects¶
The functionality described above for scales has also been implemented for scale-aspects. Multiplication, division and exponentiation operations can be used with ScaleAspect objects. For instance
>>> m = ScaleAspect(
... Scale( ('ml_si_m_ratio', 17771593641054934856197983478245767638) ),
... Aspect( ('ml_length', 993853592179723568440264076369400241) )
... )
>>> s = ScaleAspect(
... Scale( ('ml_si_s_ratio', 276296348539283398608930897564542275037) ),
... Aspect( ('ml_time', 59007067547744628223483093626372886675) )
... )
>>> print( m/s )
(m, length)/(s, time)
>>> print( expr(1.5, m/s ) )
1.5 (m, length)/(s, time)
The units conversion process now checks the compatibility of each term’s scale and aspect
>>> length = Aspect( ('ml_length', 993853592179723568440264076369400241) )
>>> foot = ScaleAspect(ft,length)
>>> y = expr(1.5, m/s )
>>> convert(y, foot/s )
Expression(4.92126,(ft, length)/(s, time))
Note, the earlier declaration of ft created a Scale, which does not specify an aspect. Mixing of scales and scale-aspects is not supported at present, so the code above explicitly promotes ft to a ScaleAspect foot, with aspect length.