ericman314 / UnitMath

JavaScript library for unit conversion and arithmetic
Apache License 2.0
31 stars 7 forks source link

Auto add quantities, base quantities, and/or unit systems #21

Closed ericman314 closed 1 year ago

ericman314 commented 5 years ago

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.

josdejong commented 5 years ago

Just for my understanding a few questions:

  1. I suppose in mathjs I first select a system, like si. Now, when I create a unit, is it only defined in the si system? Is the unit gone when I switch to a different system such as auto?
  2. I don't fully understand why your example mi / hr is not simplified to mph without defining mph as 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.

ericman314 commented 5 years ago
  1. 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.create is used however, the custom units are gone.
  2. 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():

  1. Begin with an unsimplified Unit such as 30000 kg / m s^2
  2. If the configured system is auto, then examine the individual units and choose the system that is most represented. In this case, kg and m are found in the si system, and s is found in both the si and us systems, so si is chosen. In the event of a tie, si is favored.
  3. Search for a matching quantity. In this case, the unit 30000 kg / m s^2 matches quantity PRESSURE which has the definition MASS LENGTH^-1 TIME^-2.
  4. If a matching quantity is found, see if the chosen system has an entry for that quantity. In this case, the si system lists Pa as the preferred unit of PRESSURE.
  5. 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.)
  6. Finally, if the resulting Unit contains fewer symbols than the unsimplified Unit ("fewer" here means it has to be simplifyThreshold symbols less than the unsimplified), accept the simplification, otherwise reject it.
  7. After simplifying, a prefix is chosen (that's a whole different algorithm).
  8. The result in this example is 30 kPa.

Why is mi / hr not simplified to mph

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

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.

josdejong commented 5 years ago

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.

ericman314 commented 5 years ago

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.

josdejong commented 5 years ago

The docs are very clear, and the new name autoAddToSystem is much more self-explanatory :+1: . Makes sense to me.

ericman314 commented 1 year ago

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.