From f953443eb8d9a64b100ca55fa1dcabe429c673f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Nov 2019 09:44:32 +0100 Subject: [PATCH 01/12] [poincare] Power: when reducing, apply multinome development only when the target is the User --- poincare/src/power.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 77bba4c05..43e36181c 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -743,8 +743,12 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex } /* Step 13: (a0+a1+...am)^n with n integer - * -> a^n+?a^(n-1)*b+?a^(n-2)*b^2+...+b^n (Multinome) */ + * -> a^n+?a^(n-1)*b+?a^(n-2)*b^2+...+b^n (Multinome) + * We apply this rule only when the target is the User. Indeed, developing + * the multinome is likely to increase the numbers of operations and to + * lead to precision loss. */ if (!letPowerAtRoot + && reductionContext.target() == ExpressionNode::ReductionTarget::User && indexType == ExpressionNode::Type::Rational && !static_cast(index).signedIntegerNumerator().isZero() && static_cast(index).isInteger() From 11b8ed72a17c41fce9583cbe90393c64929a8ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Nov 2019 11:14:02 +0100 Subject: [PATCH 02/12] [poincare] Do not infer ReductionTarget but give it as parameter of denominator, addMissingFactors, factorizeSineAndCosine --- poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/expression_node.h | 2 +- poincare/include/poincare/multiplication.h | 10 ++--- poincare/include/poincare/power.h | 4 +- poincare/include/poincare/rational.h | 2 +- poincare/src/addition.cpp | 4 +- poincare/src/expression_node.cpp | 2 +- poincare/src/multiplication.cpp | 42 ++++++++++----------- poincare/src/power.cpp | 10 ++--- poincare/src/rational.cpp | 2 +- 10 files changed, 39 insertions(+), 41 deletions(-) diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 13cdd7aad..df49a70a8 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -342,7 +342,7 @@ protected: * Warning: this must be called on reduced expressions */ Expression makePositiveAnyNegativeNumeralFactor(ExpressionNode::ReductionContext reductionContext); - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { return node()->denominator(context, complexFormat, angleUnit); } + Expression denominator(ExpressionNode::ReductionContext reductionContext) const { return node()->denominator(reductionContext); } Expression shallowReduce(ExpressionNode::ReductionContext reductionContext) { return node()->shallowReduce(reductionContext); } Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext) { return node()->shallowBeautify(reductionContext); } Expression deepBeautify(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 0dd39c7fd..70a653acb 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -196,7 +196,7 @@ public: /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); /* Return a clone of the denominator part of the expression */ - /*!*/ virtual Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + /*!*/ virtual Expression denominator(ExpressionNode::ReductionContext reductionContext) const; /* LayoutShape is used to check if the multiplication sign can be omitted between two expressions. It depends on the "layout syle" of the on the right of the left expression */ enum class LayoutShape { Decimal, diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index bd38e44c3..09d2e9d2c 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -47,7 +47,7 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext reductionContext) override; - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override; + Expression denominator(ExpressionNode::ReductionContext reductionContext) const override; // Approximation template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { @@ -82,7 +82,7 @@ public: Expression setSign(ExpressionNode::Sign s, ExpressionNode::ReductionContext reductionContext); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Expression denominator(ExpressionNode::ReductionContext reductionContext) const; void sortChildrenInPlace(NAryExpressionNode::ExpressionOrder order, Context * context, bool canBeInterrupted) { NAryExpression::sortChildrenInPlace(order, context, false, canBeInterrupted); } @@ -94,8 +94,8 @@ private: void mergeInChildByFactorizingBase(int i, Expression e, ExpressionNode::ReductionContext reductionContext); void factorizeExponent(int i, int j, ExpressionNode::ReductionContext reductionContext); Expression distributeOnOperandAtIndex(int index, ExpressionNode::ReductionContext reductionContext); - void addMissingFactors(Expression factor, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - void factorizeSineAndCosine(int i, int j, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + void addMissingFactors(Expression factor, ExpressionNode::ReductionContext reductionContext); + void factorizeSineAndCosine(int i, int j, ExpressionNode::ReductionContext reductionContext); static bool HaveSameNonNumeralFactors(const Expression & e1, const Expression & e2); static bool TermsHaveIdenticalBase(const Expression & e1, const Expression & e2); static bool TermsHaveIdenticalExponent(const Expression & e1, const Expression & e2); @@ -104,7 +104,7 @@ private: static const Expression CreateExponent(Expression e); /* Warning: mergeNegativePower doesnot always return a multiplication: * *(b^-1,c^-1) -> (bc)^-1 */ - Expression mergeNegativePower(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + Expression mergeNegativePower(ExpressionNode::ReductionContext reductionContext); static inline const Expression Base(const Expression e); }; diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index 76bcb6c71..06097d962 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -52,7 +52,7 @@ private: LayoutShape rightLayoutShape() const override { return LayoutShape::RightOfPower; } int simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted) const override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted) const override; - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override; + Expression denominator(ReductionContext reductionContext) const override; // Evaluation template static MatrixComplex computeOnComplexAndMatrix(const std::complex c, const MatrixComplex n, Preferences::ComplexFormat complexFormat); template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex d, Preferences::ComplexFormat complexFormat); @@ -82,7 +82,7 @@ private: constexpr static int k_maxNumberOfTermsInExpandedMultinome = 25; // Simplification - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Expression denominator(ExpressionNode::ReductionContext reductionContext) const; Expression simplifyPowerPower(ExpressionNode::ReductionContext reductionContext); Expression simplifyPowerMultiplication(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 4ec5e0b1a..84da9e017 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -57,7 +57,7 @@ private: Expression shallowBeautify(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { assert(!m_negative); return isInteger() ? LayoutShape::Integer : LayoutShape::Fraction; }; Expression setSign(Sign s, ReductionContext reductionContext) override; - Expression denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override; + Expression denominator(ReductionContext reductionContext) const override; bool m_negative; uint8_t m_numberOfDigitsNumerator; uint8_t m_numberOfDigitsDenominator; diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index 41b314ed2..182a06cca 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -354,7 +354,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte Multiplication commonDenominator = Multiplication::Builder(); for (int i = 0; i < numberOfChildren(); i++) { Expression childI = childAtIndex(i); - Expression currentDenominator = childI.denominator(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression currentDenominator = childI.denominator(reductionContext); if (!currentDenominator.isUninitialized()) { if (currentDenominator.recursivelyMatches(Expression::IsRandom, reductionContext.context(), true)) { // Remove "random" factors @@ -364,7 +364,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte continue; } // Make commonDenominator = LeastCommonMultiple(commonDenominator, denominator); - commonDenominator.addMissingFactors(currentDenominator, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + commonDenominator.addMissingFactors(currentDenominator, reductionContext); } } if (commonDenominator.numberOfChildren() == 0) { diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index 0b4a4219d..c69dfadd2 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -129,7 +129,7 @@ void ExpressionNode::setChildrenInPlace(Expression other) { Expression(this).defaultSetChildrenInPlace(other); } -Expression ExpressionNode::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression ExpressionNode::denominator(ReductionContext reductionContext) const { return Expression(); } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 0e3aa4c1d..62e500494 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -200,8 +200,8 @@ Expression MultiplicationNode::shallowBeautify(ReductionContext reductionContext return Multiplication(this).shallowBeautify(reductionContext); } -Expression MultiplicationNode::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Multiplication(this).denominator(context, complexFormat, angleUnit); +Expression MultiplicationNode::denominator(ReductionContext reductionContext) const { + return Multiplication(this).denominator(reductionContext); } /* Multiplication */ @@ -287,7 +287,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu /* Step 2: Merge negative powers: a*b^(-1)*c^(-pi)*d = a*(b*c^pi)^(-1) * This also turns 2/3*a into 2*a*3^(-1) */ - Expression thisExp = mergeNegativePower(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression thisExp = mergeNegativePower(reductionContext); if (thisExp.type() == ExpressionNode::Type::Power) { return thisExp.shallowBeautify(reductionContext); } @@ -320,13 +320,13 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu return thisExp; } -Expression Multiplication::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression Multiplication::denominator(ExpressionNode::ReductionContext reductionContext) const { // Merge negative power: a*b^-1*c^(-Pi)*d = a*(b*c^Pi)^-1 // WARNING: we do not want to change the expression but to create a new one. Multiplication thisClone = clone().convert(); - Expression e = thisClone.mergeNegativePower(context, complexFormat, angleUnit); + Expression e = thisClone.mergeNegativePower(reductionContext); if (e.type() == ExpressionNode::Type::Power) { - return e.denominator(context, complexFormat, angleUnit); + return e.denominator(reductionContext); } else { assert(e.type() == ExpressionNode::Type::Multiplication); for (int i = 0; i < e.numberOfChildren(); i++) { @@ -492,7 +492,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext for (int j = i+1; j < numberOfChildren(); j++) { Expression o2 = childAtIndex(j); if (Base(o2).type() == ExpressionNode::Type::Cosine && TermHasNumeralExponent(o2) && Base(o2).childAtIndex(0).isIdenticalTo(x)) { - factorizeSineAndCosine(i, j, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + factorizeSineAndCosine(i, j, reductionContext); break; } } @@ -719,10 +719,10 @@ Expression Multiplication::distributeOnOperandAtIndex(int i, ExpressionNode::Red return a.shallowReduce(reductionContext); // Order terms, put under a common denominator if needed } -void Multiplication::addMissingFactors(Expression factor, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +void Multiplication::addMissingFactors(Expression factor, ExpressionNode::ReductionContext reductionContext) { if (factor.type() == ExpressionNode::Type::Multiplication) { for (int j = 0; j < factor.numberOfChildren(); j++) { - addMissingFactors(factor.childAtIndex(j), context, complexFormat, angleUnit); + addMissingFactors(factor.childAtIndex(j), reductionContext); } return; } @@ -743,7 +743,6 @@ void Multiplication::addMissingFactors(Expression factor, Context * context, Pre if (factor.type() != ExpressionNode::Type::Rational) { /* If factor is not a rational, we merge it with the child of identical * base if any. Otherwise, we add it as an new child. */ - ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User); for (int i = 0; i < numberOfChildren(); i++) { if (TermsHaveIdenticalBase(childAtIndex(i), factor)) { Expression sub = Subtraction::Builder(CreateExponent(childAtIndex(i)), CreateExponent(factor)).deepReduce(reductionContext); @@ -764,10 +763,10 @@ void Multiplication::addMissingFactors(Expression factor, Context * context, Pre } } addChildAtIndexInPlace(factor.clone(), 0, numberOfChildren()); - sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, context, true); + sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, reductionContext.context(), true); } -void Multiplication::factorizeSineAndCosine(int i, int j, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +void Multiplication::factorizeSineAndCosine(int i, int j, ExpressionNode::ReductionContext reductionContext) { /* This function turn sin(x)^p * cos(x)^q into either: * - tan(x)^p*cos(x)^(p+q) if |p|<|q| * - tan(x)^(-q)*sin(x)^(p+q) otherwise */ @@ -783,7 +782,6 @@ void Multiplication::factorizeSineAndCosine(int i, int j, Context * context, Pre Number absP = p.clone().convert().setSign(ExpressionNode::Sign::Positive); Number absQ = q.clone().convert().setSign(ExpressionNode::Sign::Positive); Expression tan = Tangent::Builder(x.clone()); - ExpressionNode::ReductionContext userReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User); if (Number::NaturalOrder(absP, absQ) < 0) { // Replace sin(x) by tan(x) or sin(x)^p by tan(x)^p if (p.isRationalOne()) { @@ -791,19 +789,19 @@ void Multiplication::factorizeSineAndCosine(int i, int j, Context * context, Pre } else { replaceChildAtIndexInPlace(i, Power::Builder(tan, p)); } - childAtIndex(i).shallowReduce(userReductionContext); + childAtIndex(i).shallowReduce(reductionContext); // Replace cos(x)^q by cos(x)^(p+q) replaceChildAtIndexInPlace(j, Power::Builder(Base(childAtIndex(j)), sumPQ)); - childAtIndex(j).shallowReduce(userReductionContext); + childAtIndex(j).shallowReduce(reductionContext); } else { // Replace cos(x)^q by tan(x)^(-q) Expression newPower = Power::Builder(tan, Number::Multiplication(q, Rational::Builder(-1))); - newPower.childAtIndex(1).shallowReduce(userReductionContext); + newPower.childAtIndex(1).shallowReduce(reductionContext); replaceChildAtIndexInPlace(j, newPower); - newPower.shallowReduce(userReductionContext); + newPower.shallowReduce(reductionContext); // Replace sin(x)^p by sin(x)^(p+q) replaceChildAtIndexInPlace(i, Power::Builder(Base(childAtIndex(i)), sumPQ)); - childAtIndex(i).shallowReduce(userReductionContext); + childAtIndex(i).shallowReduce(reductionContext); } } @@ -857,7 +855,7 @@ bool Multiplication::TermHasNumeralExponent(const Expression & e) { return false; } -Expression Multiplication::mergeNegativePower(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Expression Multiplication::mergeNegativePower(ExpressionNode::ReductionContext reductionContext) { /* mergeNegativePower groups all factors that are power of form a^(-b) together * for instance, a^(-1)*b^(-c)*c = c*(a*b^c)^(-1) */ Multiplication m = Multiplication::Builder(); @@ -876,7 +874,7 @@ Expression Multiplication::mergeNegativePower(Context * context, Preferences::Co while (i < numberOfChildren()) { if (childAtIndex(i).type() == ExpressionNode::Type::Power) { Expression p = childAtIndex(i); - Expression positivePIndex = p.childAtIndex(1).makePositiveAnyNegativeNumeralFactor( ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User)); + Expression positivePIndex = p.childAtIndex(1).makePositiveAnyNegativeNumeralFactor(reductionContext); if (!positivePIndex.isUninitialized()) { // Remove a^(-b) from the Multiplication removeChildAtIndexInPlace(i); @@ -894,10 +892,10 @@ Expression Multiplication::mergeNegativePower(Context * context, Preferences::Co if (m.numberOfChildren() == 0) { return *this; } - m.sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, context, true); + m.sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, reductionContext.context(), true); Power p = Power::Builder(m.squashUnaryHierarchyInPlace(), Rational::Builder(-1)); addChildAtIndexInPlace(p, 0, numberOfChildren()); - sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, context, true); + sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, true, canBeInterrupted); }, reductionContext.context(), true); return squashUnaryHierarchyInPlace(); } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 43e36181c..7130d720d 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -211,8 +211,8 @@ int PowerNode::simplificationOrderSameType(const ExpressionNode * e, bool ascend return SimplificationOrder(childAtIndex(1), e->childAtIndex(1), ascending, canBeInterrupted); } -Expression PowerNode::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Power(this).denominator(context, complexFormat, angleUnit); +Expression PowerNode::denominator(ReductionContext reductionContext) const { + return Power(this).denominator(reductionContext); } // Evaluation @@ -849,7 +849,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { // Step 1: X^-y -> 1/(X->shallowBeautify)^y - Expression p = denominator(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression p = denominator(reductionContext); // If the denominator is initialized, the index of the power is of form -y if (!p.isUninitialized()) { Division d = Division::Builder(Rational::Builder(1), p); @@ -891,11 +891,11 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont // Private // Simplification -Expression Power::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression Power::denominator(ExpressionNode::ReductionContext reductionContext) const { // Clone the power Expression clone = Power::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()); // If the power is of form x^(-y), denominator should be x^y - Expression positiveIndex = clone.childAtIndex(1).makePositiveAnyNegativeNumeralFactor(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User)); + Expression positiveIndex = clone.childAtIndex(1).makePositiveAnyNegativeNumeralFactor(reductionContext); if (!positiveIndex.isUninitialized()) { // if y was -1, clone is now x^1, denominator is then only x // we cannot shallowReduce the clone as it is not attached to its parent yet diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index cd9b99beb..b04ed48a9 100644 --- a/poincare/src/rational.cpp +++ b/poincare/src/rational.cpp @@ -145,7 +145,7 @@ Expression RationalNode::shallowBeautify(ReductionContext reductionContext) { return Rational(this).shallowBeautify(); } -Expression RationalNode::denominator(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression RationalNode::denominator(ReductionContext reductionContext) const { return Rational(this).denominator(); } From ccf848a9ebff543e73fca9196232891c3732ecfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Nov 2019 16:49:55 +0100 Subject: [PATCH 03/12] [poincare] Add test: do not expand multinome when reduction target is System --- apps/regression/model/model.cpp | 4 +- apps/shared/expression_model.cpp | 2 +- apps/shared/poincare_helpers.h | 4 +- apps/solver/equation_store.cpp | 2 +- poincare/include/poincare/expression.h | 4 +- poincare/include/poincare/expression_node.h | 10 ++++- poincare/src/equal.cpp | 6 ++- poincare/src/expression.cpp | 12 +++--- poincare/src/matrix.cpp | 2 +- poincare/src/power.cpp | 14 +++---- poincare/test/expression_properties.cpp | 2 +- poincare/test/helper.cpp | 8 ++-- poincare/test/helper.h | 5 ++- poincare/test/simplification.cpp | 44 +++++++++++++++------ 14 files changed, 75 insertions(+), 44 deletions(-) diff --git a/apps/regression/model/model.cpp b/apps/regression/model/model.cpp index ca91effd3..d949b1571 100644 --- a/apps/regression/model/model.cpp +++ b/apps/regression/model/model.cpp @@ -18,14 +18,14 @@ void Model::tidy() { Poincare::Expression Model::simplifiedExpression(double * modelCoefficients, Poincare::Context * context) { Expression e = expression(modelCoefficients); if (!e.isUninitialized()) { - PoincareHelpers::Simplify(&e, context); + PoincareHelpers::Simplify(&e, context, ExpressionNode::ReductionTarget::SystemForApproximation); } return e; } double Model::levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) { Expression yExpression = Number::DecimalNumber(y); - PoincareHelpers::Simplify(&yExpression, context); + PoincareHelpers::Simplify(&yExpression, context, ExpressionNode::ReductionTarget::SystemForApproximation); Expression modelExpression = simplifiedExpression(modelCoefficients, context); double result = PoincareHelpers::NextIntersection(modelExpression, "x", xMin, step, xMax, context, yExpression).x1(); return result; diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index 015526c11..dcdb5d347 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -47,7 +47,7 @@ Expression ExpressionModel::expressionReduced(const Storage::Record * record, Po m_expression = Undefined::Builder(); } else { m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); - PoincareHelpers::Simplify(&m_expression, context); + PoincareHelpers::Simplify(&m_expression, context, ExpressionNode::ReductionTarget::SystemForApproximation); // simplify might return an uninitialized Expression if interrupted if (m_expression.isUninitialized()) { m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index b8d73f340..0f2ba050e 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -62,10 +62,10 @@ inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Contex return Poincare::Expression::ParseAndSimplify(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); } -inline void Simplify(Poincare::Expression * e, Poincare::Context * context, bool symbolicComputation = true) { +inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, bool symbolicComputation = true) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); - *e = e->simplify(context, complexFormat, preferences->angleUnit(), symbolicComputation); + *e = e->simplify(context, complexFormat, preferences->angleUnit(), target, symbolicComputation); } inline void ParseAndSimplifyAndApproximate(const char * text, Poincare::Expression * simplifiedExpression, Poincare::Expression * approximateExpression, Poincare::Context * context, bool symbolicComputation = true) { diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index d083bb533..ea3112b53 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -276,7 +276,7 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact assert(degree == 2); // Compute delta = b*b-4ac Expression delta = Subtraction::Builder(Power::Builder(coefficients[1].clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), coefficients[0].clone(), coefficients[2].clone())); - delta = delta.simplify(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit()); + delta = delta.simplify(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::SystemForApproximation); if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index df49a70a8..b1abfac91 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -221,11 +221,11 @@ public: * (For instance, in Polar mode, they return an expression of the form * r*e^(i*th) reduced and approximated.) */ static Expression ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation = true); - Expression simplify(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation = true); + Expression simplify(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target, bool symbolicComputation = true); static void ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation = true); void simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation = true); - Expression reduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + Expression reduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target = ExpressionNode::ReductionTarget::SystemForApproximation); Expression mapOnMatrixFirstChild(ExpressionNode::ReductionContext reductionContext); static Expression ExpressionWithoutSymbols(Expression expressionWithSymbols, Context * context); diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 70a653acb..96d8ebe96 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -110,7 +110,15 @@ public: /* Properties */ enum class ReductionTarget { - System = 0, + /* Minimal reduction: this at least reduces rationals operations as + * "1-0.3-0.7 --> 0" */ + SystemForApproximation = 0, + /* Expansion of Newton multinome to be able to identify polynoms */ + SystemForAnalysis, + /* Additional features as: + * - factorizing on a common denominator + * - turning complex expression into the form a+ib + * - identifying tangent in cos/sin polynoms ... */ User }; enum class Sign { diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index 611aa6b86..c51d3fb56 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -46,7 +46,11 @@ Evaluation EqualNode::templatedApproximate(Context * context, Preferences::Co Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { Expression sub = Subtraction::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()); - return sub.reduce(context, complexFormat, angleUnit); + /* When reducing the equation, we specify the reduction target to be + * SystemForAnalysis. This enables to expand Newton multinom to be able to + * detect polynom correctly ("(x=2)^2" in this form won't be detected + * unless expanded). */ + return sub.reduce(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForAnalysis); } Expression Equal::shallowReduce() { diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index cce1c46aa..2b95ece7c 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -519,7 +519,7 @@ Expression Expression::ParseAndSimplify(const char * text, Context * context, Pr if (exp.isUninitialized()) { return Undefined::Builder(); } - exp = exp.simplify(context, complexFormat, angleUnit, symbolicSimplification); + exp = exp.simplify(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicSimplification); /* simplify might have been interrupted, in which case the resulting * expression is uninitialized, so we need to check that. */ if (exp.isUninitialized()) { @@ -547,9 +547,9 @@ void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * } } -Expression Expression::simplify(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation) { +Expression Expression::simplify(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target, bool symbolicComputation) { sSimplificationHasBeenInterrupted = false; - ExpressionNode::ReductionContext c = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::System, symbolicComputation); + ExpressionNode::ReductionContext c = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, target, symbolicComputation); Expression e = deepReduce(c); if (!sSimplificationHasBeenInterrupted) { e = e.deepBeautify(c); @@ -618,7 +618,7 @@ void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expre Expression e = clone().deepReduce(userReductionContext); if (sSimplificationHasBeenInterrupted) { sSimplificationHasBeenInterrupted = false; - ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::System, symbolicComputation); + ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation); e = deepReduce(systemReductionContext); } *simplifiedExpression = Expression(); @@ -730,9 +730,9 @@ Expression Expression::angleUnitToRadian(Preferences::AngleUnit angleUnit) { return *this; } -Expression Expression::reduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Expression Expression::reduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target) { sSimplificationHasBeenInterrupted = false; - return deepReduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::System, true)); + return deepReduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, target, true)); } Expression Expression::deepReduce(ExpressionNode::ReductionContext reductionContext) { diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 6a206b345..757981199 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -115,7 +115,7 @@ void Matrix::addChildrenAsRowInPlace(TreeHandle t, int i) { int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool inPlace) { Matrix m = inPlace ? *this : clone().convert(); - ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::System); + ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation); m = m.rowCanonize(systemReductionContext, nullptr); int rank = m.numberOfRows(); int i = rank-1; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 7130d720d..6897375ff 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -744,11 +744,11 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex /* Step 13: (a0+a1+...am)^n with n integer * -> a^n+?a^(n-1)*b+?a^(n-2)*b^2+...+b^n (Multinome) - * We apply this rule only when the target is the User. Indeed, developing - * the multinome is likely to increase the numbers of operations and to - * lead to precision loss. */ + * We don't apply this rule when the target is the SystemForApproximation. + * Indeed, developing the multinome is likely to increase the numbers of + * operations and lead to precision loss. */ if (!letPowerAtRoot - && reductionContext.target() == ExpressionNode::ReductionTarget::User + && reductionContext.target() != ExpressionNode::ReductionTarget::SystemForApproximation && indexType == ExpressionNode::Type::Rational && !static_cast(index).signedIntegerNumerator().isZero() && static_cast(index).isInteger() @@ -871,12 +871,12 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return result; } - /* Optional Step 3: if the ReductionTarget is the System, turn a^(p/q) into - * (root(a, q))^p + /* Optional Step 3: if the ReductionTarget is the SystemForApproximation, + * turn a^(p/q) into (root(a, q))^p * Indeed, root(a, q) can have a real root which is not the principale angle * but that we want to return in real complex format. This special case is * handled in NthRoot approximation but not in Power approximation. */ - if (reductionContext.target() == ExpressionNode::ReductionTarget::System && childAtIndex(1).type() == ExpressionNode::Type::Rational) { + if (reductionContext.target() != ExpressionNode::ReductionTarget::User && childAtIndex(1).type() == ExpressionNode::Type::Rational) { Integer p = childAtIndex(1).convert().signedIntegerNumerator(); Integer q = childAtIndex(1).convert().integerDenominator(); Expression nthRoot = q.isOne() ? childAtIndex(0) : NthRoot::Builder(childAtIndex(0), Rational::Builder(q)); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index d138e8b6a..e2f7697e9 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -311,7 +311,7 @@ QUIZ_CASE(poincare_preperties_get_variables) { void assert_reduced_expression_has_polynomial_coefficient(const char * expression, const char * symbolName, const char ** coefficients, Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, false); - e = e.reduce(&globalContext, complexFormat, angleUnit); + e = e.reduce(&globalContext, complexFormat, angleUnit, SystemForAnalysis); Expression coefficientBuffer[Poincare::Expression::k_maxNumberOfPolynomialCoefficients]; int d = e.getPolynomialReducedCoefficients(symbolName, coefficientBuffer, &globalContext, complexFormat, Radian); for (int i = 0; i <= d; i++) { diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index a35e01306..d79d8b27d 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -54,11 +54,11 @@ Poincare::Expression parse_expression(const char * expression, bool addParenthes return result; } -void assert_simplify(const char * expression, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat) { +void assert_simplify(const char * expression, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, false); quiz_assert_print_if_failure(!e.isUninitialized(), expression); - e = e.simplify(&globalContext, complexFormat, angleUnit); + e = e.simplify(&globalContext, complexFormat, angleUnit, target); quiz_assert_print_if_failure(!(e.isUninitialized()), expression); } @@ -68,7 +68,7 @@ void assert_parsed_expression_simplify_to(const char * expression, const char * if (target == ExpressionNode::ReductionTarget::User) { copy.simplifyAndApproximate(©, nullptr, context, complexFormat, angleUnit, symbolicComputation); } else { - copy = copy.simplify(context, complexFormat, angleUnit, symbolicComputation); + copy = copy.simplify(context, complexFormat, angleUnit, target, symbolicComputation); } if (copy.isUninitialized()) { return e; @@ -81,7 +81,7 @@ template void assert_expression_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { int numberOfDigits = sizeof(T) == sizeof(double) ? PrintFloat::k_numberOfStoredSignificantDigits : PrintFloat::k_numberOfPrintedSignificantDigits; numberOfDigits = numberOfSignificantDigits > 0 ? numberOfSignificantDigits : numberOfDigits; - assert_parsed_expression_process_to(expression, approximation, ExpressionNode::ReductionTarget::System, complexFormat, angleUnit, false, [](Expression e, Context * context, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation) { + assert_parsed_expression_process_to(expression, approximation, ExpressionNode::ReductionTarget::SystemForApproximation, complexFormat, angleUnit, false, [](Expression e, Context * context, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool symbolicComputation) { return e.approximate(context, complexFormat, angleUnit); }, numberOfDigits); } diff --git a/poincare/test/helper.h b/poincare/test/helper.h index ea742d44b..7a110e016 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -5,7 +5,8 @@ const char * MaxIntegerString(); // (2^32)^k_maxNumberOfDigits-1 const char * OverflowedIntegerString(); // (2^32)^k_maxNumberOfDigits const char * BigOverflowedIntegerString(); // OverflowedIntegerString with a 2 on first digit -constexpr Poincare::ExpressionNode::ReductionTarget System = Poincare::ExpressionNode::ReductionTarget::System; +constexpr Poincare::ExpressionNode::ReductionTarget SystemForApproximation = Poincare::ExpressionNode::ReductionTarget::SystemForApproximation; +constexpr Poincare::ExpressionNode::ReductionTarget SystemForAnalysis = Poincare::ExpressionNode::ReductionTarget::SystemForAnalysis; constexpr Poincare::ExpressionNode::ReductionTarget User = Poincare::ExpressionNode::ReductionTarget::User; constexpr Poincare::Preferences::AngleUnit Degree = Poincare::Preferences::AngleUnit::Degree; constexpr Poincare::Preferences::AngleUnit Radian = Poincare::Preferences::AngleUnit::Radian; @@ -30,7 +31,7 @@ Poincare::Expression parse_expression(const char * expression, bool addParenthes // Simplification -void assert_simplify(const char * expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian); +void assert_simplify(const char * expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User); void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, Poincare::ExpressionNode::ReductionTarget target = User, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, bool symbolicComputation = true); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 2699ddc57..9379274d4 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -959,36 +959,54 @@ QUIZ_CASE(poincare_simplification_complex_format) { } QUIZ_CASE(poincare_simplification_reduction_target) { - assert_parsed_expression_simplify_to("1/π+1/x", "1/x+1/π", System); + // Factorize on the same denominator only for ReductionTarget = User + assert_parsed_expression_simplify_to("1/π+1/x", "1/x+1/π", SystemForAnalysis); + assert_parsed_expression_simplify_to("1/π+1/x", "1/x+1/π", SystemForApproximation); assert_parsed_expression_simplify_to("1/π+1/x", "\u0012x+π\u0013/\u0012π×x\u0013", User); - assert_parsed_expression_simplify_to("1/(1+𝐢)", "1/\u0012𝐢+1\u0013", System); + // Display in the form a+ib only for ReductionTarget = User + assert_parsed_expression_simplify_to("1/(1+𝐢)", "1/\u0012𝐢+1\u0013", SystemForAnalysis); + assert_parsed_expression_simplify_to("1/(1+𝐢)", "1/\u0012𝐢+1\u0013", SystemForApproximation); assert_parsed_expression_simplify_to("1/(1+𝐢)", "1/2-1/2×𝐢", User); - assert_parsed_expression_simplify_to("sin(x)/(cos(x)×cos(x))", "sin(x)/cos(x)^2", System); + // Replace sin/cos-->tan for ReductionTarget = User + assert_parsed_expression_simplify_to("sin(x)/(cos(x)×cos(x))", "sin(x)/cos(x)^2", SystemForAnalysis); + assert_parsed_expression_simplify_to("sin(x)/(cos(x)×cos(x))", "sin(x)/cos(x)^2", SystemForApproximation); assert_parsed_expression_simplify_to("sin(x)/(cos(x)×cos(x))", "tan(x)/cos(x)", User); - assert_parsed_expression_simplify_to("x^0", "x^0", System); + // Apply rule x^0 --> 1 for ReductionTarget = User (because this is not always true) + assert_parsed_expression_simplify_to("x^0", "x^0", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^0", "x^0", SystemForApproximation); assert_parsed_expression_simplify_to("x^0", "1", User); + assert_parsed_expression_simplify_to("(1+x)/(1+x)", "(x+1)^0", SystemForApproximation); + assert_parsed_expression_simplify_to("(1+x)/(1+x)", "1", User); - assert_parsed_expression_simplify_to("x^(2/3)", "root(x,3)^2", System); + // Apply rule x^(2/3) --> root(x,3)^2 for ReductionTarget = System + assert_parsed_expression_simplify_to("x^(2/3)", "root(x,3)^2", SystemForApproximation); + assert_parsed_expression_simplify_to("x^(2/3)", "root(x,3)^2", SystemForAnalysis); assert_parsed_expression_simplify_to("x^(2/3)", "x^\u00122/3\u0013", User); - assert_parsed_expression_simplify_to("x^(1/3)", "root(x,3)", System); - assert_parsed_expression_simplify_to("x^(1/3)", "root(x,3)", System); - assert_parsed_expression_simplify_to("x^2", "x^2", System); + assert_parsed_expression_simplify_to("x^(1/3)", "root(x,3)", SystemForApproximation); + assert_parsed_expression_simplify_to("x^(1/3)", "root(x,3)", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^(1/3)", "root(x,3)", User); + assert_parsed_expression_simplify_to("x^2", "x^2", SystemForApproximation); assert_parsed_expression_simplify_to("x^2", "x^2", User); - assert_parsed_expression_simplify_to("1/(√(2)+√(3))", "1/\u0012√(3)+√(2)\u0013", System); + // Remove square root at denominator for ReductionTarget = User + assert_parsed_expression_simplify_to("1/(√(2)+√(3))", "1/\u0012√(3)+√(2)\u0013", SystemForApproximation); assert_parsed_expression_simplify_to("1/(√(2)+√(3))", "√(3)-√(2)", User); - assert_parsed_expression_simplify_to("sign(abs(x))", "sign(abs(x))", System); + // Always reduce sign for ReductionTarget = User + assert_parsed_expression_simplify_to("sign(abs(x))", "sign(abs(x))", SystemForApproximation); assert_parsed_expression_simplify_to("sign(abs(x))", "1", User); - assert_parsed_expression_simplify_to("atan(1/x)", "atan(1/x)", System); + // Apply rule atan(1/x)-> (π×sign(x)-2×atan(x))/2 for ReductionTarget = User (as it is not always true) + assert_parsed_expression_simplify_to("atan(1/x)", "atan(1/x)", SystemForApproximation); assert_parsed_expression_simplify_to("atan(1/x)", "\u0012π×sign(x)-2×atan(x)\u0013/2", User); - assert_parsed_expression_simplify_to("(1+x)/(1+x)", "(x+1)^0", System); - assert_parsed_expression_simplify_to("(1+x)/(1+x)", "1", User); + // Expand multinome when ReductionTarget is not SystemForApproximation as it increases precision loss + assert_parsed_expression_simplify_to("(2+x)^2", "(x+2)^2", SystemForApproximation); + assert_parsed_expression_simplify_to("(2+x)^2", "x^2+4×x+4", SystemForAnalysis); + assert_parsed_expression_simplify_to("(2+x)^2", "x^2+4×x+4", User); } QUIZ_CASE(poincare_simplification_mix) { From c659cc74774e8022777fc2d0a08227f853eecc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Nov 2019 17:00:43 +0100 Subject: [PATCH 04/12] [poincare] EquationStore: add tests --- apps/solver/test/equation_store.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index f98a56125..396c6a9ed 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -128,6 +128,11 @@ QUIZ_CASE(equation_solve) { const char * solutions11[] = {"\u0012\u0012√\u0012π^\u00122\u0013-2π+8√\u00125\u0013+9\u0013-π+1\u0013/\u00124\u0013\u0013", "\u0012\u0012-√\u0012π^\u00122\u0013-2π+8√\u00125\u0013+9\u0013-π+1\u0013/\u00124\u0013\u0013", "π^\u00122\u0013-2π+8√\u00125\u0013+9"}; // (√(π^2-2π+8√(5)+9)-π+1)/4, (-√(π^2-2π+8×√(5)+9)-π+1)/4, π^2-2π+8√(5)+9 assert_equation_system_exact_solve_to(equations11, EquationStore::Error::NoError, EquationStore::Type::PolynomialMonovariable, (const char **)variablesx, solutions11, 3); + // (x-3)^2 + const char * equations21[] = {"(x-3)^2=0", 0}; + const char * solutions21[] = {"3", "0"}; + assert_equation_system_exact_solve_to(equations21, EquationStore::Error::NoError, EquationStore::Type::PolynomialMonovariable, (const char **)variablesx, solutions21, 2); + // TODO // x^3 - 4x^2 + 6x - 24 = 0 //const char * equations10[] = {"2×x^2-4×x+4=3", 0}; @@ -205,6 +210,11 @@ QUIZ_CASE(equation_solve_complex_format) { const char * equations4[] = {"x+√(-1)×√(-1)=0", 0}; assert_equation_system_exact_solve_to(equations4, EquationStore::Error::EquationUnreal, EquationStore::Type::LinearSystem, (const char **)variablesx, nullptr, 0); + // root(-8,3)*x+3 = 0 --> 3/2 in R + const char * equations5[] = {"root(-8,3)*x+3=0", 0}; + const char * solutions5[] = {"\u0012\u00123\u0013/\u00122\u0013\u0013"}; + assert_equation_system_exact_solve_to(equations5, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions5, 1); + Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); // x+𝐢 = 0 --> x = -𝐢 assert_equation_system_exact_solve_to(equations0, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions0, 1); @@ -224,6 +234,9 @@ QUIZ_CASE(equation_solve_complex_format) { const char * solutions4[] = {"1"}; assert_equation_system_exact_solve_to(equations4, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions4, 1); + const char * solutions5Cartesain[] = {"-\u0012\u00123\u0013/\u00124\u0013\u0013+\u0012\u00123√\u00123\u0013\u0013/\u00124\u0013\u0013𝐢"}; //-3/4+(3√3/4)*𝐢 + assert_equation_system_exact_solve_to(equations5, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions5Cartesain, 1); + Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar); // x+𝐢 = 0 --> x = e^(-π/2×i) const char * solutions0Polar[] = {"ℯ^\u0012-\u0012\u0012π\u0013/\u00122\u0013\u0013𝐢\u0013"}; // ℯ^(-(π/2)𝐢) @@ -240,6 +253,9 @@ QUIZ_CASE(equation_solve_complex_format) { const char * solutions3Polar[] = {"ℯ^\u0012-\u0012\u00123π\u0013/\u00124\u0013\u0013𝐢\u0013", "ℯ^\u0012\u0012\u0012π\u0013/\u00124\u0013\u0013𝐢\u0013", "4ℯ^\u0012\u0012\u0012π\u0013/\u00122\u0013\u0013𝐢\u0013"}; // ℯ^(-(3×π/4)𝐢)"‰, "ℯ^((π/4)𝐢)", "4ℯ^((π/2)𝐢) assert_equation_system_exact_solve_to(equations3, EquationStore::Error::NoError, EquationStore::Type::PolynomialMonovariable, (const char **)variablesx, solutions3Polar, 3); + const char * solutions5Polar[] = {"\u0012\u00123\u0013/\u00122\u0013\u0013ℯ^\u0012\u0012\u00122π\u0013/\u00123\u0013\u0013𝐢\u0013"}; //3/2ℯ^\u0012\u00122π\u0012/3\u0013𝐢"}; + assert_equation_system_exact_solve_to(equations5, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions5Polar, 1); + } QUIZ_CASE(equation_and_symbolic_computation) { From b320f28e5989b88f8cd8cc0a9343ca7f300d3487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 20 Nov 2019 10:20:45 +0100 Subject: [PATCH 05/12] [poincare] Fix comments --- poincare/src/equal.cpp | 2 +- poincare/src/power.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index c51d3fb56..c7a5dd767 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -48,7 +48,7 @@ Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat Expression sub = Subtraction::Builder(childAtIndex(0).clone(), childAtIndex(1).clone()); /* When reducing the equation, we specify the reduction target to be * SystemForAnalysis. This enables to expand Newton multinom to be able to - * detect polynom correctly ("(x=2)^2" in this form won't be detected + * detect polynom correctly ("(x+2)^2" in this form won't be detected * unless expanded). */ return sub.reduce(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForAnalysis); } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 6897375ff..d94699392 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -871,8 +871,8 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return result; } - /* Optional Step 3: if the ReductionTarget is the SystemForApproximation, - * turn a^(p/q) into (root(a, q))^p + /* Optional Step 3: if the ReductionTarget is the SystemForApproximation or + * SystemForAnalysis, turn a^(p/q) into (root(a, q))^p * Indeed, root(a, q) can have a real root which is not the principale angle * but that we want to return in real complex format. This special case is * handled in NthRoot approximation but not in Power approximation. */ From 5ec09fdbd66a7aad7e3329767fcaceff7ec976a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Nov 2019 10:57:49 +0100 Subject: [PATCH 06/12] [apps/shared] ValuesController: fix didChangeCell to reload all cells corresponding to the abscissa cell --- apps/shared/values_controller.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/shared/values_controller.cpp b/apps/shared/values_controller.cpp index ec0a2c4bd..8786a4a3d 100644 --- a/apps/shared/values_controller.cpp +++ b/apps/shared/values_controller.cpp @@ -243,10 +243,23 @@ void ValuesController::didChangeCell(int column, int row) { return; } + // Find the abscissa column corresponding to column + int abscissaColumn = 0; + int nbOfColumns = numberOfColumnsForAbscissaColumn(abscissaColumn); + while (column >= nbOfColumns) { + abscissaColumn = nbOfColumns; + nbOfColumns += numberOfColumnsForAbscissaColumn(abscissaColumn); + } + // Update the memoization of rows linked to the changed cell int nbOfMemoizedColumns = numberOfMemoizedColumn(); - for (int i = column+1; i < column+numberOfColumnsForAbscissaColumn(column); i++) { + int nbOfColumnsForAbscissa = numberOfColumnsForAbscissaColumn(abscissaColumn); + for (int i = abscissaColumn+1; i < abscissaColumn+nbOfColumnsForAbscissa; i++) { int memoizedI = valuesColumnForAbsoluteColumn(i) - m_firstMemoizedColumn; + if (memoizedI < 0 || memoizedI >= nbOfMemoizedColumns) { + // The changed column is out of the memoized table + continue; + } fillMemoizedBuffer(i, row, nbOfMemoizedColumns*memoizedRow+memoizedI); } } From 89ea25556cd10c181821673c5e6e2fcb98d92ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 22 Nov 2019 11:35:53 +0100 Subject: [PATCH 07/12] [poincare] Add test to print the float 1000 On emscripten platform, print_float(1000.0) used to return "0000" --- poincare/test/print_float.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/poincare/test/print_float.cpp b/poincare/test/print_float.cpp index 0403bacf8..b5ea82238 100644 --- a/poincare/test/print_float.cpp +++ b/poincare/test/print_float.cpp @@ -108,6 +108,9 @@ QUIZ_CASE(assert_print_floats) { assert_float_prints_to(10000000000000000000000000000.0, "1ᴇ28", DecimalMode, 14); assert_float_prints_to(10000000000000000000000000000.0, "10ᴇ27", EngineeringMode, 14); + // This used to crash on web platform + assert_float_prints_to(1000.0, "1000", DecimalMode, 7); + assert_float_prints_to(1000000.0f, "1ᴇ6", ScientificMode, 7); assert_float_prints_to(1000000.0f, "1000000", DecimalMode, 7); assert_float_prints_to(1000000.0f, "1ᴇ6", EngineeringMode, 7); From 8f69d9b8b94eb61e6f72a3f5c21ebff3a08f0ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 22 Nov 2019 15:57:27 +0100 Subject: [PATCH 08/12] [build] Fix target %.dfu for N0110: avoid downloading flasher on addresses that are being executed (in NumWorks DFU mode, execution jumps at the end of the stack, which is where we upload the flasher. The flasher is not required when NumWorks DFU mode is working) --- build/targets.device.n0110.mak | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 437d613d8..3abc930e4 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -12,7 +12,10 @@ $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_ex @echo " using an USB cable and press at the same time the 6 key and the RESET" @echo " button on the back of your device." $(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done - $(Q) $(PYTHON) build/device/dfu.py -u $(word 2,$^) - $(Q) sleep 2 + $(eval DFU_SLAVE := $(shell $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11")) + $(Q) if [[ "$(DFU_SLAVE)" == *"0483:df11"* ]]; \ + then \ + $(PYTHON) build/device/dfu.py -u $(word 2,$^); \ + sleep 2; \ + fi $(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^) - From 5f7692686f0022e7073d331515dc850c5e6a754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 25 Nov 2019 13:38:02 +0100 Subject: [PATCH 09/12] [ion/storage] Fix memoization dirtying when renaming a record Don't forget to update the CRC32 of the record Scenario : New funciton f(x) = 1, change it to parametric, rename it a(t), creae a new function -> instead of being empty, it is has a "ghost" value of [t 1]. --- ion/src/shared/storage.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 27d8a7326..cf0b19826 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -345,11 +345,14 @@ Storage::Record::ErrorStatus Storage::setBaseNameWithExtensionOfRecord(Record re return notifyFullnessToDelegate(); } overrideSizeAtPosition(p, newRecordSize); - overrideBaseNameWithExtensionAtPosition(p+sizeof(record_size_t), baseName, extension); + char * fullNamePosition = p + sizeof(record_size_t); + overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension); + // Recompute the CRC32 + record = Record(fullNamePosition); notifyChangeToDelegate(record); m_lastRecordRetrieved = record; m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; + return Record::ErrorStatus::None; } return Record::ErrorStatus::RecordDoesNotExist; } From 967dc0ea158fbaaab08182d1928cd85545179491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 25 Nov 2019 14:15:50 +0100 Subject: [PATCH 10/12] [apps/sequence] Set the Nstart/Nend messages at construction This fixes the scenario: Add a sequence, go to the Table then click on setTheInterval -> it displayed Xstart Xend instead of Nstart Nend --- apps/sequence/values/values_controller.cpp | 5 ++++- apps/sequence/values/values_controller.h | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index c5ba80fd4..36ae99790 100644 --- a/apps/sequence/values/values_controller.cpp +++ b/apps/sequence/values/values_controller.cpp @@ -24,6 +24,8 @@ ValuesController::ValuesController(Responder * parentResponder, InputEventHandle StackViewController * stack = ((StackViewController *)valuesController->stackController()); IntervalParameterController * controller = valuesController->intervalParameterController(); controller->setInterval(valuesController->intervalAtColumn(valuesController->selectedColumn())); + /* No need to change Nstart/Nend messages because they are the only messages + * used and we set them in ValuesController::ValuesController(...) */ stack->push(controller); return true; }, this), k_font) @@ -32,6 +34,7 @@ ValuesController::ValuesController(Responder * parentResponder, InputEventHandle m_sequenceTitleCells[i].setOrientation(Shared::FunctionTitleCell::Orientation::HorizontalIndicator); } setupSelectableTableViewAndCells(inputEventHandlerDelegate); + setDefaultStartEndMessages(); } // TableViewDataSource @@ -64,7 +67,7 @@ I18n::Message ValuesController::emptyMessage() { } // ValuesController -void ValuesController::setStartEndMessages(Shared::IntervalParameterController * controller, int column) { +void ValuesController::setDefaultStartEndMessages() { m_intervalParameterController.setStartEndMessages(I18n::Message::NStart, I18n::Message::NEnd); } diff --git a/apps/sequence/values/values_controller.h b/apps/sequence/values/values_controller.h index 11ea61c53..e24558e4a 100644 --- a/apps/sequence/values/values_controller.h +++ b/apps/sequence/values/values_controller.h @@ -33,7 +33,11 @@ private: constexpr static int k_maxNumberOfDisplayableCells = k_maxNumberOfDisplayableSequences * k_maxNumberOfDisplayableRows; // ValuesController - void setStartEndMessages(Shared::IntervalParameterController * controller, int column) override; + void setStartEndMessages(Shared::IntervalParameterController * controller, int column) override { + setDefaultStartEndMessages(); + } + + void setDefaultStartEndMessages(); I18n::Message valuesParameterMessageAtColumn(int columnIndex) const override; int maxNumberOfCells() override { return k_maxNumberOfDisplayableCells; } int maxNumberOfFunctions() override { return k_maxNumberOfDisplayableSequences; } From 4c11e73ad3a90db5641eb355252031931249447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 25 Nov 2019 14:39:58 +0100 Subject: [PATCH 11/12] [ion] UTF8Helper: fix CopyAndRemoveCodePoint to avoid copying truncated code point in the buffer --- ion/src/shared/unicode/utf8_helper.cpp | 9 ++++++--- ion/test/utf8_helper.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index f8c6345fc..d69016075 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -5,7 +5,6 @@ namespace UTF8Helper { -static inline int minInt(int x, int y) { return x < y ? x : y; } static inline size_t minSizeT(size_t x, size_t y) { return x < y ? x : y; } int CountOccurrences(const char * s, CodePoint c) { @@ -110,7 +109,11 @@ void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePo // Remove CodePoint c while (codePoint != UCodePointNull && bufferIndex < dstSize) { if (codePoint != c) { - int copySize = minInt(nextPointer - currentPointer, dstSize - bufferIndex); + int copySize = nextPointer - currentPointer; + if (copySize > dstSize - 1 - bufferIndex) { + // Copying the current code point to the buffer would overflow the buffer + break; + } memcpy(dst + bufferIndex, currentPointer, copySize); bufferIndex+= copySize; } @@ -118,7 +121,7 @@ void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePo codePoint = decoder.nextCodePoint(); nextPointer = decoder.stringPosition(); } - *(dst + minInt(bufferIndex, dstSize - 1)) = 0; + *(dst + bufferIndex) = 0; } void RemoveCodePoint(char * buffer, CodePoint c, const char * * pointerToUpdate, const char * stoppingPosition) { diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index 6398df9cd..cd1d1f36b 100644 --- a/ion/test/utf8_helper.cpp +++ b/ion/test/utf8_helper.cpp @@ -87,6 +87,21 @@ QUIZ_CASE(ion_utf8_copy_and_remove_code_point) { c = UCodePointLatinLetterSmallCapitalE; result = "124"; assert_copy_and_remove_code_point_gives(buffer, bufferSize, s, c, result); + + // The buffer size is to small to hold s + s = "1234ᴇ"; + c = '5'; + result = "1234"; // "1234ᴇ" size is 7 + assert_copy_and_remove_code_point_gives(buffer, 6, s, c, result); + assert_copy_and_remove_code_point_gives(buffer, 7, s, c, result); + result = "1234ᴇ"; + assert_copy_and_remove_code_point_gives(buffer, 8, s, c, result); + + s = "1234ᴇ"; + c = '4'; + result = "123ᴇ"; + assert_copy_and_remove_code_point_gives(buffer, 7, s, c, result); + } void assert_remove_code_point_gives(char * buffer, CodePoint c, const char * * indexToUpdate, const char * stoppingPosition, const char * indexToUpdateResult, const char * result) { From 984d1ff924e85e111439d1220ed7da4ed192d6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Nov 2019 10:17:36 +0100 Subject: [PATCH 12/12] [poincare] Expression::CreateComplexExpression: fix crash when creating Polar form --- poincare/src/expression.cpp | 9 +++++++-- poincare/test/simplification.cpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 2b95ece7c..5e3652b2a 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -856,8 +856,13 @@ Expression Expression::CreateComplexExpression(Expression ra, Expression tb, Pre Expression norm; Expression exp; if (!isOneRa || isZeroTb) { - assert(!isNegativeRa); // norm cannot be negative - norm = ra; + /* Norm cannot be negative but can be preceded by a negative sign (for + * instance "-log(0.3)") which would lead to isNegativeRa = True. */ + if (isNegativeRa) { + norm = Opposite::Builder(ra); + } else { + norm = ra; + } } if (!isZeroRa && !isZeroTb) { Expression arg; diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 9379274d4..86a546098 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -941,6 +941,7 @@ QUIZ_CASE(poincare_simplification_complex_format) { assert_parsed_expression_simplify_to("[[1,√(-1)]]", "[[1,ℯ^\u0012π/2×𝐢\u0013]]", User, Radian, Polar); assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Polar); assert_parsed_expression_simplify_to("atan(-2)", "atan(2)×ℯ^\u0012π×𝐢\u0013", User, Radian, Polar); + assert_parsed_expression_simplify_to("cos(42π)", "-cos(42×π)×ℯ^\x12π×𝐢\x13", User, Degree, Polar); // User defined variable assert_parsed_expression_simplify_to("a", "a", User, Radian, Polar);