[apps/poincare] Use symbolicComputation in recursivelyMatches

This fixes a failed assertion for the scenario:
[3]->x then, in the Equation app, solve x+1->0
This commit is contained in:
Léa Saviot
2020-06-15 15:39:04 +02:00
parent 1f0b3770e3
commit cf4eaa3d1f
14 changed files with 49 additions and 25 deletions

View File

@@ -176,7 +176,7 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
ExpressionNode::Type::PredictionInterval
};
return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type));
}, context, true)
}, context)
)
{
m_displayOutput = DisplayOutput::ApproximateOnly;

View File

@@ -15,7 +15,7 @@ using namespace Shared;
namespace Solver {
bool Equation::containsIComplex(Context * context) const {
return expressionClone().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, context, true);
return expressionClone().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, context);
}
Expression Equation::Model::standardForm(const Storage::Record * record, Context * context, bool replaceFunctionsButNotSymbols) const {
@@ -44,8 +44,7 @@ Expression Equation::Model::standardForm(const Storage::Record * record, Context
[](const Expression e, Context * context) {
return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context);
},
contextToUse,
true))
contextToUse))
{
*returnedExpression = Undefined::Builder();
} else if (expressionRed.type() == ExpressionNode::Type::Equal) {

View File

@@ -156,7 +156,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex
}
const Expression e = eq->standardForm(context, replaceFunctionsButNotSymbols); // The standard form is memoized so there is no double computation even if replaceFunctionsButNotSymbols is true.
if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined) {
if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined || e.recursivelyMatches(Expression::IsMatrix, context, replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition)) {
return Error::EquationUndefined;
}
if (e.type() == ExpressionNode::Type::Unreal) {

View File

@@ -151,7 +151,7 @@ public:
bool isDivisionOfIntegers() const;
bool hasDefinedComplexApproximation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const;
typedef bool (*ExpressionTest)(const Expression e, Context * context);
bool recursivelyMatches(ExpressionTest test, Context * context, bool replaceSymbols = true) const;
bool recursivelyMatches(ExpressionTest test, Context * context, ExpressionNode::SymbolicComputation replaceSymbols = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) const;
typedef bool (*ExpressionTypeTest)(const Expression e, const void * context);
bool hasExpression(ExpressionTypeTest test, const void * context) const;
// WARNING: this method must be called on reduced (sorted) expressions

View File

@@ -128,7 +128,8 @@ public:
ReplaceAllSymbolsWithDefinitionsOrUndefined = 0,
ReplaceAllDefinedSymbolsWithDefinition = 1,
ReplaceDefinedFunctionsWithDefinitions = 2,
ReplaceAllSymbolsWithUndefined = 3 // Used in UnitConvert::shallowReduce
ReplaceAllSymbolsWithUndefined = 3, // Used in UnitConvert::shallowReduce
DoNotReplaceAnySymbol = 4
};
enum class UnitConversion {
None = 0,

View File

@@ -355,7 +355,7 @@ bool Addition::TermsHaveIdenticalNonNumeralFactors(const Expression & e1, const
int numberOfNonNumeralFactors = numberOfNonNumeralFactorsInE1;
if (numberOfNonNumeralFactors == 1) {
Expression nonNumeralFactor = FirstNonNumeralFactor(e1);
if (nonNumeralFactor.recursivelyMatches(Expression::IsRandom, context, true)) {
if (nonNumeralFactor.recursivelyMatches(Expression::IsRandom, context)) {
return false;
}
return FirstNonNumeralFactor(e1).isIdenticalTo(FirstNonNumeralFactor(e2));
@@ -380,7 +380,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte
Expression childI = childAtIndex(i);
Expression currentDenominator = childI.denominator(reductionContext);
if (!currentDenominator.isUninitialized()) {
if (currentDenominator.recursivelyMatches(Expression::IsRandom, reductionContext.context(), true)) {
if (currentDenominator.recursivelyMatches(Expression::IsRandom, reductionContext.context())) {
// Remove "random" factors
removeChildInPlace(childI, childI.numberOfChildren());
a.addChildAtIndexInPlace(childI, a.numberOfChildren(), a.numberOfChildren());

View File

@@ -87,14 +87,29 @@ bool Expression::isRationalOne() const {
return type() == ExpressionNode::Type::Rational && convert<const Rational>().isOne();
}
bool Expression::recursivelyMatches(ExpressionTest test, Context * context, bool replaceSymbols) const {
bool Expression::recursivelyMatches(ExpressionTest test, Context * context, ExpressionNode::SymbolicComputation replaceSymbols) const {
if (test(*this, context)) {
return true;
}
// Handle symbols and functions
ExpressionNode::Type t = type();
if (replaceSymbols && (t == ExpressionNode::Type::Symbol || t == ExpressionNode::Type::Function)) {
return SymbolAbstract::matches(convert<const SymbolAbstract>(), test, context);
if (t == ExpressionNode::Type::Symbol || t == ExpressionNode::Type::Function) {
assert(replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition
|| replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions
|| replaceSymbols == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol); // We need only those cases for now
if (replaceSymbols == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol
|| (replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions
&& t == ExpressionNode::Type::Symbol))
{
return false;
}
assert(replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition
|| t == ExpressionNode::Type::Function);
return SymbolAbstract::matches(convert<const SymbolAbstract>(), test, context);
}
const int childrenCount = this->numberOfChildren();
for (int i = 0; i < childrenCount; i++) {
if (childAtIndex(i).recursivelyMatches(test, context, replaceSymbols)) {
@@ -197,7 +212,7 @@ bool containsVariables(const Expression e, char * variables, int maxVariableSize
}
bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const {
assert(!recursivelyMatches(IsMatrix, context, true));
assert(!recursivelyMatches(IsMatrix, context, symbolicComputation));
// variables is in fact of type char[k_maxNumberOfVariables][maxVariableSize]
int index = 0;
while (variables[index*maxVariableSize] != 0) {
@@ -223,7 +238,7 @@ bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Ex
/* degree is supposed to be 0 or 1. Otherwise, it means that equation
* is 'undefined' due to the reduction of 0*inf for example.
* (ie, x*y*inf = 0) */
assert(!recursivelyMatches([](const Expression e, Context * context) { return e.isUndefined(); }, context, true));
assert(!recursivelyMatches([](const Expression e, Context * context) { return e.isUndefined(); }, context));
return false;
}
/* The equation is can be written: a_1*x+a_0 with a_1 and a_0 x-independent.
@@ -473,7 +488,7 @@ int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expres
/* Units */
bool Expression::hasUnit() const {
return recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Unit; }, nullptr, false);
return recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Unit; }, nullptr, ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol);
}
/* Complex */
@@ -494,7 +509,7 @@ Preferences::ComplexFormat Expression::UpdatedComplexFormatWithTextInput(Prefere
}
Preferences::ComplexFormat Expression::UpdatedComplexFormatWithExpressionInput(Preferences::ComplexFormat complexFormat, const Expression & exp, Context * context) {
if (complexFormat == Preferences::ComplexFormat::Real && exp.recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, context, true)) {
if (complexFormat == Preferences::ComplexFormat::Real && exp.recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, context)) {
return Preferences::ComplexFormat::Cartesian;
}
return complexFormat;

View File

@@ -120,6 +120,9 @@ Expression Function::shallowReduce(ExpressionNode::ReductionContext reductionCon
{
return replaceWithUndefinedInPlace();
}
if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol) {
return *this;
}
Expression result = SymbolAbstract::Expand(*this, reductionContext.context(), true, reductionContext.symbolicComputation());
if (result.isUninitialized()) {
if (reductionContext.symbolicComputation() != ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined) {

View File

@@ -598,7 +598,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext
while (i < numberOfChildren()-1) {
Expression oi = childAtIndex(i);
Expression oi1 = childAtIndex(i+1);
if (oi.recursivelyMatches(Expression::IsRandom, context, true)) {
if (oi.recursivelyMatches(Expression::IsRandom, context)) {
// Do not factorize random or randint
} else if (TermsHaveIdenticalBase(oi, oi1)) {
bool shouldFactorizeBase = true;
@@ -715,7 +715,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext
* reduce expressions such as (x+y)^(-1)*(x+y)(a+b).
* If there is a random somewhere, do not expand. */
Expression p = parent();
bool hasRandom = recursivelyMatches(Expression::IsRandom, context, true);
bool hasRandom = recursivelyMatches(Expression::IsRandom, context);
if (shouldExpand
&& (p.isUninitialized() || p.type() != ExpressionNode::Type::Multiplication)
&& !hasRandom)

View File

@@ -151,7 +151,9 @@ bool Symbol::isRegressionSymbol(const char * c, Poincare::Context * context) {
}
Expression Symbol::shallowReduce(ExpressionNode::ReductionContext reductionContext) {
if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions) {
if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions
|| reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol)
{
return *this;
}
if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined) {

View File

@@ -62,12 +62,14 @@ size_t SymbolAbstract::TruncateExtension(char * dst, const char * src, size_t le
bool SymbolAbstract::matches(const SymbolAbstract & symbol, ExpressionTest test, Context * context) {
Expression e = SymbolAbstract::Expand(symbol, context, false);
return !e.isUninitialized() && e.recursivelyMatches(test, context, false);
return !e.isUninitialized() && e.recursivelyMatches(test, context, ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol);
}
Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * context, bool clone, ExpressionNode::SymbolicComputation symbolicComputation) {
bool shouldNotReplaceSymbols = symbolicComputation == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions;
if (symbol.type() == ExpressionNode::Type::Symbol && shouldNotReplaceSymbols) {
if (symbolicComputation == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol
|| (symbol.type() == ExpressionNode::Type::Symbol && shouldNotReplaceSymbols))
{
return clone ? symbol.clone() : *const_cast<SymbolAbstract *>(&symbol);
}
Expression e = context->expressionForSymbolAbstract(symbol, clone);

View File

@@ -205,7 +205,7 @@ QUIZ_CASE(poincare_context_user_variable_properties) {
quiz_assert(Symbol::Builder('a').recursivelyMatches(Expression::IsMatrix, &context));
assert_expression_approximates_to<double>("1.2→b", "1.2");
quiz_assert(Symbol::Builder('b').recursivelyMatches(Expression::IsApproximate, &context, true));
quiz_assert(Symbol::Builder('b').recursivelyMatches(Expression::IsApproximate, &context));
/* [[x]]→f(x) expression contains a matrix, so its simplification is going
* to be interrupted. We thus rather approximate it instead of simplifying it.
@@ -214,7 +214,7 @@ QUIZ_CASE(poincare_context_user_variable_properties) {
assert_expression_approximates_to<double>("[[x]]→f(x)", "[[undef]]");
quiz_assert(Function::Builder("f", 1, Symbol::Builder('x')).recursivelyMatches(Poincare::Expression::IsMatrix, &context));
assert_expression_approximates_to<double>("0.2*x→g(x)", "undef");
quiz_assert(Function::Builder("g", 1, Rational::Builder(2)).recursivelyMatches(Expression::IsApproximate, &context, true));
quiz_assert(Function::Builder("g", 1, Rational::Builder(2)).recursivelyMatches(Expression::IsApproximate, &context));
// Clean the storage for other tests
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();

View File

@@ -35,12 +35,12 @@ QUIZ_CASE(poincare_properties_is_parametered_expression) {
void assert_expression_has_property(const char * expression, Context * context, Expression::ExpressionTest test) {
Expression e = parse_expression(expression, context, false);
quiz_assert_print_if_failure(e.recursivelyMatches(test, context, true), expression);
quiz_assert_print_if_failure(e.recursivelyMatches(test, context), expression);
}
void assert_expression_has_not_property(const char * expression, Context * context, Expression::ExpressionTest test) {
Expression e = parse_expression(expression, context, false);
quiz_assert_print_if_failure(!e.recursivelyMatches(test, context, true), expression);
quiz_assert_print_if_failure(!e.recursivelyMatches(test, context), expression);
}
QUIZ_CASE(poincare_properties_is_approximate) {
@@ -360,6 +360,7 @@ QUIZ_CASE(poincare_properties_get_polynomial_coefficients) {
const char * coefficient7[] = {"4", 0};
assert_reduced_expression_has_polynomial_coefficient("x+1", "x", coefficient7 );
const char * coefficient8[] = {"2", "1", 0};
assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, DoNotReplaceAnySymbol);
assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, ReplaceDefinedFunctionsWithDefinitions);
assert_reduced_expression_has_polynomial_coefficient("f(x)", "x", coefficient4, Cartesian, Radian, ReplaceDefinedFunctionsWithDefinitions);

View File

@@ -12,6 +12,7 @@ constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllDefinedSymbols
constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllSymbolsWithDefinitionsOrUndefined = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined;
constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceDefinedFunctionsWithDefinitions = Poincare::ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions;
constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllSymbolsWithUndefined = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined;
constexpr Poincare::ExpressionNode::SymbolicComputation DoNotReplaceAnySymbol = Poincare::ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol;
constexpr Poincare::ExpressionNode::UnitConversion NoUnitConversion = Poincare::ExpressionNode::UnitConversion::None;
constexpr Poincare::ExpressionNode::UnitConversion DefaultUnitConversion = Poincare::ExpressionNode::UnitConversion::Default;
constexpr Poincare::ExpressionNode::UnitConversion InternationalSystemUnitConversion = Poincare::ExpressionNode::UnitConversion::InternationalSystem;