Auto add quantities, base quantities, and/or unit systems
A few tests in math.js are still failing; these are related to creating custom units, and expecting those units to be used when simplifying expressions (they are not currently). In UnitMath, a unit has to be part of a system in order for it to appear in a simplified expression. When the chosen unit system is auto, N m becomes J, and ft lbf becomes BTU.
Say we do createUnit(mph, 1 mi/hr) in math.js. Currently, mi / hr does not simplify to mph as we would expect. To fix this, we need to assign mph as the VELOCITY unit of the us system. This is possible if you include additional options to UnitMath:
definitions: {
units: {
mph: '1 mi/hr'
},
unitSystems: {
us: {
VELOCITY: 'mph'
}
}
}
But math.js doesn't have access to the logic used to match quantities or systems, so it wouldn't be able to produce the unitSystems object above. Things get even more complicated if a new quantity is introduced. I imagine that many users of UnitMath (including math.js) will not want to be encumbered by the verbose definitions required to make custom units members of a unit system:
definitions: {
units: {
snap: '1 m/s^4'
},
unitSystems: {
si: {
JOUNCE: 'snap'
}
},
quantities: {
JOUNCE: 'LENGTH TIME^-4'
}
}
We could, however, create a flag that would cause the unitSystems and quantities objects to be created automatically. That way, if we then do createUnit(mph, 1 mi/hr) followed by 5 mi / hr, we should get 5 mph. But 5 km / hr will still give 5 km/hr since it's a different unit system.
definitions: {
units: {
snap: {
value: '1 m/s^4',
system: 'si' // or 'auto' to auto-select based on value
}
}
}
So in math.js, we would merely add the system: 'auto' to each custom unit.
@harrysarson, @josdejong, any thoughts?
P.S. How it might work:
Just before the units are finished being created in UnitStore.js, we look at each unit and if it has the system key, we match its value to existing quantities. If an existing quantity matches, we use that, and if there is not a match, we add an additional quantity (snap_QUANT or something). Then, if system is an existing system we use that, and if it is 'auto' then we match value to a unit system (si in this case due to the m). Then we add the additional entries to quantities and unitSystems. Since system is part of the unit definition, the user can retrieve the current definitions using unit.definitions(), recovering the original options passed.
Just for my understanding a few questions:
- I suppose in mathjs I first select a system, like
si. Now, when I create a unit, is it only defined in thesisystem? Is the unit gone when I switch to a different system such asauto? - I don't fully understand why your example
mi / hris not simplified tomphwithout definingmphas a new quantity (formerly we called this "base unit", right?). Do we have to define new quantities for every unit in order to have simplfication working? When does simplification take place and when not?
It sounds to me lie we have to create createUnit to do the smart stuff, and offer a simple interface to generate the actual definitions object behind the scenes. If I'm not mistaken, the old createUnit in mathjs was layered: it allowed entering an advanced definition of a new unit, but also allows simple input, more user friendly input which is internally transformed into the correct definition.
- Since the unit system is now part of the math.js config, then if the config is changed, the custom units are recreated in a new instance of UnitMath. If
math.createis used however, the custom units are gone. - This requires a bit of a longer answer. First, here is the terminology used in
BuiltIns.js:
unit: meter, second, ohm, gram, etc. prefix: u, m, k, M, G, etc. base quantity (formerly base unit): The independent quantities, such as LENGTH, TIME, MASS, CURRENT, etc. quantity: The derived quantities: VELOCITY, MOMENTUM, VOLTAGE, ACCELERATION, etc. system: For each system like si, us, cgs, this matches each quantity or base quantity with a preferred unit.
For clarity during this discussion, I'll refer to a complete value like 34 m / s as a Unit (bold, capitalized). I haven't found a better name yet to differentiate units from Units.
Here is the algorithm behind simplify():
- Begin with an unsimplified Unit such as
30000 kg / m s^2 - If the configured system is
auto, then examine the individual units and choose the system that is most represented. In this case,kgandmare found in the si system, andsis found in both thesiandussystems, sosiis chosen. In the event of a tie,siis favored. - Search for a matching quantity. In this case, the unit
30000 kg / m s^2matches quantityPRESSUREwhich has the definitionMASS LENGTH^-1 TIME^-2. - If a matching quantity is found, see if the chosen system has an entry for that quantity. In this case, the
sisystem listsPaas the preferred unit ofPRESSURE. - If a matching quantity is not found or the system does not list a preferred unit for the quantity, form a representation of the Unit using the base quantities of the chosen system. (If the chosen system does not contain preferred units for the base quantities, the simplification is rejected.)
- Finally, if the resulting Unit contains fewer symbols than the unsimplified Unit ("fewer" here means it has to be
simplifyThresholdsymbols less than the unsimplified), accept the simplification, otherwise reject it. - After simplifying, a prefix is chosen (that's a whole different algorithm).
- The result in this example is
30 kPa.
Why is
mi / hrnot simplified tomph
This is because the us system does not list a preferred unit for VELOCITY, so the Unit is just built using the us units for LENGTH and TIME, which gives ft / s. (But ft / s is rejected since it is not fewer symbols than mi / hr.)
In order to make mi / hr simplify to mph, the mph unit must be listed as the preferred VELOCITY unit in the us system. This is done by including a unitSystems: { us: { VELOCITY: 'mph' } } in the config options, or, as this issue proposes, including system: 'us' in the unit definition for mph itself.
The old
createUnitin mathjs was layered: it allowed entering an advanced definition of a new unit, but also allows simple input, more user friendly input which is internally transformed into the correct definition.
That's correct. We made it possible to define a unit by supplying a Unit string, or additional options. This is still the case; see the examples here.
What will change:
Nothing will change for the math.js user. For the UnitMath user, it adds a shortcut which automatically adds the new custom units to the correct system. For math.js's interface with UnitMath, we will just need to add either system: 'auto' or system: config.unitSystem to each unit definition.
Thanks for yoru clear explanation Eric, I understand it now. I was missing the part of quantity not being base quantity. I understand now that this is essential to define if you want to be able to simplify to a created unit (like mph). It makes sense, this way you will only simplify to meaningful quantities instead of random (shorter) quantities.
I think your proposed solution makes sense: automatically creating quanties when adding a new unit if it doesn't match an existing quantity. And of course allowing the user to optionally define a quantity if he wants.
I've updated the UnitMath README. I renamed the system option to autoAddToSystem to avoid confusion with other similarly named symbols. I haven't started the implementation yet, but if you would, kindly review the README above (search for addToUnitSystem) and see if that API makes sense to you.
The docs are very clear, and the new name autoAddToSystem is much more self-explanatory :+1: . Makes sense to me.
Closing this, as I think a lot of the discussion is out-of-date. v1.0.0 completely changes how base units and systems are defined, and should resolve a lot of these issues. Custom units won't automatically be added to systems, but adding them manually will be super, super easy.