[poincare] Unit conversion

This commit is contained in:
Léa Saviot
2020-02-03 16:13:52 +01:00
parent 1393e5e973
commit c543985094
16 changed files with 205 additions and 16 deletions

View File

@@ -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 \
)

View File

@@ -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);

View File

@@ -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:

View File

@@ -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<typename T> static Complex<T> compute(const std::complex<T> c, const std::complex<T> d, Preferences::ComplexFormat complexFormat) { return Complex<T>::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<Multiplication, MultiplicationNode>(); }

View File

@@ -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<typename T> static Complex<T> compute(const std::complex<T> c, const std::complex<T> d, Preferences::ComplexFormat complexFormat);

View File

@@ -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;

View File

@@ -0,0 +1,55 @@
#ifndef POINCARE_UNIT_CONVERT_H
#define POINCARE_UNIT_CONVERT_H
#include <poincare/expression.h>
#include <poincare/unit.h>
#include <poincare/evaluation.h>
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<float> approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate<float>(context, complexFormat, angleUnit); }
Evaluation<double> approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate<double>(context, complexFormat, angleUnit); }
template<typename T> Evaluation<T> 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<UnitConvert, UnitConvertNode>(ArrayBuilder<TreeHandle>(value, unit).array(), 2); }
// Expression
Expression shallowReduce(ExpressionNode::ReductionContext reductionContext);
private:
UnitConvertNode * node() const { return static_cast<UnitConvertNode *>(Expression::node()); }
};
}
#endif

View File

@@ -84,6 +84,7 @@
#include <poincare/tangent.h>
#include <poincare/undefined.h>
#include <poincare/unit.h>
#include <poincare/unit_convert.h>
#include <poincare/unreal.h>
#include <poincare/variable_context.h>

View File

@@ -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<Matrix *>(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);
}

View File

@@ -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<typename T>
MatrixComplex<T> MultiplicationNode::computeOnMatrices(const MatrixComplex<T> m, const MatrixComplex<T> n, Preferences::ComplexFormat complexFormat) {
if (m.numberOfColumns() != n.numberOfRows()) {

View File

@@ -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<SymbolAbstract&>(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<SymbolAbstract&>(rightHandSide));
leftHandSide = UnitConvert::Builder(leftHandSide, rightHandSide);
}
bool Parser::parseBinaryOperator(const Expression & leftHandSide, Expression & rightHandSide, Token::Type stoppingType) {

View File

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

View File

@@ -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));

View File

@@ -362,6 +362,7 @@ template Sum TreeHandle::FixedArityBuilder<Sum, SumNode>(TreeHandle*, size_t);
template SumLayout TreeHandle::FixedArityBuilder<SumLayout, SumLayoutNode>(TreeHandle*, size_t);
template Tangent TreeHandle::FixedArityBuilder<Tangent, TangentNode>(TreeHandle*, size_t);
template Undefined TreeHandle::FixedArityBuilder<Undefined, UndefinedNode>(TreeHandle*, size_t);
template UnitConvert TreeHandle::FixedArityBuilder<UnitConvert, UnitConvertNode>(TreeHandle*, size_t);
template Unreal TreeHandle::FixedArityBuilder<Unreal, UnrealNode>(TreeHandle*, size_t);
template MatrixLayout TreeHandle::NAryBuilder<MatrixLayout, MatrixLayoutNode>(TreeHandle*, size_t);

View File

@@ -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<UnitNode *>(node());
const Dimension * dim = unitNode->dimension();
const Representative * rep = unitNode->representative();

View File

@@ -0,0 +1,74 @@
#include <poincare/unit_convert.h>
#include <poincare/code_point_layout.h>
#include <poincare/complex.h>
#include <poincare/context.h>
#include <poincare/division.h>
#include <poincare/float.h>
#include <poincare/horizontal_layout.h>
#include <poincare/infinity.h>
#include <poincare/multiplication.h>
#include <poincare/serialization_helper.h>
#include <poincare/symbol.h>
#include <poincare/undefined.h>
#include <ion.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <utility>
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<typename T>
Evaluation<T> 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<T>::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<double>(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<double>::Builder(floatValue), finalUnit);
static_cast<Multiplication &>(division).mergeMultiplicationChildrenInPlace();
replaceWithInPlace(division);
return division;
}
template Evaluation<float> UnitConvertNode::templatedApproximate<float>(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const;
template Evaluation<double> UnitConvertNode::templatedApproximate<double>(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const;
}