From c5439850942fe6920bc67b3e82d9f11871ab82db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 3 Feb 2020 16:13:52 +0100 Subject: [PATCH] [poincare] Unit conversion --- poincare/Makefile | 1 + poincare/include/poincare/expression.h | 4 ++ poincare/include/poincare/expression_node.h | 6 +- poincare/include/poincare/multiplication.h | 3 + poincare/include/poincare/power.h | 2 + poincare/include/poincare/unit.h | 2 + poincare/include/poincare/unit_convert.h | 55 +++++++++++++++ poincare/include/poincare_nodes.h | 1 + poincare/src/expression.cpp | 15 ++++- poincare/src/multiplication.cpp | 10 +++ poincare/src/parsing/parser.cpp | 38 +++++++---- poincare/src/parsing/parser.h | 2 +- poincare/src/power.cpp | 4 ++ poincare/src/tree_handle.cpp | 1 + poincare/src/unit.cpp | 3 + poincare/src/unit_convert.cpp | 74 +++++++++++++++++++++ 16 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 poincare/include/poincare/unit_convert.h create mode 100644 poincare/src/unit_convert.cpp diff --git a/poincare/Makefile b/poincare/Makefile index c7bcf21b4..b29805acf 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -144,6 +144,7 @@ poincare_src += $(addprefix poincare/src/,\ trigonometry_cheat_table.cpp \ undefined.cpp \ unit.cpp \ + unit_convert.cpp \ unreal.cpp \ variable_context.cpp \ ) diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 77dec6c9e..ebe497804 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -100,6 +100,7 @@ class Expression : public TreeHandle { friend class Trigonometry; friend class TrigonometryCheatTable; friend class Unit; + friend class UnitConvert; friend class AdditionNode; friend class DerivativeNode; @@ -196,6 +197,8 @@ public: static constexpr int k_maxNumberOfPolynomialCoefficients = k_maxPolynomialDegree+1; int getPolynomialReducedCoefficients(const char * symbolName, Expression coefficients[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const; Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { return node()->replaceSymbolWithExpression(symbol, expression); } + bool beautifiedExpressionHasUnits() const { return node()->beautifiedExpressionHasUnits(); } // This must be called on a beautified expression + bool isUnitsOnly(Context * context) const; /* Complex */ static bool EncounteredComplex(); @@ -395,6 +398,7 @@ private: /* Properties */ int defaultGetPolynomialCoefficients(Context * context, const char * symbol, Expression expression[]) const; + bool reducedExpressionIsUnitsOnly() const { return node()->reducedExpressionIsUnitsOnly(); } // This must be called on a reduced expression /* Builder */ static bool IsZero(const Expression e); diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index d343a8a2d..d8b18d2a9 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -40,6 +40,7 @@ public: Constant, Symbol, Store, + UnitConvert, Equal, Sine, Cosine, @@ -126,7 +127,8 @@ public: enum class SymbolicComputation { ReplaceAllSymbolsWithDefinitionsOrUndefined = 0, ReplaceAllDefinedSymbolsWithDefinition = 1, - ReplaceDefinedFunctionsWithDefinitions = 2 + ReplaceDefinedFunctionsWithDefinitions = 2, + ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits = 3 // Used in Expression::isUnitsOnly }; enum class Sign { Negative = -1, @@ -174,6 +176,8 @@ public: virtual int getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable) const; virtual float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const; bool isOfType(Type * types, int length) const; + virtual bool beautifiedExpressionHasUnits() const { return false; } // This must be called on a beautified expression + virtual bool reducedExpressionIsUnitsOnly() const { return false; } // This must be called on a reduced expression /* Simplification */ /* SimplificationOrder returns: diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 7fb300738..586a5e1c6 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -25,6 +25,8 @@ public: int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; + bool beautifiedExpressionHasUnits() const override; + bool reducedExpressionIsUnitsOnly() const override; // Approximation template static Complex compute(const std::complex c, const std::complex d, Preferences::ComplexFormat complexFormat) { return Complex::Builder(c*d); } @@ -65,6 +67,7 @@ class Multiplication : public NAryExpression { friend class AdditionNode; friend class Addition; friend class Power; + friend class UnitConvert; public: Multiplication(const MultiplicationNode * n) : NAryExpression(n) {} static Multiplication Builder() { return TreeHandle::NAryBuilder(); } diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index 55861e502..03462471f 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -32,6 +32,8 @@ public: int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; + bool beautifiedExpressionHasUnits() const override { return reducedExpressionIsUnitsOnly(); } + bool reducedExpressionIsUnitsOnly() const override; template static Complex compute(const std::complex c, const std::complex d, Preferences::ComplexFormat complexFormat); diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index ca91bc214..5b7b65e27 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -112,6 +112,8 @@ public: // Expression Properties Type type() const override { return Type::Unit; } Sign sign(Context * context) const override; + bool beautifiedExpressionHasUnits() const override { return true; } + bool reducedExpressionIsUnitsOnly() const override { return true; } /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h new file mode 100644 index 000000000..6e1027fb6 --- /dev/null +++ b/poincare/include/poincare/unit_convert.h @@ -0,0 +1,55 @@ +#ifndef POINCARE_UNIT_CONVERT_H +#define POINCARE_UNIT_CONVERT_H + +#include +#include +#include + +namespace Poincare { + +class UnitConvertNode /*final*/ : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(UnitConvertNode); } + int numberOfChildren() const override { return 2; } +#if POINCARE_TREE_LOG + virtual void logNodeName(std::ostream & stream) const override { + stream << "UnivtConvert"; + } +#endif + + // ExpressionNode + Type type() const override { return Type::UnitConvert; } + int polynomialDegree(Context * context, const char * symbolName) const override { return -1; } + +private: + // Simplification + void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override {} + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { assert(false); return LayoutShape::MoreLetters; }; + // Layout + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Evalutation + Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +}; + +class UnitConvert final : public Expression { +friend class UnitConvertNode; +public: + UnitConvert(const UnitConvertNode * n) : Expression(n) {} + static UnitConvert Builder(Expression value, Expression unit) { return TreeHandle::FixedArityBuilder(ArrayBuilder(value, unit).array(), 2); } + + // Expression + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + +private: + UnitConvertNode * node() const { return static_cast(Expression::node()); } +}; + +} + +#endif diff --git a/poincare/include/poincare_nodes.h b/poincare/include/poincare_nodes.h index c0b5a00f7..33076f227 100644 --- a/poincare/include/poincare_nodes.h +++ b/poincare/include/poincare_nodes.h @@ -84,6 +84,7 @@ #include #include #include +#include #include #include diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index cbfeb9e00..d9bc84a82 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -453,6 +453,14 @@ int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expres return degree; } +bool Expression::isUnitsOnly(Context * context) const { + if (type() == ExpressionNode::Type::Unit) { + return true; + } + Expression thisBeautified = clone().reduce(ExpressionNode::ReductionContext(context, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, ExpressionNode::ReductionTarget::SystemForApproximation, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits)); // The values do not really matter except for the symbolicComputation + return thisBeautified.reducedExpressionIsUnitsOnly(); +} + /* Complex */ bool Expression::EncounteredComplex() { @@ -684,8 +692,13 @@ void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expre if (approximateExpression) { static_cast(approximateExpression)->setDimensions(m.numberOfRows(), m.numberOfColumns()); } + } else if (e.type() == ExpressionNode::Type::UnitConvert) { + /* Case 2: the initial expression is a unit convert, so we already beautified the result. */ + *simplifiedExpression = e; + *approximateExpression = e; + return; } else { - /* Case 2: the reduced expression is scalar or too complex to respect the + /* Case 3: the reduced expression is scalar or too complex to respect the * complex format. */ return e.beautifyAndApproximateScalar(simplifiedExpression, approximateExpression, userReductionContext, context, complexFormat, angleUnit); } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 688984f67..afc80acba 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -62,6 +62,16 @@ bool MultiplicationNode::childAtIndexNeedsUserParentheses(const Expression & chi return child.isOfType(types, 2); } +bool MultiplicationNode::beautifiedExpressionHasUnits() const { + assert(numberOfChildren() > 0); + return Expression(this).childAtIndex(numberOfChildren() - 1).beautifiedExpressionHasUnits(); +} + +bool MultiplicationNode::reducedExpressionIsUnitsOnly() const { + assert(numberOfChildren() > 0); + return Expression(this).childAtIndex(0).reducedExpressionIsUnitsOnly(); +} + template MatrixComplex MultiplicationNode::computeOnMatrices(const MatrixComplex m, const MatrixComplex n, Preferences::ComplexFormat complexFormat) { if (m.numberOfColumns() != n.numberOfRows()) { diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index cb16516eb..a32e23d7e 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -60,7 +60,7 @@ Expression Parser::parseUntil(Token::Type stoppingType) { typedef void (Parser::*TokenParser)(Expression & leftHandSide, Token::Type stoppingType); static constexpr TokenParser tokenParsers[] = { &Parser::parseUnexpected, // Token::EndOfStream - &Parser::parseStore, // Token::Store + &Parser::parseStoreOrUnitConvert, // Token::Store &Parser::parseEqual, // Token::Equal &Parser::parseUnexpected, // Token::RightSystemParenthesis &Parser::parseUnexpected, // Token::RightBracket @@ -275,30 +275,42 @@ void Parser::parseEqual(Expression & leftHandSide, Token::Type stoppingType) { } } -void Parser::parseStore(Expression & leftHandSide, Token::Type stoppingType) { +void Parser::parseStoreOrUnitConvert(Expression & leftHandSide, Token::Type stoppingType) { if (leftHandSide.isUninitialized()) { m_status = Status::Error; // Left-hand side missing. return; } // At this point, m_currentToken is Token::Store. - popToken(); - if (!m_currentToken.is(Token::Identifier) || IsReservedName(m_currentToken.text(), m_currentToken.length())) { - m_status = Status::Error; // The right-hand side of Token::Store must be symbol or function that is not reserved. + bool parseId = m_nextToken.is(Token::Identifier) && !IsReservedName(m_nextToken.text(), m_nextToken.length()); + if (parseId) { + popToken(); + // Try parsing a store + Expression rightHandSide; + parseCustomIdentifier(rightHandSide, m_currentToken.text(), m_currentToken.length(), true); + if (m_status != Status::Progress) { + return; + } + if (!m_nextToken.is(Token::EndOfStream) + || !(rightHandSide.type() == ExpressionNode::Type::Symbol + || (rightHandSide.type() == ExpressionNode::Type::Function + && rightHandSide.childAtIndex(0).type() == ExpressionNode::Type::Symbol))) + { + m_status = Status::Error; // Store expects a single symbol or function. + return; + } + leftHandSide = Store::Builder(leftHandSide, static_cast(rightHandSide)); return; } - Expression rightHandSide; - parseCustomIdentifier(rightHandSide, m_currentToken.text(), m_currentToken.length(), true); + // Try parsing a unit convert + Expression rightHandSide = parseUntil(stoppingType); if (m_status != Status::Progress) { return; } - if (!m_nextToken.is(Token::EndOfStream) || - !( rightHandSide.type() == ExpressionNode::Type::Symbol || - (rightHandSide.type() == ExpressionNode::Type::Function && rightHandSide.childAtIndex(0).type() == ExpressionNode::Type::Symbol))) - { - m_status = Status::Error; // Store expects a single symbol or function. + if (!m_nextToken.is(Token::EndOfStream) || !rightHandSide.isUnitsOnly(m_context)) { + m_status = Status::Error; // Store expects a single symbol or function or a unit. return; } - leftHandSide = Store::Builder(leftHandSide, static_cast(rightHandSide)); + leftHandSide = UnitConvert::Builder(leftHandSide, rightHandSide); } bool Parser::parseBinaryOperator(const Expression & leftHandSide, Expression & rightHandSide, Token::Type stoppingType) { diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 9679bd341..7bf6c156f 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -64,7 +64,7 @@ private: void parseCaret(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); void parseCaretWithParenthesis(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); void parseEqual(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); - void parseStore(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); + void parseStoreOrUnitConvert(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); void parseLeftSuperscript(Expression & leftHandSide, Token::Type stoppingType = (Token::Type)0); // Parsing helpers diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 738d8a31e..5191ddc42 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -85,6 +85,10 @@ int PowerNode::getPolynomialCoefficients(Context * context, const char * symbolN return Power(this).getPolynomialCoefficients(context, symbolName, coefficients); } +bool PowerNode::reducedExpressionIsUnitsOnly() const { + return childAtIndex(0)->type() == ExpressionNode::Type::Unit; +} + bool PowerNode::isReal(Context * context) const { Expression base(childAtIndex(0)); Expression index(childAtIndex(1)); diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 1a59ddddc..c2dfc851a 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -362,6 +362,7 @@ template Sum TreeHandle::FixedArityBuilder(TreeHandle*, size_t); template SumLayout TreeHandle::FixedArityBuilder(TreeHandle*, size_t); template Tangent TreeHandle::FixedArityBuilder(TreeHandle*, size_t); template Undefined TreeHandle::FixedArityBuilder(TreeHandle*, size_t); +template UnitConvert TreeHandle::FixedArityBuilder(TreeHandle*, size_t); template Unreal TreeHandle::FixedArityBuilder(TreeHandle*, size_t); template MatrixLayout TreeHandle::NAryBuilder(TreeHandle*, size_t); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index f95e5784d..79aea12e1 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -221,6 +221,9 @@ Unit Unit::Builder(const Dimension * dimension, const Representative * represent } Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits) { + return *this; + } UnitNode * unitNode = static_cast(node()); const Dimension * dim = unitNode->dimension(); const Representative * rep = unitNode->representative(); diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp new file mode 100644 index 000000000..ad61b3dd5 --- /dev/null +++ b/poincare/src/unit_convert.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Poincare { + +Expression UnitConvertNode::shallowReduce(ReductionContext reductionContext) { + return UnitConvert(this).shallowReduce(reductionContext); +} + +int UnitConvertNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + //TODO LEA factorize with Store + constexpr int stringMaxSize = CodePoint::MaxCodePointCharLength + 1; + char string[stringMaxSize]; + SerializationHelper::CodePoint(string, stringMaxSize, UCodePointRightwardsArrow); + return SerializationHelper::Infix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, string); +} + +Layout UnitConvertNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + //TODO LEA factorize with Store + HorizontalLayout result = HorizontalLayout::Builder(); + result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); + result.addChildAtIndex(CodePointLayout::Builder(UCodePointRightwardsArrow), result.numberOfChildren(), result.numberOfChildren(), nullptr); + result.addOrMergeChildAtIndex(childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), result.numberOfChildren(), false); + return std::move(result); +} + +template +Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + /* If we are here, it means that the unit convert node was not shallowReduced. + * Otherwise, it would have been replaced by the division of the value by the + * unit. We thus return Undefined. */ + return Complex::Undefined(); +} + +Expression UnitConvert::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + // UnitConvert the expression. + Expression finalUnit = childAtIndex(1).clone(); + Expression division = Division::Builder(childAtIndex(0), childAtIndex(1)); + division = division.simplify(reductionContext); + if (division.beautifiedExpressionHasUnits()) { + return replaceWithUndefinedInPlace(); + } + double floatValue = division.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + if (std::isinf(floatValue)) { + return Infinity::Builder(false); //FIXME sign? + } + if (std::isnan(floatValue)) { + return Undefined::Builder(); + } + division = Multiplication::Builder(Float::Builder(floatValue), finalUnit); + static_cast(division).mergeMultiplicationChildrenInPlace(); + replaceWithInPlace(division); + return division; +} + +template Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + +}