[poincare] Add comments and alternative algorithm to beautify units

Change-Id: I4b1ce9528d9d6796fe08f8566ee5d1efafa1d87d
This commit is contained in:
Hugo Saint-Vignes
2020-06-16 17:03:38 +02:00
committed by Émilie Feral
parent 51f1cdb076
commit 11e41bb4dc
3 changed files with 113 additions and 8 deletions

View File

@@ -85,6 +85,8 @@ public:
public:
template<typename T>
struct Vector {
/* SupportSize is defined as the number of distinct base units.
* Norm is defined as the sum of all unit exponents absolute values. */
struct Metrics {
size_t supportSize;
T norm;

View File

@@ -331,21 +331,81 @@ static bool CanSimplifyUnitProduct(
* 'bestRemainder' are updated accordingly. */
Unit::Dimension::Vector<Integer> simplifiedExponents;
Integer (*operationOnExponents)(const Integer &, const Integer &) = entryUnitExponent == -1 ? Integer::Addition : Integer::Subtraction;
#if 0
/* In the current algorithm, simplification is attempted using derived units
* with no exponents. Some good simplifications might be missed:
* For instance with _A^2*_s^2, a first attempt will be to simplify to
* _C_A_s which has a bigger supportSize and will not be kept, the output
* will stay _A^2*_s^2.
* With the commented code, this issue is solved by trying to simplify with
* the highest exponent possible, so that, in this example, _A^2*_s^2 can be
* simplified to _C^2.
* An optimization might be possible using algorithms minimizing the sum of
* absolute difference of array elements */
int n = 0;
Integer best_norm;
Integer norm_temp = unitsMetrics.norm;
/* To extend this algorithm to square root simplifications, rational exponents
* can be handled, and a 1/2 step can be used (but it should be asserted that
* no square root simplification is performed if all exponents are integers.*/
int step = 1;
for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) {
// Setting simplifiedExponents to unitsExponents
simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i));
}
do {
best_norm = norm_temp;
n+= step;
for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) {
// Simplify unitsExponents with base units from derived unit
simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), step * entryUnitExponents->coefficientAtIndex(i)));
}
Unit::Dimension::Vector<Integer>::Metrics simplifiedMetrics = simplifiedExponents.metrics();
norm_temp = Integer::Addition(n, simplifiedMetrics.norm);
} while (norm_temp.isLowerThan(best_norm));
// Undo last step as it did not reduce the norm
n -= step;
Integer derivedMetricNorm = n * step * entryUnitNorm;
#else
Integer derivedMetricNorm = entryUnitNorm;
#endif
for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) {
// Simplify unitsExponents with base units from derived unit
#if 0
simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), -step * entryUnitExponents->coefficientAtIndex(i)));
#else
simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(unitsExponents.coefficientAtIndex(i), entryUnitExponents->coefficientAtIndex(i)));
#endif
}
Unit::Dimension::Vector<Integer>::Metrics simplifiedMetrics = simplifiedExponents.metrics();
// Compute metrics to evaluate the simplification
bool isSimpler = (1 + simplifiedMetrics.supportSize < unitsMetrics.supportSize);
/* Note: A metric is considered simpler if the support size (number of
* symbols) is reduced. A norm taking coefficients into account is possible.
* One could use the sum of all coefficients to favor _C_s from _A_s^2.
* However, replacing _m_s^-2 with _N_kg^-1 should be avoided. */
// TODO : Remove Metrics vectors entirely
Unit::Dimension::Vector<Integer>::Metrics candidateMetrics = {
.supportSize = 1 + simplifiedMetrics.supportSize,
.norm = Integer::Addition(entryUnitNorm, simplifiedMetrics.norm)
.norm = Integer::Addition(derivedMetricNorm, simplifiedMetrics.norm)
};
bool isSimpler = candidateMetrics.supportSize < unitsMetrics.supportSize ||
(candidateMetrics.supportSize == unitsMetrics.supportSize &&
candidateMetrics.norm.isLowerThan(unitsMetrics.norm));
if (isSimpler) {
#if 0
bestUnitExponent = entryUnitExponent * n * step;
#else
bestUnitExponent = entryUnitExponent;
#endif
bestRemainderExponents = simplifiedExponents;
bestRemainderMetrics = simplifiedMetrics;
/* unitsMetrics (support size and norm) is updated and will be taken into
* account in next iterations of CanSimplifyUnitProduct. */
unitsMetrics = candidateMetrics;
}
return isSimpler;
@@ -402,9 +462,11 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu
while (unitsMetrics.supportSize > 1) {
const Unit::Dimension * bestDim = nullptr;
int8_t bestUnitExponent = 0;
// Look up in the table of derived units.
for (const Unit::Dimension * dim = Unit::DimensionTable + Unit::NumberOfBaseUnits; dim < Unit::DimensionTableUpperBound; dim++) {
const Unit::Dimension::Vector<int8_t> * entryUnitExponents = dim->vector();
int8_t entryUnitNorm = entryUnitExponents->metrics().norm;
// A simplification is tried by either multiplying or dividing
if (CanSimplifyUnitProduct(
unitsExponents, unitsMetrics,
entryUnitExponents, entryUnitNorm, 1,
@@ -417,30 +479,48 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu
bestUnitExponent, bestRemainderExponents, bestRemainderMetrics
))
{
/* If successful, unitsMetrics, bestUnitExponent,
* bestRemainderExponents and bestRemainderMetrics have been updated*/
bestDim = dim;
}
}
if (bestDim == nullptr) {
// No simplification could be performed
break;
}
// Build and add the best derived unit
Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix());
#if 0
if (bestUnitExponent != 1) {
derivedUnit = Power::Builder(derivedUnit, Rational::Builder(bestUnitExponent));
}
#else
assert(bestUnitExponent == 1 || bestUnitExponent == -1);
if (bestUnitExponent == -1) {
derivedUnit = Power::Builder(derivedUnit, Rational::Builder(-1));
}
#endif
const int position = unitsAccu.numberOfChildren();
unitsAccu.addChildAtIndexInPlace(derivedUnit, position, position);
// Update remainder units and their exponents for next simplifications
unitsExponents = bestRemainderExponents;
unitsMetrics = bestRemainderMetrics;
}
// Apply simplifications
if (unitsAccu.numberOfChildren() > 0) {
// Divide by derived units
units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext);
Expression newUnits;
// Separate units and generated values
units = units.removeUnit(&newUnits);
// Assemble final value
Multiplication m = Multiplication::Builder(units);
self.replaceWithInPlace(m);
m.addChildAtIndexInPlace(self, 0, 1);
self = m;
// Update units with derived and base units
if (newUnits.isUninitialized()) {
units = unitsAccu;
} else {

View File

@@ -215,6 +215,33 @@ QUIZ_CASE(poincare_simplification_units) {
assert_parsed_expression_simplify_to("_mol^-1", "1×_mol^\u0012-1\u0013");
assert_parsed_expression_simplify_to("_cd^-1", "1×_cd^\u0012-1\u0013");
/* Power of SI units */
assert_parsed_expression_simplify_to("_s^3", "1×_s^3");
assert_parsed_expression_simplify_to("_m^2", "1×_m^2");
assert_parsed_expression_simplify_to("_m^3", "1×_m^3");
assert_parsed_expression_simplify_to("_m^(1/2)", "1×_m^\u00121/2\u0013");
/* Possible improvements */
/* Ignored derived metrics :
* -> Possible solution : Favor unities from user input. We do not want to
* favor positive exponents to avoid a Velocity being displayed as _m*_Hz
* assert_parsed_expression_simplify_to("_Hz", "_Hz");
* assert_parsed_expression_simplify_to("_S", "_S");
*/
/* Non unitary exponents on Derived metrics :
* -> See CanSimplifyUnitProduct in multiplication.cpp
* assert_parsed_expression_simplify_to("_C^3", "1×_C^3");
* assert_parsed_expression_simplify_to("_N^(1/2)", "1×_N^\u00121/2\u0013");
*/
/* Taking exponents complexity into account :
* -> See note on metrics in CanSimplifyUnitProduct in multiplication.cpp
* assert_parsed_expression_simplify_to("_C×_s", "1×_C×_s");
* assert_parsed_expression_simplify_to("_C^10", "1×_C^10");
* assert_parsed_expression_simplify_to("_ha", "1×_ha");
* FIXME : int8_t norm metric overflow, only visible with a non constant norm
* assert_parsed_expression_simplify_to("_C^130", "1×_C^130"); */
assert_parsed_expression_simplify_to("_m_s^-2", "1×_m×_s^\u0012-2\u0013");
/* SI derived units with special names and symbols */
assert_parsed_expression_simplify_to("_kg×_m×_s^(-2)", "1×_N");
assert_parsed_expression_simplify_to("_kg×_m^(-1)×_s^(-2)", "1×_Pa");
@@ -224,10 +251,6 @@ QUIZ_CASE(poincare_simplification_units) {
assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-3)×_A^(-1)", "1×_V");
assert_parsed_expression_simplify_to("_m^(-2)×_kg^(-1)×_s^4×_A^2", "1×_F");
assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-3)×_A^(-2)", "1×");
// FIXME _S should not be simplified to _Ω^(-1)
// A possible solution: a unit with exponent +1 is simpler than a unit with exponent -1.
// The same should probably go for Hz.
// assert_parsed_expression_simplify_to("_kg^(-1)×_m^(-2)×_s^3×_A^2", "1×_S");
assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-2)×_A^(-1)", "1×_Wb");
assert_parsed_expression_simplify_to("_kg×_s^(-2)×_A^(-1)", "1×_T");
assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-2)×_A^(-2)", "1×_H");