From f00c135b6913cc720c00e436971ad4e78c72bada Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 29 Jun 2020 15:28:19 +0200 Subject: [PATCH] [apps/calculation] Add additional outputs for matrix Change-Id: Ia4b285eb0f28eaed838d32a1fdfb785d13664f65 --- apps/calculation/Makefile | 1 + .../expressions_list_controller.cpp | 28 +++-- .../expressions_list_controller.h | 8 +- .../integer_list_controller.cpp | 40 +++---- .../integer_list_controller.h | 6 +- .../matrix_list_controller.cpp | 103 ++++++++++++++++++ .../matrix_list_controller.h | 27 +++++ .../rational_list_controller.cpp | 23 ++-- .../rational_list_controller.h | 5 +- .../unit_list_controller.cpp | 78 ++++++------- .../additional_outputs/unit_list_controller.h | 5 - apps/calculation/calculation.cpp | 3 + apps/calculation/calculation.h | 1 + apps/calculation/history_controller.cpp | 5 +- apps/calculation/history_controller.h | 2 + poincare/include/poincare/based_integer.h | 3 +- poincare/include/poincare/matrix.h | 1 + poincare/src/based_integer.cpp | 9 ++ poincare/src/matrix.cpp | 11 ++ poincare/src/matrix_trace.cpp | 6 +- 20 files changed, 253 insertions(+), 112 deletions(-) create mode 100644 apps/calculation/additional_outputs/matrix_list_controller.cpp create mode 100644 apps/calculation/additional_outputs/matrix_list_controller.h diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 453e46cf0..43f8f92c3 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -17,6 +17,7 @@ app_calculation_src = $(addprefix apps/calculation/,\ additional_outputs/integer_list_controller.cpp \ additional_outputs/scrollable_three_expressions_cell.cpp \ additional_outputs/list_controller.cpp \ + additional_outputs/matrix_list_controller.cpp \ additional_outputs/rational_list_controller.cpp \ additional_outputs/trigonometry_graph_cell.cpp \ additional_outputs/trigonometry_list_controller.cpp \ diff --git a/apps/calculation/additional_outputs/expressions_list_controller.cpp b/apps/calculation/additional_outputs/expressions_list_controller.cpp index 2ea1b3ef3..de98173d2 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.cpp +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -11,7 +11,7 @@ ExpressionsListController::ExpressionsListController(EditExpressionController * ListController(editExpressionController), m_cells{} { - for (int i = 0; i < k_maxNumberOfCells; i++) { + for (int i = 0; i < k_maxNumberOfRows; i++) { m_cells[i].setParentResponder(m_listController.selectableTableView()); } } @@ -21,15 +21,17 @@ void ExpressionsListController::didEnterResponderChain(Responder * previousFirst } int ExpressionsListController::reusableCellCount(int type) { - return k_maxNumberOfCells; + return k_maxNumberOfRows; } void ExpressionsListController::viewDidDisappear() { ListController::viewDidDisappear(); - // Reset cell memoization to avoid taking extra space in the pool - for (int i = 0; i < k_maxNumberOfCells; i++) { + // Reset layout and cell memoization to avoid taking extra space in the pool + for (int i = 0; i < k_maxNumberOfRows; i++) { m_cells[i].setLayout(Layout()); + m_layouts[i] = Layout(); } + m_expression = Expression(); } HighlightCell * ExpressionsListController::reusableCell(int index, int type) { @@ -43,24 +45,34 @@ KDCoordinate ExpressionsListController::rowHeight(int j) { } void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + /* Note : To further optimize memoization space in the pool, layout + * serialization could be memoized instead, and layout would be recomputed + * here, when setting cell's layout. */ ExpressionTableCellWithPointer * myCell = static_cast(cell); myCell->setLayout(layoutAtIndex(index)); myCell->setAccessoryMessage(messageAtIndex(index)); myCell->reloadScroll(); } +int ExpressionsListController::numberOfRows() const { + int nbOfRows = 0; + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + if (!m_layouts[i].isUninitialized()) { + nbOfRows++; + } + } + return nbOfRows; +} + void ExpressionsListController::setExpression(Poincare::Expression e) { // Reinitialize memoization - for (int i = 0; i < k_maxNumberOfCells; i++) { + for (int i = 0; i < k_maxNumberOfRows; i++) { m_layouts[i] = Layout(); } m_expression = e; } Poincare::Layout ExpressionsListController::layoutAtIndex(int index) { - if (m_layouts[index].isUninitialized()) { - computeLayoutAtIndex(index); - } assert(!m_layouts[index].isUninitialized()); return m_layouts[index]; } diff --git a/apps/calculation/additional_outputs/expressions_list_controller.h b/apps/calculation/additional_outputs/expressions_list_controller.h index 4d6ade149..f03947a26 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.h +++ b/apps/calculation/additional_outputs/expressions_list_controller.h @@ -22,22 +22,22 @@ public: KDCoordinate rowHeight(int j) override; int typeAtLocation(int i, int j) override { return 0; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; + int numberOfRows() const override; // IllustratedListController void setExpression(Poincare::Expression e) override; protected: - constexpr static int k_maxNumberOfCells = 4; + constexpr static int k_maxNumberOfRows = 5; int textAtIndex(char * buffer, size_t bufferSize, int index) override; Poincare::Expression m_expression; // Memoization of layouts - mutable Poincare::Layout m_layouts[k_maxNumberOfCells]; + mutable Poincare::Layout m_layouts[k_maxNumberOfRows]; private: Poincare::Layout layoutAtIndex(int index); - virtual void computeLayoutAtIndex(int index) = 0; virtual I18n::Message messageAtIndex(int index) = 0; // Cells - ExpressionTableCellWithPointer m_cells[k_maxNumberOfCells]; + ExpressionTableCellWithPointer m_cells[k_maxNumberOfRows]; }; } diff --git a/apps/calculation/additional_outputs/integer_list_controller.cpp b/apps/calculation/additional_outputs/integer_list_controller.cpp index 98deb516d..634c24f3a 100644 --- a/apps/calculation/additional_outputs/integer_list_controller.cpp +++ b/apps/calculation/additional_outputs/integer_list_controller.cpp @@ -11,10 +11,6 @@ using namespace Shared; namespace Calculation { -int IntegerListController::numberOfRows() const { - return 3 + factorExpressionIsComputable(); -} - Integer::Base baseAtIndex(int index) { switch (index) { case 0: @@ -27,12 +23,20 @@ Integer::Base baseAtIndex(int index) { } } -void IntegerListController::computeLayoutAtIndex(int index) { - assert(m_expression.type() == ExpressionNode::Type::BasedInteger); - // For index = k_indexOfFactorExpression, the layout is assumed to be alreday memoized because it is needed to compute the numberOfRows - assert(index < k_indexOfFactorExpression); - Integer i = static_cast(m_expression).integer(); - m_layouts[index] = i.createLayout(baseAtIndex(index)); +void IntegerListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + static_assert(k_maxNumberOfRows >= k_indexOfFactorExpression + 1, "k_maxNumberOfRows must be greater than k_indexOfFactorExpression"); + assert(!m_expression.isUninitialized() && m_expression.type() == ExpressionNode::Type::BasedInteger); + Integer integer = static_cast(m_expression).integer(); + for (int index = 0; index < k_indexOfFactorExpression; ++index) { + m_layouts[index] = integer.createLayout(baseAtIndex(index)); + } + // Computing factorExpression + Expression factor = Factor::Builder(m_expression.clone()); + PoincareHelpers::Simplify(&factor, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + if (!factor.isUndefined()) { + m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor); + } } I18n::Message IntegerListController::messageAtIndex(int index) { @@ -48,20 +52,4 @@ I18n::Message IntegerListController::messageAtIndex(int index) { } } -bool IntegerListController::factorExpressionIsComputable() const { - if (!m_layouts[k_indexOfFactorExpression].isUninitialized()) { - // The factor expression is already memoized - return !m_layouts[k_indexOfFactorExpression].isEmpty(); - } - Poincare::Context * context = App::app()->localContext(); - Expression factor = Factor::Builder(m_expression.clone()); - PoincareHelpers::Simplify(&factor, context, ExpressionNode::ReductionTarget::User); - if (!factor.isUndefined()) { - m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor); - return true; - } - m_layouts[k_indexOfFactorExpression] = EmptyLayout::Builder(); - return false; -} - } diff --git a/apps/calculation/additional_outputs/integer_list_controller.h b/apps/calculation/additional_outputs/integer_list_controller.h index b76f46d4d..e052632f9 100644 --- a/apps/calculation/additional_outputs/integer_list_controller.h +++ b/apps/calculation/additional_outputs/integer_list_controller.h @@ -10,13 +10,11 @@ public: IntegerListController(EditExpressionController * editExpressionController) : ExpressionsListController(editExpressionController) {} - //ListViewDataSource - int numberOfRows() const override; + void setExpression(Poincare::Expression e) override; + private: static constexpr int k_indexOfFactorExpression = 3; - void computeLayoutAtIndex(int index) override; I18n::Message messageAtIndex(int index) override; - bool factorExpressionIsComputable() const; }; } diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp new file mode 100644 index 000000000..4eaf9e465 --- /dev/null +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -0,0 +1,103 @@ +#include "matrix_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +void MatrixListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + assert(!m_expression.isUninitialized()); + static_assert(k_maxNumberOfRows >= k_maxNumberOfOutputRows, "k_maxNumberOfRows must be greater than k_maxNumberOfOutputRows"); + + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Poincare::Preferences::ComplexFormat currentComplexFormat = preferences->complexFormat(); + if (currentComplexFormat == Poincare::Preferences::ComplexFormat::Real) { + /* Temporary change complex format to avoid all additional expressions to be + * "unreal" (with [i] for instance). As additional results are computed from + * the output, which is built taking ComplexFormat into account, there are + * no risks of displaying additional results on an unreal output. */ + preferences->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); + } + + Context * context = App::app()->localContext(); + ExpressionNode::ReductionContext reductionContext( + context, + preferences->complexFormat(), + preferences->angleUnit(), + ExpressionNode::ReductionTarget::SystemForApproximation, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + + // The expression must be reduced to call methods such as determinant or trace + assert(m_expression.type() == ExpressionNode::Type::Matrix); + + bool mIsSquared = (static_cast(m_expression).numberOfRows() == static_cast(m_expression).numberOfColumns()); + size_t index = 0; + size_t messageIndex = 0; + // 1. Matrix determinant if square matrix + if (mIsSquared) { + /* Determinant is reduced so that a null determinant can be detected. + * However, some exceptions remain such as cos(x)^2+sin(x)^2-1 which will + * not be reduced to a rational, but will be null in theory. */ + Expression determinant = Determinant::Builder(m_expression.clone()).reduce(reductionContext); + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(determinant, context, preferences); + // 2. Matrix inverse if invertible matrix + // A squared matrix is invertible if and only if determinant is non null + if (!determinant.isUndefined() && !determinant.isRationalZero()) { + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); + } + } + // 3. Matrix row echelon form + messageIndex = 2; + Expression rowEchelonForm = MatrixRef::Builder(m_expression.clone()); + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(rowEchelonForm, context, preferences); + /* 4. Matrix reduced row echelon form + * it can be computed from row echelon form to save computation time.*/ + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixRref::Builder(rowEchelonForm), context, preferences); + // 5. Matrix trace if square matrix + if (mIsSquared) { + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixTrace::Builder(m_expression.clone()), context, preferences); + } + // Reset complex format as before + preferences->setComplexFormat(currentComplexFormat); +} + +Poincare::Layout MatrixListController::getLayoutFromExpression(Expression e, Context * context, Poincare::Preferences * preferences) { + assert(!e.isUninitialized()); + // Simplify or approximate expression + Expression approximateExpression; + Expression simplifiedExpression; + e.simplifyAndApproximate(&simplifiedExpression, &approximateExpression, context, + preferences->complexFormat(), preferences->angleUnit(), + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + // simplify might have been interrupted, in which case we use approximate + if (simplifiedExpression.isUninitialized()) { + assert(!approximateExpression.isUninitialized()); + return Shared::PoincareHelpers::CreateLayout(approximateExpression); + } + return Shared::PoincareHelpers::CreateLayout(simplifiedExpression); +} + +I18n::Message MatrixListController::messageAtIndex(int index) { + // Message index is mapped in setExpression because it depends on the Matrix. + assert(index < k_maxNumberOfOutputRows && index >=0); + I18n::Message messages[k_maxNumberOfOutputRows] = { + I18n::Message::Determinant, + I18n::Message::Inverse, + I18n::Message::RowEchelonForm, + I18n::Message::ReducedRowEchelonForm, + I18n::Message::Trace}; + return messages[m_indexMessageMap[index]]; +} + +} \ No newline at end of file diff --git a/apps/calculation/additional_outputs/matrix_list_controller.h b/apps/calculation/additional_outputs/matrix_list_controller.h new file mode 100644 index 000000000..b0774e42c --- /dev/null +++ b/apps/calculation/additional_outputs/matrix_list_controller.h @@ -0,0 +1,27 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_MATRIX_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_MATRIX_LIST_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class MatrixListController : public ExpressionsListController { +public: + MatrixListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController) {} + + void setExpression(Poincare::Expression e) override; + +private: + I18n::Message messageAtIndex(int index) override; + Poincare::Layout getLayoutFromExpression(Poincare::Expression e, Poincare::Context * context, Poincare::Preferences * preferences); + // Map from cell index to message index + constexpr static int k_maxNumberOfOutputRows = 5; + int m_indexMessageMap[k_maxNumberOfOutputRows]; +}; + +} + +#endif + + diff --git a/apps/calculation/additional_outputs/rational_list_controller.cpp b/apps/calculation/additional_outputs/rational_list_controller.cpp index 3fc762954..c55837927 100644 --- a/apps/calculation/additional_outputs/rational_list_controller.cpp +++ b/apps/calculation/additional_outputs/rational_list_controller.cpp @@ -9,34 +9,31 @@ using namespace Shared; namespace Calculation { -int RationalListController::numberOfRows() const { - return 2; -} - Integer extractInteger(const Expression e) { assert(e.type() == ExpressionNode::Type::BasedInteger); return static_cast(e).integer(); } -void RationalListController::computeLayoutAtIndex(int index) { +void RationalListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + assert(!m_expression.isUninitialized()); + static_assert(k_maxNumberOfRows >= 2, "k_maxNumberOfRows must be greater than 2"); + bool negative = false; Expression div = m_expression; if (m_expression.type() == ExpressionNode::Type::Opposite) { negative = true; div = m_expression.childAtIndex(0); } + assert(div.type() == ExpressionNode::Type::Division); Integer numerator = extractInteger(div.childAtIndex(0)); numerator.setNegative(negative); Integer denominator = extractInteger(div.childAtIndex(1)); - Expression e; - if (index == 0) { - e = Integer::CreateMixedFraction(numerator, denominator); - } else { - assert(index == 1); - e = Integer::CreateEuclideanDivision(numerator, denominator); - } - m_layouts[index] = PoincareHelpers::CreateLayout(e); + + int index = 0; + m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateMixedFraction(numerator, denominator)); + m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateEuclideanDivision(numerator, denominator)); } I18n::Message RationalListController::messageAtIndex(int index) { diff --git a/apps/calculation/additional_outputs/rational_list_controller.h b/apps/calculation/additional_outputs/rational_list_controller.h index 62d0de5a9..5ceae4e9b 100644 --- a/apps/calculation/additional_outputs/rational_list_controller.h +++ b/apps/calculation/additional_outputs/rational_list_controller.h @@ -10,10 +10,9 @@ public: RationalListController(EditExpressionController * editExpressionController) : ExpressionsListController(editExpressionController) {} - //ListViewDataSource - int numberOfRows() const override; + void setExpression(Poincare::Expression e) override; + private: - void computeLayoutAtIndex(int index) override; I18n::Message messageAtIndex(int index) override; int textAtIndex(char * buffer, size_t bufferSize, int index) override; }; diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 45eb58c4b..eed7e7bb5 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -15,12 +15,15 @@ namespace Calculation { void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); - // Reinitialize m_memoizedExpressions - for (size_t i = 0; i < k_maxNumberOfCells; i++) { - m_memoizedExpressions[i] = Expression(); + static_assert(k_maxNumberOfRows >= 3, "k_maxNumberOfRows must be greater than 3"); + + Poincare::Expression expressions[k_maxNumberOfRows]; + // Initialize expressions + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + expressions[i] = Expression(); } - size_t numberOfMemoizedExpressions = 0; + size_t numberOfExpressions = 0; // 1. First rows: miscellaneous classic units for some dimensions Expression copy = m_expression.clone(); Expression units; @@ -32,7 +35,7 @@ void UnitListController::setExpression(Poincare::Expression e) { if (Unit::IsSISpeed(units)) { // 1.a. Turn speed into km/h - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Multiplication::Builder( Unit::Kilometer(), @@ -45,7 +48,7 @@ void UnitListController::setExpression(Poincare::Expression e) { requireSimplification = true; // Simplify the conversion } else if (Unit::IsSIVolume(units)) { // 1.b. Turn volume into L - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Unit::Liter() ); @@ -53,14 +56,14 @@ void UnitListController::setExpression(Poincare::Expression e) { canChangeUnitPrefix = true; // Pick best prefix (mL) } else if (Unit::IsSIEnergy(units)) { // 1.c. Turn energy into Wh - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Multiplication::Builder( Unit::Watt(), Unit::Hour() ) ); - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Unit::ElectronVolt() ); @@ -69,21 +72,21 @@ void UnitListController::setExpression(Poincare::Expression e) { } else if (Unit::IsSITime(units)) { // Turn time into ? year + ? month + ? day + ? h + ? min + ? s double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); - m_memoizedExpressions[numberOfMemoizedExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit()); + expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit()); } // 1.d. Simplify and tune prefix of all computed expressions size_t currentExpressionIndex = 0; - while (currentExpressionIndex < numberOfMemoizedExpressions) { - assert(!m_memoizedExpressions[currentExpressionIndex].isUninitialized()); + while (currentExpressionIndex < numberOfExpressions) { + assert(!expressions[currentExpressionIndex].isUninitialized()); if (requireSimplification) { - Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); + Shared::PoincareHelpers::Simplify(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); } if (canChangeUnitPrefix) { Expression newUnits; // Reduce to be able to removeUnit - PoincareHelpers::Reduce(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); - m_memoizedExpressions[currentExpressionIndex] = m_memoizedExpressions[currentExpressionIndex].removeUnit(&newUnits); - double value = Shared::PoincareHelpers::ApproximateToScalar(m_memoizedExpressions[currentExpressionIndex], App::app()->localContext()); + PoincareHelpers::Reduce(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); + expressions[currentExpressionIndex] = expressions[currentExpressionIndex].removeUnit(&newUnits); + double value = Shared::PoincareHelpers::ApproximateToScalar(expressions[currentExpressionIndex], App::app()->localContext()); ExpressionNode::ReductionContext reductionContext( App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), @@ -91,36 +94,36 @@ void UnitListController::setExpression(Poincare::Expression e) { ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext); - m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); + expressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); } currentExpressionIndex++; } // 2. IS units only - assert(numberOfMemoizedExpressions < k_maxNumberOfCells - 1); - m_memoizedExpressions[numberOfMemoizedExpressions] = m_expression.clone(); - Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[numberOfMemoizedExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem); - numberOfMemoizedExpressions++; + assert(numberOfExpressions < k_maxNumberOfRows - 1); + expressions[numberOfExpressions] = m_expression.clone(); + Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem); + numberOfExpressions++; // 3. Get rid of duplicates Expression reduceExpression = m_expression.clone(); - // Make m_expression compareable to m_memoizedExpressions (turn BasedInteger into Rational for instance) + // Make m_expression comparable to expressions (turn BasedInteger into Rational for instance) Shared::PoincareHelpers::Simplify(&reduceExpression, App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::None); currentExpressionIndex = 1; - while (currentExpressionIndex < numberOfMemoizedExpressions) { + while (currentExpressionIndex < numberOfExpressions) { bool duplicateFound = false; for (size_t i = 0; i < currentExpressionIndex + 1; i++) { - // Compare the currentExpression to all previous memoized expressions and to m_expression - Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : m_memoizedExpressions[i]; + // Compare the currentExpression to all previous expressions and to m_expression + Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : expressions[i]; assert(!comparedExpression.isUninitialized()); - if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) { - numberOfMemoizedExpressions--; + if (comparedExpression.isIdenticalTo(expressions[currentExpressionIndex])) { + numberOfExpressions--; // Shift next expressions - for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) { - m_memoizedExpressions[j] = m_memoizedExpressions[j+1]; + for (size_t j = currentExpressionIndex; j < numberOfExpressions; j++) { + expressions[j] = expressions[j+1]; } // Remove last expression - m_memoizedExpressions[numberOfMemoizedExpressions] = Expression(); + expressions[numberOfExpressions] = Expression(); // The current expression has been discarded, no need to increment the current index duplicateFound = true; break; @@ -131,21 +134,12 @@ void UnitListController::setExpression(Poincare::Expression e) { currentExpressionIndex++; } } -} - -int UnitListController::numberOfRows() const { - int nbOfRows = 0; - for (size_t i = 0; i < k_maxNumberOfCells; i++) { - if (!m_memoizedExpressions[i].isUninitialized()) { - nbOfRows++; + // Memoize layouts + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + if (!expressions[i].isUninitialized()) { + m_layouts[i] = Shared::PoincareHelpers::CreateLayout(expressions[i]); } } - return nbOfRows; -} - -void UnitListController::computeLayoutAtIndex(int index) { - assert(!m_memoizedExpressions[index].isUninitialized()); - m_layouts[index] = Shared::PoincareHelpers::CreateLayout(m_memoizedExpressions[index]); } I18n::Message UnitListController::messageAtIndex(int index) { diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h index e3fdee036..58f6d1e0d 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.h +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -12,13 +12,8 @@ public: void setExpression(Poincare::Expression e) override; - //ListViewDataSource - int numberOfRows() const override; private: - void computeLayoutAtIndex(int index) override; I18n::Message messageAtIndex(int index) override; - // Memoization of expressions - mutable Poincare::Expression m_memoizedExpressions[k_maxNumberOfCells]; }; } diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 975ed02f3..c51091768 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -291,6 +291,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) { return AdditionalInformationType::Complex; } + if (o.type() == ExpressionNode::Type::Matrix) { + return AdditionalInformationType::Matrix; + } return AdditionalInformationType::None; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 10f4e0662..be7f87c9b 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -41,6 +41,7 @@ public: Rational, Trigonometry, Unit, + Matrix, Complex }; static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; } diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 16e68704d..6d51e7862 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -17,7 +17,8 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo m_integerController(editExpressionController), m_rationalController(editExpressionController), m_trigonometryController(editExpressionController), - m_unitController(editExpressionController) + m_unitController(editExpressionController), + m_matrixController(editExpressionController) { for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_calculationHistory[i].setParentResponder(&m_selectableTableView); @@ -110,6 +111,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { vc = &m_rationalController; } else if (additionalInfoType == Calculation::AdditionalInformationType::Unit) { vc = &m_unitController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::Matrix) { + vc = &m_matrixController; } if (vc) { vc->setExpression(e); diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index 021a0e876..e289eb5fc 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -10,6 +10,7 @@ #include "additional_outputs/rational_list_controller.h" #include "additional_outputs/trigonometry_list_controller.h" #include "additional_outputs/unit_list_controller.h" +#include "additional_outputs/matrix_list_controller.h" namespace Calculation { @@ -48,6 +49,7 @@ private: RationalListController m_rationalController; TrigonometryListController m_trigonometryController; UnitListController m_unitController; + MatrixListController m_matrixController; }; } diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index 566a334a4..a3660e08c 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -17,7 +17,7 @@ public: size_t size() const override; #if POINCARE_TREE_LOG void logNodeName(std::ostream & stream) const override { - stream << "Based Integer"; + stream << "BasedInteger"; } virtual void logAttributes(std::ostream & stream) const override; #endif @@ -38,6 +38,7 @@ public: template T templatedApproximate() const; private: + int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return m_base == Integer::Base::Decimal ? LayoutShape::Integer : LayoutShape::BinaryHexadecimal; } Integer::Base m_base; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index a08843758..def7da820 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -75,6 +75,7 @@ public: /* Operation on matrix */ int rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool inPlace = false); + Expression createTrace(); // Inverse the array in-place. Array has to be given in the form array[row_index][column_index] template static int ArrayInverse(T * array, int numberOfRows, int numberOfColumns); static Matrix CreateIdentity(int dim); diff --git a/poincare/src/based_integer.cpp b/poincare/src/based_integer.cpp index b8783745e..1c5141bc7 100644 --- a/poincare/src/based_integer.cpp +++ b/poincare/src/based_integer.cpp @@ -66,6 +66,15 @@ template T BasedIntegerNode::templatedApproximate() const { // Comparison +int BasedIntegerNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + if (!ascending) { + return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); + } + assert(e->type() == ExpressionNode::Type::BasedInteger); + const BasedIntegerNode * other = static_cast(e); + return Integer::NaturalOrder(integer(), other->integer()); +} + Expression BasedIntegerNode::shallowReduce(ReductionContext reductionContext) { return BasedInteger(this).shallowReduce(); } diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index c5037f4e2..0c5f37cd7 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -146,6 +146,16 @@ int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Pr return rank; } +Expression Matrix::createTrace() { + assert(numberOfRows() == numberOfColumns()); + int n = numberOfRows(); + Addition a = Addition::Builder(); + for (int i = 0; i < n; i++) { + a.addChildAtIndexInPlace(matrixChild(i,i).clone(), i, i); + } + return std::move(a); +} + template int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { if (numberOfRows != numberOfColumns) { @@ -378,6 +388,7 @@ Expression Matrix::createInverse(ExpressionNode::ReductionContext reductionConte } Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext, bool * couldComputeDeterminant, bool inPlace) { + // Determinant must be called on a reduced matrix only. *couldComputeDeterminant = true; Matrix m = inPlace ? *this : clone().convert(); int dim = m.numberOfRows(); diff --git a/poincare/src/matrix_trace.cpp b/poincare/src/matrix_trace.cpp index ab9aefc8f..f678e3bbe 100644 --- a/poincare/src/matrix_trace.cpp +++ b/poincare/src/matrix_trace.cpp @@ -48,11 +48,7 @@ Expression MatrixTrace::shallowReduce(ExpressionNode::ReductionContext reduction if (matrixChild0.numberOfRows() != matrixChild0.numberOfColumns()) { return replaceWithUndefinedInPlace(); } - int n = matrixChild0.numberOfRows(); - Addition a = Addition::Builder(); - for (int i = 0; i < n; i++) { - a.addChildAtIndexInPlace(matrixChild0.matrixChild(i,i), i, i); // No need to clone - } + Expression a = matrixChild0.createTrace(); replaceWithInPlace(a); return a.shallowReduce(reductionContext); }