diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 679dbfad5..0d1297133 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -1,5 +1,17 @@ name: Continuous integration -on: [pull_request, push] +#on: [pull_request, push] +on: + pull_request: + workflow_dispatch: + inputs: + triggerIos: + description: 'Run iOS tests' + required: true + default: 'no' + triggerMacos: + description: 'Run macOS tests' + required: true + default: 'no' jobs: # nintendo_3ds: @@ -11,7 +23,7 @@ jobs: # - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro # - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM # - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH - + # - uses: actions/checkout@v1 # with: # submodules: true @@ -94,6 +106,23 @@ jobs: with: name: epsilon-binpack-n0110.tgz path: output/release/device/n0110/binpack-n0110.tgz + windows: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - uses: msys2/setup-msys2@v2 + - uses: actions/checkout@v2 + - run: pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config make mingw-w64-x86_64-python3 mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-libpng + - run: make -j2 PLATFORM=simulator + - run: make -j2 PLATFORM=simulator epsilon.official.exe + - run: make -j2 PLATFORM=simulator test.headless.exe + - run: output/release/simulator/windows/test.headless.exe + - uses: actions/upload-artifact@master + with: + name: epsilon-windows.exe + path: output/release/simulator/windows/epsilon.exe web: runs-on: ubuntu-latest steps: @@ -121,3 +150,34 @@ jobs: name: epsilon-linux.bin path: output/release/simulator/linux/epsilon.bin - run: make -j2 PLATFORM=simulator test.headless.bin + macos: + if: github.event.inputs.triggerMacos == 'yes' + runs-on: macOS-latest + steps: + - run: brew install numworks/tap/epsilon-sdk + - uses: actions/checkout@v2 + - run: make -j2 PLATFORM=simulator + - run: make -j2 PLATFORM=simulator epsilon.official.app + - run: make -j2 PLATFORM=simulator ARCH=x86_64 test.headless.bin + - run: output/release/simulator/macos/x86_64/test.headless.bin + - uses: actions/upload-artifact@master + with: + name: epsilon-macos.zip + path: output/release/simulator/macos/epsilon.app + ios: + if: github.event.inputs.triggerIos == 'yes' + runs-on: macOS-latest + steps: + - run: brew install numworks/tap/epsilon-sdk + - uses: actions/checkout@v2 + - run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 + - run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 epsilon.official.ipa + - run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 test.ipa + - run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 APPLE_PLATFORM=ios-simulator + - uses: actions/upload-artifact@master + with: + name: epsilon-ios.ipa + path: output/release/simulator/ios/epsilon.ipa + +env: + ACCEPT_OFFICIAL_TOS: 1 diff --git a/apps/Makefile b/apps/Makefile index d33eaa3c4..1d7242030 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -42,7 +42,10 @@ apps_src += $(addprefix apps/,\ title_bar_view.cpp \ ) -tests_src += apps/exam_mode_configuration_non_official.cpp +tests_src += $(addprefix apps/,\ + exam_mode_configuration_official.cpp \ +) + snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;) apps_declaration = $(foreach i,$(apps),$(i) m_$(subst :,,$(i));) @@ -56,6 +59,9 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m # I18n file generation +country_preferences = apps/country_preferences.csv +language_preferences = apps/language_preferences.csv + # The header is refered to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) @@ -65,14 +71,14 @@ i18n_files += $(addprefix apps/language_,$(addsuffix _iso6391.universal.i18n, $( endif i18n_files += $(call i18n_with_universal_for,shared) -i18n_files += $(call i18n_without_universal_for,toolbox) +i18n_files += $(call i18n_with_universal_for,toolbox) i18n_files += $(call i18n_without_universal_for,variables) $(eval $(call rule_for, \ I18N, \ apps/i18n.cpp, \ $(i18n_files), \ - $$(PYTHON) apps/i18n.py --codepoints $(code_points) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \ + $$(PYTHON) apps/i18n.py --codepoints $(code_points) --countrypreferences $(country_preferences) --languagepreferences $(language_preferences) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \ global \ )) @@ -87,7 +93,7 @@ $(eval $(call depends_on_image,apps/title_bar_view.cpp,apps/exam_icon.png)) $(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/apps/i18n.h $(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.h -apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src) +apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_graph_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src) apps_tests_src += $(addprefix apps/,\ alternate_empty_nested_menu_controller.cpp \ diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index a88c52415..dd1bbb1eb 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -59,8 +59,23 @@ AppsContainer::AppsContainer() : } bool AppsContainer::poincareCircuitBreaker() { + constexpr uint64_t minimalPressDuration = 20; + static uint64_t beginningOfInterruption = 0; Ion::Keyboard::State state = Ion::Keyboard::scan(); - return state.keyDown(Ion::Keyboard::Key::Back); + bool interrupt = state.keyDown(Ion::Keyboard::Key::Back) || state.keyDown(Ion::Keyboard::Key::Home) || state.keyDown(Ion::Keyboard::Key::OnOff); + if (!interrupt) { + beginningOfInterruption = 0; + return false; + } + if (beginningOfInterruption == 0) { + beginningOfInterruption = Ion::Timing::millis(); + return false; + } + if (Ion::Timing::millis() - beginningOfInterruption > minimalPressDuration) { + beginningOfInterruption = 0; + return true; + } + return false; } App::Snapshot * AppsContainer::hardwareTestAppSnapshot() { @@ -181,7 +196,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { } if (changedZoom) { KDIonContext::sharedContext()->updatePostProcessingEffects(); - redrawWindow(true); + redrawWindow(); return true; } } @@ -341,7 +356,7 @@ void AppsContainer::shutdownDueToLowBattery() { * case. */ return; } - while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) { Ion::Backlight::setBrightness(0); if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { /* Unless the LED is lit up for the exam mode, switch off the LED. IF the @@ -365,15 +380,15 @@ bool AppsContainer::updateAlphaLock() { return m_window.updateAlphaLock(); } -OnBoarding::PopUpController * AppsContainer::promptController() { +OnBoarding::PromptController * AppsContainer::promptController() { if (k_promptNumberOfMessages == 0) { return nullptr; } return &m_promptController; } -void AppsContainer::redrawWindow(bool force) { - m_window.redraw(force); +void AppsContainer::redrawWindow() { + m_window.redraw(); } void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) { diff --git a/apps/apps_container.h b/apps/apps_container.h index 4ba60dc7a..d9263c0a9 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -16,8 +16,8 @@ #include "global_preferences.h" #include "backlight_dimming_timer.h" #include "shared/global_context.h" -#include "on_boarding/pop_up_controller.h" #include "clock_timer.h" +#include "on_boarding/prompt_controller.h" #include @@ -47,8 +47,8 @@ public: void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); - OnBoarding::PopUpController * promptController(); - void redrawWindow(bool force = false); + OnBoarding::PromptController * promptController(); + void redrawWindow(); void activateExamMode(GlobalPreferences::ExamMode examMode); // Exam pop-up controller delegate void examDeactivatingPopUpIsDismissed() override; @@ -74,7 +74,7 @@ private: MathToolbox m_mathToolbox; MathVariableBoxController m_variableBoxController; ExamPopUpController m_examPopUpController; - OnBoarding::PopUpController m_promptController; + OnBoarding::PromptController m_promptController; BatteryTimer m_batteryTimer; SuspendTimer m_suspendTimer; BacklightDimmingTimer m_backlightDimmingTimer; diff --git a/apps/battery_timer.cpp b/apps/battery_timer.cpp index db53d7c90..bde13c21c 100644 --- a/apps/battery_timer.cpp +++ b/apps/battery_timer.cpp @@ -9,7 +9,7 @@ BatteryTimer::BatteryTimer() : bool BatteryTimer::fire() { AppsContainer * container = AppsContainer::sharedAppsContainer(); bool needRedrawing = container->updateBatteryState(); - if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { + if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) { container->shutdownDueToLowBattery(); } return needRedrawing; 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..be0212561 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,20 @@ 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()); + /* By reseting m_layouts, numberOfRow will go down to 0, and the highlighted + * cells won't be unselected. Therefore we unselect them here. */ + m_cells[i].setHighlighted(false); + m_layouts[i] = Layout(); } + m_expression = Expression(); } HighlightCell * ExpressionsListController::reusableCell(int index, int type) { @@ -43,24 +48,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/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp index 370729bb2..b850ad48a 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.cpp +++ b/apps/calculation/additional_outputs/illustrated_list_controller.cpp @@ -11,6 +11,7 @@ namespace Calculation { IllustratedListController::IllustratedListController(EditExpressionController * editExpressionController) : ListController(editExpressionController, this), + m_calculationStore(m_calculationStoreBuffer, k_calculationStoreBufferSize), m_additionalCalculationCells{} { for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.h b/apps/calculation/additional_outputs/illustrated_list_controller.h index ea5b484de..c0137969e 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.h +++ b/apps/calculation/additional_outputs/illustrated_list_controller.h @@ -39,7 +39,10 @@ protected: private: int textAtIndex(char * buffer, size_t bufferSize, int index) override; virtual CodePoint expressionSymbol() const = 0; + // Set the size of the buffer needed to store the additional calculation constexpr static int k_maxNumberOfAdditionalCalculations = 4; + constexpr static int k_calculationStoreBufferSize = k_maxNumberOfAdditionalCalculations * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *)); + char m_calculationStoreBuffer[k_calculationStoreBufferSize]; // Cells virtual HighlightCell * illustrationCell() = 0; ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations]; 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..a8bf46559 --- /dev/null +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -0,0 +1,106 @@ +#include "matrix_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#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(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + 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.nullStatus(context) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); + } + } + // 3. Matrix row echelon form + messageIndex = 2; + Expression rowEchelonForm = MatrixRowEchelonForm::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(MatrixReducedRowEchelonForm::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(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + 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::AdditionalDeterminant, + I18n::Message::AdditionalInverse, + I18n::Message::AdditionalRowEchelonForm, + I18n::Message::AdditionalReducedRowEchelonForm, + I18n::Message::AdditionalTrace}; + return messages[m_indexMessageMap[index]]; +} + +} 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..869826d5e 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -15,112 +15,62 @@ 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; - // 1. First rows: miscellaneous classic units for some dimensions + /* 1. First rows: miscellaneous classic units for some dimensions, in both + * metric and imperial units. */ Expression copy = m_expression.clone(); Expression units; // Reduce to be able to recognize units - PoincareHelpers::Reduce(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User); - copy = copy.removeUnit(&units); - bool requireSimplification = false; - bool canChangeUnitPrefix = false; + PoincareHelpers::ReduceAndRemoveUnit(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &units); + double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); + ExpressionNode::ReductionContext reductionContext( + App::app()->localContext(), + Preferences::sharedPreferences()->complexFormat(), + Preferences::sharedPreferences()->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::ReductionTarget::User, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext); - if (Unit::IsSISpeed(units)) { - // 1.a. Turn speed into km/h - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Multiplication::Builder( - Unit::Kilometer(), - Power::Builder( - Unit::Hour(), - Rational::Builder(-1) - ) - ) - ); - requireSimplification = true; // Simplify the conversion - } else if (Unit::IsSIVolume(units)) { - // 1.b. Turn volume into L - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Unit::Liter() - ); - requireSimplification = true; // Simplify the conversion - canChangeUnitPrefix = true; // Pick best prefix (mL) - } else if (Unit::IsSIEnergy(units)) { - // 1.c. Turn energy into Wh - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Multiplication::Builder( - Unit::Watt(), - Unit::Hour() - ) - ); - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Unit::ElectronVolt() - ); - requireSimplification = true; // Simplify the conversion - canChangeUnitPrefix = true; // Pick best prefix (kWh) - } 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()); - } - // 1.d. Simplify and tune prefix of all computed expressions - size_t currentExpressionIndex = 0; - while (currentExpressionIndex < numberOfMemoizedExpressions) { - assert(!m_memoizedExpressions[currentExpressionIndex].isUninitialized()); - if (requireSimplification) { - Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[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()); - ExpressionNode::ReductionContext reductionContext( - App::app()->localContext(), - Preferences::sharedPreferences()->complexFormat(), - Preferences::sharedPreferences()->angleUnit(), - ExpressionNode::ReductionTarget::User, - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext); - m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); - } - currentExpressionIndex++; - } + // 2. SI units only + 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++; - // 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++; - - // 3. Get rid of duplicates + /* 3. Get rid of duplicates + * We find duplicates by comparing the serializations, to eliminate + * expressions that only differ by the types of their number nodes. */ 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) { + int currentExpressionIndex = 0; + 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]; + constexpr int buffersSize = Constant::MaxSerializedExpressionSize; + char buffer1[buffersSize]; + int size1 = PoincareHelpers::Serialize(expressions[currentExpressionIndex], buffer1, buffersSize); + for (int i = 0; i < currentExpressionIndex + 1; 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--; + char buffer2[buffersSize]; + int size2 = PoincareHelpers::Serialize(comparedExpression, buffer2, buffersSize); + if (size1 == size2 && strcmp(buffer1, buffer2) == 0) { + numberOfExpressions--; // Shift next expressions - for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) { - m_memoizedExpressions[j] = m_memoizedExpressions[j+1]; + for (int 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 +81,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/app.cpp b/apps/calculation/app.cpp index 8f4fd1459..2c5c97d3c 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -31,6 +31,8 @@ App * App::Snapshot::unpack(Container * container) { void App::Snapshot::reset() { m_calculationStore.deleteAll(); + m_cacheBuffer[0] = 0; + m_cacheBufferInformation = 0; } App::Descriptor * App::Snapshot::descriptor() { @@ -38,14 +40,14 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -void App::Snapshot::tidy() { - m_calculationStore.tidy(); +App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculationBufferSize) +{ } App::App(Snapshot * snapshot) : ExpressionFieldDelegateApp(snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), - m_editExpressionController(&m_modalViewController, this, &m_historyController, snapshot->calculationStore()) + m_editExpressionController(&m_modalViewController, this, snapshot->cacheBuffer(), snapshot->cacheBufferInformationAddress(), &m_historyController, snapshot->calculationStore()) { } @@ -70,7 +72,17 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) { return false; } } - return !expression.isUninitialized(); + return !(expression.isUninitialized() || expression.type() == ExpressionNode::Type::Equal); +} + +void App::didBecomeActive(Window * window) { + m_editExpressionController.restoreInput(); + Shared::ExpressionFieldDelegateApp::didBecomeActive(window); +} + +void App::willBecomeInactive() { + m_editExpressionController.memoizeInput(); + Shared::ExpressionFieldDelegateApp::willBecomeInactive(); } } diff --git a/apps/calculation/app.h b/apps/calculation/app.h index ee12934c7..975989ffa 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -6,6 +6,7 @@ #include "history_controller.h" #include "../shared/text_field_delegate_app.h" #include +#include "../shared/shared_app.h" namespace Calculation { @@ -18,15 +19,22 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: + Snapshot(); App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; CalculationStore * calculationStore() { return &m_calculationStore; } + char * cacheBuffer() { return m_cacheBuffer; } + size_t * cacheBufferInformationAddress() { return &m_cacheBufferInformation; } private: - void tidy() override; CalculationStore m_calculationStore; + // Set the size of the buffer needed to store the calculations + static constexpr int k_calculationBufferSize = 10 * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *)); + char m_calculationBuffer[k_calculationBufferSize]; + char m_cacheBuffer[EditExpressionController::k_cacheBufferSize]; + size_t m_cacheBufferInformation; }; static App * app() { return static_cast(Container::activeApp()); @@ -36,9 +44,12 @@ public: bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override; // TextFieldDelegateApp bool isAcceptableExpression(const Poincare::Expression expression) override; + private: App(Snapshot * snapshot); HistoryController m_historyController; + void didBecomeActive(Window * window) override; + void willBecomeInactive() override; EditExpressionController m_editExpressionController; }; diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index 18aab0374..406f27e84 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binär" PrimeFactors = "Primfaktoren" MixedFraction = "Gemischte Zahl" EuclideanDivision = "Division mit Rest" +AdditionalDeterminant = "Determinante" +AdditionalInverse = "Inverse" +AdditionalRowEchelonForm = "Stufenform" +AdditionalReducedRowEchelonForm = "Reduzierte Stufenform" +AdditionalTrace = "Spur" \ No newline at end of file diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index 399532fb7..0e54f24cf 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binary" PrimeFactors = "Prime factors" MixedFraction = "Mixed fraction" EuclideanDivision = "Euclidean division" +AdditionalDeterminant = "Determinant" +AdditionalInverse = "Inverse" +AdditionalRowEchelonForm = "Row echelon form" +AdditionalReducedRowEchelonForm = "Reduced row echelon form" +AdditionalTrace = "Trace" \ No newline at end of file diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 25c3c5035..057481a0d 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binario" PrimeFactors = "Factores primos" MixedFraction = "Fracción mixta" EuclideanDivision = "División euclidiana" +AdditionalDeterminant = "Determinante" +AdditionalInverse = "Inversa" +AdditionalRowEchelonForm = "Matriz escalonada" +AdditionalReducedRowEchelonForm = "Matriz escalonada reducida" +AdditionalTrace = "Traza" \ No newline at end of file diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index ca8e28739..0e155e294 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binaire" PrimeFactors = "Facteurs premiers" MixedFraction = "Fraction mixte" EuclideanDivision = "Division euclidienne" +AdditionalDeterminant = "Déterminant" +AdditionalInverse = "Inverse" +AdditionalRowEchelonForm = "Forme échelonnée" +AdditionalReducedRowEchelonForm = "Forme échelonnée réduite" +AdditionalTrace = "Trace" \ No newline at end of file diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index 6544c6622..ca4102893 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -4,6 +4,11 @@ AdditionalResults = "Risultati complementari" DecimalBase = "Decimale" HexadecimalBase = "Esadecimale" BinaryBase = "Binario" -PrimeFactors = "Fattori primi" +PrimeFactors = "Fattorizzazione" MixedFraction = "Frazione mista" EuclideanDivision = "Divisione euclidea" +AdditionalDeterminant = "Determinante" +AdditionalInverse = "Inversa" +AdditionalRowEchelonForm = "Matrice a scalini" +AdditionalReducedRowEchelonForm = "Matrice ridotta a scalini" +AdditionalTrace = "Traccia" \ No newline at end of file diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index 0ae59c24a..51e412cb4 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -1,9 +1,14 @@ -CalculApp = "Calculatie" -CalculAppCapital = "CALCULATIE" -AdditionalResults = "Bijkomende resultaten" +CalculApp = "Rekenen" +CalculAppCapital = "REKENEN" +AdditionalResults = "Aanvullende resultaten" DecimalBase = "Decimaal" HexadecimalBase = "Hexadecimaal" -BinaryBase = "Binaire" -PrimeFactors = "Priemfactoren" +BinaryBase = "Binair" +PrimeFactors = "Ontbinding" MixedFraction = "Gemengde breuk" EuclideanDivision = "Geheeltallige deling" +AdditionalDeterminant = "Determinant" +AdditionalInverse = "Inverse" +AdditionalRowEchelonForm = "Echelonvorm" +AdditionalReducedRowEchelonForm = "Gereduceerde echelonvorm" +AdditionalTrace = "Spoor" \ No newline at end of file diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index b8e8717aa..941363a04 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -4,6 +4,11 @@ AdditionalResults = "Resultados adicionais" DecimalBase = "Decimal" HexadecimalBase = "Hexadecimal" BinaryBase = "Binário" -PrimeFactors = "Fatores primos" +PrimeFactors = "Fatorização" MixedFraction = "Fração mista" EuclideanDivision = "Divisão euclidiana" +AdditionalDeterminant = "Determinante" +AdditionalInverse = "Matriz inversa" +AdditionalRowEchelonForm = "Matriz escalonada" +AdditionalReducedRowEchelonForm = "Matriz escalonada reduzida" +AdditionalTrace = "Traço" \ No newline at end of file diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 31b5cd8ff..c293cbbe8 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -169,7 +169,8 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { ExpressionNode::Type::Sum, ExpressionNode::Type::Derivative, ExpressionNode::Type::ConfidenceInterval, - ExpressionNode::Type::PredictionInterval + ExpressionNode::Type::PredictionInterval, + ExpressionNode::Type::Sequence }; return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type)); }, context) @@ -218,7 +219,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( Preferences * preferences = Preferences::sharedPreferences(); // TODO: complex format should not be needed here (as it is not used to create layouts) Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); - m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit()) ? EqualSign::Equal : EqualSign::Approximation; + m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()) ? EqualSign::Equal : EqualSign::Approximation; return m_equalSign; } else { /* Do not override m_equalSign in case there is enough room in the pool @@ -254,32 +255,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co } if (o.hasUnit()) { Expression unit; - PoincareHelpers::Reduce(&o, - App::app()->localContext(), - ExpressionNode::ReductionTarget::User, - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, - ExpressionNode::UnitConversion::None); - o = o.removeUnit(&unit); - // There might be no unit in the end, if the reduction was interrupted. - if (!unit.isUninitialized()) { - if (Unit::IsSI(unit)) { - if (Unit::IsSISpeed(unit) || Unit::IsSIVolume(unit) || Unit::IsSIEnergy(unit)) { - /* All these units will provide misc. classic representatives in - * addition to the SI unit in additional information. */ - return AdditionalInformationType::Unit; - } - if (Unit::IsSITime(unit)) { - /* If the number of seconds is above 60s, we can write it in the form - * of an addition: 23_min + 12_s for instance. */ - double value = Shared::PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); - if (value > Unit::SecondsPerMinute) { - return AdditionalInformationType::Unit; - } - } - return AdditionalInformationType::None; - } - return AdditionalInformationType::Unit; - } + PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); + double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); + return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None; } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; @@ -291,6 +269,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 29e82f28d..be7f87c9b 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -21,6 +21,7 @@ class CalculationStore; class Calculation { friend CalculationStore; public: + static constexpr int k_numberOfExpressions = 4; enum class EqualSign : uint8_t { Unknown, Approximation, @@ -40,6 +41,7 @@ public: Rational, Trigonometry, Unit, + Matrix, Complex }; static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; } @@ -48,7 +50,7 @@ public: * calculations instead of clearing less space, then fail to serialize, clear * more space, fail to serialize, clear more space, etc., until reaching * sufficient free space. */ - static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize; } + static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize + sizeof(Calculation *); } Calculation() : m_displayOutput(DisplayOutput::Unknown), @@ -93,8 +95,6 @@ public: // Additional Information AdditionalInformationType additionalInformationType(Poincare::Context * context); private: - static constexpr int maxWidth = 314; - static constexpr int k_numberOfExpressions = 4; static constexpr KDCoordinate k_heightComputationFailureHeight = 50; static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000"; diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index af83020ec..e0dcc12f1 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -12,58 +12,47 @@ using namespace Shared; namespace Calculation { -CalculationStore::CalculationStore() : - m_bufferEnd(m_buffer), - m_numberOfCalculations(0), - m_slidedBuffer(false), - m_indexOfFirstMemoizedCalculationPointer(0) +CalculationStore::CalculationStore(char * buffer, int size) : + m_buffer(buffer), + m_bufferSize(size), + m_calculationAreaEnd(m_buffer), + m_numberOfCalculations(0) { - resetMemoizedModelsAfterCalculationIndex(-1); + assert(m_buffer != nullptr); + assert(m_bufferSize > 0); } +// Returns an expiring pointer to the calculation of index i ExpiringPointer CalculationStore::calculationAtIndex(int i) { - assert(!m_slidedBuffer); assert(i >= 0 && i < m_numberOfCalculations); - assert(m_indexOfFirstMemoizedCalculationPointer >= 0); - if (i >= m_indexOfFirstMemoizedCalculationPointer && i < m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { - // The calculation is within the range of memoized calculations - Calculation * c = m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer]; - if (c != nullptr) { - // The pointer was memoized - return ExpiringPointer(c); - } - c = bufferCalculationAtIndex(i); - m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer] = c; - return c; + // m_buffer is the adress of the oldest calculation in calculation store + Calculation * c = (Calculation *) m_buffer; + if (i != m_numberOfCalculations-1) { + // The calculation we want is not the oldest one so we get its pointer + c = *reinterpret_cast(addressOfPointerToCalculationOfIndex(i+1)); } - // Slide the memoization buffer - if (i >= m_indexOfFirstMemoizedCalculationPointer) { - // Slide the memoization buffer to the left - memmove(m_memoizedCalculationPointers, m_memoizedCalculationPointers+1, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *)); - m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers - 1] = nullptr; - m_indexOfFirstMemoizedCalculationPointer++; - } else { - // Slide the memoization buffer to the right - memmove(m_memoizedCalculationPointers+1, m_memoizedCalculationPointers, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *)); - m_memoizedCalculationPointers[0] = nullptr; - m_indexOfFirstMemoizedCalculationPointer--; - } - return calculationAtIndex(i); + return ExpiringPointer(c); } +// Pushes an expression in the store ExpiringPointer CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) { - /* Compute ans now, before the buffer is slided and before the calculation + /* Compute ans now, before the buffer is updated and before the calculation * might be deleted */ Expression ans = ansExpression(context); - // Prepare the buffer for the new calculation - int minSize = Calculation::MinimalSize(); - assert(k_bufferSize > minSize); - while (remainingBufferSize() < minSize || m_numberOfCalculations > k_maxNumberOfCalculations) { - deleteLastCalculation(); + /* Prepare the buffer for the new calculation + *The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */ + int minSize = Calculation::MinimalSize() + sizeof(Calculation *); + assert(m_bufferSize > minSize); + while (remainingBufferSize() < minSize) { + // If there is no more space to store a calculation, we delete the oldest one + deleteOldestCalculation(); } - char * newCalculationsLocation = slideCalculationsToEndOfBuffer(); - char * nextSerializationLocation = m_buffer; + + // Getting the adresses of the limits of the free space + char * beginingOfFreeSpace = (char *)m_calculationAreaEnd; + char * endOfFreeSpace = beginingOfMemoizationArea(); + char * previousCalc = beginingOfFreeSpace; // Add the beginning of the calculation { @@ -71,23 +60,23 @@ ExpiringPointer CalculationStore::push(const char * text, Context * * available, so this memmove will not overide anything. */ Calculation newCalc = Calculation(); size_t calcSize = sizeof(newCalc); - memmove(nextSerializationLocation, &newCalc, calcSize); - nextSerializationLocation += calcSize; + memcpy(beginingOfFreeSpace, &newCalc, calcSize); + beginingOfFreeSpace += calcSize; } /* Add the input expression. * We do not store directly the text entered by the user because we do not * want to keep Ans symbol in the calculation store. */ - const char * inputSerialization = nextSerializationLocation; + const char * inputSerialization = beginingOfFreeSpace; { Expression input = Expression::Parse(text, context).replaceSymbolWithExpression(Symbol::Ans(), ans); - if (!pushSerializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) { + if (!pushSerializeExpression(input, beginingOfFreeSpace, &endOfFreeSpace)) { /* If the input does not fit in the store (event if the current * calculation is the only calculation), just replace the calculation with * undef. */ return emptyStoreAndPushUndef(context, heightComputer); } - nextSerializationLocation += strlen(nextSerializationLocation) + 1; + beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1; } // Compute and serialize the outputs @@ -110,30 +99,27 @@ ExpiringPointer CalculationStore::push(const char * text, Context * if (i == numberOfOutputs - 1) { numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits(); } - if (!pushSerializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation, numberOfSignificantDigits)) { + if (!pushSerializeExpression(outputs[i], beginingOfFreeSpace, &endOfFreeSpace, numberOfSignificantDigits)) { /* If the exat/approximate output does not fit in the store (event if the * current calculation is the only calculation), replace the output with * undef if it fits, else replace the whole calcualtion with undef. */ Expression undef = Undefined::Builder(); - if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) { + if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) { return emptyStoreAndPushUndef(context, heightComputer); } } - nextSerializationLocation += strlen(nextSerializationLocation) + 1; + beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1; } } + // Storing the pointer of the end of the new calculation + memcpy(endOfFreeSpace-sizeof(Calculation*),&beginingOfFreeSpace,sizeof(beginingOfFreeSpace)); - // Restore the other calculations - size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation; - memcpy(nextSerializationLocation, newCalculationsLocation, slideSize); - m_slidedBuffer = false; + // The new calculation is now stored m_numberOfCalculations++; - m_bufferEnd+= nextSerializationLocation - m_buffer; - // Clean the memoization - resetMemoizedModelsAfterCalculationIndex(-1); - - ExpiringPointer calculation = ExpiringPointer(reinterpret_cast(m_buffer)); + // The end of the calculation storage area is updated + m_calculationAreaEnd += beginingOfFreeSpace - previousCalc; + ExpiringPointer calculation = ExpiringPointer(reinterpret_cast(previousCalc)); /* Heights are computed now to make sure that the display output is decided * accordingly to the remaining size in the Poincare pool. Once it is, it * can't change anymore: the calculation heights are fixed which ensures that @@ -144,36 +130,42 @@ ExpiringPointer CalculationStore::push(const char * text, Context * return calculation; } +// Delete the calculation of index i void CalculationStore::deleteCalculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); - assert(!m_slidedBuffer); - ExpiringPointer calcI = calculationAtIndex(i); - char * nextCalc = reinterpret_cast(calcI->next()); - assert(m_bufferEnd >= nextCalc); - size_t slidingSize = m_bufferEnd - nextCalc; - memmove((char *)(calcI.pointer()), nextCalc, slidingSize); - m_bufferEnd -= (nextCalc - (char *)(calcI.pointer())); - m_numberOfCalculations--; - resetMemoizedModelsAfterCalculationIndex(i); -} - -void CalculationStore::deleteAll() { - /* We might call deleteAll because the app closed due to a pool allocation - * failure, so we cannot assert that m_slidedBuffer is false. */ - m_slidedBuffer = false; - m_bufferEnd = m_buffer; - m_numberOfCalculations = 0; - resetMemoizedModelsAfterCalculationIndex(-1); -} - -void CalculationStore::tidy() { - if (m_slidedBuffer) { - deleteAll(); + if (i == 0) { + ExpiringPointer lastCalculationPointer = calculationAtIndex(0); + m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer()); + m_numberOfCalculations--; return; } - resetMemoizedModelsAfterCalculationIndex(-1); + char * calcI = (char *)calculationAtIndex(i).pointer(); + char * nextCalc = (char *) calculationAtIndex(i-1).pointer(); + assert(m_calculationAreaEnd >= nextCalc); + size_t slidingSize = m_calculationAreaEnd - nextCalc; + // Slide the i-1 most recent calculations right after the i+1'th + memmove(calcI, nextCalc, slidingSize); + m_calculationAreaEnd -= nextCalc - calcI; + // Recompute pointer to calculations after the i'th + recomputeMemoizedPointersAfterCalculationIndex(i); + m_numberOfCalculations--; } +// Delete the oldest calculation in the store and returns the amount of space freed by the operation +size_t CalculationStore::deleteOldestCalculation() { + char * oldBufferEnd = (char *) m_calculationAreaEnd; + deleteCalculationAtIndex(numberOfCalculations()-1); + char * newBufferEnd = (char *) m_calculationAreaEnd; + return oldBufferEnd - newBufferEnd; +} + +// Delete all calculations +void CalculationStore::deleteAll() { + m_calculationAreaEnd = m_buffer; + m_numberOfCalculations = 0; +} + +// Replace "Ans" by its expression Expression CalculationStore::ansExpression(Context * context) { if (numberOfCalculations() == 0) { return Rational::Builder(0); @@ -192,92 +184,42 @@ Expression CalculationStore::ansExpression(Context * context) { return mostRecentCalculation->exactOutput(); } -Calculation * CalculationStore::bufferCalculationAtIndex(int i) { - int currentIndex = 0; - for (Calculation * c : *this) { - if (currentIndex == i) { - return c; - } - currentIndex++; - } - assert(false); - return nullptr; -} - +// Push converted expression in the buffer bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) { - assert(m_slidedBuffer); - assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + assert(*newCalculationsLocation <= m_buffer + m_bufferSize); bool expressionIsPushed = false; while (true) { size_t locationSize = *newCalculationsLocation - location; expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1); - if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) { + if (expressionIsPushed || *newCalculationsLocation >= m_buffer + m_bufferSize) { break; } - *newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); - assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + *newCalculationsLocation = *newCalculationsLocation + deleteOldestCalculation(); + assert(*newCalculationsLocation <= m_buffer + m_bufferSize); } return expressionIsPushed; } -char * CalculationStore::slideCalculationsToEndOfBuffer() { - int calculationsSize = m_bufferEnd - m_buffer; - char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize; - memmove(calculationsNewPosition, m_buffer, calculationsSize); - m_slidedBuffer = true; - return calculationsNewPosition; -} -size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) { - assert(m_numberOfCalculations > 0); - size_t result; - if (!m_slidedBuffer) { - assert(calculationsStart == nullptr); - const char * previousBufferEnd = m_bufferEnd; - m_bufferEnd = lastCalculationPosition(m_buffer); - assert(previousBufferEnd > m_bufferEnd); - result = previousBufferEnd - m_bufferEnd; - } else { - assert(calculationsStart != nullptr); - const char * lastCalc = lastCalculationPosition(calculationsStart); - assert(*lastCalc == 0); - result = m_buffer + k_bufferSize - lastCalc; - memmove(const_cast(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result); - } - m_numberOfCalculations--; - resetMemoizedModelsAfterCalculationIndex(-1); - return result; -} - -const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const { - assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize); - Calculation * c = reinterpret_cast(const_cast(calculationsStart)); - int calculationIndex = 0; - while (calculationIndex < m_numberOfCalculations - 1) { - c = c->next(); - calculationIndex++; - } - return reinterpret_cast(c); -} Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) { /* We end up here as a result of a failed calculation push. The store * attributes are not necessarily clean, so we need to reset them. */ - m_slidedBuffer = false; deleteAll(); return push(Undefined::Name(), context, heightComputer); } -void CalculationStore::resetMemoizedModelsAfterCalculationIndex(int index) { - if (index < m_indexOfFirstMemoizedCalculationPointer) { - memset(&m_memoizedCalculationPointers, 0, k_numberOfMemoizedCalculationPointers * sizeof(Calculation *)); - return; - } - if (index >= m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { - return; - } - for (int i = index - m_indexOfFirstMemoizedCalculationPointer; i < k_numberOfMemoizedCalculationPointers; i++) { - m_memoizedCalculationPointers[i] = nullptr; +// Recompute memoized pointers to the calculations after index i +void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) { + assert(index < m_numberOfCalculations); + // Clear pointer and recompute new ones + Calculation * c = calculationAtIndex(index).pointer(); + Calculation * nextCalc; + while (index != 0) { + nextCalc = c->next(); + memcpy(addressOfPointerToCalculationOfIndex(index), &nextCalc, sizeof(Calculation *)); + c = nextCalc; + index--; } } diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 9f69d3edf..53bc3fdf4 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -7,37 +7,45 @@ namespace Calculation { -/* To optimize the storage space, we use one big buffer for all calculations. - * - * The previous solution was to keep 10 calculations, each containing 3 buffers - * (for input and outputs). To optimize the storage, we then wanted to put all - * outputs in a cache where they could be deleted to add a new entry, and - * recomputed on cache miss. However, the computation depends too much on the - * state of the memory for this to be possible. For instance: - * 6->a - * a+1 - * Perform some big computations that remove a+1 from the cache - * Delete a from the variable box. - * Scroll up to display a+1 : a does not exist anymore so the outputs won't be - * recomputed correctly. - * - * Now we do not cap the number of calculations and just delete the oldests to - * create space for a new calculation. */ +/* + To optimize the storage space, we use one big buffer for all calculations. + The calculations are stored one after another while pointers to the end of each + calculation are stored at the end of the buffer, in the opposite direction. + By doing so, we can memoize every calculation entered while not limiting + the number of calculation stored in the buffer. + + If the remaining space is too small for storing a new calculation, we + delete the oldest one. + + Memory layout : + <- Available space for new calculations -> ++--------------------------------------------------------------------------------------------------------------------+ +| | | | | | | | | | +| Calculation 3 | Calculation 2 | Calculation 1 | Calculation O | |p0|p1|p2|p3| +| Oldest | | | | | | | | | ++--------------------------------------------------------------------------------------------------------------------+ +^ ^ ^ ^ ^ ^ +m_buffer p3 p2 p1 p0 a + +m_calculationAreaEnd = p0 +a = addressOfPointerToCalculation(0) +*/ class CalculationStore { public: CalculationStore(); + CalculationStore(char * buffer, int size); Shared::ExpiringPointer calculationAtIndex(int i); typedef KDCoordinate (*HeightComputer)(Calculation * c, bool expanded); Shared::ExpiringPointer push(const char * text, Poincare::Context * context, HeightComputer heightComputer); void deleteCalculationAtIndex(int i); void deleteAll(); + int remainingBufferSize() const { assert(m_calculationAreaEnd >= m_buffer); return m_bufferSize - (m_calculationAreaEnd - m_buffer) - m_numberOfCalculations*sizeof(Calculation*); } int numberOfCalculations() const { return m_numberOfCalculations; } Poincare::Expression ansExpression(Poincare::Context * context); - void tidy(); + int bufferSize() { return m_bufferSize; } + private: - static constexpr int k_maxNumberOfCalculations = 25; - static constexpr int k_bufferSize = 10 * Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize; class CalculationIterator { public: @@ -53,26 +61,22 @@ private: }; CalculationIterator begin() const { return CalculationIterator(m_buffer); } - CalculationIterator end() const { return CalculationIterator(m_bufferEnd); } + CalculationIterator end() const { return CalculationIterator(m_calculationAreaEnd); } - Calculation * bufferCalculationAtIndex(int i); - int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); } bool pushSerializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); - char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations - size_t deleteLastCalculation(const char * calculationsStart = nullptr); - const char * lastCalculationPosition(const char * calculationsStart) const; Shared::ExpiringPointer emptyStoreAndPushUndef(Poincare::Context * context, HeightComputer heightComputer); - char m_buffer[k_bufferSize]; - const char * m_bufferEnd; + char * m_buffer; + int m_bufferSize; + const char * m_calculationAreaEnd; int m_numberOfCalculations; - bool m_slidedBuffer; + + size_t deleteOldestCalculation(); + char * addressOfPointerToCalculationOfIndex(int i) {return m_buffer + m_bufferSize - (m_numberOfCalculations - i)*sizeof(Calculation *);} // Memoization - static constexpr int k_numberOfMemoizedCalculationPointers = 10; - void resetMemoizedModelsAfterCalculationIndex(int index); - int m_indexOfFirstMemoizedCalculationPointer; - mutable Calculation * m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers]; + char * beginingOfMemoizationArea() {return addressOfPointerToCalculationOfIndex(0);}; + void recomputeMemoizedPointersAfterCalculationIndex(int index); }; } diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 2ae7929a9..7f747d359 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -38,13 +38,14 @@ void EditExpressionController::ContentView::reload() { markRectAsDirty(bounds()); } -EditExpressionController::EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore) : +EditExpressionController::EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, char * cacheBuffer, size_t * cacheBufferInformation, HistoryController * historyController, CalculationStore * calculationStore) : ViewController(parentResponder), + m_cacheBuffer(cacheBuffer), + m_cacheBufferInformation(cacheBufferInformation), m_historyController(historyController), m_calculationStore(calculationStore), m_contentView(this, static_cast(m_historyController->view()), inputEventHandlerDelegate, this, this) { - m_cacheBuffer[0] = 0; } void EditExpressionController::insertTextBody(const char * text) { @@ -58,6 +59,15 @@ void EditExpressionController::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(m_contentView.expressionField()); } +void EditExpressionController::restoreInput() { + m_contentView.expressionField()->restoreContent(m_cacheBuffer, *m_cacheBufferInformation); + clearCacheBuffer(); +} + +void EditExpressionController::memoizeInput() { + *m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize); +} + void EditExpressionController::viewWillAppear() { m_historyController->viewWillAppear(); } @@ -128,7 +138,7 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event } if (event == Ion::Events::Up) { if (m_calculationStore->numberOfCalculations() > 0) { - m_cacheBuffer[0] = 0; + clearCacheBuffer(); m_contentView.expressionField()->setEditing(false, false); Container::activeApp()->setFirstResponder(m_historyController); } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index eee1c4033..32a6ec84c 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -7,7 +7,6 @@ #include "../shared/text_field_delegate.h" #include "../shared/layout_field_delegate.h" #include "history_controller.h" -#include "calculation_store.h" #include "selectable_table_view.h" namespace Calculation { @@ -15,11 +14,23 @@ namespace Calculation { /* TODO: implement a split view */ class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate { public: - EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore); + EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, char * cacheBuffer, size_t * cacheBufferInformation, HistoryController * historyController, CalculationStore * calculationStore); + + /* k_layoutBufferMaxSize dictates the size under which the expression being + * edited can be remembered when the user leaves Calculation. */ + static constexpr int k_layoutBufferMaxSize = 1024; + /* k_cacheBufferSize is the size of the array to which m_cacheBuffer points. + * It is used both as a way to buffer expression when pushing them the + * CalculationStore, and as a storage for the current input when leaving the + * application. */ + static constexpr int k_cacheBufferSize = (k_layoutBufferMaxSize < Constant::MaxSerializedExpressionSize) ? Constant::MaxSerializedExpressionSize : k_layoutBufferMaxSize; + View * view() override { return &m_contentView; } void didBecomeFirstResponder() override; void viewWillAppear() override; void insertTextBody(const char * text); + void restoreInput(); + void memoizeInput(); /* TextFieldDelegate */ bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; @@ -47,11 +58,12 @@ private: ExpressionField m_expressionField; }; void reloadView(); + void clearCacheBuffer() { m_cacheBuffer[0] = 0; *m_cacheBufferInformation = 0; } bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation); bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR); bool inputViewDidAbortEditing(const char * text); - static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize; - char m_cacheBuffer[k_cacheBufferSize]; + char * m_cacheBuffer; + size_t * m_cacheBufferInformation; HistoryController * m_historyController; CalculationStore * m_calculationStore; ContentView m_contentView; 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/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index eb2045778..1ce0c4fe1 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -5,9 +5,17 @@ #include #include "../calculation_store.h" +typedef ::Calculation::Calculation::DisplayOutput DisplayOutput; +typedef ::Calculation::Calculation::EqualSign EqualSign ; +typedef ::Calculation::Calculation::NumberOfSignificantDigits NumberOfSignificantDigits; + using namespace Poincare; using namespace Calculation; +static constexpr int calculationBufferSize = 10 * (sizeof(::Calculation::Calculation) + ::Calculation::Calculation::k_numberOfExpressions * ::Constant::MaxSerializedExpressionSize + sizeof(::Calculation::Calculation *)); +char calculationBuffer[calculationBufferSize]; + + void assert_store_is(CalculationStore * store, const char * * result) { for (int i = 0; i < store->numberOfCalculations(); i++) { quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0); @@ -18,7 +26,7 @@ KDCoordinate dummyHeight(::Calculation::Calculation * c, bool expanded) { return QUIZ_CASE(calculation_store) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); // Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0} const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"}; for (int i = 0; i < 10; i++) { @@ -29,101 +37,117 @@ QUIZ_CASE(calculation_store) { assert_store_is(&store, result); for (int i = 9; i > 0; i = i-2) { - store.deleteCalculationAtIndex(i); + store.deleteCalculationAtIndex(i); } // Store is now {9, 7, 5, 3, 1} const char * result2[] = {"9", "7", "5", "3", "1"}; assert_store_is(&store, result2); store.deleteAll(); + + // Checking if the store handles correctly the delete of the oldest calculation when full + static int minSize = ::Calculation::Calculation::MinimalSize(); + char text[2] = {'0', 0}; + while (store.remainingBufferSize() > minSize) { + store.push(text, &globalContext, dummyHeight); + } + int numberOfCalculations1 = store.numberOfCalculations(); + /* The buffer is now to full to push a new calculation. + * Trying to push a new one should delete the oldest one*/ + store.push(text, &globalContext, dummyHeight); + int numberOfCalculations2 = store.numberOfCalculations(); + // The numberOfCalculations should be the same + quiz_assert(numberOfCalculations1 == numberOfCalculations2); + store.deleteAll(); + quiz_assert(store.remainingBufferSize() == store.bufferSize()); } QUIZ_CASE(calculation_ans) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); store.push("1+3/4", &globalContext, dummyHeight); store.push("ans+2/3", &globalContext, dummyHeight); Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0); - quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate); + quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximate); quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0); store.push("ans+0.22", &globalContext, dummyHeight); lastCalculation = store.calculationAtIndex(0); - quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle); - quiz_assert(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0); + quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximateToggle); + quiz_assert(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0); store.deleteAll(); } -void assertCalculationIs(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) { +void assertCalculationIs(const char * input, DisplayOutput display, EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) { store->push(input, context, dummyHeight); Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(context) == display); - if (sign != ::Calculation::Calculation::EqualSign::Unknown) { + if (sign != EqualSign::Unknown) { quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign); } if (exactOutput) { quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input); } if (displayedApproximateOutput) { - quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input); + quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input); } if (storedApproximateOutput) { - quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input); + quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input); } store->deleteAll(); } QUIZ_CASE(calculation_significant_digits) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); - assertCalculationIs("123456789", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store); - assertCalculationIs("1234567", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store); + assertCalculationIs("123456789", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store); + assertCalculationIs("1234567", DisplayOutput::ApproximateOnly, EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store); } QUIZ_CASE(calculation_display_exact_approximate) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); - assertCalculationIs("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); - assertCalculationIs("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); - assertCalculationIs("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store); - assertCalculationIs("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1/2", DisplayOutput::ExactAndApproximate, EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1/3", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1/0", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("2x-x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("[[1,2,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("[[1,x,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store); + assertCalculationIs("28^7", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("3+√(2)→a", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationIs("3+2→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "5", "5", "5", &globalContext, &store); + assertCalculationIs("3+2→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "5", "5", "5", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationIs("3→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store); + assertCalculationIs("3→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationIs("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("3+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); - assertCalculationIs("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store); - assertCalculationIs("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", "5", &globalContext, &store); - assertCalculationIs("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+1+random()", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+1+round(1.343,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store); + assertCalculationIs("randint(2,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "5", "5", "5", &globalContext, &store); + assertCalculationIs("confidence(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("prediction(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("prediction95(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); } QUIZ_CASE(calculation_symbolic_computation) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); - assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); - assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); - assertCalculationIs("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); - assertCalculationIs("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store); - assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("1+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("f(2)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store); + assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); @@ -131,18 +155,18 @@ QUIZ_CASE(calculation_symbolic_computation) { QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); - assertCalculationIs("int((ℯ^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation - assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); - assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); - assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); - assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); - assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); - assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); - assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); - assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); - assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); + assertCalculationIs("int((ℯ^(-x))-x^(0.5), x, 0, 3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation + assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); + assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); + assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); + assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); } @@ -150,34 +174,34 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) { QUIZ_CASE(calculation_complex_format) { Shared::GlobalContext globalContext; - CalculationStore store; + CalculationStore store(calculationBuffer,calculationBufferSize); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real); - assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); - assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); - assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); - assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store); - assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store); - assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); + assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); + assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("ln(-2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); - assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); - assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store); - assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store); - assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); + assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store); + assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar); - assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^\u00123.141593×𝐢\u0013", "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store); - assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); - assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+𝐢", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, "ℯ^\u00123.141593×𝐢\u0013", "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); } diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 3b599e827..4db851b22 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -89,16 +89,18 @@ App::App(Snapshot * snapshot) : , snapshot->lockOnConsole() #endif ), - m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey, ButtonRowController::Size::Large), + m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray, ButtonRowController::Size::Large), m_menuController(&m_listFooter, this, snapshot->scriptStore(), &m_listFooter), m_codeStackViewController(&m_modalViewController, &m_listFooter), m_variableBoxController(snapshot->scriptStore()) { + Clipboard::sharedClipboard()->enterPython(); } App::~App() { assert(!m_consoleController.inputRunLoopActive()); deinitPython(); + Clipboard::sharedClipboard()->exitPython(); } bool App::handleEvent(Ion::Events::Event event) { diff --git a/apps/code/app.h b/apps/code/app.h index 2903390a6..494f5c0f8 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -9,6 +9,7 @@ #include "script_store.h" #include "python_toolbox.h" #include "variable_box_controller.h" +#include "../shared/shared_app.h" namespace Code { @@ -21,7 +22,7 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public SharedApp::Snapshot { public: Snapshot(); App * unpack(Container * container) override; diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index b0e507e92..f1c208068 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -30,8 +30,8 @@ PythonColor = "Definiere eine RGB-Farbe" PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" +PythonColorGray = "Gray color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" PythonColorOrange = "Orange color" PythonColorPink = "Pink color" PythonColorPurple = "Purple color" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index d546a6392..cd115b9aa 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -1,7 +1,7 @@ PythonPound = "Comment" PythonPercent = "Modulo" Python1J = "Imaginary i" -PythonLF = "line feed" +PythonLF = "Line feed" PythonTab = "Tabulation" PythonAmpersand = "Bitwise and" PythonSymbolExp = "Bitwise exclusive or" @@ -30,8 +30,8 @@ PythonColor = "Define a rgb color" PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" +PythonColorGray = "Gray color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" PythonColorOrange = "Orange color" PythonColorPink = "Pink color" PythonColorPurple = "Purple color" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 6a30ad360..ee8b58f59 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -30,8 +30,8 @@ PythonColor = "Define a rgb color" PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" +PythonColorGray = "Gray color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" PythonColorOrange = "Orange color" PythonColorPink = "Pink color" PythonColorPurple = "Purple color" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 8561a0c05..333f50b8b 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -30,8 +30,8 @@ PythonColor = "Définit une couleur rvb" PythonColorBlack = "Couleur noire" PythonColorBlue = "Couleur bleue" PythonColorBrown = "Couleur marron" +PythonColorGray = "Couleur grise" PythonColorGreen = "Couleur verte" -PythonColorGrey = "Couleur grise" PythonColorOrange = "Couleur orange" PythonColorPink = "Couleur rose" PythonColorPurple = "Couleur violette" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index a1c54071a..27a7ea408 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -30,8 +30,8 @@ PythonColor = "Definisci un colore rvb" PythonColorBlack = "Colore nero" PythonColorBlue = "Colore blu" PythonColorBrown = "Colore marrone" +PythonColorGray = "Colore grigio" PythonColorGreen = "Colore verde" -PythonColorGrey = "Colore grigio" PythonColorOrange = "Colore arancione" PythonColorPink = "Colore rosa" PythonColorPurple = "Colore viola" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index bc0361ed8..b3ec42257 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -13,14 +13,14 @@ PythonAbs = "Absolute waarde" PythonAcos = "Arccosinus" PythonAcosh = "Arccosinus hyperbolicus" PythonAppend = "Voeg x toe aan het eind van je lijst" -PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" +PythonArrow = "Pijl van (x,y) naar (x+dx,y+dy)" PythonAsin = "Arcsinus" PythonAsinh = "Arcsinus hyperbolicus" PythonAtan = "Arctangens" PythonAtan2 = "Geeft atan(y/x)" PythonAtanh = "Arctangens hyperbolicus" -PythonAxis = "Set the axes to (xmin,xmax,ymin,ymax)" -PythonBar = "Draw a bar plot with x values" +PythonAxis = "Stel de assen in (xmin,xmax,ymin,ymax)" +PythonBar = "Teken staafdiagram met x-waarden" PythonBin = "Zet integer om in een binair getal" PythonCeil = "Plafond" PythonChoice = "Geeft willek. getal van de lijst" @@ -30,8 +30,8 @@ PythonColor = "Definieer een rgb kleur" PythonColorBlack = "Zwarte kleur" PythonColorBlue = "Blauwe kleur" PythonColorBrown = "Bruine kleur" +PythonColorGray = "Grijze kleur" PythonColorGreen = "Groene kleur" -PythonColorGrey = "Grijze kleur" PythonColorOrange = "Oranje kleur" PythonColorPink = "Roze kleur" PythonColorPurple = "Paarse kleur" @@ -60,15 +60,15 @@ PythonFrExp = "Mantisse en exponent van x: (m,e)" PythonGamma = "Gammafunctie" PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" PythonGetrandbits = "Integer met k willekeurige bits" -PythonGrid = "Toggle the visibility of the grid" +PythonGrid = "Verander zichtbaarheid raster" PythonHex = "Zet integer om in hexadecimaal" -PythonHist = "Draw the histogram of x" +PythonHist = "Teken het histogram van x" PythonImportCmath = "Importeer cmath module" PythonImportIon = "Importeer ion module" PythonImportKandinsky = "Importeer kandinsky module" PythonImportRandom = "Importeer random module" PythonImportMath = "Importeer math module" -PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" +PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module" PythonImportTime = "Importeer time module" PythonImportOs = "Importeer os module" PythonOsUname = " Krijg systeeminfo" @@ -84,7 +84,7 @@ PythonIonFunction = "ion module voorvoegsel" PythonIsFinite = "Controleer of x eindig is" PythonIsInfinite = "Controleer of x oneindig is" PythonIsKeyDown = "Geef True als k toets omlaag is" -PythonIsNaN = "Controleer of x geen nummer is" +PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" PythonKeyLeft = "PIJL NAAR LINKS toets" PythonKeyUp = "PIJL OMHOOG toets" @@ -146,14 +146,14 @@ PythonModf = "Fractionele en gehele delen van x" PythonMonotonic = "Waarde van een monotone klok" PythonOct = "Integer omzetten naar octaal" PythonPhase = "Fase van z in radialen" -PythonPlot = "Plot y versus x as lines" +PythonPlot = "Plot y versus x als lijnen" PythonPolar = "z in poolcoördinaten" PythonPop = "Verwijder en breng het laatste item terug" PythonPower = "x tot de macht y" PythonPrint = "Print object" PythonRadians = "Zet x om van graden naar radialen" PythonRandint = "Geeft willek. integer in [a,b]" -PythonRandom = "Een willekeurig getal in [0,1[" +PythonRandom = "Een willekeurig getal in [0,1)" PythonRandomFunction = "random module voorvoegsel" PythonRandrange = "Willek. getal in range(start, stop)" PythonRangeStartStop = "Lijst van start tot stop-1" @@ -162,10 +162,10 @@ PythonRect = "z in cartesiaanse coördinaten" PythonRemove = "Verwijder het eerste voorkomen van x" PythonReverse = "Keer de elementen van de lijst om" PythonRound = "Rond af op n cijfers" -PythonScatter = "Draw a scatter plot of y versus x" +PythonScatter = "Teken scatterplot van y versus x" PythonSeed = "Start willek. getallengenerator" PythonSetPixel = "Kleur pixel (x,y)" -PythonShow = "Display the figure" +PythonShow = "Figuur weergeven" PythonSin= "Sinus" PythonSinh = "Sinus hyperbolicus" PythonSleep = "Stel executie voor t seconden uit" @@ -174,7 +174,7 @@ PythonSqrt = "Vierkantswortel" PythonSum = "Sommeer de items van een lijst" PythonTan = "Tangens" PythonTanh = "Tangens hyperbolicus" -PythonText = "Display a text at (x,y) coordinates" +PythonText = "Geef tekst weer op coördinaten (x,y)" PythonTimeFunction = "time module voorvoegsel" PythonTrunc = "x afgeknot tot een integer" PythonTurtleBackward = "Ga achterwaarts met x pixels" @@ -199,7 +199,7 @@ PythonTurtleSetposition = "Plaats de schildpad" PythonTurtleShowturtle = "Laat de schildpad zien" PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10" PythonTurtleWrite = "Display a text" -PythonUniform = "Zwevendekommagetal in [a,b]" +PythonUniform = "Decimaal getal in [a,b]" PythonImportTime = "Import time module" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Wait for n second" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index b0decb0ab..de29d19d4 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -30,8 +30,8 @@ PythonColor = "Define uma cor rgb" PythonColorBlack = "Cor preta" PythonColorBlue = "Cor azul" PythonColorBrown = "Cor castanha" +PythonColorGray = "Cor cinzenta" PythonColorGreen = "Cor verde" -PythonColorGrey = "Cor cinzenta" PythonColorOrange = "Cor laranja" PythonColorPink = "Cor rosa" PythonColorPurple = "Cor roxa" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 768a4d7d2..001c58349 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -32,8 +32,8 @@ PythonCommandColor = "color(r,g,b)" PythonCommandColorBlack = "'black'" PythonCommandColorBlue = "'blue'" PythonCommandColorBrown = "'brown'" +PythonCommandColorGray = "'gray'" PythonCommandColorGreen = "'green'" -PythonCommandColorGrey = "'grey'" PythonCommandColorOrange = "'orange'" PythonCommandColorPink = "'pink'" PythonCommandColorPurple = "'purple'" diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 0e761d9ae..ffc2a7768 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -23,7 +23,7 @@ void EditorController::setScript(Script script, int scriptIndex) { m_script = script; m_scriptIndex = scriptIndex; - /* We edit the script direclty in the storage buffer. We thus put all the + /* We edit the script directly in the storage buffer. We thus put all the * storage available space at the end of the current edited script and we set * its size. * @@ -36,8 +36,8 @@ void EditorController::setScript(Script script, int scriptIndex) { * * */ - size_t newScriptSize = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script); - m_editorView.setText(const_cast(m_script.content()), newScriptSize - Script::StatusSize()); + Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script); + m_editorView.setText(const_cast(m_script.content()), m_script.contentSize()); } void EditorController::willExitApp() { diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 8c4832c8c..2541612d5 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -67,11 +67,19 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate firstLine = m_offset / glyphSize.height(); KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height(); - char lineNumber[4]; + char lineNumber[k_lineNumberCharLength]; int numberOfLines = bounds().height() / glyphSize.height() + 1; for (int i=0; i= 10) { + line.serialize(lineNumber, k_lineNumberCharLength); + } else { + // Add a leading "0" + lineNumber[0] = '0'; + line.serialize(lineNumber + 1, k_lineNumberCharLength - 1); + } KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width(); ctx->drawString( lineNumber, diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index ab3038c94..547f7340b 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -42,6 +42,7 @@ private: KDSize minimalSizeForOptimalDisplay() const override; private: static constexpr KDCoordinate k_margin = 2; + static constexpr int k_lineNumberCharLength = 3; const KDFont * m_font; KDCoordinate m_offset; }; diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 5634dbf67..18250e77f 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -1,44 +1,20 @@ #include "helpers.h" -#include -#include -#include +#include namespace Code { namespace Helpers { -class EventTextPair { -public: - constexpr EventTextPair(Ion::Events::Event event, const char * text) : m_event(event), m_text(text) {} - Ion::Events::Event event() const { return m_event; } - const char * text() const { return m_text; } -private: - const Ion::Events::Event m_event; - const char * m_text; -}; - -static_assert('\x11' == UCodePointEmpty, "Unicode error"); -static constexpr EventTextPair sEventTextMap[] = { - EventTextPair(Ion::Events::XNT, "x"), - EventTextPair(Ion::Events::Exp, "exp(\x11)"), - EventTextPair(Ion::Events::Ln, "log(\x11)"), - EventTextPair(Ion::Events::Log, "log10(\x11)"), - EventTextPair(Ion::Events::Imaginary, "1j"), - EventTextPair(Ion::Events::Power, "**"), - EventTextPair(Ion::Events::Pi, "pi"), - EventTextPair(Ion::Events::Sqrt, "sqrt(\x11)"), - EventTextPair(Ion::Events::Square, "**2"), - EventTextPair(Ion::Events::Multiplication, "*"), - EventTextPair(Ion::Events::EE, "e"), -}; - const char * PythonTextForEvent(Ion::Events::Event event) { - for (size_t i=0; imenuController()->editedScriptIndex(); m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning); - if (addAutocompletionTextAtIndex(0)) { - m_contentView.setAutocompleting(true); - } + addAutocompletionTextAtIndex(0); } bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) { @@ -467,6 +465,7 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn return false; } autocompletionLocation += textToInsertLength; + m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation); } @@ -479,8 +478,9 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength)) { m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); + return true; } - return true; + return (textToInsertLength > 0); } void PythonTextArea::cycleAutocompletion(bool downwards) { diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index fa8f72f24..2a0ba410d 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -131,7 +131,7 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false) }; const ToolboxMessageTree TurtleModuleChildren[] = { @@ -168,7 +168,7 @@ const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false) }; const ToolboxMessageTree RandomModuleChildren[] = { @@ -341,8 +341,8 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGrid, I18n::Message::PythonGrid), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex), diff --git a/apps/code/script.h b/apps/code/script.h index 9d2df78c6..6f7df9cda 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -49,6 +49,7 @@ public: bool autoImportationStatus() const; void toggleAutoimportationStatus(); const char * content() const; + size_t contentSize() { return value().size - k_statusSize; } /* Fetched status */ bool fetchedFromConsole() const; diff --git a/apps/code/script_node_cell.cpp b/apps/code/script_node_cell.cpp index 1c8580a44..0dc8675c3 100644 --- a/apps/code/script_node_cell.cpp +++ b/apps/code/script_node_cell.cpp @@ -13,7 +13,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons // If it exists, draw the description name. const char * descriptionName = m_scriptNode->description(); if (descriptionName != nullptr) { - ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GreyDark, backgroundColor); + ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GrayDark, backgroundColor); } // Draw the node name diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index 50fbd1632..d7b219d87 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -1,6 +1,6 @@ Functions = "Funzioni" Catalog = "Catalogo" Modules = "Moduli" -LoopsAndTests = "Loops e test" +LoopsAndTests = "Cicli e test" Files = "Files" Exceptions = "Exceptions" diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n index 84b6e1f4c..849bd76a6 100644 --- a/apps/code/toolbox.nl.i18n +++ b/apps/code/toolbox.nl.i18n @@ -1,6 +1,6 @@ Functions = "Functies" Catalog = "Catalogus" Modules = "Modules" -LoopsAndTests = "Loops and tests" +LoopsAndTests = "Herhalingen en testen" Files = "Files" Exceptions = "Exceptions" diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 9881b213d..279c9c57c 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -45,7 +45,7 @@ VariableBoxController::VariableBoxController(ScriptStore * scriptStore) : { for (int i = 0; i < k_scriptOriginsCount; i++) { m_subtitleCells[i].setBackgroundColor(Palette::WallScreen); - m_subtitleCells[i].setTextColor(Palette::BlueishGrey); + m_subtitleCells[i].setTextColor(Palette::SecondaryText); } } diff --git a/apps/country_preferences.csv b/apps/country_preferences.csv new file mode 100644 index 000000000..e900d55d7 --- /dev/null +++ b/apps/country_preferences.csv @@ -0,0 +1,11 @@ +CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat,CountryPreferences::HomeAppsLayout +WW,StandardOnly,MedianOfSublist,Metric,Default +CA,StandardOnly,MedianOfSublist,Metric,Default +DE,StandardOnly,MedianOfSublist,Metric,Default +ES,StandardOnly,MedianOfSublist,Metric,Default +FR,StandardOnly,CumulatedFrequency,Metric,Default +GB,StandardOnly,MedianOfSublist,Metric,Default +IT,StandardOnly,CumulatedFrequency,Metric,Default +NL,All,MedianOfSublist,Metric,Default +PT,StandardOnly,MedianOfSublist,Metric,Default +US,StandardOnly,MedianOfSublist,Imperial,HidePython diff --git a/apps/country_preferences.h b/apps/country_preferences.h new file mode 100644 index 000000000..2ec6bb7ee --- /dev/null +++ b/apps/country_preferences.h @@ -0,0 +1,42 @@ +#ifndef COUNTRY_PREFERENCES_H +#define COUNTRY_PREFERENCES_H + +#include + +class CountryPreferences { +public: + enum class AvailableExamModes : uint8_t { + StandardOnly, + All + }; + + enum class MethodForQuartiles : uint8_t { + MedianOfSublist, + CumulatedFrequency + }; + + enum class HomeAppsLayout : uint8_t { + Default, + HidePython, + }; + + constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat, HomeAppsLayout homeAppsLayout) : + m_availableExamModes(availableExamModes), + m_methodForQuartiles(methodForQuartiles), + m_unitFormat(unitFormat), + m_homeAppsLayout(homeAppsLayout) + {} + + constexpr AvailableExamModes availableExamModes() const { return m_availableExamModes; } + constexpr MethodForQuartiles methodForQuartiles() const { return m_methodForQuartiles; } + constexpr Poincare::Preferences::UnitFormat unitFormat() const { return m_unitFormat; } + constexpr HomeAppsLayout homeAppsLayout() const { return m_homeAppsLayout; } + +private: + const AvailableExamModes m_availableExamModes; + const MethodForQuartiles m_methodForQuartiles; + const Poincare::Preferences::UnitFormat m_unitFormat; + const HomeAppsLayout m_homeAppsLayout; +}; + +#endif diff --git a/apps/exam_mode_configuration.h b/apps/exam_mode_configuration.h index 7ec6e4201..600f312d2 100644 --- a/apps/exam_mode_configuration.h +++ b/apps/exam_mode_configuration.h @@ -18,7 +18,7 @@ I18n::Message examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, // Exam mode behaviour KDColor examModeColor(GlobalPreferences::ExamMode mode); -bool appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode); +bool appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode); bool exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode); } diff --git a/apps/exam_mode_configuration_non_official.cpp b/apps/exam_mode_configuration_non_official.cpp index b535e5037..7379be6cf 100644 --- a/apps/exam_mode_configuration_non_official.cpp +++ b/apps/exam_mode_configuration_non_official.cpp @@ -1,16 +1,13 @@ #include "exam_mode_configuration.h" -using namespace Poincare; - -constexpr Shared::SettingsMessageTree s_examModeMode[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeModeStandard), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSym), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSymNoText)}; -constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeMode, s_examModeMode), Shared::SettingsMessageTree(I18n::Message::ActivateExamMode)}; +constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::Default)}; int ExamModeConfiguration::numberOfAvailableExamMode() { - return 2; + return 1; } GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) { - return (s_modelExamChildren[index].label() == I18n::Message::ExamModeModeStandard) ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::NoSym; + return GlobalPreferences::ExamMode::Standard; } I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) { @@ -22,23 +19,20 @@ I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPref I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default}; return warnings[line]; } - assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText); + assert(mode == GlobalPreferences::ExamMode::Standard); I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3}; return warnings[line]; } KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) { - assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText); + assert(mode == GlobalPreferences::ExamMode::Standard); return KDColorRed; } -bool ExamModeConfiguration::appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode) { - if (mode == GlobalPreferences::ExamMode::NoSymNoText) { - return appExaminationLevel == App::Descriptor::ExaminationLevel::Basic; - } +bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) { return false; } bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) { - return mode == GlobalPreferences::ExamMode::NoSymNoText ? true : false; -} + return false; +} \ No newline at end of file diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp new file mode 100644 index 000000000..5cdc1fb8f --- /dev/null +++ b/apps/exam_mode_configuration_official.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 +// Caution: Dutch exam mode is subject to a compliance certification by a government agency. Distribution of a non-certified Dutch exam mode is illegal. + +#include "exam_mode_configuration.h" + +constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; + +int ExamModeConfiguration::numberOfAvailableExamMode() { + if (GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::StandardOnly + || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) + { + return 1; + } + assert(GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::All); + return 2; +} + +GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) { + return index == 0 ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::Dutch; +} + +I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) { + return index == 0 ? I18n::Message::ActivateExamMode : I18n::Message::ActivateDutchExamMode; +} + +I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, int line) { + if (mode == GlobalPreferences::ExamMode::Off) { + I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default}; + return warnings[line]; + } else if (mode == GlobalPreferences::ExamMode::Standard) { + I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3}; + return warnings[line]; + } + assert(mode == GlobalPreferences::ExamMode::Dutch); + I18n::Message warnings[] = {I18n::Message::ActiveDutchExamModeMessage1, I18n::Message::ActiveDutchExamModeMessage2, I18n::Message::ActiveDutchExamModeMessage3}; + return warnings[line]; +} + +KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) { + /* The Dutch exam mode LED is supposed to be orange but we can only make + * blink "pure" colors: with RGB leds on or off (as the PWM is used for + * blinking). The closest "pure" color is Yellow. Moreover, Orange LED is + * already used when the battery is charging. Using yellow, we can assert + * that the yellow LED only means that Dutch exam mode is on and avoid + * confusing states when the battery is charging and states when the Dutch + * exam mode is on. */ + return mode == GlobalPreferences::ExamMode::Dutch ? KDColorYellow : KDColorRed; +} + +bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) { + return appName == I18n::Message::CodeApp && mode == GlobalPreferences::ExamMode::Dutch; +} + +bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) { + return mode == GlobalPreferences::ExamMode::Dutch; +} diff --git a/apps/exam_pop_up_controller.cpp b/apps/exam_pop_up_controller.cpp index 82111dbd6..75a998565 100644 --- a/apps/exam_pop_up_controller.cpp +++ b/apps/exam_pop_up_controller.cpp @@ -1,16 +1,32 @@ #include "exam_pop_up_controller.h" #include "apps_container.h" #include "exam_mode_configuration.h" -#include -#include "global_preferences.h" #include #include using namespace Poincare; ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) : - ViewController(nullptr), - m_contentView(this), + PopUpController( + k_numberOfLines, + Invocation( + [](void * context, void * sender) { + ExamPopUpController * controller = (ExamPopUpController *)context; + GlobalPreferences::ExamMode mode = controller->targetExamMode(); + assert(mode != GlobalPreferences::ExamMode::Unknown); + GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode); + AppsContainer * container = AppsContainer::sharedAppsContainer(); + if (mode == GlobalPreferences::ExamMode::Off) { + Ion::LED::setColor(KDColorBlack); + Ion::LED::updateColorWithPlugAndCharge(); + } else { + container->activateExamMode(mode); + } + container->refreshPreferences(); + Container::activeApp()->dismissModalViewController(); + return true; + }, this) + ), m_targetExamMode(GlobalPreferences::ExamMode::Unknown), m_delegate(delegate) { @@ -18,11 +34,9 @@ ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) void ExamPopUpController::setTargetExamMode(GlobalPreferences::ExamMode mode) { m_targetExamMode = mode; - m_contentView.setMessagesForExamMode(mode); -} - -View * ExamPopUpController::view() { - return &m_contentView; + for (int i = 0; i < k_numberOfLines; i++) { + m_contentView.setMessage(i, ExamModeConfiguration::examModeActivationWarningMessage(mode, i)); + } } void ExamPopUpController::viewDidDisappear() { @@ -30,103 +44,3 @@ void ExamPopUpController::viewDidDisappear() { m_delegate->examDeactivatingPopUpIsDismissed(); } } - -void ExamPopUpController::didBecomeFirstResponder() { - m_contentView.setSelectedButton(0); -} - -bool ExamPopUpController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) { - m_contentView.setSelectedButton(0); - return true; - } - if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) { - m_contentView.setSelectedButton(1); - return true; - } - return false; -} - -ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : - m_cancelButton(parentResponder, I18n::Message::Cancel, Invocation([](void * context, void * sender) { - Container::activeApp()->dismissModalViewController(); - return true; - }, parentResponder), KDFont::SmallFont), - m_okButton(parentResponder, I18n::Message::Ok, Invocation([](void * context, void * sender) { - ExamPopUpController * controller = (ExamPopUpController *)context; - GlobalPreferences::ExamMode mode = controller->targetExamMode(); - assert(mode != GlobalPreferences::ExamMode::Unknown); - GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode); - AppsContainer * container = AppsContainer::sharedAppsContainer(); - if (mode == GlobalPreferences::ExamMode::Off) { - Ion::LED::setColor(KDColorBlack); - Ion::LED::updateColorWithPlugAndCharge(); - } else { - container->activateExamMode(mode); - } - container->refreshPreferences(); - Container::activeApp()->dismissModalViewController(); - return true; - }, parentResponder), KDFont::SmallFont), - m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextViews{} -{ - for (int i = 0; i < k_maxNumberOfLines; i++) { - m_messageTextViews[i].setFont(KDFont::SmallFont); - m_messageTextViews[i].setAlignment(0.5f, 0.5f); - m_messageTextViews[i].setBackgroundColor(KDColorBlack); - m_messageTextViews[i].setTextColor(KDColorWhite); - } -} - -void ExamPopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), KDColorBlack); -} - -void ExamPopUpController::ContentView::setSelectedButton(int selectedButton) { - m_cancelButton.setHighlighted(selectedButton == 0); - m_okButton.setHighlighted(selectedButton == 1); - Container::activeApp()->setFirstResponder(selectedButton == 0 ? &m_cancelButton : &m_okButton); -} - -int ExamPopUpController::ContentView::selectedButton() { - if (m_cancelButton.isHighlighted()) { - return 0; - } - return 1; -} - -void ExamPopUpController::ContentView::setMessagesForExamMode(GlobalPreferences::ExamMode mode) { - for (int i = 0; i < k_maxNumberOfLines; i++) { - m_messageTextViews[i].setMessage(ExamModeConfiguration::examModeActivationWarningMessage(mode, i)); - } -} - -int ExamPopUpController::ContentView::numberOfSubviews() const { - return 6; -} - -View * ExamPopUpController::ContentView::subviewAtIndex(int index) { - switch (index) { - case 0: - return &m_warningTextView; - case 4: - return &m_cancelButton; - case 5: - return &m_okButton; - default: - return &m_messageTextViews[index-1]; - } -} - -void ExamPopUpController::ContentView::layoutSubviews(bool force) { - KDCoordinate height = bounds().height(); - KDCoordinate width = bounds().width(); - KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force); - for (int i = 0; i < k_maxNumberOfLines; i++) { - m_messageTextViews[i].setFrame(KDRect(0, k_topMargin+k_paragraphHeight+(i+1)*textHeight, width, textHeight), force); - } - m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); - m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); -} diff --git a/apps/exam_pop_up_controller.h b/apps/exam_pop_up_controller.h index c335126f7..192f38f47 100644 --- a/apps/exam_pop_up_controller.h +++ b/apps/exam_pop_up_controller.h @@ -1,53 +1,20 @@ #ifndef APPS_EXAM_POP_UP_CONTROLLER_H #define APPS_EXAM_POP_UP_CONTROLLER_H -#include +#include #include "exam_pop_up_controller_delegate.h" #include "global_preferences.h" -class HighContrastButton : public Button { -public: - using Button::Button; - KDColor highlightedBackgroundColor() const override { return Palette::ButtonBackgroundSelectedHighContrast; } -}; - -class ExamPopUpController : public ViewController { +class ExamPopUpController : public PopUpController { public: ExamPopUpController(ExamPopUpControllerDelegate * delegate); void setTargetExamMode(GlobalPreferences::ExamMode mode); GlobalPreferences::ExamMode targetExamMode() const { return m_targetExamMode; } - // View Controller - View * view() override; void viewDidDisappear() override; - // Responder - void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; private: - class ContentView : public View { - public: - ContentView(Responder * parentResponder); - void drawRect(KDContext * ctx, KDRect rect) const override; - void setSelectedButton(int selectedButton); - int selectedButton(); - void setMessagesForExamMode(GlobalPreferences::ExamMode mode); - private: - constexpr static KDCoordinate k_buttonMargin = 10; - constexpr static KDCoordinate k_buttonHeight = 20; - constexpr static KDCoordinate k_topMargin = 12; - constexpr static KDCoordinate k_paragraphHeight = 20; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews(bool force = false) override; - HighContrastButton m_cancelButton; - HighContrastButton m_okButton; - MessageTextView m_warningTextView; - constexpr static int k_maxNumberOfLines = 3; - MessageTextView m_messageTextViews[k_maxNumberOfLines]; - }; - ContentView m_contentView; + constexpr static int k_numberOfLines = 3; GlobalPreferences::ExamMode m_targetExamMode; ExamPopUpControllerDelegate * m_delegate; }; #endif - diff --git a/apps/global_preferences.h b/apps/global_preferences.h index d0038910b..4367c20f7 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -16,6 +16,12 @@ public: static GlobalPreferences * sharedGlobalPreferences(); I18n::Language language() const { return m_language; } void setLanguage(I18n::Language language) { m_language = language; } + I18n::Country country() const { return m_country; } + void setCountry(I18n::Country country) { m_country = country; } + CountryPreferences::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast(m_country)].availableExamModes(); } + CountryPreferences::MethodForQuartiles methodForQuartiles() const { return I18n::CountryPreferencesArray[static_cast(m_country)].methodForQuartiles(); } + Poincare::Preferences::UnitFormat unitFormat() const { return I18n::CountryPreferencesArray[static_cast(m_country)].unitFormat(); } + CountryPreferences::HomeAppsLayout homeAppsLayout() const { return I18n::CountryPreferencesArray[static_cast(m_country)].homeAppsLayout(); } bool isInExamMode() const { return (int8_t)examMode() > 0; } bool isInExamModeSymbolic() const { return !((int8_t)examMode() > 1); } ExamMode examMode() const; @@ -30,15 +36,18 @@ public: void setFont(const KDFont * font) { m_font = font; } constexpr static int NumberOfBrightnessStates = 15; private: - static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have be an error when processing an empty EPSILON_I18N flag + static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have been an error when processing an empty EPSILON_I18N flag + static_assert(I18n::NumberOfCountries > 0, "I18n::NumberOfCountries is not superior to 0"); // There should already have been an error when processing an empty EPSILON_COUNTRIES flag GlobalPreferences() : m_language((I18n::Language)0), + m_country((I18n::Country)0), m_examMode(ExamMode::Unknown), m_tempExamMode(ExamMode::Standard), m_showPopUp(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), m_font(KDFont::LargeFont) {} I18n::Language m_language; + I18n::Country m_country; static_assert((int8_t)GlobalPreferences::ExamMode::Off == 0, "GlobalPreferences::isInExamMode() is not right"); static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right"); mutable ExamMode m_examMode; diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 4f440fea8..cc8bc34ad 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -1,9 +1,12 @@ apps += Graph::App app_headers += apps/graph/app.h +app_graph_test_src = $(addprefix apps/graph/,\ + continuous_function_store.cpp \ +) + app_graph_src = $(addprefix apps/graph/,\ app.cpp \ - continuous_function_store.cpp \ graph/banner_view.cpp \ graph/calculation_graph_controller.cpp \ graph/calculation_parameter_controller.cpp \ @@ -31,8 +34,15 @@ app_graph_src = $(addprefix apps/graph/,\ values/values_controller.cpp \ ) +app_graph_src += $(app_graph_test_src) apps_src += $(app_graph_src) i18n_files += $(call i18n_without_universal_for,graph/base) +tests_src += $(addprefix apps/graph/test/,\ + caching.cpp \ + helper.cpp \ + ranges.cpp \ +) + $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png)) diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index a856c2c1a..68a4b4a9e 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -55,10 +55,10 @@ void App::Snapshot::tidy() { App::App(Snapshot * snapshot) : FunctionApp(snapshot, &m_inputViewController), m_listController(&m_listFooter, &m_listHeader, &m_listFooter, this), - m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey), + m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray), m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController), m_listStackViewController(&m_tabViewController, &m_listHeader), - m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->previousModelsVersions(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader), + m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n index a809429e5..21d52275d 100644 --- a/apps/graph/base.nl.i18n +++ b/apps/graph/base.nl.i18n @@ -12,22 +12,22 @@ IntervalTheta = "θ interval" IntervalX = "x interval" FunctionDomain = "Plotbereik" FunctionColor = "Functiekleur" -NoFunction = "Geen functie" -NoActivatedFunction = "Geen functie is ingeschakelt" -PlotOptions = "Plot opties" +NoFunction = "Geen functie gedefinieerd" +NoActivatedFunction = "Geen functie ingeschakeld" +PlotOptions = "Plotopties" Compute = "Bereken" Zeros = "Nulpunten" -Tangent = "Tangens" +Tangent = "Raaklijn" Intersection = "Snijpunt" -Preimage = "Inverse beeld" +Preimage = "Origineel" SelectLowerBound = "Selecteer ondergrens " SelectUpperBound = "Selecteer bovengrens " NoMaximumFound = "Geen maximum gevonden" NoMinimumFound = "Geen minimum gevonden" NoZeroFound = "Geen nulpunt gevonden" NoIntersectionFound = "Geen snijpunt gevonden" -NoPreimageFound = "Geen inverse beeld gevonden" +NoPreimageFound = "Geen origineel gevonden" DerivativeFunctionColumn = "Afgeleide functie kolom" HideDerivativeColumn = "Verberg de afgeleide functie" AllowedCharactersAZaz09 = "Toegestane tekens: A-Z, a-z, 0-9, _" -ReservedName = "Voorbehouden naam" +ReservedName = "Gereserveerde naam" diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h index fce291049..1789bc682 100644 --- a/apps/graph/continuous_function_store.h +++ b/apps/graph/continuous_function_store.h @@ -16,8 +16,9 @@ public: return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); } Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } -private: + Shared::ContinuousFunctionCache * cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? m_functionCaches + i : nullptr; } Ion::Storage::Record::ErrorStatus addEmptyModel() override; +private: const char * modelExtension() const override { return Ion::Storage::funcExtension; } Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override; Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override; @@ -26,6 +27,8 @@ private: return isFunctionActive(model, context) && plotType == static_cast(model)->plotType(); } mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; + mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; + }; } diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index a68dc5156..3580395a5 100644 --- a/apps/graph/graph/calculation_graph_controller.cpp +++ b/apps/graph/graph/calculation_graph_controller.cpp @@ -30,7 +30,7 @@ void CalculationGraphController::viewWillAppear() { m_isActive = true; assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); m_cursor->moveTo(pointOfInterest.x1(), pointOfInterest.x1(), pointOfInterest.x2()); - m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews); reloadBannerView(); } @@ -64,7 +64,7 @@ bool CalculationGraphController::handleEnter() { return true; } -bool CalculationGraphController::moveCursorHorizontally(int direction, bool fast) { +bool CalculationGraphController::moveCursorHorizontally(int direction, int scrollspeed) { if (!m_isActive) { return false; } diff --git a/apps/graph/graph/calculation_graph_controller.h b/apps/graph/graph/calculation_graph_controller.h index 14f5cc797..4bf7a9fa5 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -31,7 +31,7 @@ protected: bool m_isActive; private: bool handleEnter() override; - bool moveCursorHorizontally(int direction, bool fast = false) override; + bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } Shared::CurveView * curveView() override { return m_graphView; } }; diff --git a/apps/graph/graph/extremum_graph_controller.h b/apps/graph/graph/extremum_graph_controller.h index dc93bfc96..cadfbeb68 100644 --- a/apps/graph/graph/extremum_graph_controller.h +++ b/apps/graph/graph/extremum_graph_controller.h @@ -12,6 +12,9 @@ public: TELEMETRY_ID("Minimum"); private: Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + // Prevent horizontal panning to preserve search interval + float cursorRightMarginRatio() override { return 0.0f; } + float cursorLeftMarginRatio() override { return 0.0f; } }; class MaximumGraphController : public CalculationGraphController { @@ -21,6 +24,9 @@ public: TELEMETRY_ID("Maximum"); private: Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + // Prevent horizontal panning to preserve search interval + float cursorRightMarginRatio() override { return 0.0f; } + float cursorLeftMarginRatio() override { return 0.0f; } }; } diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index cc9dbb93a..a6de84268 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -7,8 +7,8 @@ using namespace Shared; namespace Graph { -GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : - FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, previousModelsVersions, rangeVersion, angleUnitVersion), +GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) : + FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion), m_bannerView(this, inputEventHandlerDelegate, this), m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(curveViewRange), @@ -35,118 +35,10 @@ void GraphController::viewWillAppear() { selectFunctionWithCursor(indexFunctionSelectedByCursor()); } -bool GraphController::defautRangeIsNormalized() const { +bool GraphController::defaultRangeIsNormalized() const { return functionStore()->displaysNonCartesianFunctions(); } -void GraphController::interestingFunctionRange(ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - const int balancedBound = std::floor((tMax-tMin)/2/step); - for (int j = -balancedBound; j <= balancedBound ; j++) { - float t = (tMin+tMax)/2 + step * j; - Coordinate2D xy = f->evaluateXYAtParameter(t, context); - float x = xy.x1(); - float y = xy.x2(); - if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) { - *xm = std::min(*xm, x); - *xM = std::max(*xM, x); - *ym = std::min(*ym, y); - *yM = std::max(*yM, y); - } - } -} - -void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const { - float resultxMin = FLT_MAX; - float resultxMax = -FLT_MAX; - float resultyMin = FLT_MAX; - float resultyMax = -FLT_MAX; - assert(functionStore()->numberOfActiveFunctions() > 0); - int functionsCount = 0; - if (functionStore()->displaysNonCartesianFunctions(&functionsCount)) { - for (int i = 0; i < functionsCount; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - if (f->plotType() == ContinuousFunction::PlotType::Cartesian) { - continue; - } - /* Scan x-range from the middle to the extrema in order to get balanced - * y-range for even functions (y = 1/x). */ - double tMin = f->tMin(); - double tMax = f->tMax(); - assert(!std::isnan(tMin)); - assert(!std::isnan(tMax)); - assert(!std::isnan(f->rangeStep())); - interestingFunctionRange(f, tMin, tMax, f->rangeStep(), &resultxMin, &resultxMax, &resultyMin, &resultyMax); - } - if (resultxMin > resultxMax) { - resultxMin = - Range1D::k_default; - resultxMax = Range1D::k_default; - } - } else { - resultxMin = const_cast(this)->interactiveCurveViewRange()->xMin(); - resultxMax = const_cast(this)->interactiveCurveViewRange()->xMax(); - } - /* In practice, a step smaller than a pixel's width is needed for sampling - * the values of a function. Otherwise some relevant extremal values may be - * missed. */ - for (int i = 0; i < functionsCount; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - if (f->plotType() != ContinuousFunction::PlotType::Cartesian) { - continue; - } - /* Scan x-range from the middle to the extrema in order to get balanced - * y-range for even functions (y = 1/x). */ - assert(!std::isnan(f->tMin())); - assert(!std::isnan(f->tMax())); - const double tMin = std::max(f->tMin(), resultxMin); - const double tMax = std::min(f->tMax(), resultxMax); - const double step = (tMax - tMin) / (2.0 * (m_view.bounds().width() - 1.0)); - interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax); - } - if (resultyMin > resultyMax) { - resultyMin = - Range1D::k_default; - resultyMax = Range1D::k_default; - } - - *xm = resultxMin; - *xM = resultxMax; - *ym = resultyMin; - *yM = resultyMax; -} - -float GraphController::interestingXHalfRange() const { - float characteristicRange = 0.0f; - Poincare::Context * context = textFieldDelegateApp()->localContext(); - ContinuousFunctionStore * store = functionStore(); - int nbActiveFunctions = store->numberOfActiveFunctions(); - double tMin = INFINITY; - double tMax = -INFINITY; - for (int i = 0; i < nbActiveFunctions; i++) { - ExpiringPointer f = store->modelForRecord(store->activeRecordAtIndex(i)); - float fRange = f->expressionReduced(context).characteristicXRange(context, Poincare::Preferences::sharedPreferences()->angleUnit()); - if (!std::isnan(fRange) && !std::isinf(fRange)) { - characteristicRange = std::max(fRange, characteristicRange); - } - // Compute the combined range of the functions - assert(f->plotType() == ContinuousFunction::PlotType::Cartesian); // So that tMin tMax represents xMin xMax - tMin = std::min(tMin, f->tMin()); - tMax = std::max(tMax, f->tMax()); - } - constexpr float rangeMultiplicator = 1.6f; - if (characteristicRange > 0.0f ) { - return rangeMultiplicator * characteristicRange; - } - float defaultXHalfRange = InteractiveCurveViewRangeDelegate::interestingXHalfRange(); - assert(tMin <= tMax); - if (tMin >= -defaultXHalfRange && tMax <= defaultXHalfRange) { - /* If the combined Range of the functions is smaller than the default range, - * use it. */ - float f = rangeMultiplicator * (float)std::max(std::fabs(tMin), std::fabs(tMax)); - return (std::isnan(f) || std::isinf(f)) ? defaultXHalfRange : f; - } - return defaultXHalfRange; -} - void GraphController::selectFunctionWithCursor(int functionIndex) { FunctionGraphController::selectFunctionWithCursor(functionIndex); ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex)); @@ -165,9 +57,9 @@ void GraphController::reloadBannerView() { reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record); } -bool GraphController::moveCursorHorizontally(int direction, bool fast) { +bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, fast); + return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, scrollSpeed); } int GraphController::nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const { @@ -187,10 +79,6 @@ double GraphController::defaultCursorT(Ion::Storage::Record record) { return function->tMin(); } -bool GraphController::shouldSetDefaultOnModelChange() const { - return functionStore()->displaysNonCartesianFunctions(); -} - void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) { if (functionsCount == 1) { return; diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 84240fcd3..433ab9ebe 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -15,28 +15,25 @@ namespace Graph { class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); + GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } - float interestingXHalfRange() const override; - void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override; private: int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; BannerView * bannerView() override { return &m_bannerView; } void reloadBannerView() override; - bool moveCursorHorizontally(int direction, bool fast = false) override; + bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override; double defaultCursorT(Ion::Storage::Record record) override; Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } GraphView * functionGraphView() override { return &m_view; } CurveParameterController * curveParameterController() override { return &m_curveParameterController; } ContinuousFunctionStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } - bool defautRangeIsNormalized() const override; + bool defaultRangeIsNormalized() const override; void interestingFunctionRange(Shared::ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const; - bool shouldSetDefaultOnModelChange() const override; void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override; Shared::RoundCursorView m_cursorView; diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index ad42ac727..249ef8bd1 100644 --- a/apps/graph/graph/graph_controller_helper.cpp +++ b/apps/graph/graph/graph_controller_helper.cpp @@ -10,7 +10,7 @@ using namespace Poincare; namespace Graph { -bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast) { +bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed) { ExpiringPointer function = App::app()->functionStore()->modelForRecord(record); double tCursorPosition = cursor->t(); double t = tCursorPosition; @@ -27,11 +27,11 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer double dir = (direction > 0 ? 1.0 : -1.0); double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor; - if (fast) { - // TODO Navigate quicker the longer the user presses? (slow start) - step *= 5.0; - } - t += dir * step; + t += dir * step * scrollSpeed; + + // If possible, round t so that f(x) matches f evaluated at displayed x + t = FunctionBannerDelegate::getValueDisplayedOnBanner(t, App::app()->localContext(), 0.05 * step, true); + t = std::max(tMin, std::min(tMax, t)); Coordinate2D xy = function->evaluateXYAtParameter(t, App::app()->localContext()); cursor->moveTo(t, xy.x1(), xy.x2()); diff --git a/apps/graph/graph/graph_controller_helper.h b/apps/graph/graph/graph_controller_helper.h index dcc6daf02..b0e131497 100644 --- a/apps/graph/graph/graph_controller_helper.h +++ b/apps/graph/graph/graph_controller_helper.h @@ -11,7 +11,7 @@ class App; class GraphControllerHelper { protected: - bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast = false); + bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed = 1); void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record); virtual BannerView * bannerView() = 0; private: diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 4d5b3c5b7..9d34bbf4d 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -1,6 +1,7 @@ #include "graph_view.h" #include "../app.h" #include +#include using namespace Shared; @@ -27,7 +28,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { const int activeFunctionsCount = functionStore->numberOfActiveFunctions(); for (int i = 0; i < activeFunctionsCount ; i++) { Ion::Storage::Record record = functionStore->activeRecordAtIndex(i); - ExpiringPointer f = functionStore->modelForRecord(record);; + ExpiringPointer f = functionStore->modelForRecord(record); + ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i); Shared::ContinuousFunction::PlotType type = f->plotType(); Poincare::Expression e = f->expressionReduced(context()); if (e.isUndefined() || ( @@ -38,21 +40,24 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { } float tmin = f->tMin(); float tmax = f->tMax(); - /* The step is a fraction of tmax-tmin. We will evaluate the function at - * every step and if the consecutive dots are close enough, we won't - * evaluate any more dot within the step. We pick a very strange fraction - * denominator to avoid evaluating a periodic function periodically. For - * example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ) - * defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0 - * which would lead to no curve at all. With 10.0938275501223, the - * problematic functions are the functions whose period is proportionned to - * 10.0938275501223 which are hopefully rare enough. - * TODO: The drawCurve algorithm should use the derivative function to know - * how fast the function moves... */ - float tstep = (tmax-tmin)/10.0938275501223f; - // Cartesian + float tCacheMin, tCacheStep, tStepNonCartesian; + if (type == ContinuousFunction::PlotType::Cartesian) { + float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + /* Here, tCacheMin can depend on rect (and change as the user move) + * because cache can be panned for cartesian curves, instead of being + * entirely invalidated. */ + tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft); + tCacheStep = pixelWidth(); + } else { + tCacheMin = tmin; + // Compute tCacheStep and tStepNonCartesian + ContinuousFunctionCache::ComputeNonCartesianSteps(&tStepNonCartesian, &tCacheStep, tmax, tmin); + } + ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); + if (type == Shared::ContinuousFunction::PlotType::Cartesian) { + // Cartesian drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; @@ -72,18 +77,22 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right()); drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GraphTangent, false); } - continue; + } else if (type == Shared::ContinuousFunction::PlotType::Polar) { + // Polar + drawPolarCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), false, f->color()); + } else { + // Parametric + assert(type == Shared::ContinuousFunction::PlotType::Parametric); + drawCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), false, f->color()); } - - // Polar or parametric - assert( - type == Shared::ContinuousFunction::PlotType::Polar || - type == Shared::ContinuousFunction::PlotType::Parametric); - drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { - ContinuousFunction * f = (ContinuousFunction *)model; - Poincare::Context * c = (Poincare::Context *)context; - return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), false, f->color()); } } diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index c3e190a3a..ded212f6d 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -7,6 +7,19 @@ namespace Graph { class GraphView : public Shared::FunctionGraphView { public: + /* The step is a fraction of tmax-tmin. We will evaluate the function at + * every step and if the consecutive dots are close enough, we won't + * evaluate any more dot within the step. We pick a very strange fraction + * denominator to avoid evaluating a periodic function periodically. For + * example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ) + * defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0 + * which would lead to no curve at all. With 10.0938275501223, the + * problematic functions are the functions whose period is proportionned to + * 10.0938275501223 which are hopefully rare enough. + * TODO: The drawCurve algorithm should use the derivative function to know + * how fast the function moves... */ + static constexpr float k_graphStepDenominator = 10.0938275501223f; + GraphView(Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); void reload() override; diff --git a/apps/graph/graph/intersection_graph_controller.h b/apps/graph/graph/intersection_graph_controller.h index dfd69232c..46bce552a 100644 --- a/apps/graph/graph/intersection_graph_controller.h +++ b/apps/graph/graph/intersection_graph_controller.h @@ -13,6 +13,9 @@ private: void reloadBannerView() override; Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Ion::Storage::Record m_intersectedRecord; + // Prevent horizontal panning to preserve search interval + float cursorRightMarginRatio() override { return 0.0f; } + float cursorLeftMarginRatio() override { return 0.0f; } }; } diff --git a/apps/graph/graph/root_graph_controller.h b/apps/graph/graph/root_graph_controller.h index 6bb7264a2..b808cb920 100644 --- a/apps/graph/graph/root_graph_controller.h +++ b/apps/graph/graph/root_graph_controller.h @@ -12,6 +12,9 @@ public: TELEMETRY_ID("Root"); private: Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + // Prevent horizontal panning to preserve search interval + float cursorRightMarginRatio() override { return 0.0f; } + float cursorLeftMarginRatio() override { return 0.0f; } }; } diff --git a/apps/graph/graph/tangent_graph_controller.cpp b/apps/graph/graph/tangent_graph_controller.cpp index f3cacdbcb..af70b1b42 100644 --- a/apps/graph/graph/tangent_graph_controller.cpp +++ b/apps/graph/graph/tangent_graph_controller.cpp @@ -24,7 +24,7 @@ const char * TangentGraphController::title() { void TangentGraphController::viewWillAppear() { Shared::SimpleInteractiveCurveViewController::viewWillAppear(); - m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_graphView->drawTangent(true); m_graphView->setOkView(nullptr); m_graphView->selectMainView(true); @@ -51,7 +51,7 @@ bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, co assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2(); m_cursor->moveTo(floatBody, floatBody, y); - interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; @@ -90,7 +90,7 @@ void TangentGraphController::reloadBannerView() { m_bannerView->reload(); } -bool TangentGraphController::moveCursorHorizontally(int direction, bool fast) { +bool TangentGraphController::moveCursorHorizontally(int direction, int scrollSpeed) { return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record); } diff --git a/apps/graph/graph/tangent_graph_controller.h b/apps/graph/graph/tangent_graph_controller.h index e75947a05..eed6d6379 100644 --- a/apps/graph/graph/tangent_graph_controller.h +++ b/apps/graph/graph/tangent_graph_controller.h @@ -24,7 +24,7 @@ private: Shared::CurveView * curveView() override { return m_graphView; } BannerView * bannerView() override { return m_bannerView; }; void reloadBannerView() override; - bool moveCursorHorizontally(int direction, bool fast = false) override; + bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; bool handleEnter() override; GraphView * m_graphView; BannerView * m_bannerView; diff --git a/apps/graph/list/domain_parameter_controller.cpp b/apps/graph/list/domain_parameter_controller.cpp index 326a7eb85..25a927704 100644 --- a/apps/graph/list/domain_parameter_controller.cpp +++ b/apps/graph/list/domain_parameter_controller.cpp @@ -10,7 +10,13 @@ namespace Graph { DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : FloatParameterController(parentResponder), m_domainCells{}, - m_record() + m_record(), + m_tempDomain(), + m_confirmPopUpController(Invocation([](void * context, void * sender) { + Container::activeApp()->dismissModalViewController(); + ((DomainParameterController *)context)->stackController()->pop(); + return true; + }, this)) { for (int i = 0; i < k_totalNumberOfCell; i++) { m_domainCells[i].setParentResponder(&m_selectableTableView); @@ -18,12 +24,10 @@ DomainParameterController::DomainParameterController(Responder * parentResponder } } -const char * DomainParameterController::title() { - return I18n::translate(I18n::Message::FunctionDomain); -} - -int DomainParameterController::numberOfRows() const { - return k_totalNumberOfCell+1; +void DomainParameterController::viewWillAppear() { + // Initialize m_tempParameters to the extracted value. + extractParameters(); + FloatParameterController::viewWillAppear(); } void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { @@ -56,10 +60,6 @@ void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, in FloatParameterController::willDisplayCellForIndex(cell, index); } -int DomainParameterController::reusableParameterCellCount(int type) { - return k_totalNumberOfCell; -} - HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) { assert(index >= 0 && index < k_totalNumberOfCell); return &m_domainCells[index]; @@ -70,20 +70,49 @@ bool DomainParameterController::handleEvent(Ion::Events::Event event) { stackController()->pop(); return true; } + if (event == Ion::Events::Back && !equalTempParameters()) { + // Open pop-up to confirm discarding values + Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); + return true; + } return false; } float DomainParameterController::parameterAtIndex(int index) { - return index == 0 ? function()->tMin() : function()->tMax(); + return index == 0 ? m_tempDomain.min() : m_tempDomain.max(); +} + +void DomainParameterController::extractParameters() { + setParameterAtIndex(0, function()->tMin()); + setParameterAtIndex(1, function()->tMax()); + /* Setting m_tempDomain tMin might affect m_tempDomain.max(), but setting tMax + * right after will not affect m_tempDomain.min() because Function's Range1D + * parameters are valid (tMax>tMin), and final tMin value is already set. + * Same happens in confirmParameters when setting function's parameters from + * valid m_tempDomain parameters. */ + assert(equalTempParameters()); } bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) { - // TODO: what to do if the xmin > xmax? - parameterIndex == 0 ? function()->setTMin(f) : function()->setTMax(f); + /* Setting Min (or Max) parameter can alter the previously set Max + * (or Min) parameter if Max <= Min. */ + parameterIndex == 0 ? m_tempDomain.setMin(f) : m_tempDomain.setMax(f); return true; } +void DomainParameterController::confirmParameters() { + function()->setTMin(parameterAtIndex(0)); + function()->setTMax(parameterAtIndex(1)); + // See comment on Range1D initialization in extractParameters + assert(equalTempParameters()); +} + +bool DomainParameterController::equalTempParameters() { + return function()->tMin() == m_tempDomain.min() && function()->tMax() == m_tempDomain.max(); +} + void DomainParameterController::buttonAction() { + confirmParameters(); StackViewController * stack = stackController(); stack->pop(); stack->pop(); diff --git a/apps/graph/list/domain_parameter_controller.h b/apps/graph/list/domain_parameter_controller.h index 1b8da3712..00f876c0e 100644 --- a/apps/graph/list/domain_parameter_controller.h +++ b/apps/graph/list/domain_parameter_controller.h @@ -6,6 +6,8 @@ #include "../../shared/continuous_function.h" #include "../../shared/expiring_pointer.h" #include "../../shared/float_parameter_controller.h" +#include "../../shared/range_1D.h" +#include "../../shared/discard_pop_up_controller.h" namespace Graph { @@ -14,17 +16,18 @@ public: DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); // ViewController - const char * title() override; + const char * title() override { return I18n::translate(I18n::Message::FunctionDomain); } TELEMETRY_ID("DomainParameter"); // ListViewDataSource - int numberOfRows() const override; + int numberOfRows() const override { return k_totalNumberOfCell+1; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; void setRecord(Ion::Storage::Record record) { m_record = record; } private: constexpr static int k_totalNumberOfCell = 2; - int reusableParameterCellCount(int type) override; + void viewWillAppear() override; + int reusableParameterCellCount(int type) override { return k_totalNumberOfCell; } HighlightCell * reusableParameterCell(int index, int type) override; bool handleEvent(Ion::Events::Event event) override; bool setParameterAtIndex(int parameterIndex, float f) override; @@ -32,8 +35,16 @@ private: void buttonAction() override; InfinityTolerance infinityAllowanceForRow(int row) const override; Shared::ExpiringPointer function() const; + // Applies temporary parameters to function. + void confirmParameters(); + // Extracts parameters from function, setting m_tempDomain parameters. + void extractParameters(); + // Return true if temporary parameters and function parameters are equal. + bool equalTempParameters(); MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell]; Ion::Storage::Record m_record; + Shared::Range1D m_tempDomain; + Shared::DiscardPopUpController m_confirmPopUpController; }; } diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index 38f0284f2..fd801cda2 100644 --- a/apps/graph/list/text_field_function_title_cell.cpp +++ b/apps/graph/list/text_field_function_title_cell.cpp @@ -8,7 +8,8 @@ namespace Graph { TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) : Shared::FunctionTitleCell(orientation), Responder(listController), - m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f) + m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f), + m_textFieldBuffer("") { } @@ -62,6 +63,11 @@ void TextFieldFunctionTitleCell::layoutSubviews(bool force) { m_textField.setAlignment(horizontalAlignment, verticalAlignment()); } +void TextFieldFunctionTitleCell::reloadCell() { + layoutSubviews(); + FunctionTitleCell::reloadCell(); +} + void TextFieldFunctionTitleCell::didBecomeFirstResponder() { if (isEditing()) { Container::activeApp()->setFirstResponder(&m_textField); diff --git a/apps/graph/list/text_field_function_title_cell.h b/apps/graph/list/text_field_function_title_cell.h index dd76523e5..05d6dcab7 100644 --- a/apps/graph/list/text_field_function_title_cell.h +++ b/apps/graph/list/text_field_function_title_cell.h @@ -35,6 +35,7 @@ public: return &m_textField; } void layoutSubviews(bool force = false) override; + void reloadCell() override; // Responder void didBecomeFirstResponder() override; diff --git a/apps/graph/test/caching.cpp b/apps/graph/test/caching.cpp new file mode 100644 index 000000000..5448699fc --- /dev/null +++ b/apps/graph/test/caching.cpp @@ -0,0 +1,124 @@ +#include +#include "helper.h" +#include + +using namespace Poincare; +using namespace Shared; + +namespace Graph { + +bool floatEquals(float a, float b, float tolerance = 1.f/static_cast(Ion::Display::Height)) { + /* The default value for the tolerance is chosen so that the error introduced + * by caching would not typically be visible on screen. */ + return (std::isnan(a) && std::isnan(b)) || IsApproximatelyEqual(a, b, tolerance, 0.); +} + +void assert_check_cartesian_cache_against_function(ContinuousFunction * function, ContinuousFunctionCache * cache, Context * context, float tMin) { + /* We set the cache to nullptr to force the evaluation (otherwise we would be + * comparing the cache against itself). */ + function->setCache(nullptr); + + float t; + for (int i = 0; i < Ion::Display::Width; i++) { + t = tMin + i*cache->step(); + Coordinate2D cacheValues = cache->valueForParameter(function, context, t); + Coordinate2D functionValues = function->evaluateXYAtParameter(t, context); + quiz_assert(floatEquals(t, cacheValues.x1())); + quiz_assert(floatEquals(t, functionValues.x1())); + quiz_assert(floatEquals(cacheValues.x2(), functionValues.x2())); + } + /* We set back the cache, so that it will not be invalidated in + * PrepareForCaching later. */ + function->setCache(cache); +} + +void assert_cartesian_cache_stays_valid_while_panning(ContinuousFunction * function, Context * context, InteractiveCurveViewRange * range, CurveViewCursor * cursor, ContinuousFunctionStore * store, float step) { + ContinuousFunctionCache * cache = store->cacheAtIndex(0); + assert(cache); + + float tMin, tStep; + constexpr float margin = 0.04f; + constexpr int numberOfMoves = 30; + for (int i = 0; i < numberOfMoves; i++) { + cursor->moveTo(cursor->t() + step, cursor->x() + step, function->evaluateXYAtParameter(cursor->x() + step, context).x2()); + range->panToMakePointVisible(cursor->x(), cursor->y(), margin, margin, margin, margin, (range->xMax() - range->xMin()) / (Ion::Display::Width - 1)); + tMin = range->xMin(); + tStep = (range->xMax() - range->xMin()) / (Ion::Display::Width - 1); + ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tStep); + assert_check_cartesian_cache_against_function(function, cache, context, tMin); + } +} + +void assert_check_polar_cache_against_function(ContinuousFunction * function, Context * context, InteractiveCurveViewRange * range, ContinuousFunctionStore * store) { + ContinuousFunctionCache * cache = store->cacheAtIndex(0); + assert(cache); + + float tMin = range->xMin(); + float tMax = range->xMax(); + + float tStep, tCacheStep; + ContinuousFunctionCache::ComputeNonCartesianSteps(&tStep, &tCacheStep, tMax, tMin); + + ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tCacheStep); + + // Fill the cache + float t; + for (int i = 0; i < Ion::Display::Width / 2; i++) { + t = tMin + i*cache->step(); + function->evaluateXYAtParameter(t, context); + } + + function->setCache(nullptr); + for (int i = 0; i < Ion::Display::Width / 2; i++) { + t = tMin + i*cache->step(); + Coordinate2D cacheValues = cache->valueForParameter(function, context, t); + Coordinate2D functionValues = function->evaluateXYAtParameter(t, context); + quiz_assert(floatEquals(cacheValues.x1(), functionValues.x1())); + quiz_assert(floatEquals(cacheValues.x2(), functionValues.x2())); + } +} + +void assert_cache_stays_valid(ContinuousFunction::PlotType type, const char * definition, float rangeXMin = -5, float rangeXMax = 5) { + GlobalContext globalContext; + ContinuousFunctionStore functionStore; + + InteractiveCurveViewRange graphRange; + graphRange.setXMin(rangeXMin); + graphRange.setXMax(rangeXMax); + graphRange.setYMin(-3.f); + graphRange.setYMax(3.f); + + CurveViewCursor cursor; + ContinuousFunction * function = addFunction(definition, type, &functionStore, &globalContext); + Coordinate2D origin = function->evaluateXYAtParameter(0.f, &globalContext); + cursor.moveTo(0.f, origin.x1(), origin.x2()); + + if (type == Cartesian) { + assert_cartesian_cache_stays_valid_while_panning(function, &globalContext, &graphRange, &cursor, &functionStore, 2.f); + assert_cartesian_cache_stays_valid_while_panning(function, &globalContext, &graphRange, &cursor, &functionStore, -0.4f); + } else { + assert_check_polar_cache_against_function(function, &globalContext, &graphRange, &functionStore); + } + + functionStore.removeAll(); +} + +QUIZ_CASE(graph_caching) { + assert_cache_stays_valid(Cartesian, "x"); + assert_cache_stays_valid(Cartesian, "x^2"); + assert_cache_stays_valid(Cartesian, "sin(x)"); + assert_cache_stays_valid(Cartesian, "sin(x)", -1e6f, 2e8f); + assert_cache_stays_valid(Cartesian, "sin(x^2)"); + assert_cache_stays_valid(Cartesian, "1/x"); + assert_cache_stays_valid(Cartesian, "1/x", -5e-5f, 5e-5f); + assert_cache_stays_valid(Cartesian, "-ℯ^x"); + + assert_cache_stays_valid(Polar, "1", 0.f, 360.f); + assert_cache_stays_valid(Polar, "θ", 0.f, 360.f); + assert_cache_stays_valid(Polar, "sin(θ)", 0.f, 360.f); + assert_cache_stays_valid(Polar, "sin(θ)", 2e-4f, 1e-3f); + assert_cache_stays_valid(Polar, "cos(5θ)", 0.f, 360.f); + assert_cache_stays_valid(Polar, "cos(5θ)", -1e8f, 1e8f); +} + +} diff --git a/apps/graph/test/helper.cpp b/apps/graph/test/helper.cpp new file mode 100644 index 000000000..6c4f4ac3c --- /dev/null +++ b/apps/graph/test/helper.cpp @@ -0,0 +1,16 @@ +#include "helper.h" + +namespace Graph { + +ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context) { + Ion::Storage::Record::ErrorStatus err = store->addEmptyModel(); + assert(err == Ion::Storage::Record::ErrorStatus::None); + (void) err; // Silence compilation warning about unused variable. + Ion::Storage::Record record = store->recordAtIndex(store->numberOfModels() - 1); + ContinuousFunction * f = static_cast(store->modelForRecord(record).operator->()); + f->setPlotType(type, Poincare::Preferences::AngleUnit::Degree, context); + f->setContent(definition, context); + return f; +} + +} diff --git a/apps/graph/test/helper.h b/apps/graph/test/helper.h new file mode 100644 index 000000000..727022bf6 --- /dev/null +++ b/apps/graph/test/helper.h @@ -0,0 +1,20 @@ +#ifndef APPS_GRAPH_TEST_HELPER_H +#define APPS_GRAPH_TEST_HELPER_H + +#include "../app.h" +#include "../../poincare/test/helper.h" + +using namespace Poincare; +using namespace Shared; + +namespace Graph { + +constexpr ContinuousFunction::PlotType Cartesian = ContinuousFunction::PlotType::Cartesian; +constexpr ContinuousFunction::PlotType Polar = ContinuousFunction::PlotType::Polar; +constexpr ContinuousFunction::PlotType Parametric = ContinuousFunction::PlotType::Parametric; + +ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context); + +} + +#endif diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp new file mode 100644 index 000000000..c1f75478c --- /dev/null +++ b/apps/graph/test/ranges.cpp @@ -0,0 +1,216 @@ +#include +#include "helper.h" + +using namespace Poincare; +using namespace Shared; + +namespace Graph { + +class AdHocGraphController : public InteractiveCurveViewRangeDelegate { +public: + /* These margins are obtained from instance methods of the various derived + * class of SimpleInteractiveCurveViewController. As we cannot create an + * instance of this class here, we define those directly. */ + static constexpr float k_topMargin = 0.068f; + static constexpr float k_bottomMargin = 0.132948f; + static constexpr float k_leftMargin = 0.04f; + static constexpr float k_rightMargin = 0.04f; + + static float Ratio() { return InteractiveCurveViewRange::NormalYXRatio() / (1.f + k_topMargin + k_bottomMargin); } + + Context * context() { return &m_context; } + ContinuousFunctionStore * functionStore() const { return &m_store; } + + // InteractiveCurveViewRangeDelegate + bool defaultRangeIsNormalized() const override { return functionStore()->displaysNonCartesianFunctions(); } + void interestingRanges(InteractiveCurveViewRange * range) override { DefaultInterestingRanges(range, context(), functionStore(), Ratio()); } + float addMargin(float x, float range, bool isVertical, bool isMin) override { return DefaultAddMargin(x, range, isVertical, isMin, k_topMargin, k_bottomMargin, k_leftMargin, k_rightMargin); } + void updateZoomButtons() override {} + +private: + mutable GlobalContext m_context; + mutable ContinuousFunctionStore m_store; +}; + +bool float_equal(float a, float b, float tolerance = 10.f * FLT_EPSILON) { + return IsApproximatelyEqual(a, b, tolerance, 0.); +} + +template +void assert_best_range_is(const char * const (&definitions)[N], ContinuousFunction::PlotType const (&plotTypes)[N], float targetXMin, float targetXMax, float targetYMin, float targetYMax, Poincare::Preferences::AngleUnit angleUnit = Radian) { + assert(std::isfinite(targetXMin) && std::isfinite(targetXMax) && std::isfinite(targetYMin) && std::isfinite(targetYMax) + && targetXMin < targetXMax && targetYMin < targetYMax); + + Preferences::sharedPreferences()->setAngleUnit(angleUnit); + + AdHocGraphController graphController; + InteractiveCurveViewRange graphRange(&graphController); + + for (size_t i = 0; i < N; i++) { + addFunction(definitions[i], plotTypes[i], graphController.functionStore(), graphController.context()); + } + graphRange.setDefault(); + float xMin = graphRange.xMin(); + float xMax = graphRange.xMax(); + float yMin = graphRange.yMin(); + float yMax = graphRange.yMax(); + quiz_assert(float_equal(xMin, targetXMin) && float_equal(xMax, targetXMax) && float_equal(yMin, targetYMin) && float_equal(yMax, targetYMax)); + + graphController.functionStore()->removeAll(); +} + +void assert_best_cartesian_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Poincare::Preferences::AngleUnit angleUnit = Radian, ContinuousFunction::PlotType plotType = Cartesian) { + const char * definitionArray[1] = { definition }; + ContinuousFunction::PlotType plotTypeArray[1] = { plotType }; + assert_best_range_is(definitionArray, plotTypeArray, targetXMin, targetXMax, targetYMin, targetYMax, angleUnit); +} + +QUIZ_CASE(graph_ranges_single_function) { + assert_best_cartesian_range_is("undef", -10, 10, -5.81249952, 4.81249952); + assert_best_cartesian_range_is("x!", -10, 10, -5.81249952, 4.81249952); + + assert_best_cartesian_range_is("0", -10, 10, -5.81249952, 4.81249952); + assert_best_cartesian_range_is("1", -10, 10, -4.81249952, 5.81249952); + assert_best_cartesian_range_is("-100", -10, 10, -105.8125, -95.1875); + assert_best_cartesian_range_is("0.01", -10, 10, -5.81249952, 4.81249952); + + assert_best_cartesian_range_is("x", -10, 10, -5.66249943, 4.96249962); + assert_best_cartesian_range_is("x+1", -12, 10, -6.19374943, 5.49374962); + assert_best_cartesian_range_is("-x+5", -6, 17, -6.55937433, 5.65937471); + assert_best_cartesian_range_is("x/2+2", -15, 7, -6.19374943, 5.49374962); + + + assert_best_cartesian_range_is("x^2", -10, 10, -1.31249952, 9.3125); + assert_best_cartesian_range_is("x^3", -10, 10, -5.16249943, 5.46249962); + assert_best_cartesian_range_is("-2x^6", -10, 10, -16000, 2000); + assert_best_cartesian_range_is("3x^2+x+10", -12, 11, 7.84062624, 20.0593758); + + assert_best_cartesian_range_is("1/x", -4.51764774, 4.51764774, -2.60000014, 2.20000005); + assert_best_cartesian_range_is("1/(1-x)", -3.51176548, 5.71176529, -2.60000014, 2.29999995); + assert_best_cartesian_range_is("1/(x^2+1)", -3.4000001, 3.4000001, -0.200000003, 1.10000002); + + assert_best_cartesian_range_is("sin(x)", -15, 15, -1.39999998, 1.20000005, Radian); + assert_best_cartesian_range_is("cos(x)", -1000, 1000, -1.39999998, 1.20000005, Degree); + assert_best_cartesian_range_is("tan(x)", -1000, 1000, -3.9000001, 3.4000001, Gradian); + assert_best_cartesian_range_is("tan(x-100)", -1200, 1200, -4, 3.5, Gradian); + + assert_best_cartesian_range_is("ℯ^x", -10, 10, -1.71249962, 8.91249943); + assert_best_cartesian_range_is("ℯ^x+4", -10, 10, 2.28750038, 12.9124994); + assert_best_cartesian_range_is("ℯ^(-x)", -10, 10, -1.71249962, 8.91249943); + assert_best_cartesian_range_is("(1-x)ℯ^(1/(1-x))", -1.8, 2.9, -3, 5.1); + + assert_best_cartesian_range_is("ln(x)", -2.85294199, 8.25294113, -3.5, 2.4000001); + assert_best_cartesian_range_is("log(x)", -0.900000036, 3.20000005, -1.23906231, 0.939062357); + + assert_best_cartesian_range_is("√(x)", -3, 10, -2.10312462, 4.80312443); + assert_best_cartesian_range_is("√(x^2+1)-x", -10, 10, -1.26249981, 9.36249924); + assert_best_cartesian_range_is("root(x^3+1,3)-x", -2, 2.5, -0.445312381, 1.94531238); +} + +QUIZ_CASE(graph_ranges_several_functions) { + { + const char * definitions[] = {"ℯ^x", "ln(x)"}; + ContinuousFunction::PlotType types[] = {Cartesian, Cartesian}; + assert_best_range_is(definitions, types, -10, 10, -2.81249952, 7.81249952); + } + { + const char * definitions[] = {"x/2+2", "-x+5"}; + ContinuousFunction::PlotType types[] = {Cartesian, Cartesian}; + assert_best_range_is(definitions, types, -16, 17, -9.21562386, 8.31562424); + } + { + const char * definitions[] = {"sin(θ)", "cos(θ)"}; + ContinuousFunction::PlotType types[] = {Polar, Polar}; + assert_best_range_is(definitions, types, -1.63235319, 2.13235331, -0.800000011, 1.20000005); + } +} + +void assert_zooms_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio, bool zoomIn) { + float ratio = zoomIn ? 1.f / ZoomCurveViewController::k_zoomOutRatio : ZoomCurveViewController::k_zoomOutRatio; + + InteractiveCurveViewRange graphRange; + graphRange.setXMin(xMin); + graphRange.setXMax(xMax); + graphRange.setYMin(yMin); + graphRange.setYMax(yMax); + + float xCenter = (xMax + xMin) / 2.f; + float yCenter = (yMax + yMin) / 2.f; + + graphRange.zoom(ratio, xCenter, yCenter); + + quiz_assert(float_equal(graphRange.xMin(), targetXMin) && float_equal(graphRange.xMax(), targetXMax) && float_equal(graphRange.yMin(), targetYMin) && float_equal(graphRange.yMax(), targetYMax)); + quiz_assert(float_equal((yMax - yMin) / (xMax - xMin), (targetYMax - targetYMin) / (targetXMax - targetXMin)) == conserveRatio); +} + +void assert_zooms_in_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio) { + assert_zooms_to(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, conserveRatio, true); +} + +void assert_zooms_out_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio) { + assert_zooms_to(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, conserveRatio, false); +} + +QUIZ_CASE(graph_ranges_zoom) { + assert_zooms_in_to( + -12, 12, -12, 12, + -8, 8, -8, 8, + true); + + assert_zooms_in_to( + -3, 3, 0, 1e-4, + -3, 3, 0, 1e-4, + true); + + assert_zooms_out_to( + -10, 10, -10, 10, + -15, 15, -15, 15, + true); + + assert_zooms_out_to( + -1, 1, 9e7, 1e8, + -1.5, 1.5, 87500000, 1e8, + false); +} + +void assert_orthonormality(float xMin, float xMax, float yMin, float yMax, bool orthonormal) { + InteractiveCurveViewRange graphRange; + graphRange.setXMin(xMin); + graphRange.setXMax(xMax); + graphRange.setYMin(yMin); + graphRange.setYMax(yMax); + + quiz_assert(graphRange.isOrthonormal() == orthonormal); +} + +void assert_is_orthonormal(float xMin, float xMax, float yMin, float yMax) { + assert_orthonormality(xMin, xMax, yMin, yMax, true); +} + +void assert_is_not_orthonormal(float xMin, float xMax, float yMin, float yMax) { + assert_orthonormality(xMin, xMax, yMin, yMax, false); +} + +QUIZ_CASE(graph_ranges_orthonormal) { + assert_is_orthonormal(-10, 10, -5.8125, 4.8125); + assert_is_orthonormal(11.37037, 17.2963, 7.529894, 10.67804); + assert_is_orthonormal(-1.94574, -1.165371, -2.476379, -2.061809); + assert_is_orthonormal(0, 1000000, 0, 531250); + assert_is_orthonormal(-3.2e-3f, 3.2e-3f, -1.7e-3f, 1.7e-3f); + assert_is_not_orthonormal(-10, 10, -10, 10); + assert_is_not_orthonormal(-10, 10, -5.8125, 4.8126); + assert_is_not_orthonormal(1234548, 1234568, 1234556, 1234568); + + /* The ratio is 0.55 instead of 0.53125, but depending on the magnitude of + * the bounds, it can land inside the margin of error. */ + assert_is_not_orthonormal(0, 20, 0, 11); + assert_is_orthonormal(1e6, 1e6 + 20, 1e6, 1e6 + 11); + + /* The ration is the desired 0.53125, but if the bounds are near equal + * numbers of large magnitude, the loss odf precision leaves us without any + * significant bits. */ + assert_is_orthonormal(0, 3.2, 0, 1.7); + assert_is_not_orthonormal(1e7, 1e7 + 3.2, 0, 1.7); +} + +} diff --git a/apps/hardware_test/app.h b/apps/hardware_test/app.h index 98b6827a6..5579381ca 100644 --- a/apps/hardware_test/app.h +++ b/apps/hardware_test/app.h @@ -11,12 +11,13 @@ #include "led_test_controller.h" #include "serial_number_controller.h" #include "vblank_test_controller.h" +#include "../shared/shared_app.h" namespace HardwareTest { class App : public ::App { public: - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: App * unpack(Container * container) override; Descriptor * descriptor() override; diff --git a/apps/hardware_test/pop_up_controller.cpp b/apps/hardware_test/pop_up_controller.cpp index 76afaa19b..ed785d24a 100644 --- a/apps/hardware_test/pop_up_controller.cpp +++ b/apps/hardware_test/pop_up_controller.cpp @@ -1,116 +1,25 @@ #include "pop_up_controller.h" -#include #include "../apps_container.h" -#include -#include namespace HardwareTest { PopUpController::PopUpController() : - ViewController(nullptr), - m_contentView(this) + ::PopUpController( + 4, + Invocation( + [](void * context, void * sender) { + AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); + bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot()); + assert(switched); + (void) switched; // Silence compilation warning about unused variable. + return true; + }, this) + ) { -} - -View * PopUpController::view() { - return &m_contentView; -} - -void PopUpController::didBecomeFirstResponder() { - m_contentView.setSelectedButton(0); -} - -bool PopUpController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) { - m_contentView.setSelectedButton(0); - return true; - } - if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) { - m_contentView.setSelectedButton(1); - return true; - } - return false; -} - -PopUpController::ContentView::ContentView(Responder * parentResponder) : - Responder(parentResponder), - m_cancelButton(this, I18n::Message::Cancel, Invocation([](void * context, void * sender) { - Container::activeApp()->dismissModalViewController(); - return true; - }, this), KDFont::SmallFont), - m_okButton(this, I18n::Message::Ok, Invocation([](void * context, void * sender) { - AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); - bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot()); - assert(switched); - (void) switched; // Silence compilation warning about unused variable. - return true; - }, this), KDFont::SmallFont), - m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView1(KDFont::SmallFont, I18n::Message::HardwareTestLaunch1, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView2(KDFont::SmallFont, I18n::Message::HardwareTestLaunch2, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView3(KDFont::SmallFont, I18n::Message::HardwareTestLaunch3, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView4(KDFont::SmallFont, I18n::Message::HardwareTestLaunch4, 0.5, 0.5, KDColorWhite, KDColorBlack) -{ -} - -void PopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), KDColorBlack); -} - -void PopUpController::ContentView::setSelectedButton(int selectedButton) { - m_cancelButton.setHighlighted(selectedButton == 0); - m_okButton.setHighlighted(selectedButton == 1); - if (selectedButton == 0) { - Container::activeApp()->setFirstResponder(&m_cancelButton); - } else { - Container::activeApp()->setFirstResponder(&m_okButton); - } -} - -int PopUpController::ContentView::selectedButton() { - if (m_cancelButton.isHighlighted()) { - return 0; - } - return 1; -} - -int PopUpController::ContentView::numberOfSubviews() const { - return 7; -} - -View * PopUpController::ContentView::subviewAtIndex(int index) { - switch (index) { - case 0: - return &m_warningTextView; - case 1: - return &m_messageTextView1; - case 2: - return &m_messageTextView2; - case 3: - return &m_messageTextView3; - case 4: - return &m_messageTextView4; - case 5: - return &m_cancelButton; - case 6: - return &m_okButton; - default: - assert(false); - return nullptr; - } -} - -void PopUpController::ContentView::layoutSubviews(bool force) { - KDCoordinate height = bounds().height(); - KDCoordinate width = bounds().width(); - KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force); - m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight), force); - m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight), force); - m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight), force); - m_messageTextView4.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+4*textHeight, width, textHeight), force); - m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); - m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); + m_contentView.setMessage(0, I18n::Message::HardwareTestLaunch1); + m_contentView.setMessage(1, I18n::Message::HardwareTestLaunch2); + m_contentView.setMessage(2, I18n::Message::HardwareTestLaunch3); + m_contentView.setMessage(3, I18n::Message::HardwareTestLaunch4); } } diff --git a/apps/hardware_test/pop_up_controller.h b/apps/hardware_test/pop_up_controller.h index b04724e95..35b74e265 100644 --- a/apps/hardware_test/pop_up_controller.h +++ b/apps/hardware_test/pop_up_controller.h @@ -1,43 +1,15 @@ -#ifndef HARDWARE_TEST_POP_UP_CONTROLLER_H -#define HARDWARE_TEST_POP_UP_CONTROLLER_H +#ifndef POP_UP_CONTROLLER_H +#define POP_UP_CONTROLLER_H -#include +#include namespace HardwareTest { -class PopUpController : public ViewController { +class PopUpController : public ::PopUpController { public: PopUpController(); - View * view() override; - void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; -private: - class ContentView : public View, public Responder { - public: - ContentView(Responder * parentResponder); - void drawRect(KDContext * ctx, KDRect rect) const override; - void setSelectedButton(int selectedButton); - int selectedButton(); - private: - constexpr static KDCoordinate k_buttonMargin = 10; - constexpr static KDCoordinate k_buttonHeight = 20; - constexpr static KDCoordinate k_topMargin = 8; - constexpr static KDCoordinate k_paragraphHeight = 20; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews(bool force = false) override; - Button m_cancelButton; - Button m_okButton; - MessageTextView m_warningTextView; - MessageTextView m_messageTextView1; - MessageTextView m_messageTextView2; - MessageTextView m_messageTextView3; - MessageTextView m_messageTextView4; - }; - ContentView m_contentView; }; } #endif - diff --git a/apps/home/Makefile b/apps/home/Makefile index fc45cb479..c76c45808 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -1,9 +1,28 @@ app_home_src = $(addprefix apps/home/,\ app.cpp \ app_cell.cpp \ + apps_layout.py \ controller.cpp \ ) apps_src += $(app_home_src) i18n_files += $(call i18n_without_universal_for,home/base) + +# Apps layout file generation + +# The header is refered to as so make sure it's +# findable this way +SFLAGS += -I$(BUILD_DIR) + +apps_layout = apps/home/apps_layout.csv + +$(eval $(call rule_for, \ + APPS_LAYOUT, \ + apps/home/apps_layout.cpp, \ + , \ + $$(PYTHON) apps/home/apps_layout.py --layouts $(apps_layout) --header $$(subst .cpp,.h,$$@) --implementation $$@ --apps $$(EPSILON_APPS), \ + global \ +)) + +$(BUILD_DIR)/apps/home/apps_layout.h: $(BUILD_DIR)/apps/home/apps_layout.cpp diff --git a/apps/home/app.h b/apps/home/app.h index a912ec3ee..50d55e7b7 100644 --- a/apps/home/app.h +++ b/apps/home/app.h @@ -3,6 +3,7 @@ #include #include "controller.h" +#include "../shared/shared_app.h" namespace Home { @@ -13,7 +14,7 @@ public: I18n::Message name() override; I18n::Message upperName() override; }; - class Snapshot : public ::App::Snapshot, public SelectableTableViewDataSource { + class Snapshot : public ::SharedApp::Snapshot, public SelectableTableViewDataSource { public: App * unpack(Container * container) override; Descriptor * descriptor() override; diff --git a/apps/home/apps_layout.csv b/apps/home/apps_layout.csv new file mode 100644 index 000000000..64469620c --- /dev/null +++ b/apps/home/apps_layout.csv @@ -0,0 +1,2 @@ +Default,calculation,rpn,graph,code,statistics,probability,solver,atom,sequence,regression,settings +HidePython,calculation,rpn,graph,code,statistics,probability,solver,atom,sequence,regression,settings \ No newline at end of file diff --git a/apps/home/apps_layout.py b/apps/home/apps_layout.py new file mode 100644 index 000000000..cff1f712b --- /dev/null +++ b/apps/home/apps_layout.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# This script builds the .h/.cpp files for managing the placement of +# applications on the home menu, from the apps_layout.csv file. + +import argparse +import csv +import io + +parser = argparse.ArgumentParser(description="Build tools for the placement of applications on the home menu.") +parser.add_argument('--header', help='the .h file to generate') +parser.add_argument('--implementation', help='the .cpp file to generate') +parser.add_argument('--apps', nargs='+', help='apps that are actually compiled') +parser.add_argument('--layouts', help='the apps_layout.csv file') + +args = parser.parse_args() + +def build_permutation(apps, appsOrdered): + res = [0] * len(apps) + i = 0 + for app in appsOrdered: + if app in apps: + res[i] = apps.index(app) + 1 + i += 1 + return res + +def parse_config_file(layouts, apps): + res = {'styles':[], 'permutations':[]} + with io.open(layouts, "r", encoding="utf-8") as f: + csvreader = csv.reader(f, delimiter=',') + for row in csvreader: + res['styles'].append(row[0]) + res['permutations'].append(build_permutation(apps, row[1:])) + return res + +data = parse_config_file(args.layouts, args.apps) + +def print_header(header, data): + f = open(header, "w") + f.write("#ifndef HOME_APPS_LAYOUT_H\n") + f.write("#define HOME_APPS_LAYOUT_H\n") + f.write("// This file is auto-generated by apps_layout.py\n\n") + f.write("namespace Home {\n\n") + + f.write("int PermutedAppSnapshotIndex(int index);\n\n") + + f.write("}\n\n") + f.write("#endif") + f.close() + +def print_implementation(implementation, data): + f = open(implementation, "w") + f.write("// This file is auto-generated by apps_layout.py\n\n") + f.write('#include "apps_layout.h"\n') + f.write("#include \n") + f.write("#include \n") + f.write("#include \n\n") + f.write("namespace Home {\n\n") + + styles = data['styles'] + permutations = data['permutations'] + + f.write("/* Permutations are built so that Permutation[n] is the index of the snapshot\n") + f.write(" * for the nth app in the Home menu. */\n\n") + + for i in range(len(styles)): + f.write("static constexpr int " + styles[i] + "AppsPermutation[] = {\n") + f.write(" 0,\n") + for j in permutations[i]: + f.write(" " + str(j) + ",\n") + f.write("};\n") + f.write('static_assert(' + styles[i] + 'AppsPermutation[0] == 0, "The Home apps must always be at index 0");\n\n') + + f.write("int PermutedAppSnapshotIndex(int index) {\n") + f.write(" CountryPreferences::HomeAppsLayout currentLayout = GlobalPreferences::sharedGlobalPreferences()->homeAppsLayout();\n") + for i in range(len(styles)): + f.write(" if (currentLayout == CountryPreferences::HomeAppsLayout::" + styles[i] + ") {\n") + f.write(" return " + styles[i] + "AppsPermutation[index];") + f.write(" }\n") + f.write(" assert(false);\n") + f.write(" return -1;\n") + f.write("}\n\n") + + f.write("}\n\n") + f.close() + +if args.header: + print_header(args.header, data) +if args.implementation: + print_implementation(args.implementation, data) diff --git a/apps/home/base.es.i18n b/apps/home/base.es.i18n index 2eca220ed..93e7bdf49 100644 --- a/apps/home/base.es.i18n +++ b/apps/home/base.es.i18n @@ -1,4 +1,4 @@ Apps = "Aplicaciones" AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Esta aplicación está" -ForbidenAppInExamMode2 = "prohibida en el modo examen" +ForbidenAppInExamMode1 = "Esta aplicación está prohibida" +ForbidenAppInExamMode2 = "en el modo de examen" diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 2faa2e488..59691c15e 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -1,5 +1,6 @@ #include "controller.h" #include "app.h" +#include #include "../apps_container.h" #include "../global_preferences.h" #include "../exam_mode_configuration.h" @@ -67,7 +68,6 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc bool Controller::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { AppsContainer * container = AppsContainer::sharedAppsContainer(); - int index = selectionDataSource()->selectedRow()*k_numberOfColumns+selectionDataSource()->selectedColumn()+1; #ifdef HOME_DISPLAY_EXTERNALS if (index >= container->numberOfApps()) { @@ -96,9 +96,8 @@ bool Controller::handleEvent(Ion::Events::Event event) { } } else { #endif - ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); - if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->examinationLevel(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1)); + if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { } else { bool switched = container->switchTo(selectedSnapshot); assert(switched); @@ -111,14 +110,14 @@ bool Controller::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::Home || event == Ion::Events::Back) { - return m_view.selectableTableView()->selectCellAtLocation(0,0); + return m_view.selectableTableView()->selectCellAtLocation(0, 0); } - if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows()) { - return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow()+1); + if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows() - 1) { + return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow() + 1); } if (event == Ion::Events::Left && selectionDataSource()->selectedRow() > 0) { - return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns()-1, selectionDataSource()->selectedRow()-1); + return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns() - 1, selectionDataSource()->selectedRow() - 1); } return false; @@ -146,7 +145,7 @@ View * Controller::view() { } int Controller::numberOfRows() const { - return ((numberOfIcons()-1)/k_numberOfColumns)+1; + return ((numberOfIcons() - 1) / k_numberOfColumns) + 1; } int Controller::numberOfColumns() const { @@ -172,7 +171,7 @@ int Controller::reusableCellCount() const { void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { AppCell * appCell = (AppCell *)cell; AppsContainer * container = AppsContainer::sharedAppsContainer(); - int appIndex = (j*k_numberOfColumns+i)+1; + int appIndex = (j * k_numberOfColumns + i) + 1; if (appIndex >= container->numberOfApps()) { #ifdef HOME_DISPLAY_EXTERNALS External::Archive::File app_file; @@ -207,7 +206,7 @@ void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { #endif } else { appCell->setVisible(true); - ::App::Descriptor * descriptor = container->appSnapshotAtIndex(appIndex)->descriptor(); + ::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor(); appCell->setAppDescriptor(descriptor); } } diff --git a/apps/i18n.py b/apps/i18n.py index a6130e49a..b883f557b 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -6,18 +6,22 @@ # properly draw upper case letters with accents, we remove them here. # It works with Python 2 and Python 3 -import sys -import re -import unicodedata import argparse +import csv import io +import re +import sys +import unicodedata parser = argparse.ArgumentParser(description="Process some i18n files.") parser.add_argument('--header', help='the .h file to generate') parser.add_argument('--implementation', help='the .cpp file to generate') parser.add_argument('--locales', nargs='+', help='locale to actually generate') +parser.add_argument('--countries', nargs='+', help='countries to actually generate') parser.add_argument('--codepoints', help='the code_points.h file') +parser.add_argument('--countrypreferences', help='the country_preferences.csv file') +parser.add_argument('--languagepreferences', help='the language_preferences.csv file') parser.add_argument('--files', nargs='+', help='an i18n file') parser.add_argument('--generateISO6391locales', type=int, nargs='+', help='whether to generate the ISO6391 codes for the languages (for instance "en" for english)') @@ -67,6 +71,18 @@ def split_line(line): def locale_from_filename(filename): return re.match(r".*\.([a-z]+)\.i18n", filename).group(1) +def check_redundancy(messages, data, locales): + redundant_names = set() + for name in messages: + redundancy = True + for i in range(1, len(locales)): + redundancy = redundancy and data[locales[i]][name] == data[locales[i-1]][name] + if redundancy: + redundant_names.add(name) + if (len(redundant_names) > 0): + sys.stderr.write("Some localized messages are redundant and can be made universal :\n\t" + "\n\t".join(sorted(redundant_names)) + "\n") + sys.exit(-1) + def parse_files(files): data = {} messages = set() @@ -91,6 +107,7 @@ def parse_files(files): else: messages.add(name) data[locale][name] = definition + #check_redundancy(messages, data, args.locales) # FIXME return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data} def parse_codepoints(file): @@ -114,46 +131,110 @@ def parse_codepoints(file): codepoints = parse_codepoints(args.codepoints) -def print_header(data, path, locales): +def parse_csv_with_header(file): + res = [] + with io.open(file, 'r', encoding='utf-8') as csvfile: + csvreader = csv.reader(csvfile, delimiter=',') + for row in csvreader: + res.append(row) + return (res[0], res[1:]) + +def parse_country_preferences(file): + countryPreferences = {} + header, records = parse_csv_with_header(file) + for record in records: + countryPreferences[record[0]] = [header[i] + "::" + record[i] for i in range(1, len(record))] + return countryPreferences + +countryPreferences = parse_country_preferences(args.countrypreferences) + +def parse_language_preferences(file): + languagePreferences = {} + header, records = parse_csv_with_header(file) + for record in records: + languagePreferences[record[0]] = (header[1], record[1]) + return languagePreferences + +languagePreferences = parse_language_preferences(args.languagepreferences) + +def print_block_from_list(target, header, data, beautify=lambda arg: arg, prefix=" ", footer="};\n\n"): + target.write(header) + for i in range(len(data)): + target.write(prefix + beautify(data[i]) + ",\n") + target.write(footer) + +def print_header(data, path, locales, countries): f = open(path, "w") f.write("#ifndef APPS_I18N_H\n") f.write("#define APPS_I18N_H\n\n") f.write("// This file is auto-generated by i18n.py\n\n") - f.write("#include \n\n") + f.write("#include \n") + f.write("#include \n\n") f.write("namespace I18n {\n\n") f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales)) + f.write("constexpr static int NumberOfCountries = %d;\n\n" % len(countries)) # Messages enumeration - f.write("enum class Message : uint16_t {\n") - f.write(" Default = 0,\n") - for message in data["universal_messages"]: - f.write(" " + message + ",\n") - f.write("\n") - f.write(" LocalizedMessageMarker,\n\n") - for message in data["messages"]: - f.write(" " + message + ",\n") - f.write("};\n\n") + print_block_from_list(f, + "enum class Message : uint16_t {\n Default = 0,\n", + data["universal_messages"], + footer="\n") + print_block_from_list(f, + " LocalizedMessageMarker,\n\n", + data["messages"]) # Languages enumeration - f.write("enum class Language : uint16_t {\n") - index = 0 - for locale in locales: - f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") +",\n") - index = index + 1 - f.write("};\n\n") + print_block_from_list(f, + "enum class Language : uint8_t {\n", + locales, + lambda arg: arg.upper()) # Language names - f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n"); - for locale in locales: - f.write(" Message::Language" + locale.upper() + ",\n") - f.write("};\n\n") + print_block_from_list(f, + "constexpr const Message LanguageNames[NumberOfLanguages] = {\n", + locales, + lambda arg: arg.upper(), + " Message::Language") if generate_ISO6391(): - # Language ISO639-1 codes - f.write("constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n"); - for locale in locales: - f.write(" Message::LanguageISO6391" + locale.upper() + ",\n") - f.write("};\n\n") + print_block_from_list(f, + "constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n", + locales, + lambda arg: arg.upper(), + " Message::LanguageISO6391") + + # Countries enumeration + print_block_from_list(f, + "enum class Country : uint8_t {\n", + countries, + lambda arg: arg.upper()) + defaultCountry = countries[-1] + + # Country names + print_block_from_list(f, + "constexpr const Message CountryNames[NumberOfCountries] = {\n", + countries, + lambda arg: arg.upper(), + " Message::Country") + + # Language preferences + f.write("constexpr static Country DefaultCountryForLanguage[NumberOfLanguages] = {\n") + for language in locales: + key = language if (language in languagePreferences) else '??' + header, country = languagePreferences[key] + line = " " + header + "::" + (country if country in countries else defaultCountry) + f.write(line + ",\n") + f.write("};\n\n") + + # Country preferences + f.write("constexpr static CountryPreferences CountryPreferencesArray[] = {\n") + for country in countries: + key = country if (country in countryPreferences) else defaultCountry + line = " CountryPreferences(" + for param in countryPreferences[key]: + line += param + ", " + f.write(line[:-2] + "),\n") + f.write("};\n\n") f.write("}\n\n") f.write("#endif\n") @@ -178,11 +259,10 @@ def print_implementation(data, path, locales): f = open(path, "a") # Re-open the file as text f.write(";\n") f.write("\n") - f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1)) - f.write(" universalDefault,\n") - for message in data["universal_messages"]: - f.write(" universal" + message + ",\n") - f.write("};\n\n") + print_block_from_list(f, + "constexpr static const char * universalMessages[%d] = {\n universalDefault,\n" % (len(data["universal_messages"])+1), + data["universal_messages"], + prefix=" universal") # Write the localized messages for message in data["messages"]: @@ -226,6 +306,6 @@ def print_implementation(data, path, locales): data = parse_files(args.files) if args.header: - print_header(data, args.header, args.locales) + print_header(data, args.header, args.locales, args.countries) if args.implementation: print_implementation(data, args.implementation, args.locales) diff --git a/apps/language_preferences.csv b/apps/language_preferences.csv new file mode 100644 index 000000000..867820f71 --- /dev/null +++ b/apps/language_preferences.csv @@ -0,0 +1,9 @@ +Language,I18n::Country +en,US +fr,FR +nl,NL +pt,PT +it,IT +de,DE +es,ES +??,WW diff --git a/apps/main.cpp b/apps/main.cpp index d0dcf00b7..3a22fda5c 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -38,6 +38,7 @@ void ion_main(int argc, const char * const argv[]) { for (int j = 0; j < I18n::NumberOfLanguages; j++) { if (strcmp(requestedLanguageId, I18n::translate(I18n::LanguageISO6391Names[j])) == 0) { GlobalPreferences::sharedGlobalPreferences()->setLanguage((I18n::Language)j); + GlobalPreferences::sharedGlobalPreferences()->setCountry(I18n::DefaultCountryForLanguage[j]); break; } } @@ -60,6 +61,16 @@ void ion_main(int argc, const char * const argv[]) { } } #endif + +#if !PLATFORM_DEVICE + /* s_stackStart must be defined as early as possible to ensure that there + * cannot be allocated memory pointers before. Otherwise, with MicroPython for + * example, stack pointer could go backward after initialization and allocated + * memory pointers could be overlooked during mark procedure. */ + volatile int stackTop; + Ion::setStackStart((void *)(&stackTop)); +#endif + AppsContainer::sharedAppsContainer()->run(); } diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 6a282996c..f33a0eaa0 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -1,4 +1,5 @@ #include "math_toolbox.h" +#include "global_preferences.h" #include "./shared/toolbox_helpers.h" #include #include @@ -71,7 +72,15 @@ const ToolboxMessageTree matricesChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::DeterminantCommandWithArg, I18n::Message::Determinant), ToolboxMessageTree::Leaf(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose), ToolboxMessageTree::Leaf(I18n::Message::TraceCommandWithArg, I18n::Message::Trace), - ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension) + ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension), + ToolboxMessageTree::Leaf(I18n::Message::RowEchelonFormCommandWithArg, I18n::Message::RowEchelonForm), + ToolboxMessageTree::Leaf(I18n::Message::ReducedRowEchelonFormCommandWithArg, I18n::Message::ReducedRowEchelonForm) +}; + +const ToolboxMessageTree vectorsChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::DotCommandWithArg, I18n::Message::Dot), + ToolboxMessageTree::Leaf(I18n::Message::CrossCommandWithArg, I18n::Message::Cross), + ToolboxMessageTree::Leaf(I18n::Message::NormVectorCommandWithArg, I18n::Message::NormVector), }; #if LIST_ARE_DEFINED @@ -84,74 +93,114 @@ const ToolboxMessageTree listsChildren[] = { }; #endif -const ToolboxMessageTree unitTimeSecondChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondSymbol, I18n::Message::UnitTimeSecond), - ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMilliSymbol, I18n::Message::UnitTimeSecondMilli), - ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMicroSymbol, I18n::Message::UnitTimeSecondMicro), - ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondNanoSymbol, I18n::Message::UnitTimeSecondNano), -}; - const ToolboxMessageTree unitTimeChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitTimeSecondMenu, unitTimeSecondChildren), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondNanoSymbol, I18n::Message::UnitTimeSecondNano), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMicroSymbol, I18n::Message::UnitTimeSecondMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMilliSymbol, I18n::Message::UnitTimeSecondMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondSymbol, I18n::Message::UnitTimeSecond), ToolboxMessageTree::Leaf(I18n::Message::UnitTimeMinuteSymbol, I18n::Message::UnitTimeMinute), ToolboxMessageTree::Leaf(I18n::Message::UnitTimeHourSymbol, I18n::Message::UnitTimeHour), ToolboxMessageTree::Leaf(I18n::Message::UnitTimeDaySymbol, I18n::Message::UnitTimeDay), ToolboxMessageTree::Leaf(I18n::Message::UnitTimeWeekSymbol, I18n::Message::UnitTimeWeek), ToolboxMessageTree::Leaf(I18n::Message::UnitTimeMonthSymbol, I18n::Message::UnitTimeMonth), - ToolboxMessageTree::Leaf(I18n::Message::UnitTimeYearSymbol, I18n::Message::UnitTimeYear)}; - -const ToolboxMessageTree unitDistanceMeterChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterKiloSymbol, I18n::Message::UnitDistanceMeterKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterSymbol, I18n::Message::UnitDistanceMeter), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMilliSymbol, I18n::Message::UnitDistanceMeterMilli), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMicroSymbol, I18n::Message::UnitDistanceMeterMicro), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterNanoSymbol, I18n::Message::UnitDistanceMeterNano), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterPicoSymbol, I18n::Message::UnitDistanceMeterPico), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeYearSymbol, I18n::Message::UnitTimeYear) }; -const ToolboxMessageTree unitDistanceImperialChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceInchSymbol, I18n::Message::UnitDistanceInch), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceFootSymbol, I18n::Message::UnitDistanceFoot), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceYardSymbol, I18n::Message::UnitDistanceYard), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMileSymbol, I18n::Message::UnitDistanceMile), +constexpr ToolboxMessageTree unitDistanceMeterPico = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterPicoSymbol, I18n::Message::UnitDistanceMeterPico); +constexpr ToolboxMessageTree unitDistanceMeterNano = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterNanoSymbol, I18n::Message::UnitDistanceMeterNano); +constexpr ToolboxMessageTree unitDistanceMeterMicro = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMicroSymbol, I18n::Message::UnitDistanceMeterMicro); +constexpr ToolboxMessageTree unitDistanceMeterMilli = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMilliSymbol, I18n::Message::UnitDistanceMeterMilli); +constexpr ToolboxMessageTree unitDistanceMeter = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterSymbol, I18n::Message::UnitDistanceMeter); +constexpr ToolboxMessageTree unitDistanceMeterKilo = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterKiloSymbol, I18n::Message::UnitDistanceMeterKilo); +constexpr ToolboxMessageTree unitDistanceAstronomicalUnit = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceAstronomicalUnitSymbol, I18n::Message::UnitDistanceAstronomicalUnit); +constexpr ToolboxMessageTree unitDistanceLightYear = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceLightYearSymbol, I18n::Message::UnitDistanceLightYear); +constexpr ToolboxMessageTree unitDistanceParsec = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceParsecSymbol, I18n::Message::UnitDistanceParsec); +constexpr ToolboxMessageTree unitDistanceInch = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceInchSymbol, I18n::Message::UnitDistanceInch); +constexpr ToolboxMessageTree unitDistanceFoot = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceFootSymbol, I18n::Message::UnitDistanceFoot); +constexpr ToolboxMessageTree unitDistanceYard = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceYardSymbol, I18n::Message::UnitDistanceYard); +constexpr ToolboxMessageTree unitDistanceMile = ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMileSymbol, I18n::Message::UnitDistanceMile); + +const ToolboxMessageTree * unitDistanceMeterChildren[] = {&unitDistanceMeterPico, &unitDistanceMeterNano, &unitDistanceMeterMicro, &unitDistanceMeterMilli, &unitDistanceMeter, &unitDistanceMeterKilo}; +const ToolboxMessageTree unitDistanceMeterNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitDistanceMeterChildren); +const ToolboxMessageTree * unitDistanceChildrenForImperialToolbox[] = { + &unitDistanceInch, + &unitDistanceFoot, + &unitDistanceYard, + &unitDistanceMile, + &unitDistanceAstronomicalUnit, + &unitDistanceLightYear, + &unitDistanceParsec, + &unitDistanceMeterNode +}; +const ToolboxMessageTree * unitDistanceImperialChildren[] = {&unitDistanceInch, &unitDistanceFoot, &unitDistanceYard, &unitDistanceMile}; +const ToolboxMessageTree unitDistanceImperialNode = ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitDistanceImperialChildren); +const ToolboxMessageTree * unitDistanceChildrenForMetricToolbox[] = { + &unitDistanceMeterPico, + &unitDistanceMeterNano, + &unitDistanceMeterMicro, + &unitDistanceMeterMilli, + &unitDistanceMeter, + &unitDistanceMeterKilo, + &unitDistanceAstronomicalUnit, + &unitDistanceLightYear, + &unitDistanceParsec, + &unitDistanceImperialNode +}; +const ToolboxMessageTree unitDistanceFork[] = { + ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildrenForMetricToolbox), + ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildrenForImperialToolbox) }; -const ToolboxMessageTree unitDistanceChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitDistanceMeterMenu, unitDistanceMeterChildren), - ToolboxMessageTree::Node(I18n::Message::UnitDistanceImperialMenu, unitDistanceImperialChildren), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceAstronomicalUnitSymbol, I18n::Message::UnitDistanceAstronomicalUnit), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceLightYearSymbol, I18n::Message::UnitDistanceLightYear), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceParsecSymbol, I18n::Message::UnitDistanceParsec)}; +constexpr ToolboxMessageTree unitMassGramMicro = ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMicroSymbol, I18n::Message::UnitMassGramMicro); +constexpr ToolboxMessageTree unitMassGramMilli = ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMilliSymbol, I18n::Message::UnitMassGramMilli); +constexpr ToolboxMessageTree unitMassGram = ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramSymbol, I18n::Message::UnitMassGram); +constexpr ToolboxMessageTree unitMassGramKilo = ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramKiloSymbol, I18n::Message::UnitMassGramKilo); +constexpr ToolboxMessageTree unitMassTonne = ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne); +constexpr ToolboxMessageTree unitMassOunce = ToolboxMessageTree::Leaf(I18n::Message::UnitMassOunceSymbol, I18n::Message::UnitMassOunce); +constexpr ToolboxMessageTree unitMassPound = ToolboxMessageTree::Leaf(I18n::Message::UnitMassPoundSymbol, I18n::Message::UnitMassPound); +constexpr ToolboxMessageTree unitMassShortTon = ToolboxMessageTree::Leaf(I18n::Message::UnitMassShortTonSymbol, I18n::Message::UnitMassShortTon); +constexpr ToolboxMessageTree unitMassLongTon = ToolboxMessageTree::Leaf(I18n::Message::UnitMassLongTonSymbol, I18n::Message::UnitMassLongTon); -const ToolboxMessageTree unitMassImperialChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitMassPoundSymbol, I18n::Message::UnitMassPound), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassOunceSymbol, I18n::Message::UnitMassOunce), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonSymbol, I18n::Message::UnitMassTon), +const ToolboxMessageTree * unitMassGramChildren[] = {&unitMassGramMicro, &unitMassGramMilli, &unitMassGram, &unitMassGramKilo, &unitMassTonne}; +const ToolboxMessageTree unitMassGramNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitMassGramChildren); +const ToolboxMessageTree * unitMassChildrenForImperialToolbox[] = { + &unitMassOunce, + &unitMassPound, + &unitMassShortTon, + &unitMassLongTon, + &unitMassGramNode }; - -const ToolboxMessageTree unitMassChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitMassImperialMenu, unitMassImperialChildren), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramKiloSymbol, I18n::Message::UnitMassGramKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramSymbol, I18n::Message::UnitMassGram), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMilliSymbol, I18n::Message::UnitMassGramMilli), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMicroSymbol, I18n::Message::UnitMassGramMicro), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramNanoSymbol, I18n::Message::UnitMassGramNano), +const ToolboxMessageTree * unitMassImperialChildren[] = {&unitMassOunce, &unitMassPound, &unitMassShortTon, &unitMassLongTon}; +const ToolboxMessageTree unitMassImperialNode = ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitMassImperialChildren); +const ToolboxMessageTree * unitMassChildrenForMetricToolbox[] = { + &unitMassGramMicro, + &unitMassGramMilli, + &unitMassGram, + &unitMassGramKilo, + &unitMassTonne, + &unitMassImperialNode +}; +const ToolboxMessageTree unitMassFork[] = { + ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildrenForMetricToolbox), + ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildrenForImperialToolbox), }; const ToolboxMessageTree unitCurrentAmpereChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereSymbol, I18n::Message::UnitCurrentAmpere), - ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMilliSymbol, I18n::Message::UnitCurrentAmpereMilli), ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMicroSymbol, I18n::Message::UnitCurrentAmpereMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMilliSymbol, I18n::Message::UnitCurrentAmpereMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereSymbol, I18n::Message::UnitCurrentAmpere), }; const ToolboxMessageTree unitTemperatureChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureKelvinSymbol, I18n::Message::UnitTemperatureKelvin)}; + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureKelvinSymbol, I18n::Message::UnitTemperatureKelvin), + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureCelsiusSymbol, I18n::Message::UnitTemperatureCelsius), + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureFahrenheitSymbol, I18n::Message::UnitTemperatureFahrenheit), +}; const ToolboxMessageTree unitAmountMoleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleSymbol, I18n::Message::UnitAmountMole), - ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleMilliSymbol, I18n::Message::UnitAmountMoleMilli), ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleMicroSymbol, I18n::Message::UnitAmountMoleMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleMilliSymbol, I18n::Message::UnitAmountMoleMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleSymbol, I18n::Message::UnitAmountMole), }; const ToolboxMessageTree unitLuminousIntensityChildren[] = { @@ -171,15 +220,16 @@ const ToolboxMessageTree unitIlluminanceChildren[] = { }; const ToolboxMessageTree unitFrequencyHertzChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzGigaSymbol, I18n::Message::UnitFrequencyHertzGiga), - ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzMegaSymbol, I18n::Message::UnitFrequencyHertzMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzSymbol, I18n::Message::UnitFrequencyHertz), ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzKiloSymbol, I18n::Message::UnitFrequencyHertzKilo), -ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzSymbol, I18n::Message::UnitFrequencyHertz)}; + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzMegaSymbol, I18n::Message::UnitFrequencyHertzMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzGigaSymbol, I18n::Message::UnitFrequencyHertzGiga) +}; const ToolboxMessageTree unitForceNewtonChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonKiloSymbol, I18n::Message::UnitForceNewtonKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonSymbol, I18n::Message::UnitForceNewton), ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonMilliSymbol, I18n::Message::UnitForceNewtonMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonSymbol, I18n::Message::UnitForceNewton), + ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonKiloSymbol, I18n::Message::UnitForceNewtonKilo), }; const ToolboxMessageTree unitPressureChildren[] = { @@ -189,29 +239,27 @@ const ToolboxMessageTree unitPressureChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitPressureAtmSymbol, I18n::Message::UnitPressureAtm)}; const ToolboxMessageTree unitEnergyJouleChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleKiloSymbol, I18n::Message::UnitEnergyJouleKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleSymbol, I18n::Message::UnitEnergyJoule), ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleMilliSymbol, I18n::Message::UnitEnergyJouleMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleSymbol, I18n::Message::UnitEnergyJoule), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleKiloSymbol, I18n::Message::UnitEnergyJouleKilo), }; - const ToolboxMessageTree unitEnergyElectronVoltChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltMegaSymbol, I18n::Message::UnitEnergyElectronVoltMega), - ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltKiloSymbol, I18n::Message::UnitEnergyElectronVoltKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltSymbol, I18n::Message::UnitEnergyElectronVolt), ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltMilliSymbol, I18n::Message::UnitEnergyElectronVoltMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltSymbol, I18n::Message::UnitEnergyElectronVolt), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltKiloSymbol, I18n::Message::UnitEnergyElectronVoltKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltMegaSymbol, I18n::Message::UnitEnergyElectronVoltMega), }; - const ToolboxMessageTree unitEnergyChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitEnergyJouleMenu, unitEnergyJouleChildren), ToolboxMessageTree::Node(I18n::Message::UnitEnergyEletronVoltMenu, unitEnergyElectronVoltChildren)}; const ToolboxMessageTree unitPowerWattChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattGigaSymbol, I18n::Message::UnitPowerWattGiga), - ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMegaSymbol, I18n::Message::UnitPowerWattMega), - ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattKiloSymbol, I18n::Message::UnitPowerWattKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattSymbol, I18n::Message::UnitPowerWatt), - ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMilliSymbol, I18n::Message::UnitPowerWattMilli), ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMicroSymbol, I18n::Message::UnitPowerWattMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMilliSymbol, I18n::Message::UnitPowerWattMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattSymbol, I18n::Message::UnitPowerWatt), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattKiloSymbol, I18n::Message::UnitPowerWattKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMegaSymbol, I18n::Message::UnitPowerWattMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattGigaSymbol, I18n::Message::UnitPowerWattGiga), }; const ToolboxMessageTree unitElectricChargeCoulombChildren[] = { @@ -220,26 +268,26 @@ const ToolboxMessageTree unitElectricChargeCoulombChildren[] = { const ToolboxMessageTree unitPotentialVoltChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltKiloSymbol, I18n::Message::UnitPotentialVoltKilo), - ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltSymbol, I18n::Message::UnitPotentialVolt), - ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltMilliSymbol, I18n::Message::UnitPotentialVoltMilli), ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltMicroSymbol, I18n::Message::UnitPotentialVoltMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltMilliSymbol, I18n::Message::UnitPotentialVoltMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltSymbol, I18n::Message::UnitPotentialVolt), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltKiloSymbol, I18n::Message::UnitPotentialVoltKilo), }; const ToolboxMessageTree unitCapacitanceFaradChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradSymbol, I18n::Message::UnitCapacitanceFarad), - ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradMilliSymbol, I18n::Message::UnitCapacitanceFaradMilli), ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradMicroSymbol, I18n::Message::UnitCapacitanceFaradMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradMilliSymbol, I18n::Message::UnitCapacitanceFaradMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradSymbol, I18n::Message::UnitCapacitanceFarad), }; const ToolboxMessageTree unitResistanceOhmChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitResistanceOhmKiloSymbol, I18n::Message::UnitResistanceOhmKilo), ToolboxMessageTree::Leaf(I18n::Message::UnitResistanceOhmSymbol, I18n::Message::UnitResistanceOhm), + ToolboxMessageTree::Leaf(I18n::Message::UnitResistanceOhmKiloSymbol, I18n::Message::UnitResistanceOhmKilo), }; const ToolboxMessageTree unitConductanceSiemensChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitConductanceSiemensSymbol, I18n::Message::UnitConductanceSiemens), ToolboxMessageTree::Leaf(I18n::Message::UnitConductanceSiemensMilliSymbol, I18n::Message::UnitConductanceSiemensMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitConductanceSiemensSymbol, I18n::Message::UnitConductanceSiemens), }; const ToolboxMessageTree unitMagneticFieldChildren[] = { @@ -248,20 +296,63 @@ const ToolboxMessageTree unitMagneticFieldChildren[] = { const ToolboxMessageTree unitInductanceChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitInductanceHenrySymbol, I18n::Message::UnitInductanceHenry)}; -const ToolboxMessageTree unitSurfaceChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar)}; +const ToolboxMessageTree unitSurfaceChildrenForMetricToolbox[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar), + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceAcreSymbol, I18n::Message::UnitSurfaceAcre) +}; +const ToolboxMessageTree unitSurfaceChildrenForImperialToolbox[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceAcreSymbol, I18n::Message::UnitSurfaceAcre), + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar) +}; +const ToolboxMessageTree unitSurfaceFork[] = { + ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildrenForMetricToolbox), + ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildrenForImperialToolbox) +}; -const ToolboxMessageTree unitVolumeLiterChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterSymbol, I18n::Message::UnitVolumeLiter), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterDeciSymbol, I18n::Message::UnitVolumeLiterDeci), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterCentiSymbol, I18n::Message::UnitVolumeLiterCenti), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterMilliSymbol, I18n::Message::UnitVolumeLiterMilli), +constexpr ToolboxMessageTree unitVolumeLiterMilli = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterMilliSymbol, I18n::Message::UnitVolumeLiterMilli); +constexpr ToolboxMessageTree unitVolumeLiterCenti = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterCentiSymbol, I18n::Message::UnitVolumeLiterCenti); +constexpr ToolboxMessageTree unitVolumeLiterDeci = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterDeciSymbol, I18n::Message::UnitVolumeLiterDeci); +constexpr ToolboxMessageTree unitVolumeLiter = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterSymbol, I18n::Message::UnitVolumeLiter); +constexpr ToolboxMessageTree unitVolumeTeaspoon = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTeaspoonSymbol, I18n::Message::UnitVolumeTeaspoon); +constexpr ToolboxMessageTree unitVolumeTablespoon = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTablespoonSymbol, I18n::Message::UnitVolumeTablespoon); +constexpr ToolboxMessageTree unitVolumeFluidOunce = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeFluidOunceSymbol, I18n::Message::UnitVolumeFluidOunce); +constexpr ToolboxMessageTree unitVolumeCup = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeCupSymbol, I18n::Message::UnitVolumeCup); +constexpr ToolboxMessageTree unitVolumePint = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumePintSymbol, I18n::Message::UnitVolumePint); +constexpr ToolboxMessageTree unitVolumeQuart = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeQuartSymbol, I18n::Message::UnitVolumeQuart); +constexpr ToolboxMessageTree unitVolumeGallon = ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeGallonSymbol, I18n::Message::UnitVolumeGallon); + +const ToolboxMessageTree * unitVolumeLiterChildren[] = {&unitVolumeLiterMilli, &unitVolumeLiterCenti, &unitVolumeLiterDeci, &unitVolumeLiter}; +const ToolboxMessageTree unitVolumeLiterNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitVolumeLiterChildren); +const ToolboxMessageTree * unitVolumeChildrenForImperialToolbox[] = { + &unitVolumeTeaspoon, + &unitVolumeTablespoon, + &unitVolumeFluidOunce, + &unitVolumeCup, + &unitVolumePint, + &unitVolumeQuart, + &unitVolumeGallon, + &unitVolumeLiterNode +}; +const ToolboxMessageTree * unitVolumeImperialChildren[] = {&unitVolumeTeaspoon, &unitVolumeTablespoon, &unitVolumeFluidOunce, &unitVolumeCup, &unitVolumePint, &unitVolumeQuart, &unitVolumeGallon}; +const ToolboxMessageTree unitVolumeImperialNode = ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitVolumeImperialChildren); +const ToolboxMessageTree * unitVolumeChildrenForMetricToolbox[] = { + &unitVolumeLiterMilli, + &unitVolumeLiterCenti, + &unitVolumeLiterDeci, + &unitVolumeLiter, + &unitVolumeImperialNode +}; +const ToolboxMessageTree unitVolumeFork[] = { + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeChildrenForMetricToolbox), + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeChildrenForImperialToolbox), }; const ToolboxMessageTree unitChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitTimeMenu, unitTimeChildren), - ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildren), - ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildren), + ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceFork, true), + ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceFork, true), + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeFork, true), + ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassFork, true), ToolboxMessageTree::Node(I18n::Message::UnitCurrentMenu, unitCurrentAmpereChildren), ToolboxMessageTree::Node(I18n::Message::UnitTemperatureMenu, unitTemperatureChildren), ToolboxMessageTree::Node(I18n::Message::UnitAmountMenu, unitAmountMoleChildren), @@ -281,8 +372,6 @@ const ToolboxMessageTree unitChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitConductanceMenu, unitConductanceSiemensChildren), ToolboxMessageTree::Node(I18n::Message::UnitMagneticFieldMenu, unitMagneticFieldChildren), ToolboxMessageTree::Node(I18n::Message::InductanceMenu, unitInductanceChildren), - ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildren), - ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeLiterChildren), }; const ToolboxMessageTree randomAndApproximationChildren[] = { @@ -719,6 +808,7 @@ const ToolboxMessageTree menu[] = { ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), ToolboxMessageTree::Node(I18n::Message::Arithmetic, arithmeticChildren), ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), + ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren), #if LIST_ARE_DEFINED ToolboxMessageTree::Node(I18n::Message::Lists,listsChildren), #endif @@ -773,3 +863,13 @@ MessageTableCellWithChevron* MathToolbox::nodeCellAtIndex(int index) { int MathToolbox::maxNumberOfDisplayedRows() { return k_maxNumberOfDisplayedRows; } + +int MathToolbox::indexAfterFork() const { + Preferences::UnitFormat unitFormat = GlobalPreferences::sharedGlobalPreferences()->unitFormat(); + if (unitFormat == Preferences::UnitFormat::Metric) { + return 0; + } + assert(unitFormat == Preferences::UnitFormat::Imperial); + return 1; +} + diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index d0301efc0..2ade3318f 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -15,6 +15,8 @@ protected: int maxNumberOfDisplayedRows() override; constexpr static int k_maxNumberOfDisplayedRows = 6; // = 240/40 private: + int indexAfterFork() const override; + MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; }; diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index 1d6c968ce..f8e01ea9f 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -4,11 +4,15 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include "global_preferences.h" using namespace Poincare; using namespace Shared; @@ -76,6 +80,8 @@ int MathVariableBoxController::numberOfRows() const { return Storage::sharedStorage()->numberOfRecordsWithExtension(Ion::Storage::expExtension); case Page::Function: return Storage::sharedStorage()->numberOfRecordsWithExtension(Ion::Storage::funcExtension); + case Page::Sequence: + return Storage::sharedStorage()->numberOfRecordsWithExtension(Ion::Storage::seqExtension); default: return 0; } @@ -101,18 +107,29 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in Storage::Record record = recordAtIndex(index); char symbolName[Shared::Function::k_maxNameWithArgumentSize]; size_t symbolLength = 0; + Layout symbolLayout; if (m_currentPage == Page::Expression) { static_assert(Shared::Function::k_maxNameWithArgumentSize > Poincare::SymbolAbstract::k_maxNameSize, "Forgot argument's size?"); symbolLength = SymbolAbstract::TruncateExtension(symbolName, record.fullName(), SymbolAbstract::k_maxNameSize); - } else { - assert(m_currentPage == Page::Function); + } else if (m_currentPage == Page::Function) { ContinuousFunction f(record); symbolLength = f.nameWithArgument( symbolName, Shared::Function::k_maxNameWithArgumentSize ); + } else { + assert(m_currentPage == Page::Sequence); + Shared::Sequence u(record); + symbolLength = u.nameWithArgumentAndType( + symbolName, + Shared::Sequence::k_maxNameWithArgumentSize + ); + Expression symbolExpression = Expression::ParseAndSimplify(symbolName, AppsContainer::sharedAppsContainer()->globalContext(), Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); + symbolLayout = symbolExpression.createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits()); + } + if (symbolLayout.isUninitialized()) { + symbolLayout = LayoutHelper::String(symbolName, symbolLength); } - Layout symbolLayout = LayoutHelper::String(symbolName, symbolLength); myCell->setLayout(symbolLayout); myCell->setAccessoryLayout(expressionLayoutForRecord(record, index)); myCell->reloadScroll(); @@ -147,7 +164,7 @@ MessageTableCellWithChevron * MathVariableBoxController::nodeCellAtIndex(int ind } MathVariableBoxController::Page MathVariableBoxController::pageAtIndex(int index) { - Page pages[2] = {Page::Expression, Page::Function}; + Page pages[k_numberOfMenuRows] = {Page::Expression, Page::Function, Page::Sequence}; return pages[index]; } @@ -192,7 +209,7 @@ bool MathVariableBoxController::selectLeaf(int selectedRow) { char nameToHandle[nameToHandleMaxSize]; size_t nameLength = SymbolAbstract::TruncateExtension(nameToHandle, record.fullName(), nameToHandleMaxSize); - if (m_currentPage == Page::Function) { + if (m_currentPage == Page::Function || m_currentPage == Page::Sequence) { // Add parentheses to a function name assert(nameLength < nameToHandleMaxSize); nameLength += UTF8Decoder::CodePointToChars('(', nameToHandle+nameLength, nameToHandleMaxSize - nameLength); @@ -212,7 +229,7 @@ bool MathVariableBoxController::selectLeaf(int selectedRow) { I18n::Message MathVariableBoxController::nodeLabelAtIndex(int index) { assert(m_currentPage == Page::RootMenu); - I18n::Message labels[2] = {I18n::Message::Expressions, I18n::Message::Functions}; + I18n::Message labels[k_numberOfMenuRows] = {I18n::Message::Expressions, I18n::Message::Functions, I18n::Message::Sequences}; return labels[index]; } @@ -248,7 +265,14 @@ Layout MathVariableBoxController::expressionLayoutForRecord(Storage::Record reco const char * MathVariableBoxController::extension() const { assert(m_currentPage != Page::RootMenu); - return m_currentPage == Page::Function ? Ion::Storage::funcExtension : Ion::Storage::expExtension; + if (m_currentPage == Page::Function) { + return Ion::Storage::funcExtension; + } else if (m_currentPage == Page::Expression) { + return Ion::Storage::expExtension; + } else { + assert(m_currentPage == Page::Sequence); + return Ion::Storage::seqExtension; + } } Storage::Record MathVariableBoxController::recordAtIndex(int rowIndex) { diff --git a/apps/math_variable_box_controller.h b/apps/math_variable_box_controller.h index 8e6052a11..86f968303 100644 --- a/apps/math_variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -27,13 +27,14 @@ public: enum class Page { RootMenu = 0, Expression = 1, - Function = 2 + Function = 2, + Sequence = 3 }; void lockDeleteEvent(Page page) { m_lockPageDelete = page; } private: constexpr static int k_maxNumberOfDisplayedRows = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin - Metric::StackTitleHeight) / Metric::ToolboxRowHeight + 2; // (240 - 18 - 50 - 20) / 40 = 3.8; the 0.8 cell can be above and below so we add +2 to get 5 - constexpr static int k_numberOfMenuRows = 2; + constexpr static int k_numberOfMenuRows = 3; constexpr static KDCoordinate k_leafMargin = 20; ExpressionTableCellWithExpression * leafCellAtIndex(int index) override; MessageTableCellWithChevron * nodeCellAtIndex(int index) override; diff --git a/apps/math_variable_box_empty_controller.cpp b/apps/math_variable_box_empty_controller.cpp index f5ad187f7..4c832c195 100644 --- a/apps/math_variable_box_empty_controller.cpp +++ b/apps/math_variable_box_empty_controller.cpp @@ -45,6 +45,13 @@ void MathVariableBoxEmptyController::setType(Type type) { layout = Poincare::LayoutHelper::String(storeFunction, strlen(storeFunction), MathVariableBoxEmptyView::k_font); break; } + case Type::Sequence: + { + messages[0] = I18n::Message::EmptySequenceBox0; + messages[1] = I18n::Message::EmptySequenceBox1; + messages[3] = I18n::Message::Default; + break; + } default: assert(false); } diff --git a/apps/math_variable_box_empty_controller.h b/apps/math_variable_box_empty_controller.h index 120ff42b6..c50665303 100644 --- a/apps/math_variable_box_empty_controller.h +++ b/apps/math_variable_box_empty_controller.h @@ -13,7 +13,8 @@ public: enum class Type { None = 0, Expressions = 1, - Functions = 2 + Functions = 2, + Sequence = 3 }; void setType(Type type); // View Controller diff --git a/apps/on_boarding/Makefile b/apps/on_boarding/Makefile index 42d22f508..ffefeaf11 100644 --- a/apps/on_boarding/Makefile +++ b/apps/on_boarding/Makefile @@ -1,9 +1,9 @@ app_on_boarding_src = $(addprefix apps/on_boarding/,\ app.cpp \ - language_controller.cpp \ logo_controller.cpp \ logo_view.cpp \ - pop_up_controller.cpp \ + localization_controller.cpp \ + prompt_controller.cpp \ power_on_self_test.cpp \ ) diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index f4c8697b4..3e914f7d9 100644 --- a/apps/on_boarding/app.cpp +++ b/apps/on_boarding/app.cpp @@ -1,4 +1,5 @@ #include "app.h" +#include "../apps_container.h" #include namespace OnBoarding { @@ -13,8 +14,8 @@ App::Descriptor * App::Snapshot::descriptor() { } App::App(Snapshot * snapshot) : - ::App(snapshot, &m_languageController), - m_languageController(&m_modalViewController), + ::App(snapshot, &m_localizationController), + m_localizationController(&m_modalViewController, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_logoController() { } @@ -44,7 +45,7 @@ void App::didBecomeActive(Window * window) { } void App::reinitOnBoarding() { - m_languageController.resetSelection(); + m_localizationController.resetSelection(); displayModalViewController(&m_logoController, 0.5f, 0.5f); } diff --git a/apps/on_boarding/app.h b/apps/on_boarding/app.h index 24a600343..d9dbe43a4 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -2,18 +2,20 @@ #define ON_BOARDING_APP_H #include -#include "language_controller.h" #include "logo_controller.h" +#include "localization_controller.h" +#include "../shared/shared_app.h" namespace OnBoarding { class App : public ::App { public: - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: App * unpack(Container * container) override; Descriptor * descriptor() override; }; + int numberOfTimers() override; Timer * timerAtIndex(int i) override; bool processEvent(Ion::Events::Event) override; @@ -21,7 +23,7 @@ public: private: App(Snapshot * snapshot); void reinitOnBoarding(); - LanguageController m_languageController; + LocalizationController m_localizationController; LogoController m_logoController; }; diff --git a/apps/on_boarding/base.fr.i18n b/apps/on_boarding/base.fr.i18n index fdd2dca84..6f25f930d 100644 --- a/apps/on_boarding/base.fr.i18n +++ b/apps/on_boarding/base.fr.i18n @@ -9,5 +9,5 @@ BetaVersionMessage2 = "du logiciel. Il est possible que certains" BetaVersionMessage3 = "bugs apparaissent." BetaVersionMessage4 = "Vous pouvez nous écrire pour nous" BetaVersionMessage5 = "faire part de vos retours à" -BetaVersionMessage6 = "contact@numworks.com" +BetaVersionMessage6 = "contact@numworks.fr" Skip = "Passer" diff --git a/apps/on_boarding/base.it.i18n b/apps/on_boarding/base.it.i18n index 33ff2bd3c..90da9192f 100644 --- a/apps/on_boarding/base.it.i18n +++ b/apps/on_boarding/base.it.i18n @@ -9,5 +9,5 @@ BetaVersionMessage2 = "di una versione beta del software." BetaVersionMessage3 = "Possono comparire alcuni bugs." BetaVersionMessage4 = "Per comunicarci un riscontro" BetaVersionMessage5 = "potete scriverci a" -BetaVersionMessage6 = "contact@numworks.com" +BetaVersionMessage6 = "contatto@numworks.it" Skip = "Saltare" diff --git a/apps/on_boarding/base.pt.i18n b/apps/on_boarding/base.pt.i18n index bd16512a0..d0f0d8a07 100644 --- a/apps/on_boarding/base.pt.i18n +++ b/apps/on_boarding/base.pt.i18n @@ -9,5 +9,5 @@ BetaVersionMessage2 = "um software beta." BetaVersionMessage3 = "Pode encontrar bugs ou falhas." BetaVersionMessage4 = "" BetaVersionMessage5 = "Por favor envie-nos o seu feedback para" -BetaVersionMessage6 = "contact@numworks.com" +BetaVersionMessage6 = "contacto@numworks.pt" Skip = "Saltar" diff --git a/apps/on_boarding/language_controller.cpp b/apps/on_boarding/language_controller.cpp deleted file mode 100644 index d4a3ae679..000000000 --- a/apps/on_boarding/language_controller.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "language_controller.h" -#include "../global_preferences.h" -#include "../apps_container.h" -#include -#include - -namespace OnBoarding { - -LanguageController::LanguageController(Responder * parentResponder) : - Shared::LanguageController( - parentResponder, - std::max(static_cast(Metric::CommonLeftMargin), - (Ion::Display::Height - I18n::NumberOfLanguages*Metric::ParameterCellHeight)/2)) -{ - static_cast(m_selectableTableView.decorator()->indicatorAtIndex(1))->setMargin( - std::max(static_cast(Metric::CommonLeftMargin), - (Ion::Display::Height - I18n::NumberOfLanguages*Metric::ParameterCellHeight)/2)); -} - -bool LanguageController::handleEvent(Ion::Events::Event event) { - if (Shared::LanguageController::handleEvent(event)) { - AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); - if (appsContainer->promptController()) { - Container::activeApp()->displayModalViewController(appsContainer->promptController(), 0.5f, 0.5f); - } else { - appsContainer->switchTo(appsContainer->appSnapshotAtIndex(0)); - } - return true; - } - if (event == Ion::Events::Back) { - return true; - } - return false; -} - -} diff --git a/apps/on_boarding/language_controller.h b/apps/on_boarding/language_controller.h deleted file mode 100644 index 10ab2c995..000000000 --- a/apps/on_boarding/language_controller.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef ON_BOARDING_LANGUAGE_CONTROLLER_H -#define ON_BOARDING_LANGUAGE_CONTROLLER_H - -#include -#include "../shared/language_controller.h" -#include "logo_controller.h" - -namespace OnBoarding { - -class LanguageController : public Shared::LanguageController { -public: - LanguageController(Responder * parentResponder); - bool handleEvent(Ion::Events::Event event) override; -}; - -} - -#endif diff --git a/apps/on_boarding/localization_controller.cpp b/apps/on_boarding/localization_controller.cpp new file mode 100644 index 000000000..91f8b81cd --- /dev/null +++ b/apps/on_boarding/localization_controller.cpp @@ -0,0 +1,40 @@ +#include "localization_controller.h" +#include +#include +#include + +namespace OnBoarding { + +int LocalizationController::indexOfCellToSelectOnReset() const { + return mode() == Mode::Language ? + Shared::LocalizationController::indexOfCellToSelectOnReset() : + IndexOfCountry(I18n::DefaultCountryForLanguage[static_cast(GlobalPreferences::sharedGlobalPreferences()->language())]); +} + +bool LocalizationController::handleEvent(Ion::Events::Event event) { + if (Shared::LocalizationController::handleEvent(event)) { + if (mode() == Mode::Language) { + setMode(Mode::Country); + viewWillAppear(); + } else { + assert(mode() == Mode::Country); + AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); + if (appsContainer->promptController()) { + Container::activeApp()->displayModalViewController(appsContainer->promptController(), 0.5f, 0.5f); + } else { + appsContainer->switchTo(appsContainer->appSnapshotAtIndex(0)); + } + } + return true; + } + if (event == Ion::Events::Back) { + if (mode() == Mode::Country) { + setMode(Mode::Language); + viewWillAppear(); + } + return true; + } + return false; +} + +} diff --git a/apps/on_boarding/localization_controller.h b/apps/on_boarding/localization_controller.h new file mode 100644 index 000000000..03e0c131f --- /dev/null +++ b/apps/on_boarding/localization_controller.h @@ -0,0 +1,21 @@ +#ifndef ON_BOARDING_LOCALIZATION_CONTROLLER_H +#define ON_BOARDING_LOCALIZATION_CONTROLLER_H + +#include +#include + +namespace OnBoarding { + +class LocalizationController : public Shared::LocalizationController { +public: + using Shared::LocalizationController::LocalizationController; + + int indexOfCellToSelectOnReset() const override; + bool shouldDisplayTitle() const override { return mode() == Mode::Country; } + + bool handleEvent(Ion::Events::Event event) override; +}; + +} + +#endif diff --git a/apps/on_boarding/pop_up_controller.cpp b/apps/on_boarding/prompt_controller.cpp similarity index 76% rename from apps/on_boarding/pop_up_controller.cpp rename to apps/on_boarding/prompt_controller.cpp index c63328b9e..e938a43a0 100644 --- a/apps/on_boarding/pop_up_controller.cpp +++ b/apps/on_boarding/prompt_controller.cpp @@ -1,21 +1,21 @@ -#include "pop_up_controller.h" +#include "prompt_controller.h" #include "../apps_container.h" #include namespace OnBoarding { -PopUpController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : +PromptController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : MessageView(messages, colors, numberOfMessages), m_skipView(KDFont::SmallFont, I18n::Message::Skip, 1.0f, 0.5f), m_okView() { } -int PopUpController::MessageViewWithSkip::numberOfSubviews() const { +int PromptController::MessageViewWithSkip::numberOfSubviews() const { return MessageView::numberOfSubviews() + 2; } -View * PopUpController::MessageViewWithSkip::subviewAtIndex(int index) { +View * PromptController::MessageViewWithSkip::subviewAtIndex(int index) { uint8_t numberOfMainMessages = MessageView::numberOfSubviews(); if (index < numberOfMainMessages) { return MessageView::subviewAtIndex(index); @@ -30,7 +30,7 @@ View * PopUpController::MessageViewWithSkip::subviewAtIndex(int index) { return nullptr; } -void PopUpController::MessageViewWithSkip::layoutSubviews(bool force) { +void PromptController::MessageViewWithSkip::layoutSubviews(bool force) { // Layout the main message MessageView::layoutSubviews(); // Layout the "skip (OK)" @@ -42,13 +42,13 @@ void PopUpController::MessageViewWithSkip::layoutSubviews(bool force) { m_okView.setFrame(KDRect(width - okSize.width()-k_okMargin, height-okSize.height()-k_okMargin, okSize), force); } -PopUpController::PopUpController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : +PromptController::PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : ViewController(nullptr), m_messageViewWithSkip(messages, colors, numberOfMessages) { } -bool PopUpController::handleEvent(Ion::Events::Event event) { +bool PromptController::handleEvent(Ion::Events::Event event) { if (event != Ion::Events::Back && event != Ion::Events::OnOff && event != Ion::Events::USBPlug && event != Ion::Events::USBEnumeration) { Container::activeApp()->dismissModalViewController(); AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); diff --git a/apps/on_boarding/pop_up_controller.h b/apps/on_boarding/prompt_controller.h similarity index 80% rename from apps/on_boarding/pop_up_controller.h rename to apps/on_boarding/prompt_controller.h index f092e8853..b0ce7b615 100644 --- a/apps/on_boarding/pop_up_controller.h +++ b/apps/on_boarding/prompt_controller.h @@ -1,5 +1,5 @@ -#ifndef ON_BOARDING_POP_UP_CONTROLLER_H -#define ON_BOARDING_POP_UP_CONTROLLER_H +#ifndef ON_PROMPT_CONTROLLER_H +#define ON_PROMPT_CONTROLLER_H #include #include @@ -8,9 +8,9 @@ namespace OnBoarding { -class PopUpController : public ViewController { +class PromptController : public ViewController { public: - PopUpController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); View * view() override { return &m_messageViewWithSkip; } bool handleEvent(Ion::Events::Event event) override; private: @@ -34,4 +34,3 @@ private: } #endif - diff --git a/apps/probability/app.h b/apps/probability/app.h index be6fb5c78..d93905c46 100644 --- a/apps/probability/app.h +++ b/apps/probability/app.h @@ -14,6 +14,7 @@ #include "calculation/left_integral_calculation.h" #include "calculation/right_integral_calculation.h" #include "calculation/finite_integral_calculation.h" +#include "../shared/shared_app.h" constexpr static size_t max(const int * data, int seed = 0) { return (*data == 0 ? seed : max(data+1, *data > seed ? *data : seed)); @@ -30,7 +31,7 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: enum class Page { Distribution, @@ -50,14 +51,23 @@ public: private: constexpr static int k_distributionSizes[] = {sizeof(BinomialDistribution),sizeof(ExponentialDistribution), sizeof(NormalDistribution), sizeof(PoissonDistribution), sizeof(UniformDistribution), 0}; constexpr static size_t k_distributionSize = max(k_distributionSizes); + constexpr static int k_calculationSizes[] = {sizeof(LeftIntegralCalculation),sizeof(FiniteIntegralCalculation), sizeof(RightIntegralCalculation), 0}; + constexpr static size_t k_calculationSize = max(k_calculationSizes); void deleteDistributionAndCalculation(); void initializeDistributionAndCalculation(); +#if __EMSCRIPTEN__ + constexpr static int k_distributionAlignments[] = {alignof(BinomialDistribution),alignof(ExponentialDistribution), alignof(NormalDistribution), alignof(PoissonDistribution), alignof(UniformDistribution), 0}; + constexpr static size_t k_distributionAlignment = max(k_distributionAlignments); + constexpr static int k_calculationAlignments[] = {alignof(LeftIntegralCalculation),alignof(FiniteIntegralCalculation), alignof(RightIntegralCalculation), 0}; + constexpr static size_t k_calculationAlignment = max(k_calculationAlignments); + alignas(k_distributionAlignment) char m_distribution[k_distributionSize]; + alignas(k_calculationAlignment) char m_calculation[k_calculationSize]; +#else char m_distribution[k_distributionSize]; - constexpr static int k_calculationSizes[] = {sizeof(LeftIntegralCalculation),sizeof(FiniteIntegralCalculation), sizeof(RightIntegralCalculation), 0}; - constexpr static size_t k_calculationSize = max(k_calculationSizes); char m_calculation[k_calculationSize]; +#endif Page m_activePage; }; static App * app() { diff --git a/apps/probability/distribution/binomial_distribution.cpp b/apps/probability/distribution/binomial_distribution.cpp index 5ee9326e0..33c06da3e 100644 --- a/apps/probability/distribution/binomial_distribution.cpp +++ b/apps/probability/distribution/binomial_distribution.cpp @@ -29,7 +29,7 @@ float BinomialDistribution::evaluateAtAbscissa(float x) const { float BinomialDistribution::xMin() const { float min = 0.0f; - float max = m_parameter1 > 0.0f ? m_parameter1 : 1.0f; + float max = m_parameter1 > 0.0 ? m_parameter1 : 1.0f; return min - k_displayLeftMarginRatio * (max - min); } @@ -43,7 +43,7 @@ float BinomialDistribution::xMax() const { } float BinomialDistribution::yMax() const { - int maxAbscissa = m_parameter2 < 1.0f ? (m_parameter1+1)*m_parameter2 : m_parameter1; + int maxAbscissa = m_parameter2 < 1.0 ? (m_parameter1+1)*m_parameter2 : m_parameter1; float result = evaluateAtAbscissa(maxAbscissa); if (result <= 0.0f || std::isnan(result)) { result = 1.0f; @@ -66,7 +66,7 @@ double BinomialDistribution::cumulativeDistributiveInverseForProbability(double } double BinomialDistribution::rightIntegralInverseForProbability(double * probability) { - if (m_parameter1 == 0.0f && (m_parameter2 == 0.0f || m_parameter2 == 1.0f)) { + if (m_parameter1 == 0.0 && (m_parameter2 == 0.0 || m_parameter2 == 1.0)) { return NAN; } if (*probability <= 0.0) { @@ -76,7 +76,7 @@ double BinomialDistribution::rightIntegralInverseForProbability(double * probabi } double BinomialDistribution::evaluateAtDiscreteAbscissa(int k) const { - return Poincare::BinomialDistribution::EvaluateAtAbscissa((double) k, (double)m_parameter1, (double)m_parameter2); + return Poincare::BinomialDistribution::EvaluateAtAbscissa((double) k, m_parameter1, m_parameter2); } } diff --git a/apps/probability/distribution/chi_squared_distribution.cpp b/apps/probability/distribution/chi_squared_distribution.cpp index 9338c8c4b..5fdc3c368 100644 --- a/apps/probability/distribution/chi_squared_distribution.cpp +++ b/apps/probability/distribution/chi_squared_distribution.cpp @@ -10,7 +10,7 @@ float ChiSquaredDistribution::xMin() const { } float ChiSquaredDistribution::xMax() const { - assert(m_parameter1 != 0.0f); + assert(m_parameter1 != 0.0); return (m_parameter1 + 5.0f * std::sqrt(m_parameter1)) * (1.0f + k_displayRightMarginRatio); } @@ -28,7 +28,7 @@ float ChiSquaredDistribution::evaluateAtAbscissa(float x) const { if (x < 0.0f) { return NAN; } - const float halfk = m_parameter1/2.0f; + const float halfk = m_parameter1/2.0; const float halfX = x/2.0f; return std::exp(-lgamma(halfk) - halfX + (halfk-1.0f) * std::log(halfX)) / 2.0f; } @@ -43,7 +43,7 @@ double ChiSquaredDistribution::cumulativeDistributiveFunctionAtAbscissa(double x return 0.0; } double result = 0.0; - if (regularizedGamma(m_parameter1/2.0f, x/2.0, k_regularizedGammaPrecision, k_maxRegularizedGammaIterations, &result)) { + if (regularizedGamma(m_parameter1/2.0, x/2.0, k_regularizedGammaPrecision, k_maxRegularizedGammaIterations, &result)) { return result; } return NAN; @@ -72,9 +72,9 @@ double ChiSquaredDistribution::cumulativeDistributiveInverseForProbability(doubl return 0.0; } const double k = m_parameter1; - const double ceilKOver2 = std::ceil(k/2.0f); - const double kOver2Minus1 = k/2.0f - 1.0f; - double xmax = m_parameter1 > 2.0f ? + const double ceilKOver2 = std::ceil(k/2.0); + const double kOver2Minus1 = k/2.0 - 1.0; + double xmax = m_parameter1 > 2.0 ? 2.0 * *probability * std::exp(std::lgamma(ceilKOver2)) / (exp(-kOver2Minus1) * std::pow(kOver2Minus1, kOver2Minus1)) : 30.0; // Ad hoc value xmax = std::isnan(xmax) ? 1000000000.0 : xmax; diff --git a/apps/probability/distribution/distribution.h b/apps/probability/distribution/distribution.h index e7e9d5332..4a862422c 100644 --- a/apps/probability/distribution/distribution.h +++ b/apps/probability/distribution/distribution.h @@ -26,7 +26,7 @@ public: virtual Type type() const = 0; virtual bool isContinuous() const = 0; virtual int numberOfParameter() = 0; - virtual float parameterValueAtIndex(int index) = 0; + virtual double parameterValueAtIndex(int index) = 0; virtual I18n::Message parameterNameAtIndex(int index) = 0; virtual I18n::Message parameterDefinitionAtIndex(int index) = 0; virtual void setParameterAtIndex(float f, int index) = 0; diff --git a/apps/probability/distribution/exponential_distribution.cpp b/apps/probability/distribution/exponential_distribution.cpp index 311c2e197..acad075fe 100644 --- a/apps/probability/distribution/exponential_distribution.cpp +++ b/apps/probability/distribution/exponential_distribution.cpp @@ -36,7 +36,8 @@ float ExponentialDistribution::evaluateAtAbscissa(float x) const { if (x < 0.0f) { return NAN; } - return m_parameter1 * std::exp(-m_parameter1 * x); + float parameter = m_parameter1; + return parameter * std::exp(-parameter * x); } bool ExponentialDistribution::authorizedValueAtIndex(float x, int index) const { @@ -50,7 +51,7 @@ double ExponentialDistribution::cumulativeDistributiveFunctionAtAbscissa(double if (x < 0.0) { return 0.0; } - return 1.0 - std::exp((double)(-m_parameter1 * x)); + return 1.0 - std::exp((-m_parameter1 * x)); } double ExponentialDistribution::cumulativeDistributiveInverseForProbability(double * probability) { @@ -60,7 +61,7 @@ double ExponentialDistribution::cumulativeDistributiveInverseForProbability(doub if (*probability <= 0.0) { return 0.0; } - return -std::log(1.0 - *probability)/(double)m_parameter1; + return -std::log(1.0 - *probability)/m_parameter1; } } diff --git a/apps/probability/distribution/fisher_distribution.cpp b/apps/probability/distribution/fisher_distribution.cpp index 66201ac0c..bd365e3ba 100644 --- a/apps/probability/distribution/fisher_distribution.cpp +++ b/apps/probability/distribution/fisher_distribution.cpp @@ -60,9 +60,7 @@ void FisherDistribution::setParameterAtIndex(float f, int index) { } double FisherDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) const { - const double d1 = m_parameter1; - const double d2 = m_parameter2; - return Poincare::RegularizedIncompleteBetaFunction(d1/2.0, d2/2.0, d1*x/(d1*x+d2)); + return Poincare::RegularizedIncompleteBetaFunction(m_parameter1/2.0, m_parameter2/2.0, m_parameter1*x/(m_parameter1*x+m_parameter2)); } double FisherDistribution::cumulativeDistributiveInverseForProbability(double * probability) { diff --git a/apps/probability/distribution/geometric_distribution.h b/apps/probability/distribution/geometric_distribution.h index e3323abfa..392b4c588 100644 --- a/apps/probability/distribution/geometric_distribution.h +++ b/apps/probability/distribution/geometric_distribution.h @@ -27,13 +27,13 @@ public: return I18n::Message::SuccessProbability; } float evaluateAtAbscissa(float x) const override { - return templatedApproximateAtAbscissa(x); + return templatedApproximateAtAbscissa(x); } bool authorizedValueAtIndex(float x, int index) const override; - double defaultComputedValue() const override { return 1.0f; } + double defaultComputedValue() const override { return 1.0; } private: double evaluateAtDiscreteAbscissa(int k) const override { - return templatedApproximateAtAbscissa((double)k); + return templatedApproximateAtAbscissa(static_cast(k)); } template T templatedApproximateAtAbscissa(T x) const; }; diff --git a/apps/probability/distribution/normal_distribution.cpp b/apps/probability/distribution/normal_distribution.cpp index 932d97254..d86b04459 100644 --- a/apps/probability/distribution/normal_distribution.cpp +++ b/apps/probability/distribution/normal_distribution.cpp @@ -31,7 +31,7 @@ I18n::Message NormalDistribution::parameterDefinitionAtIndex(int index) { } float NormalDistribution::evaluateAtAbscissa(float x) const { - return Poincare::NormalDistribution::EvaluateAtAbscissa(x, m_parameter1, m_parameter2); + return Poincare::NormalDistribution::EvaluateAtAbscissa(x, m_parameter1, m_parameter2); } bool NormalDistribution::authorizedValueAtIndex(float x, int index) const { @@ -52,11 +52,11 @@ void NormalDistribution::setParameterAtIndex(float f, int index) { } double NormalDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) const { - return Poincare::NormalDistribution::CumulativeDistributiveFunctionAtAbscissa(x, m_parameter1, m_parameter2); + return Poincare::NormalDistribution::CumulativeDistributiveFunctionAtAbscissa(x, m_parameter1, m_parameter2); } double NormalDistribution::cumulativeDistributiveInverseForProbability(double * probability) { - return Poincare::NormalDistribution::CumulativeDistributiveInverseForProbability(*probability, m_parameter1, m_parameter2); + return Poincare::NormalDistribution::CumulativeDistributiveInverseForProbability(*probability, m_parameter1, m_parameter2); } float NormalDistribution::xExtremum(bool min) const { diff --git a/apps/probability/distribution/one_parameter_distribution.h b/apps/probability/distribution/one_parameter_distribution.h index df991c719..905cea080 100644 --- a/apps/probability/distribution/one_parameter_distribution.h +++ b/apps/probability/distribution/one_parameter_distribution.h @@ -10,7 +10,7 @@ class OneParameterDistribution : public Distribution { public: OneParameterDistribution(float parameterValue) : m_parameter1(parameterValue) {} int numberOfParameter() override { return 1; } - float parameterValueAtIndex(int index) override { + double parameterValueAtIndex(int index) override { assert(index == 0); return m_parameter1; } @@ -19,7 +19,7 @@ public: m_parameter1 = f; } protected: - float m_parameter1; + double m_parameter1; }; } diff --git a/apps/probability/distribution/poisson_distribution.h b/apps/probability/distribution/poisson_distribution.h index 7c6654fa8..bfbaeac34 100644 --- a/apps/probability/distribution/poisson_distribution.h +++ b/apps/probability/distribution/poisson_distribution.h @@ -23,12 +23,12 @@ public: return I18n::Message::LambdaPoissonDefinition; } float evaluateAtAbscissa(float x) const override { - return templatedApproximateAtAbscissa(x); + return templatedApproximateAtAbscissa(x); } bool authorizedValueAtIndex(float x, int index) const override; private: double evaluateAtDiscreteAbscissa(int k) const override { - return templatedApproximateAtAbscissa((double)k); + return templatedApproximateAtAbscissa(static_cast(k)); } template T templatedApproximateAtAbscissa(T x) const; }; diff --git a/apps/probability/distribution/student_distribution.cpp b/apps/probability/distribution/student_distribution.cpp index a11640113..bdeb1acf8 100644 --- a/apps/probability/distribution/student_distribution.cpp +++ b/apps/probability/distribution/student_distribution.cpp @@ -36,7 +36,7 @@ double StudentDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) c /* TODO There are some computation errors, where the probability falsly jumps to 1. * k = 0.001 and P(x < 42000000) (for 41000000 it is around 0.5) * k = 0.01 and P(x < 8400000) (for 41000000 it is around 0.6) */ - const float k = m_parameter1; + const double k = m_parameter1; const double sqrtXSquaredPlusK = std::sqrt(x*x + k); double t = (x + sqrtXSquaredPlusK) / (2.0 * sqrtXSquaredPlusK); return Poincare::RegularizedIncompleteBetaFunction(k/2.0, k/2.0, t); diff --git a/apps/probability/distribution/two_parameter_distribution.cpp b/apps/probability/distribution/two_parameter_distribution.cpp index 5c3197c1b..0c03e9742 100644 --- a/apps/probability/distribution/two_parameter_distribution.cpp +++ b/apps/probability/distribution/two_parameter_distribution.cpp @@ -3,7 +3,7 @@ namespace Probability { -float TwoParameterDistribution::parameterValueAtIndex(int index) { +double TwoParameterDistribution::parameterValueAtIndex(int index) { assert(index >= 0 && index < 2); if (index == 0) { return m_parameter1; diff --git a/apps/probability/distribution/two_parameter_distribution.h b/apps/probability/distribution/two_parameter_distribution.h index 0f8d68f44..332a9f92a 100644 --- a/apps/probability/distribution/two_parameter_distribution.h +++ b/apps/probability/distribution/two_parameter_distribution.h @@ -12,11 +12,11 @@ public: m_parameter2(parameterValue2) {} int numberOfParameter() override { return 2; } - float parameterValueAtIndex(int index) override; + double parameterValueAtIndex(int index) override; void setParameterAtIndex(float f, int index) override; protected: - float m_parameter1; - float m_parameter2; + double m_parameter1; + double m_parameter2; }; } diff --git a/apps/probability/distribution/uniform_distribution.cpp b/apps/probability/distribution/uniform_distribution.cpp index 0a79ae160..94df87f34 100644 --- a/apps/probability/distribution/uniform_distribution.cpp +++ b/apps/probability/distribution/uniform_distribution.cpp @@ -48,14 +48,16 @@ float UniformDistribution::yMax() const { } float UniformDistribution::evaluateAtAbscissa(float t) const { - if (m_parameter2 - m_parameter1 < FLT_EPSILON) { - if (m_parameter1 - k_diracWidth<= t && t <= m_parameter2 + k_diracWidth) { + float parameter1 = m_parameter1; + float parameter2 = m_parameter2; + if (parameter2 - parameter1 < FLT_EPSILON) { + if (parameter1 - k_diracWidth<= t && t <= parameter2 + k_diracWidth) { return 2.0f * k_diracMaximum; } return 0.0f; } - if (m_parameter1 <= t && t <= m_parameter2) { - return (1.0f/(m_parameter2 - m_parameter1)); + if (parameter1 <= t && t <= parameter2) { + return (1.0f/(parameter2 - parameter1)); } return 0.0f; } @@ -73,7 +75,7 @@ bool UniformDistribution::authorizedValueAtIndex(float x, int index) const { void UniformDistribution::setParameterAtIndex(float f, int index) { TwoParameterDistribution::setParameterAtIndex(f, index); if (index == 0 && m_parameter2 < m_parameter1) { - m_parameter2 = m_parameter1 + 1.0f; + m_parameter2 = m_parameter1 + 1.0; } } diff --git a/apps/regression/Makefile b/apps/regression/Makefile index 2211e3192..56daa0ef2 100644 --- a/apps/regression/Makefile +++ b/apps/regression/Makefile @@ -31,7 +31,6 @@ app_regression_src = $(addprefix apps/regression/,\ graph_options_controller.cpp \ graph_view.cpp \ go_to_parameter_controller.cpp \ - initialisation_parameter_controller.cpp \ regression_controller.cpp \ store_controller.cpp \ store_parameter_controller.cpp \ diff --git a/apps/regression/app.cpp b/apps/regression/app.cpp index 0bef9c82b..80069de44 100644 --- a/apps/regression/app.cpp +++ b/apps/regression/app.cpp @@ -27,7 +27,6 @@ App::Snapshot::Snapshot() : m_store(), m_cursor(), m_graphSelectedDotIndex(-1), - m_modelVersion(0), m_rangeVersion(0), m_selectedSeriesIndex(-1) { @@ -39,7 +38,6 @@ App * App::Snapshot::unpack(Container * container) { void App::Snapshot::reset() { m_store.reset(); - m_modelVersion = 0; m_rangeVersion = 0; setActiveTab(0); } @@ -59,7 +57,7 @@ App::App(Snapshot * snapshot, Poincare::Context * parentContext) : m_calculationController(&m_calculationAlternateEmptyViewController, &m_calculationHeader, snapshot->store()), m_calculationAlternateEmptyViewController(&m_calculationHeader, &m_calculationController, &m_calculationController), m_calculationHeader(&m_tabViewController, &m_calculationAlternateEmptyViewController, &m_calculationController), - m_graphController(&m_graphAlternateEmptyViewController, this, &m_graphHeader, snapshot->store(), snapshot->cursor(), snapshot->modelVersion(), snapshot->previousModelsVersions(), snapshot->rangeVersion(), snapshot->graphSelectedDotIndex(), snapshot->selectedSeriesIndex()), + m_graphController(&m_graphAlternateEmptyViewController, this, &m_graphHeader, snapshot->store(), snapshot->cursor(), snapshot->rangeVersion(), snapshot->graphSelectedDotIndex(), snapshot->selectedSeriesIndex()), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), diff --git a/apps/regression/app.h b/apps/regression/app.h index c00e8b4ad..c7ed77286 100644 --- a/apps/regression/app.h +++ b/apps/regression/app.h @@ -8,6 +8,7 @@ #include "regression_controller.h" #include "store.h" #include "store_controller.h" +#include "../shared/shared_app.h" namespace Regression { @@ -20,7 +21,7 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot, public TabViewDataSource { + class Snapshot : public ::SharedApp::Snapshot, public TabViewDataSource { public: Snapshot(); App * unpack(Container * container) override; @@ -30,15 +31,12 @@ public: Shared::CurveViewCursor * cursor() { return &m_cursor; } int * graphSelectedDotIndex() { return &m_graphSelectedDotIndex; } int * selectedSeriesIndex() { return &m_selectedSeriesIndex; } - uint32_t * modelVersion() { return &m_modelVersion; } - uint32_t * previousModelsVersions() { return m_store.seriesChecksum(); } uint32_t * rangeVersion() { return &m_rangeVersion; } private: void tidy() override; Store m_store; Shared::CurveViewCursor m_cursor; int m_graphSelectedDotIndex; - uint32_t m_modelVersion; uint32_t m_rangeVersion; int m_selectedSeriesIndex; }; diff --git a/apps/regression/base.de.i18n b/apps/regression/base.de.i18n index b49e91e7c..619e8a0ba 100644 --- a/apps/regression/base.de.i18n +++ b/apps/regression/base.de.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regression" RegressionAppCapital = "REGRESSION" Regression = "Regression" -Reg = "reg" MeanDot = "mittel" RegressionCurve = "Regressionskurve" XPrediction = "Berechne Y" diff --git a/apps/regression/base.en.i18n b/apps/regression/base.en.i18n index 7b257c653..8ee05f87a 100644 --- a/apps/regression/base.en.i18n +++ b/apps/regression/base.en.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regression" RegressionAppCapital = "REGRESSION" Regression = "Regression" -Reg = "reg" MeanDot = "mean" RegressionCurve = "Regression curve" XPrediction = "Prediction given X" diff --git a/apps/regression/base.es.i18n b/apps/regression/base.es.i18n index 147304a40..30fa05f51 100644 --- a/apps/regression/base.es.i18n +++ b/apps/regression/base.es.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regresión" RegressionAppCapital = "REGRESIÓN" Regression = "Regresión" -Reg = "reg" MeanDot = "media" RegressionCurve = "Curva de regresión" XPrediction = "Predicción dado X" diff --git a/apps/regression/base.fr.i18n b/apps/regression/base.fr.i18n index c8f80d875..21a47ed37 100644 --- a/apps/regression/base.fr.i18n +++ b/apps/regression/base.fr.i18n @@ -1,7 +1,6 @@ RegressionApp = "Régressions" RegressionAppCapital = "RÉGRESSIONS" Regression = "Régression" -Reg = "reg" MeanDot = "moyen" RegressionCurve = "Courbe de régression" XPrediction = "Prédiction sachant X" diff --git a/apps/regression/base.it.i18n b/apps/regression/base.it.i18n index 06976e196..a7976dea5 100644 --- a/apps/regression/base.it.i18n +++ b/apps/regression/base.it.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regressione" RegressionAppCapital = "REGRESSIONE" Regression = "Regressione" -Reg = "reg" MeanDot = "media" RegressionCurve = "Curva di regressione" XPrediction = "Previsione data X" diff --git a/apps/regression/base.nl.i18n b/apps/regression/base.nl.i18n index f5411a0a0..1aaa848e8 100644 --- a/apps/regression/base.nl.i18n +++ b/apps/regression/base.nl.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regressie" RegressionAppCapital = "REGRESSIE" Regression = "Regressie" -Reg = "reg" MeanDot = "gemiddelde" RegressionCurve = "Regressielijn" XPrediction = "Voorspelling gegeven X" @@ -11,9 +10,9 @@ NumberOfDots = "Aantal punten" Covariance = "Covariantie" Linear = "Lineair" Proportional = "Proportioneel" -Quadratic = "Kwadratisch" -Cubic = "Kubiek" -Quartic = "Quartic" +Quadratic = "Tweedegraads" +Cubic = "Derdegraads" +Quartic = "Vierdegraads" Logarithmic = "Logaritmisch" Power = "Macht" Trigonometrical = "Trigonometrisch" diff --git a/apps/regression/base.pt.i18n b/apps/regression/base.pt.i18n index e5db50f79..9bdf48065 100644 --- a/apps/regression/base.pt.i18n +++ b/apps/regression/base.pt.i18n @@ -1,11 +1,10 @@ RegressionApp = "Regressão" RegressionAppCapital = "REGRESSÃO" Regression = "Regressão" -Reg = "reg" MeanDot = "média" RegressionCurve = "Curva de regressão" -XPrediction = "Predição dado X" -YPrediction = "Predição dado Y" +XPrediction = "Previsão dado X" +YPrediction = "Previsão dado Y" ValueNotReachedByRegression = "Valor não alcançado nesta janela" NumberOfDots = "Número de pontos" Covariance = "Covariância" diff --git a/apps/regression/base.universal.i18n b/apps/regression/base.universal.i18n index a26602af1..e974d8a9f 100644 --- a/apps/regression/base.universal.i18n +++ b/apps/regression/base.universal.i18n @@ -8,3 +8,4 @@ PowerRegressionFormula = " y=a·x^b " TrigonometricRegressionFormula = " y=a·sin(b·x+c)+d " LogisticRegressionFormula = " y=c/(1+a·exp(-b·x)) " Dash = "-" +Reg = "reg" diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 34b8e70ea..14b68c105 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -116,7 +116,8 @@ Responder * CalculationController::defaultController() { } int CalculationController::numberOfRows() const { - return 1 + k_totalNumberOfDoubleBufferRows + 4 + maxNumberOfCoefficients() + hasLinearRegression() * 2; + // rows for : title + Mean ... Variance + Number of points ... Regression + Coefficients + (R) + R2 + return 1 + k_totalNumberOfDoubleBufferRows + 4 + maxNumberOfCoefficients() + hasLinearRegression() + 1; } int CalculationController::numberOfColumns() const { @@ -131,11 +132,10 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int myCell->setEven(j%2 == 0); myCell->setHighlighted(i == selectedColumn() && j == selectedRow()); + const int numberRows = numberOfRows(); // Calculation title if (i == 0) { - bool shouldDisplayRAndR2 = hasLinearRegression(); - int numberRows = numberOfRows(); - if (shouldDisplayRAndR2 && j == numberRows-1) { + if (j == numberRows - 1) { EvenOddExpressionCell * myCell = (EvenOddExpressionCell *)cell; myCell->setLayout(m_r2Layout); return; @@ -147,7 +147,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int myCell->setMessage(titles[j-1]); return; } - if (shouldDisplayRAndR2 && j == numberRows - 2) { + if (hasLinearRegression() && j == numberRows - 2) { myCell->setMessage(I18n::Message::R); return; } @@ -233,10 +233,14 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int } if (j > k_regressionCellIndex + maxNumberCoefficients) { - // Fill r and r2 if needed - if (modelType == Model::Type::Linear) { - const int calculationIndex = j - k_regressionCellIndex - maxNumberCoefficients - 1; - double calculation = calculationIndex == 0 ? m_store->correlationCoefficient(seriesNumber) : m_store->squaredCorrelationCoefficient(seriesNumber); + // Fill r (if needed, before last row) and r2 (last row) + if ((modelType == Model::Type::Linear && j == numberRows - 2) || j == numberRows - 1) { + double calculation; + if (j == numberRows - 1) { + calculation = m_store->determinationCoefficientForSeries(seriesNumber, globContext); + } else { + calculation = m_store->correlationCoefficient(seriesNumber); + } constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(numberSignificantDigits); char buffer[bufferSize]; PoincareHelpers::ConvertFloatToText(calculation, buffer, bufferSize, numberSignificantDigits); @@ -337,9 +341,8 @@ int CalculationController::typeAtLocation(int i, int j) { if (i == 0 && j == 0) { return k_hideableCellType; } - bool shouldDisplayRAndR2 = hasLinearRegression(); int numberRows = numberOfRows(); - if (shouldDisplayRAndR2 && i == 0 && j == numberRows-1) { + if (i == 0 && j == numberRows-1) { return k_r2CellType; } if (i == 0) { diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index b207c3ed9..48cfd4a42 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -2,6 +2,7 @@ #include "../shared/poincare_helpers.h" #include "../shared/text_helpers.h" #include "../apps_container.h" +#include "../shared/poincare_helpers.h" #include #include #include @@ -11,14 +12,13 @@ using namespace Shared; namespace Regression { -GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, Store * store, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex) : - InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, store, &m_view, cursor, modelVersion, previousModelsVersions, rangeVersion), +GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, Store * store, CurveViewCursor * cursor, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex) : + InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, store, &m_view, cursor, rangeVersion), m_crossCursorView(), m_roundCursorView(), m_bannerView(this, inputEventHandlerDelegate, this), m_view(store, m_cursor, &m_bannerView, &m_crossCursorView), m_store(store), - m_initialisationParameterController(this, m_store), m_graphOptionsController(this, inputEventHandlerDelegate, m_store, m_cursor, this), m_selectedDotIndex(selectedDotIndex), m_selectedSeriesIndex(selectedSeriesIndex) @@ -29,10 +29,6 @@ GraphController::GraphController(Responder * parentResponder, InputEventHandlerD m_store->setDelegate(this); } -ViewController * GraphController::initialisationParameterController() { - return &m_initialisationParameterController; -} - bool GraphController::isEmpty() const { return m_store->isEmpty(); } @@ -200,7 +196,7 @@ void GraphController::reloadBannerView() { // Set "r2=..." numberOfChar = 0; legend = " r2="; - double r2 = m_store->squaredCorrelationCoefficient(*m_selectedSeriesIndex); + double r2 = m_store->determinationCoefficientForSeries(*m_selectedSeriesIndex, globalContext()); numberOfChar += strlcpy(buffer, legend, bufferSize); numberOfChar += PoincareHelpers::ConvertFloatToText(r2, buffer + numberOfChar, bufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); m_bannerView.subTextAtIndex(1+index)->setText(buffer); @@ -219,7 +215,7 @@ void GraphController::reloadBannerView() { m_bannerView.reload(); } -bool GraphController::moveCursorHorizontally(int direction, bool fast) { +bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) { double x; double y; if (*m_selectedDotIndex >= 0) { @@ -235,7 +231,8 @@ bool GraphController::moveCursorHorizontally(int direction, bool fast) { } *m_selectedDotIndex = dotSelected; } else { - x = m_cursor->x() + direction * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + double step = direction * scrollSpeed * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + x = m_cursor->x() + step; y = yValue(*m_selectedSeriesIndex, x, globalContext()); } m_cursor->moveTo(x, x, y); @@ -260,12 +257,26 @@ void GraphController::initCursorParameters() { double x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); double y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); m_cursor->moveTo(x, x, y); - if (m_store->yAuto()) { - m_store->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); - } *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } +bool GraphController::cursorMatchesModel() { + if (m_store->seriesIsEmpty(*m_selectedSeriesIndex)) { + return false; + } + Coordinate2D xy; + if (*m_selectedDotIndex == -1) { + xy = xyValues(*m_selectedSeriesIndex, m_cursor->t(), globalContext()); + } else if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { + xy = Coordinate2D(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1)); + } else if (*m_selectedDotIndex > m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { + return false; + } else { + xy = Coordinate2D(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); + } + return PoincareHelpers::equalOrBothNan(xy.x1(), m_cursor->x()) && PoincareHelpers::equalOrBothNan(xy.x2(), m_cursor->y()); +} + bool GraphController::moveCursorVertically(int direction) { Poincare::Context * context = globalContext(); double x = m_cursor->x(); @@ -345,15 +356,6 @@ bool GraphController::moveCursorVertically(int direction) { return false; } -uint32_t GraphController::modelVersion() { - return m_store->storeChecksum(); -} - -uint32_t GraphController::modelVersionAtIndex(int i) { - assert(i < numberOfMemoizedVersions()); - return *(m_store->seriesChecksum() + i); -} - uint32_t GraphController::rangeVersion() { return m_store->rangeChecksum(); } @@ -382,23 +384,6 @@ int GraphController::estimatedBannerNumberOfLines() const { return (selectedSeriesIndex() < 0) ? 3 : m_store->modelForSeries(selectedSeriesIndex())->bannerLinesCount(); } -InteractiveCurveViewRangeDelegate::Range GraphController::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) { - float minY = FLT_MAX; - float maxY = -FLT_MAX; - for (int series = 0; series < Store::k_numberOfSeries; series++) { - for (int k = 0; k < m_store->numberOfPairsOfSeries(series); k++) { - if (m_store->xMin() <= m_store->get(series, 0, k) && m_store->get(series, 0, k) <= m_store->xMax()) { - minY = std::min(minY, m_store->get(series, 1, k)); - maxY = std::max(maxY, m_store->get(series, 1, k)); - } - } - } - InteractiveCurveViewRangeDelegate::Range range; - range.min = minY; - range.max = maxY; - return range; -} - void GraphController::setRoundCrossCursorView() { /* At this point, the model (selected series and dot indices) should be up * to date. */ diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index d81680c18..570c91ff5 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -5,7 +5,6 @@ #include "store.h" #include "graph_options_controller.h" #include "graph_view.h" -#include "initialisation_parameter_controller.h" #include "../shared/interactive_curve_view_controller.h" #include "../shared/curve_view_cursor.h" #include "../shared/cursor_view.h" @@ -16,8 +15,7 @@ namespace Regression { class GraphController : public Shared::InteractiveCurveViewController { public: - GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, Store * store, Shared::CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex); - ViewController * initialisationParameterController() override; + GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, Store * store, Shared::CurveViewCursor * cursor, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex); bool isEmpty() const override; I18n::Message emptyMessage() override; void viewWillAppear() override; @@ -25,7 +23,7 @@ public: int selectedSeriesIndex() const { return *m_selectedSeriesIndex; } // moveCursorHorizontally and Vertically are public to be used in tests - bool moveCursorHorizontally(int direction, bool fast = false) override; + bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; bool moveCursorVertically(int direction) override; private: @@ -42,10 +40,8 @@ private: // InteractiveCurveViewController void initCursorParameters() override; - uint32_t modelVersion() override; - uint32_t modelVersionAtIndex(int i) override; + bool cursorMatchesModel() override; uint32_t rangeVersion() override; - size_t numberOfMemoizedVersions() const override { return Store::k_numberOfSeries; } int selectedCurveIndex() const override { return *m_selectedSeriesIndex; } bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override; Poincare::Coordinate2D xyValues(int curveIndex, double x, Poincare::Context * context) const override; @@ -54,16 +50,12 @@ private: int numberOfCurves() const override; int estimatedBannerNumberOfLines() const override; - // InteractiveCurveViewRangeDelegate - Shared::InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; - void setRoundCrossCursorView(); Shared::CursorView m_crossCursorView; Shared::RoundCursorView m_roundCursorView; BannerView m_bannerView; GraphView m_view; Store * m_store; - InitialisationParameterController m_initialisationParameterController; GraphOptionsController m_graphOptionsController; /* The selectedDotIndex is -1 when no dot is selected, m_numberOfPairs when * the mean dot is selected and the dot index otherwise */ diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index 13a31112e..fb2987e15 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -34,7 +34,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { for (int index = 0; index < m_store->numberOfPairsOfSeries(series); index++) { drawDot(ctx, rect, m_store->get(series, 0, index), m_store->get(series, 1, index), color); } - drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), color, Size::Medium); + drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), color, Size::Small); drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), Palette::BackgroundHard); } } diff --git a/apps/regression/initialisation_parameter_controller.cpp b/apps/regression/initialisation_parameter_controller.cpp deleted file mode 100644 index 421d3f7c3..000000000 --- a/apps/regression/initialisation_parameter_controller.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "initialisation_parameter_controller.h" -#include - -using namespace Shared; - -namespace Regression { - -InitialisationParameterController::InitialisationParameterController(Responder * parentResponder, Store * store) : - ViewController(parentResponder), - m_selectableTableView(this), - m_store(store) -{ -} - -const char * InitialisationParameterController::title() { - return I18n::translate(I18n::Message::Initialization); -} - -View * InitialisationParameterController::view() { - return &m_selectableTableView; -} - -void InitialisationParameterController::didBecomeFirstResponder() { - selectCellAtLocation(0, 0); - Container::activeApp()->setFirstResponder(&m_selectableTableView); -} - -bool InitialisationParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - RangeMethodPointer rangeMethods[k_totalNumberOfCells] = {&InteractiveCurveViewRange::roundAbscissa, - &InteractiveCurveViewRange::normalize, &InteractiveCurveViewRange::setDefault}; - (m_store->*rangeMethods[selectedRow()])(); - StackViewController * stack = (StackViewController *)parentResponder(); - stack->pop(); - return true; - } - return false; -} - -int InitialisationParameterController::numberOfRows() const { - return k_totalNumberOfCells; -}; - - -HighlightCell * InitialisationParameterController::reusableCell(int index) { - assert(index >= 0); - assert(index < k_totalNumberOfCells); - return &m_cells[index]; -} - -int InitialisationParameterController::reusableCellCount() const { - return k_totalNumberOfCells; -} - -KDCoordinate InitialisationParameterController::cellHeight() { - return Metric::ParameterCellHeight; -} - -void InitialisationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; - I18n::Message titles[3] = {I18n::Message::RoundAbscissa, I18n::Message::Orthonormal, I18n::Message::DefaultSetting}; - myCell->setMessage(titles[index]); -} - -} diff --git a/apps/regression/initialisation_parameter_controller.h b/apps/regression/initialisation_parameter_controller.h deleted file mode 100644 index d3c97b59e..000000000 --- a/apps/regression/initialisation_parameter_controller.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef REGRESSION_INITIALISATION_PARAMETER_CONTROLLER_H -#define REGRESSION_INITIALISATION_PARAMETER_CONTROLLER_H - -#include -#include "store.h" -#include - -namespace Regression { - -class InitialisationParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { -public: - InitialisationParameterController(Responder * parentResponder, Store * store); - View * view() override; - const char * title() override; - bool handleEvent(Ion::Events::Event event) override; - void didBecomeFirstResponder() override; - int numberOfRows() const override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() const override; - void willDisplayCellForIndex(HighlightCell * cell, int index) override; -private: - constexpr static int k_totalNumberOfCells = 3; - MessageTableCell m_cells[k_totalNumberOfCells]; - SelectableTableView m_selectableTableView; - Store * m_store; -}; - -} - -#endif diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index ec9c50a48..5587fea8c 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -84,6 +84,14 @@ void LogisticModel::specializedInitCoefficientsForFit(double * modelCoefficients * that is "close enough" to c to seed the coefficient, without being too * dependent on outliers.*/ modelCoefficients[2] = 2.0 * store->standardDeviationOfColumn(series, 1); + /* TODO : Try two different sets of seeds to find a better fit for both + * x = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} + * y = {5.0, 9.0, 40.0, 64.0, 144.0, 200.0, 269.0, 278.0, 290.0, 295.0} + * (Coefficients should be {64.9, 1.0, 297.4}) + * And + * x = {4, 3, 21, 1, 6} + * y = {0, 4, 5, 4, 58} + * (Coefficients should be {370162529, 4.266, 31.445} with R2=0.4 at least) */ } diff --git a/apps/regression/model/model.cpp b/apps/regression/model/model.cpp index 13f711574..8aec7709a 100644 --- a/apps/regression/model/model.cpp +++ b/apps/regression/model/model.cpp @@ -35,6 +35,7 @@ void Model::fit(Store * store, int series, double * modelCoefficients, Poincare: if (dataSuitableForFit(store, series)) { initCoefficientsForFit(modelCoefficients, k_initialCoefficientValue, false, store, series); fitLevenbergMarquardt(store, series, modelCoefficients, context); + uniformizeCoefficientsFromFit(modelCoefficients); } else { initCoefficientsForFit(modelCoefficients, NAN, true); } @@ -64,6 +65,7 @@ void Model::fitLevenbergMarquardt(Store * store, int series, double * modelCoeff while (smallChi2ChangeCounts < k_consecutiveSmallChi2ChangesLimit && iterationCount < k_maxIterations) { // Create the alpha prime matrix (it is symmetric) double coefficientsAPrime[Model::k_maxNumberOfCoefficients * Model::k_maxNumberOfCoefficients]; + assert(n > 0); // Ensure that coefficientsAPrime is initialized for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { double alphaPrime = alphaPrimeCoefficient(store, series, modelCoefficients, i, j, lambda); diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index 78f14aab6..1b36fa6a8 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -64,6 +64,7 @@ private: int solveLinearSystem(double * solutions, double * coefficients, double * constants, int solutionDimension, Poincare::Context * context); void initCoefficientsForFit(double * modelCoefficients, double defaultValue, bool forceDefaultValue, Store * store = nullptr, int series = -1) const; virtual void specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store = nullptr, int series = -1) const; + virtual void uniformizeCoefficientsFromFit(double * modelCoefficients) const {} }; } diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index b6ddd6359..5638d44cd 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -9,23 +9,16 @@ #include #include #include -#include #include +#include using namespace Poincare; using namespace Shared; namespace Regression { -static double toRadians(Poincare::Preferences::AngleUnit angleUnit) { - switch (Poincare::Preferences::sharedPreferences()->angleUnit()) { - case Poincare::Preferences::AngleUnit::Degree: - return M_PI/180.0; - case Poincare::Preferences::AngleUnit::Gradian: - return M_PI/200.0; - default: - return 1; - } +static double toRadians() { + return M_PI / Trigonometry::PiInAngleUnit(Poincare::Preferences::sharedPreferences()->angleUnit()); } Layout TrigonometricModel::layout() { @@ -41,8 +34,9 @@ double TrigonometricModel::evaluate(double * modelCoefficients, double x) const double b = modelCoefficients[1]; double c = modelCoefficients[2]; double d = modelCoefficients[3]; - double radianX = x * toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); - return a*sin(b*radianX+c)+d; + double radian = toRadians(); + // sin() is here defined for radians, so b*x+c are converted in radians. + return a * std::sin(radian * (b * x + c)) + d; } double TrigonometricModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { @@ -54,36 +48,82 @@ double TrigonometricModel::partialDerivate(double * modelCoefficients, int deriv double a = modelCoefficients[0]; double b = modelCoefficients[1]; double c = modelCoefficients[2]; - double radianX = x * toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); - + double radian = toRadians(); + /* sin() and cos() are here defined for radians, so b*x+c are converted in + * radians. The added coefficient also appear in derivatives. */ if (derivateCoefficientIndex == 0) { // Derivate with respect to a: sin(b*x+c) - return sin(b * radianX + c); + return std::sin(radian * (b * x + c)); } if (derivateCoefficientIndex == 1) { // Derivate with respect to b: x*a*cos(b*x+c); - return radianX * a * cos(b * radianX + c); + return radian * x * a * std::cos(radian * (b * x + c)); } assert(derivateCoefficientIndex == 2); - // Derivatewith respect to c: a*cos(b*x+c) - return a * cos(b * radianX + c); + // Derivate with respect to c: a*cos(b*x+c) + return radian * a * std::cos(radian * (b * x + c)); } void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { assert(store != nullptr && series >= 0 && series < Store::k_numberOfSeries && !store->seriesIsEmpty(series)); - for (int i = 1; i < k_numberOfCoefficients - 1; i++) { - modelCoefficients[i] = defaultValue; - } /* We try a better initialization than the default value. We hope that this * will improve the gradient descent to find correct coefficients. * * Init the "amplitude" coefficient. We take twice the standard deviation, * because for a normal law, this interval contains 99.73% of the values. We - * do not take half of the apmlitude of the series, because this would be too - * dependant on outliers. */ + * do not take half of the amplitude of the series, because this would be too + * dependent on outliers. */ modelCoefficients[0] = 3.0*store->standardDeviationOfColumn(series, 1); // Init the "y delta" coefficient modelCoefficients[k_numberOfCoefficients - 1] = store->meanOfColumn(series, 1); + // Init the b coefficient + double rangeX = store->maxValueOfColumn(series, 0) - store->minValueOfColumn(series, 0); + double piInAngleUnit = Trigonometry::PiInAngleUnit(Poincare::Preferences::sharedPreferences()->angleUnit()); + if (rangeX > 0) { + /* b/2π represents the frequency of the sine (in radians). Instead of + * initializing it to 0, we use the inverse of X series' range as an order + * of magnitude for it. It can help avoiding a regression that overfits the + * data with a very high frequency. This period also depends on the + * angleUnit. We take it into account so that it doesn't impact the result + * (although coefficients b and c depends on the angleUnit). */ + modelCoefficients[1] = (2.0 * piInAngleUnit) / rangeX; + } else { + // Coefficient b must not depend on angleUnit. + modelCoefficients[1] = defaultValue * piInAngleUnit; + } + /* No shift is assumed, coefficient c is set to 0. + * If it were to be non-null, angleUnit must be taken into account. + * modelCoefficients[2] = initialCValue * piInAngleUnit; */ + modelCoefficients[2] = 0.0; +} + +void TrigonometricModel::uniformizeCoefficientsFromFit(double * modelCoefficients) const { + // Coefficients must be unique. + double piInAngleUnit = Trigonometry::PiInAngleUnit(Poincare::Preferences::sharedPreferences()->angleUnit()); + // A must be positive. + if (modelCoefficients[0] < 0.0) { + // A * sin(B * x + C) + D = -A * sin(B * x + C + π) + D + modelCoefficients[0] *= -1.0; + modelCoefficients[2] += piInAngleUnit; + } + // B must be positive. + if (modelCoefficients[1] < 0.0) { + /* A * sin(B * x + C) + D = -A * sin(-B * x - C) + D + * -A * sin(-B * x - C) + D = A * sin(-B * x - C + π) + D */ + modelCoefficients[1] *= -1.0; + modelCoefficients[2] *= -1.0; + modelCoefficients[2] += piInAngleUnit; + } + // C must be between -π (excluded) and π (included). + if (modelCoefficients[2] <= -piInAngleUnit || modelCoefficients[2] > piInAngleUnit) { + /* A*sin(B*x + C) + D = A*sin(B*x + C - 2π) = A*sin(B*x + C + 2π) + * Using remainder(C,2π) = C - 2π * round(C / 2π) */ + modelCoefficients[2] -= 2.0 * piInAngleUnit * std::round(modelCoefficients[2] / (2.0 * piInAngleUnit)); + if (modelCoefficients[2] == -piInAngleUnit) { + // Keep π instead of -π + modelCoefficients[2] = piInAngleUnit; + } + } } Expression TrigonometricModel::expression(double * modelCoefficients) { diff --git a/apps/regression/model/trigonometric_model.h b/apps/regression/model/trigonometric_model.h index 83ed05113..124a6353c 100644 --- a/apps/regression/model/trigonometric_model.h +++ b/apps/regression/model/trigonometric_model.h @@ -17,6 +17,7 @@ public: private: static constexpr int k_numberOfCoefficients = 4; void specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const override; + void uniformizeCoefficientsFromFit(double * modelCoefficients) const override; Poincare::Expression expression(double * modelCoefficients) override; }; diff --git a/apps/regression/regression_context.cpp b/apps/regression/regression_context.cpp index 956da75d8..ef84c9aad 100644 --- a/apps/regression/regression_context.cpp +++ b/apps/regression/regression_context.cpp @@ -9,7 +9,7 @@ using namespace Shared; namespace Regression { -const Expression RegressionContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { +const Expression RegressionContext::expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isRegressionSymbol(symbol.name(), nullptr)) { const char * seriesName = symbol.name(); assert(strlen(seriesName) == 2); diff --git a/apps/regression/regression_context.h b/apps/regression/regression_context.h index c9e5fe8dd..0ab483acc 100644 --- a/apps/regression/regression_context.h +++ b/apps/regression/regression_context.h @@ -9,7 +9,7 @@ namespace Regression { class RegressionContext : public Shared::StoreContext { public: using Shared::StoreContext::StoreContext; - const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; + const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; }; } diff --git a/apps/regression/regression_controller.cpp b/apps/regression/regression_controller.cpp index 188007d6b..b9fd1d434 100644 --- a/apps/regression/regression_controller.cpp +++ b/apps/regression/regression_controller.cpp @@ -29,7 +29,7 @@ const char * RegressionController::title() { } void RegressionController::didBecomeFirstResponder() { - selectCellAtLocation(0, 0); + selectCellAtLocation(0, static_cast(m_store->seriesRegressionType(m_series))); Container::activeApp()->setFirstResponder(&m_selectableTableView); } diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index cfba25903..e081924f5 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -139,18 +139,44 @@ int Store::nextDot(int series, int direction, int dot) { /* Window */ void Store::setDefault() { - m_yAuto = true; - float minX = FLT_MAX; - float maxX = -FLT_MAX; + setZoomNormalize(false); + + float xMin, xMax, yMin, yMax; + float mins[k_numberOfSeries], maxs[k_numberOfSeries]; for (int series = 0; series < k_numberOfSeries; series++) { - if (!seriesIsEmpty(series)) { - minX = std::min(minX, minValueOfColumn(series, 0)); - maxX = std::max(maxX, maxValueOfColumn(series, 0)); - } + bool empty = seriesIsEmpty(series); + mins[series] = empty ? NAN : minValueOfColumn(series, 0); + maxs[series] = empty ? NAN : maxValueOfColumn(series, 0); } - float range = maxX - minX; - setXMin(minX - k_displayHorizontalMarginRatio*range); - setXMax(maxX + k_displayHorizontalMarginRatio*range); + Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &xMin, &xMax); + + for (int series = 0; series < k_numberOfSeries; series++) { + bool empty = seriesIsEmpty(series); + mins[series] = empty ? NAN : minValueOfColumn(series, 1); + maxs[series] = empty ? NAN : maxValueOfColumn(series, 1); + } + Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &yMin, &yMax); + + Poincare::Zoom::SanitizeRange(&xMin, &xMax, &yMin, &yMax, NormalYXRatio()); + + m_xRange.setMin(xMin); + m_xRange.setMax(xMax); + m_yRange.setMin(yMin); + m_yRange.setMax(yMax); + bool revertToOrthonormal = shouldBeNormalized(); + + float range = xMax - xMin; + setXMin(xMin - k_displayHorizontalMarginRatio * range); + setXMax(xMax + k_displayHorizontalMarginRatio * range); + + range = yMax - yMin; + setYMin(roundLimit(m_delegate->addMargin(yMin, range, true, true ), range, true)); + setYMax(roundLimit(m_delegate->addMargin(yMax, range, true, false), range, false)); + + if (revertToOrthonormal) { + normalize(); + } + setZoomAuto(true); } /* Series */ @@ -161,7 +187,7 @@ bool Store::seriesIsEmpty(int series) const { /* Calculations */ -double * Store::coefficientsForSeries(int series, Poincare::Context * globalContext) { +void Store::updateCoefficients(int series, Poincare::Context * globalContext) { assert(series >= 0 && series <= k_numberOfSeries); assert(!seriesIsEmpty(series)); uint32_t storeChecksumSeries = storeChecksumForSeries(series); @@ -170,6 +196,8 @@ double * Store::coefficientsForSeries(int series, Poincare::Context * globalCont m_angleUnit = currentAngleUnit; for (int i = 0; i < k_numberOfSeries; i++) { if (m_regressionTypes[i] == Model::Type::Trigonometric) { + /* TODO : Assuming regression should be independent of angleUnit, + * coefficients b and c could just be converted to the new angle unit.*/ m_regressionChanged[i] = true; } } @@ -179,10 +207,25 @@ double * Store::coefficientsForSeries(int series, Poincare::Context * globalCont seriesModel->fit(this, series, m_regressionCoefficients[series], globalContext); m_regressionChanged[series] = false; m_seriesChecksum[series] = storeChecksumSeries; + /* m_determinationCoefficient must be updated after m_seriesChecksum and m_regressionChanged + * updates to avoid infinite recursive calls as computeDeterminationCoefficient calls + * yValueForXValue which calls coefficientsForSeries which calls updateCoefficients */ + m_determinationCoefficient[series] = computeDeterminationCoefficient(series, globalContext); } +} + +double * Store::coefficientsForSeries(int series, Poincare::Context * globalContext) { + updateCoefficients(series, globalContext); return m_regressionCoefficients[series]; } +double Store::determinationCoefficientForSeries(int series, Poincare::Context * globalContext) { + /* Returns the Determination coefficient (R2). + * It will be updated if the regression has been updated */ + updateCoefficients(series, globalContext); + return m_determinationCoefficient[series]; +} + double Store::doubleCastedNumberOfPairsOfSeries(int series) const { return DoublePairStore::numberOfPairsOfSeries(series); } @@ -210,37 +253,47 @@ float Store::minValueOfColumn(int series, int i) const { return minColumn; } -double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) const { +double Store::squaredOffsettedValueSumOfColumn(int series, int i, bool lnOfSeries, double offset) const { double result = 0; - for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + const int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + double value = m_data[series][i][k]; if (lnOfSeries) { - result += log(m_data[series][i][k]) * log(m_data[series][i][k]); - } else { - result += m_data[series][i][k]*m_data[series][i][k]; + value = log(value); } + value -= offset; + result += value * value; } return result; } +double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) const { + return squaredOffsettedValueSumOfColumn(series, i, lnOfSeries, 0.0); +} + double Store::columnProductSum(int series, bool lnOfSeries) const { double result = 0; for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + double value0 = m_data[series][0][k]; + double value1 = m_data[series][1][k]; if (lnOfSeries) { - result += log(m_data[series][0][k]) * log(m_data[series][1][k]); - } else { - result += m_data[series][0][k] * m_data[series][1][k]; + value0 = log(value0); + value1 = log(value1); } + result += value0 * value1; } return result; } double Store::meanOfColumn(int series, int i, bool lnOfSeries) const { - return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series); + return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i, lnOfSeries) / numberOfPairsOfSeries(series); } double Store::varianceOfColumn(int series, int i, bool lnOfSeries) const { + /* We use the Var(X) = E[(X-E[X])^2] definition instead of Var(X) = E[X^2] - E[X]^2 + * to ensure a positive result and to minimize rounding errors */ double mean = meanOfColumn(series, i, lnOfSeries); - return squaredValueSumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series) - mean*mean; + return squaredOffsettedValueSumOfColumn(series, i, lnOfSeries, mean)/numberOfPairsOfSeries(series); } double Store::standardDeviationOfColumn(int series, int i, bool lnOfSeries) const { @@ -248,7 +301,9 @@ double Store::standardDeviationOfColumn(int series, int i, bool lnOfSeries) cons } double Store::covariance(int series, bool lnOfSeries) const { - return columnProductSum(series, lnOfSeries)/numberOfPairsOfSeries(series) - meanOfColumn(series, 0, lnOfSeries)*meanOfColumn(series, 1, lnOfSeries); + double mean0 = meanOfColumn(series, 0, lnOfSeries); + double mean1 = meanOfColumn(series, 1, lnOfSeries); + return columnProductSum(series, lnOfSeries)/numberOfPairsOfSeries(series) - mean0 * mean1; } double Store::slope(int series, bool lnOfSeries) const { @@ -268,20 +323,55 @@ double Store::yValueForXValue(int series, double x, Poincare::Context * globalCo double Store::xValueForYValue(int series, double y, Poincare::Context * globalContext) { Model * model = regressionModel((int)m_regressionTypes[series]); double * coefficients = coefficientsForSeries(series, globalContext); - return model->levelSet(coefficients, xMin(), xGridUnit()/10.0, xMax(), y, globalContext); + return model->levelSet(coefficients, xMin(), xGridUnit() / 10.0, xMax(), y, globalContext); } double Store::correlationCoefficient(int series) const { - double sd0 = standardDeviationOfColumn(series, 0); - double sd1 = standardDeviationOfColumn(series, 1); - return (sd0 == 0.0 || sd1 == 0.0) ? 1.0 : covariance(series)/(sd0*sd1); -} - -double Store::squaredCorrelationCoefficient(int series) const { - double cov = covariance(series); + /* Returns the correlation coefficient (R) between the series X and Y. + * In non-linear regressions, its square is different from the determinationCoefficient + * It is usually displayed in linear regressions only to avoid confusion */ double v0 = varianceOfColumn(series, 0); double v1 = varianceOfColumn(series, 1); - return (v0 == 0.0 || v1 == 0.0) ? 1.0 : cov*cov/(v0*v1); + return (v0 == 0.0 || v1 == 0.0) ? 1.0 : covariance(series) / std::sqrt(v0 * v1); +} + +double Store::computeDeterminationCoefficient(int series, Poincare::Context * globalContext) { + /* Computes and returns the determination coefficient (R2) of the regression. + * For linear regressions, it is equal to the square of the correlation + * coefficient between the series Y and the evaluated values. + * With proportional regression or badly fitted models, R2 can technically be + * negative. R2<0 means that the regression is less effective than a + * constant set to the series average. It should not happen with regression + * models that can fit a constant observation. */ + // Residual sum of squares + double ssr = 0; + // Total sum of squares + double sst = 0; + double mean = meanOfColumn(series, 1); + const int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + // Difference between the observation and the estimated value of the model + double evaluation = yValueForXValue(series, m_data[series][0][k], globalContext); + if (std::isnan(evaluation) || std::isinf(evaluation)) { + // Data Not Suitable for evaluation + return NAN; + } + double residual = m_data[series][1][k] - evaluation; + ssr += residual * residual; + // Difference between the observation and the overall observations mean + double difference = m_data[series][1][k] - mean; + sst += difference * difference; + } + if (sst == 0.0) { + /* Observation was constant, r2 is undefined. Return 1 if estimations + * exactly matched observations. 0 is usually returned otherwise. */ + return (ssr <= DBL_EPSILON) ? 1.0 : 0.0; + } + double r2 = 1.0 - ssr / sst; + // Check if regression fit was optimal. + // TODO : Optimize regression fitting so that r2 cannot be negative. + // assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional); + return r2; } Model * Store::regressionModel(int index) { diff --git a/apps/regression/store.h b/apps/regression/store.h index 92acd0f8f..8402dbecf 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -57,8 +57,11 @@ public: bool seriesIsEmpty(int series) const override; // Calculation + void updateCoefficients(int series, Poincare::Context * globalContext); double * coefficientsForSeries(int series, Poincare::Context * globalContext); + double determinationCoefficientForSeries(int series, Poincare::Context * globalContext); // R2 double doubleCastedNumberOfPairsOfSeries(int series) const; + double squaredOffsettedValueSumOfColumn(int series, int i, bool lnOfSeries = false, double offset = 0) const; double squaredValueSumOfColumn(int series, int i, bool lnOfSeries = false) const; double columnProductSum(int series, bool lnOfSeries = false) const; double meanOfColumn(int series, int i, bool lnOfSeries = false) const; @@ -69,13 +72,15 @@ public: double yIntercept(int series, bool lnOfSeries = false) const; double yValueForXValue(int series, double x, Poincare::Context * globalContext); double xValueForYValue(int series, double y, Poincare::Context * globalContext); - double correlationCoefficient(int series) const; - double squaredCorrelationCoefficient(int series) const; + double correlationCoefficient(int series) const; // R + + // To speed up computation during drawings, float is returned. + float maxValueOfColumn(int series, int i) const; + float minValueOfColumn(int series, int i) const; private: + double computeDeterminationCoefficient(int series, Poincare::Context * globalContext); constexpr static float k_displayHorizontalMarginRatio = 0.05f; void resetMemoization(); - float maxValueOfColumn(int series, int i) const; //TODO LEA why float ? - float minValueOfColumn(int series, int i) const; //TODO LEA why float ? Model * regressionModel(int index); uint32_t m_seriesChecksum[k_numberOfSeries]; Model::Type m_regressionTypes[k_numberOfSeries]; @@ -90,6 +95,7 @@ private: TrigonometricModel m_trigonometricModel; LogisticModel m_logisticModel; double m_regressionCoefficients[k_numberOfSeries][Model::k_maxNumberOfCoefficients]; + double m_determinationCoefficient[k_numberOfSeries]; bool m_regressionChanged[k_numberOfSeries]; Poincare::Preferences::AngleUnit m_angleUnit; }; diff --git a/apps/regression/store_parameter_controller.cpp b/apps/regression/store_parameter_controller.cpp index e06c2b859..1655e035b 100644 --- a/apps/regression/store_parameter_controller.cpp +++ b/apps/regression/store_parameter_controller.cpp @@ -76,6 +76,7 @@ int StoreParameterController::typeAtLocation(int i, int j) { void StoreParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (index == numberOfRows() -1) { m_changeRegressionCell.setLayout(static_cast(m_store)->modelForSeries(m_series)->layout()); + return; } Shared::StoreParameterController::willDisplayCellForIndex(cell, index); } diff --git a/apps/regression/store_parameter_controller.h b/apps/regression/store_parameter_controller.h index 08c532405..24b34158d 100644 --- a/apps/regression/store_parameter_controller.h +++ b/apps/regression/store_parameter_controller.h @@ -23,6 +23,7 @@ public: int typeAtLocation(int i, int j) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: + I18n::Message sortMessage() override { return I18n::Message::SortValues; } static constexpr int k_regressionCellType = 1; MessageTableCellWithChevronAndExpression m_changeRegressionCell; bool m_lastSelectionIsRegression; diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 0abbad61a..eebec4776 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -5,6 +5,8 @@ #include "../model/model.h" #include "../regression_context.h" #include "../store.h" +#include +#include using namespace Poincare; using namespace Regression; @@ -12,42 +14,63 @@ using namespace Regression; /* The data was generated by choosing X1 and the coefficients of the regression, * then filling Y1 with the regression formula + random()/10. */ -void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::Type modelType, double * trueCoefficients) { - double precision = 0.1; +void setRegressionPoints(Regression::Store * store, int series, int numberOfPoints, double * xi, double * yi = nullptr) { + for (int i = 0; i < numberOfPoints; i++) { + store->set(xi[i], series, 0, i); + if (yi != nullptr) { + store->set(yi[i], series, 1, i); + } + } +} + +void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::Type modelType, double * trueCoefficients, double trueR2) { int series = 0; Regression::Store store; - // Set the points and the regression type - for (int i = 0; i < numberOfPoints; i++) { - store.set(xi[i], series, 0, i); - store.set(yi[i], series, 1, i); - } + setRegressionPoints(&store, series, numberOfPoints, xi, yi); + store.setSeriesRegressionType(series, modelType); Shared::GlobalContext globalContext; RegressionContext context(&store, &globalContext); - store.setSeriesRegressionType(series, modelType); - // Compute the coefficients + double precision = 1e-2; + // When trueCoefficients = 0, a DBL_EPSILON reference ensures that the only accepted errors are due to double approximations + double reference = 1e6 * DBL_EPSILON; + + // Compute and compare the coefficients double * coefficients = store.coefficientsForSeries(series, &context); - - // Compare the coefficients int numberOfCoefs = store.modelForSeries(series)->numberOfCoefficients(); for (int i = 0; i < numberOfCoefs; i++) { - quiz_assert(std::fabs(coefficients[i] - trueCoefficients[i]) < precision); + quiz_assert(IsApproximatelyEqual(coefficients[i], trueCoefficients[i], precision, reference)); } + + // Compute and check r2 value and sign + double r2 = store.determinationCoefficientForSeries(series, &globalContext); + quiz_assert(r2 <= 1.0 && (r2 >= 0.0 || modelType == Model::Type::Proportional)); + quiz_assert(IsApproximatelyEqual(r2, trueR2, precision, reference)); } QUIZ_CASE(linear_regression) { double x[] = {1.0, 8.0, 14.0, 79.0}; double y[] = {-3.581, 20.296, 40.676, 261.623}; double coefficients[] = {3.4, -7.0}; - assert_regression_is(x, y, 4, Model::Type::Linear, coefficients); + double r2 = 1.0; + assert_regression_is(x, y, 4, Model::Type::Linear, coefficients, r2); +} + +QUIZ_CASE(linear_regression2) { + double x[] = {-5.0, 2.0, 4.0, 5.6, 9.0}; + double y[] = {22.0, 1.0, 13.0, 28.36, 78.0}; + double coefficients[] = {3.31824, 18.1191}; + double r2 = 0.343; + assert_regression_is(x, y, 5, Model::Type::Linear, coefficients, r2); } QUIZ_CASE(proportional_regression) { double x[] = {7.0, 5.0, 1.0, 9.0, 3.0}; double y[] = {-41.4851, -29.62186, -6.454245, -53.4976, -18.03325}; double coefficients[] = {-5.89}; - assert_regression_is(x, y, 5, Model::Type::Proportional, coefficients); + double r2 = 0.9999648161902982; + assert_regression_is(x, y, 5, Model::Type::Proportional, coefficients, r2); } QUIZ_CASE(proportional_regression2) { @@ -55,66 +78,155 @@ QUIZ_CASE(proportional_regression2) { double x[numberOfPoints] = {5.0, 2.0, 3.0, 4.0}; double y[numberOfPoints] = {10.0, 6.0, 7.0, 8.0}; double coefficients[] = {2.12963963}; - assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients); + double r2 = 0.53227513227513223; + assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients, r2); +} + +QUIZ_CASE(proportional_regression3) { + constexpr int numberOfPoints = 4; + double x[numberOfPoints] = {1.0, 2.0, 3.0, 4.0}; + double y[numberOfPoints] = {0.0, 0.0, 0.0, 0.0}; + double coefficients[] = {0.0}; + double r2 = 1.0; + assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients, r2); +} + +QUIZ_CASE(proportional_regression4) { + constexpr int numberOfPoints = 3; + double x[numberOfPoints] = {-1.0, 0.0, 1.0}; + double y[numberOfPoints] = {1.0, 1.0, 1.0}; + double coefficients[] = {0.0}; + // Y is constant, and proportional regression cannot fit it, R2 is null. + double r2 = 0.0; + assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients, r2); +} + +QUIZ_CASE(proportional_regression5) { + constexpr int numberOfPoints = 3; + double x[numberOfPoints] = {-1.0, 0.0, 1.0}; + double y[numberOfPoints] = {1.0, 1.01, 1.0}; + double coefficients[] = {0.0}; + /* In this case, proportional regression performed poorly compared to a + * constant regression, R2 is negative. */ + double r2 = -45300.5; + assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients, r2); } QUIZ_CASE(quadratic_regression) { double x[] = {-34.0, -12.0, 5.0, 86.0, -2.0}; double y[] = {-8241.389, -1194.734, -59.163, - 46245.39, -71.774}; - double coefficients[] = {-6.5, 21.3, -3.2}; - assert_regression_is(x, y, 5, Model::Type::Quadratic, coefficients); + double coefficients[] = {-6.50001, 21.3004, -3.15799}; + double r2 = 1.0; + assert_regression_is(x, y, 5, Model::Type::Quadratic, coefficients, r2); } QUIZ_CASE(cubic_regression) { double x[] = {-3.0, -2.8, -1.0, 0.0, 12.0}; double y[] = {691.261, 566.498, 20.203, -12.865, -34293.21}; - double coefficients[] = {-21.2, 16.0, 4.1, -12.9}; - assert_regression_is(x, y, 5, Model::Type::Cubic, coefficients); + double coefficients[] = {-21.2015, 16.0141, 4.14522, -12.8658}; + double r2 = 1.0; + assert_regression_is(x, y, 5, Model::Type::Cubic, coefficients, r2); } QUIZ_CASE(quartic_regression) { double x[] = {1.6, 3.5, 3.5, -2.8, 6.4, 5.3, 2.9, -4.8, -5.7, 3.1}; double y[] = {-112.667, -1479.824, -1479.805, 1140.276, -9365.505, -5308.355, -816.925, 5554.007, 9277.107, -1009.874}; - double coefficients[] = {0.6, -43, 21.5, 3.1, -0.5}; - assert_regression_is(x, y, 10, Model::Type::Quartic, coefficients); + double coefficients[] = {0.59998, -42.9998, 21.5015, 3.09232, -0.456824}; + double r2 = 1.0; + assert_regression_is(x, y, 10, Model::Type::Quartic, coefficients, r2); } QUIZ_CASE(logarithmic_regression) { double x[] = {0.2, 0.5, 5, 7}; double y[] = {-11.952, -9.035, -1.695, -0.584}; - double coefficients[] = {3.2, -6.9}; - assert_regression_is(x, y, 4, Model::Type::Logarithmic, coefficients); + double coefficients[] = {3.19383, -6.81679}; + double r2 = 0.999994; + assert_regression_is(x, y, 4, Model::Type::Logarithmic, coefficients, r2); } QUIZ_CASE(exponential_regression) { double x[] = {5.5, 5.6, 5.7, 5.8, 5.9, 6.0}; double y[] = {-276.842, -299.956, -324.933, -352.0299, -381.314, -413.0775}; double coefficients[] = {-3.4, 0.8}; - assert_regression_is(x, y, 6, Model::Type::Exponential, coefficients); + double r2 = 1.0; + assert_regression_is(x, y, 6, Model::Type::Exponential, coefficients, r2); } QUIZ_CASE(exponential_regression2) { double x[] = {0, 1, 2, 3}; double y[] = {3000, 3315.513, 3664.208, 4049.576}; double coefficients[] = {3000, .1}; - assert_regression_is(x, y, 4, Model::Type::Exponential, coefficients); + double r2 = 1.0; + assert_regression_is(x, y, 4, Model::Type::Exponential, coefficients, r2); } QUIZ_CASE(exponential_regression3) { double x[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; double y[] = {-1, -.3678794, -.1353353, -.04978707, -.01831564, -.006737947, -.002478752, -.000911882, -.0003354626, -.0001234098, -.00004539993}; double coefficients[] = {-1, -1}; - assert_regression_is(x, y, 11, Model::Type::Exponential, coefficients); + double r2 = 0.9999999999999992; + assert_regression_is(x, y, 11, Model::Type::Exponential, coefficients, r2); + + // TODO : This data produce a wrong fit currently + // double x2[] = {1.0, 2.0, 3.0, 4.0}; + // double y2[] = {2.0, 3.0, 4.0, 1.0}; + // double coefficients2[] = {2.905, -0.0606857}; + // double r22 = 0.838388; + // assert_regression_is(x2, y2, 4, Model::Type::Exponential, coefficients2, r22); } QUIZ_CASE(power_regression) { double x[] = {1.0, 50.0, 34.0, 67.0, 20.0}; double y[] = {71.860, 2775514, 979755.1, 6116830, 233832.9}; double coefficients[] = {71.8, 2.7}; - assert_regression_is(x, y, 5, Model::Type::Power, coefficients); + double r2 = 1.0; + assert_regression_is(x, y, 5, Model::Type::Power, coefficients, r2); + + // TODO : This data produce a wrong fit currently + // double x2[] = {1.0, 2.0, 3.0, 4.0}; + // double y2[] = {2.0, 3.0, 4.0, 1.0}; + // double coefficients2[] = {2.54948, -0.0247463}; + // double r22 = 0.833509; + // assert_regression_is(x2, y2, 4, Model::Type::Power, coefficients2, r22); } -// No case for trigonometric regression, because it has no unique solution +void assert_trigonomatric_regression_is(double * xi, double * yi, int numberOfPoints, double * trueCoefficients, double trueR2, Poincare::Preferences::AngleUnit trueCoeffcientsUnit) { + // Test the trigonometric regression at all angle units + const Preferences::AngleUnit previousAngleUnit = Preferences::sharedPreferences()->angleUnit(); + const Poincare::Preferences::AngleUnit units[3] = {Poincare::Preferences::AngleUnit::Radian, Poincare::Preferences::AngleUnit::Degree, Poincare::Preferences::AngleUnit::Gradian}; + for (int i = 0; i < 3; ++i) { + Poincare::Preferences::AngleUnit unit = units[i]; + Poincare::Preferences::sharedPreferences()->setAngleUnit(unit); + double unitFactor = Trigonometry::PiInAngleUnit(unit) / Trigonometry::PiInAngleUnit(trueCoeffcientsUnit); + // True coefficients b and c are converted to the tested angle unit + double coefficientsUnit[] = {trueCoefficients[0], trueCoefficients[1] * unitFactor, trueCoefficients[2] * unitFactor, trueCoefficients[3]}; + assert_regression_is(xi, yi, numberOfPoints, Model::Type::Trigonometric, coefficientsUnit, trueR2); + } + // Restore previous angleUnit + Poincare::Preferences::sharedPreferences()->setAngleUnit(previousAngleUnit); +} + +QUIZ_CASE(trigonometric_regression1) { + double r2 = 0.9994216; + double x[] = {1, 31, 61, 91, 121, 151, 181, 211, 241, 271, 301, 331, 361}; + double y[] = {9.24, 10.05, 11.33, 12.72, 14.16, 14.98, 15.14, 14.41, 13.24, 11.88, 10.54, 9.48, 9.19}; + double coefficients[] = {2.9723, 0.016780, -1.3067, 12.146}; + int numberOfPoints = sizeof(x) / sizeof(double); + assert(sizeof(y) == sizeof(double) * numberOfPoints); + + assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); +} + +QUIZ_CASE(trigonometric_regression2) { + double r2 = 0.9154; + double x[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48}; + double y[] = {-2, -4, -5, -2, 3, 6, 8, 11, 9, 5, 2, 1, 0, -3, -5, -2, 3, 5, 7, 10, 10, 5, 2, 2, 1}; + double coefficients[] = {6.42, 0.26, -2.16, 2.82}; + int numberOfPoints = sizeof(x) / sizeof(double); + assert(sizeof(y) == sizeof(double) * numberOfPoints); + + assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); +} QUIZ_CASE(logistic_regression) { @@ -123,11 +235,113 @@ QUIZ_CASE(logistic_regression) { double x1[] = {2.3, 5.6, 1.1, 4.3}; double y1[] = {3.948, 4.694, 2.184, 4.656}; double coefficients1[] = {6, 1.5, 4.7}; - assert_regression_is(x1, y1, 4, Model::Type::Logistic, coefficients1); + double r21 = 0.9999999917270119; + assert_regression_is(x1, y1, 4, Model::Type::Logistic, coefficients1, r21); // This data produced a wrong fit before double x2[] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; double y2[] = {5.0, 9.0, 40.0, 64.0, 144.0, 200.0, 269.0, 278.0, 290.0, 295.0}; double coefficients2[] = {64.9, 1.0, 297.4}; - assert_regression_is(x2, y2, 10, Model::Type::Logistic, coefficients2); + double r22 = 0.9984396821656006; + assert_regression_is(x2, y2, 10, Model::Type::Logistic, coefficients2, r22); + + // TODO : This data produce a wrong fit currently + // double x3[] = {1.0, 3.0, 4.0, 6.0, 8.0}; + // double y3[] = {4.0, 4.0, 0.0, 58.0, 5.0}; + // No source of truth for coefficient, r2 should at least be positive. + // double coefficients3[] = {-0.1, -0.4, -4}; + // double r23 = 0.75; + // assert_regression_is(x3, y3, 5, Model::Type::Logistic, coefficients3, r23); +} + +// Testing column and regression calculation + +void assert_column_calculations_is(double * xi, int numberOfPoints, double trueMean, double trueSum, double trueSquaredSum, double trueStandardDeviation, double trueVariance) { + int series = 0; + Regression::Store store; + + setRegressionPoints(&store, series, numberOfPoints, xi); + + // Compute and compare the regression calculations metrics + double mean = store.meanOfColumn(series,0); + double sum = store.sumOfColumn(series,0); + double squaredSum = store.squaredValueSumOfColumn(series,0); + double standardDeviation = store.standardDeviationOfColumn(series,0); + double variance = store.varianceOfColumn(series,0); + + // Check that squaredSum, standardDeviation and variance are positive + quiz_assert(squaredSum >= 0.0); + quiz_assert(standardDeviation >= 0.0); + quiz_assert(variance >= 0.0); + + double precision = 1e-3; + // When the expected value is 0, the expected coefficient must be negligible against reference. + // The least likely value to be null is trueSquaredSum + double reference = trueSquaredSum; + + quiz_assert(IsApproximatelyEqual(variance, trueVariance, precision, reference)); + quiz_assert(IsApproximatelyEqual(squaredSum, trueSquaredSum, precision, reference)); + + // adapt the reference + reference = std::sqrt(trueSquaredSum); + + quiz_assert(IsApproximatelyEqual(mean, trueMean, precision, reference)); + quiz_assert(IsApproximatelyEqual(sum, trueSum, precision, reference)); + quiz_assert(IsApproximatelyEqual(standardDeviation, trueStandardDeviation, precision, reference)); +} + +QUIZ_CASE(column_calculation) { + double x[] = {2.3, 5.6, 1.1, 4.3}; + double mean = 3.325; + double sum = 13.3; + double squaredSum = 56.35; + double standardDeviation = 1.741228; + double variance = 3.031875; + assert_column_calculations_is(x, 4, mean, sum, squaredSum, standardDeviation, variance); +} + +QUIZ_CASE(constant_column_calculation) { + // This data produced a negative variance before + double x[] = {-996.85840734641, -996.85840734641, -996.85840734641}; + double mean = -996.85840734641; + double sum = -2990.57522203923; + double squaredSum = 2981180.0528916633; + double standardDeviation = 0.0; + double variance = 0.0; + assert_column_calculations_is(x, 3, mean, sum, squaredSum, standardDeviation, variance); +} + +void assert_regression_calculations_is(double * xi, double * yi, int numberOfPoints, double trueCovariance, double trueProductSum, double trueR) { + int series = 0; + Regression::Store store; + + setRegressionPoints(&store, series, numberOfPoints, xi, yi); + + double precision = 1e-3; + + // Compute and compare the regression calculations metrics + double covariance = store.covariance(series); + double productSum = store.columnProductSum(series); + + // trueProductSum and trueCovariance are using each other as reference + // By construction, they often have a close value with a numberOfPoints factor + quiz_assert(IsApproximatelyEqual(covariance, trueCovariance, precision, trueProductSum / numberOfPoints)); + quiz_assert(IsApproximatelyEqual(productSum, trueProductSum, precision, trueCovariance * numberOfPoints)); + + // When trueR = 0, a DBL_EPSILON reference ensures that the only accepted errors are due to double approximations + // sqrt is used because the R is computed from sqrt(V1*V0) + double reference = 100.0 * std::sqrt(DBL_EPSILON); + + double r = store.correlationCoefficient(series); + quiz_assert(r >= 0.0); + quiz_assert(IsApproximatelyEqual(r, trueR, precision, reference)); +} + +QUIZ_CASE(regression_calculation) { + double x[] = {1.0, 50.0, 34.0, 67.0, 20.0}; + double y[] = {71.860, 2775514, 979755.1, 6116830.0, 233832.9}; + double covariance = 4.7789036e7; + double productSum = 586591713.26; + double r = 0.919088; + assert_regression_calculations_is(x, y, 5, covariance, productSum, r); } diff --git a/apps/sequence/Makefile b/apps/sequence/Makefile index f36277bca..9331234ba 100644 --- a/apps/sequence/Makefile +++ b/apps/sequence/Makefile @@ -2,10 +2,7 @@ apps += Sequence::App app_headers += apps/sequence/app.h app_sequence_test_src = $(addprefix apps/sequence/,\ - cache_context.cpp \ - sequence.cpp \ - sequence_context.cpp \ - sequence_store.cpp \ + \ ) app_sequence_src = $(addprefix apps/sequence/,\ @@ -22,7 +19,6 @@ app_sequence_src = $(addprefix apps/sequence/,\ list/type_parameter_controller.cpp \ values/interval_parameter_controller.cpp \ values/values_controller.cpp \ - sequence_title_cell.cpp \ ) app_sequence_src += $(app_sequence_test_src) diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index d9d20f78c..d9ccf0931 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -1,6 +1,7 @@ #include "app.h" #include "../apps_container.h" #include "sequence_icon.h" +#include "../shared/global_context.h" using namespace Poincare; @@ -24,7 +25,6 @@ const Image * App::Descriptor::icon() { App::Snapshot::Snapshot() : Shared::FunctionApp::Snapshot::Snapshot(), - m_sequenceStore(), m_graphRange() { } @@ -44,18 +44,17 @@ App::Descriptor * App::Snapshot::descriptor() { } void App::Snapshot::tidy() { - m_sequenceStore.tidy(); m_graphRange.setDelegate(nullptr); } App::App(Snapshot * snapshot) : FunctionApp(snapshot, &m_inputViewController), - m_sequenceContext(AppsContainer::sharedAppsContainer()->globalContext(), snapshot->functionStore()), + m_sequenceContext(AppsContainer::sharedAppsContainer()->globalContext(), static_cast(AppsContainer::sharedAppsContainer()->globalContext())->sequenceStore()), m_listController(&m_listFooter, this, &m_listHeader, &m_listFooter), - m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey), + m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray), m_listHeader(nullptr, &m_listFooter, &m_listController), m_listStackViewController(&m_tabViewController, &m_listHeader), - m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->functionStore(), snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->previousModelsVersions(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader), + m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->functionStore(), snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), @@ -68,8 +67,15 @@ App::App(Snapshot * snapshot) : { } -SequenceContext * App::localContext() { +Shared::SequenceContext * App::localContext() { return &m_sequenceContext; } +NestedMenuController * App::variableBoxForInputEventHandler(InputEventHandler * textInput) { + MathVariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); + varBox->setSender(textInput); + varBox->lockDeleteEvent(MathVariableBoxController::Page::Sequence); + return varBox; +} + } diff --git a/apps/sequence/app.h b/apps/sequence/app.h index 12139933b..57b192d5f 100644 --- a/apps/sequence/app.h +++ b/apps/sequence/app.h @@ -2,14 +2,16 @@ #define SEQUENCE_APP_H #include -#include "sequence_context.h" -#include "sequence_store.h" +#include "../shared/sequence_context.h" +#include "../shared/sequence_store.h" #include "graph/graph_controller.h" #include "graph/curve_view_range.h" #include "list/list_controller.h" #include "values/values_controller.h" #include "../shared/function_app.h" #include "../shared/interval.h" +#include "../shared/global_context.h" +#include "../apps_container.h" namespace Sequence { @@ -28,12 +30,11 @@ public: App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; - SequenceStore * functionStore() override { return &m_sequenceStore; } + Shared::SequenceStore * functionStore() override { return static_cast(AppsContainer::sharedAppsContainer()->globalContext())->sequenceStore(); } CurveViewRange * graphRange() { return &m_graphRange; } Shared::Interval * interval() { return &m_interval; } private: void tidy() override; - SequenceStore m_sequenceStore; CurveViewRange m_graphRange; Shared::Interval m_interval; }; @@ -47,8 +48,9 @@ public: // TODO: override variableBoxForInputEventHandler to lock sequence in the variable box once they appear there // NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; CodePoint XNT() override { return 'n'; } - SequenceContext * localContext() override; - SequenceStore * functionStore() override { return snapshot()->functionStore(); } + NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; + Shared::SequenceContext * localContext() override; + Shared::SequenceStore * functionStore() override { return static_cast(AppsContainer::sharedAppsContainer()->globalContext())->sequenceStore(); } Shared::Interval * interval() { return snapshot()->interval(); } ValuesController * valuesController() override { return &m_valuesController; @@ -58,7 +60,7 @@ public: } private: App(Snapshot * snapshot); - SequenceContext m_sequenceContext; + Shared::SequenceContext m_sequenceContext; ListController m_listController; ButtonRowController m_listFooter; ButtonRowController m_listHeader; diff --git a/apps/sequence/base.nl.i18n b/apps/sequence/base.nl.i18n index 507923183..34a63d53d 100644 --- a/apps/sequence/base.nl.i18n +++ b/apps/sequence/base.nl.i18n @@ -11,7 +11,7 @@ SequenceOptions = "Rij-opties" SequenceColor = "Rij-kleur" DeleteSequence = "Rij verwijderen" NoSequence = "Geen rij ingevoerd" -NoActivatedSequence = "Geen rij is ingeschakelt" +NoActivatedSequence = "Geen rij is ingeschakeld" NStart = "N begin" NEnd = "N einde" TermSum = "Som van termen" diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp deleted file mode 100644 index be5a834b0..000000000 --- a/apps/sequence/cache_context.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "cache_context.h" -#include "sequence_store.h" -#include - -using namespace Poincare; - -namespace Sequence { - -template -CacheContext::CacheContext(Context * parentContext) : - ContextWithParent(parentContext), - m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}} -{ -} - -template -const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { - // [u|v|w](n(+1)?) - if (symbol.type() == ExpressionNode::Type::Symbol - && symbol.name()[0] >= SequenceStore::k_sequenceNames[0][0] - && symbol.name()[0] <= SequenceStore::k_sequenceNames[MaxNumberOfSequences-1][0] - && (strcmp(symbol.name()+1, "(n)") == 0 || strcmp(symbol.name()+1, "(n+1)") == 0)) - { - Symbol s = const_cast(static_cast(symbol)); - return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); - } - return ContextWithParent::expressionForSymbolAbstract(symbol, clone); -} - -template -void CacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol) { - m_values[nameIndexForSymbol(symbol)][rankIndexForSymbol(symbol)] = value; -} - -template -int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { - assert(strlen(symbol.name()) == 4 || strlen(symbol.name()) == 6); // [u|v|w](n(+1)?) - char name = symbol.name()[0]; - assert(name >= SequenceStore::k_sequenceNames[0][0] && name <= SequenceStore::k_sequenceNames[MaxNumberOfSequences-1][0]); // u, v or w - return name - 'u'; -} - -template -int CacheContext::rankIndexForSymbol(const Poincare::Symbol & symbol) { - assert(strlen(symbol.name()) == 4 || strlen(symbol.name()) == 6); // u(n) or u(n+1) - if (symbol.name()[3] == ')') { // .(n) - return 0; - } - // .(n+1) - return 1; -} - -template class CacheContext; -template class CacheContext; - -} diff --git a/apps/sequence/graph/curve_parameter_controller.cpp b/apps/sequence/graph/curve_parameter_controller.cpp index cab2f4330..546672d18 100644 --- a/apps/sequence/graph/curve_parameter_controller.cpp +++ b/apps/sequence/graph/curve_parameter_controller.cpp @@ -1,6 +1,7 @@ #include "curve_parameter_controller.h" #include "graph_controller.h" #include +#include using namespace Shared; diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index a4374164d..983bda7b3 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -15,75 +15,16 @@ CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) : MemoizedCurveViewRange::protectedSetXMin(-k_displayLeftMarginRatio * xMax(), k_lowerMaxFloat, k_upperMaxFloat); } -void CurveViewRange::roundAbscissa() { - int roundedXMean = std::round(xCenter()); - float halfScreenWidth = ((float)Ion::Display::Width)/2.0f; - float newXMin = roundedXMean - halfScreenWidth; - float newXMax = roundedXMean + halfScreenWidth - 1.0f; +void CurveViewRange::normalize(bool forceChangeY) { + Shared::InteractiveCurveViewRange::normalize(forceChangeY); + + /* The X axis is not supposed to go into the negatives, save for a small + * margin. However, after normalizing, it could be the case. We thus shift + * the X range rightward to the origin. */ float interestingXMin = m_delegate->interestingXMin(); - if (newXMin < interestingXMin) { - newXMin = interestingXMin - k_displayLeftMarginRatio * (float)Ion::Display::Width; - newXMax = newXMin + (float)Ion::Display::Width; - } - if (std::isnan(newXMin) || std::isnan(newXMax)) { - return; - } - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - setXMin(newXMin); -} - -void CurveViewRange::normalize() { - float xMean = xCenter(); - float yMean = yCenter(); - - const float unit = std::max(xGridUnit(), yGridUnit()); - - // Compute the X - const float newXHalfRange = NormalizedXHalfRange(unit); - float newXMin = xMean - newXHalfRange; - float newXMax = xMean + newXHalfRange; - float interestingXMin = m_delegate->interestingXMin(); - if (newXMin < interestingXMin) { - newXMin = interestingXMin -k_displayLeftMarginRatio*2.0f*newXHalfRange; - newXMax = newXMin + 2.0f*newXHalfRange; - } - if (!std::isnan(newXMin) && !std::isnan(newXMax)) { - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); - } - - // Compute the Y - m_yAuto = false; - const float newYHalfRange = NormalizedYHalfRange(unit); - float newYMin = yMean - newYHalfRange; - float newYMax = clipped(yMean + newYHalfRange, true); - if (!std::isnan(newYMin) && !std::isnan(newYMax)) { - m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); - } -} - -void CurveViewRange::setTrigonometric() { - float interestingXMin = m_delegate->interestingXMin(); - float interestingXRange = Preferences::sharedPreferences()->angleUnit() == Preferences::AngleUnit::Degree ? 1200.0f : 21.0f; - m_xRange.setMax(interestingXMin + interestingXRange, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(interestingXMin - k_displayLeftMarginRatio * interestingXRange, k_lowerMaxFloat, k_upperMaxFloat); - - m_yAuto = false; - constexpr float y = 1.6f; - m_yRange.setMax(y, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(-y, k_lowerMaxFloat, k_upperMaxFloat); -} - -void CurveViewRange::setDefault() { - if (m_delegate == nullptr) { - return; - } - m_yAuto = true; - float interestingXMin = m_delegate->interestingXMin(); - float interestingXRange = m_delegate->interestingXHalfRange(); - m_xRange.setMax(interestingXMin + interestingXRange, k_lowerMaxFloat, k_upperMaxFloat); - setXMin(interestingXMin - k_displayLeftMarginRatio * interestingXRange); + float xRange = xMax() - xMin(); + m_xRange.setMin(interestingXMin - k_displayLeftMarginRatio * xRange); + setXMax(xMin() + xRange); } } diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index cb50fb502..8205a9139 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -8,11 +8,9 @@ namespace Sequence { class CurveViewRange : public Shared::InteractiveCurveViewRange { public: CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr); - void roundAbscissa() override; - void normalize() override; - void setTrigonometric() override; - void setDefault() override; + void normalize(bool forceChangeY = false) override; private: + virtual bool hasDefaultRange() const override { return false; } constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index d35077101..906d56dfa 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -2,15 +2,18 @@ #include #include #include "../app.h" +#include +#include #include +#include using namespace Shared; using namespace Poincare; namespace Sequence { -GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : - FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, graphRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, previousModelsVersions, rangeVersion, angleUnitVersion), +GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) : + FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, graphRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion), m_bannerView(this, inputEventHandlerDelegate, this), m_view(sequenceStore, graphRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(graphRange), @@ -36,28 +39,13 @@ float GraphController::interestingXMin() const { int nmin = INT_MAX; int nbOfActiveModels = functionStore()->numberOfActiveFunctions(); for (int i = 0; i < nbOfActiveModels; i++) { - Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); nmin = std::min(nmin, s->initialRank()); } assert(nmin < INT_MAX); return nmin; } -float GraphController::interestingXHalfRange() const { - float standardRange = Shared::FunctionGraphController::interestingXHalfRange(); - int nmin = INT_MAX; - int nmax = 0; - int nbOfActiveModels = functionStore()->numberOfActiveFunctions(); - for (int i = 0; i < nbOfActiveModels; i++) { - Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - int firstInterestingIndex = s->initialRank(); - nmin = std::min(nmin, firstInterestingIndex); - nmax = std::max(nmax, firstInterestingIndex + static_cast(standardRange)); - } - assert(nmax - nmin >= standardRange); - return nmax - nmin; -} - bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp(); double floatBody; @@ -67,7 +55,7 @@ bool GraphController::textFieldDidFinishEditing(TextField * textField, const cha floatBody = std::fmax(0, std::round(floatBody)); double y = xyValues(selectedCurveIndex(), floatBody, myApp->localContext()).x2(); m_cursor->moveTo(floatBody, floatBody, y); - interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); m_view.reload(); return true; @@ -79,19 +67,19 @@ bool GraphController::handleEnter() { return FunctionGraphController::handleEnter(); } -bool GraphController::moveCursorHorizontally(int direction, bool fast) { +bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) { double xCursorPosition = std::round(m_cursor->x()); if (direction < 0 && xCursorPosition <= 0) { return false; } // The cursor moves by step that is larger than 1 and than a pixel's width. - const int step = std::ceil(m_view.pixelWidth()); + const int step = std::ceil(m_view.pixelWidth()) * scrollSpeed; double x = direction > 0 ? xCursorPosition + step: xCursorPosition - step; if (x < 0.0) { return false; } - Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); + Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); double y = s->evaluateXYAtParameter(x, textFieldDelegateApp()->localContext()).x2(); m_cursor->moveTo(x, x, y); return true; diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index 2177bc230..cc31b3285 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -8,27 +8,26 @@ #include "term_sum_controller.h" #include "../../shared/function_graph_controller.h" #include "../../shared/cursor_view.h" -#include "../sequence_store.h" +#include "../../shared/sequence_store.h" namespace Sequence { class GraphController final : public Shared::FunctionGraphController { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); + GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::SequenceStore * sequenceStore, CurveViewRange * graphRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; TermSumController * termSumController() { return &m_termSumController; } // InteractiveCurveViewRangeDelegate float interestingXMin() const override; - float interestingXHalfRange() const override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; private: Shared::XYBannerView * bannerView() override { return &m_bannerView; } bool handleEnter() override; - bool moveCursorHorizontally(int direction, bool fast = false) override; + bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; double defaultCursorT(Ion::Storage::Record record) override; CurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } - SequenceStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } + Shared::SequenceStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } GraphView * functionGraphView() override { return &m_view; } CurveParameterController * curveParameterController() override { return &m_curveParameterController; } Shared::CursorView m_cursorView; diff --git a/apps/sequence/graph/graph_view.cpp b/apps/sequence/graph/graph_view.cpp index 3dbb7bd92..3f71d92a5 100644 --- a/apps/sequence/graph/graph_view.cpp +++ b/apps/sequence/graph/graph_view.cpp @@ -19,7 +19,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { const int step = std::ceil(pixelWidth()); for (int i = 0; i < m_sequenceStore->numberOfActiveFunctions(); i++) { Ion::Storage::Record record = m_sequenceStore->activeRecordAtIndex(i); - Sequence * s = m_sequenceStore->modelForRecord(record);; + Shared::Sequence * s = m_sequenceStore->modelForRecord(record);; float rectXMin = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); rectXMin = rectXMin < 0 ? 0 : rectXMin; float rectXMax = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); diff --git a/apps/sequence/graph/graph_view.h b/apps/sequence/graph/graph_view.h index 62550ca86..e7f0fa60c 100644 --- a/apps/sequence/graph/graph_view.h +++ b/apps/sequence/graph/graph_view.h @@ -2,17 +2,17 @@ #define SEQUENCE_GRAPH_VIEW_H #include "../../shared/function_graph_view.h" -#include "../sequence_store.h" +#include "../../shared/sequence_store.h" namespace Sequence { class GraphView : public Shared::FunctionGraphView { public: - GraphView(SequenceStore * sequenceStore, Shared::InteractiveCurveViewRange * graphRange, + GraphView(Shared::SequenceStore * sequenceStore, Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); void drawRect(KDContext * ctx, KDRect rect) const override; private: - SequenceStore * m_sequenceStore; + Shared::SequenceStore * m_sequenceStore; }; } diff --git a/apps/sequence/graph/term_sum_controller.cpp b/apps/sequence/graph/term_sum_controller.cpp index 71cad17bb..cd2a47d88 100644 --- a/apps/sequence/graph/term_sum_controller.cpp +++ b/apps/sequence/graph/term_sum_controller.cpp @@ -49,7 +49,7 @@ double TermSumController::cursorNextStep(double x, int direction) { } Layout TermSumController::createFunctionLayout(Shared::ExpiringPointer function) { - Sequence * sequence = static_cast(function.pointer()); + Shared::Sequence * sequence = static_cast(function.pointer()); return sequence->nameLayout(); } diff --git a/apps/sequence/list/list_controller.cpp b/apps/sequence/list/list_controller.cpp index 48e59d38a..56f78b1d3 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -2,6 +2,7 @@ #include "../app.h" #include #include +#include using namespace Shared; using namespace Poincare; @@ -31,7 +32,7 @@ int ListController::numberOfExpressionRows() const { SequenceStore * store = const_cast(this)->modelStore(); const int modelsCount = store->numberOfModels(); for (int i = 0; i < modelsCount; i++) { - Sequence * sequence = store->modelForRecord(store->recordAtIndex(i)); + Shared::Sequence * sequence = store->modelForRecord(store->recordAtIndex(i)); numberOfRows += sequence->numberOfElements(); } return numberOfRows + (modelsCount == store->maxNumberOfModels()? 0 : 1); @@ -42,7 +43,7 @@ KDCoordinate ListController::expressionRowHeight(int j) { if (isAddEmptyRow(j)) { return defaultHeight; } - Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(modelIndexForRow(j))); + Shared::Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(modelIndexForRow(j))); Layout layout = sequence->layout(); if (sequenceDefinitionForRow(j) == 1) { layout = sequence->firstInitialConditionLayout(); @@ -67,7 +68,7 @@ Toolbox * ListController::toolboxForInputEventHandler(InputEventHandler * textIn // Set extra cells int recurrenceDepth = -1; int sequenceDefinition = sequenceDefinitionForRow(selectedRow()); - Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(modelIndexForRow(selectedRow()))); + Shared::Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(modelIndexForRow(selectedRow()))); if (sequenceDefinition == 0) { recurrenceDepth = sequence->numberOfElements()-1; } @@ -85,7 +86,7 @@ void ListController::selectPreviousNewSequenceCell() { void ListController::editExpression(int sequenceDefinition, Ion::Events::Event event) { Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); - Sequence * sequence = modelStore()->modelForRecord(record); + Shared::Sequence * sequence = modelStore()->modelForRecord(record); InputViewController * inputController = Shared::FunctionApp::app()->inputViewController(); if (event == Ion::Events::OK || event == Ion::Events::EXE) { char initialTextContent[Constant::MaxSerializedExpressionSize]; @@ -147,7 +148,7 @@ bool ListController::editInitialConditionOfSelectedRecordWithText(const char * t // Reset memoization of the selected cell which always corresponds to the k_memoizedCellsCount/2 memoized cell resetMemoizationForIndex(k_memoizedCellsCount/2); Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); - Sequence * sequence = modelStore()->modelForRecord(record); + Shared::Sequence * sequence = modelStore()->modelForRecord(record); Context * context = App::app()->localContext(); Ion::Storage::Record::ErrorStatus error = firstInitialCondition? sequence->setFirstInitialConditionContent(text, context) : sequence->setSecondInitialConditionContent(text, context); return (error == Ion::Storage::Record::ErrorStatus::None); @@ -179,7 +180,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { myCell->setBaseline(baseline(j)); // Set the layout Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(j)); - Sequence * sequence = modelStore()->modelForRecord(record); + Shared::Sequence * sequence = modelStore()->modelForRecord(record); if (sequenceDefinitionForRow(j) == 0) { myCell->setLayout(sequence->definitionName()); } @@ -197,7 +198,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) { FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(j)); - Sequence * sequence = modelStore()->modelForRecord(record); + Shared::Sequence * sequence = modelStore()->modelForRecord(record); if (sequenceDefinitionForRow(j) == 0) { myCell->setLayout(sequence->layout()); } @@ -223,7 +224,7 @@ int ListController::modelIndexForRow(int j) { int sequenceIndex = -1; do { sequenceIndex++; - Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(sequenceIndex)); + Shared::Sequence * sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(sequenceIndex)); rowIndex += sequence->numberOfElements(); } while (rowIndex <= j); return sequenceIndex; @@ -238,7 +239,7 @@ int ListController::sequenceDefinitionForRow(int j) { } int rowIndex = 0; int sequenceIndex = -1; - Sequence * sequence; + Shared::Sequence * sequence; do { sequenceIndex++; sequence = modelStore()->modelForRecord(modelStore()->recordAtIndex(sequenceIndex)); @@ -259,7 +260,7 @@ void ListController::editExpression(Ion::Events::Event event) { void ListController::reinitSelectedExpression(ExpiringPointer model) { // Invalidate the sequences context cache App::app()->localContext()->resetCache(); - Sequence * sequence = static_cast(model.pointer()); + Shared::Sequence * sequence = static_cast(model.pointer()); switch (sequenceDefinitionForRow(selectedRow())) { case 1: if (sequence->firstInitialConditionExpressionClone().isUninitialized()) { diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index 70387a3bb..d16af18d9 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -2,8 +2,8 @@ #define SEQUENCE_LIST_CONTROLLER_H #include -#include "../sequence_title_cell.h" -#include "../sequence_store.h" +#include "../../shared/sequence_title_cell.h" +#include "../../shared/sequence_store.h" #include "../../shared/function_expression_cell.h" #include "../../shared/function_list_controller.h" #include "../../shared/input_event_handler_delegate.h" @@ -40,9 +40,9 @@ private: void reinitSelectedExpression(Shared::ExpiringPointer model) override; void editExpression(Ion::Events::Event event) override; bool removeModelRow(Ion::Storage::Record record) override; - SequenceStore * modelStore() override; - constexpr static int k_maxNumberOfRows = 3*MaxNumberOfSequences; - SequenceTitleCell m_sequenceTitleCells[k_maxNumberOfRows]; + Shared::SequenceStore * modelStore() override; + constexpr static int k_maxNumberOfRows = 3*Shared::MaxNumberOfSequences; + Shared::SequenceTitleCell m_sequenceTitleCells[k_maxNumberOfRows]; Shared::FunctionExpressionCell m_expressionCells[k_maxNumberOfRows]; ListParameterController m_parameterController; TypeParameterController m_typeParameterController; diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index ebdd85905..9058083e3 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -2,6 +2,7 @@ #include "list_controller.h" #include "../app.h" #include "../../shared/poincare_helpers.h" +#include using namespace Poincare; using namespace Shared; @@ -67,7 +68,7 @@ bool ListParameterController::textFieldShouldFinishEditing(TextField * textField } bool ListParameterController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { - static float maxFirstIndex = std::pow(10.0f, Sequence::k_initialRankNumberOfDigits) - 1.0f; + static float maxFirstIndex = std::pow(10.0f, Shared::Sequence::k_initialRankNumberOfDigits) - 1.0f; /* -1 to take into account a double recursive sequence, which has * SecondIndex = FirstIndex + 1 */ double floatBody; @@ -141,8 +142,8 @@ void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int if (myCell->isEditing()) { return; } - char buffer[Sequence::k_initialRankNumberOfDigits+1]; - Poincare::Integer(sequence()->initialRank()).serialize(buffer, Sequence::k_initialRankNumberOfDigits+1); + char buffer[Shared::Sequence::k_initialRankNumberOfDigits+1]; + Poincare::Integer(sequence()->initialRank()).serialize(buffer, Shared::Sequence::k_initialRankNumberOfDigits+1); myCell->setAccessoryText(buffer); } } @@ -155,7 +156,7 @@ int ListParameterController::totalNumberOfCells() const { }; bool ListParameterController::hasInitialRankRow() const { - return !m_record.isNull() && const_cast(this)->sequence()->type() != Sequence::Type::Explicit; + return !m_record.isNull() && const_cast(this)->sequence()->type() != Shared::Sequence::Type::Explicit; } } diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index cc5ddae78..911630a0c 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -3,8 +3,8 @@ #include "../../shared/list_parameter_controller.h" #include "../../shared/parameter_text_field_delegate.h" -#include "../sequence.h" -#include "../sequence_store.h" +#include "../../shared/sequence.h" +#include "../../shared/sequence_store.h" #include "type_parameter_controller.h" namespace Sequence { @@ -31,7 +31,7 @@ private: constexpr static int k_totalNumberOfCell = 4; #endif int totalNumberOfCells() const override; - Sequence * sequence() { return static_cast(function().pointer()); } + Shared::Sequence * sequence() { return static_cast(function().pointer()); } bool hasInitialRankRow() const; MessageTableCellWithChevronAndExpression m_typeCell; MessageTableCellWithEditableText m_initialRankCell; diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index a25ce35cf..8b87de89c 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -1,5 +1,5 @@ #include "sequence_toolbox.h" -#include "../sequence_store.h" +#include "../../shared/sequence_store.h" #include #include #include @@ -20,20 +20,17 @@ SequenceToolbox::SequenceToolbox() : } bool SequenceToolbox::handleEvent(Ion::Events::Event event) { - if (selectedRow() < m_numberOfAddedCells && stackDepth() == 0) { + if (stackDepth() == 0 && selectedRow() < m_numberOfAddedCells) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { return selectAddedCell(selectedRow()); } return false; } - return MathToolbox::handleEventForRow(event, mathToolboxIndex(selectedRow())); + return MathToolbox::handleEventForRow(event, selectedRow() - stackRowOffset()); } int SequenceToolbox::numberOfRows() const { - if (stackDepth() == 0) { - return MathToolbox::numberOfRows()+m_numberOfAddedCells; - } - return MathToolbox::numberOfRows(); + return MathToolbox::numberOfRows() + stackRowOffset(); } HighlightCell * SequenceToolbox::reusableCell(int index, int type) { @@ -52,14 +49,14 @@ void SequenceToolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { cell->reloadCell(); return; } - MathToolbox::willDisplayCellForIndex(cell, mathToolboxIndex(index)); + MathToolbox::willDisplayCellForIndex(cell, index - stackRowOffset()); } int SequenceToolbox::typeAtLocation(int i, int j) { if (stackDepth() == 0 && j < m_numberOfAddedCells) { return 2; } - return MathToolbox::typeAtLocation(i,mathToolboxIndex(j)); + return MathToolbox::typeAtLocation(i, j - stackRowOffset()); } void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recurrenceDepth) { @@ -69,14 +66,14 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu m_numberOfAddedCells = 0; return; } - /* The cells added reprensent the sequence at smaller ranks than its depth + /* The cells added represent the sequence at smaller ranks than its depth * and the other sequence at ranks smaller or equal to the depth, ie: * if the sequence is u(n+1), we add cells u(n), v(n), v(n+1), w(n), w(n+1). * There is a special case for double recurrent sequences because we do not * want to parse symbols u(n+2), v(n+2) or w(n+2). */ m_numberOfAddedCells = 0; - int sequenceIndex = SequenceStore::sequenceIndexForName(sequenceName[0]); - for (int i = 0; i < MaxNumberOfSequences; i++) { + int sequenceIndex = Shared::SequenceStore::sequenceIndexForName(sequenceName[0]); + for (int i = 0; i < Shared::MaxNumberOfSequences; i++) { for (int j = 0; j < recurrenceDepth+1; j++) { // When defining u(n+1) for ex, don't add [u|v|w](n+2) or u(n+1) if (j == 2 || (j == recurrenceDepth && sequenceIndex == i)) { @@ -84,7 +81,7 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu } const char * indice = j == 0 ? "n" : "n+1"; m_addedCellLayout[m_numberOfAddedCells++] = HorizontalLayout::Builder( - CodePointLayout::Builder(SequenceStore::k_sequenceNames[i][0], KDFont::LargeFont), + CodePointLayout::Builder(Shared::SequenceStore::k_sequenceNames[i][0], KDFont::LargeFont), VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); } @@ -100,12 +97,4 @@ bool SequenceToolbox::selectAddedCell(int selectedRow){ return true; } -int SequenceToolbox::mathToolboxIndex(int index) { - int indexMathToolbox = index; - if (stackDepth() == 0) { - indexMathToolbox = index - m_numberOfAddedCells; - } - return indexMathToolbox; -} - } diff --git a/apps/sequence/list/sequence_toolbox.h b/apps/sequence/list/sequence_toolbox.h index c67122a9f..de0fe6047 100644 --- a/apps/sequence/list/sequence_toolbox.h +++ b/apps/sequence/list/sequence_toolbox.h @@ -17,8 +17,11 @@ public: int typeAtLocation(int i, int j) override; void buildExtraCellsLayouts(const char * sequenceName, int recurrenceDepth); private: + /* At 0 depth, there are additional rows to display. With the exception of + * NestedMenuController::returnToPreviousMenu(), it must be ignored in + * parent's classes. */ + int stackRowOffset() const override { return stackDepth() == 0 ? m_numberOfAddedCells : 0; } bool selectAddedCell(int selectedRow); - int mathToolboxIndex(int index); ExpressionTableCell m_addedCells[k_maxNumberOfDisplayedRows]; Poincare::Layout m_addedCellLayout[k_maxNumberOfDisplayedRows]; int m_numberOfAddedCells; diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index cec1c3e04..e7ad0c56e 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace Poincare; using namespace Shared; @@ -48,21 +49,21 @@ void TypeParameterController::viewDidDisappear() { } void TypeParameterController::didBecomeFirstResponder() { - selectCellAtLocation(0, 0); + selectCellAtLocation(0, m_record == nullptr ? 0 : static_cast(sequence()->type())); Container::activeApp()->setFirstResponder(&m_selectableTableView); } bool TypeParameterController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { if (!m_record.isNull()) { - Sequence::Type sequenceType = (Sequence::Type)selectedRow(); + Shared::Sequence::Type sequenceType = (Shared::Sequence::Type)selectedRow(); if (sequence()->type() != sequenceType) { m_listController->selectPreviousNewSequenceCell(); sequence()->setType(sequenceType); // Invalidate sequence context cache when changing sequence type App::app()->localContext()->resetCache(); // Reset the first index if the new type is "Explicit" - if (sequenceType == Sequence::Type::Explicit) { + if (sequenceType == Shared::Sequence::Type::Explicit) { sequence()->setInitialRank(0); } } @@ -79,8 +80,8 @@ bool TypeParameterController::handleEvent(Ion::Events::Event event) { } assert(error == Ion::Storage::Record::ErrorStatus::None); Ion::Storage::Record record = sequenceStore()->recordAtIndex(sequenceStore()->numberOfModels()-1); - Sequence * newSequence = sequenceStore()->modelForRecord(record); - newSequence->setType((Sequence::Type)selectedRow()); + Shared::Sequence * newSequence = sequenceStore()->modelForRecord(record); + newSequence->setType((Shared::Sequence::Type)selectedRow()); Container::activeApp()->dismissModalViewController(); m_listController->editExpression(0, Ion::Events::OK); return true; diff --git a/apps/sequence/list/type_parameter_controller.h b/apps/sequence/list/type_parameter_controller.h index 711e0f073..f6bf68862 100644 --- a/apps/sequence/list/type_parameter_controller.h +++ b/apps/sequence/list/type_parameter_controller.h @@ -3,7 +3,7 @@ #include #include -#include "../sequence_store.h" +#include "../../shared/sequence_store.h" namespace Sequence { @@ -28,11 +28,11 @@ public: void setRecord(Ion::Storage::Record record); private: StackViewController * stackController() const; - Sequence * sequence() { + Shared::Sequence * sequence() { assert(!m_record.isNull()); return sequenceStore()->modelForRecord(m_record); } - SequenceStore * sequenceStore(); + Shared::SequenceStore * sequenceStore(); constexpr static int k_totalNumberOfCell = 3; ExpressionTableCellWithPointer m_explicitCell; ExpressionTableCellWithPointer m_singleRecurrenceCell; diff --git a/apps/sequence/sequence_context.cpp b/apps/sequence/sequence_context.cpp deleted file mode 100644 index fbb485f14..000000000 --- a/apps/sequence/sequence_context.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "sequence_context.h" -#include "sequence_store.h" -#include - -using namespace Poincare; -using namespace Shared; - -namespace Sequence { - -template -TemplatedSequenceContext::TemplatedSequenceContext() : - m_rank(-1), - m_values{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}} -{ -} - -template -T TemplatedSequenceContext::valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const { - return m_values[sequenceIndex][rank]; -} - -template -void TemplatedSequenceContext::resetCache() { - m_rank = -1; -} - -template -bool TemplatedSequenceContext::iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx) { - if (m_rank > n) { - m_rank = -1; - } - if (n < 0 || n-m_rank > k_maxRecurrentRank) { - return false; - } - while (m_rank++ < n) { - step(sequenceStore, sqctx); - } - m_rank--; - return true; -} - -template -void TemplatedSequenceContext::step(SequenceStore * sequenceStore, SequenceContext * sqctx) { - /* Shift values */ - for (int i = 0; i < MaxNumberOfSequences; i++) { - for (int j = MaxRecurrenceDepth; j > 0; j--) { - m_values[i][j] = m_values[i][j-1]; - } - m_values[i][0] = NAN; - } - - /* Evaluate new u(n) and v(n) */ - // sequences hold u, v, w in this order - Sequence * sequences[MaxNumberOfSequences] = {nullptr, nullptr, nullptr}; - for (int i = 0; i < sequenceStore->numberOfModels(); i++) { - Sequence * u = sequenceStore->modelForRecord(sequenceStore->recordAtIndex(i)); - sequences[SequenceStore::sequenceIndexForName(u->fullName()[0])] = u->isDefined() ? u : nullptr; - } - - /* Approximate u, v and w at the new rank. We have to evaluate all sequences - * MaxNumberOfSequences times in case the evaluations depend on each other. - * For example, if u expression depends on v and v depends on w. At the first - * iteration, we can only evaluate w, at the second iteration we evaluate v - * and u can only be known at the third iteration . */ - for (int k = 0; k < MaxNumberOfSequences; k++) { - for (int i = 0; i < MaxNumberOfSequences; i++) { - if (std::isnan(m_values[i][0])) { - m_values[i][0] = sequences[i] ? sequences[i]->approximateToNextRank(m_rank, sqctx) : NAN; - } - } - } -} - -template class TemplatedSequenceContext; -template class TemplatedSequenceContext; - -} diff --git a/apps/sequence/sequence_context.h b/apps/sequence/sequence_context.h deleted file mode 100644 index e4ca2cb42..000000000 --- a/apps/sequence/sequence_context.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef SEQUENCE_SEQUENCE_CONTEXT_H -#define SEQUENCE_SEQUENCE_CONTEXT_H - -#include -#include -#include - -namespace Sequence { - -constexpr static int MaxRecurrenceDepth = 2; -static constexpr int MaxNumberOfSequences = 3; - -class SequenceStore; -class SequenceContext; - -template -class TemplatedSequenceContext { -public: - TemplatedSequenceContext(); - T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const; - void resetCache(); - bool iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx); -private: - constexpr static int k_maxRecurrentRank = 10000; - /* Cache: - * In order to accelerate the computation of values of recurrent sequences, - * we memoize the last computed values of the sequence and their associated - * ranks (n and n+1 for instance). Thereby, when another evaluation at a - * superior rank k > n+1 is called, we avoid iterating from 0 but can start - * from n. */ - void step(SequenceStore * sequenceStore, SequenceContext * sqctx); - int m_rank; - T m_values[MaxNumberOfSequences][MaxRecurrenceDepth+1]; -}; - -class SequenceContext : public Poincare::ContextWithParent { -public: - SequenceContext(Poincare::Context * parentContext, SequenceStore * sequenceStore) : - ContextWithParent(parentContext), - m_floatSequenceContext(), - m_doubleSequenceContext(), - m_sequenceStore(sequenceStore) {} - /* expressionForSymbolAbstract & setExpressionForSymbolAbstractName directly call the parent - * context respective methods. Indeed, special chars like n, u(n), u(n+1), - * v(n), v(n+1) are taken into accound only when evaluating sequences which - * is done in another context. */ - template T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const { - if (sizeof(T) == sizeof(float)) { - return m_floatSequenceContext.valueOfSequenceAtPreviousRank(sequenceIndex, rank); - } - return m_doubleSequenceContext.valueOfSequenceAtPreviousRank(sequenceIndex, rank); - } - void resetCache() { - m_floatSequenceContext.resetCache(); - m_doubleSequenceContext.resetCache(); - } - template bool iterateUntilRank(int n) { - if (sizeof(T) == sizeof(float)) { - return m_floatSequenceContext.iterateUntilRank(n, m_sequenceStore, this); - } - return m_doubleSequenceContext.iterateUntilRank(n, m_sequenceStore, this); - } -private: - TemplatedSequenceContext m_floatSequenceContext; - TemplatedSequenceContext m_doubleSequenceContext; - SequenceStore * m_sequenceStore; -}; - -} - -#endif diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 8d423b010..fb729ab14 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -3,14 +3,13 @@ #include #include #include -#include "../sequence_store.h" -#include "../sequence_context.h" +#include "../../shared/sequence_store.h" +#include "../../shared/sequence_context.h" #include "../../shared/poincare_helpers.h" using namespace Poincare; -using namespace Shared; -namespace Sequence { +namespace Shared { Sequence * addSequence(SequenceStore * store, Sequence::Type type, const char * definition, const char * condition1, const char * condition2, Context * context) { Ion::Storage::Record::ErrorStatus err = store->addEmptyModel(); @@ -31,12 +30,12 @@ Sequence * addSequence(SequenceStore * store, Sequence::Type type, const char * void check_sequences_defined_by(double result[MaxNumberOfSequences][10], Sequence::Type types[MaxNumberOfSequences], const char * definitions[MaxNumberOfSequences], const char * conditions1[MaxNumberOfSequences], const char * conditions2[MaxNumberOfSequences]) { Shared::GlobalContext globalContext; - SequenceStore store; - SequenceContext sequenceContext(&globalContext, &store); + SequenceStore * store = globalContext.sequenceStore(); + SequenceContext sequenceContext(&globalContext, store); Sequence * seqs[MaxNumberOfSequences]; for (int i = 0; i < MaxNumberOfSequences; i++) { - seqs[i] = addSequence(&store, types[i], definitions[i], conditions1[i], conditions2[i], &globalContext); + seqs[i] = addSequence(store, types[i], definitions[i], conditions1[i], conditions2[i], &globalContext); } for (int j = 0; j < 10; j++) { @@ -47,20 +46,25 @@ void check_sequences_defined_by(double result[MaxNumberOfSequences][10], Sequenc } } } - store.removeAll(); + store->removeAll(); + /* The store is a global variable that has been contructed through + * GlobalContext::sequenceStore singleton. It won't be destructed. However, + * we need to make sure that the pool is empty between quiz_cases. */ + store->tidy(); } void check_sum_of_sequence_between_bounds(double result, double start, double end, Sequence::Type type, const char * definition, const char * condition1, const char * condition2) { Shared::GlobalContext globalContext; - SequenceStore store; - SequenceContext sequenceContext(&globalContext, &store); + SequenceStore * store = globalContext.sequenceStore(); + SequenceContext sequenceContext(&globalContext, store); - Sequence * seq = addSequence(&store, type, definition, condition1, condition2, &globalContext); + Sequence * seq = addSequence(store, type, definition, condition1, condition2, &globalContext); double sum = PoincareHelpers::ApproximateToScalar(seq->sumBetweenBounds(start, end, &sequenceContext), &globalContext); quiz_assert(std::fabs(sum - result) < 0.00000001); - store.removeAll(); + store->removeAll(); + store->tidy(); // Cf comment above } QUIZ_CASE(sequence_evaluation) { @@ -336,6 +340,124 @@ QUIZ_CASE(sequence_evaluation) { conditions1[2] = nullptr; conditions2[2] = nullptr; check_sequences_defined_by(results28, types, definitions, conditions1, conditions2); + + // u independent, v depends on u(3) + // u(n) = n; v(n) = u(5)+n + double results29[MaxNumberOfSequences][10] = {{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}, + {5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0}, + {}}; + types[0] = Sequence::Type::Explicit; + types[1] = Sequence::Type::Explicit; + definitions[0] = "n"; + definitions[1] = "u(5)+n"; + definitions[2] = nullptr; + conditions1[0] = nullptr; + conditions2[0] = nullptr; + conditions1[1] = nullptr; + conditions2[1] = nullptr; + conditions1[2] = nullptr; + conditions2[2] = nullptr; + check_sequences_defined_by(results29, types, definitions, conditions1, conditions2); + +// u independent, v depends on u(2) +// u(n) = n; v(n+1) = v(n)-u(2) + double results30[MaxNumberOfSequences][10] = {{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}, + {2.0, 0.0, -2.0, -4.0, -6.0, -8.0, -10.0, -12.0, -14.0, -16.0}, + {}}; + types[0] = Sequence::Type::Explicit; + types[1] = Sequence::Type::SingleRecurrence; + definitions[0] = "n"; + definitions[1] = "v(n)-u(2)"; + conditions1[0] = nullptr; + conditions2[0] = nullptr; + conditions1[1] = "u(2)"; + check_sequences_defined_by(results30, types, definitions, conditions1, conditions2); + +// u and v interdependent +// u(n+2) = n + v(3) + u(n+1) - u(n); v(n) = u(n) - u(1) + double results31[MaxNumberOfSequences][10] = {{0.0, 3.0, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + {-3.0, 0.0, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + {}}; + types[0] = Sequence::Type::DoubleRecurrence; + types[1] = Sequence::Type::Explicit; + definitions[0] = "n+v(3)+u(n+1)-u(n)"; + definitions[1] = "u(n)-u(1)"; + conditions1[0] = "0"; + conditions2[0] = "3"; + check_sequences_defined_by(results31, types, definitions, conditions1, conditions2); + +// u is independent, v depends on u(120) and w(5), w depends on u(8) +// u(n) = n; v(n+2) = v(n+1) + v(n) + u(120); w(n+1) = w(n) - u(8) + double results32[MaxNumberOfSequences][10] = {{0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}, + {46.0, 6.0, 172.0, 298.0, 590.0, 1008.0, 1718.0, 2846.0, 4684.0, 7650.0}, + {6.0, 14.0, 22.0, 30.0, 38.0, 46.0, 54.0, 62.0, 70.0, 78.0}}; + types[0] = Sequence::Type::Explicit; + types[1] = Sequence::Type::DoubleRecurrence; + types[2] = Sequence::Type::SingleRecurrence; + definitions[0] = "n"; + definitions[1] = "v(n+1)+v(n)+u(120)"; + definitions[2] = "w(n)+u(8)"; + conditions1[0] = nullptr; + conditions2[0] = nullptr; + conditions1[1] = "w(5)"; + conditions2[1] = "6"; + conditions1[2] = "6"; + conditions2[2] = nullptr; + check_sequences_defined_by(results32, types, definitions, conditions1, conditions2); + + // u independent, v depends on u(n+6) + // u(n) = 9n; v(n+1) = u(n+6)+v(0); v(0) = 9 + double results33[MaxNumberOfSequences][10] = {{0.0, 9.0, 18.0, 27.0, 36.0, 45.0, 54.0, 63.0, 72.0, 81.0}, + {9.0, 63.0, 72.0, 81.0, 90.0, 99.0, 108.0, 117.0, 126.0, 135.0}, + {}}; + types[0] = Sequence::Type::Explicit; + types[1] = Sequence::Type::SingleRecurrence; + definitions[0] = "9n"; + definitions[1] = "u(n+6)+v(0)"; + definitions[2] = nullptr; + conditions1[1] = "9"; + check_sequences_defined_by(results33, types, definitions, conditions1, conditions2); + + // Explicit self-referencing sequences + double results34[MaxNumberOfSequences][10] = { + {NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + {NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN}, + {NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN} + }; + types[0] = Sequence::Type::Explicit; types[1] = Sequence::Type::Explicit; types[2] = Sequence::Type::Explicit; + conditions1[0] = nullptr; conditions1[1] = nullptr; conditions1[2] = nullptr; + conditions2[0] = nullptr; conditions2[1] = nullptr; conditions2[2] = nullptr; + definitions[0] = "|u(0)|"; + definitions[1] = "floor(v(0))"; + definitions[2] = "ceil(w(0))"; + check_sequences_defined_by(results34, types, definitions, conditions1, conditions2); + definitions[0] = "acos(u(0))"; + definitions[1] = "asin(v(0))"; + definitions[2] = "atan(w(0))"; + check_sequences_defined_by(results34, types, definitions, conditions1, conditions2); + definitions[0] = "cos(u(0))"; + definitions[1] = "sin(v(0))"; + definitions[2] = "tan(w(0))"; + check_sequences_defined_by(results34, types, definitions, conditions1, conditions2); + definitions[0] = "1+u(0)"; + definitions[1] = "2*v(0)"; + definitions[2] = "2^(u(0))"; + check_sequences_defined_by(results34, types, definitions, conditions1, conditions2); + definitions[0] = "ℯ^(u(0))"; + definitions[1] = "√(v(0))"; + definitions[2] = "log(u(0))"; + check_sequences_defined_by(results34, types, definitions, conditions1, conditions2); + + // u(n) = log(v(2)); v(n) = 10 + double results35[MaxNumberOfSequences][10] = {{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}, + {10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0}, + {}}; + types[0] = Sequence::Type::Explicit; + types[1] = Sequence::Type::Explicit; + definitions[0] = "log(v(2))"; + definitions[1] = "10"; + definitions[2] = nullptr; + check_sequences_defined_by(results35, types, definitions, conditions1, conditions2); } QUIZ_CASE(sequence_sum_evaluation) { diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index 36ae99790..9afe28fc8 100644 --- a/apps/sequence/values/values_controller.cpp +++ b/apps/sequence/values/values_controller.cpp @@ -3,6 +3,7 @@ #include #include "../../shared/poincare_helpers.h" #include "../app.h" +#include using namespace Poincare; @@ -51,8 +52,8 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in return; } if (typeAtLocation(i,j) == k_functionTitleCellType) { - SequenceTitleCell * myCell = (SequenceTitleCell *)cell; - Sequence * sequence = functionStore()->modelForRecord(recordAtColumn(i)); + Shared::SequenceTitleCell * myCell = (Shared::SequenceTitleCell *)cell; + Shared::Sequence * sequence = functionStore()->modelForRecord(recordAtColumn(i)); myCell->setLayout(sequence->nameLayout()); myCell->setColor(sequence->color()); } @@ -95,7 +96,7 @@ Shared::Interval * ValuesController::intervalAtColumn(int columnIndex) { void ValuesController::fillMemoizedBuffer(int column, int row, int index) { char * buffer = memoizedBufferAtIndex(index); double abscissa = intervalAtColumn(column)->element(row-1); // Subtract the title row from row to get the element index - Shared::ExpiringPointer sequence = functionStore()->modelForRecord(recordAtColumn(column)); + Shared::ExpiringPointer sequence = functionStore()->modelForRecord(recordAtColumn(column)); Coordinate2D xy = sequence->evaluateXYAtParameter(abscissa, textFieldDelegateApp()->localContext()); Shared::PoincareHelpers::ConvertFloatToText(xy.x2(), buffer, k_valuesCellBufferSize, Preferences::LargeNumberOfSignificantDigits); } diff --git a/apps/sequence/values/values_controller.h b/apps/sequence/values/values_controller.h index e24558e4a..80cd98b8f 100644 --- a/apps/sequence/values/values_controller.h +++ b/apps/sequence/values/values_controller.h @@ -1,8 +1,8 @@ #ifndef SEQUENCE_VALUES_CONTROLLER_H #define SEQUENCE_VALUES_CONTROLLER_H -#include "../sequence_store.h" -#include "../sequence_title_cell.h" +#include "../../shared/sequence_store.h" +#include "../../shared/sequence_title_cell.h" #include "../../shared/values_controller.h" #include "interval_parameter_controller.h" @@ -46,7 +46,7 @@ private: bool setDataAtLocation(double floatBody, int columnIndex, int rowIndex) override; // Model getters - SequenceStore * functionStore() const override { return static_cast(Shared::ValuesController::functionStore()); } + Shared::SequenceStore * functionStore() const override { return static_cast(Shared::ValuesController::functionStore()); } Shared::Interval * intervalAtColumn(int columnIndex) override; // Function evaluation memoization @@ -75,7 +75,7 @@ private: assert (j >= 0 && j < abscissaTitleCellsCount()); return &m_abscissaTitleCell; } - SequenceTitleCell * functionTitleCells(int j) override { + Shared::SequenceTitleCell * functionTitleCells(int j) override { assert(j >= 0 && j < k_maxNumberOfDisplayableSequences); return &m_sequenceTitleCells[j]; } @@ -85,7 +85,7 @@ private: } SelectableTableView m_selectableTableView; - SequenceTitleCell m_sequenceTitleCells[k_maxNumberOfDisplayableSequences]; + Shared::SequenceTitleCell m_sequenceTitleCells[k_maxNumberOfDisplayableSequences]; EvenOddBufferTextCell m_floatCells[k_maxNumberOfDisplayableCells]; EvenOddMessageTextCell m_abscissaTitleCell; EvenOddEditableTextCell m_abscissaCells[k_maxNumberOfDisplayableRows]; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 92ad4a2f2..65188f76d 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -19,7 +19,7 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/display_mode_controller.cpp \ sub_menu/exam_mode_controller.cpp \ sub_menu/generic_sub_controller.cpp \ - sub_menu/language_controller.cpp \ + sub_menu/localization_controller.cpp \ sub_menu/preferences_controller.cpp \ sub_menu/contributors_controller.cpp \ sub_menu/math_options_controller.cpp \ diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index fc266026b..75b598d87 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -32,7 +32,7 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_popUpCell(I18n::Message::Default, KDFont::LargeFont), m_selectableTableView(this), m_mathOptionsController(this, inputEventHandlerDelegate), - m_languageController(this, Metric::CommonTopMargin), + m_localizationController(this, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_accessibilityController(this), m_dateTimeController(this), m_examModeController(this), @@ -83,9 +83,10 @@ bool MainController::handleEvent(Ion::Events::Event event) { } return false; } - if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language) { + if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language || model()->childAtIndex(selectedRow())->label() == I18n::Message::Country) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - stackController()->push(&m_languageController); + m_localizationController.setMode(model()->childAtIndex(selectedRow())->label() == I18n::Message::Language ? LocalizationController::Mode::Language : LocalizationController::Mode::Country); + stackController()->push(&m_localizationController); return true; } return false; @@ -191,6 +192,11 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { static_cast(cell)->setSubtitle(I18n::LanguageNames[index]); return; } + if (model()->childAtIndex(index)->label() == I18n::Message::Country) { + int index = (int)(globalPreferences->country()); + static_cast(cell)->setSubtitle(I18n::CountryNames[index]); + return; + } if (model()->childAtIndex(index)->label() == I18n::Message::UpdatePopUp || model()->childAtIndex(index)->label() == I18n::Message::BetaPopUp) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 8d5064b82..7ef06c84a 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -8,7 +8,7 @@ #include "sub_menu/accessibility_controller.h" #include "sub_menu/datetime_controller.h" #include "sub_menu/exam_mode_controller.h" -#include "sub_menu/language_controller.h" +#include "sub_menu/localization_controller.h" #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" @@ -71,7 +71,7 @@ private: MessageTableCellWithSwitch m_popUpCell; SelectableTableView m_selectableTableView; MathOptionsController m_mathOptionsController; - LanguageController m_languageController; + LocalizationController m_localizationController; AccessibilityController m_accessibilityController; DateTimeController m_dateTimeController; ExamModeController m_examModeController; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index b86ce15b8..b462cc14b 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -12,6 +12,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), SettingsMessageTree(I18n::Message::BetaPopUp), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 570948924..965fffe07 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -11,6 +11,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 10f57fe41..3c74faffc 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -12,6 +12,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), diff --git a/apps/settings/sub_menu/accessibility_controller.cpp b/apps/settings/sub_menu/accessibility_controller.cpp index 1df981f37..3fdf2e4e8 100644 --- a/apps/settings/sub_menu/accessibility_controller.cpp +++ b/apps/settings/sub_menu/accessibility_controller.cpp @@ -63,7 +63,7 @@ bool AccessibilityController::handleEvent(Ion::Events::Event event) { KDIonContext::sharedContext()->gamma.setGamma(redGamma, greenGamma, blueGamma); KDIonContext::sharedContext()->updatePostProcessingEffects(); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - AppsContainer::sharedAppsContainer()->redrawWindow(true); + AppsContainer::sharedAppsContainer()->redrawWindow(); return true; } diff --git a/apps/settings/sub_menu/datetime_controller.cpp b/apps/settings/sub_menu/datetime_controller.cpp index 2d75b1ee1..f3ca05760 100644 --- a/apps/settings/sub_menu/datetime_controller.cpp +++ b/apps/settings/sub_menu/datetime_controller.cpp @@ -41,7 +41,7 @@ bool DateTimeController::handleEvent(Ion::Events::Event event) { for (int i = 0; i < numberOfRows(); i++) { m_selectableTableView.reloadCellAtLocation(0, i); } - AppsContainer::sharedAppsContainer()->redrawWindow(true); + AppsContainer::sharedAppsContainer()->redrawWindow(); return true; } diff --git a/apps/settings/sub_menu/language_controller.cpp b/apps/settings/sub_menu/language_controller.cpp deleted file mode 100644 index 9edb1857a..000000000 --- a/apps/settings/sub_menu/language_controller.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "language_controller.h" - -namespace Settings { - -bool LanguageController::handleEvent(Ion::Events::Event event) { - if (Shared::LanguageController::handleEvent(event) || event == Ion::Events::Left) { - static_cast(parentResponder())->pop(); - return true; - } - return false; -} - -} diff --git a/apps/settings/sub_menu/language_controller.h b/apps/settings/sub_menu/language_controller.h deleted file mode 100644 index e0e358aca..000000000 --- a/apps/settings/sub_menu/language_controller.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SETTINGS_LANGUAGE_CONTROLLER_H -#define SETTINGS_LANGUAGE_CONTROLLER_H - -#include -#include "../../shared/language_controller.h" - -namespace Settings { - -class LanguageController : public Shared::LanguageController { -public: - using Shared::LanguageController::LanguageController; - bool handleEvent(Ion::Events::Event event) override; - TELEMETRY_ID("Language"); -}; - -} - -#endif diff --git a/apps/settings/sub_menu/localization_controller.cpp b/apps/settings/sub_menu/localization_controller.cpp new file mode 100644 index 000000000..047fee4a7 --- /dev/null +++ b/apps/settings/sub_menu/localization_controller.cpp @@ -0,0 +1,21 @@ +#include "localization_controller.h" +#include + + +namespace Settings { + +int LocalizationController::indexOfCellToSelectOnReset() const { + return mode() == Mode::Language ? + Shared::LocalizationController::indexOfCellToSelectOnReset() : + IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country()); +} + +bool LocalizationController::handleEvent(Ion::Events::Event event) { + if (Shared::LocalizationController::handleEvent(event) || event == Ion::Events::Left) { + static_cast(parentResponder())->pop(); + return true; + } + return false; +} + +} diff --git a/apps/settings/sub_menu/localization_controller.h b/apps/settings/sub_menu/localization_controller.h new file mode 100644 index 000000000..531014cf4 --- /dev/null +++ b/apps/settings/sub_menu/localization_controller.h @@ -0,0 +1,21 @@ +#ifndef SETTING_LOCALIZATION_CONTROLLER_H +#define SETTING_LOCALIZATION_CONTROLLER_H + +#include +#include + +namespace Settings { + +class LocalizationController : public Shared::LocalizationController { +public: + using Shared::LocalizationController::LocalizationController; + + int indexOfCellToSelectOnReset() const override; + bool shouldDisplayTitle() const override { return false; } + + bool handleEvent(Ion::Events::Event event) override; + TELEMETRY_ID("Localization"); +}; +} + +#endif diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index f95ae5eb2..2aba6a869 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -210,7 +210,7 @@ void PreferencesController::setPreferenceWithValueIndex(I18n::Message message, i } else if (message == I18n::Message::SymbolMultiplication) { preferences->setSymbolMultiplication((Preferences::SymbolMultiplication)valueIndex); } else if (message == I18n::Message::SymbolFunction) { - preferences->setSymbolOfFunction((Preferences::SymbolFunction)valueIndex); + preferences->setSymbolOfFunction((Preferences::SymbolFunction)valueIndex); } else if (message == I18n::Message::FontSizes) { GlobalPreferences::sharedGlobalPreferences()->setFont(valueIndex == 0 ? KDFont::LargeFont : KDFont::SmallFont); } diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 09e4007e4..8fab811af 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -1,20 +1,34 @@ ActivateDeactivate = "Aktivieren/Deaktivieren" -ActivateDutchExamMode = "Activate Dutch exam mode" +ActivateDutchExamMode = "Prüfungsmodus starten NL" ActivateExamMode = "Prüfungsmodus starten" ActiveExamModeMessage1 = "Alle Ihre Daten werden " ActiveExamModeMessage2 = "gelöscht, wenn Sie den " ActiveExamModeMessage3 = "Prüfungsmodus einschalten." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." +ActiveDutchExamModeMessage1 = "Alle Ihre Daten werden gelöscht, wenn" +ActiveDutchExamModeMessage2 = "Sie den Prüfungsmodus einschalten. " +ActiveDutchExamModeMessage3 = "Python wird nicht verfügbar sein." Axis = "Achse" Cancel = "Abbrechen" ClearColumn = "Spalte löschen" ColumnOptions = "Optionen der Spalte" +ConfirmDiscard1 = "Alle Änderungen werden verworfen" +ConfirmDiscard2 = "" CopyColumnInList = "Die Spalte in einer Liste kopieren" +Country = "Land" +CountryCA = "Kanada " +CountryDE = "Deutschland " +CountryES = "Spanien " +CountryFR = "Frankreich " +CountryGB = "Vereinigtes Königreich " +CountryIT = "Italien " +CountryNL = "Niederlande " +CountryPT = "Portugal " +CountryUS = "Vereinigte Staaten " +CountryWW = "International " +CountryWarning1 = "Diese Einstellung definiert die verwendeten" +CountryWarning2 = "mathematischen Konventionen." DataNotSuitable = "Daten nicht geeignet" DataTab = "Daten" -DefaultSetting = "Grundeinstellung" Deg = "gra" Deviation = "Varianz" DisplayValues = "Werte anzeigen" @@ -33,7 +47,6 @@ HardwareTestLaunch1 = "Sie sind dabei den Hardwaretest zu" HardwareTestLaunch2 = "starten. Um ihn wieder zu verlassen," HardwareTestLaunch3 = "müssen Sie einen Reset durchfuhren," HardwareTestLaunch4 = "der Ihre Daten löschen wird." -Initialization = "Initialisierung" IntervalSet = "Intervall einstellen" Language = "Sprache" LowBattery = "Batterie erschöpft" @@ -42,6 +55,7 @@ Move = " Verschieben: " NameCannotStartWithNumber = "Ein Name darf nicht mit einer Zahl beginnen" NameTaken = "Dieser Name ist bereits vergeben" NameTooLong = "Der Name ist zu lang" +Navigate = "Navigieren" NEnd = "N Endwert" Next = "Nächste" NoDataToPlot = "Keine Daten zum Zeichnen" @@ -54,10 +68,10 @@ Orthonormal = "Orthonormal" Plot = "Graphen zeichnen" PoolMemoryFull1 = "Der Arbeitsspeicher ist voll." PoolMemoryFull2 = "Versuchen Sie es erneut." -Rad = "rad" Rename = "Umbenennen" -RoundAbscissa = "Ganzzahl" Sci = "wiss" +SortValues = "Nach steigenden Werten sortieren" +SortSizes = "Nach steigenden Frequenzen sortieren" SquareSum = "Quadratsumme" StatTab = "Stats" StandardDeviation = "Standardabweichung" @@ -72,7 +86,6 @@ ThetaEnd = "θ Endwert" ThetaStart = "θ Startwert" TStart = "T Startwert" ToZoom = "Zoom: " -Trigonometric = "Trigonometrisch" UndefinedValue = "Nicht definierter Wert" ValuesTab = "Tabelle" Warning = "Achtung" @@ -81,8 +94,3 @@ XStart = "X Startwert" Zoom = "Zoom" Developers = "Entwickler" BetaTesters = "Beta-Tester" -ExamModeMode = "Modus" -ExamModeModeStandard = "Standard " -ExamModeModeNoSym = "Ohne Symbole " -ExamModeModeNoSymNoText = "Ohne Symbole & Text " -ExamModeModeDutch = "Niederländisch " diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index b0992cddc..4bfc847f8 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -11,10 +11,24 @@ Axis = "Axes" Cancel = "Cancel" ClearColumn = "Clear column" ColumnOptions = "Column options" +ConfirmDiscard1 = "All changes will be discarded" +ConfirmDiscard2 = "" CopyColumnInList = "Export the column to a list" +Country = "Country" +CountryCA = "Canada " +CountryDE = "Germany " +CountryES = "Spain " +CountryFR = "France " +CountryGB = "United Kingdom " +CountryIT = "Italy " +CountryNL = "Netherlands " +CountryPT = "Portugal " +CountryUS = "United States " +CountryWW = "International " +CountryWarning1 = "This setting defines the" +CountryWarning2 = "mathematical conventions used." DataNotSuitable = "Data not suitable" DataTab = "Data" -DefaultSetting = "Basic settings" Deg = "deg" Deviation = "Variance" DisplayValues = "Display values" @@ -33,15 +47,15 @@ HardwareTestLaunch1 = "You are starting the hardware" HardwareTestLaunch2 = " test. At the end of the test, you" HardwareTestLaunch3 = "will have to reset the device and" HardwareTestLaunch4 = "all your data will be deleted." -Initialization = "Preadjustment" IntervalSet = "Set the interval" Language = "Language" LowBattery = "Low battery" Mean = "Mean" -Move = " Move: " +Move = " Pan: " NameCannotStartWithNumber = "A name cannot start with a number" NameTaken = "This name has already been taken" NameTooLong = "This name is too long" +Navigate = "Navigate" Next = "Next" NoDataToPlot = "No data to draw" NoFunctionToDelete = "No function to delete" @@ -50,14 +64,14 @@ NEnd = "N end" NStart = "N start" Ok = "Confirm" Or = " or " -Orthonormal = "Orthonormal" +Orthonormal = "Equal axes" Plot = "Plot graph" PoolMemoryFull1 = "The working memory is full." PoolMemoryFull2 = "Try again." -Rad = "rad" Rename = "Rename" -RoundAbscissa = "Integer" Sci = "sci" +SortValues = "Sort by increasing values" +SortSizes = "Sort by increasing frequencies" SquareSum = "Sum of squares" StandardDeviation = "Standard deviation" StoreExpressionNotAllowed = "'store' is not allowed" @@ -72,7 +86,6 @@ ThetaEnd = "θ end" ThetaStart = "θ start" TStart = "T start" ToZoom = "Zoom: " -Trigonometric = "Trigonometrical" UndefinedValue = "Undefined value" ValuesTab = "Table" Warning = "Warning" @@ -83,6 +96,6 @@ Developers = "Developers" BetaTesters = "Beta testers" ExamModeMode = "Mode" ExamModeModeStandard = "Standard " -ExamModeModeNoSym = "No sym " -ExamModeModeNoSymNoText = "No sym no text " -ExamModeModeDutch = "Dutch " +ExamModeModeNoSym = "Ohne Symbole " +ExamModeModeNoSymNoText = "Ohne Symbole & Text " +ExamModeModeDutch = "Niederländisch " diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 60559f351..e2ec594d1 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -1,20 +1,34 @@ ActivateDeactivate = "Activar/Desactivar" ActivateExamMode = "Activar el modo examen" -ActivateDutchExamMode = "Activate Dutch exam mode" +ActivateDutchExamMode = "Activar el modo examen NL" ActiveExamModeMessage1 = "Todos sus datos se " ActiveExamModeMessage2 = "eliminaran al activar " ActiveExamModeMessage3 = "el modo examen." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." +ActiveDutchExamModeMessage1 = "Todos sus datos se eliminaran al" +ActiveDutchExamModeMessage2 = "activar el modo examen. La aplicación" +ActiveDutchExamModeMessage3 = "Python ya no estará disponible." Axis = "Ejes" Cancel = "Cancelar" ClearColumn = "Borrar la columna" ColumnOptions = "Opciones de la columna" +ConfirmDiscard1 = "Se perderán todos los cambios" +ConfirmDiscard2 = "" CopyColumnInList = "Copiar la columna en una lista" +Country = "País" +CountryCA = "Canadá " +CountryDE = "Alemania " +CountryES = "España " +CountryFR = "Francia " +CountryGB = "Reino Unido " +CountryIT = "Italia " +CountryNL = "Países Bajos " +CountryPT = "Portugal " +CountryUS = "Estados Unidos " +CountryWW = "Internacional " +CountryWarning1 = "Este ajuste define las convenciones" +CountryWarning2 = "matemáticas utilizadas." DataNotSuitable = "Datos no adecuados" DataTab = "Datos" -DefaultSetting = "Ajustes básicos" Deg = "gra" Deviation = "Varianza" DisplayValues = "Visualizar los valores" @@ -33,7 +47,6 @@ HardwareTestLaunch1 = "Esta iniciando la prueba de" HardwareTestLaunch2 = "fabrica. Para quitar la prueba," HardwareTestLaunch3 = "debera resetear su equipo." HardwareTestLaunch4 = "" -Initialization = "Inicialización" IntervalSet = "Ajustar el intervalo" Language = "Idioma" LowBattery = "Batería baja" @@ -42,6 +55,7 @@ Move = " Mover : " NameCannotStartWithNumber = "Un nombre no puede empezar con un número" NameTaken = "Este nombre ya está en uso" NameTooLong = "Este nombre es demasiado largo" +Navigate = "Navegar" NEnd = "N fin" Next = "Siguiente" NoDataToPlot = "Ningunos datos que dibujar" @@ -54,10 +68,10 @@ Orthonormal = "Ortonormal" Plot = "Dibujar el gráfico" PoolMemoryFull1 = "La memoria de trabajo está llena." PoolMemoryFull2 = "Intente de nuevo." -Rad = "rad" Rename = "Renombrar" -RoundAbscissa = "Abscisas enteras" Sci = "cie" +SortValues = "Ordenar por valores crecientes" +SortSizes = "Ordenar por frecuencias crecientes" SquareSum = "Suma cuadrados" StandardDeviation = "Desviación típica" StatTab = "Medidas" @@ -72,7 +86,6 @@ ThetaEnd = "θ fin" ThetaStart = "θ inicio" TStart = "T inicio" ToZoom = "Zoom : " -Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" ValuesTab = "Tabla" Warning = "Cuidado" @@ -81,8 +94,3 @@ XStart = "X inicio" Zoom = "Zoom" Developers = "Desarrolladores" BetaTesters = "Probadores beta" -ExamModeMode = "Modo" -ExamModeModeStandard = "Estándar " -ExamModeModeNoSym = "Sin simbólico " -ExamModeModeNoSymNoText = "sin simbolismo sin texto " -ExamModeModeDutch = "Holandés " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index cb6af48fe..46bd9ad25 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -1,20 +1,34 @@ ActivateDeactivate = "Activer/Désactiver" ActivateExamMode = "Activer le mode examen" -ActivateDutchExamMode = "Activate Dutch exam mode" +ActivateDutchExamMode = "Activer le mode examen NL" ActiveExamModeMessage1 = "Toutes vos données seront " ActiveExamModeMessage2 = "supprimées si vous activez " ActiveExamModeMessage3 = "le mode examen." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." +ActiveDutchExamModeMessage1 = "Toutes vos données seront supprimées " +ActiveDutchExamModeMessage2 = "si vous activez le mode examen." +ActiveDutchExamModeMessage3 = "Python sera inaccessible." Axis = "Axes" Cancel = "Annuler" ClearColumn = "Effacer la colonne" ColumnOptions = "Options de la colonne" +ConfirmDiscard1 = "Toutes les modifications apportées" +ConfirmDiscard2 = "seront perdues" CopyColumnInList = "Copier la colonne dans une liste" +Country = "Pays" +CountryCA = "Canada " +CountryDE = "Allemagne " +CountryES = "Espagne " +CountryFR = "France " +CountryGB = "Royaume-Uni " +CountryIT = "Italie " +CountryNL = "Pays-Bas " +CountryPT = "Portugal " +CountryUS = "Etats-Unis " +CountryWW = "International " +CountryWarning1 = "Ce réglage permet de définir les" +CountryWarning2 = "conventions mathématiques utilisées." DataNotSuitable = "Les données ne conviennent pas" DataTab = "Données" -DefaultSetting = "Réglages de base" Deg = "deg" Deviation = "Variance" DisplayValues = "Afficher les valeurs" @@ -33,7 +47,6 @@ HardwareTestLaunch1 = "Vous allez lancer le test usine." HardwareTestLaunch2 = "Pour en sortir vous devrez" HardwareTestLaunch3 = "appuyer sur le bouton reset" HardwareTestLaunch4 = "ce qui supprimera vos données." -Initialization = "Initialisation" IntervalSet = "Régler l'intervalle" Language = "Langue" LowBattery = "Batterie faible" @@ -42,6 +55,7 @@ Move = " Déplacer : " NameCannotStartWithNumber = "Un nom ne peut pas commencer par un chiffre" NameTaken = "Ce nom est déjà utilisé" NameTooLong = "Ce nom est trop long" +Navigate = "Naviguer" Next = "Suivant" NEnd = "N fin" NoDataToPlot = "Aucune donnée à tracer" @@ -54,10 +68,10 @@ Orthonormal = "Orthonormé" Plot = "Tracer le graphique" PoolMemoryFull1 = "La mémoire de travail est pleine." PoolMemoryFull2 = "Réessayez." -Rad = "rad" Rename = "Renommer" -RoundAbscissa = "Abscisses entières" Sci = "sci" +SortValues = "Trier par valeurs croissantes" +SortSizes = "Trier par effectifs croissants" SquareSum = "Somme des carrés" StandardDeviation = "Écart type" StatTab = "Stats" @@ -72,7 +86,6 @@ ThetaEnd = "θ fin" ThetaStart = "θ début" TStart = "T début" ToZoom = "Zoomer : " -Trigonometric = "Trigonométrique" UndefinedValue = "Valeur non définie" ValuesTab = "Tableau" Warning = "Attention" @@ -81,8 +94,3 @@ XStart = "X début" Zoom = "Zoom" Developers = "Développeurs" BetaTesters = "Beta testeurs" -ExamModeMode = "Mode" -ExamModeModeStandard = "Standard " -ExamModeModeNoSym = "Sans symbolique " -ExamModeModeNoSymNoText = "Sans symbolique ni texte " -ExamModeModeDutch = "Néerlandais " diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 3abdfd8ce..a4cd36c4f 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -1,20 +1,34 @@ ActivateDeactivate = "Attivare/Disattivare" -ActivateExamMode = "Attivare modalità d'esame" -ActivateDutchExamMode = "Activate Dutch exam mode" -ActiveExamModeMessage1 = "Tutti i vostri dati saranno " -ActiveExamModeMessage2 = "cancellati se attivate " +ActivateExamMode = "Attiva modalità d'esame" +ActivateDutchExamMode = "Attiva modalità d'esame NL" +ActiveExamModeMessage1 = "Tutti i tuoi dati saranno " +ActiveExamModeMessage2 = "cancellati se attivi " ActiveExamModeMessage3 = "la modalità d'esame." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." +ActiveDutchExamModeMessage1 = "Tutti i tuoi dati saranno cancellati" +ActiveDutchExamModeMessage2 = "se attivi la modalità d'esame." +ActiveDutchExamModeMessage3 = "L'app Python sarà inaccessibile." Axis = "Assi" Cancel = "Annullare" ClearColumn = "Cancella la colonna" ColumnOptions = "Opzioni colonna" +ConfirmDiscard1 = "Tutte le modifiche verranno perse" +ConfirmDiscard2 = "" CopyColumnInList = "Copia colonna in una lista" +Country = "Paese" +CountryCA = "Canada " +CountryDE = "Germania " +CountryES = "Spagna " +CountryFR = "Francia " +CountryGB = "Regno Unito " +CountryIT = "Italia " +CountryNL = "Paesi Bassi " +CountryPT = "Portogallo " +CountryUS = "Stati Uniti " +CountryWW = "Internazionale " +CountryWarning1 = "Questa opzione permette di definire le" +CountryWarning2 = "convenzioni matematiche utilizzate." DataNotSuitable = "I dati non sono adeguati" DataTab = "Dati" -DefaultSetting = "Impostazioni di base" Deg = "deg" Deviation = "Varianza" DisplayValues = "Mostra valori" @@ -33,7 +47,6 @@ HardwareTestLaunch1 = "Farete il test hardware." HardwareTestLaunch2 = "Per uscire dovrete" HardwareTestLaunch3 = "premere il tasto reset" HardwareTestLaunch4 = "che cancellerà i vostri dati." -Initialization = "Pre-regolazione" IntervalSet = "Imposta l'intervallo" Language = "Lingua" LowBattery = "Batteria bassa" @@ -42,6 +55,7 @@ Move = " Spostare : " NameCannotStartWithNumber = "Un nome non può cominciare con un numero" NameTaken = "Questo nome è già utilizzato" NameTooLong = "Questo nome è troppo lungo" +Navigate = "Navigare" Next = "Successivo" NEnd = "N finale" NoDataToPlot = "Nessun dato da tracciare" @@ -50,14 +64,14 @@ NoValueToCompute = "Nessun valore da calcolare" NStart = "N iniziale" Ok = "Conferma" Or = " o " -Orthonormal = "Ortogonale" +Orthonormal = "Ortonormale" Plot = "Traccia grafico" PoolMemoryFull1 = "La memoria di lavoro è piena." PoolMemoryFull2 = "Riprovare." -Rad = "rad" Rename = "Rinominare" -RoundAbscissa = "Ascisse intere" Sci = "sci" +SortValues = "Ordinare per valori crescenti" +SortSizes = "Ordinare per frequenze crescenti" SquareSum = "Somma dei quadrati" StandardDeviation = "Deviazione standard" StatTab = "Stats" @@ -72,7 +86,6 @@ ThetaEnd = "θ finale" ThetaStart = "θ iniziale" TStart = "T iniziale" ToZoom = "Ingrandire : " -Trigonometric = "Trigonometrica" UndefinedValue = "Valore non definito" ValuesTab = "Tabella" Warning = "Attenzione" @@ -81,8 +94,3 @@ XStart = "X iniziale" Zoom = "Zoom" Developers = "Developers" BetaTesters = "Beta testers" -ExamModeMode = "Mode" -ExamModeModeStandard = "Standard " -ExamModeModeNoSym = "No sym " -ExamModeModeNoSymNoText = "No sym no text " -ExamModeModeDutch = "Dutch " diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 5f13bc6ab..d3d66f6f7 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -6,15 +6,29 @@ ActiveExamModeMessage2 = "gewist wanneer je de " ActiveExamModeMessage3 = "examenstand activeert." ActiveDutchExamModeMessage1 = "Al je gegevens worden gewist wanneer" ActiveDutchExamModeMessage2 = "je de examenstand activeert. De Python" -ActiveDutchExamModeMessage3 = "applicatie wordt uitgeschakelt." +ActiveDutchExamModeMessage3 = "applicatie wordt uitgeschakeld." Axis = "Assen" Cancel = "Annuleer" ClearColumn = "Wis kolom" ColumnOptions = "Kolomopties" +ConfirmDiscard1 = "Alle wijzigingen worden verwijderd" +ConfirmDiscard2 = "" CopyColumnInList = "Exporteer de kolom naar een lijst" +Country = "Land" +CountryCA = "Canada " +CountryDE = "Duitsland " +CountryES = "Spanje " +CountryFR = "Frankrijk " +CountryGB = "Verenigd Koninkrijk " +CountryIT = "Italië " +CountryNL = "Nederland " +CountryPT = "Portugal " +CountryUS = "Verenigde Staten " +CountryWW = "Internationale " +CountryWarning1 = "Deze instelling definieert de" +CountryWarning2 = "gebruikte wiskundige conventies." DataNotSuitable = "Gegevens niet geschikt" DataTab = "Gegevens" -DefaultSetting = "Standaardinstelling" Deg = "deg" Deviation = "Variantie" DisplayValues = "Waarden weergeven" @@ -33,15 +47,15 @@ HardwareTestLaunch1 = "Je start de hardware test. " HardwareTestLaunch2 = "Aan het eind van de test moet " HardwareTestLaunch3 = "je de rekenmachine resetten en " HardwareTestLaunch4 = "worden al je gegevens verwijderd." -Initialization = "Voorgedefinieerd" IntervalSet = "Stel het interval in" Language = "Taal" LowBattery = "Batterij bijna leeg" Mean = "Gemiddelde" Move = " Verplaats: " -NameCannotStartWithNumber = "Een naam kan niet beginnen met een nummer" +NameCannotStartWithNumber = "Een naam kan niet beginnen met een getal" NameTaken = "Deze naam is al in gebruik" NameTooLong = "Deze naam is te lang" +Navigate = "Navigeren" Next = "Volgende" NoDataToPlot = "Geen gegevens om te plotten" NoFunctionToDelete = "Geen functie om te verwijderen" @@ -54,10 +68,10 @@ Orthonormal = "Orthonormaal" Plot = "Grafiek plotten" PoolMemoryFull1 = "Het werkgeheugen is vol." PoolMemoryFull2 = "Opnieuw proberen." -Rad = "rad" Rename = "Hernoem" -RoundAbscissa = "Geheel getal" Sci = "sci" +SortValues = "Sorteer waarden oplopend" +SortSizes = "Sorteer frequenties oplopend" SquareSum = "Som van kwadraten" StandardDeviation = "Standaardafwijking" StoreExpressionNotAllowed = "'opslaan' is niet toegestaan" @@ -65,14 +79,13 @@ StatTab = "Stats" Step = "Stap" StorageMemoryFull1 = "Het geheugen is vol." StorageMemoryFull2 = "Wis de gegevens en probeer opnieuw." -SyntaxError = "Syntax error" +SyntaxError = "Syntaxisfout" Sym = "sym" TEnd = "T einde" ThetaEnd = "θ einde" ThetaStart = "θ begin" TStart = "T begin" ToZoom = "Zoom: " -Trigonometric = "Goniometrisch" UndefinedValue = "Ongedefinieerde waarde" ValuesTab = "Tabel" Warning = "Waarschuwing" @@ -81,8 +94,3 @@ XStart = "X begin" Zoom = "Zoom" Developers = "Developers" BetaTesters = "Beta testers" -ExamModeMode = "Mode" -ExamModeModeStandard = "Standard " -ExamModeModeNoSym = "No sym " -ExamModeModeNoSymNoText = "No sym no text " -ExamModeModeDutch = "Dutch " diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 8d3306802..8424cb2e9 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -1,20 +1,34 @@ ActivateDeactivate = "Ativar/Desativar" ActivateExamMode = "Ativar o modo de exame" -ActivateDutchExamMode = "Activate Dutch exam mode" +ActivateDutchExamMode = "Ativar o modo de exame NL" ActiveExamModeMessage1 = "Todos os seus dados serão " ActiveExamModeMessage2 = "apagados se ativar " ActiveExamModeMessage3 = "o modo de exame." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." +ActiveDutchExamModeMessage1 = "Todos os seus dados serão apagados " +ActiveDutchExamModeMessage2 = "se ativar o modo de exame. A" +ActiveDutchExamModeMessage3 = "aplicação Python estará indisponível." Axis = "Eixos" Cancel = "Cancelar" ClearColumn = "Excluir coluna" ColumnOptions = "Opções de coluna" +ConfirmDiscard1 = "Todas as alterações serão perdidas" +ConfirmDiscard2 = "" CopyColumnInList = "Copiar a coluna para uma lista" +Country = "País" +CountryCA = "Canadá " +CountryDE = "Alemanha " +CountryES = "Espanha " +CountryFR = "França " +CountryGB = "Reino Unido " +CountryIT = "Itália " +CountryNL = "Países Baixos " +CountryPT = "Portugal " +CountryUS = "Estados Unidos " +CountryWW = "Internacional " +CountryWarning1 = "Esta opção define as convenções" +CountryWarning2 = "matemáticas utilizadas." DataNotSuitable = "Dados não adequados" DataTab = "Dados" -DefaultSetting = "Configurações básicas" Deg = "gra" Deviation = "Variância" DisplayValues = "Exibir os valores" @@ -33,7 +47,6 @@ HardwareTestLaunch1 = "Vai executar o teste da planta." HardwareTestLaunch2 = "Para sair tem que executar" HardwareTestLaunch3 = "uma redefinição, que irá apagar" HardwareTestLaunch4 = "os seus dados." -Initialization = "Inicialização" IntervalSet = "Ajustar o intervalo" Language = "Idioma" LowBattery = "Bateria fraca" @@ -42,6 +55,7 @@ Move = " Mover : " NameCannotStartWithNumber = "O nome não pode começar com um número" NameTaken = "Este nome já está a ser usado" NameTooLong = "Este nome é muito longo" +Navigate = "Navegar" NEnd = "N fim" Next = "Seguinte" NoDataToPlot = "Não há dados para desenhar" @@ -54,10 +68,10 @@ Orthonormal = "Ortonormado" Plot = "Traçar o gráfico" PoolMemoryFull1 = "A memória de trabalho está cheia." PoolMemoryFull2 = "Tente novamente." -Rad = "rad" Rename = "Renomear" -RoundAbscissa = "Inteiro" Sci = "cie" +SortValues = "Ordenar por ordem crescente" +SortSizes = "Ordenar por ordem crescente" SquareSum = "Soma dos quadrados" StandardDeviation = "Desvio padrão" StatTab = "Estat" @@ -72,7 +86,6 @@ ThetaEnd = "θ fim" ThetaStart = "θ início" TStart = "T início" ToZoom = "Zoom : " -Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" ValuesTab = "Tabela" Warning = "Atenção" @@ -81,8 +94,3 @@ XStart = "X início" Zoom = "Zoom" Developers = "Desenvolvedores" BetaTesters = "Testadores beta" -ExamModeMode = "Modo" -ExamModeModeStandard = "Padrão " -ExamModeModeNoSym = "Sem simbólico " -ExamModeModeNoSymNoText = "Sem simbólico sem texto " -ExamModeModeDutch = "Holandês " diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 7775218d0..5bd085a0b 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -14,15 +14,13 @@ UnitDistanceMeterMilliSymbol = "_mm" UnitDistanceMeterMicroSymbol = "_μm" UnitDistanceMeterNanoSymbol = "_nm" UnitDistanceMeterPicoSymbol = "_pm" -UnitDistanceInchSymbol = "_in" -UnitDistanceFootSymbol = "_ft" -UnitDistanceYardSymbol = "_yd" UnitDistanceMileSymbol = "_mi" +UnitDistanceYardSymbol = "_yd" +UnitDistanceFootSymbol = "_ft" +UnitDistanceInchSymbol = "_in" UnitDistanceAstronomicalUnitSymbol = "_au" UnitDistanceLightYearSymbol = "_ly" UnitDistanceParsecSymbol = "_pc" -UnitMassPoundSymbol = "_lb" -UnitMassOunceSymbol = "_oz" UnitMassTonSymbol = "_ton" UnitMassGramKiloSymbol = "_kg" UnitMassGramSymbol = "_g" @@ -30,10 +28,16 @@ UnitMassGramMilliSymbol = "_mg" UnitMassGramMicroSymbol = "_μg" UnitMassGramNanoSymbol = "_ng" UnitMassTonneSymbol = "_t" +UnitMassOunceSymbol = "_oz" +UnitMassPoundSymbol = "_lb" +UnitMassShortTonSymbol = "_shtn" +UnitMassLongTonSymbol = "_lgtn" UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" UnitTemperatureKelvinSymbol = "_K" +UnitTemperatureCelsiusSymbol = "_°C" +UnitTemperatureFahrenheitSymbol = "_°F" UnitAmountMoleSymbol = "_mol" UnitAmountMoleMilliSymbol = "_mmol" UnitAmountMoleMicroSymbol = "_μmol" @@ -79,11 +83,19 @@ UnitConductanceSiemensSymbol = "_S" UnitConductanceSiemensMilliSymbol = "_mS" UnitMagneticFieldTeslaSymbol = "_T" UnitInductanceHenrySymbol = "_H" +UnitSurfaceAcreSymbol = "_acre" UnitSurfaceHectarSymbol = "_ha" UnitVolumeLiterSymbol = "_L" UnitVolumeLiterDeciSymbol = "_dL" UnitVolumeLiterCentiSymbol = "_cL" UnitVolumeLiterMilliSymbol = "_mL" +UnitVolumeTeaspoonSymbol = "_tsp" +UnitVolumeTablespoonSymbol = "_tbsp" +UnitVolumeFluidOunceSymbol = "_floz" +UnitVolumeCupSymbol = "_cup" +UnitVolumePintSymbol = "_pt" +UnitVolumeQuartSymbol = "_qt" +UnitVolumeGallonSymbol = "_gal" A = "a" AbsCommandWithArg = "abs(x)" AcoshCommandWithArg = "acosh(x)" @@ -104,12 +116,15 @@ CodeApp = "Python" ConfidenceCommandWithArg = "confidence(f,n)" ConjCommandWithArg = "conj(z)" CoshCommandWithArg = "cosh(x)" +CrossCommandWithArg = "cross(u,v)" D = "d" +DefaultSetting = "Auto" DeterminantCommandWithArg = "det(M)" DiffCommandWithArg = "diff(f(x),x,a)" DiffCommand = "diff(\x11,x,\x11)" DimensionCommandWithArg = "dim(M)" DiscriminantFormulaDegree2 = "Δ=b^2-4ac" +DotCommandWithArg = "dot(u,v)" E = "e" Equal = "=" FactorCommandWithArg = "factor(n)" @@ -124,7 +139,7 @@ IntCommand = "int(\x11,x,\x11,\x11)" IntCommandWithArg = "int(f(x),x,a,b)" InvBinomialCommandWithArg = "invbinom(a,n,p)" InverseCommandWithArg = "inverse(M)" -InvNormCommandWithArg = "invnorm(a,μ,σ2)" +InvNormCommandWithArg = "invnorm(a,μ,σ)" InvSortCommandWithArg = "sort>(L)" K = "k" Lambda = "λ" @@ -137,9 +152,10 @@ MaxCommandWithArg = "max(L)" MinCommandWithArg = "min(L)" Mu = "μ" N = "n" -NormCDFCommandWithArg = "normcdf(a,μ,σ2)" -NormCDF2CommandWithArg = "normcdf2(a,b,μ,σ2)" -NormPDFCommandWithArg = "normpdf(x,μ,σ2)" +NormCDFCommandWithArg = "normcdf(a,μ,σ)" +NormCDF2CommandWithArg = "normcdf2(a,b,μ,σ)" +NormPDFCommandWithArg = "normpdf(x,μ,σ)" +NormVectorCommandWithArg = "norm(u)" PermuteCommandWithArg = "permute(n,r)" P = "p" Prediction95CommandWithArg = "prediction95(p,n)" @@ -150,10 +166,13 @@ QuoCommandWithArg = "quo(p,q)" RandintCommandWithArg = "randint(a,b)" RandomCommandWithArg = "random()" ReCommandWithArg = "re(z)" +ReducedRowEchelonFormCommandWithArg = "rref(M)" RemCommandWithArg = "rem(p,q)" RootCommandWithArg = "root(x,n)" RoundCommandWithArg = "round(x,n)" +RowEchelonFormCommandWithArg = "ref(M)" R = "r" +Rad = "rad" Shift = "shift" Sigma = "σ" SinhCommandWithArg = "sinh(x)" @@ -164,6 +183,8 @@ Sxy = "∑xy" T = "t" TanhCommandWithArg = "tanh(x)" Theta = "θ" +ThetaMax = "θmax" +ThetaMin = "θmin" TMax = "Tmax" TMin = "Tmin" TraceCommandWithArg = "trace(M)" @@ -171,12 +192,9 @@ TransposeCommandWithArg = "transpose(M)" XMax = "Xmax" XMin = "Xmin" X = "x" -YAuto = "Y auto" YMax = "Ymax" YMin = "Ymin" Y = "y" -ThetaMax = "θmax" -ThetaMin = "θmin" ElementHMass = "1.00794" ElementHeMass = "4.002602" ElementLiMass = "6.941" @@ -433,4 +451,4 @@ RydbergConstant = "10973731.568160_m^-1" HartreeConstant = "4.3597447222071·10^-18_J" MagneticFluxQuantum = "2.067833848·10^-15_Wb" ConductanceQuantum = "7.748091729·10^-5_S" -CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1" \ No newline at end of file +CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 4ffcd50df..bd421f508 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -1,5 +1,7 @@ app_shared_test_src = $(addprefix apps/shared/,\ continuous_function.cpp\ + continuous_function_cache.cpp \ + curve_view_cursor.cpp \ curve_view_range.cpp \ curve_view.cpp \ dots.cpp \ @@ -9,11 +11,14 @@ app_shared_test_src = $(addprefix apps/shared/,\ expression_model_store.cpp \ function.cpp \ global_context.cpp \ - interactive_curve_view_range_delegate.cpp \ interactive_curve_view_range.cpp \ + interactive_curve_view_range_delegate.cpp \ labeled_curve_view.cpp \ memoized_curve_view_range.cpp \ range_1D.cpp \ + sequence.cpp\ + sequence_context.cpp\ + sequence_store.cpp\ toolbox_helpers.cpp \ zoom_and_pan_curve_view_controller.cpp \ zoom_curve_view_controller.cpp \ @@ -25,7 +30,6 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ cursor_view.cpp \ - curve_view_cursor.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ expression_model_list_controller.cpp \ @@ -40,29 +44,36 @@ app_shared_src = $(addprefix apps/shared/,\ function_list_controller.cpp \ function_store.cpp \ function_title_cell.cpp \ + function_zoom_and_pan_curve_view_controller.cpp \ go_to_parameter_controller.cpp \ hideable_even_odd_buffer_text_cell.cpp \ hideable_even_odd_cell.cpp \ hideable_even_odd_editable_text_cell.cpp \ - initialisation_parameter_controller.cpp \ input_event_handler_delegate_app.cpp \ interactive_curve_view_controller.cpp \ interval.cpp \ interval_parameter_controller.cpp \ - language_controller.cpp \ layout_field_delegate.cpp \ list_parameter_controller.cpp \ + localization_controller.cpp \ message_view.cpp \ ok_view.cpp \ parameter_text_field_delegate.cpp \ + discard_pop_up_controller.cpp \ post_and_hardware_tests.cpp \ range_parameter_controller.cpp \ regular_table_view_data_source.cpp \ round_cursor_view.cpp \ scrollable_multiple_expressions_view.cpp \ scrollable_two_expressions_cell.cpp \ + sequence.cpp\ + sequence_cache_context.cpp \ + sequence_context.cpp\ + sequence_store.cpp\ + sequence_title_cell.cpp \ separable.cpp \ separator_even_odd_buffer_text_cell.cpp \ + shared_app.cpp \ simple_interactive_curve_view_controller.cpp \ store_cell.cpp \ store_controller.cpp \ @@ -80,8 +91,24 @@ app_shared_src = $(addprefix apps/shared/,\ values_parameter_controller.cpp \ vertical_cursor_view.cpp \ xy_banner_view.cpp\ - zoom_parameter_controller.cpp \ ) app_shared_src += $(app_shared_test_src) apps_src += $(app_shared_src) + +# The .cpp files could also be added to app_shared_test_src in their respective makefiles +# -> it would then be impossible to run the shared test alone +app_shared_test_src += $(addprefix apps/graph/,\ + continuous_function_store.cpp\ +) + +app_shared_test_src += $(addprefix apps/shared/,\ + sequence.cpp \ + sequence_cache_context.cpp \ + sequence_context.cpp \ + sequence_store.cpp \ +) + +tests_src += $(addprefix apps/shared/test/,\ + function_alignement.cpp\ +) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index ebac4e160..e127feed5 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -125,6 +125,8 @@ void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences recordData()->setPlotType(newPlotType); + setCache(nullptr); + // Recompute the layouts m_model.tidy(); @@ -251,10 +253,74 @@ float ContinuousFunction::tMax() const { void ContinuousFunction::setTMin(float tMin) { recordData()->setTMin(tMin); + setCache(nullptr); } void ContinuousFunction::setTMax(float tMax) { recordData()->setTMax(tMax); + setCache(nullptr); +} + +void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, float targetRatio, Poincare::Context * context) const { + if (plotType() != PlotType::Cartesian) { + assert(std::isfinite(tMin()) && std::isfinite(tMax()) && std::isfinite(rangeStep()) && rangeStep() > 0); + protectedFullRangeForDisplay(tMin(), tMax(), rangeStep(), xMin, xMax, context, true); + protectedFullRangeForDisplay(tMin(), tMax(), rangeStep(), yMin, yMax, context, false); + return; + } + + if (!basedOnCostlyAlgorithms(context)) { + Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { + /* When evaluating sin(x)/x close to zero using the standard sine function, + * one can detect small variations, while the cardinal sine is supposed to be + * locally monotonous. To smooth our such variations, we round the result of + * the evaluations. As we are not interested in precise results but only in + * ordering, this approximation is sufficient. */ + constexpr float precision = 1e-5; + return precision * std::round(static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision); + }; + bool fullyComputed = Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this); + + evaluation = [](float x, Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; + + if (fullyComputed) { + /* The function has points of interest. */ + Zoom::RefinedYRangeForDisplay(evaluation, xMin, xMax, yMin, yMax, context, this); + return; + } + + /* Try to display an orthonormal range. */ + Zoom::RangeWithRatioForDisplay(evaluation, targetRatio, xMin, xMax, yMin, yMax, context, this); + if (std::isfinite(*xMin) && std::isfinite(*xMax) && std::isfinite(*yMin) && std::isfinite(*yMax)) { + return; + } + + /* The function's profile is not great for an orthonormal range. + * Try a basic range. */ + *xMin = - Zoom::k_defaultHalfRange; + *xMax = Zoom::k_defaultHalfRange; + Zoom::RefinedYRangeForDisplay(evaluation, xMin, xMax, yMin, yMax, context, this); + if (std::isfinite(*xMin) && std::isfinite(*xMax) && std::isfinite(*yMin) && std::isfinite(*yMax)) { + return; + } + + /* The function's order of magnitude cannot be computed. Try to just display + * the full function. */ + float step = (*xMax - *xMin) / k_polarParamRangeSearchNumberOfPoints; + Zoom::FullRange(evaluation, *xMin, *xMax, step, yMin, yMax, context, this); + if (std::isfinite(*xMin) && std::isfinite(*xMax) && std::isfinite(*yMin) && std::isfinite(*yMax)) { + return; + } + } + + /* The function makes use of some costly algorithms and cannot be computed in + * a timely manner, or it is probably undefined. */ + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { @@ -347,7 +413,23 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e * the derivative table. */ } +Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { + setCache(nullptr); + return ExpressionModelHandle::setContent(c, context); +} + +bool ContinuousFunction::basedOnCostlyAlgorithms(Context * context) const { + return expressionReduced(context).hasExpression([](const Expression e, const void * context) { + return e.type() == ExpressionNode::Type::Sequence + || e.type() == ExpressionNode::Type::Integral + || e.type() == ExpressionNode::Type::Derivative; + }, nullptr); +} + template Coordinate2D ContinuousFunction::templatedApproximateAtParameter(float, Poincare::Context *) const; template Coordinate2D ContinuousFunction::templatedApproximateAtParameter(double, Poincare::Context *) const; +template Poincare::Coordinate2D ContinuousFunction::privateEvaluateXYAtParameter(float, Poincare::Context *) const; +template Poincare::Coordinate2D ContinuousFunction::privateEvaluateXYAtParameter(double, Poincare::Context *) const; + } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 612651231..e5f5a57c8 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -10,6 +10,7 @@ */ #include "global_context.h" +#include "continuous_function_cache.h" #include "function.h" #include "range_1D.h" #include @@ -18,11 +19,15 @@ namespace Shared { class ContinuousFunction : public Function { + /* We want the cache to be able to call privateEvaluateXYAtParameter to + * bypass cache lookup when memoizing the function's values. */ + friend class ContinuousFunctionCache; public: static void DefaultName(char buffer[], size_t bufferSize); static ContinuousFunction NewModel(Ion::Storage::Record::ErrorStatus * error, const char * baseName = nullptr); ContinuousFunction(Ion::Storage::Record record = Record()) : - Function(record) + Function(record), + m_cache(nullptr) {} I18n::Message parameterMessageName() const override; CodePoint symbol() const override; @@ -43,7 +48,7 @@ public: return templatedApproximateAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(float t, Poincare::Context * context) const override { - return privateEvaluateXYAtParameter(t, context); + return (m_cache) ? m_cache->valueForParameter(this, context, t) : privateEvaluateXYAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(double t, Poincare::Context * context) const override { return privateEvaluateXYAtParameter(t, context); @@ -65,6 +70,8 @@ public: void setTMax(float tMax); float rangeStep() const override { return plotType() == PlotType::Cartesian ? NAN : (tMax() - tMin())/k_polarParamRangeSearchNumberOfPoints; } + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, float targetRatio, Poincare::Context * context) const override; + // Extremum Poincare::Coordinate2D nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const; Poincare::Coordinate2D nextMaximumFrom(double start, double step, double max, Poincare::Context * context) const; @@ -73,11 +80,21 @@ public: Poincare::Coordinate2D nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, Poincare::Expression e, double eDomainMin = -INFINITY, double eDomainMax = INFINITY) const; // Integral Poincare::Expression sumBetweenBounds(double start, double end, Poincare::Context * context) const override; + + // Cache + ContinuousFunctionCache * cache() const { return m_cache; } + void setCache(ContinuousFunctionCache * v) { m_cache = v; } + Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) override; private: constexpr static float k_polarParamRangeSearchNumberOfPoints = 100.0f; // This is ad hoc, no special justification typedef Poincare::Coordinate2D (*ComputePointOfInterest)(Poincare::Expression e, char * symbol, double start, double step, double max, Poincare::Context * context); Poincare::Coordinate2D nextPointOfInterestFrom(double start, double step, double max, Poincare::Context * context, ComputePointOfInterest compute) const; template Poincare::Coordinate2D privateEvaluateXYAtParameter(T t, Poincare::Context * context) const; + void didBecomeInactive() override { m_cache = nullptr; } + + void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; + bool basedOnCostlyAlgorithms(Poincare::Context * context) const; + /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on * Shared::Function::RecordDataBuffer about packing. */ @@ -115,6 +132,7 @@ private: RecordDataBuffer * recordData() const; template Poincare::Coordinate2D templatedApproximateAtParameter(T t, Poincare::Context * context) const; Model m_model; + ContinuousFunctionCache * m_cache; }; } diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp new file mode 100644 index 000000000..00a8cd687 --- /dev/null +++ b/apps/shared/continuous_function_cache.cpp @@ -0,0 +1,163 @@ +#include "continuous_function_cache.h" +#include "continuous_function.h" +#include + +namespace Shared { + +constexpr int ContinuousFunctionCache::k_sizeOfCache; +constexpr float ContinuousFunctionCache::k_cacheHitTolerance; +constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; + +// public +void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { + ContinuousFunction * function = static_cast(fun); + + if (!cache) { + /* ContinuousFunctionStore::cacheAtIndex has returned a nullptr : the index + * of the function we are trying to draw is greater than the number of + * available caches, so we just tell the function to not lookup any cache. */ + function->setCache(nullptr); + return; + } + + if (tStep < 3 * k_cacheHitTolerance) { + /* If tStep is lower than twice the tolerance, we risk shifting the index + * by 1 for cache hits. As an added safety, we add another buffer of + * k_cacheHitTolerance, raising the threshold for caching to three times + * the tolerance. */ + function->setCache(nullptr); + return; + } + if (function->cache() != cache) { + cache->clear(); + function->setCache(cache); + } else if (tStep != 0.f && tStep != cache->step()) { + cache->clear(); + } + + if (function->plotType() == ContinuousFunction::PlotType::Cartesian && tStep != 0) { + function->cache()->pan(function, tMin); + } + function->cache()->setRange(function, tMin, tStep); +} + +void ContinuousFunctionCache::clear() { + m_startOfCache = 0; + m_tStep = 0; + invalidateBetween(0, k_sizeOfCache); +} + +Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t) { + int resIndex = indexForParameter(function, t); + if (resIndex < 0) { + return function->privateEvaluateXYAtParameter(t, context); + } + return valuesAtIndex(function, context, t, resIndex); +} + +void ContinuousFunctionCache::ComputeNonCartesianSteps(float * tStep, float * tCacheStep, float tMax, float tMin) { + // Expected step length + *tStep = (tMax - tMin) / Graph::GraphView::k_graphStepDenominator; + /* Parametric and polar functions require caching both x and y values, + * with the same k_sizeOfCache. To cover the entire range, + * number of cacheable points is half the cache size. */ + const int numberOfCacheablePoints = k_sizeOfCache / 2; + const int numberOfWholeSteps = static_cast(Graph::GraphView::k_graphStepDenominator); + static_assert(numberOfCacheablePoints % numberOfWholeSteps == 0, "numberOfCacheablePoints should be a multiple of numberOfWholeSteps for optimal caching"); + const int multiple = numberOfCacheablePoints / numberOfWholeSteps; + static_assert(multiple && !(multiple & (multiple - 1)), "multiple should be a power of 2 for optimal caching"); + /* Define cacheStep such that every whole graph steps are equally divided + * For instance, with : + * graphStepDenominator = 10.1 + * numberOfCacheablePoints = 160 + * tMin [----------------|----------------| ... |----------------|**] tMax + * step1 step2 step10 step11 + * There are 11 steps, the first 10 are whole and have an equal size (tStep). + * There are 16 cache points in the first 10 steps, 160 total cache points. */ + *tCacheStep = *tStep / multiple; +} + +// private +void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { + for (int i = iInf; i < iSup; i++) { + m_cache[i] = NAN; + } +} + +void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { + m_tMin = tMin; + m_tStep = tStep; +} + +int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * function, float t) const { + float delta = (t - m_tMin) / m_tStep; + if (delta < 0 || delta > INT_MAX) { + return -1; + } + int res = std::round(delta); + assert(res >= 0); + if ((res >= k_sizeOfCache && function->plotType() == ContinuousFunction::PlotType::Cartesian) + || (res >= k_sizeOfCache / 2 && function->plotType() != ContinuousFunction::PlotType::Cartesian) + || std::fabs(res - delta) > k_cacheHitTolerance) { + return -1; + } + assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0); + return (res + m_startOfCache) % k_sizeOfCache; +} + +Poincare::Coordinate2D ContinuousFunctionCache::valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i) { + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + if (std::isnan(m_cache[i])) { + m_cache[i] = function->privateEvaluateXYAtParameter(t, context).x2(); + } + return Poincare::Coordinate2D(t, m_cache[i]); + } + if (std::isnan(m_cache[2 * i]) || std::isnan(m_cache[2 * i + 1])) { + Poincare::Coordinate2D res = function->privateEvaluateXYAtParameter(t, context); + m_cache[2 * i] = res.x1(); + m_cache[2 * i + 1] = res.x2(); + } + return Poincare::Coordinate2D(m_cache[2 * i], m_cache[2 * i + 1]); +} + +void ContinuousFunctionCache::pan(ContinuousFunction * function, float newTMin) { + assert(function->plotType() == ContinuousFunction::PlotType::Cartesian); + if (newTMin == m_tMin) { + return; + } + + float dT = (newTMin - m_tMin) / m_tStep; + m_tMin = newTMin; + if (std::fabs(dT) > INT_MAX) { + clear(); + return; + } + int dI = std::round(dT); + if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::fabs(dT - dI) > k_cacheHitTolerance) { + clear(); + return; + } + + int oldStart = m_startOfCache; + m_startOfCache = (m_startOfCache + dI) % k_sizeOfCache; + if (m_startOfCache < 0) { + m_startOfCache += k_sizeOfCache; + } + if (dI > 0) { + if (m_startOfCache > oldStart) { + invalidateBetween(oldStart, m_startOfCache); + } else { + invalidateBetween(oldStart, k_sizeOfCache); + invalidateBetween(0, m_startOfCache); + } + } else { + if (m_startOfCache > oldStart) { + invalidateBetween(m_startOfCache, k_sizeOfCache); + invalidateBetween(0, oldStart); + } else { + invalidateBetween(m_startOfCache, oldStart); + } + } +} + +} diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h new file mode 100644 index 000000000..4ccf8b015 --- /dev/null +++ b/apps/shared/continuous_function_cache.h @@ -0,0 +1,56 @@ +#ifndef SHARED_CONTINUOUS_FUNCTION_CACHE_H +#define SHARED_CONTINUOUS_FUNCTION_CACHE_H + +#include "../graph/graph/graph_view.h" +#include +#include +#include + +namespace Shared { + +class ContinuousFunction; + +class ContinuousFunctionCache { +public: + static constexpr int k_numberOfAvailableCaches = 2; + + static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); + + ContinuousFunctionCache() { clear(); } + + float step() const { return m_tStep; } + void clear(); + Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t); + // Sets step parameters for non-cartesian curves + static void ComputeNonCartesianSteps(float * tStep, float * tCacheStep, float tMax, float tMin); +private: + /* The size of the cache is chosen to optimize the display of cartesian + * functions */ + static constexpr int k_sizeOfCache = Ion::Display::Width; + /* We need a certain amount of tolerance since we try to evaluate the + * equality of floats. But the value has to be chosen carefully. Too high of + * a tolerance causes false positives, which lead to errors in curves + * (ex : 1/x with a vertical line at 0). Too low of a tolerance causes false + * negatives, which slows down the drawing. + * + * The value 128*FLT_EPSILON has been found to be the lowest for which all + * indices verify indexForParameter(tMin + index * tStep) = index. */ + static constexpr float k_cacheHitTolerance = 128.0f * FLT_EPSILON; + + void invalidateBetween(int iInf, int iSup); + void setRange(ContinuousFunction * function, float tMin, float tStep); + int indexForParameter(const ContinuousFunction * function, float t) const; + Poincare::Coordinate2D valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i); + void pan(ContinuousFunction * function, float newTMin); + + float m_tMin, m_tStep; + float m_cache[k_sizeOfCache]; + /* m_startOfCache is used to implement a circular buffer for easy panning + * with cartesian functions. When dealing with parametric or polar functions, + * m_startOfCache should be zero.*/ + int m_startOfCache; +}; + +} + +#endif diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 4eeb6e2af..25c22b796 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include using namespace Poincare; @@ -111,32 +113,40 @@ float CurveView::pixelHeight() const { return (m_curveViewRange->yMax() - m_curveViewRange->yMin()) / (m_frame.height() - 1); } +float CurveView::pixelLength(Axis axis) const { + return axis == Axis::Horizontal ? pixelWidth() : pixelHeight(); +} + float CurveView::pixelToFloat(Axis axis, KDCoordinate p) const { return (axis == Axis::Horizontal) ? m_curveViewRange->xMin() + p * pixelWidth() : m_curveViewRange->yMax() - p * pixelHeight(); } +static float clippedFloat(float f) { + /* Make sure that the returned value is between the maximum and minimum + * possible values of KDCoordinate. */ + if (f == NAN) { + return NAN; + } else if (f < KDCOORDINATE_MIN) { + return KDCOORDINATE_MIN; + } else if (f > KDCOORDINATE_MAX) { + return KDCOORDINATE_MAX; + } else { + return f; + } +} + float CurveView::floatToPixel(Axis axis, float f) const { float result = (axis == Axis::Horizontal) ? (f - m_curveViewRange->xMin()) / pixelWidth() : (m_curveViewRange->yMax() - f) / pixelHeight(); - /* Make sure that the returned value is between the maximum and minimum - * possible values of KDCoordinate. */ - if (result == NAN) { - return NAN; - } else if (result < KDCOORDINATE_MIN) { - return KDCOORDINATE_MIN; - } else if (result > KDCOORDINATE_MAX) { - return KDCOORDINATE_MAX; - } else { - return result; - } + return clippedFloat(result); } float CurveView::floatLengthToPixelLength(Axis axis, float f) const { - float dist = floatToPixel(axis, f) - floatToPixel(axis, 0.0f); - return axis == Axis::Vertical ? - dist : dist; + float dist = f / pixelLength(axis); + return clippedFloat(dist); } float CurveView::floatLengthToPixelLength(float dx, float dy) const { @@ -146,8 +156,7 @@ float CurveView::floatLengthToPixelLength(float dx, float dy) const { } float CurveView::pixelLengthToFloatLength(Axis axis, float f) const { - f = axis == Axis::Vertical ? -f : f; - return pixelToFloat(axis, floatToPixel(axis, 0.0f) + f); + return f*pixelLength(axis); } void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor boldColor, KDColor lightColor) const { @@ -179,6 +188,9 @@ float CurveView::gridUnit(Axis axis) const { int CurveView::numberOfLabels(Axis axis) const { float labelStep = 2.0f * gridUnit(axis); + if (labelStep <= 0.0f) { + return 0; + } float minLabel = std::ceil(min(axis)/labelStep); float maxLabel = std::floor(max(axis)/labelStep); int numberOfLabels = maxLabel - minLabel + 1; @@ -218,19 +230,24 @@ void CurveView::computeLabels(Axis axis) { if (axis == Axis::Horizontal) { if (labelBuffer[0] == 0) { - /* Some labels are too big and may overlap their neighbours. We write the + /* Some labels are too big and may overlap their neighbors. We write the * extrema labels only. */ computeHorizontalExtremaLabels(); - return; + break; } if (i > 0 && strcmp(labelBuffer, label(axis, i-1)) == 0) { /* We need to increase the number if significant digits, otherwise some * labels are rounded to the same value. */ computeHorizontalExtremaLabels(true); - return; + break; } } } + int maxNumberOfLabels = (axis == Axis::Horizontal ? k_maxNumberOfXLabels : k_maxNumberOfYLabels); + // All remaining labels are empty. They shouldn't be accessed anyway. + for (int i = axisLabelsCount; i < maxNumberOfLabels; i++) { + label(axis, i)[0] = 0; + } } void CurveView::simpleDrawBothAxesLabels(KDContext * ctx, KDRect rect) const { @@ -360,6 +377,9 @@ void CurveView::drawLabelsAndGraduations(KDContext * ctx, KDRect rect, Axis axis return; } + // Labels will be pulled. They must be up to date with current curve view. + assert(m_drawnRangeVersion == m_curveViewRange->rangeChecksum()); + // Draw the labels for (int i = minDrawnLabel; i < maxDrawnLabel; i++) { KDCoordinate labelPosition = std::round(floatToPixel(axis, labelValueAtIndex(axis, i))); @@ -450,14 +470,14 @@ void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor KDCoordinate diameter = 0; const uint8_t * mask = nullptr; switch (size) { + case Size::Tiny: + diameter = Dots::TinyDotDiameter; + mask = (const uint8_t *)Dots::TinyDotMask; + break; case Size::Small: diameter = Dots::SmallDotDiameter; mask = (const uint8_t *)Dots::SmallDotMask; break; - case Size::Medium: - diameter = Dots::MediumDotDiameter; - mask = (const uint8_t *)Dots::MediumDotMask; - break; default: assert(size == Size::Large); diameter = Dots::LargeDotDiameter; @@ -611,6 +631,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd float previousY = NAN; float y = NAN; int i = 0; + bool isLastSegment = false; do { previousT = t; t = tStart + (i++) * tStep; @@ -619,9 +640,11 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd } if (t >= tEnd) { t = tEnd - FLT_EPSILON; + isLastSegment = true; } if (previousT == t) { - break; + // No need to draw segment. Happens when tStep << tStart . + continue; } previousX = x; previousY = y; @@ -632,7 +655,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, x, std::min(0.0f, y), std::max(0.0f, y), color, 1); } joinDots(ctx, rect, xyFloatEvaluation, model, context, drawStraightLinesEarly, previousT, previousX, previousY, t, x, y, color, thick, k_maxNumberOfIterations, xyDoubleEvaluation); - } while (true); + } while (!isLastSegment); } void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const { @@ -648,6 +671,123 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } +float PolarThetaFromCoordinates(float x, float y, Preferences::AngleUnit angleUnit) { + // Return θ, between -π and π in given angleUnit for a (x,y) position. + return Trigonometry::ConvertRadianToAngleUnit(std::arg(std::complex(x,y)), angleUnit).real(); +} + +void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const { + // Compute rect limits + float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); + float rectUp = pixelToFloat(Axis::Vertical, rect.top() + k_externRectMargin); + float rectDown = pixelToFloat(Axis::Vertical, rect.bottom() - k_externRectMargin); + + const Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + const float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); + /* Cancel optimization if : + * - One of rect limits is nan. + * - Step is too large, see cache optimization comments + * ("To optimize cache..."). */ + bool cancelOptimization = std::isnan(rectLeft + rectRight + rectUp + rectDown) || tStep >= piInAngleUnit; + + bool rectOverlapsNegativeAbscissaAxis = false; + if (cancelOptimization || (rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f)) { + if (cancelOptimization || rectRight > 0.0f) { + // Origin is inside rect, tStart and tEnd cannot be optimized + return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + // Rect view overlaps the abscissa, on the left of the origin. + rectOverlapsNegativeAbscissaAxis = true; + } + + float tMin, tMax; + /* Compute angular coordinate of each corners of rect. + * t3 --- t2 + * | | + * t4 --- t1 */ + float t1 = PolarThetaFromCoordinates(rectRight, rectDown, angleUnit); + float t2 = PolarThetaFromCoordinates(rectRight, rectUp, angleUnit); + if (!rectOverlapsNegativeAbscissaAxis) { + float t3 = PolarThetaFromCoordinates(rectLeft, rectUp, angleUnit); + float t4 = PolarThetaFromCoordinates(rectLeft, rectDown, angleUnit); + /* The area between tMin and tMax (modulo π) is the only area where + * something needs to be plotted. */ + tMin = std::min(std::min(t1,t2),std::min(t3,t4)); + tMax = std::max(std::max(t1,t2),std::max(t3,t4)); + } else { + /* PolarThetaFromCoordinates yields coordinates between -π and π. When rect + * is overlapping the negative abscissa (at this point, the origin cannot be + * inside rect), t1 and t4 have a negative angle whereas t2 and t3 have a + * positive angle. We ensure here that tMin is t2 (modulo 2π), tMax is t1, + * and that tMax-tMin is minimal and positive. */ + tMin = t2 - 2 * piInAngleUnit; + tMax = t1; + } + + // Add a thousandth of π as a margin to avoid visible approximation errors. + tMax += piInAngleUnit / 1000.0f; + tMin -= piInAngleUnit / 1000.0f; + + /* To optimize cache hits, the area actually drawn will be extended to nearest + * cached θ. tStep being a multiple of cache steps (see + * ComputeNonCartesianSteps), we extend segments on both ends to the closest + * θ = tStart + tStep * i + * If the drawn segment is extended too much, it might overlap with the next + * extended segment. + * For example, with * the segments that must be drawn and piInAngleUnit=7 : + * tStart tEnd + * kπ | (k+1)π (k+2)π (k+3)π (k+4)π (k+5)π (k+6)π |(k+7)π + * |-------|-------|-------|-------|-------|-------|-------|-- + * tMax-tMin=3 : |---***-|---***-|---***-|---***-|---***-|---***-|---***-|-- + * A - tStep=3 : |---***-|---***-|---***-|---***-|---***-|---***-|---***-|-- + * |___^^^_|__ | ^^^^^^|___ _|__^^^^^|^ |___^^^_|__ + * | | ^^^^^|^ | ^^^ | | ^^^^^^| | + * + * B - tStep=6 : |---***-|---***-|---***-|---***-|---***-|---***-|---***-|-- + * |___^^^^|^^ | ^^^^^^| ^|^^^^^^^|^^^^ | ^^^^|^^ + * | | ^^^^^|^ |^^^^^^ | ^^|^^^^^^^|^^^ | + * In situation A, Step are small enough, not all segments must be drawn. + * In situation B, The entire range should be drawn, and two extended segments + * overlap (at tStart+5*tStep). Optimization is useless. + * If tStep < piInAngleUnit - (tMax - tMin), situation B cannot happen. */ + if (tStep >= piInAngleUnit - tMax + tMin) { + return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + + /* The number of segments to draw can be reduced by drawing curve on intervals + * where (tMin%π, tMax%π) intersects (tStart, tEnd).For instance : + * if tStart=-π, tEnd=3π, tMin=π/4 and tMax=π/3, a curve is drawn between : + * - [ π/4, π/3 ], [ 2π + π/4, 2π + π/3 ] + * - [ -π + π/4, -π + π/3 ], [ π + π/4, π + π/3 ] in case f(θ) is negative */ + + // 1 - Set offset so that tStart <= tMax+thetaOffset < piInAngleUnit+tStart + float thetaOffset = std::ceil((tStart - tMax)/piInAngleUnit) * piInAngleUnit; + + // 2 - Increase offset until tMin + thetaOffset > tEnd + float tCache2 = tStart; + while (tMin + thetaOffset <= tEnd) { + float tS = std::max(tMin + thetaOffset, tStart); + float tE = std::min(tMax + thetaOffset, tEnd); + // Draw curve if there is an intersection + if (tS <= tE) { + /* To maximize cache hits, we floor (and ceil) tS (and tE) to the closest + * cached value. Step is small enough so that the extra drawn curve cannot + * overlap as tMax + tStep < piInAngleUnit + tMin) */ + int i = std::floor((tS - tStart) / tStep); + assert(tStart + tStep * i >= tCache2); + float tCache1 = tStart + tStep * i; + + int j = std::ceil((tE - tStart) / tStep); + tCache2 = std::min(tStart + tStep * j, tEnd); + + assert(tCache1 <= tCache2); + drawCurve(ctx, rect, tCache1, tCache2, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + thetaOffset += piInAngleUnit; + } +} + void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound, float highlightUpperBound) const { float rectMin = pixelToFloat(Axis::Horizontal, rect.left()); @@ -711,7 +851,7 @@ void CurveView::joinDots(KDContext * ctx, KDRect rect, EvaluateXYForFloatParamet const float deltaX = pxf - puf; const float deltaY = pyf - pvf; if (isFirstDot // First dot has to be stamped - || (!isLeftDotValid && maxNumberOfRecursion == 0) // Last step of the recursion with an undefined left dot: we stamp the last right dot + || (!isLeftDotValid && maxNumberOfRecursion <= 0) // Last step of the recursion with an undefined left dot: we stamp the last right dot || (isLeftDotValid && deltaX*deltaX + deltaY*deltaY < circleDiameter * circleDiameter / 4.0f)) { // the dots are already close enough // the dots are already joined /* We need to be sure that the point is not an artifact caused by error @@ -748,8 +888,27 @@ void CurveView::joinDots(KDContext * ctx, KDRect rect, EvaluateXYForFloatParamet } } if (maxNumberOfRecursion > 0) { - joinDots(ctx, rect, xyFloatEvaluation, model, context, drawStraightLinesEarly, t, x, y, ct, cx, cy, color, thick, maxNumberOfRecursion-1, xyDoubleEvaluation); - joinDots(ctx, rect, xyFloatEvaluation, model, context, drawStraightLinesEarly, ct, cx, cy, s, u, v, color, thick, maxNumberOfRecursion-1, xyDoubleEvaluation); + float xmin = min(Axis::Horizontal); + float xmax = max(Axis::Horizontal); + float ymax = max(Axis::Vertical); + float ymin = min(Axis::Vertical); + + int nextMaxNumberOfRecursion = maxNumberOfRecursion - 1; + // If both dots are out of rect bounds, and on a same side + if ((xmax < x && xmax < u) || (x < xmin && u < xmin) || + (ymax < y && ymax < v) || (y < ymin && v < ymin)) { + /* Discard a recursion step to save computation time on dots that are + * likely not to be drawn. It can alter precision with some functions when + * zooming excessively (compared to plot range) on local minimums + * For instance, plotting parametric function [t,|t-π|] with t in [0,360], + * x in [-1,20] and y in [-1,3] will show inaccuracies that would + * otherwise have been visible at higher zoom only, with x in [2,4] and y + * in [-0.2,0.2] in this case. */ + nextMaxNumberOfRecursion--; + } + + joinDots(ctx, rect, xyFloatEvaluation, model, context, drawStraightLinesEarly, t, x, y, ct, cx, cy, color, thick, nextMaxNumberOfRecursion, xyDoubleEvaluation); + joinDots(ctx, rect, xyFloatEvaluation, model, context, drawStraightLinesEarly, ct, cx, cy, s, u, v, color, thick, nextMaxNumberOfRecursion, xyDoubleEvaluation); } } @@ -862,14 +1021,32 @@ void CurveView::layoutSubviews(bool force) { KDRect CurveView::cursorFrame() { KDRect cursorFrame = KDRectZero; - if (m_cursorView && m_mainViewSelected && !std::isnan(m_curveViewCursor->x()) && !std::isnan(m_curveViewCursor->y())) { + if (m_cursorView && m_mainViewSelected && std::isfinite(m_curveViewCursor->x()) && std::isfinite(m_curveViewCursor->y())) { KDSize cursorSize = m_cursorView->minimalSizeForOptimalDisplay(); - KDCoordinate xCursorPixelPosition = std::round(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); - KDCoordinate yCursorPixelPosition = std::round(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); - cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, yCursorPixelPosition - (cursorSize.height()-1)/2, cursorSize.width(), cursorSize.height()); + float xCursorPixelPosition = std::round(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); + float yCursorPixelPosition = std::round(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); + + /* If the cursor is not visible, put its frame to zero, because it might be + * very far out of the visible frame and thus later overflow KDCoordinate. + * The "2" factor is a big safety margin. */ + constexpr int maxCursorPixel = KDCOORDINATE_MAX / 2; + // Assert we are not removing visible cursors + static_assert((Ion::Display::Width * 2 < maxCursorPixel) + && (Ion::Display::Height * 2 < maxCursorPixel), + "maxCursorPixel is should be bigger"); + if (std::abs(yCursorPixelPosition) > maxCursorPixel + || std::abs(xCursorPixelPosition) > maxCursorPixel) + { + return KDRectZero; + } + + KDCoordinate xCursor = xCursorPixelPosition; + KDCoordinate yCursor = yCursorPixelPosition; + KDCoordinate xCursorFrame = xCursor - (cursorSize.width()-1)/2; + cursorFrame = KDRect(xCursorFrame, yCursor - (cursorSize.height()-1)/2, cursorSize); if (cursorSize.height() == 0) { KDCoordinate bannerHeight = (m_bannerView != nullptr) ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0; - cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, 0, cursorSize.width(),bounds().height()-bannerHeight); + cursorFrame = KDRect(xCursorFrame, 0, cursorSize.width(), bounds().height() - bannerHeight); } } return cursorFrame; diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 983d802bb..91e73c5b3 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -41,6 +41,7 @@ public: void setForceOkDisplay(bool force) { m_forceOkDisplay = force; } float pixelWidth() const; float pixelHeight() const; + float pixelLength(Axis axis) const; protected: CurveViewRange * curveViewRange() const { return m_curveViewRange; } void setCurveViewRange(CurveViewRange * curveViewRange); @@ -74,11 +75,11 @@ protected: KDColor color, bool thick = true ) const; enum class Size : uint8_t { + Tiny, Small, - Medium, Large }; - void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const; + void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Tiny) const; /* 'drawArrow' draws the edge of an arrow pointing to (x,y) with the * orientation (dx,dy). * The parameters defining the shape of the arrow are the length of @@ -109,6 +110,7 @@ protected: void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; + void drawPolarCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const; void drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const; void computeLabels(Axis axis); diff --git a/apps/shared/curve_view_range.cpp b/apps/shared/curve_view_range.cpp index 93ae6bc6d..46196263d 100644 --- a/apps/shared/curve_view_range.cpp +++ b/apps/shared/curve_view_range.cpp @@ -10,8 +10,8 @@ namespace Shared { uint32_t CurveViewRange::rangeChecksum() { - float data[4] = {xMin(), xMax(), yMin(), yMax()}; - size_t dataLengthInBytes = 4*sizeof(float); + float data[7] = {xMin(), xMax(), yMin(), yMax(), xGridUnit(), yGridUnit(), offscreenYAxis()}; + size_t dataLengthInBytes = sizeof(data); assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 return Ion::crc32Word((uint32_t *)data, dataLengthInBytes/sizeof(uint32_t)); } diff --git a/apps/shared/curve_view_range.h b/apps/shared/curve_view_range.h index caa1d9ab0..b463389ce 100644 --- a/apps/shared/curve_view_range.h +++ b/apps/shared/curve_view_range.h @@ -2,6 +2,7 @@ #define SHARED_CURVE_VIEW_RANGE_H #include +#include namespace Shared { @@ -11,7 +12,7 @@ public: X, Y }; - virtual uint32_t rangeChecksum(); + uint32_t rangeChecksum(); virtual float xMin() const = 0; virtual float xMax() const = 0; @@ -23,19 +24,21 @@ public: return computeGridUnit(k_minNumberOfXGridUnits, k_maxNumberOfXGridUnits, xMax() - xMin()); } virtual float yGridUnit() const { - return computeGridUnit(k_minNumberOfYGridUnits, k_maxNumberOfYGridUnits, yMax() - yMin()); + return computeGridUnit(k_minNumberOfYGridUnits, k_maxNumberOfYGridUnits, yMax() - yMin() + offscreenYAxis()); } constexpr static float k_maxNumberOfXGridUnits = 18.0f; constexpr static float k_maxNumberOfYGridUnits = 13.0f; -private: +protected: constexpr static float k_minNumberOfXGridUnits = 7.0f; constexpr static float k_minNumberOfYGridUnits = 5.0f; +private: + virtual float offscreenYAxis() const { return 0.f; } /* The grid units is constrained to be a number of type: k*10^n with k = 1,2 or 5 * and n a relative integer. The choice of x and y grid units depend on the * grid range.*/ - constexpr static float k_smallGridUnitMantissa = 1.0f; - constexpr static float k_mediumGridUnitMantissa = 2.0f; - constexpr static float k_largeGridUnitMantissa = 5.0f; + constexpr static float k_smallGridUnitMantissa = Poincare::Zoom::k_smallUnitMantissa; + constexpr static float k_mediumGridUnitMantissa = Poincare::Zoom::k_mediumUnitMantissa; + constexpr static float k_largeGridUnitMantissa = Poincare::Zoom::k_largeUnitMantissa; float computeGridUnit(float minNumberOfUnits, float maxNumberOfUnits, float range) const; }; diff --git a/apps/shared/discard_pop_up_controller.cpp b/apps/shared/discard_pop_up_controller.cpp new file mode 100644 index 000000000..19a5aa550 --- /dev/null +++ b/apps/shared/discard_pop_up_controller.cpp @@ -0,0 +1,12 @@ +#include "discard_pop_up_controller.h" + +namespace Shared { + +DiscardPopUpController::DiscardPopUpController(Invocation OkInvocation) : + PopUpController(2, OkInvocation) +{ + m_contentView.setMessage(0, I18n::Message::ConfirmDiscard1); + m_contentView.setMessage(1, I18n::Message::ConfirmDiscard2); +} + +} diff --git a/apps/shared/discard_pop_up_controller.h b/apps/shared/discard_pop_up_controller.h new file mode 100644 index 000000000..6c54d4945 --- /dev/null +++ b/apps/shared/discard_pop_up_controller.h @@ -0,0 +1,15 @@ +#ifndef SHARED_DISCARD_POP_UP_CONTROLLER_H +#define SHARED_DISCARD_POP_UP_CONTROLLER_H + +#include + +namespace Shared { + +class DiscardPopUpController : public PopUpController { +public: + DiscardPopUpController(Invocation OkInvocation); +}; + +} + +#endif diff --git a/apps/shared/dots.cpp b/apps/shared/dots.cpp index 0f178b44e..1dc852660 100644 --- a/apps/shared/dots.cpp +++ b/apps/shared/dots.cpp @@ -2,7 +2,7 @@ namespace Shared { -const uint8_t Dots::SmallDotMask[Dots::SmallDotDiameter][Dots::SmallDotDiameter] = { +const uint8_t Dots::TinyDotMask[Dots::TinyDotDiameter][Dots::TinyDotDiameter] = { {0xE1, 0x45, 0x0C, 0x45, 0xE1}, {0x45, 0x00, 0x00, 0x00, 0x45}, {0x00, 0x00, 0x00, 0x00, 0x00}, @@ -10,7 +10,7 @@ const uint8_t Dots::SmallDotMask[Dots::SmallDotDiameter][Dots::SmallDotDiameter] {0xE1, 0x45, 0x0C, 0x45, 0xE1}, }; -const uint8_t Dots::MediumDotMask[Dots::MediumDotDiameter][Dots::MediumDotDiameter] = { +const uint8_t Dots::SmallDotMask[Dots::SmallDotDiameter][Dots::SmallDotDiameter] = { {0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1}, {0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45}, {0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, diff --git a/apps/shared/dots.h b/apps/shared/dots.h index 07d65ff44..c42be15dc 100644 --- a/apps/shared/dots.h +++ b/apps/shared/dots.h @@ -7,10 +7,10 @@ namespace Shared { class Dots { public: - static constexpr KDCoordinate SmallDotDiameter = 5; + static constexpr KDCoordinate TinyDotDiameter = 5; + static const uint8_t TinyDotMask[TinyDotDiameter][TinyDotDiameter]; + static constexpr KDCoordinate SmallDotDiameter = 7; static const uint8_t SmallDotMask[SmallDotDiameter][SmallDotDiameter]; - static constexpr KDCoordinate MediumDotDiameter = 7; - static const uint8_t MediumDotMask[MediumDotDiameter][MediumDotDiameter]; static constexpr KDCoordinate LargeDotDiameter = 10; static const uint8_t LargeDotMask[LargeDotDiameter][LargeDotDiameter]; }; diff --git a/apps/shared/double_pair_store.h b/apps/shared/double_pair_store.h index f83e88812..7ef5edb18 100644 --- a/apps/shared/double_pair_store.h +++ b/apps/shared/double_pair_store.h @@ -63,6 +63,7 @@ public: assert(i < Palette::numberOfLightDataColors()); return Palette::DataColorLight[i]; } + double * data() { return reinterpret_cast(&m_data); } protected: virtual double defaultValue(int series, int i, int j) const; double m_data[k_numberOfSeries][k_numberOfColumnsPerSeries][k_maxNumberOfPairs]; diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index 307956e02..54170b644 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -73,10 +73,14 @@ 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, ExpressionNode::ReductionTarget::SystemForApproximation); + /* 'Simplify' routine might need to call expressionReduced on the very + * same function. So we need to keep a valid m_expression while executing + * 'Simplify'. Thus, we use a temporary expression. */ + Expression tempExpression = m_expression.clone(); + PoincareHelpers::Simplify(&tempExpression, context, ExpressionNode::ReductionTarget::SystemForApproximation); // simplify might return an uninitialized Expression if interrupted - if (m_expression.isUninitialized()) { - m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + if (!tempExpression.isUninitialized()) { + m_expression = tempExpression; } } } diff --git a/apps/shared/expression_model.h b/apps/shared/expression_model.h index b57ae5ac2..6fe6501a3 100644 --- a/apps/shared/expression_model.h +++ b/apps/shared/expression_model.h @@ -22,6 +22,7 @@ public: Ion::Storage::Record::ErrorStatus setExpressionContent(Ion::Storage::Record * record, const Poincare::Expression & newExpression); virtual void tidy() const; + bool hasValidExpression() { return !m_expression.isUninitialized(); } protected: // Setters helper static Poincare::Expression BuildExpressionFromText(const char * c, CodePoint symbol = 0, Poincare::Context * context = nullptr); diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index b07c18e36..8d6229a18 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -30,7 +30,7 @@ public: * behaviour but it is not true for its child classes (for example, in * Sequence). */ virtual void tidy() { model()->tidy(); } - Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) { return editableModel()->setContent(this, c, context, symbol()); } + virtual Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) { return editableModel()->setContent(this, c, context, symbol()); } Ion::Storage::Record::ErrorStatus setExpressionContent(const Poincare::Expression & e) { return editableModel()->setExpressionContent(this, e); } protected: ExpressionModel * editableModel() { return const_cast(model()); } diff --git a/apps/shared/float_parameter_controller.h b/apps/shared/float_parameter_controller.h index 21dc7f94b..0d7f2495e 100644 --- a/apps/shared/float_parameter_controller.h +++ b/apps/shared/float_parameter_controller.h @@ -38,11 +38,11 @@ protected: int activeCell(); StackViewController * stackController(); virtual T parameterAtIndex(int index) = 0; + virtual void buttonAction(); SelectableTableView m_selectableTableView; ButtonWithSeparator m_okButton; private: constexpr static int k_buttonMargin = 6; - virtual void buttonAction(); virtual InfinityTolerance infinityAllowanceForRow(int row) const { return InfinityTolerance::None; } virtual int reusableParameterCellCount(int type) = 0; virtual HighlightCell * reusableParameterCell(int index, int type) = 0; diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 31b1e01e0..3f3150d6f 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -1,10 +1,16 @@ #include "function.h" +#include "interactive_curve_view_range.h" +#include "range_1D.h" #include "poincare_helpers.h" #include "poincare/src/parsing/parser.h" +#include +#include #include -#include -#include +#include #include +#include +#include +#include using namespace Poincare; @@ -44,6 +50,9 @@ KDColor Function::color() const { void Function::setActive(bool active) { recordData()->setActive(active); + if (!active) { + didBecomeInactive(); + } } int Function::printValue(double cursorT, double cursorX, double cursorY, char * buffer, int bufferSize, int precision, Poincare::Context * context) { @@ -74,4 +83,19 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(d.buffer)); } +void Function::protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const { + Poincare::Zoom::ValueAtAbscissa evaluation; + if (xRange) { + evaluation = [](float x, Poincare::Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x1(); + }; + } else { + evaluation = [](float x, Poincare::Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; + } + + Poincare::Zoom::FullRange(evaluation, tMin, tMax, tStep, min, max, context, this); +} + } diff --git a/apps/shared/function.h b/apps/shared/function.h index 52bdff733..a5daa9c59 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -45,7 +45,7 @@ public: // Name int name(char * buffer, size_t bufferSize); - int nameWithArgument(char * buffer, size_t bufferSize); + virtual int nameWithArgument(char * buffer, size_t bufferSize); virtual int printValue(double cursorT, double cursorX, double cursorY, char * buffer, int bufferSize, int precision, Poincare::Context * context); virtual I18n::Message parameterMessageName() const = 0; @@ -53,13 +53,24 @@ public: virtual Poincare::Coordinate2D evaluateXYAtParameter(float t, Poincare::Context * context) const = 0; virtual Poincare::Coordinate2D evaluateXYAtParameter(double t, Poincare::Context * context) const = 0; virtual Poincare::Expression sumBetweenBounds(double start, double end, Poincare::Context * context) const = 0; + + // Range + virtual void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, float targetRatio, Poincare::Context * context) const = 0; + protected: /* RecordDataBuffer is the layout of the data buffer of Record * representing a Function. We want to avoid padding which would: * - increase the size of the storage file * - introduce junk memory zone which are then crc-ed in Storage::checksum - * creating dependency on uninitialized values. */ - class RecordDataBuffer { + * creating dependency on uninitialized values. + * - complicate getters, setters and record handling + * In addition, Record::value() is a pointer to an address inside + * Ion::Storage::sharedStorage(), and it might be unaligned. We use the packed + * keyword to warn the compiler that it members are potentially unaligned + * (otherwise, the compiler can emit instructions that work only on aligned + * objects). It also solves the padding issue mentioned above. + */ + class __attribute__((packed)) RecordDataBuffer { public: RecordDataBuffer(KDColor color) : m_color(color), m_active(true) {} KDColor color() const { @@ -69,20 +80,22 @@ protected: void setActive(bool active) { m_active = active; } private: #if __EMSCRIPTEN__ - /* Record::value() is a pointer to an address inside - * Ion::Storage::sharedStorage(), and it might be unaligned. However, for - * emscripten memory representation, loads and stores must be aligned; + /* For emscripten memory representation, loads and stores must be aligned; * performing a normal load or store on an unaligned address can fail * silently. We thus use 'emscripten_align1_short' type, the unaligned * version of uint16_t type to avoid producing an alignment error on the * emscripten platform. */ static_assert(sizeof(emscripten_align1_short) == sizeof(uint16_t), "emscripten_align1_short should have the same size as uint16_t"); - emscripten_align1_short m_color __attribute__((packed)); + emscripten_align1_short m_color; #else - uint16_t m_color __attribute__((packed)); + uint16_t m_color; #endif bool m_active; }; + + void protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const; + virtual void didBecomeInactive() {} + private: RecordDataBuffer * recordData() const; }; diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 0b7a95454..fbdc4f43a 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -7,18 +7,12 @@ namespace Shared { FunctionApp::Snapshot::Snapshot() : m_cursor(), m_indexFunctionSelectedByCursor(0), - m_modelVersion(0), - m_rangeVersion(0), - m_angleUnitVersion(Preferences::AngleUnit::Radian) + m_rangeVersion(0) { - assert(m_previousModelsVersions[0] == 0); } void FunctionApp::Snapshot::reset() { m_indexFunctionSelectedByCursor = 0; - m_modelVersion = 0; - assert(sizeof(m_previousModelsVersions) == sizeof(uint32_t) * FunctionGraphController::sNumberOfMemoizedModelVersions); - memset(m_previousModelsVersions, 0, sizeof(m_previousModelsVersions)); m_rangeVersion = 0; setActiveTab(0); } @@ -37,13 +31,9 @@ void FunctionApp::willBecomeInactive() { ::App::willBecomeInactive(); } -bool FunctionApp::isAcceptableExpression(const Poincare::Expression expression) { - /* We forbid functions whose type is equal because the input "2+f(3)" would be - * simplify to an expression with an nested equal node which makes no sense. */ - if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, false, Expression(), localContext()) || expression.type() == ExpressionNode::Type::Equal) { - return false; - } - return TextFieldDelegateApp::isAcceptableExpression(expression); + +bool FunctionApp::isAcceptableExpression(const Expression exp) { + return TextFieldDelegateApp::isAcceptableExpression(exp) && ExpressionCanBeSerialized(exp, false, Expression(), localContext()); } } diff --git a/apps/shared/function_app.h b/apps/shared/function_app.h index d15e4c5e8..6ee6dce2a 100644 --- a/apps/shared/function_app.h +++ b/apps/shared/function_app.h @@ -6,19 +6,17 @@ #include "function_store.h" #include "curve_view_cursor.h" #include "values_controller.h" +#include "shared_app.h" namespace Shared { class FunctionApp : public ExpressionFieldDelegateApp { public: - class Snapshot : public ::App::Snapshot, public TabViewDataSource { + class Snapshot : public ::SharedApp::Snapshot, public TabViewDataSource { public: Snapshot(); CurveViewCursor * cursor() { return &m_cursor; } - uint32_t * modelVersion() { return &m_modelVersion; } - uint32_t * previousModelsVersions() { return m_previousModelsVersions; } uint32_t * rangeVersion() { return &m_rangeVersion; } - Poincare::Preferences::AngleUnit * angleUnitVersion() { return &m_angleUnitVersion; } virtual FunctionStore * functionStore() = 0; int * indexFunctionSelectedByCursor() { return &m_indexFunctionSelectedByCursor; } void reset() override; @@ -27,10 +25,7 @@ public: CurveViewCursor m_cursor; private: int m_indexFunctionSelectedByCursor; - uint32_t m_modelVersion; - uint32_t m_previousModelsVersions[FunctionGraphController::sNumberOfMemoizedModelVersions]; uint32_t m_rangeVersion; - Poincare::Preferences::AngleUnit m_angleUnitVersion; }; static FunctionApp * app() { return static_cast(Container::activeApp()); diff --git a/apps/shared/function_banner_delegate.cpp b/apps/shared/function_banner_delegate.cpp index 87e0f953b..29c3d4f3b 100644 --- a/apps/shared/function_banner_delegate.cpp +++ b/apps/shared/function_banner_delegate.cpp @@ -7,6 +7,12 @@ using namespace Poincare; namespace Shared { +constexpr int k_precision = Preferences::MediumNumberOfSignificantDigits; + +int convertDoubleToText(double t, char * buffer, int bufferSize) { + return PoincareHelpers::ConvertFloatToText(t, buffer, bufferSize, k_precision); +} + void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, Poincare::Context * context) { ExpiringPointer function = functionStore->modelForRecord(record); constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::charSizeForFloatsWithPrecision(Preferences::LargeNumberOfSignificantDigits); @@ -18,9 +24,7 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor strlcpy(buffer + numberOfChar, "=", bufferSize - numberOfChar); bannerView()->abscissaSymbol()->setText(buffer); - constexpr int precision = Preferences::MediumNumberOfSignificantDigits; - - numberOfChar = PoincareHelpers::ConvertFloatToText(cursor->t(), buffer, bufferSize, precision); + numberOfChar = convertDoubleToText(cursor->t(), buffer, bufferSize); assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar); bannerView()->abscissaValue()->setText(buffer); @@ -28,7 +32,7 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor numberOfChar = function->nameWithArgument(buffer, bufferSize); assert(numberOfChar <= bufferSize); numberOfChar += strlcpy(buffer+numberOfChar, "=", bufferSize-numberOfChar); - numberOfChar += function->printValue(cursor->t(), cursor->x(),cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, precision, context); + numberOfChar += function->printValue(cursor->t(), cursor->x(),cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, k_precision, context); assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar); bannerView()->ordinateView()->setText(buffer); @@ -36,4 +40,22 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor bannerView()->reload(); } +double FunctionBannerDelegate::getValueDisplayedOnBanner(double t, Poincare::Context * context, double deltaThreshold, bool roundToZero) { + if (roundToZero && std::fabs(t) < deltaThreshold) { + // Round to 0 to avoid rounding to unnecessary low non-zero value. + return 0.0; + } + // Convert float to text + constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::charSizeForFloatsWithPrecision(k_precision); + char buffer[bufferSize]; + int numberOfChar = convertDoubleToText(t, buffer, bufferSize); + assert(numberOfChar <= bufferSize); + // Silence compiler warnings + (void) numberOfChar; + // Extract displayed value + double displayedValue = PoincareHelpers::ApproximateToScalar(buffer, context); + // Return displayed value if difference from t is under deltaThreshold + return std::fabs(displayedValue-t) < deltaThreshold ? displayedValue : t; +} + } diff --git a/apps/shared/function_banner_delegate.h b/apps/shared/function_banner_delegate.h index f02a1f000..30acdcf1f 100644 --- a/apps/shared/function_banner_delegate.h +++ b/apps/shared/function_banner_delegate.h @@ -10,6 +10,14 @@ namespace Shared { class FunctionBannerDelegate { public: constexpr static int k_maxNumberOfCharacters = 50; + /* getValueDisplayedOnBanner returns the value of t as displayed in the + * banner, unless the difference from t exceeds deltaThreshold. If so, + * return t. For instance, when a function is plotted between 1.000001 and + * 1.000003, and the user goes to x = 1.000002, a small deltaThreshold + * prevents him from being sent to x = 1 + * Note : Due to double encoding, not all values of t can be properly rounded. + * For instance, x displayed as 0.01 can at best be encoded to x=0.010...02 */ + static double getValueDisplayedOnBanner(double t, Poincare::Context * context, double deltaThreshold, bool roundToZero = false); protected: void reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, Poincare::Context * context); virtual XYBannerView * bannerView() = 0; diff --git a/apps/shared/function_go_to_parameter_controller.cpp b/apps/shared/function_go_to_parameter_controller.cpp index c5b21cb43..bcb827923 100644 --- a/apps/shared/function_go_to_parameter_controller.cpp +++ b/apps/shared/function_go_to_parameter_controller.cpp @@ -2,6 +2,7 @@ #include "function_app.h" #include #include +#include namespace Shared { @@ -15,6 +16,10 @@ bool FunctionGoToParameterController::confirmParameterAtIndex(int parameterIndex assert(parameterIndex == 0); FunctionApp * myApp = FunctionApp::app(); ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); + // If possible, round f so that we go to the evaluation of the displayed f + double pixelWidth = (m_graphRange->xMax() - m_graphRange->xMin()) / Ion::Display::Width; + f = FunctionBannerDelegate::getValueDisplayedOnBanner(f, myApp->localContext(), pixelWidth, false); + Poincare::Coordinate2D xy = function->evaluateXYAtParameter(f, myApp->localContext()); m_cursor->moveTo(f, xy.x1(), xy.x2()); m_graphRange->centerAxisAround(CurveViewRange::Axis::X, m_cursor->x()); diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 21b3c652b..f43985e9f 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -1,5 +1,6 @@ #include "function_graph_controller.h" #include "function_app.h" +#include "poincare_helpers.h" #include "../apps_container.h" #include #include @@ -11,10 +12,8 @@ using namespace Poincare; namespace Shared { -FunctionGraphController::FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion) : - InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, interactiveRange, curveView, cursor, modelVersion, previousModelsVersions, rangeVersion), - m_initialisationParameterController(this, interactiveRange), - m_angleUnitVersion(angleUnitVersion), +FunctionGraphController::FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion) : + InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, interactiveRange, curveView, cursor, rangeVersion), m_indexFunctionSelectedByCursor(indexFunctionSelectedByCursor) { } @@ -26,10 +25,6 @@ bool FunctionGraphController::isEmpty() const { return false; } -ViewController * FunctionGraphController::initialisationParameterController() { - return &m_initialisationParameterController; -} - void FunctionGraphController::didBecomeFirstResponder() { if (curveView()->isMainViewSelected()) { bannerView()->abscissaValue()->setParentResponder(this); @@ -47,11 +42,7 @@ void FunctionGraphController::viewWillAppear() { if (functionGraphView()->context() == nullptr) { functionGraphView()->setContext(textFieldDelegateApp()->localContext()); } - Preferences::AngleUnit newAngleUnitVersion = Preferences::sharedPreferences()->angleUnit(); - if (*m_angleUnitVersion != newAngleUnitVersion) { - *m_angleUnitVersion = newAngleUnitVersion; - initCursorParameters(); - } + InteractiveCurveViewController::viewWillAppear(); } @@ -73,56 +64,21 @@ void FunctionGraphController::reloadBannerView() { reloadBannerViewForCursorOnFunction(m_cursor, record, functionStore(), AppsContainer::sharedAppsContainer()->globalContext()); } -InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - float min = FLT_MAX; - float max = -FLT_MAX; - float xMin = interactiveCurveViewRange->xMin(); - float xMax = interactiveCurveViewRange->xMax(); - assert(functionStore()->numberOfActiveFunctions() > 0); - for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - /* Scan x-range from the middle to the extrema in order to get balanced - * y-range for even functions (y = 1/x). */ - double tMin = f->tMin(); - if (std::isnan(tMin)) { - tMin = xMin; - } else if (f->shouldClipTRangeToXRange()) { - tMin = std::max(tMin, xMin); - } - double tMax = f->tMax(); - if (std::isnan(tMax)) { - tMax = xMax; - } else if (f->shouldClipTRangeToXRange()) { - tMax = std::min(tMax, xMax); - } - /* In practice, a step smaller than a pixel's width is needed for sampling - * the values of a function. Otherwise some relevant extremal values may be - * missed. */ - float rangeStep = f->rangeStep(); - const float step = std::isnan(rangeStep) ? curveView()->pixelWidth() / 2.0f : rangeStep; - const int balancedBound = std::floor((tMax-tMin)/2/step); - for (int j = -balancedBound; j <= balancedBound ; j++) { - float t = (tMin+tMax)/2 + step * j; - Coordinate2D xy = f->evaluateXYAtParameter(t, context); - float x = xy.x1(); - if (!std::isnan(x) && !std::isinf(x) && x >= xMin && x <= xMax) { - float y = xy.x2(); - if (!std::isnan(y) && !std::isinf(y)) { - min = std::min(min, y); - max = std::max(max, y); - } - } - } - } - InteractiveCurveViewRangeDelegate::Range range; - range.min = min; - range.max = max; - return range; -} - double FunctionGraphController::defaultCursorT(Ion::Storage::Record record) { - return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f; + Poincare::Context * context = textFieldDelegateApp()->localContext(); + ExpiringPointer function = functionStore()->modelForRecord(record); + float gridUnit = 2 * interactiveCurveViewRange()->xGridUnit(); + + float yMin = interactiveCurveViewRange()->yMin(), yMax = interactiveCurveViewRange()->yMax(); + float middle = (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f; + float resLeft = gridUnit * std::floor(middle / gridUnit); + float yLeft = function->evaluateXYAtParameter(resLeft, context).x2(); + float resRight = resLeft + gridUnit; + float yRight = function->evaluateXYAtParameter(resRight, context).x2(); + if ((yMin < yLeft && yLeft < yMax) || !(yMin < yRight && yRight < yMax)) { + return resLeft; + } + return resRight; } FunctionStore * FunctionGraphController::functionStore() const { @@ -145,9 +101,6 @@ void FunctionGraphController::initCursorParameters() { functionIndex = 0; } m_cursor->moveTo(t, xy.x1(), xy.x2()); - if (interactiveCurveViewRange()->yAuto()) { - interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); - } selectFunctionWithCursor(functionIndex); } @@ -171,18 +124,20 @@ bool FunctionGraphController::moveCursorVertically(int direction) { return true; } +bool FunctionGraphController::cursorMatchesModel() { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + if (indexFunctionSelectedByCursor() >= functionStore()->numberOfActiveFunctions()) { + return false; + } + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); + Coordinate2D xy = f->evaluateXYAtParameter(m_cursor->t(), context); + return PoincareHelpers::equalOrBothNan(xy.x1(), m_cursor->x()) && PoincareHelpers::equalOrBothNan(xy.x2(), m_cursor->y()); +} + CurveView * FunctionGraphController::curveView() { return functionGraphView(); } -uint32_t FunctionGraphController::modelVersion() { - return functionStore()->storeChecksum(); -} - -uint32_t FunctionGraphController::modelVersionAtIndex(int i) { - return functionStore()->storeChecksumAtIndex(i); -} - uint32_t FunctionGraphController::rangeVersion() { return interactiveCurveViewRange()->rangeChecksum(); } @@ -199,4 +154,9 @@ int FunctionGraphController::numberOfCurves() const { return functionStore()->numberOfActiveFunctions(); } +void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) { + float ratio = InteractiveCurveViewRange::NormalYXRatio() / (1 + cursorTopMarginRatio() + cursorBottomMarginRatio()); + DefaultInterestingRanges(range, textFieldDelegateApp()->localContext(), functionStore(), ratio); +} + } diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 9e7297efe..cac996fe8 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -2,7 +2,6 @@ #define SHARED_FUNCTION_GRAPH_CONTROLLER_H #include -#include "initialisation_parameter_controller.h" #include "function_banner_delegate.h" #include "interactive_curve_view_controller.h" #include "function_store.h" @@ -13,13 +12,13 @@ namespace Shared { class FunctionGraphController : public InteractiveCurveViewController, public FunctionBannerDelegate { public: - static constexpr size_t sNumberOfMemoizedModelVersions = 5; - FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion); + FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion); bool isEmpty() const override; - ViewController * initialisationParameterController() override; void didBecomeFirstResponder() override; void viewWillAppear() override; + void interestingRanges(Shared::InteractiveCurveViewRange * range) override; + protected: float cursorTopMarginRatio() override { return 0.068f; } void reloadBannerView() override; @@ -38,24 +37,19 @@ protected: Poincare::Coordinate2D xyValues(int curveIndex, double t, Poincare::Context * context) const override; int numberOfCurves() const override; void initCursorParameters() override; + bool cursorMatchesModel() override; CurveView * curveView() override; + void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; + private: virtual FunctionGraphView * functionGraphView() = 0; virtual FunctionCurveParameterController * curveParameterController() = 0; - // InteractiveCurveViewRangeDelegate - InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; - // InteractiveCurveViewController bool moveCursorVertically(int direction) override; - uint32_t modelVersion() override; - uint32_t modelVersionAtIndex(int i) override; uint32_t rangeVersion() override; - size_t numberOfMemoizedVersions() const override { return sNumberOfMemoizedModelVersions; } - InitialisationParameterController m_initialisationParameterController; - Poincare::Preferences::AngleUnit * m_angleUnitVersion; int * m_indexFunctionSelectedByCursor; }; diff --git a/apps/shared/function_store.cpp b/apps/shared/function_store.cpp index 4ef45d956..77ed25132 100644 --- a/apps/shared/function_store.cpp +++ b/apps/shared/function_store.cpp @@ -6,11 +6,4 @@ uint32_t FunctionStore::storeChecksum() { return Ion::Storage::sharedStorage()->checksum(); } -uint32_t FunctionStore::storeChecksumAtIndex(int i) { - if (numberOfActiveFunctions() <= i) { - return 0; - } - return activeRecordAtIndex(i).checksum(); -} - } diff --git a/apps/shared/function_store.h b/apps/shared/function_store.h index 0233a37dd..2790f6703 100644 --- a/apps/shared/function_store.h +++ b/apps/shared/function_store.h @@ -13,7 +13,6 @@ class FunctionStore : public ExpressionModelStore { public: FunctionStore() : ExpressionModelStore() {} uint32_t storeChecksum(); - uint32_t storeChecksumAtIndex(int i); int numberOfActiveFunctions() const { return numberOfModelsSatisfyingTest(&isFunctionActive, nullptr); } diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp similarity index 55% rename from apps/shared/zoom_parameter_controller.cpp rename to apps/shared/function_zoom_and_pan_curve_view_controller.cpp index 7eaafc276..2b6ab38af 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp @@ -1,21 +1,22 @@ -#include "zoom_parameter_controller.h" +#include "function_zoom_and_pan_curve_view_controller.h" #include #include namespace Shared { -ZoomParameterController::ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : +FunctionZoomAndPanCurveViewController::FunctionZoomAndPanCurveViewController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : ZoomAndPanCurveViewController(parentResponder), m_contentView(curveView), - m_interactiveRange(interactiveRange) + m_interactiveRange(interactiveRange), + m_restoreZoomAuto(false) { } -const char * ZoomParameterController::title() { - return I18n::translate(I18n::Message::Zoom); +const char * FunctionZoomAndPanCurveViewController::title() { + return I18n::translate(I18n::Message::Navigate); } -void ZoomParameterController::viewWillAppear() { +void FunctionZoomAndPanCurveViewController::viewWillAppear() { ViewController::viewWillAppear(); m_contentView.curveView()->setOkView(nullptr); /* We need to change the curve range to keep the same visual aspect of the @@ -23,23 +24,41 @@ void ZoomParameterController::viewWillAppear() { adaptCurveRange(true); } -void ZoomParameterController::viewDidDisappear() { +void FunctionZoomAndPanCurveViewController::viewDidDisappear() { // Restore the curve range adaptCurveRange(false); } -void ZoomParameterController::didBecomeFirstResponder() { +void FunctionZoomAndPanCurveViewController::didBecomeFirstResponder() { m_contentView.layoutSubviews(); } -void ZoomParameterController::adaptCurveRange(bool viewWillAppear) { +bool FunctionZoomAndPanCurveViewController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Back) { + /* If Auto is still on (because the navigation menu was brought up and + * closed immediately), we need to deactivate it to prevent the range from + * being recomputed in InteractiveCurveViewController::viewWillAppear(). + * We need to store it's state to reset it later in viewDidDisappear(), so + * that open navigation without moving doesn't deactivate the Auto. */ + m_restoreZoomAuto = m_interactiveRange->zoomAuto(); + m_interactiveRange->setZoomAuto(false); + } + return ZoomAndPanCurveViewController::handleEvent(event); +} + +void FunctionZoomAndPanCurveViewController::adaptCurveRange(bool viewWillAppear) { float currentYMin = m_interactiveRange->yMin(); float currentRange = m_interactiveRange->yMax() - m_interactiveRange->yMin(); float newYMin = 0; if (viewWillAppear) { - newYMin = currentYMin + ((float)ContentView::k_legendHeight)/((float)k_standardViewHeight)*currentRange; + float rangeOffscreen = ((float)ContentView::k_legendHeight)/((float)k_standardViewHeight)*currentRange; + newYMin = currentYMin + rangeOffscreen; + m_interactiveRange->setOffscreenYAxis(rangeOffscreen); } else { newYMin = m_interactiveRange->yMax() - currentRange*((float)k_standardViewHeight)/(((float)k_standardViewHeight)-((float)ContentView::k_legendHeight)); + m_interactiveRange->setOffscreenYAxis(0.f); + m_interactiveRange->setZoomAuto(m_restoreZoomAuto); + m_restoreZoomAuto = false; } m_interactiveRange->setYMin(newYMin); m_contentView.curveView()->reload(); @@ -47,16 +66,16 @@ void ZoomParameterController::adaptCurveRange(bool viewWillAppear) { /* Content View */ -ZoomParameterController::ContentView::ContentView(CurveView * curveView) : +FunctionZoomAndPanCurveViewController::ContentView::ContentView(CurveView * curveView) : m_curveView(curveView) { } -int ZoomParameterController::ContentView::numberOfSubviews() const { +int FunctionZoomAndPanCurveViewController::ContentView::numberOfSubviews() const { return 2; } -View * ZoomParameterController::ContentView::subviewAtIndex(int index) { +View * FunctionZoomAndPanCurveViewController::ContentView::subviewAtIndex(int index) { assert(index >= 0 && index < 2); /* The order of subviews matters here: redrawing curve view can be long and * if it was redraw before the legend view, you could see noise when @@ -67,19 +86,19 @@ View * ZoomParameterController::ContentView::subviewAtIndex(int index) { return m_curveView; } -void ZoomParameterController::ContentView::layoutSubviews(bool force) { - assert(bounds().height() == ZoomParameterController::k_standardViewHeight); +void FunctionZoomAndPanCurveViewController::ContentView::layoutSubviews(bool force) { + assert(bounds().height() == FunctionZoomAndPanCurveViewController::k_standardViewHeight); m_curveView->setFrame(KDRect(0, 0, bounds().width(), bounds().height() - k_legendHeight), force); m_legendView.setFrame(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight), force); } -CurveView * ZoomParameterController::ContentView::curveView() { +CurveView * FunctionZoomAndPanCurveViewController::ContentView::curveView() { return m_curveView; } /* Legend View */ -ZoomParameterController::ContentView::LegendView::LegendView() +FunctionZoomAndPanCurveViewController::ContentView::LegendView::LegendView() { I18n::Message messages[k_numberOfLegends] = {I18n::Message::Move, I18n::Message::ToZoom, I18n::Message::Or}; float horizontalAlignments[k_numberOfLegends] = {1.0f, 1.0f, 0.5f}; @@ -95,15 +114,15 @@ ZoomParameterController::ContentView::LegendView::LegendView() } } -void ZoomParameterController::ContentView::LegendView::drawRect(KDContext * ctx, KDRect rect) const { +void FunctionZoomAndPanCurveViewController::ContentView::LegendView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight), Palette::SubMenuBackground); } -int ZoomParameterController::ContentView::LegendView::numberOfSubviews() const { +int FunctionZoomAndPanCurveViewController::ContentView::LegendView::numberOfSubviews() const { return k_numberOfLegends+k_numberOfTokens; } -View * ZoomParameterController::ContentView::LegendView::subviewAtIndex(int index) { +View * FunctionZoomAndPanCurveViewController::ContentView::LegendView::subviewAtIndex(int index) { assert(index >= 0 && index < k_numberOfTokens+k_numberOfLegends); if (index < k_numberOfLegends) { return &m_legends[index]; @@ -111,7 +130,7 @@ View * ZoomParameterController::ContentView::LegendView::subviewAtIndex(int inde return &m_legendPictograms[index-k_numberOfLegends]; } -void ZoomParameterController::ContentView::LegendView::layoutSubviews(bool force) { +void FunctionZoomAndPanCurveViewController::ContentView::LegendView::layoutSubviews(bool force) { KDCoordinate height = bounds().height(); KDCoordinate xOrigin = 0; KDCoordinate legendWidth = m_legends[0].minimalSizeForOptimalDisplay().width(); diff --git a/apps/shared/zoom_parameter_controller.h b/apps/shared/function_zoom_and_pan_curve_view_controller.h similarity index 79% rename from apps/shared/zoom_parameter_controller.h rename to apps/shared/function_zoom_and_pan_curve_view_controller.h index 293f166cf..8acfd040a 100644 --- a/apps/shared/zoom_parameter_controller.h +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.h @@ -1,19 +1,20 @@ -#ifndef SHARED_ZOOM_PARAMETER_CONTROLLER_H -#define SHARED_ZOOM_PARAMETER_CONTROLLER_H +#ifndef SHARED_FUNCTION_ZOOM_AND_PAN_CURVE_VIEW_CONTROLLER_H +#define SHARED_FUNCTION_ZOOM_AND_PAN_CURVE_VIEW_CONTROLLER_H #include "zoom_and_pan_curve_view_controller.h" #include namespace Shared { -class ZoomParameterController : public ZoomAndPanCurveViewController { +class FunctionZoomAndPanCurveViewController : public ZoomAndPanCurveViewController { public: - ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveCurveViewRange, CurveView * curveView); + FunctionZoomAndPanCurveViewController(Responder * parentResponder, InteractiveCurveViewRange * interactiveCurveViewRange, CurveView * curveView); const char * title() override; View * view() override { return &m_contentView; } void viewWillAppear() override; void viewDidDisappear() override; void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; TELEMETRY_ID("Zoom"); private: constexpr static KDCoordinate k_standardViewHeight = 175; @@ -53,6 +54,7 @@ private: ContentView m_contentView; InteractiveCurveViewRange * m_interactiveRange; + bool m_restoreZoomAuto; }; } diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 8109f9468..5df988953 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -1,6 +1,9 @@ #include "global_context.h" #include "continuous_function.h" +#include "sequence.h" #include "poincare_helpers.h" +#include +#include #include #include @@ -10,6 +13,11 @@ namespace Shared { constexpr const char * GlobalContext::k_extensions[]; +SequenceStore * GlobalContext::sequenceStore() { + static SequenceStore sequenceStore; + return &sequenceStore; +} + bool GlobalContext::SymbolAbstractNameIsFree(const char * baseName) { return SymbolAbstractRecordWithBaseName(baseName).isNull(); } @@ -18,9 +26,11 @@ const Layout GlobalContext::LayoutForRecord(Ion::Storage::Record record) { assert(!record.isNull()); if (Ion::Storage::FullNameHasExtension(record.fullName(), Ion::Storage::expExtension, strlen(Ion::Storage::expExtension))) { return PoincareHelpers::CreateLayout(ExpressionForActualSymbol(record)); - } else { - assert(Ion::Storage::FullNameHasExtension(record.fullName(), Ion::Storage::funcExtension, strlen(Ion::Storage::funcExtension))); + } else if (Ion::Storage::FullNameHasExtension(record.fullName(), Ion::Storage::funcExtension, strlen(Ion::Storage::funcExtension))) { return ContinuousFunction(record).layout(); + } else { + assert(Ion::Storage::FullNameHasExtension(record.fullName(), Ion::Storage::seqExtension, strlen(Ion::Storage::seqExtension))); + return Sequence(record).layout(); } } @@ -36,22 +46,26 @@ Context::SymbolAbstractType GlobalContext::expressionTypeForIdentifier(const cha const char * extension = Ion::Storage::sharedStorage()->extensionOfRecordBaseNamedWithExtensions(identifier, length, k_extensions, k_numberOfExtensions); if (extension == nullptr) { return Context::SymbolAbstractType::None; + } else if (extension == Ion::Storage::expExtension) { + return Context::SymbolAbstractType::Symbol; + } else if (extension == Ion::Storage::funcExtension) { + return Context::SymbolAbstractType::Function; + } else { + assert(extension == Ion::Storage::seqExtension); + return Context::SymbolAbstractType::Sequence; } - assert(k_numberOfExtensions == 2); - assert(extension == Ion::Storage::expExtension || extension == Ion::Storage::funcExtension); - return extension == Ion::Storage::expExtension ? Context::SymbolAbstractType::Symbol : Context::SymbolAbstractType::Function; } -const Expression GlobalContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { +const Expression GlobalContext::expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { Ion::Storage::Record r = SymbolAbstractRecordWithBaseName(symbol.name()); - return ExpressionForSymbolAndRecord(symbol, r); + return ExpressionForSymbolAndRecord(symbol, r, this, unknownSymbolValue); } void GlobalContext::setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) { /* If the new expression contains the symbol, replace it because it will be * destroyed afterwards (to be able to do A+2->A) */ Ion::Storage::Record record = SymbolAbstractRecordWithBaseName(symbol.name()); - Expression e = ExpressionForSymbolAndRecord(symbol, record); + Expression e = ExpressionForSymbolAndRecord(symbol, record, this); if (e.isUninitialized()) { e = Undefined::Builder(); } @@ -69,12 +83,14 @@ void GlobalContext::setExpressionForSymbolAbstract(const Expression & expression } } -const Expression GlobalContext::ExpressionForSymbolAndRecord(const SymbolAbstract & symbol, Ion::Storage::Record r) { +const Expression GlobalContext::ExpressionForSymbolAndRecord(const SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx, float unknownSymbolValue ) { if (symbol.type() == ExpressionNode::Type::Symbol) { return ExpressionForActualSymbol(r); + } else if (symbol.type() == ExpressionNode::Type::Function) { + return ExpressionForFunction(symbol, r); } - assert(symbol.type() == ExpressionNode::Type::Function); - return ExpressionForFunction(symbol, r); + assert(symbol.type() == ExpressionNode::Type::Sequence); + return ExpressionForSequence(symbol, r, ctx, unknownSymbolValue); } const Expression GlobalContext::ExpressionForActualSymbol(Ion::Storage::Record r) { @@ -99,6 +115,38 @@ const Expression GlobalContext::ExpressionForFunction(const SymbolAbstract & sym return e; } +const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx, float unknownSymbolValue) { + if (!Ion::Storage::FullNameHasExtension(r.fullName(), Ion::Storage::seqExtension, strlen(Ion::Storage::seqExtension))) { + return Expression(); + } + /* An function record value has metadata before the expression. To get the + * expression, use the function record handle. */ + Sequence seq(r); + Expression rank = symbol.childAtIndex(0).clone(); + rank = rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(unknownSymbolValue)); + rank = rank.simplify(ExpressionNode::ReductionContext(ctx, Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::SystemForApproximation)); + if (!rank.isUninitialized()) { + bool rankIsInteger = false; + double rankValue = rank.approximateToScalar(ctx, Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit()); + if (rank.type() == ExpressionNode::Type::Rational) { + Rational n = static_cast(rank); + rankIsInteger = n.isInteger(); + } else if (!std::isnan(unknownSymbolValue)) { + /* If unknownSymbolValue is not nan, then we are in the graph app. In order + * to allow functions like f(x) = u(x+0.5) to be ploted, we need to + * approximate the rank and check if it is an integer. Unfortunatly this + * leads to some edge cases were, because of quantification, we have + * floor(x) = x while x is not integer.*/ + rankIsInteger = std::floor(rankValue) == rankValue; + } + if (rankIsInteger && !seq.badlyReferencesItself(ctx)) { + SequenceContext sqctx(ctx, sequenceStore()); + return Float::Builder(seq.evaluateXYAtParameter(rankValue, &sqctx).x2()); + } + } + return Float::Builder(NAN); +} + Ion::Storage::Record::ErrorStatus GlobalContext::SetExpressionForActualSymbol(const Expression & expression, const SymbolAbstract & symbol, Ion::Storage::Record previousRecord) { if (!previousRecord.isNull() && Ion::Storage::FullNameHasExtension(previousRecord.fullName(), Ion::Storage::funcExtension, strlen(Ion::Storage::funcExtension))) { /* A function can overwrite a variable, but a variable cannot be created if diff --git a/apps/shared/global_context.h b/apps/shared/global_context.h index de6b53fc9..bf71a2c99 100644 --- a/apps/shared/global_context.h +++ b/apps/shared/global_context.h @@ -8,13 +8,14 @@ #include #include #include +#include "sequence_store.h" namespace Shared { class GlobalContext final : public Poincare::Context { public: - static constexpr int k_numberOfExtensions = 2; - static constexpr const char * k_extensions[] = {Ion::Storage::expExtension, Ion::Storage::funcExtension}; + static constexpr int k_numberOfExtensions = 3; + static constexpr const char * k_extensions[] = {Ion::Storage::expExtension, Ion::Storage::funcExtension, Ion::Storage::seqExtension}; // Storage information static bool SymbolAbstractNameIsFree(const char * baseName); @@ -28,19 +29,21 @@ public: * The expression recorded in global context is already an expression. * Otherwise, we would need the context and the angle unit to evaluate it */ SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override; - const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; + const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; void setExpressionForSymbolAbstract(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol) override; - + static SequenceStore * sequenceStore(); private: // Expression getters - static const Poincare::Expression ExpressionForSymbolAndRecord(const Poincare::SymbolAbstract & symbol, Ion::Storage::Record r); + static const Poincare::Expression ExpressionForSymbolAndRecord(const Poincare::SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx, float unknownSymbolValue = NAN); static const Poincare::Expression ExpressionForActualSymbol(Ion::Storage::Record r); static const Poincare::Expression ExpressionForFunction(const Poincare::SymbolAbstract & symbol, Ion::Storage::Record r); + static const Poincare::Expression ExpressionForSequence(const Poincare::SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx, float unknownSymbolValue = NAN); // Expression setters static Ion::Storage::Record::ErrorStatus SetExpressionForActualSymbol(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol, Ion::Storage::Record previousRecord); static Ion::Storage::Record::ErrorStatus SetExpressionForFunction(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol, Ion::Storage::Record previousRecord); // Record getter static Ion::Storage::Record SymbolAbstractRecordWithBaseName(const char * name); + }; } diff --git a/apps/shared/initialisation_parameter_controller.cpp b/apps/shared/initialisation_parameter_controller.cpp deleted file mode 100644 index 531fed091..000000000 --- a/apps/shared/initialisation_parameter_controller.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "initialisation_parameter_controller.h" -#include -#include - -namespace Shared { - -View * InitialisationParameterController::view() { - return &m_selectableTableView; -} - -const char * InitialisationParameterController::title() { - return I18n::translate(I18n::Message::Initialization); -} - -bool InitialisationParameterController::handleEvent(Ion::Events::Event event) { -if (event == Ion::Events::OK || event == Ion::Events::EXE) { - RangeMethodPointer rangeMethods[k_totalNumberOfCells] = { - &InteractiveCurveViewRange::setTrigonometric, - &InteractiveCurveViewRange::roundAbscissa, - &InteractiveCurveViewRange::normalize, - &InteractiveCurveViewRange::setDefault}; - (m_graphRange->*rangeMethods[selectedRow()])(); - StackViewController * stack = (StackViewController *)parentResponder(); - stack->pop(); - return true; - } - return false; -} - -void InitialisationParameterController::didBecomeFirstResponder() { - m_selectableTableView.selectCellAtLocation(0, 0); - Container::activeApp()->setFirstResponder(&m_selectableTableView); -} - -int InitialisationParameterController::numberOfRows() const { - return k_totalNumberOfCells; -} - -KDCoordinate InitialisationParameterController::cellHeight() { - return Metric::ParameterCellHeight; -} - -HighlightCell * InitialisationParameterController::reusableCell(int index) { - assert(index >= 0); - assert(index < k_totalNumberOfCells); - return &m_cells[index]; -} - -int InitialisationParameterController::reusableCellCount() const { - return k_totalNumberOfCells; -} - -void InitialisationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - I18n::Message titles[4] = { - I18n::Message::Trigonometric, - I18n::Message::RoundAbscissa, - I18n::Message::Orthonormal, - I18n::Message::DefaultSetting}; - ((MessageTableCell *)cell)->setMessage(titles[index]); -} - -} diff --git a/apps/shared/initialisation_parameter_controller.h b/apps/shared/initialisation_parameter_controller.h deleted file mode 100644 index e1f1c925c..000000000 --- a/apps/shared/initialisation_parameter_controller.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef SHARED_INITIALISATION_PARAMETER_CONTROLLER_H -#define SHARED_INITIALISATION_PARAMETER_CONTROLLER_H - -#include -#include "interactive_curve_view_range.h" -#include - -namespace Shared { - -class InitialisationParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { -public: - InitialisationParameterController(Responder * parentResponder, Shared::InteractiveCurveViewRange * graphRange) : - ViewController(parentResponder), - m_selectableTableView(this, this, this), - m_graphRange(graphRange) - {} - View * view() override; - const char * title() override; - TELEMETRY_ID("Initialization"); - bool handleEvent(Ion::Events::Event event) override; - void didBecomeFirstResponder() override; - int numberOfRows() const override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() const override; - void willDisplayCellForIndex(HighlightCell * cell, int index) override; -private: - constexpr static int k_totalNumberOfCells = 4; - MessageTableCell m_cells[k_totalNumberOfCells]; - SelectableTableView m_selectableTableView; - InteractiveCurveViewRange * m_graphRange; -}; - -} - -#endif diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index c6b736296..30d77a3ce 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -1,3 +1,4 @@ +#include "function_banner_delegate.h" #include "interactive_curve_view_controller.h" #include #include @@ -7,78 +8,63 @@ using namespace Poincare; namespace Shared { -InteractiveCurveViewController::InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion) : +InteractiveCurveViewController::InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * rangeVersion) : SimpleInteractiveCurveViewController(parentResponder, cursor), ButtonRowDelegate(header, nullptr), - m_modelVersion(modelVersion), - m_previousModelsVersions(previousModelsVersions), m_rangeVersion(rangeVersion), m_rangeParameterController(this, inputEventHandlerDelegate, interactiveRange), m_zoomParameterController(this, interactiveRange, curveView), m_interactiveRange(interactiveRange), + m_autoButton(this, I18n::Message::DefaultSetting, Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + graphController->autoButtonAction(); + return true; + }, this), KDFont::SmallFont), + m_normalizeButton(this, I18n::Message::Orthonormal, Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + graphController->normalizeButtonAction(); + return true; + }, this), KDFont::SmallFont), + m_navigationButton(this, I18n::Message::Navigate, Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + graphController->navigationButtonAction(); + return true; + }, this), KDFont::SmallFont), m_rangeButton(this, I18n::Message::Axis, Invocation([](void * context, void * sender) { InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; graphController->rangeParameterController()->setRange(graphController->interactiveRange()); StackViewController * stack = graphController->stackController(); stack->push(graphController->rangeParameterController()); return true; - }, this), KDFont::SmallFont), - m_zoomButton(this, I18n::Message::Zoom, Invocation([](void * context, void * sender) { - InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; - StackViewController * stack = graphController->stackController(); - stack->push(graphController->zoomParameterController()); - return true; - }, this), KDFont::SmallFont), - m_defaultInitialisationButton(this, I18n::Message::Initialization, Invocation([](void * context, void * sender) { - InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; - StackViewController * stack = graphController->stackController(); - stack->push(graphController->initialisationParameterController()); - return true; }, this), KDFont::SmallFont) { } float InteractiveCurveViewController::addMargin(float y, float range, bool isVertical, bool isMin) { - /* The provided min or max range limit y is altered by adding a margin. - * In pixels, the view's height occupied by the vertical range is equal to - * viewHeight - topMargin - bottomMargin. - * Hence one pixel must correspond to - * range / (viewHeight - topMargin - bottomMargin). - * Finally, adding topMargin pixels of margin, say at the top, comes down - * to adding - * range * topMargin / (viewHeight - topMargin - bottomMargin) - * which is equal to - * range * topMarginRatio / ( 1 - topMarginRatio - bottomMarginRatio) - * where - * topMarginRation = topMargin / viewHeight - * bottomMarginRatio = bottomMargin / viewHeight. - * The same goes horizontally. - */ - float topMarginRatio = isVertical ? cursorTopMarginRatio() : k_cursorRightMarginRatio; - float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : k_cursorLeftMarginRatio; - assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct - float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; - float ratio = isMin ? -bottomMarginRatio : topMarginRatio; - /* We want to add slightly more than the required margin, so that - * InteractiveCurveViewRange::panToMakePointVisible does not think a point is - * invisible due to precision problems when checking if it is outside the - * required margin. This is why we add a 1.05f factor. */ - ratio = 1.05f * ratio / ratioDenominator; - return y + ratio * range; + return DefaultAddMargin(y, range, isVertical, isMin, cursorTopMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), cursorRightMarginRatio()); +} + +void InteractiveCurveViewController::updateZoomButtons() { + m_autoButton.setState(m_interactiveRange->zoomAuto()); + m_normalizeButton.setState(m_interactiveRange->zoomNormalize()); } const char * InteractiveCurveViewController::title() { return I18n::translate(I18n::Message::GraphTab); } +void InteractiveCurveViewController::setCurveViewAsMainView() { + header()->setSelectedButton(-1); + curveView()->selectMainView(true); + Container::activeApp()->setFirstResponder(this); + reloadBannerView(); + curveView()->reload(); +} + bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) { if (!curveView()->isMainViewSelected()) { if (event == Ion::Events::Down) { - header()->setSelectedButton(-1); - curveView()->selectMainView(true); - Container::activeApp()->setFirstResponder(this); - reloadBannerView(); - curveView()->reload(); + setCurveViewAsMainView(); return true; } if (event == Ion::Events::Up) { @@ -93,7 +79,8 @@ bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) { if (moveCursorVertically(direction)) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), - cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio + cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), + curveView()->pixelWidth() ); reloadBannerView(); curveView()->reload(); @@ -127,11 +114,11 @@ int InteractiveCurveViewController::numberOfButtons(ButtonRowController::Positio if (isEmpty()) { return 0; } - return 3; + return 4; } Button * InteractiveCurveViewController::buttonAtIndex(int index, ButtonRowController::Position position) const { - const Button * buttons[3] = {&m_rangeButton, &m_zoomButton, &m_defaultInitialisationButton}; + const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_navigationButton, &m_rangeButton}; return (Button *)buttons[index]; } @@ -139,58 +126,21 @@ Responder * InteractiveCurveViewController::defaultController() { return tabController(); } -bool InteractiveCurveViewController::previousModelsWereAllDeleted() { - bool result = true; - const int modelsCount = numberOfCurves(); - const int memoizationCount = numberOfMemoizedVersions(); - - // Look for a current model that is the same as in the previous version - for (int i = 0; i < modelsCount; i++) { - uint32_t currentVersion = modelVersionAtIndex(i); - for (int j = 0; j < memoizationCount; j++) { - uint32_t * previousVersion = m_previousModelsVersions + j; - if (currentVersion == *previousVersion) { - result = false; - break; - } - } - if (!result) { - break; - } - } - - // Update the memoization - for (int i = 0; i < memoizationCount; i++) { - uint32_t * previousVersion = m_previousModelsVersions + i; - uint32_t newVersion = modelVersionAtIndex(i); - if (*previousVersion != newVersion) { - *previousVersion = newVersion; - } - } - return result; -} - void InteractiveCurveViewController::viewWillAppear() { SimpleInteractiveCurveViewController::viewWillAppear(); - uint32_t newModelVersion = modelVersion(); - if (*m_modelVersion != newModelVersion) { - // Put previousModelsWereAllDeleted first to update the model versions - if (previousModelsWereAllDeleted() || *m_modelVersion == 0 || numberOfCurves() == 1 || shouldSetDefaultOnModelChange()) { - interactiveCurveViewRange()->setDefault(); - } - *m_modelVersion = newModelVersion; - didChangeRange(interactiveCurveViewRange()); - /* Warning: init cursor parameter before reloading banner view. Indeed, - * reloading banner view needs an updated cursor to load the right data. */ + + if (m_interactiveRange->zoomAuto()) { + m_interactiveRange->setDefault(); + } + + /* Warning: init cursor parameter before reloading banner view. Indeed, + * reloading banner view needs an updated cursor to load the right data. */ + uint32_t newRangeVersion = rangeVersion(); + if ((*m_rangeVersion != newRangeVersion && !isCursorVisible()) || !cursorMatchesModel()) { initCursorParameters(); } - uint32_t newRangeVersion = rangeVersion(); - if (*m_rangeVersion != newRangeVersion) { - *m_rangeVersion = newRangeVersion; - if (!isCursorVisible()) { - initCursorParameters(); - } - } + *m_rangeVersion = newRangeVersion; + curveView()->setOkView(&m_okView); if (!curveView()->isMainViewSelected()) { curveView()->selectMainView(true); @@ -218,9 +168,13 @@ bool InteractiveCurveViewController::textFieldDidFinishEditing(TextField * textF if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { return false; } + /* If possible, round floatBody so that we go to the evaluation of the + * displayed floatBody */ + floatBody = FunctionBannerDelegate::getValueDisplayedOnBanner(floatBody, textFieldDelegateApp()->localContext(), curveView()->pixelWidth(), false); + Coordinate2D xy = xyValues(selectedCurveIndex(), floatBody, textFieldDelegateApp()->localContext()); m_cursor->moveTo(floatBody, xy.x1(), xy.x2()); - interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; @@ -246,15 +200,18 @@ bool InteractiveCurveViewController::isCursorVisible() { float xRange = range->xMax() - range->xMin(); float yRange = range->yMax() - range->yMin(); return - m_cursor->x() >= range->xMin() + k_cursorLeftMarginRatio * xRange && - m_cursor->x() <= range->xMax() - k_cursorRightMarginRatio * xRange && - m_cursor->y() >= range->yMin() + cursorBottomMarginRatio() * yRange && - m_cursor->y() <= range->yMax() - cursorTopMarginRatio() * yRange; + m_cursor->x() >= range->xMin() + cursorLeftMarginRatio() * xRange && + m_cursor->x() <= range->xMax() - cursorRightMarginRatio() * xRange && + m_cursor->y() >= range->yMin() + cursorBottomMarginRatio() * yRange && + m_cursor->y() <= range->yMax() - cursorTopMarginRatio() * yRange; } int InteractiveCurveViewController::closestCurveIndexVertically(bool goingUp, int currentCurveIndex, Poincare::Context * context) const { double x = m_cursor->x(); double y = m_cursor->y(); + if (std::isnan(y)) { + y = goingUp ? -INFINITY : INFINITY; + } double nextY = goingUp ? DBL_MAX : -DBL_MAX; int nextCurveIndex = -1; int curvesCount = numberOfCurves(); @@ -315,4 +272,28 @@ float InteractiveCurveViewController::estimatedBannerHeight() const { return BannerView::HeightGivenNumberOfLines(estimatedBannerNumberOfLines()); } +bool InteractiveCurveViewController::autoButtonAction() { + if (m_interactiveRange->zoomAuto()) { + m_interactiveRange->setZoomAuto(false); + } else { + m_interactiveRange->setDefault(); + initCursorParameters(); + setCurveViewAsMainView(); + } + return m_interactiveRange->zoomAuto(); +} + +bool InteractiveCurveViewController::normalizeButtonAction() { + if (!m_interactiveRange->zoomNormalize()) { + m_interactiveRange->setZoomAuto(false); + m_interactiveRange->normalize(); + setCurveViewAsMainView(); + } + return m_interactiveRange->zoomNormalize(); +} + +void InteractiveCurveViewController::navigationButtonAction() { + stackController()->push(zoomParameterController()); +} + } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 99927b65a..a64db50c0 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -5,14 +5,15 @@ #include "cursor_view.h" #include "ok_view.h" #include "range_parameter_controller.h" -#include "zoom_parameter_controller.h" +#include "function_zoom_and_pan_curve_view_controller.h" #include +#include namespace Shared { class InteractiveCurveViewController : public SimpleInteractiveCurveViewController, public InteractiveCurveViewRangeDelegate, public ButtonRowDelegate, public AlternateEmptyViewDefaultDelegate { public: - InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion); + InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * rangeVersion); const char * title() override; bool handleEvent(Ion::Events::Event event) override; @@ -21,15 +22,12 @@ public: RangeParameterController * rangeParameterController(); ViewController * zoomParameterController(); - virtual ViewController * initialisationParameterController() = 0; int numberOfButtons(ButtonRowController::Position position) const override; Button * buttonAtIndex(int index, ButtonRowController::Position position) const override; Responder * defaultController() override; - bool previousModelsWereAllDeleted(); - void viewWillAppear() override; void viewDidDisappear() override; void willExitResponderChain(Responder * nextFirstResponder) override; @@ -41,10 +39,10 @@ protected: virtual StackViewController * stackController() const; virtual void initCursorParameters() = 0; virtual bool moveCursorVertically(int direction) = 0; - virtual uint32_t modelVersion() = 0; - virtual uint32_t modelVersionAtIndex(int i) = 0; virtual uint32_t rangeVersion() = 0; bool isCursorVisible(); + // The cursor does not match if selected model has been edited or deleted + virtual bool cursorMatchesModel() = 0; // Closest vertical curve helper int closestCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const; @@ -70,18 +68,24 @@ private: // InteractiveCurveViewRangeDelegate float addMargin(float x, float range, bool isVertical, bool isMin) override; + void updateZoomButtons() override; + + void setCurveViewAsMainView(); + + void navigationButtonAction(); + /* Those two methods return the new status for the button, ie either + * m_interactiveRange->m_zoomAuto or m_zoomNormalize respectively. */ + bool autoButtonAction(); + bool normalizeButtonAction(); - virtual bool shouldSetDefaultOnModelChange() const { return false; } - virtual size_t numberOfMemoizedVersions() const = 0; - uint32_t * m_modelVersion; - uint32_t * m_previousModelsVersions; uint32_t * m_rangeVersion; RangeParameterController m_rangeParameterController; - ZoomParameterController m_zoomParameterController; + FunctionZoomAndPanCurveViewController m_zoomParameterController; InteractiveCurveViewRange * m_interactiveRange; + ButtonState m_autoButton; + ButtonState m_normalizeButton; + Button m_navigationButton; Button m_rangeButton; - Button m_zoomButton; - Button m_defaultInitialisationButton; }; } diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 9e733b874..45b212eab 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -3,33 +3,61 @@ #include #include #include +#include #include +#include #include using namespace Poincare; namespace Shared { -uint32_t InteractiveCurveViewRange::rangeChecksum() { - float data[5] = {xMin(), xMax(), yMin(), yMax(), m_yAuto ? 1.0f : 0.0f}; - size_t dataLengthInBytes = 5*sizeof(float); - assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 - return Ion::crc32Word((uint32_t *)data, dataLengthInBytes/sizeof(uint32_t)); +void InteractiveCurveViewRange::setDelegate(InteractiveCurveViewRangeDelegate * delegate) { + m_delegate = delegate; + if (delegate) { + m_delegate->updateZoomButtons(); + } } -void InteractiveCurveViewRange::setYAuto(bool yAuto) { - m_yAuto = yAuto; - notifyRangeChange(); +void InteractiveCurveViewRange::setZoomAuto(bool v) { + if (m_zoomAuto == v) { + return; + } + m_zoomAuto = v; + if (m_delegate) { + m_delegate->updateZoomButtons(); + } +} + +void InteractiveCurveViewRange::setZoomNormalize(bool v) { + if (m_zoomNormalize == v) { + return; + } + m_zoomNormalize = v; + if (m_delegate) { + m_delegate->updateZoomButtons(); + } +} + +float InteractiveCurveViewRange::roundLimit(float y, float range, bool isMin) { + /* Floor/ceil to a round number, with a precision depending on the range. + * A range within : | Will have a magnitude : | 3.14 would be floored to : + * [100,1000] | 10 | 0 + * [10,100] | 1 | 3 + * [1,10] | 0.1 | 3.1 */ + float magnitude = std::pow(10.0f, Poincare::IEEE754::exponentBase10(range) - 1.0f); + if (isMin) { + return magnitude * std::floor(y / magnitude); + } + return magnitude * std::ceil(y / magnitude); } void InteractiveCurveViewRange::setXMin(float xMin) { MemoizedCurveViewRange::protectedSetXMin(xMin, k_lowerMaxFloat, k_upperMaxFloat); - notifyRangeChange(); } void InteractiveCurveViewRange::setXMax(float xMax) { MemoizedCurveViewRange::protectedSetXMax(xMax, k_lowerMaxFloat, k_upperMaxFloat); - notifyRangeChange(); } void InteractiveCurveViewRange::setYMin(float yMin) { @@ -40,6 +68,22 @@ void InteractiveCurveViewRange::setYMax(float yMax) { MemoizedCurveViewRange::protectedSetYMax(yMax, k_lowerMaxFloat, k_upperMaxFloat); } +float InteractiveCurveViewRange::yGridUnit() const { + float res = MemoizedCurveViewRange::yGridUnit(); + if (m_zoomNormalize) { + /* When m_zoomNormalize is active, both xGridUnit and yGridUnit will be the + * same. To declutter the X axis, we try a unit twice as large. We check + * that it allows enough graduations on the Y axis, but if the standard + * unit would lead to too many graduations on the X axis, we force the + * larger unit anyways. */ + float numberOfUnits = (yMax() - yMin()) / res; + if (numberOfUnits > k_maxNumberOfXGridUnits || numberOfUnits / 2.f > k_minNumberOfYGridUnits) { + return 2 * res; + } + } + return res; +} + void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { float xMi = xMin(); float xMa = xMax(); @@ -50,131 +94,111 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { } float centerX = std::isnan(x) || std::isinf(x) ? xCenter() : x; float centerY = std::isnan(y) || std::isinf(y) ? yCenter() : y; - float newXMin = centerX*(1.0f-ratio)+ratio*xMi; - float newXMax = centerX*(1.0f-ratio)+ratio*xMa; - m_yAuto = false; - if (!std::isnan(newXMin) && !std::isnan(newXMax)) { - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); + Zoom::SetZoom(ratio, centerX, centerY, &xMi, &xMa, &yMi, &yMa); + if (!std::isnan(xMi) && !std::isnan(xMa)) { + setZoomAuto(false); + m_xRange.setMax(xMa, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMin(xMi, k_lowerMaxFloat, k_upperMaxFloat); } - float newYMin = centerY*(1.0f-ratio)+ratio*yMi; - float newYMax = centerY*(1.0f-ratio)+ratio*yMa; - if (!std::isnan(newYMin) && !std::isnan(newYMax)) { - m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); + if (!std::isnan(yMi) && !std::isnan(yMa)) { + setZoomAuto(false); + m_yRange.setMax(yMa, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMin(yMi, k_lowerMaxFloat, k_upperMaxFloat); } + m_offscreenYAxis *= ratio; + setZoomNormalize(isOrthonormal()); } void InteractiveCurveViewRange::panWithVector(float x, float y) { - m_yAuto = false; if (clipped(xMin() + x, false) != xMin() + x || clipped(xMax() + x, true) != xMax() + x || clipped(yMin() + y, false) != yMin() + y || clipped(yMax() + y, true) != yMax() + y || std::isnan(clipped(xMin() + x, false)) || std::isnan(clipped(xMax() + x, true)) || std::isnan(clipped(yMin() + y, false)) || std::isnan(clipped(yMax() + y, true))) { return; } + if (x != 0.f || y != 0.f) { + setZoomAuto(false); + } m_xRange.setMax(xMax()+x, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(xMin() + x, k_lowerMaxFloat, k_upperMaxFloat); m_yRange.setMax(yMax()+y, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMin() + y, k_lowerMaxFloat, k_upperMaxFloat); } -void InteractiveCurveViewRange::roundAbscissa() { - // Set x range - float newXMin = std::round(xCenter()) - (float)Ion::Display::Width/2.0f; - float newXMax = std::round(xCenter()) + (float)Ion::Display::Width/2.0f-1.0f; - if (std::isnan(newXMin) || std::isnan(newXMax)) { - return; - } - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - setXMin(newXMin); -} - -void InteractiveCurveViewRange::normalize() { +void InteractiveCurveViewRange::normalize(bool forceChangeY) { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ - m_yAuto = false; + + if (isOrthonormal()) { + return; + } + + setZoomAuto(false); + + float newXMin = xMin(), newXMax = xMax(), newYMin = yMin(), newYMax = yMax(); const float unit = std::max(xGridUnit(), yGridUnit()); + const float newXHalfRange = NormalizedXHalfRange(unit); + const float newYHalfRange = NormalizedYHalfRange(unit); + float normalizedYXRatio = newYHalfRange/newXHalfRange; - // Set x range - float newXHalfRange = NormalizedXHalfRange(unit); - float newXMin = xCenter() - newXHalfRange; - float newXMax = xCenter() + newXHalfRange; - if (!std::isnan(newXMin) && !std::isnan(newXMax)) { - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); - } - // Set y range - float newYHalfRange = NormalizedYHalfRange(unit); - float newYMin = yCenter() - newYHalfRange; - float newYMax = yCenter() + newYHalfRange; - if (!std::isnan(newYMin) && !std::isnan(newYMax)) { - m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); - } -} + /* Most of the time, we do not want to shrink, to avoid hiding parts of the + * function. However, when forceChangeY is true, we shrink if the Y range is + * the longer one. */ + bool shrink = forceChangeY && (newYMax - newYMin) / (newXMax - newXMin) > normalizedYXRatio; + Zoom::SetToRatio(normalizedYXRatio, &newXMin, &newXMax, &newYMin, &newYMax, shrink); -void InteractiveCurveViewRange::setTrigonometric() { - m_yAuto = false; - // Set x range - float x = (Preferences::sharedPreferences()->angleUnit() == Preferences::AngleUnit::Degree) ? 600.0f : 10.5f; - m_xRange.setMax(x, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(-x, k_lowerMaxFloat, k_upperMaxFloat); - // Set y range - float y = 1.6f; - m_yRange.setMax(y, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(-y, k_lowerMaxFloat, k_upperMaxFloat); + m_xRange.setMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); + m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); + + /* The range should be close to orthonormal, unless : + * - it has been clipped because the maximum bounds have been reached. + * - the the bounds are too close and of too large a magnitude, leading to + * a drastic loss of significance. */ + assert(isOrthonormal() + || xMin() <= - k_lowerMaxFloat || xMax() >= k_lowerMaxFloat || yMin() <= - k_lowerMaxFloat || yMax() >= k_lowerMaxFloat + || normalizationSignificantBits() <= 0); + setZoomNormalize(isOrthonormal()); } void InteractiveCurveViewRange::setDefault() { if (m_delegate == nullptr) { return; } - if (!m_delegate->defautRangeIsNormalized()) { - m_yAuto = true; - m_xRange.setMax(m_delegate->interestingXHalfRange(), k_lowerMaxFloat, k_upperMaxFloat); - setXMin(-xMax()); - return; + + /* If m_zoomNormalize was left active, xGridUnit() would return the value of + * yGridUnit, even if the ranger were not truly normalized. We use + * setZoomNormalize to refresh the button in case the graph does not end up + * normalized. */ + setZoomNormalize(false); + + // Compute the interesting range + m_delegate->interestingRanges(this); + /* If the horizontal bounds are integers, they are preset values and should + * not be changed. */ + bool isDefaultRange = hasDefaultRange(); + + // Add margins, then round limits. + float newXMin = xMin(), newXMax = xMax(); + if (!isDefaultRange) { + float xRange = xMax() - xMin(); + newXMin = roundLimit(m_delegate->addMargin(xMin(), xRange, false, true), xRange, true); + newXMax = roundLimit(m_delegate->addMargin(xMax(), xRange, false, false), xRange, false); } - - m_yAuto = false; - // Compute the interesting ranges - float a,b,c,d; - m_delegate->interestingRanges(&a, &b, &c, &d); - m_xRange.setMin(a, k_lowerMaxFloat, k_upperMaxFloat); - m_xRange.setMax(b, k_lowerMaxFloat, k_upperMaxFloat); - m_yRange.setMin(c, k_lowerMaxFloat, k_upperMaxFloat); - m_yRange.setMax(d, k_lowerMaxFloat, k_upperMaxFloat); - - // Add margins - float xRange = xMax() - xMin(); - float yRange = yMax() - yMin(); - m_xRange.setMin(m_delegate->addMargin(xMin(), xRange, false, true), k_lowerMaxFloat, k_upperMaxFloat); + m_xRange.setMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); // Use MemoizedCurveViewRange::protectedSetXMax to update xGridUnit - MemoizedCurveViewRange::protectedSetXMax(m_delegate->addMargin(xMax(), xRange, false, false), k_lowerMaxFloat, k_upperMaxFloat); - m_yRange.setMin(m_delegate->addMargin(yMin(), yRange, true, true), k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMax(m_delegate->addMargin(yMax(), yRange, true, false), k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); + float yRange = yMax() - yMin(); + m_yRange.setMin(roundLimit(m_delegate->addMargin(yMin(), yRange, true , true), yRange, true), k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMax(roundLimit(m_delegate->addMargin(yMax(), yRange, true , false), yRange, false), k_lowerMaxFloat, k_upperMaxFloat); - // Normalize the axes, so that a polar circle is displayed as a circle - xRange = xMax() - xMin(); - yRange = yMax() - yMin(); - float xyRatio = xRange/yRange; - - const float unit = std::max(xGridUnit(), yGridUnit()); - const float newXHalfRange = NormalizedXHalfRange(unit); - const float newYHalfRange = NormalizedYHalfRange(unit); - float normalizedXYRatio = newXHalfRange/newYHalfRange; - if (xyRatio < normalizedXYRatio) { - float newXRange = normalizedXYRatio * yRange; - assert(newXRange > xRange); - float delta = (newXRange - xRange) / 2.0f; - m_xRange.setMin(xMin() - delta, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMax(xMax()+delta, k_lowerMaxFloat, k_upperMaxFloat); - } else if (xyRatio > normalizedXYRatio) { - float newYRange = newYHalfRange/newXHalfRange * xRange; - assert(newYRange > yRange); - float delta = (newYRange - yRange) / 2.0f; - m_yRange.setMin(yMin() - delta, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMax(yMax()+delta, k_lowerMaxFloat, k_upperMaxFloat); + if (m_delegate->defaultRangeIsNormalized() || shouldBeNormalized()) { + /* Normalize the axes, so that a polar circle is displayed as a circle. + * If we are displaying cartesian functions with a default range, we want + * the X bounds untouched. */ + normalize(isDefaultRange && !m_delegate->defaultRangeIsNormalized()); } + + setZoomAuto(true); } void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { @@ -186,33 +210,45 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { if (std::fabs(position/range) > k_maxRatioPositionRange) { range = Range1D::defaultRangeLengthFor(position); } - m_xRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); + float newXMax = position + range/2.0f; + if (xMax() != newXMax) { + setZoomAuto(false); + m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetXMin(newXMax - range, k_lowerMaxFloat, k_upperMaxFloat); + } } else { - m_yAuto = false; float range = yMax() - yMin(); if (std::fabs(position/range) > k_maxRatioPositionRange) { range = Range1D::defaultRangeLengthFor(position); } - m_yRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); + float newYMax = position + range/2.0f; + if (yMax() != newYMax) { + setZoomAuto(false); + m_yRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); + } } + + setZoomNormalize(isOrthonormal()); } -void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio) { +void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio, float pixelWidth) { if (!std::isinf(x) && !std::isnan(x)) { const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; if (x < xMin() + leftMargin) { - m_yAuto = false; - const float newXMin = x - leftMargin; + setZoomAuto(false); + /* The panning increment is a whole number of pixels so that the caching + * for cartesian functions is not invalidated. */ + const float newXMin = std::floor((x - leftMargin - xMin()) / pixelWidth) * pixelWidth + xMin(); m_xRange.setMax(newXMin + xRange, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); } const float rightMargin = rightMarginRatio * xRange; if (x > xMax() - rightMargin) { - m_yAuto = false; - m_xRange.setMax(x + rightMargin, k_lowerMaxFloat, k_upperMaxFloat); + setZoomAuto(false); + const float newXMax = std::ceil((x + rightMargin - xMax()) / pixelWidth) * pixelWidth + xMax(); + m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(xMax() - xRange, k_lowerMaxFloat, k_upperMaxFloat); } } @@ -220,24 +256,57 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float yRange = yMax() - yMin(); const float bottomMargin = bottomMarginRatio * yRange; if (y < yMin() + bottomMargin) { - m_yAuto = false; + setZoomAuto(false); const float newYMin = y - bottomMargin; m_yRange.setMax(newYMin + yRange, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); } const float topMargin = topMarginRatio * yRange; if (y > yMax() - topMargin) { - m_yAuto = false; + setZoomAuto(false); m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); } } + + /* Panning to a point greater than the maximum range of 10^8 could make the + * graph not normalized.*/ + setZoomNormalize(isOrthonormal()); } -void InteractiveCurveViewRange::notifyRangeChange() { - if (m_delegate) { - m_delegate->didChangeRange(this); +bool InteractiveCurveViewRange::shouldBeNormalized() const { + float ratio = (yMax() - yMin()) / (xMax() - xMin()); + return ratio >= NormalYXRatio() / k_orthonormalTolerance && ratio <= NormalYXRatio() * k_orthonormalTolerance; +} + +bool InteractiveCurveViewRange::isOrthonormal() const { + int significantBits = normalizationSignificantBits(); + if (significantBits <= 0) { + return false; } + float ratio = (yMax() - yMin() + offscreenYAxis()) / (xMax() - xMin()); + /* The last N (= 23 - significantBits) bits of "ratio" mantissa have become + * insignificant. "tolerance" is the difference between ratio with those N + * bits set to 1, and ratio with those N bits set to 0 ; i.e. a measure of + * the interval in which numbers are indistinguishable from ratio with this + * level of precision. */ + float tolerance = std::pow(2.f, IEEE754::exponent(ratio) - significantBits); + return ratio - tolerance <= NormalYXRatio() && ratio + tolerance >= NormalYXRatio(); +} + +int InteractiveCurveViewRange::normalizationSignificantBits() const { + float xr = std::fabs(xMin()) > std::fabs(xMax()) ? xMax() / xMin() : xMin() / xMax(); + float yr = std::fabs(yMin()) > std::fabs(yMax()) ? yMax() / yMin() : yMin() / yMax(); + /* The subtraction x - y induces a loss of significance of -log2(1-x/y) + * bits. Since normalizing requires computing xMax - xMin and yMax - yMin, + * the ratio of the normalized range will deviate from the Normal ratio. We + * add an extra two lost bits to account for loss of precision from other + * sources. */ + float loss = std::log2(std::min(1.f - xr, 1.f - yr)); + if (loss > 0.f) { + loss = 0.f; + } + return std::floor(loss + 23.f - 2.f); } } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index f6484017a..9a4ae27c2 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -13,15 +13,32 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { public: InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) : MemoizedCurveViewRange(), - m_yAuto(true), - m_delegate(delegate) - {} + m_delegate(nullptr), + m_offscreenYAxis(0.f), + m_zoomAuto(true), + m_zoomNormalize(false) + { + if (delegate) { + setDelegate(delegate); + } + } - void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } - uint32_t rangeChecksum() override; + static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } + /* The method isOrthonormal takes the loss of significance when changing the + * ratio into account. */ + bool isOrthonormal() const; - bool yAuto() const { return m_yAuto; } - void setYAuto(bool yAuto); + void setDelegate(InteractiveCurveViewRangeDelegate * delegate); + + bool zoomAuto() const { return m_zoomAuto; } + void setZoomAuto(bool v); + bool zoomNormalize() const { return m_zoomNormalize; } + void setZoomNormalize(bool v); + float roundLimit(float y, float range, bool isMin); + + // MemoizedCurveViewRange + float xGridUnit() const override { return m_zoomNormalize ? yGridUnit() : MemoizedCurveViewRange::xGridUnit(); } + float yGridUnit() const override; // CurveViewWindow void setXMin(float f) override; @@ -29,19 +46,22 @@ public: void setYMin(float f) override; void setYMax(float f) override; + void setOffscreenYAxis(float f) { m_offscreenYAxis = f; } + // Window void zoom(float ratio, float x, float y); void panWithVector(float x, float y); - virtual void roundAbscissa(); - virtual void normalize(); - virtual void setTrigonometric(); + virtual void normalize(bool forceChangeY = false); virtual void setDefault(); void centerAxisAround(Axis axis, float position); - void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation); + void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); + protected: constexpr static float k_upperMaxFloat = 1E+8f; constexpr static float k_lowerMaxFloat = 9E+7f; constexpr static float k_maxRatioPositionRange = 1E5f; + /* The tolerance is chosen to normalize sqrt(x) */ + constexpr static float k_orthonormalTolerance = 1.78f; static float clipped(float x, bool isMax) { return Range1D::clipped(x, isMax, k_lowerMaxFloat, k_upperMaxFloat); } /* In normalized settings, we put each axis so that 1cm = 2 units. For now, * the screen has size 43.2mm * 57.6mm. @@ -57,10 +77,17 @@ protected: * 2 * 1 unit -> 10.0mm * So normalizedYHalfRange = 43.2mm * 170/240 * 1 unit / 10.0mm */ constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } - bool m_yAuto; + bool shouldBeNormalized() const; + virtual bool hasDefaultRange() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); } + InteractiveCurveViewRangeDelegate * m_delegate; private: - void notifyRangeChange(); + float offscreenYAxis() const override { return m_offscreenYAxis; } + int normalizationSignificantBits() const; + + float m_offscreenYAxis; + bool m_zoomAuto; + bool m_zoomNormalize; }; static_assert(Ion::Display::WidthInTenthOfMillimeter == 576, "Use the new screen width to compute Shared::InteractiveCurveViewRange::NormalizedXHalfRange"); diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp index 1232676ff..01ea2baf5 100644 --- a/apps/shared/interactive_curve_view_range_delegate.cpp +++ b/apps/shared/interactive_curve_view_range_delegate.cpp @@ -1,47 +1,58 @@ #include "interactive_curve_view_range_delegate.h" #include "interactive_curve_view_range.h" -#include -#include +#include "function_store.h" +#include namespace Shared { -bool InteractiveCurveViewRangeDelegate::didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) { - /* When y auto is ticked, top and bottom margins are added to ensure that - * the cursor can be move along the curve, in the current x-range, - * without panning the window. */ - if (!interactiveCurveViewRange->yAuto()) { - return false; +void InteractiveCurveViewRangeDelegate::DefaultInterestingRanges(InteractiveCurveViewRange * range, Poincare::Context * context, FunctionStore * functionStore, float targetRatio) { + constexpr int maxLength = 10; + float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength]; + int length = functionStore->numberOfActiveFunctions(); + + for (int i = 0; i < length; i++) { + ExpiringPointer f = functionStore->modelForRecord(functionStore->activeRecordAtIndex(i)); + f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, targetRatio, context); } - Range yRange = computeYRange(interactiveCurveViewRange); - float max = yRange.max; - float min = yRange.min; - float range = max - min; - if (max < min) { - range = 0.0f; - } - if (interactiveCurveViewRange->yMin() == addMargin(min, range, true, true) && interactiveCurveViewRange->yMax() == addMargin(max, range, true, false)) { - return false; - } - if (min == max) { - // Add same margin on top of / below the curve, to center it on the screen - float step = max != 0.0f ? 2.0f * Range1D::defaultRangeLengthFor(max) : 1.0f; - min = min - step; - max = max + step; - } - if (min == FLT_MAX && max == -FLT_MAX) { - min = -1.0f; - max = 1.0f; - } - range = max - min; - interactiveCurveViewRange->setYMin(addMargin(min, range, true, true)); - interactiveCurveViewRange->setYMax(addMargin(max, range, true, false)); - if (std::isinf(interactiveCurveViewRange->xMin())) { - interactiveCurveViewRange->setYMin(-FLT_MAX); - } - if (std::isinf(interactiveCurveViewRange->xMax())) { - interactiveCurveViewRange->setYMax(FLT_MAX); - } - return true; + + float xMin, xMax, yMin, yMax; + Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax); + Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax); + Poincare::Zoom::SanitizeRange(&xMin, &xMax, &yMin, &yMax, range->NormalYXRatio()); + + range->setXMin(xMin); + range->setXMax(xMax); + range->setYMin(yMin); + range->setYMax(yMax); +} + +float InteractiveCurveViewRangeDelegate::DefaultAddMargin(float x, float range, bool isVertical, bool isMin, float top, float bottom, float left, float right) { + /* The provided min or max range limit y is altered by adding a margin. + * In pixels, the view's height occupied by the vertical range is equal to + * viewHeight - topMargin - bottomMargin. + * Hence one pixel must correspond to + * range / (viewHeight - topMargin - bottomMargin). + * Finally, adding topMargin pixels of margin, say at the top, comes down + * to adding + * range * topMargin / (viewHeight - topMargin - bottomMargin) + * which is equal to + * range * topMarginRatio / ( 1 - topMarginRatio - bottomMarginRatio) + * where + * topMarginRation = topMargin / viewHeight + * bottomMarginRatio = bottomMargin / viewHeight. + * The same goes horizontally. + */ + float topMarginRatio = isVertical ? top : right; + float bottomMarginRatio = isVertical ? bottom : left; + assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct + float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; + float ratio = isMin ? -bottomMarginRatio : topMarginRatio; + /* We want to add slightly more than the required margin, so that + * InteractiveCurveViewRange::panToMakePointVisible does not think a point is + * invisible due to precision problems when checking if it is outside the + * required margin. This is why we add a 1.05f factor. */ + ratio = 1.05f * ratio / ratioDenominator; + return x + ratio * range; } } diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index b9a98b0a3..d21964000 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -1,27 +1,26 @@ #ifndef SHARED_INTERACTIVE_CURVE_VIEW_DELEGATE_H #define SHARED_INTERACTIVE_CURVE_VIEW_DELEGATE_H +#include #include namespace Shared { class InteractiveCurveViewRange; +class FunctionStore; class InteractiveCurveViewRangeDelegate { public: - bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange); - virtual float interestingXMin() const { return -interestingXHalfRange(); } - virtual float interestingXHalfRange() const { return 10.0f; } - virtual bool defautRangeIsNormalized() const { return false; } - virtual void interestingRanges(float * xm, float * xM, float * ym, float * yM) const { assert(false); } + static constexpr float k_defaultXHalfRange = 10.0f; + + static void DefaultInterestingRanges(InteractiveCurveViewRange * range, Poincare::Context * context, FunctionStore * functionStore, float targetRatio); + static float DefaultAddMargin(float x, float range, bool isVertical, bool isMin, float top, float bottom, float left, float right); + + virtual float interestingXMin() const { return -k_defaultXHalfRange; } + virtual bool defaultRangeIsNormalized() const { return false; } + virtual void interestingRanges(InteractiveCurveViewRange * range) { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; -protected: - struct Range { - float min; - float max; - }; -private: - virtual Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) = 0; + virtual void updateZoomButtons() = 0; }; } diff --git a/apps/shared/interval.cpp b/apps/shared/interval.cpp index 1fccb9e81..6b5151308 100644 --- a/apps/shared/interval.cpp +++ b/apps/shared/interval.cpp @@ -23,6 +23,10 @@ void Interval::deleteElementAtIndex(int index) { m_numberOfElements--; } +bool Interval::hasSameParameters(IntervalParameters parameters) { + return (m_parameters.start() == parameters.start() && m_parameters.end() == parameters.end() && m_parameters.step() == parameters.step()); +} + double Interval::element(int i) { assert(i >= 0 && i < numberOfElements()); computeElements(); diff --git a/apps/shared/interval.h b/apps/shared/interval.h index 95c269d68..bf7c97925 100644 --- a/apps/shared/interval.h +++ b/apps/shared/interval.h @@ -23,6 +23,7 @@ public: double m_end; double m_step; }; + bool hasSameParameters(IntervalParameters parameters); double element(int i); IntervalParameters * parameters() { return &m_parameters; } void setParameters(IntervalParameters parameters) { m_parameters = parameters; } @@ -30,8 +31,7 @@ public: void forceRecompute(){ m_needCompute = true;} void reset(); void clear(); - // TODO: decide the max number of elements after optimization - constexpr static int k_maxNumberOfElements = 50; + constexpr static int k_maxNumberOfElements = 101; private: void computeElements(); int m_numberOfElements; diff --git a/apps/shared/interval_parameter_controller.cpp b/apps/shared/interval_parameter_controller.cpp index 2404f4604..11256bfd4 100644 --- a/apps/shared/interval_parameter_controller.cpp +++ b/apps/shared/interval_parameter_controller.cpp @@ -13,7 +13,12 @@ IntervalParameterController::IntervalParameterController(Responder * parentRespo m_intervalCells{}, m_title(I18n::Message::IntervalSet), m_startMessage(I18n::Message::XStart), - m_endMessage(I18n::Message::XEnd) + m_endMessage(I18n::Message::XEnd), + m_confirmPopUpController(Invocation([](void * context, void * sender) { + Container::activeApp()->dismissModalViewController(); + ((IntervalParameterController *)context)->stackController()->pop(); + return true; + }, this)) { for (int i = 0; i < k_totalNumberOfCell; i++) { m_intervalCells[i].setParentResponder(&m_selectableTableView); @@ -87,6 +92,11 @@ bool IntervalParameterController::handleEvent(Ion::Events::Event event) { stackController()->pop(); return true; } + if (event == Ion::Events::Back && !m_interval->hasSameParameters(*SharedTempIntervalParameters())) { + // Open pop-up to confirm discarding values + Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); + return true; + } return false; } diff --git a/apps/shared/interval_parameter_controller.h b/apps/shared/interval_parameter_controller.h index 21c689a3a..0821f033f 100644 --- a/apps/shared/interval_parameter_controller.h +++ b/apps/shared/interval_parameter_controller.h @@ -5,6 +5,7 @@ #include "interval.h" #include "float_parameter_controller.h" #include +#include "discard_pop_up_controller.h" namespace Shared { @@ -33,6 +34,7 @@ private: I18n::Message m_title; I18n::Message m_startMessage; I18n::Message m_endMessage; + DiscardPopUpController m_confirmPopUpController; }; } diff --git a/apps/shared/language_controller.cpp b/apps/shared/language_controller.cpp deleted file mode 100644 index 5c0897f53..000000000 --- a/apps/shared/language_controller.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "language_controller.h" -#include "../global_preferences.h" -#include "../apps_container.h" -#include - -namespace Shared { - -LanguageController::LanguageController(Responder * parentResponder, KDCoordinate verticalMargin) : - ViewController(parentResponder), - m_selectableTableView(this, this, this) -{ - m_selectableTableView.setTopMargin(verticalMargin); - m_selectableTableView.setBottomMargin(verticalMargin); - for (int i = 0; i < I18n::NumberOfLanguages; i++) { - m_cells[i].setMessageFont(KDFont::LargeFont); - } -} - -void LanguageController::resetSelection() { - m_selectableTableView.deselectTable(); - selectCellAtLocation(0, (int)(GlobalPreferences::sharedGlobalPreferences()->language())); -} - -const char * LanguageController::title() { - return I18n::translate(I18n::Message::Language); -} - -View * LanguageController::view() { - return &m_selectableTableView; -} - -void LanguageController::didBecomeFirstResponder() { - Container::activeApp()->setFirstResponder(&m_selectableTableView); -} - -void LanguageController::viewWillAppear() { - ViewController::viewWillAppear(); - resetSelection(); -} - -bool LanguageController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - GlobalPreferences::sharedGlobalPreferences()->setLanguage((I18n::Language)selectedRow()); - /* We need to reload the whole title bar in order to translate both the - * "Settings" title and the degree preference. */ - AppsContainer::sharedAppsContainer()->reloadTitleBarView(); - return true; - } - return false; -} - -int LanguageController::numberOfRows() const { - return I18n::NumberOfLanguages; -} - -KDCoordinate LanguageController::cellHeight() { - return Metric::ParameterCellHeight; -} - -HighlightCell * LanguageController::reusableCell(int index) { - return &m_cells[index]; -} - -int LanguageController::reusableCellCount() const { - return I18n::NumberOfLanguages; -} - -void LanguageController::willDisplayCellForIndex(HighlightCell * cell, int index) { - static_cast(cell)->setMessage(I18n::LanguageNames[index]); -} - -} diff --git a/apps/shared/language_controller.h b/apps/shared/language_controller.h deleted file mode 100644 index a3ab284e2..000000000 --- a/apps/shared/language_controller.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef SHARED_LANGUAGE_CONTROLLER_H -#define SHARED_LANGUAGE_CONTROLLER_H - -#include -#include - -namespace Shared { - -class LanguageController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { -public: - LanguageController(Responder * parentResponder, KDCoordinate verticalMargin); - void resetSelection(); - - View * view() override; - const char * title() override; - void didBecomeFirstResponder() override; - void viewWillAppear() override; - bool handleEvent(Ion::Events::Event event) override; - - int numberOfRows() const override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() const override; - - void willDisplayCellForIndex(HighlightCell * cell, int index) override; - -protected: - SelectableTableView m_selectableTableView; - -private: - MessageTableCell m_cells[I18n::NumberOfLanguages]; -}; - -} - -#endif diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp new file mode 100644 index 000000000..ad7da122b --- /dev/null +++ b/apps/shared/localization_controller.cpp @@ -0,0 +1,200 @@ +#include "localization_controller.h" +#include +#include + +namespace Shared { + +// ContentView +constexpr int LocalizationController::ContentView::k_numberOfCountryWarningLines; + +LocalizationController::ContentView::ContentView(LocalizationController * controller, SelectableTableViewDataSource * dataSource) : + m_controller(controller), + m_selectableTableView(controller, controller, dataSource), + m_countryTitleMessage(KDFont::LargeFont, I18n::Message::Country), + m_borderView(Palette::BackgroundApps) +{ + m_countryTitleMessage.setBackgroundColor(Palette::WallScreen); + m_countryTitleMessage.setAlignment(0.5f, 0.5f); + assert(k_numberOfCountryWarningLines == 2); // textMessages is not overflowed + I18n::Message textMessages[k_numberOfCountryWarningLines] = {I18n::Message::CountryWarning1, I18n::Message::CountryWarning2}; + for (int i = 0; i < k_numberOfCountryWarningLines; i++) { + m_countryWarningLines[i].setBackgroundColor(Palette::WallScreen); + m_countryWarningLines[i].setFont(KDFont::SmallFont); + m_countryWarningLines[i].setAlignment(0.5f, 0.5f); + m_countryWarningLines[i].setMessage(textMessages[i]); + } +} + +int LocalizationController::ContentView::numberOfSubviews() const { + return 1 + m_controller->shouldDisplayTitle() + (k_numberOfCountryWarningLines + 1) * m_controller->shouldDisplayWarning(); +} + +View * LocalizationController::ContentView::subviewAtIndex(int i) { + assert(i < numberOfSubviews()); + /* This relies on the fact that the title is never displayed without the warning. */ + assert((!m_controller->shouldDisplayTitle()) || m_controller->shouldDisplayWarning()); + switch (i) { + case 0: + return &m_selectableTableView; + case 3: + return &m_borderView; + case 4: + return &m_countryTitleMessage; + default: + return &m_countryWarningLines[i-1]; + } +} + +void LocalizationController::ContentView::modeHasChanged() { + layoutSubviews(); + markRectAsDirty(bounds()); +} + +void LocalizationController::ContentView::layoutSubviews(bool force) { + KDCoordinate origin = 0; + if (m_controller->shouldDisplayTitle()) { + origin = layoutTitleSubview(force, Metric::CommonTopMargin + origin); + } + if (m_controller->shouldDisplayWarning()) { + origin = layoutWarningSubview(force, Metric::CommonTopMargin + origin) + Metric::CommonTopMargin; + } + origin = layoutTableSubview(force, origin); + assert(origin <= bounds().height()); +} + +KDCoordinate LocalizationController::ContentView::layoutTitleSubview(bool force, KDCoordinate verticalOrigin) { + KDCoordinate titleHeight = m_countryTitleMessage.font()->glyphSize().height(); + m_countryTitleMessage.setFrame(KDRect(0, verticalOrigin, bounds().width(), titleHeight), force); + return verticalOrigin + titleHeight; +} + +KDCoordinate LocalizationController::ContentView::layoutWarningSubview(bool force, KDCoordinate verticalOrigin) { + assert(k_numberOfCountryWarningLines > 0); + KDCoordinate textHeight = m_countryWarningLines[0].font()->glyphSize().height(); + for (int i = 0; i < k_numberOfCountryWarningLines; i++) { + m_countryWarningLines[i].setFrame(KDRect(0, verticalOrigin, bounds().width(), textHeight), force); + verticalOrigin += textHeight; + } + return verticalOrigin; +} + +KDCoordinate LocalizationController::ContentView::layoutTableSubview(bool force, KDCoordinate verticalOrigin) { + KDCoordinate tableHeight = std::min( + bounds().height() - verticalOrigin, + m_selectableTableView.minimalSizeForOptimalDisplay().height()); + KDCoordinate tableHeightSansMargin = tableHeight - m_selectableTableView.bottomMargin(); + + if (m_controller->shouldDisplayWarning()) { + /* If the top cell is cut, bot not enough to hide part of the text, it will + * appear squashed. To prevent that, we increase the top margin slightly, + * so that the top cell will be cropped in the middle. */ + KDCoordinate rowHeight = m_controller->cellHeight() + Metric::CellSeparatorThickness; + KDCoordinate incompleteCellHeight = tableHeightSansMargin - (tableHeightSansMargin / rowHeight) * rowHeight; + KDCoordinate offset = std::max(0, incompleteCellHeight - rowHeight / 2); + tableHeight -= offset; + verticalOrigin += offset; + + m_borderView.setFrame(KDRect(Metric::CommonLeftMargin, verticalOrigin, bounds().width() - Metric::CommonLeftMargin - Metric::CommonRightMargin, Metric::CellSeparatorThickness), force); + } + m_selectableTableView.setFrame(KDRect(0, verticalOrigin, bounds().width(), tableHeight), force); + return verticalOrigin + tableHeight; +} + +// LocalizationController +constexpr int LocalizationController::k_numberOfCells; + +int LocalizationController::IndexOfCountry(I18n::Country country) { + /* As we want to order the countries alphabetically in the selected language, + * the index of a country in the table is the number of other countries that + * go before it in alphabetical order. */ + int res = 0; + for (int c = 0; c < I18n::NumberOfCountries; c++) { + if (country != static_cast(c) && strcmp(I18n::translate(I18n::CountryNames[static_cast(country)]), I18n::translate(I18n::CountryNames[c])) > 0) { + res += 1; + } + } + return res; +} + +I18n::Country LocalizationController::CountryAtIndex(int i) { + /* This method is called for each country one after the other, so we could + * save a lot of computations by memoizing the IndexInTableOfCountry. + * However, since the number of countries is fairly small, and the country + * menu is unlikely to be used more than once or twice in the device's + * lifespan, we skim on memory usage here.*/ + for (int c = 0; c < I18n::NumberOfCountries; c++) { + I18n::Country country = static_cast(c); + if (i == IndexOfCountry(country)) { + return country; + } + } + assert(false); + return (I18n::Country)0; +} + +LocalizationController::LocalizationController(Responder * parentResponder, KDCoordinate verticalMargin, LocalizationController::Mode mode) : + ViewController(parentResponder), + m_contentView(this, this), + m_mode(mode) +{ + selectableTableView()->setTopMargin((shouldDisplayWarning()) ? 0 : verticalMargin); + selectableTableView()->setBottomMargin(verticalMargin); + for (int i = 0; i < k_numberOfCells; i++) { + m_cells[i].setMessageFont(KDFont::LargeFont); + } +} + +void LocalizationController::resetSelection() { + selectableTableView()->deselectTable(); + selectCellAtLocation(0, indexOfCellToSelectOnReset()); +} + +void LocalizationController::setMode(LocalizationController::Mode mode) { + selectableTableView()->deselectTable(); + m_mode = mode; + selectableTableView()->setTopMargin((shouldDisplayWarning()) ? 0 : selectableTableView()->bottomMargin()); + m_contentView.modeHasChanged(); +} + +int LocalizationController::indexOfCellToSelectOnReset() const { + assert(mode() == Mode::Language); + return static_cast(GlobalPreferences::sharedGlobalPreferences()->language()); +} + +const char * LocalizationController::title() { + if (mode() == Mode::Language) { + return I18n::translate(I18n::Message::Language); + } + assert(mode() == Mode::Country); + return I18n::translate(I18n::Message::Country); +} + +void LocalizationController::viewWillAppear() { + ViewController::viewWillAppear(); + resetSelection(); + selectableTableView()->reloadData(); +} + +bool LocalizationController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (mode() == Mode::Language) { + GlobalPreferences::sharedGlobalPreferences()->setLanguage(static_cast(selectedRow())); + AppsContainer::sharedAppsContainer()->reloadTitleBarView(); + } else { + assert(mode() == Mode::Country); + GlobalPreferences::sharedGlobalPreferences()->setCountry(CountryAtIndex(selectedRow())); + } + return true; + } + return false; +} + +void LocalizationController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (mode() == Mode::Language) { + static_cast(cell)->setMessage(I18n::LanguageNames[index]); + return; + } + assert(mode() == Mode::Country); + static_cast(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); +} +} diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h new file mode 100644 index 000000000..c011df59e --- /dev/null +++ b/apps/shared/localization_controller.h @@ -0,0 +1,80 @@ +#ifndef LOCALIZATION_CONTROLLER_H +#define LOCALIZATION_CONTROLLER_H + +#include +#include +#include + +namespace Shared { + +class LocalizationController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + static int IndexOfCountry(I18n::Country country); + static I18n::Country CountryAtIndex(int i); + + enum class Mode : uint8_t { + Language, + Country + }; + + LocalizationController(Responder * parentResponder, KDCoordinate verticalMargin, Mode mode); + void resetSelection(); + Mode mode() const { return m_mode; } + void setMode(Mode mode); + + virtual int indexOfCellToSelectOnReset() const; + virtual bool shouldDisplayTitle() const = 0; + bool shouldDisplayWarning() const { return mode() == Mode::Country; } + + View * view() override { return &m_contentView; } + const char * title() override; + void didBecomeFirstResponder() override { Container::activeApp()->setFirstResponder(selectableTableView()); } + void viewWillAppear() override; + bool handleEvent(Ion::Events::Event event) override; + + int numberOfRows() const override { return (mode() == Mode::Country) ? I18n::NumberOfCountries : I18n::NumberOfLanguages; } + KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } + HighlightCell * reusableCell(int index) override { return &m_cells[index]; } + int reusableCellCount() const override { return (mode() == Mode::Country) ? I18n::NumberOfCountries : I18n::NumberOfLanguages; } + + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + +protected: + class ContentView : public View { + public: + ContentView(LocalizationController * controller, SelectableTableViewDataSource * dataSource); + + SelectableTableView * selectableTableView() { return &m_selectableTableView; } + void drawRect(KDContext * ctx, KDRect rect) const override { ctx->fillRect(bounds(), Palette::WallScreen); } + void modeHasChanged(); + + private: + constexpr static int k_numberOfCountryWarningLines = 2; + + void layoutSubviews(bool force = false) override; + KDCoordinate layoutTitleSubview(bool force, KDCoordinate verticalOrigin); + KDCoordinate layoutWarningSubview(bool force, KDCoordinate verticalOrigin); + KDCoordinate layoutTableSubview(bool force, KDCoordinate verticalOrigin); + int numberOfSubviews() const override; + View * subviewAtIndex(int i) override; + + LocalizationController * m_controller; + SelectableTableView m_selectableTableView; + MessageTextView m_countryTitleMessage; + MessageTextView m_countryWarningLines[k_numberOfCountryWarningLines]; + SolidColorView m_borderView; + }; + + SelectableTableView * selectableTableView() { return m_contentView.selectableTableView(); } + + ContentView m_contentView; + +private: + static constexpr int k_numberOfCells = I18n::NumberOfLanguages > I18n::NumberOfCountries ? I18n::NumberOfLanguages : I18n::NumberOfCountries; + MessageTableCell m_cells[k_numberOfCells]; + Mode m_mode; +}; + +} + +#endif diff --git a/apps/shared/ok_view.cpp b/apps/shared/ok_view.cpp index 907a3d7d4..7a11f3a56 100644 --- a/apps/shared/ok_view.cpp +++ b/apps/shared/ok_view.cpp @@ -3,26 +3,26 @@ namespace Shared { const uint8_t okMask[OkView::k_okSize][OkView::k_okSize] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xE1, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0xE1, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0xFF, 0xFF}, - {0xFF, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0xFF}, - {0xE1, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0xE1}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x0C, 0xE1, 0xFF, 0x00, 0xFF, 0xFF, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x0C, 0xE1, 0x45, 0x0C, 0xFF, 0x00, 0xFF, 0x0C, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xE1, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xE1, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x0C, 0xE1, 0x45, 0x0C, 0xFF, 0x00, 0xFF, 0x0C, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x0C, 0xE1, 0xFF, 0x00, 0xFF, 0xFF, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0x0C}, - {0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C}, - {0xE1, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0xE1}, - {0xFF, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0xFF}, - {0xFF, 0xFF, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xE1, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0xE1, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0x0C, 0x45, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x45, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xA9, 0x59, 0x20, 0x06, 0x0C, 0x28, 0x59, 0xAA, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0x24, 0x41, 0x97, 0xD8, 0xF5, 0xF5, 0xD8, 0x97, 0x41, 0x24, 0xAD, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFC, 0x76, 0x27, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x27, 0x76, 0xFC, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x76, 0x3D, 0xED, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x3E, 0x76, 0xFF, 0xFF}, + {0xFF, 0xAB, 0x26, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x27, 0xAB, 0xFF}, + {0xF9, 0x1D, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC2, 0x1C, 0xF9}, + {0xA4, 0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, 0xA3}, + {0x54, 0x98, 0xFF, 0xFF, 0xFF, 0xEC, 0x4C, 0x06, 0x3E, 0xE1, 0xFF, 0x00, 0xFF, 0xE6, 0x00, 0xFF, 0xFF, 0xFF, 0x98, 0x53}, + {0x1F, 0xD8, 0xFF, 0xFF, 0xFF, 0x5D, 0x64, 0xEF, 0x73, 0x53, 0xFF, 0x00, 0xE6, 0x2A, 0x7D, 0xFF, 0xFF, 0xFF, 0xD9, 0x1F}, + {0x07, 0xF5, 0xFF, 0xFF, 0xFF, 0x0F, 0xE9, 0xFF, 0xE8, 0x0C, 0xFF, 0x00, 0x2A, 0x69, 0xFA, 0xFF, 0xFF, 0xFF, 0xF5, 0x06}, + {0x0D, 0xEE, 0xFF, 0xFF, 0xFF, 0x0F, 0xE9, 0xFF, 0xE8, 0x0C, 0xFF, 0x00, 0x7D, 0x29, 0xC4, 0xFF, 0xFF, 0xFF, 0xF5, 0x06}, + {0x26, 0xD4, 0xFF, 0xFF, 0xFF, 0x5E, 0x62, 0xEE, 0x73, 0x53, 0xFF, 0x00, 0xFF, 0x7E, 0x6F, 0xFF, 0xFF, 0xFF, 0xD9, 0x1F}, + {0x54, 0x98, 0xFF, 0xFF, 0xFF, 0xEC, 0x4D, 0x07, 0x3F, 0xE2, 0xFF, 0x00, 0xFF, 0xD2, 0x00, 0xFF, 0xFF, 0xFF, 0x98, 0x53}, + {0xA4, 0x43, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x43, 0xA3}, + {0xF9, 0x1D, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x1C, 0xF8}, + {0xFF, 0xAA, 0x28, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x27, 0xAA, 0xFF}, + {0xFF, 0xFF, 0x75, 0x3E, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEE, 0x3E, 0x75, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFC, 0x6C, 0x27, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xBC, 0x27, 0x6B, 0xFC, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xAC, 0x24, 0x43, 0x98, 0xDA, 0xF6, 0xF0, 0xD4, 0x98, 0x43, 0x24, 0xAB, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xA4, 0x53, 0x20, 0x05, 0x05, 0x20, 0x53, 0xA4, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, }; void OkView::drawRect(KDContext * ctx, KDRect rect) const { diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 774547e17..6c4eb0a77 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -1,6 +1,7 @@ #ifndef SHARED_POINCARE_HELPERS_H #define SHARED_POINCARE_HELPERS_H +#include #include #include #include @@ -53,33 +54,38 @@ template inline T ApproximateToScalar(const char * text, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); - return Poincare::Expression::ApproximateToScalar(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); + return Poincare::Expression::ApproximateToScalar(text, context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), symbolicComputation); } inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); - return Poincare::Expression::ParseAndSimplify(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); + return Poincare::Expression::ParseAndSimplify(text, context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), symbolicComputation); } inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = Poincare::ExpressionNode::UnitConversion::Default) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); - *e = e->simplify(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation, unitConversion)); + *e = e->simplify(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), target, symbolicComputation, unitConversion)); } inline void Reduce(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = Poincare::ExpressionNode::UnitConversion::Default) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); - *e = e->reduce(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation, unitConversion)); + *e = e->reduce(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), target, symbolicComputation, unitConversion)); +} + +inline void ReduceAndRemoveUnit(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::Expression * unit, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = Poincare::ExpressionNode::UnitConversion::Default) { + PoincareHelpers::Reduce(e, context, target, symbolicComputation, unitConversion); + *e = e->removeUnit(unit); } inline void ParseAndSimplifyAndApproximate(const char * text, Poincare::Expression * simplifiedExpression, Poincare::Expression * approximateExpression, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); - Poincare::Expression::ParseAndSimplifyAndApproximate(text, simplifiedExpression, approximateExpression, context, complexFormat, preferences->angleUnit(), symbolicComputation); + Poincare::Expression::ParseAndSimplifyAndApproximate(text, simplifiedExpression, approximateExpression, context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), symbolicComputation); } inline typename Poincare::Coordinate2D NextMinimum(const Poincare::Expression e, const char * symbol, double start, double step, double max, Poincare::Context * context) { @@ -107,6 +113,8 @@ inline typename Poincare::Coordinate2D NextIntersection(const Poincare:: return e.nextIntersection(symbol, start, step, max, context, complexFormat, preferences->angleUnit(), expression); } +inline bool equalOrBothNan(double a, double b) { return a == b || (std::isnan(a) && std::isnan(b)); } + } } diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index 1557e82b6..bd1b7a3b9 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -3,6 +3,7 @@ #include #include +#include #if __EMSCRIPTEN__ #include @@ -18,8 +19,8 @@ class __attribute__((packed)) Range1D final { public: /* If m_min and m_max are too close, we cannot divide properly the range by * the number of pixels, which creates a drawing problem. */ - constexpr static float k_minFloat = 1E-4f; - constexpr static float k_default = 10.0f; + constexpr static float k_minFloat = Poincare::Zoom::k_minimalRangeLength; + constexpr static float k_default = Poincare::Zoom::k_defaultHalfRange; Range1D(float min = -k_default, float max = k_default) : m_min(min), m_max(max) @@ -32,10 +33,10 @@ public: static float defaultRangeLengthFor(float position); private: #if __EMSCRIPTEN__ - // See comment about emscripten alignement in Shared::Function::RecordDataBuffer + // See comment about emscripten alignment in Shared::Function::RecordDataBuffer static_assert(sizeof(emscripten_align1_short) == sizeof(uint16_t), "emscripten_align1_short should have the same size as uint16_t"); - emscripten_align1_float m_min __attribute__((packed)); - emscripten_align1_float m_max __attribute__((packed)); + emscripten_align1_float m_min; + emscripten_align1_float m_max; #else float m_min; float m_max; diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index f8e180649..3f05c4dcc 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -9,19 +9,21 @@ RangeParameterController::RangeParameterController(Responder * parentResponder, FloatParameterController(parentResponder), m_interactiveRange(interactiveRange), m_tempInteractiveRange(*interactiveRange), - m_xRangeCells{}, - m_yRangeCells{}, - m_yAutoCell(I18n::Message::YAuto) + m_rangeCells{}, + m_confirmPopUpController(Invocation([](void * context, void * sender) { + Container::activeApp()->dismissModalViewController(); + ((RangeParameterController *)context)->stackController()->pop(); + return true; + }, this)) { - for (int i = 0; i < k_numberOfEditableTextCell; i++) { - m_xRangeCells[i].setParentResponder(&m_selectableTableView); - m_xRangeCells[i].textField()->setDelegates(inputEventHandlerDelegate, this); - } - for (int i = 0; i < k_numberOfConvertibleTextCell; i++) { - m_yRangeCells[i].setParentResponder(&m_selectableTableView); - m_yRangeCells[i].setInteractiveCurveViewRange(&m_tempInteractiveRange); - m_yRangeCells[i].textField()->setDelegates(inputEventHandlerDelegate, this); + for (int i = 0; i < k_numberOfTextCell; i++) { + m_rangeCells[i].setParentResponder(&m_selectableTableView); + m_rangeCells[i].textField()->setDelegates(inputEventHandlerDelegate, this); } + int margin = Metric::CommonLargeMargin; + m_selectableTableView.setTopMargin(margin); + m_selectableTableView.setBottomMargin(margin); + static_cast(m_selectableTableView.decorator())->verticalBar()->setMargin(margin); } const char * RangeParameterController::title() { @@ -29,57 +31,29 @@ const char * RangeParameterController::title() { } int RangeParameterController::numberOfRows() const { - return k_numberOfTextCell+2; -} - -int RangeParameterController::typeAtLocation(int i, int j) { - if (j == numberOfRows()-1) { - return 0; - } - if (j >= 0 && j < 2) { - return 1; - } - if (j == 2) { - return 2; - } - return 3; + return k_numberOfTextCell+1; } void RangeParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (index == numberOfRows()-1) { return; } - if (index == 2) { - SwitchView * switchView = (SwitchView *)m_yAutoCell.accessoryView(); - switchView->setState(m_tempInteractiveRange.yAuto()); - return; - } MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *)cell; - I18n::Message labels[k_numberOfTextCell+1] = {I18n::Message::XMin, I18n::Message::XMax, I18n::Message::Default, I18n::Message::YMin, I18n::Message::YMax}; + I18n::Message labels[k_numberOfTextCell] = {I18n::Message::XMin, I18n::Message::XMax, I18n::Message::YMin, I18n::Message::YMax}; myCell->setMessage(labels[index]); - KDColor yColor = m_tempInteractiveRange.yAuto() ? Palette::SecondaryText : Palette::PrimaryText; - KDColor colors[k_numberOfTextCell+1] = {Palette::PrimaryText, Palette::PrimaryText, Palette::PrimaryText, yColor, yColor}; - myCell->setTextColor(colors[index]); + myCell->setTextColor(Palette::PrimaryText); FloatParameterController::willDisplayCellForIndex(cell, index); } -bool RangeParameterController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { - if (FloatParameterController::textFieldDidFinishEditing(textField, text, event)) { - m_selectableTableView.reloadData(); - return true; - } - return false; -} - void RangeParameterController::setRange(InteractiveCurveViewRange * range){ m_interactiveRange = range; m_tempInteractiveRange = *range; } bool RangeParameterController::handleEvent(Ion::Events::Event event) { - if (activeCell() == 2 && (event == Ion::Events::OK || event == Ion::Events::EXE)) { - m_tempInteractiveRange.setYAuto(!m_tempInteractiveRange.yAuto()); - m_selectableTableView.reloadData(); + if (event == Ion::Events::Back && m_interactiveRange->rangeChecksum() != m_tempInteractiveRange.rangeChecksum()) { + // Open pop-up to confirm discarding values + Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); return true; } return FloatParameterController::handleEvent(event); @@ -88,47 +62,33 @@ bool RangeParameterController::handleEvent(Ion::Events::Event event) { float RangeParameterController::parameterAtIndex(int parameterIndex) { ParameterGetterPointer getters[k_numberOfTextCell] = {&InteractiveCurveViewRange::xMin, &InteractiveCurveViewRange::xMax, &InteractiveCurveViewRange::yMin, &InteractiveCurveViewRange::yMax}; - int index = parameterIndex > 2 ? parameterIndex - 1 : parameterIndex; - return (m_tempInteractiveRange.*getters[index])(); + return (m_tempInteractiveRange.*getters[parameterIndex])(); } bool RangeParameterController::setParameterAtIndex(int parameterIndex, float f) { ParameterSetterPointer setters[k_numberOfTextCell] = {&InteractiveCurveViewRange::setXMin, &InteractiveCurveViewRange::setXMax, &InteractiveCurveViewRange::setYMin, &InteractiveCurveViewRange::setYMax}; - int index = parameterIndex > 2 ? parameterIndex - 1 : parameterIndex; - (m_tempInteractiveRange.*setters[index])(f); + (m_tempInteractiveRange.*setters[parameterIndex])(f); return true; } HighlightCell * RangeParameterController::reusableParameterCell(int index, int type) { - if (type == 2) { - assert(index == 0); - return &m_yAutoCell; - } - if (type == 1) { - assert(index >= 0); - assert(index < k_numberOfEditableTextCell); - return &m_xRangeCells[index]; - } - assert(index >= 0); - assert(index < k_numberOfConvertibleTextCell); - return &m_yRangeCells[index]; + assert(type == 1); + assert(index >= 0 && index < k_numberOfTextCell); + return m_rangeCells + index; } int RangeParameterController::reusableParameterCellCount(int type) { - if (type == 2) { - return 1; - } - if (type == 1) { - return k_numberOfEditableTextCell; - } - return k_numberOfConvertibleTextCell; + assert(type == 1); + return k_numberOfTextCell; } void RangeParameterController::buttonAction() { *m_interactiveRange = m_tempInteractiveRange; - StackViewController * stack = stackController(); - stack->pop(); + m_interactiveRange->setZoomAuto(false); + m_interactiveRange->setZoomNormalize(m_interactiveRange->isOrthonormal()); + + FloatParameterController::buttonAction(); } } diff --git a/apps/shared/range_parameter_controller.h b/apps/shared/range_parameter_controller.h index a2006f284..52e75a6c5 100644 --- a/apps/shared/range_parameter_controller.h +++ b/apps/shared/range_parameter_controller.h @@ -4,6 +4,7 @@ #include #include "interactive_curve_view_range.h" #include "float_parameter_controller.h" +#include "discard_pop_up_controller.h" namespace Shared { @@ -12,39 +13,21 @@ public: RangeParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, InteractiveCurveViewRange * interactiveCurveViewRange); const char * title() override; int numberOfRows() const override; - int typeAtLocation(int i, int j) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; - bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - bool handleEvent(Ion::Events::Event event) override; void setRange(InteractiveCurveViewRange * range); + bool handleEvent(Ion::Events::Event event) override; TELEMETRY_ID("Range"); private: - class MessageTableCellWithConvertibleEditableText : public MessageTableCellWithEditableText { - public: - Responder * responder() override { - if (m_interactiveRange->yAuto()) { - return nullptr; - } else { - return this; - } - } - void setInteractiveCurveViewRange(InteractiveCurveViewRange * interactiveCurveViewRange) { m_interactiveRange = interactiveCurveViewRange; } - private: - InteractiveCurveViewRange * m_interactiveRange; - }; HighlightCell * reusableParameterCell(int index, int type) override; int reusableParameterCellCount(int type) override; float parameterAtIndex(int index) override; bool setParameterAtIndex(int parameterIndex, float f) override; void buttonAction() override; - constexpr static int k_numberOfEditableTextCell = 2; - constexpr static int k_numberOfConvertibleTextCell = 2; - constexpr static int k_numberOfTextCell = k_numberOfEditableTextCell+k_numberOfConvertibleTextCell; + constexpr static int k_numberOfTextCell = 4; InteractiveCurveViewRange * m_interactiveRange; InteractiveCurveViewRange m_tempInteractiveRange; - MessageTableCellWithEditableText m_xRangeCells[k_numberOfEditableTextCell]; - MessageTableCellWithConvertibleEditableText m_yRangeCells[k_numberOfConvertibleTextCell]; - MessageTableCellWithSwitch m_yAutoCell; + MessageTableCellWithEditableText m_rangeCells[k_numberOfTextCell]; + DiscardPopUpController m_confirmPopUpController; }; } diff --git a/apps/sequence/sequence.cpp b/apps/shared/sequence.cpp similarity index 62% rename from apps/sequence/sequence.cpp rename to apps/shared/sequence.cpp index 64643ace1..20ffb0ec6 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -1,5 +1,5 @@ #include "sequence.h" -#include "cache_context.h" +#include "sequence_cache_context.h" #include "sequence_store.h" #include #include @@ -7,27 +7,52 @@ #include #include #include +#include +#include +#include #include "../shared/poincare_helpers.h" #include #include #include -using namespace Shared; using namespace Poincare; -namespace Sequence { +namespace Shared { I18n::Message Sequence::parameterMessageName() const { return I18n::Message::N; } -void Sequence::tidy() { - m_definition.tidyName(); - Function::tidy(); // m_definitionName.tidy() - m_firstInitialCondition.tidy(); - m_firstInitialCondition.tidyName(); - m_secondInitialCondition.tidy(); - m_secondInitialCondition.tidyName(); +int Sequence::nameWithArgument(char * buffer, size_t bufferSize) { + int seqNameSize = name(buffer, bufferSize); + assert(seqNameSize > 0); + size_t result = seqNameSize; + assert(result <= bufferSize); + buffer[result++] = '('; + assert(result <= bufferSize); + assert(UTF8Decoder::CharSizeOfCodePoint(symbol()) <= 2); + result += UTF8Decoder::CodePointToChars(symbol(), buffer+result, bufferSize-result); + assert(result <= bufferSize); + result += strlcpy(buffer+result, ")", bufferSize-result); + return result; +} + +int Sequence::nameWithArgumentAndType(char * buffer, size_t bufferSize) { + int result = nameWithArgument(buffer, bufferSize); + assert(result >= 1); + int offset = result - 1; + switch (type()) + { + case Type::SingleRecurrence: + result += strlcpy(buffer+offset, "+1)", bufferSize-offset); + break; + case Type::DoubleRecurrence: + result += strlcpy(buffer+offset, "+2)", bufferSize-offset); + break; + default: + break; + } + return result; } Sequence::Type Sequence::type() const { @@ -46,6 +71,7 @@ void Sequence::setType(Type t) { setInitialRank(0); } recordData()->setType(t); + m_definition.tidyName(); tidy(); /* Reset all contents */ switch (t) { @@ -107,18 +133,74 @@ bool Sequence::isEmpty() { (type == Type::SingleRecurrence || data->initialConditionSize(1) == 0))); } +bool Sequence::badlyReferencesItself(Context * context) { + Expression e = expressionReduced(context); + bool value = e.hasExpression([](Expression e, const void * sequencePointer) { + if (e.type() != ExpressionNode::Type::Sequence) { + return false; + } + Sequence * seq = (Sequence *)(sequencePointer); + const char * symbolName = static_cast(e).name(); + /* symbolName is either u, v or w while seq->fullName has the extention .seq + * at the end. Therefore we cannot use strcmp on the two strings. We just + * want to check if the first char are identical*/ + if (strncmp(symbolName, seq->fullName(), strlen(symbolName)) == 0) { + /* The expression of the sequence contains a reference to itself. + * We must check if the sequence can be calculated before continuing + * If the sequence is of explicit type, it cannot reference itself. + * If the sequence is of SingleRecurrent type, it can be defined by: + * u(initialRank and u(n). + * If the sequence is of DoubleRecurrent type, it can be defined by: + * u(initialRank), u(initialRank+1), u(n) and u(n+1). + * In any other case, the value of the sequence cannot be computed. + * We therefore return NAN. */ + Expression rank = e.childAtIndex(0); + if (seq->type() == Sequence::Type::Explicit || + (!(rank.isIdenticalTo(Rational::Builder(seq->initialRank())) || rank.isIdenticalTo(Symbol::Builder(UCodePointUnknown))) && + (seq->type() == Sequence::Type::SingleRecurrence || (seq->type() == Sequence::Type::DoubleRecurrence && !(rank.isIdenticalTo(Rational::Builder(seq->initialRank()+1)) || rank.isIdenticalTo(Addition::Builder(Symbol::Builder(UCodePointUnknown), Rational::Builder(1)))))))) + { + return true; + } + } + return false; + }, reinterpret_cast(this)); + return value; +} + template T Sequence::templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const { T n = std::round(x); int sequenceIndex = SequenceStore::sequenceIndexForName(fullName()[0]); if (sqctx->iterateUntilRank(n)) { - return sqctx->valueOfSequenceAtPreviousRank(sequenceIndex, 0); + return sqctx->valueOfCommonRankSequenceAtPreviousRank(sequenceIndex, 0); } return NAN; } template -T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { +T Sequence::valueAtRank(int n, SequenceContext *sqctx) { + if (n < 0 || badlyReferencesItself(sqctx)) { + return NAN; + } + int sequenceIndex = SequenceStore::sequenceIndexForName(fullName()[0]); + if (sqctx->independentSequenceRank(sequenceIndex) > n || sqctx->independentSequenceRank(sequenceIndex) < 0) { + // Reset cache indexes and cache values + sqctx->setIndependentSequenceRank(-1, sequenceIndex); + for (int i = 0 ; i < MaxRecurrenceDepth+1; i++) { + sqctx->setIndependentSequenceValue(NAN, sequenceIndex, i); + } + } + while(sqctx->independentSequenceRank(sequenceIndex) < n) { + sqctx->stepSequenceAtIndex(sequenceIndex); + } + /* In case we have sqctx->independentSequenceRank(sequenceIndex) = n, we can return the + * value */ + T value = sqctx->independentSequenceValue(sequenceIndex, 0); + return value; +} + +template +T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIndex) const { if (n < initialRank() || n < 0) { return NAN; } @@ -127,12 +209,29 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { char unknownN[bufferSize]; Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); - CacheContext ctx = CacheContext(sqctx); + SequenceCacheContext ctx = SequenceCacheContext(sqctx); // Hold values u(n), u(n-1), u(n-2), v(n), v(n-1), v(n-2)... T values[MaxNumberOfSequences][MaxRecurrenceDepth+1]; + + /* In case we step only one sequence to the next step, the data stored in + * values is not necessarily u(n), u(n-1).... Indeed, since the indexes are + * independent, if the index for u is 3 but the one for v is 5, value will + * hold u(3), u(2), u(1) | v(5), v(4), v(3). Therefore, the calculation will + * be wrong if they relay on a symbol such as u(n). To prevent this, we align + * all the values around the index of the sequence we are stepping. */ + int independentRank = sqctx->independentSequenceRank(sequenceIndex); for (int i = 0; i < MaxNumberOfSequences; i++) { - for (int j = 0; j < MaxRecurrenceDepth+1; j++) { - values[i][j] = sqctx->valueOfSequenceAtPreviousRank(i, j); + if (sequenceIndex != -1 && sqctx->independentSequenceRank(i) != independentRank) { + int offset = independentRank - sqctx->independentSequenceRank(i); + if (offset != 0) { + for (int j = MaxRecurrenceDepth; j >= 0; j--) { + values[i][j] = j-offset < 0 || j-offset > MaxRecurrenceDepth ? NAN : sqctx->independentSequenceValue(i, j-offset); + } + } + } else { + for (int j = 0; j < MaxRecurrenceDepth+1; j++) { + values[i][j] = sequenceIndex != -1 ? sqctx->independentSequenceValue(i, j) : sqctx->valueOfCommonRankSequenceAtPreviousRank(i, j); + } } } // Hold symbols u(n), u(n+1), v(n), v(n+1), w(n), w(n+1) @@ -144,7 +243,6 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { symbols[i][j] = Symbol::Builder(name[j], strlen(name[j])); } } - switch (type()) { case Type::Explicit: { @@ -157,7 +255,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { case Type::SingleRecurrence: { if (n == initialRank()) { - return PoincareHelpers::ApproximateToScalar(firstInitialConditionExpressionReduced(sqctx), sqctx); + return PoincareHelpers::ApproximateWithValueForSymbol(firstInitialConditionExpressionReduced(sqctx), unknownN, (T)NAN, &ctx); } for (int i = 0; i < MaxNumberOfSequences; i++) { // Set in context u(n) = u(n-1) and u(n+1) = u(n) for all sequences @@ -169,10 +267,10 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { default: { if (n == initialRank()) { - return PoincareHelpers::ApproximateToScalar(firstInitialConditionExpressionReduced(sqctx), sqctx); + return PoincareHelpers::ApproximateWithValueForSymbol(firstInitialConditionExpressionReduced(sqctx), unknownN, (T)NAN, &ctx); } if (n == initialRank()+1) { - return PoincareHelpers::ApproximateToScalar(secondInitialConditionExpressionReduced(sqctx), sqctx); + return PoincareHelpers::ApproximateWithValueForSymbol(secondInitialConditionExpressionReduced(sqctx), unknownN, (T)NAN, &ctx); } for (int i = 0; i < MaxNumberOfSequences; i++) { // Set in context u(n) = u(n-2) and u(n+1) = u(n-1) for all sequences @@ -205,6 +303,15 @@ Expression Sequence::sumBetweenBounds(double start, double end, Poincare::Contex return Float::Builder(result); } +void Sequence::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, float targetRatio, Poincare::Context * context) const { + Poincare::Zoom::ValueAtAbscissa evaluation = [](float x, Poincare::Context * context, const void * auxiliary) { + return static_cast(static_cast(auxiliary)->initialRank()); + }; + Poincare::Zoom::FullRange(evaluation, 0, 1, 1, xMin, xMax, context, this); + *xMax += Poincare::Zoom::k_defaultHalfRange; + protectedFullRangeForDisplay(*xMin, *xMax, 1.f, yMin, yMax, context, false); +} + Sequence::RecordDataBuffer * Sequence::recordData() const { assert(!isNull()); Ion::Storage::Record::Data d = value(); @@ -293,7 +400,9 @@ void Sequence::InitialConditionModel::buildName(Sequence * sequence) { template double Sequence::templatedApproximateAtAbscissa(double, SequenceContext*) const; template float Sequence::templatedApproximateAtAbscissa(float, SequenceContext*) const; -template double Sequence::approximateToNextRank(int, SequenceContext*) const; -template float Sequence::approximateToNextRank(int, SequenceContext*) const; +template double Sequence::approximateToNextRank(int, SequenceContext*, int) const; +template float Sequence::approximateToNextRank(int, SequenceContext*, int) const; +template double Sequence::valueAtRank(int, SequenceContext *); +template float Sequence::valueAtRank(int, SequenceContext *); } diff --git a/apps/sequence/sequence.h b/apps/shared/sequence.h similarity index 88% rename from apps/sequence/sequence.h rename to apps/shared/sequence.h index d8e2a5f6d..b20eb644b 100644 --- a/apps/sequence/sequence.h +++ b/apps/shared/sequence.h @@ -1,5 +1,5 @@ -#ifndef SEQUENCE_SEQUENCE_H -#define SEQUENCE_SEQUENCE_H +#ifndef APPS_SHARED_SEQUENCE_H +#define APPS_SHARED_SEQUENCE_H #include "../shared/function.h" #include "sequence_context.h" @@ -9,7 +9,7 @@ #include #endif -namespace Sequence { +namespace Shared { /* WARNING: after calling setType, setInitialRank, setContent, setFirstInitialConditionContent * or setSecondInitialConditionContent, the sequence context needs to @@ -29,7 +29,8 @@ public: } I18n::Message parameterMessageName() const override; CodePoint symbol() const override { return 'n'; } - void tidy() override; + int nameWithArgument(char * buffer, size_t bufferSize) override; + int nameWithArgumentAndType(char * buffer, size_t bufferSize); // MetaData getters Type type() const; int initialRank() const; @@ -58,6 +59,8 @@ public: Poincare::Layout nameLayout(); bool isDefined() override; bool isEmpty() override; + bool hasValidExpression(Poincare::Context * context) { return m_definition.hasValidExpression() && !badlyReferencesItself(context); } + bool badlyReferencesItself(Poincare::Context * context); // Approximation Poincare::Coordinate2D evaluateXYAtParameter(float x, Poincare::Context * context) const override { return Poincare::Coordinate2D(x, templatedApproximateAtAbscissa(x, static_cast(context))); @@ -65,17 +68,22 @@ public: Poincare::Coordinate2D evaluateXYAtParameter(double x, Poincare::Context * context) const override { return Poincare::Coordinate2D(x,templatedApproximateAtAbscissa(x, static_cast(context))); } - template T approximateToNextRank(int n, SequenceContext * sqctx) const; + template T approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIndex = -1) const; + template T valueAtRank(int n, SequenceContext * sqctx); Poincare::Expression sumBetweenBounds(double start, double end, Poincare::Context * context) const override; constexpr static int k_initialRankNumberOfDigits = 3; // m_initialRank is capped by 999 + + //Range + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, float targetRatio, Poincare::Context * context) const override; + private: constexpr static const KDFont * k_layoutFont = KDFont::LargeFont; /* RecordDataBuffer is the layout of the data buffer of Record * representing a Sequence. See comment in * Shared::Function::RecordDataBuffer about packing. */ - class RecordDataBuffer : public Shared::Function::RecordDataBuffer { + class __attribute__((packed)) RecordDataBuffer : public Shared::Function::RecordDataBuffer { public: RecordDataBuffer(KDColor color) : Shared::Function::RecordDataBuffer(color), @@ -100,11 +108,11 @@ private: Type m_type; uint8_t m_initialRank; #if __EMSCRIPTEN__ - // See comment about emscripten alignement in Shared::Function::RecordDataBuffer + // See comment about emscripten alignment in Shared::Function::RecordDataBuffer static_assert(sizeof(emscripten_align1_short) == sizeof(uint16_t), "emscripten_align1_short should have the same size as uint16_t"); - emscripten_align1_short m_initialConditionSizes[2] __attribute__((packed)); + emscripten_align1_short m_initialConditionSizes[2]; #else - uint16_t m_initialConditionSizes[2] __attribute__((packed)); + uint16_t m_initialConditionSizes[2]; #endif }; diff --git a/apps/shared/sequence_cache_context.cpp b/apps/shared/sequence_cache_context.cpp new file mode 100644 index 000000000..92842203a --- /dev/null +++ b/apps/shared/sequence_cache_context.cpp @@ -0,0 +1,80 @@ +#include "sequence_cache_context.h" +#include "sequence.h" +#include "sequence_store.h" +#include "poincare_helpers.h" +#include +#include +#include +#include + +using namespace Poincare; + +namespace Shared { + +template +SequenceCacheContext::SequenceCacheContext(SequenceContext * sequenceContext) : + ContextWithParent(sequenceContext), + m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}}, + m_sequenceContext(sequenceContext) +{ +} + +template +const Expression SequenceCacheContext::expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { + // [u|v|w](n(+1)?) + if (symbol.type() == ExpressionNode::Type::Sequence) { + int index = nameIndexForSymbol(const_cast(static_cast(symbol))); + Expression rank = symbol.childAtIndex(0).clone(); + if (rank.isIdenticalTo(Symbol::Builder(UCodePointUnknown))) { + return Float::Builder(m_values[index][0]); + } if (rank.isIdenticalTo(Addition::Builder(Symbol::Builder(UCodePointUnknown), Rational::Builder(1)))) { + return Float::Builder(m_values[index][1]); + } + Ion::Storage::Record record = m_sequenceContext->sequenceStore()->recordAtIndex(index); + if (!record.isNull()) { + Sequence * seq = m_sequenceContext->sequenceStore()->modelForRecord(record); + rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(unknownSymbolValue)); + T n = PoincareHelpers::ApproximateToScalar(rank, this); + // In case the sequence referenced is not defined or if the rank is not an int, return NAN + if (seq->fullName() != nullptr) { + if (std::floor(n) == n) { + Expression sequenceExpression = seq->expressionReduced(this); + if (seq->hasValidExpression(this)) { + return Float::Builder(seq->valueAtRank(n, m_sequenceContext)); + } + } + } + } else { + return Float::Builder(NAN); + } + } + return ContextWithParent::expressionForSymbolAbstract(symbol, clone); +} + +template +void SequenceCacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol) { + m_values[nameIndexForSymbol(symbol)][rankIndexForSymbol(symbol)] = value; +} + +template +int SequenceCacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { + assert(symbol.name()[0] >= 'u' && symbol.name()[0] <= 'w'); // [u|v|w] + char name = symbol.name()[0]; + assert(name >= SequenceStore::k_sequenceNames[0][0] && name <= SequenceStore::k_sequenceNames[MaxNumberOfSequences-1][0]); // u, v or w + return name - 'u'; +} + +template +int SequenceCacheContext::rankIndexForSymbol(const Poincare::Symbol & symbol) { + assert(strcmp(symbol.name()+1, "(n)") == 0 || strcmp(symbol.name()+1, "(n+1)") == 0); // u(n) or u(n+1) + if (symbol.name()[3] == ')') { // (n) + return 0; + } + // (n+1) + return 1; +} + +template class SequenceCacheContext; +template class SequenceCacheContext; + +} diff --git a/apps/sequence/cache_context.h b/apps/shared/sequence_cache_context.h similarity index 57% rename from apps/sequence/cache_context.h rename to apps/shared/sequence_cache_context.h index 37c6200e5..a3495f512 100644 --- a/apps/sequence/cache_context.h +++ b/apps/shared/sequence_cache_context.h @@ -1,23 +1,24 @@ -#ifndef SEQUENCE_CACHE_CONTEXT_H -#define SEQUENCE_CACHE_CONTEXT_H +#ifndef SHARED_SEQUENCE_CACHE_CONTEXT_H +#define SHARED_SEQUENCE_CACHE_CONTEXT_H #include #include #include #include "sequence_context.h" -namespace Sequence { +namespace Shared { template -class CacheContext : public Poincare::ContextWithParent { +class SequenceCacheContext : public Poincare::ContextWithParent { public: - CacheContext(Poincare::Context * parentContext); - const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; + SequenceCacheContext(SequenceContext * sequenceContext); + const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); T m_values[MaxNumberOfSequences][MaxRecurrenceDepth]; + SequenceContext * m_sequenceContext; }; } diff --git a/apps/shared/sequence_context.cpp b/apps/shared/sequence_context.cpp new file mode 100644 index 000000000..1accf43ad --- /dev/null +++ b/apps/shared/sequence_context.cpp @@ -0,0 +1,126 @@ +#include "sequence_context.h" +#include "sequence_store.h" +#include "sequence_cache_context.h" +#include "../shared/poincare_helpers.h" +#include + +using namespace Poincare; + +namespace Shared { + +template +TemplatedSequenceContext::TemplatedSequenceContext() : + m_commonRank(-1), + m_commonRankValues{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}}, + m_independentRanks{-1, -1, -1}, + m_independentRankValues{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}} +{ +} + +template +T TemplatedSequenceContext::valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) const { + return m_commonRankValues[sequenceIndex][rank]; +} + +template +void TemplatedSequenceContext::resetCache() { + /* We only need to reset the ranks. Indeed, when we compute the values of the + * sequences, we use ranks as memoization indexes. Therefore, we know that the + * values stored in m_commomValues and m_independentRankValues are dirty + * and do not use them. */ + m_commonRank = -1; + for (int i = 0; i < MaxNumberOfSequences; i ++) { + m_independentRanks[i] = -1; + } +} + +template +bool TemplatedSequenceContext::iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx) { + if (m_commonRank > n) { + m_commonRank = -1; + } + if (n < 0 || n-m_commonRank > k_maxRecurrentRank) { + return false; + } + while (m_commonRank < n) { + step(sqctx); + } + return true; +} + +template +void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceIndex) { + // First we increment the rank + bool stepMultipleSequences = sequenceIndex == -1; + if (stepMultipleSequences) { + m_commonRank++; + } else { + setIndependentSequenceRank(independentSequenceRank(sequenceIndex) + 1, sequenceIndex); + } + + // Then we shift the values stored in the arrays + int start, stop, rank; + T * sequenceArray; + if (stepMultipleSequences) { + start = 0; + stop = MaxNumberOfSequences; + sequenceArray = reinterpret_cast(&m_commonRankValues); + rank = m_commonRank; + } else { + start = sequenceIndex; + stop = sequenceIndex + 1; + sequenceArray = reinterpret_cast(&m_independentRankValues); + rank = independentSequenceRank(sequenceIndex); + } + + for (int i = start; i < stop; i++) { + T * rowPointer = sequenceArray + i * (MaxRecurrenceDepth + 1); + for (int j = MaxRecurrenceDepth; j > 0; j--) { + *(rowPointer + j) = *(rowPointer + j - 1); + } + *rowPointer= NAN; + } + + // We create an array containing the sequences we want to update + Sequence * sequences[MaxNumberOfSequences] = {nullptr, nullptr, nullptr}; + int usedSize = stepMultipleSequences ? MaxNumberOfSequences : 1; + SequenceStore * sequenceStore = sqctx->sequenceStore(); + stop = stepMultipleSequences ? sequenceStore->numberOfModels() : start + 1; + for (int i = start; i < stop; i++) { + Ion::Storage::Record record = sequenceStore->recordAtIndex(i); + if (!record.isNull()) { + Sequence * u = sequenceStore->modelForRecord(record); + int index = stepMultipleSequences ? SequenceStore::sequenceIndexForName(u->fullName()[0]) : 0; + sequences[index] = u->isDefined() ? u : nullptr; + } else { + sequences[i] = nullptr; + } + } + + // We approximate the value of the next rank for each sequence we want to update + stop = stepMultipleSequences ? MaxNumberOfSequences : sequenceIndex + 1; + /* In case stop is MaxNumberOfSequences, we approximate u, v and w at the new + * rank. We have to evaluate all sequences MaxNumberOfSequences times in case + * the evaluations depend on each other. + * For example, if u expression depends on v and v depends on w. At the first + * iteration, we can only evaluate w, at the second iteration we evaluate v + * and u can only be known at the third iteration. + * In case stop is 1, there is only one sequence we want to update. Moreover, + * the call to approximateToNextRank will handle potential dependencies. */ + for (int k = 0; k < usedSize; k++) { + for (int i = start; i < stop; i++) { + T * pointerToUpdate = sequenceArray + i * (MaxRecurrenceDepth + 1); + if (std::isnan(*(pointerToUpdate))) { + int j = stepMultipleSequences ? i : 0; + *pointerToUpdate = sequences[j] ? sequences[j]->template approximateToNextRank(rank, sqctx, sequenceIndex) : NAN; + } + } + } +} + +template class TemplatedSequenceContext; +template class TemplatedSequenceContext; +template void * SequenceContext::helper(); +template void * SequenceContext::helper(); + +} diff --git a/apps/shared/sequence_context.h b/apps/shared/sequence_context.h new file mode 100644 index 000000000..83c8d8e9f --- /dev/null +++ b/apps/shared/sequence_context.h @@ -0,0 +1,109 @@ +#ifndef APPS_SHARED_SEQUENCE_CONTEXT_H +#define APPS_SHARED_SEQUENCE_CONTEXT_H + +#include +#include +#include + +namespace Shared { + +constexpr static int MaxRecurrenceDepth = 2; +static constexpr int MaxNumberOfSequences = 3; + +class SequenceStore; +class SequenceContext; + +template +class TemplatedSequenceContext { +public: + TemplatedSequenceContext(); + T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) const; + void resetCache(); + bool iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx); + + int independentSequenceRank(int sequenceIndex) { return m_independentRanks[sequenceIndex]; } + void setIndependentSequenceRank(int rank, int sequenceIndex) { m_independentRanks[sequenceIndex] = rank; } + T independentSequenceValue(int sequenceIndex, int depth) { return m_independentRankValues[sequenceIndex][depth]; } + void setIndependentSequenceValue(T value, int sequenceIndex, int depth) { m_independentRankValues[sequenceIndex][depth] = value; } + void step(SequenceContext * sqctx, int sequenceIndex = -1); +private: + constexpr static int k_maxRecurrentRank = 10000; + /* Cache: + * We use two types of cache : + * The first one is used to to accelerate the + * computation of values of recurrent sequences. We memoize the last computed + * values of the sequences and their associated ranks (n and n+1 for instance). + * Thereby, when another evaluation at a superior rank k > n+1 is called, + * we avoid iterating from 0 but can start from n. This cache allows us to step + * all of the sequences at once. + * + * The second one used used for fixed term computation. For instance, if a + * sequence is defined using a fixed term of another, u(3) for instance, we + * compute its value through the second type of cache. This way, we do not + * erase the data stored in the first type of cache and we can compute the + * values of each sequence at independent rank. This means that + * (u(3), v(5), w(10)) can be computed at the same time. + * This cache is therefore used for independent steps of sequences + */ + int m_commonRank; + T m_commonRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; + + // Used for fixed computations + int m_independentRanks[MaxNumberOfSequences]; + T m_independentRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; +}; + +class SequenceContext : public Poincare::ContextWithParent { +public: + SequenceContext(Poincare::Context * parentContext, SequenceStore * sequenceStore) : + ContextWithParent(parentContext), + m_floatSequenceContext(), + m_doubleSequenceContext(), + m_sequenceStore(sequenceStore) {} + /* expressionForSymbolAbstract & setExpressionForSymbolAbstractName directly call the parent + * context respective methods. Indeed, special chars like n, u(n), u(n+1), + * v(n), v(n+1) are taken into accound only when evaluating sequences which + * is done in another context. */ + template T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) { + return static_cast*>(helper())->valueOfCommonRankSequenceAtPreviousRank(sequenceIndex, rank); + } + + void resetCache() { + m_floatSequenceContext.resetCache(); + m_doubleSequenceContext.resetCache(); + } + + template bool iterateUntilRank(int n) { + return static_cast*>(helper())->iterateUntilRank(n, m_sequenceStore, this); + } + + template int independentSequenceRank(int sequenceIndex) { + return static_cast*>(helper())->independentSequenceRank(sequenceIndex); + } + + template void setIndependentSequenceRank(int rank, int sequenceIndex) { + static_cast*>(helper())->setIndependentSequenceRank(rank, sequenceIndex); + } + + template T independentSequenceValue(int sequenceIndex, int depth) { + return static_cast*>(helper())->independentSequenceValue(sequenceIndex, depth); + } + + template void setIndependentSequenceValue(T value, int sequenceIndex, int depth) { + static_cast*>(helper())->setIndependentSequenceValue(value, sequenceIndex, depth); + } + + template void stepSequenceAtIndex(int sequenceIndex) { + static_cast*>(helper())->step(this, sequenceIndex); + } + SequenceStore * sequenceStore() { return m_sequenceStore; } +private: + TemplatedSequenceContext m_floatSequenceContext; + TemplatedSequenceContext m_doubleSequenceContext; + SequenceStore * m_sequenceStore; + template void * helper() { return sizeof(T) == sizeof(float) ? (void*) &m_floatSequenceContext : (void*) &m_doubleSequenceContext; } +}; + +} + +#endif diff --git a/apps/sequence/sequence_store.cpp b/apps/shared/sequence_store.cpp similarity index 98% rename from apps/sequence/sequence_store.cpp rename to apps/shared/sequence_store.cpp index 18012cfa6..a9e54d717 100644 --- a/apps/sequence/sequence_store.cpp +++ b/apps/shared/sequence_store.cpp @@ -5,7 +5,7 @@ extern "C" { #include } -namespace Sequence { +namespace Shared { constexpr const char * SequenceStore::k_sequenceNames[MaxNumberOfSequences]; diff --git a/apps/sequence/sequence_store.h b/apps/shared/sequence_store.h similarity index 91% rename from apps/sequence/sequence_store.h rename to apps/shared/sequence_store.h index dd6346ee9..6e0a48184 100644 --- a/apps/sequence/sequence_store.h +++ b/apps/shared/sequence_store.h @@ -1,13 +1,12 @@ -#ifndef SEQUENCE_SEQUENCE_STORE_H -#define SEQUENCE_SEQUENCE_STORE_H +#ifndef APPS_SHARED_SEQUENCE_STORE_H +#define APPS_SHARED_SEQUENCE_STORE_H #include "../shared/function_store.h" -#include "../shared/global_context.h" #include "sequence.h" #include #include -namespace Sequence { +namespace Shared { class SequenceStore : public Shared::FunctionStore { public: @@ -27,6 +26,7 @@ public: static constexpr const char * k_sequenceNames[MaxNumberOfSequences] = { "u", "v", "w" }; + Sequence sequenceAtIndex(int i) { assert(i < MaxNumberOfSequences && i >= 0); return m_sequences[i]; } private: const char * modelExtension() const override { return Ion::Storage::seqExtension; } diff --git a/apps/sequence/sequence_title_cell.cpp b/apps/shared/sequence_title_cell.cpp similarity index 83% rename from apps/sequence/sequence_title_cell.cpp rename to apps/shared/sequence_title_cell.cpp index f1802b10a..7bc349c0d 100644 --- a/apps/sequence/sequence_title_cell.cpp +++ b/apps/shared/sequence_title_cell.cpp @@ -1,10 +1,9 @@ #include "sequence_title_cell.h" #include -using namespace Shared; using namespace Poincare; -namespace Sequence { +namespace Shared { SequenceTitleCell::SequenceTitleCell() : Shared::FunctionTitleCell(Orientation::VerticalIndicator), @@ -43,6 +42,16 @@ void SequenceTitleCell::setColor(KDColor color) { m_titleTextView.setTextColor(color); } +void SequenceTitleCell::reloadCell() { + /* When creating a new sequence, the layout has not yet been initialized, but + * it is needed in layoutSubview to compute the vertical alignment. */ + if (TreeNode::IsValidIdentifier(layout().identifier())) { + layoutSubviews(); + } + m_titleTextView.reloadCell(); + FunctionTitleCell::reloadCell(); +} + int SequenceTitleCell::numberOfSubviews() const { return 1; } @@ -53,6 +62,7 @@ View * SequenceTitleCell::subviewAtIndex(int index) { } void SequenceTitleCell::layoutSubviews(bool force) { + assert(TreeNode::IsValidIdentifier(layout().identifier())); if (m_orientation == Orientation::VerticalIndicator) { m_titleTextView.setAlignment(k_verticalOrientationHorizontalAlignment, verticalAlignment()); } diff --git a/apps/sequence/sequence_title_cell.h b/apps/shared/sequence_title_cell.h similarity index 88% rename from apps/sequence/sequence_title_cell.h rename to apps/shared/sequence_title_cell.h index b858fe1a0..fe7473db7 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/shared/sequence_title_cell.h @@ -1,10 +1,10 @@ -#ifndef SEQUENCE_SEQUENCE_TITLE_CELL_H -#define SEQUENCE_SEQUENCE_TITLE_CELL_H +#ifndef APPS_SHARED_SEQUENCE_TITLE_CELL_H +#define APPS_SHARED_SEQUENCE_TITLE_CELL_H #include "../shared/function_title_cell.h" #include -namespace Sequence { +namespace Shared { class SequenceTitleCell : public Shared::FunctionTitleCell { public: @@ -20,6 +20,7 @@ public: Poincare::Layout layout() const override { return m_titleTextView.layout(); } + void reloadCell() override; private: static constexpr float k_horizontalOrientationAlignment = 0.5f; static constexpr float k_verticalOrientationHorizontalAlignment = 0.9f; diff --git a/apps/shared/shared_app.cpp b/apps/shared/shared_app.cpp new file mode 100644 index 000000000..24c341392 --- /dev/null +++ b/apps/shared/shared_app.cpp @@ -0,0 +1,10 @@ +#include "shared_app.h" +#include "global_context.h" +#include + +void SharedApp::Snapshot::pack(App * app) { + /* Since the sequence store is now accessible from every app, when exiting + * any application, we need to tidy it.*/ + static_cast(AppsContainer::sharedAppsContainer()->globalContext())->sequenceStore()->tidy(); + App::Snapshot::pack(app); +} \ No newline at end of file diff --git a/apps/shared/shared_app.h b/apps/shared/shared_app.h new file mode 100644 index 000000000..ebe0a9003 --- /dev/null +++ b/apps/shared/shared_app.h @@ -0,0 +1,14 @@ +#ifndef SHARED_APP_H +#define SHARED_APP_H + +#include + +class SharedApp : public App { + public: + class Snapshot : public App::Snapshot { + public: + void pack(App * app) override; + }; +}; + +#endif \ No newline at end of file diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index 374ce26a9..3169adb59 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -28,10 +28,11 @@ bool SimpleInteractiveCurveViewController::textFieldDidReceiveEvent(TextField * bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Event event) { int direction = event == Ion::Events::Left ? -1 : 1; - if (moveCursorHorizontally(direction, Ion::Events::isLongRepetition())) { + if (moveCursorHorizontally(direction, Ion::Events::repetitionFactor())) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), - cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio + cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), + curveView()->pixelWidth() ); reloadBannerView(); curveView()->reload(); diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index 7827e649b..ab34fc3e4 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -15,8 +15,8 @@ public: bool handleEvent(Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: - constexpr static float k_cursorRightMarginRatio = 0.04f; // (cursorWidth/2)/(graphViewWidth-1) - constexpr static float k_cursorLeftMarginRatio = 0.04f; // (cursorWidth/2)/(graphViewWidth-1) + virtual float cursorRightMarginRatio() { return 0.04f; } // (cursorWidth/2)/(graphViewWidth-1) + virtual float cursorLeftMarginRatio() { return 0.04f; } // (cursorWidth/2)/(graphViewWidth-1) virtual float cursorTopMarginRatio() { return 0.07f; } // (cursorHeight/2)/(graphViewHeight-1) virtual float cursorBottomMarginRatio() = 0; // (cursorHeight/2+bannerHeight)/(graphViewHeight-1) constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; @@ -28,7 +28,7 @@ protected: /* the result of moveCursorVertically/Horizontally means: * false -> the cursor cannot move in this direction * true -> the cursor moved */ - virtual bool moveCursorHorizontally(int direction, bool fast = false) { return false; } + virtual bool moveCursorHorizontally(int direction, int scrollSpeed = 1) { return false; } virtual bool handleEnter() = 0; CurveViewCursor * m_cursor; }; diff --git a/apps/shared/store_parameter_controller.cpp b/apps/shared/store_parameter_controller.cpp index 752cdad26..97bfc3b55 100644 --- a/apps/shared/store_parameter_controller.cpp +++ b/apps/shared/store_parameter_controller.cpp @@ -1,5 +1,6 @@ #include "store_parameter_controller.h" #include "store_controller.h" +#include #include namespace Shared { @@ -9,61 +10,91 @@ StoreParameterController::StoreParameterController(Responder * parentResponder, m_store(store), m_series(0), m_selectableTableView(this, this, this), - m_deleteColumn(I18n::Message::ClearColumn), - m_fillWithFormula(I18n::Message::FillWithFormula), -#if COPY_IMPORT_LIST - m_copyColumn(I18n::Message::CopyColumnInList), - m_importList(I18n::Message::ImportList), -#endif + m_cells{I18n::Message::ClearColumn, I18n::Message::FillWithFormula}, m_storeController(storeController), m_xColumnSelected(true) { + +} + +void StoreParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + MessageTableCellWithEditableText * myCell = static_cast(cell); + assert(index >= 0 && index < k_totalNumberOfCell); + if (index == 2) { + myCell->setMessage(sortMessage()); + } + ListViewDataSource::willDisplayCellForIndex(cell, index); } const char * StoreParameterController::title() { return I18n::translate(I18n::Message::ColumnOptions); } +void StoreParameterController::viewWillAppear() { + m_selectableTableView.reloadData(); +} + void StoreParameterController::didBecomeFirstResponder() { selectCellAtLocation(0, 0); Container::activeApp()->setFirstResponder(&m_selectableTableView); } bool StoreParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - switch (selectedRow()) { - case 0: - { - if (m_xColumnSelected) { - m_store->deleteAllPairsOfSeries(m_series); - } else { - m_store->resetColumn(m_series, !m_xColumnSelected); - } - StackViewController * stack = ((StackViewController *)parentResponder()); - stack->pop(); - return true; + if (event != Ion::Events::OK && event != Ion::Events::EXE) { + return false; + } + switch (selectedRow()) { + case 0: + { + if (m_xColumnSelected) { + m_store->deleteAllPairsOfSeries(m_series); + } else { + m_store->resetColumn(m_series, !m_xColumnSelected); } - case 1: - { - m_storeController->displayFormulaInput(); - StackViewController * stack = ((StackViewController *)parentResponder()); - stack->pop(); - return true; + break; + } + case 1: + { + m_storeController->displayFormulaInput(); + break; + } + case 2: + { + Poincare::Helpers::Swap swapRows = [](int i, int j, void * context, int numberOfElements) { + double * contextI = (static_cast(context) + i); + double * contextJ = (static_cast(context) + j); + double * contextIOtherColumn = (static_cast(context) + DoublePairStore::k_maxNumberOfPairs + i); + double * contextJOtherColumn = (static_cast(context) + DoublePairStore::k_maxNumberOfPairs + j); + double temp1 = *contextI; + double temp2 = *contextIOtherColumn; + *contextI = *contextJ; + *contextIOtherColumn = *contextJOtherColumn; + *contextJ = temp1; + *contextJOtherColumn = temp2; + }; + Poincare::Helpers::Compare compareX = [](int a, int b, void * context, int numberOfElements)->bool{ + double * contextA = (static_cast(context) + a); + double * contextB = (static_cast(context) + b); + return *contextA > *contextB; + }; + Poincare::Helpers::Compare compareY = [](int a, int b, void * context, int numberOfElements)->bool{ + double * contextAOtherColumn = (static_cast(context) + DoublePairStore::k_maxNumberOfPairs + a); + double * contextBOtherColumn = (static_cast(context) + DoublePairStore::k_maxNumberOfPairs + b); + return *contextAOtherColumn > *contextBOtherColumn; + }; + double * seriesContext = m_store->data() + m_series * DoublePairStore::k_numberOfColumnsPerSeries * DoublePairStore::k_maxNumberOfPairs; + if (m_xColumnSelected) { + Poincare::Helpers::Sort(swapRows, compareX, seriesContext, m_store->numberOfPairsOfSeries(m_series)); + } else { + Poincare::Helpers::Sort(swapRows, compareY, seriesContext, m_store->numberOfPairsOfSeries(m_series)); } - -#if COPY_IMPORT_LIST - /* TODO: implement copy column and import list */ - case 2: - return true; - case 3: - return true; -#endif - default: - assert(false); - return false; + break; } } - return false; + assert(selectedRow() >= 0 && selectedRow() <= 2); + StackViewController * stack = static_cast(parentResponder()); + stack->pop(); + return true; } KDCoordinate StoreParameterController::cumulatedHeightFromIndex(int j) { @@ -88,8 +119,7 @@ HighlightCell * StoreParameterController::reusableCell(int index, int type) { assert(type == k_standardCellType); assert(index >= 0); assert(index < k_totalNumberOfCell); - HighlightCell * cells[] = {&m_deleteColumn, &m_fillWithFormula};// {&m_deleteColumn, &m_fillWithFormula, &m_copyColumn, &m_importList}; - return cells[index]; + return &m_cells[index]; } } diff --git a/apps/shared/store_parameter_controller.h b/apps/shared/store_parameter_controller.h index 8480c7370..efc8c6f12 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -13,10 +13,15 @@ class StoreParameterController : public ViewController, public ListViewDataSourc public: StoreParameterController(Responder * parentResponder, DoublePairStore * store, StoreController * storeController); void selectXColumn(bool xColumnSelected) { m_xColumnSelected = xColumnSelected; } - void selectSeries(int series) { m_series = series; } + void selectSeries(int series) { + assert(series >= 0 && series < DoublePairStore::k_numberOfSeries); + m_series = series; + } View * view() override { return &m_selectableTableView; } + void willDisplayCellForIndex(HighlightCell * cell, int index) override; const char * title() override; bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; void didBecomeFirstResponder() override; int numberOfRows() const override { return k_totalNumberOfCell; } KDCoordinate rowHeight(int j) override { return Metric::ParameterCellHeight; } @@ -34,15 +39,9 @@ protected: int m_series; SelectableTableView m_selectableTableView; private: -#if COPY_IMPORT_LIST - constexpr static int k_totalNumberOfCell = 4; - MessageTableCellWithChevron m_copyColumn; - MessageTableCellWithChevron m_importList; -#else - constexpr static int k_totalNumberOfCell = 2; -#endif - MessageTableCell m_deleteColumn; - MessageTableCell m_fillWithFormula; + virtual I18n::Message sortMessage() { return m_xColumnSelected ? I18n::Message::SortValues : I18n::Message::SortSizes; } + constexpr static int k_totalNumberOfCell = 3; + MessageTableCell m_cells[k_totalNumberOfCell]; StoreController * m_storeController; bool m_xColumnSelected; }; diff --git a/apps/shared/store_selectable_table_view.cpp b/apps/shared/store_selectable_table_view.cpp index 83b086deb..8ceb76f61 100644 --- a/apps/shared/store_selectable_table_view.cpp +++ b/apps/shared/store_selectable_table_view.cpp @@ -9,34 +9,36 @@ StoreSelectableTableView::StoreSelectableTableView(DoublePairStore * store, Resp } bool StoreSelectableTableView::handleEvent(Ion::Events::Event event) { + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::Down) { - return selectNonHiddenCellAtLocation(selectedColumn(), selectedRow()+1); + return selectNonHiddenCellAtClippedLocation(selectedColumn(), selectedRow() + step); } if (event == Ion::Events::Up) { - return selectNonHiddenCellAtLocation(selectedColumn(), selectedRow()-1); + return selectNonHiddenCellAtClippedLocation(selectedColumn(), selectedRow() - step); } if (event == Ion::Events::Left) { - return selectNonHiddenCellAtLocation(selectedColumn()-1, selectedRow()); + return selectNonHiddenCellAtClippedLocation(selectedColumn() - step, selectedRow()); } if (event == Ion::Events::Right) { - return selectNonHiddenCellAtLocation(selectedColumn()+1, selectedRow()); + return selectNonHiddenCellAtClippedLocation(selectedColumn() + step, selectedRow()); } return false; } -bool StoreSelectableTableView::selectNonHiddenCellAtLocation(int i, int j) { - if (i < 0 || i >= dataSource()->numberOfColumns()) { - return false; +bool StoreSelectableTableView::selectNonHiddenCellAtClippedLocation(int i, int j) { + // Clip i to retrieve a valid seriesIndex + if (i < 0) { + i = 0; + } else if (i >= dataSource()->numberOfColumns()) { + i = dataSource()->numberOfColumns() - 1; } - if (j < 0 || j >= dataSource()->numberOfRows()) { - return false; - } - int seriesIndex = i/DoublePairStore::k_numberOfColumnsPerSeries; + int seriesIndex = i / DoublePairStore::k_numberOfColumnsPerSeries; int numberOfPairsOfCurrentSeries = m_store->numberOfPairsOfSeries(seriesIndex); if (j > 1 + numberOfPairsOfCurrentSeries) { - return selectCellAtLocation(i, 1 + numberOfPairsOfCurrentSeries); + j = 1 + numberOfPairsOfCurrentSeries; } - return selectCellAtLocation(i, j); + // if negative, j will be clipped in selectCellAtClippedLocation + return selectCellAtClippedLocation(i, j); } } diff --git a/apps/shared/store_selectable_table_view.h b/apps/shared/store_selectable_table_view.h index e8592d831..09efdf101 100644 --- a/apps/shared/store_selectable_table_view.h +++ b/apps/shared/store_selectable_table_view.h @@ -12,7 +12,7 @@ public: StoreSelectableTableView(DoublePairStore * store, Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource = nullptr, SelectableTableViewDelegate * delegate = nullptr); bool handleEvent(Ion::Events::Event event) override; private: - bool selectNonHiddenCellAtLocation(int i, int j); + bool selectNonHiddenCellAtClippedLocation(int i, int j); DoublePairStore * m_store; }; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index cd597088e..423582e90 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -26,7 +26,7 @@ SumGraphController::SumGraphController(Responder * parentResponder, InputEventHa void SumGraphController::viewWillAppear() { SimpleInteractiveCurveViewController::viewWillAppear(); - m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_graphView->setBannerView(&m_legendView); m_graphView->setCursorView(&m_cursorView); m_graphView->setOkView(nullptr); @@ -77,7 +77,7 @@ bool SumGraphController::moveCursorHorizontallyToPosition(double x) { m_graphView->setAreaHighlight(m_startSum, m_cursor->x()); } m_legendView.setEditableZone(m_cursor->x()); - m_graphRange->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + m_graphRange->panToMakePointVisible(x, y, cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_graphView->reload(); return true; } @@ -155,16 +155,16 @@ void SumGraphController::reloadBannerView() { /* Legend View */ SumGraphController::LegendView::LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol) : - m_sum(0.0f, 0.5f, Palette::PrimaryText, Palette::SubMenuBackground), - m_legend(k_font, I18n::Message::Default, 0.0f, 0.5f, Palette::PrimaryText, Palette::SubMenuBackground), - m_editableZone(controller, m_textBuffer, k_editableZoneBufferSize, TextField::maxBufferSize(), inputEventHandlerDelegate, controller, k_font, 0.0f, 0.5f, Palette::PrimaryText, Palette::SubMenuBackground), + m_sum(0.0f, 0.5f, KDColorBlack, Palette::GrayMiddle), + m_legend(k_font, I18n::Message::Default, 0.0f, 0.5f, KDColorBlack, Palette::GrayMiddle), + m_editableZone(controller, m_textBuffer, k_editableZoneBufferSize, TextField::maxBufferSize(), inputEventHandlerDelegate, controller, k_font, 0.0f, 0.5f, KDColorBlack, Palette::GrayMiddle), m_sumSymbol(sumSymbol) { m_textBuffer[0] = 0; } void SumGraphController::LegendView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), Palette::SubMenuBackground); + ctx->fillRect(bounds(), Palette::GrayMiddle); } KDSize SumGraphController::LegendView::minimalSizeForOptimalDisplay() const { diff --git a/apps/shared/test/function_alignement.cpp b/apps/shared/test/function_alignement.cpp new file mode 100644 index 000000000..6654ae9eb --- /dev/null +++ b/apps/shared/test/function_alignement.cpp @@ -0,0 +1,80 @@ +#include +#include "../continuous_function.h" +#include "../../graph/continuous_function_store.h" +#include "../sequence.h" +#include "../sequence_store.h" + +namespace Shared { + +template +void interactWithBaseRecordMember(F * fct) { + /* Accessing Function record member m_color, which has a 2-byte alignment + * Only effective in DEBUG=1, as there are no compiler optimizations */ + KDColor color = fct->color(); + (void) color; // Silence compilation warning about unused variable. +} + +void interactWithRecordMember(SequenceStore * store, Ion::Storage::Record rec) { + Sequence * seq = store->modelForRecord(rec); + /* Setting Sequence type will write record member m_initialConditionSizes, + * which has a 2-byte alignment */ + seq->setType(Sequence::Type::SingleRecurrence); + interactWithBaseRecordMember(seq); +} + +void interactWithRecordMember(Graph::ContinuousFunctionStore * store, Ion::Storage::Record rec) { + ContinuousFunction * fct = store->modelForRecord(rec).pointer(); + // Setting m_min from record member m_domain, which has a 2-byte alignment + fct->setTMin(3.0f); + interactWithBaseRecordMember(fct); +} + +template +Ion::Storage::Record createRecord(T * store) { + Ion::Storage::Record::ErrorStatus err = store->addEmptyModel(); + assert(err == Ion::Storage::Record::ErrorStatus::None); + (void) err; // Silence compilation warning about unused variable. + return store->recordAtIndex(store->numberOfModels()-1); +} + +template +void testAlignmentHandlingFor() { + T store; + Ion::Storage * sharedStorage = Ion::Storage::sharedStorage(); + + sharedStorage->destroyAllRecords(); + Ion::Storage::Record rec1 = createRecord(&store); + // Evaluate the sequence shift compared to a 2-byte alignment + uintptr_t shift = reinterpret_cast(rec1.value().buffer) % 2; + + /* Interact with an alignment sensitive member. A mishandled record alignment + * should throw an abort(alignment fault) exception */ + interactWithRecordMember(&store, rec1); + + sharedStorage->destroyAllRecords(); + // Repeat the same process with a 1 byte record padding + Ion::Storage::Record::ErrorStatus err = sharedStorage->createRecordWithExtension("1", "1", "1", 1); + assert(err == Ion::Storage::Record::ErrorStatus::None); + (void) err; // Silence compilation warning about unused variable. + + Ion::Storage::Record rec2 = createRecord(&store); + shift += reinterpret_cast(rec2.value().buffer) % 2; + interactWithRecordMember(&store, rec2); + + /* If fct1 and fct2 had different shifts, the test is able to detect a + * mishandled record alignment */ + quiz_assert(shift == 1); + + sharedStorage->destroyAllRecords(); +} + +QUIZ_CASE(alignment_handling) { + /* This test main function is to crash if storage alignment is not handled + * properly. It also ensures that the right test - load and store of + * differently-aligned objects - is performed (if storage/record + * implementations change for instance). */ + testAlignmentHandlingFor(); + testAlignmentHandlingFor(); +} + +} \ No newline at end of file diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index ece157f9b..e66a87422 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -85,13 +85,7 @@ bool TextFieldDelegateApp::isFinishingEvent(Ion::Events::Event event) { } bool TextFieldDelegateApp::isAcceptableExpression(const Expression exp) { - if (exp.isUninitialized()) { - return false; - } - if (exp.type() == ExpressionNode::Type::Store) { - return false; - } - return true; + return !(exp.isUninitialized() || exp.type() == ExpressionNode::Type::Store || exp.type() == ExpressionNode::Type::Equal); } bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression, bool replaceAns, Expression ansExpression, Context * context) { diff --git a/apps/shared/zoom_curve_view_controller.cpp b/apps/shared/zoom_curve_view_controller.cpp index 45c25078d..77778be78 100644 --- a/apps/shared/zoom_curve_view_controller.cpp +++ b/apps/shared/zoom_curve_view_controller.cpp @@ -14,7 +14,7 @@ bool ZoomCurveViewController::handleEvent(Ion::Events::Event event) { } bool ZoomCurveViewController::handleZoom(Ion::Events::Event event) { - float ratio = event == Ion::Events::Plus ? 2.0f/3.0f : 3.0f/2.0f; + float ratio = event == Ion::Events::Plus ? 1.f / k_zoomOutRatio : k_zoomOutRatio; interactiveCurveViewRange()->zoom(ratio, xFocus(), yFocus()); curveView()->reload(); return true; diff --git a/apps/shared/zoom_curve_view_controller.h b/apps/shared/zoom_curve_view_controller.h index 97698ae76..da96a4d8d 100644 --- a/apps/shared/zoom_curve_view_controller.h +++ b/apps/shared/zoom_curve_view_controller.h @@ -13,6 +13,8 @@ namespace Shared { class ZoomCurveViewController : public ViewController { public: + static constexpr float k_zoomOutRatio = 3.f / 2.f; + ZoomCurveViewController(Responder * parentResponder) : ViewController(parentResponder) {} View * view() override { return curveView(); } bool handleEvent(Ion::Events::Event event) override; diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp index 536a5a8f2..11f96a32a 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -52,7 +52,7 @@ App::App(Snapshot * snapshot) : m_intervalController(nullptr, this, snapshot->equationStore()), m_alternateEmptyViewController(nullptr, &m_solutionsController, &m_solutionsController), m_listController(&m_listFooter, snapshot->equationStore(), &m_listFooter), - m_listFooter(&m_stackViewController, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey, ButtonRowController::Size::Large), + m_listFooter(&m_stackViewController, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray, ButtonRowController::Size::Large), m_stackViewController(&m_inputViewController, &m_listFooter), m_inputViewController(&m_modalViewController, &m_stackViewController, this, &m_listController, &m_listController) { @@ -68,4 +68,10 @@ void App::willBecomeInactive() { ::App::willBecomeInactive(); } + +bool App::isAcceptableExpression(const Poincare::Expression exp) { + return TextFieldDelegateApp::ExpressionCanBeSerialized(exp, false, Poincare::Expression(), localContext()) + && !(exp.isUninitialized() || exp.type() == Poincare::ExpressionNode::Type::Store); +} + } diff --git a/apps/solver/app.h b/apps/solver/app.h index a181e34a7..a4919978e 100644 --- a/apps/solver/app.h +++ b/apps/solver/app.h @@ -7,6 +7,7 @@ #include "equation_store.h" #include "interval_controller.h" #include "solutions_controller.h" +#include "../shared/shared_app.h" namespace Solver { @@ -19,7 +20,7 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: Snapshot(); App * unpack(Container * container) override; @@ -46,6 +47,9 @@ public: void willBecomeInactive() override; TELEMETRY_ID("Solver"); private: + // TextFieldDelegateApp + bool isAcceptableExpression(const Poincare::Expression expression) override; + App(Snapshot * snapshot); SolutionsController m_solutionsController; IntervalController m_intervalController; diff --git a/apps/solver/base.nl.i18n b/apps/solver/base.nl.i18n index adcfadc87..89e9d3d4d 100644 --- a/apps/solver/base.nl.i18n +++ b/apps/solver/base.nl.i18n @@ -3,22 +3,22 @@ SolverAppCapital = "VERGELIJKING" AddEquation = "Vergelijking toevoegen" ResolveEquation = "Vergelijking oplossen" ResolveSystem = "Stelsel oplossen" -UseEquationModel = "Gebruik een vergelijkingstemplate" +UseEquationModel = "Gebruik een template" RequireEquation = "De invoer moet een vergelijking zijn" UnrealEquation = "Vergelijking is niet reëel" UndefinedEquation = "Ongedefinieerde vergelijking" TooManyVariables = "Er zijn te veel onbekenden" NonLinearSystem = "Het stelsel is niet lineair" Solution = "Oplossing" -ApproximateSolution = "Benaderende oplossing" -SearchInverval = "Zoekinterval" +ApproximateSolution = "Benaderde oplossing" +SearchInverval = "Intervalbepaling" NoSolutionSystem = "Het stelsel heeft geen oplossing" NoSolutionEquation = "De vergelijking heeft geen oplossing" NoSolutionInterval = "Geen oplossing gevonden binnen het interval" EnterEquation = "Voer een vergelijking in" InfiniteNumberOfSolutions = "Er is een oneindig aantal oplossingen" -ApproximateSolutionIntervalInstruction0= "Voer het interval in om in te zoeken" -ApproximateSolutionIntervalInstruction1= "naar een benaderende oplossing" +ApproximateSolutionIntervalInstruction0= "Bepaal het interval waarin" +ApproximateSolutionIntervalInstruction1= "de oplossing moet liggen" OnlyFirstSolutionsDisplayed0 = "Alleen de eerste tien oplossingen" OnlyFirstSolutionsDisplayed1 = "worden weergegeven" PolynomeHasNoRealSolution0 = "De polynoom heeft geen" diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index 80d2a4cab..e0a4aada5 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -1,4 +1,5 @@ #include "equation.h" +#include #include #include #include @@ -18,58 +19,42 @@ bool Equation::containsIComplex(Context * context) const { return expressionClone().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast(e).isIComplex(); }, context); } -Expression Equation::Model::standardForm(const Storage::Record * record, Context * context, bool replaceFunctionsButNotSymbols) const { - Expression * returnedExpression = replaceFunctionsButNotSymbols ? &m_standardFormWithReplacedFunctionsButNotSymbols : &m_standardFormWithReplacedFunctionsAndSymbols; - if (returnedExpression->isUninitialized()) { - Expression expressionInputWithoutFunctions = Expression::ExpressionWithoutSymbols(expressionClone(record), context, replaceFunctionsButNotSymbols); - if (expressionInputWithoutFunctions.isUninitialized()) { - // The expression is circularly-defined - expressionInputWithoutFunctions = Undefined::Builder(); - } - - EmptyContext emptyContext; - Context * contextToUse = replaceFunctionsButNotSymbols ? &emptyContext : context; - - // Reduce the expression - Expression expressionRed = expressionInputWithoutFunctions.clone(); - PoincareHelpers::Simplify(&expressionRed, contextToUse, ExpressionNode::ReductionTarget::SystemForApproximation); - // simplify might return an uninitialized Expression if interrupted - if (expressionRed.isUninitialized()) { - expressionRed = expressionInputWithoutFunctions; - } - - if (expressionRed.type() == ExpressionNode::Type::Unreal) { - *returnedExpression = Unreal::Builder(); - } else if (expressionRed.recursivelyMatches( - [](const Expression e, Context * context) { - return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); - }, - contextToUse)) - { - *returnedExpression = Undefined::Builder(); - } else if (expressionRed.type() == ExpressionNode::Type::Equal) { - Preferences * preferences = Preferences::sharedPreferences(); - *returnedExpression = static_cast(expressionRed).standardEquation(contextToUse, Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), expressionInputWithoutFunctions, contextToUse), preferences->angleUnit()); - } else { - assert(expressionRed.type() == ExpressionNode::Type::Rational && static_cast(expressionRed).isOne()); - // The equality was reduced which means the equality was always true. - *returnedExpression = Rational::Builder(0); - } - if (!m_standardFormWithReplacedFunctionsButNotSymbols.isUninitialized() && !m_standardFormWithReplacedFunctionsAndSymbols.isUninitialized()) { - // Do not keep two equal expressions - if (m_standardFormWithReplacedFunctionsButNotSymbols.isIdenticalTo(m_standardFormWithReplacedFunctionsAndSymbols)) { - m_standardFormWithReplacedFunctionsButNotSymbols = m_standardFormWithReplacedFunctionsAndSymbols; - } - } +Expression Equation::Model::standardForm(const Storage::Record * record, Context * context, bool replaceFunctionsButNotSymbols, ExpressionNode::ReductionTarget reductionTarget) const { + Expression returnedExpression = Expression(); + Expression expressionInputWithoutFunctions = Expression::ExpressionWithoutSymbols(expressionClone(record), context, replaceFunctionsButNotSymbols); + if (expressionInputWithoutFunctions.isUninitialized()) { + // The expression is circularly-defined + expressionInputWithoutFunctions = Undefined::Builder(); } - return *returnedExpression; -} + EmptyContext emptyContext; + Context * contextToUse = replaceFunctionsButNotSymbols ? &emptyContext : context; -void Equation::Model::tidy() const { - ExpressionModel::tidy(); - // Free the pool of the m_standardForm - m_standardFormWithReplacedFunctionsAndSymbols = Expression(); - m_standardFormWithReplacedFunctionsButNotSymbols = Expression(); + // Reduce the expression + Expression expressionRed = expressionInputWithoutFunctions.clone(); + PoincareHelpers::Simplify(&expressionRed, contextToUse, reductionTarget); + + // simplify might return an uninitialized Expression if interrupted + if (expressionRed.isUninitialized()) { + expressionRed = expressionInputWithoutFunctions; + } + if (expressionRed.type() == ExpressionNode::Type::Unreal) { + returnedExpression = Unreal::Builder(); + } else if (expressionRed.recursivelyMatches( + [](const Expression e, Context * context) { + return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); + }, + contextToUse)) + { + returnedExpression = Undefined::Builder(); + } else if (expressionRed.type() == ExpressionNode::Type::Equal) { + Preferences * preferences = Preferences::sharedPreferences(); + returnedExpression = static_cast(expressionRed).standardEquation(contextToUse, Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), expressionInputWithoutFunctions, contextToUse), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), reductionTarget); + } else { + assert(expressionRed.type() == ExpressionNode::Type::Rational && static_cast(expressionRed).isOne()); + // The equality was reduced which means the equality was always true. + returnedExpression = Rational::Builder(0); + } + return returnedExpression; } void * Equation::Model::expressionAddress(const Ion::Storage::Record * record) const { diff --git a/apps/solver/equation.h b/apps/solver/equation.h index 549ab6998..a43db56c8 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -11,19 +11,16 @@ public: bool shouldBeClearedBeforeRemove() override { return false; } - Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols); } + Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols, Poincare::ExpressionNode::ReductionTarget reductionTarget) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols, reductionTarget); } bool containsIComplex(Poincare::Context * context) const; private: class Model : public Shared::ExpressionModel { public: - Poincare::Expression standardForm(const Ion::Storage::Record * record, Poincare::Context * context, bool replaceFunctionsButNotSymbols) const; - void tidy() const override; + Poincare::Expression standardForm(const Ion::Storage::Record * record, Poincare::Context * context, bool replaceFunctionsButNotSymbols, Poincare::ExpressionNode::ReductionTarget reductionTarget) const; private: void * expressionAddress(const Ion::Storage::Record * record) const override; size_t expressionSize(const Ion::Storage::Record * record) const override; - mutable Poincare::Expression m_standardFormWithReplacedFunctionsAndSymbols; - mutable Poincare::Expression m_standardFormWithReplacedFunctionsButNotSymbols; }; size_t metaDataSize() const override { return 0; } const Shared::ExpressionModel * model() const override { return &m_model; } diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 867a19d7c..e5865a19a 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -89,28 +89,23 @@ void EquationStore::setIntervalBound(int index, double value) { } } -double EquationStore::approximateSolutionAtIndex(int i) { - assert(m_type == Type::Monovariable && i >= 0 && i < m_numberOfSolutions); - return m_approximateSolutions[i]; -} - -bool EquationStore::haveMoreApproximationSolutions(Context * context, bool solveWithoutContext) { - if (m_numberOfSolutions < k_maxNumberOfEquations) { - return false; - } - double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; - return !std::isnan(PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context, solveWithoutContext), m_variables[0], m_approximateSolutions[m_numberOfSolutions-1], step, m_intervalApproximateSolutions[1], context)); -} - void EquationStore::approximateSolve(Poincare::Context * context, bool shouldReplaceFunctionsButNotSymbols) { + m_hasMoreThanMaxNumberOfApproximateSolution = false; + Expression undevelopedExpression = modelForRecord(definedRecordAtIndex(0))->standardForm(context, shouldReplaceFunctionsButNotSymbols, ExpressionNode::ReductionTarget::SystemForApproximation); m_userVariablesUsed = !shouldReplaceFunctionsButNotSymbols; assert(m_variables[0][0] != 0 && m_variables[1][0] == 0); assert(m_type == Type::Monovariable); m_numberOfSolutions = 0; double start = m_intervalApproximateSolutions[0]; double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; - for (int i = 0; i < k_maxNumberOfApproximateSolutions; i++) { - m_approximateSolutions[i] = PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context, shouldReplaceFunctionsButNotSymbols), m_variables[0], start, step, m_intervalApproximateSolutions[1], context); + double root; + for (int i = 0; i <= k_maxNumberOfApproximateSolutions; i++) { + root = PoincareHelpers::NextRoot(undevelopedExpression, m_variables[0], start, step, m_intervalApproximateSolutions[1], context); + if (i == k_maxNumberOfApproximateSolutions) { + m_hasMoreThanMaxNumberOfApproximateSolution = !std::isnan(root); + break; + } + m_approximateSolutions[i] = root; if (std::isnan(m_approximateSolutions[i])) { break; } else { @@ -131,6 +126,20 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context, bool return e; } +/* Equations are solved according to the following procedure : + * 1) We develop the equations using the reduction target "SystemForAnalysis". + * This expands structures like Newton multinoms and allows us to detect + * polynoms afterwards. ("(x+2)^2" in this form is not detected but is if + * expanded). + * 2) We look for classic forms of equations for which we have algorithms + * that output the exact answer. If one is recognized in the input equation, + * the exact answer is given to the user. + * 3) If no classic form has been found in the developped form, we need to use + * numerical approximation. Therefore, to prevent precision losses, we work + * with the undevelopped form of the equation. Therefore we set reductionTarget + * to SystemForApproximation. Solutions are then numericaly approximated + * between the bounds provided by the user. */ + EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols) { tidySolution(); @@ -143,6 +152,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex // TODO we look twice for variables but not the same, is there a way to not do the same work twice? m_userVariables[0][0] = 0; m_numberOfUserVariables = 0; + Expression simplifiedExpressions[k_maxNumberOfEquations]; for (int i = 0; i < numberOfDefinedModels(); i++) { Shared::ExpiringPointer eq = modelForRecord(definedRecordAtIndex(i)); @@ -150,12 +160,21 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex /* Start by looking for user variables, so that if we escape afterwards, we * know if it might be due to a user variable. */ if (m_numberOfUserVariables < Expression::k_maxNumberOfVariables) { - const Expression eWithSymbols = eq->standardForm(context, true); + const Expression eWithSymbols = eq->standardForm(context, true, ExpressionNode::ReductionTarget::SystemForAnalysis); + /* if replaceFunctionsButNotSymbols is true we can memoize the expressions + * for the rest of the function. Otherwise, we will memoize them at the + * next call to standardForm*/ + if (replaceFunctionsButNotSymbols == true) { + simplifiedExpressions[i] = eWithSymbols; + } int varCount = eWithSymbols.getVariables(context, [](const char * symbol, Poincare::Context * context) { return context->expressionTypeForIdentifier(symbol, strlen(symbol)) == Poincare::Context::SymbolAbstractType::Symbol; }, (char *)m_userVariables, Poincare::SymbolAbstract::k_maxNameSize, m_numberOfUserVariables); m_numberOfUserVariables = varCount < 0 ? Expression::k_maxNumberOfVariables : varCount; } - - const Expression e = eq->standardForm(context, replaceFunctionsButNotSymbols); // The standard form is memoized so there is no double computation even if replaceFunctionsButNotSymbols is true. + if (simplifiedExpressions[i].isUninitialized()) { + // The expression was not memoized before. + simplifiedExpressions[i] = eq->standardForm(context, replaceFunctionsButNotSymbols, ExpressionNode::ReductionTarget::SystemForAnalysis); + } + const Expression e = simplifiedExpressions[i]; if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined || e.recursivelyMatches(Expression::IsMatrix, context, replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition)) { return Error::EquationUndefined; } @@ -180,7 +199,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex bool isLinear = true; // Invalid the linear system if one equation is non-linear Preferences * preferences = Preferences::sharedPreferences(); for (int i = 0; i < numberOfDefinedModels(); i++) { - isLinear = isLinear && modelForRecord(definedRecordAtIndex(i))->standardForm(context, replaceFunctionsButNotSymbols).getLinearCoefficients((char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize, coefficients[i], &constants[i], context, updatedComplexFormat(context), preferences->angleUnit(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + isLinear = isLinear && simplifiedExpressions[i].getLinearCoefficients((char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize, coefficients[i], &constants[i], context, updatedComplexFormat(context), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); if (!isLinear) { // TODO: should we clean pool allocated memory if the system is not linear #if 0 @@ -211,13 +230,14 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex // Step 3. Polynomial & Monovariable? assert(numberOfVariables == 1 && numberOfDefinedModels() == 1); Expression polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; - int degree = modelForRecord(definedRecordAtIndex(0))->standardForm(context, replaceFunctionsButNotSymbols) + int degree = simplifiedExpressions[0] .getPolynomialReducedCoefficients( m_variables[0], polynomialCoefficients, context, updatedComplexFormat(context), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); @@ -259,7 +279,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex * solutions. */ m_exactSolutionIdentity[solutionIndex] = forbidExactSolutions || strcmp(exactBuffer, approximateBuffer) == 0; if (!m_exactSolutionIdentity[solutionIndex]) { - m_exactSolutionEquality[solutionIndex] = Expression::ParsedExpressionsAreEqual(exactBuffer, approximateBuffer, context, updatedComplexFormat(context), preferences->angleUnit()); + m_exactSolutionEquality[solutionIndex] = Expression::ParsedExpressionsAreEqual(exactBuffer, approximateBuffer, context, updatedComplexFormat(context), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); } solutionIndex++; } @@ -269,6 +289,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolutions[k_maxNumberOfExactSolutions], Expression exactSolutionsApproximations[k_maxNumberOfExactSolutions], Expression coefficients[k_maxNumberOfEquations][Expression::k_maxNumberOfVariables], Expression constants[k_maxNumberOfEquations], Context * context) { Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + Preferences::UnitFormat unitFormat = GlobalPreferences::sharedGlobalPreferences()->unitFormat(); // n unknown variables int n = 0; while (n < Expression::k_maxNumberOfVariables && m_variables[n][0] != 0) { @@ -286,7 +307,7 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution Ab.setDimensions(m, n+1); // Compute the rank of (A | b) - int rankAb = Ab.rank(context, updatedComplexFormat(context), angleUnit, true); + int rankAb = Ab.rank(context, updatedComplexFormat(context), angleUnit, unitFormat, true); // Initialize the number of solutions m_numberOfSolutions = INT_MAX; @@ -295,12 +316,13 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution for (int j = m-1; j >= 0; j--) { bool rowWithNullCoefficients = true; for (int i = 0; i < n; i++) { - if (!Ab.matrixChild(j, i).isRationalZero()) { + if (Ab.matrixChild(j, i).nullStatus(context) != ExpressionNode::NullStatus::Null) { rowWithNullCoefficients = false; break; } } - if (rowWithNullCoefficients && !Ab.matrixChild(j, n).isRationalZero()) { + if (rowWithNullCoefficients && Ab.matrixChild(j, n).nullStatus(context) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown m_numberOfSolutions = 0; } } @@ -311,7 +333,7 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution m_numberOfSolutions = n; for (int i = 0; i < m_numberOfSolutions; i++) { exactSolutions[i] = Ab.matrixChild(i,n); - exactSolutions[i].simplifyAndApproximate(&exactSolutions[i], &exactSolutionsApproximations[i], context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit()); + exactSolutions[i].simplifyAndApproximate(&exactSolutions[i], &exactSolutionsApproximations[i], context, updatedComplexFormat(context), angleUnit, unitFormat); } } } @@ -323,15 +345,16 @@ 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(ExpressionNode::ReductionContext(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::SystemForApproximation)); + delta = delta.simplify(ExpressionNode::ReductionContext(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::SystemForApproximation)); if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } - if (delta.isRationalZero()) { + if (delta.nullStatus(context) == ExpressionNode::NullStatus::Null) { // if delta = 0, x0=x1= -b/(2a) exactSolutions[0] = Division::Builder(Opposite::Builder(coefficients[1]), Multiplication::Builder(Rational::Builder(2), coefficients[2])); m_numberOfSolutions = 2; } else { + // TODO: Handle ExpressionNode::NullStatus::Unknown // x0 = (-b-sqrt(delta))/(2a) exactSolutions[0] = Division::Builder(Subtraction::Builder(Opposite::Builder(coefficients[1].clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), coefficients[2].clone())); // x1 = (-b+sqrt(delta))/(2a) @@ -340,7 +363,7 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact } exactSolutions[m_numberOfSolutions-1] = delta; for (int i = 0; i < m_numberOfSolutions; i++) { - exactSolutions[i].simplifyAndApproximate(&exactSolutions[i], &exactSolutionsApproximations[i], context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit()); + exactSolutions[i].simplifyAndApproximate(&exactSolutions[i], &exactSolutionsApproximations[i], context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); } return Error::NoError; #if 0 @@ -362,8 +385,8 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact Expression * mult5Operands[3] = {new Rational::Builder(3), a->clone(), c->clone()}; Expression * delta0 = new Subtraction::Builder(new Power::Builder(b->clone(), new Rational::Builder(2), false), new Multiplication::Builder(mult5Operands, 3, false), false); Reduce(&delta0, *context); - if (delta->isRationalZero()) { - if (delta0->isRationalZero()) { + if (delta->nullStatus(context) == ExpressionNode::NullStatus::Null) { + if (delta0->nullStatus(context) == ExpressionNode::NullStatus::Null) { // delta0 = 0 && delta = 0 --> x0 = -b/(3a) delete delta0; m_exactSolutions[0] = new Opposite::Builder(new Division::Builder(b, new Multiplication::Builder(new Rational::Builder(3), a, false), false), false); diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h index 5cc28c6b1..362392091 100644 --- a/apps/solver/equation_store.h +++ b/apps/solver/equation_store.h @@ -74,14 +74,18 @@ public: /* Approximate resolution */ double intervalBound(int index) const; void setIntervalBound(int index, double value); - double approximateSolutionAtIndex(int i); + double approximateSolutionAtIndex(int i) { + assert(m_type == Type::Monovariable && i >= 0 && i < m_numberOfSolutions); + return m_approximateSolutions[i]; + } void approximateSolve(Poincare::Context * context, bool shouldReplaceFuncionsButNotSymbols); - bool haveMoreApproximationSolutions(Poincare::Context * context, bool solveWithoutContext); + bool haveMoreApproximationSolutions() { return m_hasMoreThanMaxNumberOfApproximateSolution; } void tidy() override; static constexpr int k_maxNumberOfExactSolutions = Poincare::Expression::k_maxNumberOfVariables > Poincare::Expression::k_maxPolynomialDegree + 1? Poincare::Expression::k_maxNumberOfVariables : Poincare::Expression::k_maxPolynomialDegree + 1; static constexpr int k_maxNumberOfApproximateSolutions = 10; + bool m_hasMoreThanMaxNumberOfApproximateSolution; static constexpr int k_maxNumberOfSolutions = k_maxNumberOfExactSolutions > k_maxNumberOfApproximateSolutions ? k_maxNumberOfExactSolutions : k_maxNumberOfApproximateSolutions; private: static constexpr double k_precision = 0.01; diff --git a/apps/solver/interval_controller.cpp b/apps/solver/interval_controller.cpp index a46051dfd..986f5b4cf 100644 --- a/apps/solver/interval_controller.cpp +++ b/apps/solver/interval_controller.cpp @@ -45,7 +45,8 @@ IntervalController::IntervalController(Responder * parentResponder, InputEventHa FloatParameterController(parentResponder), m_contentView(&m_selectableTableView), m_intervalCell{}, - m_equationStore(equationStore) + m_equationStore(equationStore), + m_shouldReplaceFunctionsButNotSymbols(false) { m_selectableTableView.setTopMargin(0); m_okButton.setMessage(I18n::Message::ResolveEquation); @@ -102,7 +103,7 @@ bool IntervalController::textFieldDidFinishEditing(TextField * textField, const void IntervalController::buttonAction() { StackViewController * stack = stackController(); - m_equationStore->approximateSolve(textFieldDelegateApp()->localContext(), App::app()->solutionsController()->shouldReplaceFuncionsButNotSymbols()); + m_equationStore->approximateSolve(textFieldDelegateApp()->localContext(), m_shouldReplaceFunctionsButNotSymbols); stack->push(App::app()->solutionsControllerStack(), Palette::BannerSecondText, Palette::BannerSecondBackground, Palette::BannerSecondBorder); } diff --git a/apps/solver/interval_controller.h b/apps/solver/interval_controller.h index 5ece5e361..6e273284d 100644 --- a/apps/solver/interval_controller.h +++ b/apps/solver/interval_controller.h @@ -15,6 +15,7 @@ public: TELEMETRY_ID("Interval"); int numberOfRows() const override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void setShouldReplaceFuncionsButNotSymbols(bool shouldReplaceFunctionsButNotSymbols) { m_shouldReplaceFunctionsButNotSymbols = shouldReplaceFunctionsButNotSymbols; } private: HighlightCell * reusableParameterCell(int index, int type) override; int reusableParameterCellCount(int type) override; @@ -39,6 +40,7 @@ private: constexpr static int k_maxNumberOfCells = 2; MessageTableCellWithEditableText m_intervalCell[k_maxNumberOfCells]; EquationStore * m_equationStore; + bool m_shouldReplaceFunctionsButNotSymbols; }; } diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp index edbe539b8..376b2be52 100644 --- a/apps/solver/list_controller.cpp +++ b/apps/solver/list_controller.cpp @@ -190,7 +190,7 @@ void ListController::resolveEquations() { return; case EquationStore::Error::RequireApproximateSolution: { - App::app()->solutionsController()->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); + reinterpret_cast(App::app()->intervalController())->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); stackController()->push(App::app()->intervalController(), Palette::BannerFirstText, Palette::BannerFirstBackground, Palette::BannerFirstBorder); return; } @@ -198,7 +198,7 @@ void ListController::resolveEquations() { { assert(e == EquationStore::Error::NoError); StackViewController * stack = stackController(); - App::app()->solutionsController()->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); + reinterpret_cast(App::app()->intervalController())->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); stack->push(App::app()->solutionsControllerStack(), Palette::BannerFirstText, Palette::BannerFirstBackground, Palette::BannerFirstBorder); } } diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 22443438b..8e5e985dc 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -81,8 +81,7 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_equationStore(equationStore), m_deltaCell(0.5f, 0.5f), m_delta2Layout(), - m_contentView(this), - m_shouldReplaceFunctionsButNotSymbols(false) + m_contentView(this) { m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); const char * deltaB = "Δ=b"; @@ -112,7 +111,7 @@ void SolutionsController::viewWillAppear() { bool requireWarning = false; if (m_equationStore->type() == EquationStore::Type::Monovariable) { m_contentView.setWarningMessages(I18n::Message::OnlyFirstSolutionsDisplayed0, I18n::Message::OnlyFirstSolutionsDisplayed1); - requireWarning = m_equationStore->haveMoreApproximationSolutions(App::app()->localContext(), m_shouldReplaceFunctionsButNotSymbols); + requireWarning = m_equationStore->haveMoreApproximationSolutions(); } else if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && m_equationStore->numberOfSolutions() == 1) { assert(Preferences::sharedPreferences()->complexFormat() == Preferences::ComplexFormat::Real); m_contentView.setWarningMessages(I18n::Message::PolynomeHasNoRealSolution0, I18n::Message::PolynomeHasNoRealSolution1); diff --git a/apps/solver/solutions_controller.h b/apps/solver/solutions_controller.h index 8fac34e02..4fac929a1 100644 --- a/apps/solver/solutions_controller.h +++ b/apps/solver/solutions_controller.h @@ -11,8 +11,6 @@ namespace Solver { class SolutionsController : public ViewController, public AlternateEmptyViewDefaultDelegate, public SelectableTableViewDataSource, public TableViewDataSource, public SelectableTableViewDelegate { public: SolutionsController(Responder * parentResponder, EquationStore * equationStore); - void setShouldReplaceFuncionsButNotSymbols(bool shouldReplaceFuncionsButNotSymbols) { m_shouldReplaceFunctionsButNotSymbols = shouldReplaceFuncionsButNotSymbols; } - bool shouldReplaceFuncionsButNotSymbols() const { return m_shouldReplaceFunctionsButNotSymbols; } /* ViewController */ const char * title() override; View * view() override { return &m_contentView; } @@ -111,7 +109,6 @@ private: EvenOddBufferTextCell m_approximateValueCells[k_numberOfApproximateValueCells]; MessageCell m_messageCells[k_numberOfMessageCells]; ContentView m_contentView; - bool m_shouldReplaceFunctionsButNotSymbols; }; } diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index 5a5534a6f..8ffbd0c54 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -78,9 +78,12 @@ QUIZ_CASE(equation_solve) { unset("x"); // Monovariable non-polynomial equation + Poincare::Preferences::sharedPreferences()->setAngleUnit(Degree); assert_solves_numerically_to("cos(x)=0", -100, 100, {-90.0, 90.0}); assert_solves_numerically_to("cos(x)=0", -900, 1000, {-810.0, -630.0, -450.0, -270.0, -90.0, 90.0, 270.0, 450.0, 630.0, 810.0}); assert_solves_numerically_to("√(y)=0", -900, 1000, {0}, "y"); + assert_solves_numerically_to("ℯ^x=0", -1000, 1000, {}); + assert_solves_numerically_to("ℯ^x/1000=0", -1000, 1000, {}); // Long variable names assert_solves_to("2abcde+3=4", "abcde=1/2"); @@ -89,8 +92,12 @@ QUIZ_CASE(equation_solve) { // conj(x)*x+1 = 0 assert_solves_to_error("conj(x)*x+1=0", RequireApproximateSolution); assert_solves_numerically_to("conj(x)*x+1=0", -100, 100, {}); + + assert_solves_to_error("(x-10)^7=0", RequireApproximateSolution); + assert_solves_numerically_to("(x-10)^7=0", -100, 100, {10}); } + QUIZ_CASE(equation_solve_complex_real) { set_complex_format(Real); assert_solves_to("x+𝐢=0", "x=-𝐢"); // We still want complex solutions if the input has some complex value diff --git a/apps/solver/test/helpers.cpp b/apps/solver/test/helpers.cpp index 119e085f6..a5bba40e3 100644 --- a/apps/solver/test/helpers.cpp +++ b/apps/solver/test/helpers.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -147,7 +148,8 @@ void set(const char * variable, const char * value) { buffer, &globalContext, Preferences::sharedPreferences()->complexFormat(), - Preferences::sharedPreferences()->angleUnit() + Preferences::sharedPreferences()->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat() ); } diff --git a/apps/statistics/app.h b/apps/statistics/app.h index ee53af2b3..76eef9438 100644 --- a/apps/statistics/app.h +++ b/apps/statistics/app.h @@ -8,6 +8,7 @@ #include "store.h" #include "store_controller.h" #include "../shared/text_field_delegate_app.h" +#include "../shared/shared_app.h" namespace Statistics { @@ -20,7 +21,7 @@ public: App::Descriptor::ExaminationLevel examinationLevel() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot, public TabViewDataSource { + class Snapshot : public ::SharedApp::Snapshot, public TabViewDataSource { public: Snapshot(); App * unpack(Container * container) override; diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index a28b5e8f5..8a84b549e 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -5,21 +5,23 @@ BoxTab = "Boxplot" Values1 = "Werte V1" Values2 = "Werte V2" Values3 = "Werte V3" -Sizes1 = "Häufigkeiten N1" -Sizes2 = "Häufigkeiten N2" -Sizes3 = "Häufigkeiten N3" +Frequencies1 = "Häufigkeiten N1" +Frequencies2 = "Häufigkeiten N2" +Frequencies3 = "Häufigkeiten N3" ImportList = "Laden einer Liste" Interval = " Intervall" -Size = " Häufigkeit" -Frequency = "Relative" +Frequency = " Häufigkeit:" +RelativeFrequency = "Relative:" HistogramSet = "Einstellung des Histogramms" RectangleWidth = "Breite der Rechtecke" BarStart = "Beginn der Serie" FirstQuartile = "Unteres Quartil" Median = "Median" ThirdQuartile = "Oberes Quartil" -TotalSize = "Anzahl der Elemente" +TotalFrequency = "Anzahl der Elemente" Range = "Spannweite" StandardDeviationSigma = "Standardabweichung σ" SampleStandardDeviationS = "Standardabweichung s" +SumValues = "Summe" +SumSquareValues = "Quadratsumme" InterquartileRange = "Interquartilsabstand" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 61091e835..a25f566fe 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -2,24 +2,26 @@ StatsApp = "Statistics" StatsAppCapital = "STATISTICS" HistogramTab = "Histogram" BoxTab = "Box" -Values1 = "Values V1" -Values2 = "Values V2" -Values3 = "Values V3" -Sizes1 = "Sizes N1" -Sizes2 = "Sizes N2" -Sizes3 = "Sizes N3" +Values1 = "Value V1" +Values2 = "Value V2" +Values3 = "Value V3" +Frequencies1 = "Frequency N1" +Frequencies2 = "Frequency N2" +Frequencies3 = "Frequency N3" ImportList = "Import from a list" Interval = " Interval " -Size = " Size" -Frequency = "Frequency" +Frequency = " Frequency:" +RelativeFrequency = "Relative:" HistogramSet = "Histogram settings" RectangleWidth = "Bin width" BarStart = "X start" FirstQuartile = "First quartile" Median = "Median" ThirdQuartile = "Third quartile" -TotalSize = "Total size" +TotalFrequency = "Number of data points" Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" -InterquartileRange = "Interquartile range" +SumValues = "Sum of values" +SumSquareValues = "Sum of squared values" +InterquartileRange = "Interquartile range" \ No newline at end of file diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 80ff1fcc4..648c85169 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -5,21 +5,23 @@ BoxTab = "Caja" Values1 = "Valores V1" Values2 = "Valores V2" Values3 = "Valores V3" -Sizes1 = "Frecuencias N1" -Sizes2 = "Frecuencias N2" -Sizes3 = "Frecuencias N3" +Frequencies1 = "Frecuencias N1" +Frequencies2 = "Frecuencias N2" +Frequencies3 = "Frecuencias N3" ImportList = "Importar una lista" Interval = " Intervalo" -Size = " Frecuencia" -Frequency = "Relativa" +Frequency = " Frecuencia:" +RelativeFrequency = "Relativa:" HistogramSet = "Parámetros del histograma" RectangleWidth = "Ancho del rectangulo" BarStart = "Principio de la serie" FirstQuartile = "Primer cuartil" Median = "Mediana" ThirdQuartile = "Tercer cuartil" -TotalSize = "Población" +TotalFrequency = "Población" Range = "Rango" StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" -InterquartileRange = "Rango intercuartilo" +SumValues = "Suma" +SumSquareValues = "Suma cuadrados" +InterquartileRange = "Rango intercuartilo" \ No newline at end of file diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index f90b4d60b..c6fbf2966 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -5,21 +5,23 @@ BoxTab = "Boîte" Values1 = "Valeurs V1" Values2 = "Valeurs V2" Values3 = "Valeurs V3" -Sizes1 = "Effectifs N1" -Sizes2 = "Effectifs N2" -Sizes3 = "Effectifs N3" +Frequencies1 = "Effectifs N1" +Frequencies2 = "Effectifs N2" +Frequencies3 = "Effectifs N3" ImportList = "Importer une liste" Interval = " Intervalle " -Size = " Effectif" -Frequency = "Fréquence" +Frequency = " Effectif:" +RelativeFrequency = "Fréquence:" HistogramSet = "Réglage de l'histogramme" RectangleWidth = "Largeur des rectangles" BarStart = "Début de la série" FirstQuartile = "Premier quartile" Median = "Médiane" ThirdQuartile = "Troisième quartile" -TotalSize = "Effectif total" +TotalFrequency = "Effectif total" Range = "Étendue" StandardDeviationSigma = "Écart type" SampleStandardDeviationS = "Écart type échantillon" +SumValues = "Somme" +SumSquareValues = "Somme des carrés" InterquartileRange = "Écart interquartile" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 99aacf03d..2879e7e63 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -5,21 +5,23 @@ BoxTab = "Box Plot" Values1 = "Valori V1" Values2 = "Valori V2" Values3 = "Valori V3" -Sizes1 = "Frequenze N1" -Sizes2 = "Frequenze N2" -Sizes3 = "Frequenze N3" +Frequencies1 = "Frequenze N1" +Frequencies2 = "Frequenze N2" +Frequencies3 = "Frequenze N3" ImportList = "Importare una lista" Interval = " Intervallo " -Size = " Frequenza" -Frequency = "Relativa" +Frequency = " Frequenza:" +RelativeFrequency = "Relativa:" HistogramSet = "Regolazione dell'istogramma" RectangleWidth = "Larghezza dei rettangoli" BarStart = "Inizio della serie" FirstQuartile = "Primo quartile" Median = "Mediana" ThirdQuartile = "Terzo quartile" -TotalSize = "Dimensione totale" +TotalFrequency = "Dimensione totale" Range = "Ampiezza" StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" -InterquartileRange = "Scarto interquartile" +SumValues = "Somma" +SumSquareValues = "Somma dei quadrati" +InterquartileRange = "Scarto interquartile" \ No newline at end of file diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 3649387c1..2e43cbc20 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -5,21 +5,23 @@ BoxTab = "Box" Values1 = "Waarden V1" Values2 = "Waarden V2" Values3 = "Waarden V3" -Sizes1 = "Frequenties N1" -Sizes2 = "Frequenties N2" -Sizes3 = "Frequenties N3" +Frequencies1 = "Frequenties N1" +Frequencies2 = "Frequenties N2" +Frequencies3 = "Frequenties N3" ImportList = "Importeren uit een lijst" Interval = " Interval " -Size = " Frequentie" -Frequency = "Proportie" +Frequency = " Frequentie:" +RelativeFrequency = "Relatieve:" HistogramSet = "Histogram instellingen" RectangleWidth = "Kolombreedte" BarStart = "X start" FirstQuartile = "Eerste kwartiel" Median = "Mediaan" ThirdQuartile = "Derde kwartiel" -TotalSize = "Totale omvang" -Range = "Bereik" +TotalFrequency = "Totale frequentie" +Range = "Spreidingsbreedte" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" -InterquartileRange = "Interkwartielafstand" +SumValues = "Som" +SumSquareValues = "Som van kwadraten" +InterquartileRange = "Interkwartielafstand" \ No newline at end of file diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 3fd12f1e7..29ac75e6e 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -5,21 +5,23 @@ BoxTab = "Caixa" Values1 = "Valores V1" Values2 = "Valores V2" Values3 = "Valores V3" -Sizes1 = "Frequências N1" -Sizes2 = "Frequências N2" -Sizes3 = "Frequências N3" +Frequencies1 = "Frequências N1" +Frequencies2 = "Frequências N2" +Frequencies3 = "Frequências N3" ImportList = "Importar de uma lista" Interval = " Intervalo" -Size = " Frequência" -Frequency = "Relativa" +Frequency = " Frequência:" +RelativeFrequency = "Relativa:" HistogramSet = "Configurando o histograma" RectangleWidth = "Largura dos retângulos" BarStart = "Início da série" FirstQuartile = "Primeiro quartil" Median = "Mediana" ThirdQuartile = "Terceiro quartil" -TotalSize = "Dimensão" +TotalFrequency = "Dimensão" Range = "Amplitude" StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" -InterquartileRange = "Amplitude interquartil" +SumValues = "Somatório" +SumSquareValues = "Soma dos quadrados" +InterquartileRange = "Amplitude interquartil" \ No newline at end of file diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index b3c76a2d1..fe21be458 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -74,7 +74,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int if (i == 0) { // Display a calculation title cell I18n::Message titles[k_totalNumberOfRows] = { - I18n::Message::TotalSize, + I18n::Message::TotalFrequency, I18n::Message::Minimum, I18n::Message::Maximum, I18n::Message::Range, @@ -85,8 +85,8 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int I18n::Message::ThirdQuartile, I18n::Message::Median, I18n::Message::InterquartileRange, - I18n::Message::Sum, - I18n::Message::SquareSum, + I18n::Message::SumValues, + I18n::Message::SumSquareValues, I18n::Message::SampleStandardDeviationS}; EvenOddMessageTextCell * calcTitleCell = static_cast(cell); calcTitleCell->setMessage(titles[j-1]); diff --git a/apps/statistics/histogram_banner_view.cpp b/apps/statistics/histogram_banner_view.cpp index f4575c6f2..2bd704f47 100644 --- a/apps/statistics/histogram_banner_view.cpp +++ b/apps/statistics/histogram_banner_view.cpp @@ -7,9 +7,9 @@ namespace Statistics { HistogramBannerView::HistogramBannerView() : m_intervalLegendView(Font(), I18n::Message::Interval, 0.0f, 0.5f, TextColor(), BackgroundColor()), m_intervalView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()), - m_sizeLegendView(Font(), I18n::Message::Size, 0.0f, 0.5f, TextColor(), BackgroundColor()), + m_sizeLegendView(Font(), I18n::Message::Frequency, 0.0f, 0.5f, TextColor(), BackgroundColor()), m_sizeView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()), - m_frequencyLegendView(Font(), I18n::Message::Frequency, 1.0f, 0.5f, TextColor(), BackgroundColor()), + m_frequencyLegendView(Font(), I18n::Message::RelativeFrequency, 0.0f, 0.5f, TextColor(), BackgroundColor()), m_frequencyView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()) { } diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index 47638f57a..79f524a27 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -27,6 +27,15 @@ HistogramController::HistogramController(Responder * parentResponder, InputEvent void HistogramController::setCurrentDrawnSeries(int series) { initYRangeParameters(series); + /* The histogram's CurveView range has been updated along the Vertical axis. + * To call drawLabelsAndGraduations (in HistogramView::drawRect()), the + * CurveView must be reloaded so that labels and their values match the new + * range. + * In this situation, we update CurveView's Vertical axis, and draw horizontal + * labels, which are independent. To avoid having to call CurveView::reload(), + * axis could be taken into account when checking if labels are up to date, + * instead of using rangeChecksum(), which mixes all axis. */ + m_view.dataViewAtIndex(series)->CurveView::reload(); } StackViewController * HistogramController::stackController() { @@ -131,10 +140,6 @@ void HistogramController::reloadBannerView() { // Add Size Data numberOfChar = 0; - legend = ": "; - legendLength = strlen(legend); - strlcpy(buffer, legend, bufferSize); - numberOfChar += legendLength; double size = 0; if (selectedSeriesIndex() >= 0) { size = m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex); @@ -146,10 +151,6 @@ void HistogramController::reloadBannerView() { // Add Frequency Data numberOfChar = 0; - legend = ": "; - legendLength = strlen(legend); - strlcpy(buffer, legend, bufferSize); - numberOfChar += legendLength; if (selectedSeriesIndex() >= 0) { double frequency = size/m_store->sumOfOccurrences(selectedSeriesIndex()); numberOfChar += PoincareHelpers::ConvertFloatToText(frequency, buffer+numberOfChar, bufferSize - numberOfChar, precision); diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index c9e3a8b45..3ba5cff2b 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -1,5 +1,6 @@ #include "histogram_parameter_controller.h" #include "app.h" +#include #include #include @@ -10,7 +11,12 @@ namespace Statistics { HistogramParameterController::HistogramParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store) : FloatParameterController(parentResponder), m_cells{}, - m_store(store) + m_store(store), + m_confirmPopUpController(Invocation([](void * context, void * sender) { + Container::activeApp()->dismissModalViewController(); + ((HistogramParameterController *)context)->stackController()->pop(); + return true; + }, this)) { for (int i = 0; i < k_numberOfCells; i++) { m_cells[i].setParentResponder(&m_selectableTableView); @@ -18,6 +24,17 @@ HistogramParameterController::HistogramParameterController(Responder * parentRes } } +void HistogramParameterController::viewWillAppear() { + // Initialize temporary parameters to the extracted value. + /* setParameterAtIndex uses the value of the other parameter, so we need to + * manually set the value of the second parameter before the first call. */ + double parameterAtIndex1 = extractParameterAtIndex(1); + m_tempFirstDrawnBarAbscissa = parameterAtIndex1; + setParameterAtIndex(0, extractParameterAtIndex(0)); + setParameterAtIndex(1, parameterAtIndex1); + FloatParameterController::viewWillAppear(); +} + const char * HistogramParameterController::title() { return I18n::translate(I18n::Message::HistogramSet); } @@ -32,11 +49,36 @@ void HistogramParameterController::willDisplayCellForIndex(HighlightCell * cell, FloatParameterController::willDisplayCellForIndex(cell, index); } -double HistogramParameterController::parameterAtIndex(int index) { +bool HistogramParameterController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Back && (extractParameterAtIndex(0) != parameterAtIndex(0) || extractParameterAtIndex(1) != parameterAtIndex(1))) { + // Temporary values are different, open pop-up to confirm discarding values + Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); + return true; + } + return false; +} + +double HistogramParameterController::extractParameterAtIndex(int index) { assert(index >= 0 && index < k_numberOfCells); return index == 0 ? m_store->barWidth() : m_store->firstDrawnBarAbscissa(); } +double HistogramParameterController::parameterAtIndex(int index) { + assert(index >= 0 && index < k_numberOfCells); + return index == 0 ? m_tempBarWidth : m_tempFirstDrawnBarAbscissa; +} + +bool HistogramParameterController::confirmParameterAtIndex(int parameterIndex, double value) { + assert(parameterIndex == 0 || parameterIndex == 1); + if (parameterIndex == 0) { + // Set the bar width + m_store->setBarWidth(value); + } else { + m_store->setFirstDrawnBarAbscissa(value); + } + return true; +} + bool HistogramParameterController::setParameterAtIndex(int parameterIndex, double value) { assert(parameterIndex == 0 || parameterIndex == 1); const bool setBarWidth = parameterIndex == 0; @@ -47,13 +89,13 @@ bool HistogramParameterController::setParameterAtIndex(int parameterIndex, doubl return false; } - const double nextFirstDrawnBarAbscissa = setBarWidth ? m_store->firstDrawnBarAbscissa() : value; - const double nextBarWidth = setBarWidth ? value : m_store->barWidth(); + const double nextFirstDrawnBarAbscissa = setBarWidth ? m_tempFirstDrawnBarAbscissa : value; + const double nextBarWidth = setBarWidth ? value : m_tempBarWidth; // The number of bars cannot be above the max assert(DoublePairStore::k_numberOfSeries > 0); for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { - const double min = setBarWidth ? m_store->minValue(i) : nextFirstDrawnBarAbscissa; + const double min = std::min(m_store->minValue(i), nextFirstDrawnBarAbscissa); double numberOfBars = std::ceil((m_store->maxValue(i) - min)/nextBarWidth); if (numberOfBars > Store::k_maxNumberOfBars) { Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); @@ -63,9 +105,9 @@ bool HistogramParameterController::setParameterAtIndex(int parameterIndex, doubl if (setBarWidth) { // Set the bar width - m_store->setBarWidth(value); + m_tempBarWidth = value; } else { - m_store->setFirstDrawnBarAbscissa(value); + m_tempFirstDrawnBarAbscissa = value; } return true; } @@ -75,5 +117,12 @@ HighlightCell * HistogramParameterController::reusableParameterCell(int index, i return &m_cells[index]; } +void HistogramParameterController::buttonAction() { + // Update parameters values and proceed. + if (confirmParameterAtIndex(0, m_tempBarWidth) && confirmParameterAtIndex(1, m_tempFirstDrawnBarAbscissa)) { + FloatParameterController::buttonAction(); + } +} + } diff --git a/apps/statistics/histogram_parameter_controller.h b/apps/statistics/histogram_parameter_controller.h index 7688b7b27..78e00216c 100644 --- a/apps/statistics/histogram_parameter_controller.h +++ b/apps/statistics/histogram_parameter_controller.h @@ -3,6 +3,7 @@ #include #include "../shared/float_parameter_controller.h" +#include "../shared/discard_pop_up_controller.h" #include "store.h" namespace Statistics { @@ -10,17 +11,26 @@ namespace Statistics { class HistogramParameterController : public Shared::FloatParameterController { public: HistogramParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegateApp, Store * store); + void viewWillAppear() override; const char * title() override; int numberOfRows() const override { return 1+k_numberOfCells; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: constexpr static int k_numberOfCells = 2; + double extractParameterAtIndex(int index); + bool handleEvent(Ion::Events::Event event) override; + double parameterAtIndex(int index) override; + bool confirmParameterAtIndex(int parameterIndex, double f); + bool setParameterAtIndex(int parameterIndex, double f) override; HighlightCell * reusableParameterCell(int index, int type) override; int reusableParameterCellCount(int type) override { return k_numberOfCells; } - double parameterAtIndex(int index) override; - bool setParameterAtIndex(int parameterIndex, double f) override; + void buttonAction() override; MessageTableCellWithEditableText m_cells[k_numberOfCells]; Store * m_store; + Shared::DiscardPopUpController m_confirmPopUpController; + // Temporary parameters + double m_tempBarWidth; + double m_tempFirstDrawnBarAbscissa; }; } diff --git a/apps/statistics/statistics_context.cpp b/apps/statistics/statistics_context.cpp index 96a891d43..f781635a7 100644 --- a/apps/statistics/statistics_context.cpp +++ b/apps/statistics/statistics_context.cpp @@ -9,7 +9,7 @@ using namespace Shared; namespace Statistics { -const Expression StatisticsContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { +const Expression StatisticsContext::expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isSeriesSymbol(symbol.name(), nullptr)) { const char * seriesName = symbol.name(); assert(strlen(seriesName) == 2); diff --git a/apps/statistics/statistics_context.h b/apps/statistics/statistics_context.h index 2ca69c089..6a37d663d 100644 --- a/apps/statistics/statistics_context.h +++ b/apps/statistics/statistics_context.h @@ -8,7 +8,7 @@ namespace Statistics { class StatisticsContext : public Shared::StoreContext { public: using Shared::StoreContext::StoreContext; - const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; + const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; }; } diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 059467a97..219f4c8dc 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -1,4 +1,5 @@ #include "store.h" +#include #include #include #include @@ -88,6 +89,15 @@ bool Store::seriesIsEmpty(int i) const { return m_seriesEmpty[i]; } +bool Store::frequenciesAreInteger(int series) const { + for (const double freq : m_data[series][1]) { + if (std::fabs(freq - std::round(freq)) > DBL_EPSILON) { + return false; + } + } + return true; +} + int Store::numberOfNonEmptySeries() const { return m_numberOfNonEmptySeries; } @@ -153,8 +163,10 @@ double Store::mean(int series) const { } double Store::variance(int series) const { + /* We use the Var(X) = E[(X-E[X])^2] definition instead of Var(X) = E[X^2] - E[X]^2 + * to ensure a positive result and to minimize rounding errors */ double m = mean(series); - return squaredValueSum(series)/sumOfOccurrences(series) - m*m; + return squaredOffsettedValueSum(series, m)/sumOfOccurrences(series); } double Store::standardDeviation(int series) const { @@ -167,12 +179,38 @@ double Store::sampleStandardDeviation(int series) const { return s*standardDeviation(series); } +/* Below is the equivalence between quartiles and cumulated population, for the + * international definition of quartiles (as medians of the lower and upper + * half-lists). Let N be the total population, and k an integer. + * + * Data repartition Cumulated population + * Q1 Q3 Q1 Q3 + * + * N = 4k --- --- --- --- k 3k --- k elements + * N = 4k+1 --- ---O––– --- k 3k+1 O 1 element + * N = 4k+2 ---O--- ---O--- k+1/2 3k+1+1/2 + * N = 4k+3 ---O---O---O--- k+1/2 3k+2+1/2 + * + * Using floor(N/2)/2 as the cumulated population for Q1 and ceil(3N/2)/2 for + * Q3 gives the right results. + * + * As this method is not well defined for rational frequencies, we escape to + * the more general definition if non-integral frequencies are found. + * */ double Store::firstQuartile(int series) const { - return sortedElementAtCumulatedFrequency(series, 1.0/4.0); + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { + return sortedElementAtCumulatedFrequency(series, 1.0/4.0); + } + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::MedianOfSublist); + return sortedElementAtCumulatedPopulation(series, std::floor(sumOfOccurrences(series) / 2.) / 2., true); } double Store::thirdQuartile(int series) const { - return sortedElementAtCumulatedFrequency(series, 3.0/4.0); + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { + return sortedElementAtCumulatedFrequency(series, 3.0/4.0); + } + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::MedianOfSublist); + return sortedElementAtCumulatedPopulation(series, std::ceil(3./2. * sumOfOccurrences(series)) / 2., true); } double Store::quartileRange(int series) const { @@ -193,10 +231,15 @@ double Store::sum(int series) const { } double Store::squaredValueSum(int series) const { + return squaredOffsettedValueSum(series, 0.0); +} + +double Store::squaredOffsettedValueSum(int series, double offset) const { double result = 0; - int numberOfPairs = numberOfPairsOfSeries(series); + const int numberOfPairs = numberOfPairsOfSeries(series); for (int k = 0; k < numberOfPairs; k++) { - result += m_data[series][0][k]*m_data[series][0][k]*m_data[series][1][k]; + double value = m_data[series][0][k] - offset; + result += value*value*m_data[series][1][k]; } return result; } @@ -247,22 +290,24 @@ double Store::sumOfValuesBetween(int series, double x1, double x2) const { } double Store::sortedElementAtCumulatedFrequency(int series, double k, bool createMiddleElement) const { - // TODO: use an other algorithm (ex quickselect) to avoid quadratic complexity assert(k >= 0.0 && k <= 1.0); - double totalNumberOfElements = sumOfOccurrences(series); - double numberOfElementsAtFrequencyK = totalNumberOfElements * k; + return sortedElementAtCumulatedPopulation(series, k * sumOfOccurrences(series), createMiddleElement); +} + +double Store::sortedElementAtCumulatedPopulation(int series, double population, bool createMiddleElement) const { + // TODO: use another algorithm (ex quickselect) to avoid quadratic complexity int numberOfPairs = numberOfPairsOfSeries(series); - double bufferValues[numberOfPairs]; + double bufferValues[k_maxNumberOfPairs]; memcpy(bufferValues, m_data[series][0], numberOfPairs*sizeof(double)); int sortedElementIndex = 0; double cumulatedNumberOfElements = 0.0; - while (cumulatedNumberOfElements < numberOfElementsAtFrequencyK-DBL_EPSILON) { + while (cumulatedNumberOfElements < population-DBL_EPSILON) { sortedElementIndex = minIndex(bufferValues, numberOfPairs); bufferValues[sortedElementIndex] = DBL_MAX; cumulatedNumberOfElements += m_data[series][1][sortedElementIndex]; } - if (createMiddleElement && std::fabs(cumulatedNumberOfElements - numberOfElementsAtFrequencyK) < DBL_EPSILON) { + if (createMiddleElement && std::fabs(cumulatedNumberOfElements - population) < DBL_EPSILON) { /* There is an element of cumulated frequency k, so the result is the mean * between this element and the next element (in terms of cumulated * frequency) that has a non-null frequency. */ diff --git a/apps/statistics/store.h b/apps/statistics/store.h index 119068da8..136791a23 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -24,6 +24,7 @@ public: bool scrollToSelectedBarIndex(int series, int index); bool isEmpty() const override; bool seriesIsEmpty(int i) const override; + bool frequenciesAreInteger(int series) const; int numberOfNonEmptySeries() const override; // Calculation @@ -43,6 +44,7 @@ public: double median(int series) const; double sum(int series) const; double squaredValueSum(int series) const; + double squaredOffsettedValueSum(int series, double offset) const; constexpr static double k_maxNumberOfBars = 10000.0; constexpr static float k_displayTopMarginRatio = 0.1f; constexpr static float k_displayRightMarginRatio = 0.04f; @@ -60,6 +62,7 @@ private: double defaultValue(int series, int i, int j) const override; double sumOfValuesBetween(int series, double x1, double x2) const; double sortedElementAtCumulatedFrequency(int series, double k, bool createMiddleElement = false) const; + double sortedElementAtCumulatedPopulation(int series, double population, bool createMiddleElement = false) const; int minIndex(double * bufferValues, int bufferLength) const; // Histogram bars double m_barWidth; diff --git a/apps/statistics/store_controller.cpp b/apps/statistics/store_controller.cpp index 84c263855..c98270579 100644 --- a/apps/statistics/store_controller.cpp +++ b/apps/statistics/store_controller.cpp @@ -46,7 +46,7 @@ void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int I18n::Message valuesMessages[] = {I18n::Message::Values1, I18n::Message::Values2, I18n::Message::Values3}; mytitleCell->setText(I18n::translate(valuesMessages[seriesIndex])); } else { - I18n::Message sizesMessages[] = {I18n::Message::Sizes1, I18n::Message::Sizes2, I18n::Message::Sizes3}; + I18n::Message sizesMessages[] = {I18n::Message::Frequencies1, I18n::Message::Frequencies2, I18n::Message::Frequencies3}; mytitleCell->setText(I18n::translate(sizesMessages[seriesIndex])); } mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::SecondaryText : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GreyDark with graph/list_controller diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index e5a08264d..18d0aac24 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -1,43 +1,113 @@ #include +#include +#include #include #include #include #include "../store.h" +#include +#include + +using namespace Poincare; namespace Statistics { -void assert_value_approximately_equal_to(double d1, double d2) { - assert((std::isnan(d1) && std::isnan(d2)) - || (std::isinf(d1) && std::isinf(d2) && d1*d2 > 0 /*same sign*/) - || fabs(d1-d2) < 0.001); +void assert_value_approximately_equal_to(double d1, double d2, double precision, double reference) { + quiz_assert((std::isnan(d1) && std::isnan(d2)) + || (std::isinf(d1) && std::isinf(d2) && d1 * d2 > 0.0 /*same sign*/) + || IsApproximatelyEqual(d1, d2, precision, reference)); } -void assert_data_statictics_equal_to(double n[], double v[], int numberOfData, double sumOfOccurrences, double maxValue, double minValue, double range, double mean, double variance, double standardDeviation, double sampleStandardDeviation, double firstQuartile, double thirdQuartile, double quartileRange, double median, double sum, double squaredValueSum) { +/* SublistMethod is the method for computing quartiles used in most + * countries, which defines quartiles as the medians of the left and right + * subsets of data. + * FrequencyMethod is the method used in France and Italy, which defines the + * quartiles as the 25th and 75th percentile, in terms of cumulated + * frequencies. */ +void assert_data_statictics_equal_to( + double v[], + double n[], + int numberOfData, + double trueSumOfOccurrences, + double trueMaxValue, + double trueMinValue, + double trueRange, + double trueMean, + double trueVariance, + double trueStandardDeviation, + double trueSampleStandardDeviation, + double trueFirstQuartileSublistMethod, + double trueThirdQuartileSublistMethod, + double trueQuartileRangeSublistMethod, + double trueFirstQuartileFrequencyMethod, + double trueThirdQuartileFrequencyMethod, + double trueQuartileRangeFrequencyMethod, + double trueMedian, + double trueSum, + double trueSquaredValueSum) { Store store; int seriesIndex = 0; // Set the data in the store for (int i = 0; i < numberOfData; i++) { - store.set(n[i], seriesIndex, 0, i); - store.set(v[i], seriesIndex, 1, i); + store.set(v[i], seriesIndex, 0, i); + store.set(n[i], seriesIndex, 1, i); } + double precision = 1e-3; + + double sumOfOccurrences = store.sumOfOccurrences(seriesIndex); + double maxValue = store.maxValue(seriesIndex); + double minValue = store.minValue(seriesIndex); + double range = store.range(seriesIndex); + double mean = store.mean(seriesIndex); + double variance = store.variance(seriesIndex); + double standardDeviation = store.standardDeviation(seriesIndex); + double sampleStandardDeviation = store.sampleStandardDeviation(seriesIndex); + double median = store.median(seriesIndex); + double sum = store.sum(seriesIndex); + double squaredValueSum = store.squaredValueSum(seriesIndex); + + // Check the positive statistics + quiz_assert(range >= 0.0); + quiz_assert(variance >= 0.0); + quiz_assert(standardDeviation >= 0.0); + quiz_assert(sampleStandardDeviation >= 0.0); + quiz_assert(squaredValueSum >= 0.0); + // Compare the statistics - assert_value_approximately_equal_to(standardDeviation * standardDeviation, variance); - assert_value_approximately_equal_to(store.sumOfOccurrences(seriesIndex), sumOfOccurrences); - assert_value_approximately_equal_to(store.maxValue(seriesIndex), maxValue); - assert_value_approximately_equal_to(store.minValue(seriesIndex), minValue); - assert_value_approximately_equal_to(store.range(seriesIndex), range); - assert_value_approximately_equal_to(store.mean(seriesIndex), mean); - assert_value_approximately_equal_to(store.variance(seriesIndex), variance); - assert_value_approximately_equal_to(store.standardDeviation(seriesIndex), standardDeviation); - assert_value_approximately_equal_to(store.sampleStandardDeviation(seriesIndex), sampleStandardDeviation); - assert_value_approximately_equal_to(store.firstQuartile(seriesIndex), firstQuartile); - assert_value_approximately_equal_to(store.thirdQuartile(seriesIndex), thirdQuartile); - assert_value_approximately_equal_to(store.quartileRange(seriesIndex), quartileRange); - assert_value_approximately_equal_to(store.median(seriesIndex), median); - assert_value_approximately_equal_to(store.sum(seriesIndex), sum); - assert_value_approximately_equal_to(store.squaredValueSum(seriesIndex), squaredValueSum); + double reference = trueSquaredValueSum; + assert_value_approximately_equal_to(variance, trueVariance, precision, reference); + assert_value_approximately_equal_to(squaredValueSum, trueSquaredValueSum, precision, reference); + + reference = std::sqrt(trueSquaredValueSum); + assert_value_approximately_equal_to(trueStandardDeviation * trueStandardDeviation, trueVariance, precision, reference); + assert_value_approximately_equal_to(sumOfOccurrences, trueSumOfOccurrences, precision, reference); + assert_value_approximately_equal_to(mean, trueMean, precision, reference); + assert_value_approximately_equal_to(standardDeviation, trueStandardDeviation, precision, reference); + assert_value_approximately_equal_to(sampleStandardDeviation, trueSampleStandardDeviation, precision, reference); + assert_value_approximately_equal_to(median, trueMedian, precision, reference); + assert_value_approximately_equal_to(sum, trueSum, precision, reference); + + // Perfect match + assert_value_approximately_equal_to(maxValue, trueMaxValue, 0.0, 0.0); + assert_value_approximately_equal_to(minValue, trueMinValue, 0.0, 0.0); + assert_value_approximately_equal_to(range, trueRange, 0.0, 0.0); + + // Compare the country specific statistics + I18n::Country country; + double quartileRange; + bool shouldUseFrequencyMethod; + for (int c = 0; c < I18n::NumberOfCountries; c++) { + country = static_cast(c); + GlobalPreferences::sharedGlobalPreferences()->setCountry(country); + quartileRange = store.quartileRange(seriesIndex); + quiz_assert(quartileRange >= 0.0); + shouldUseFrequencyMethod = GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::CumulatedFrequency; + assert_value_approximately_equal_to(store.firstQuartile(seriesIndex), shouldUseFrequencyMethod ? trueFirstQuartileFrequencyMethod : trueFirstQuartileSublistMethod, precision, reference); + assert_value_approximately_equal_to(store.thirdQuartile(seriesIndex), shouldUseFrequencyMethod ? trueThirdQuartileFrequencyMethod : trueThirdQuartileSublistMethod, precision, reference); + assert_value_approximately_equal_to(quartileRange, shouldUseFrequencyMethod ? trueQuartileRangeFrequencyMethod : trueQuartileRangeSublistMethod, 0.0, 0.0); + } } QUIZ_CASE(data_statistics) { @@ -46,11 +116,11 @@ QUIZ_CASE(data_statistics) { * 1 1 1 1 */ constexpr int listLength1 = 4; - double n1[listLength1] = {1.0, 2.0, 3.0, 4.0}; - double v1[listLength1] = {1.0, 1.0, 1.0, 1.0}; + double v1[listLength1] = {1.0, 2.0, 3.0, 4.0}; + double n1[listLength1] = {1.0, 1.0, 1.0, 1.0}; assert_data_statictics_equal_to( - n1, v1, + n1, listLength1, /* sumOfOccurrences */ 4.0, /* maxValue */ 4.0, @@ -60,9 +130,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 1.25, /* standardDeviation */ 1.118, /* sampleStandardDeviation */ 1.291, - /* firstQuartile */ 1.0, - /* thirdQuartile */ 3.0, - /* quartileRange */ 2.0, + /* firstQuartileSublistMethod */ 1.5, + /* thirdQuartileSublistMethod */ 3.5, + /* quartileRangeSublistMethod */ 2.0, + /* firstQuartileFrequencyMethod */ 1.0, + /* thirdQuartileFrequencyMethod */ 3.0, + /* quartileRangeFrequencyMethod */ 2.0, /* median */ 2.5, /* sum */ 10.0, /* squaredValueSum */ 30.0); @@ -72,11 +145,11 @@ QUIZ_CASE(data_statistics) { * 1 1 1 1 1 1 1 1 1 1 1 */ constexpr int listLength2 = 11; - double n2[listLength2] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}; - double v2[listLength2] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + double v2[listLength2] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0}; + double n2[listLength2] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; assert_data_statictics_equal_to( - n2, v2, + n2, listLength2, /* sumOfOccurrences */ 11.0, /* maxValue */ 11.0, @@ -86,9 +159,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 10.0, /* standardDeviation */ 3.1623, /* sampleStandardDeviation */ 3.3166, - /* firstQuartile */ 3.0, - /* thirdQuartile */ 9.0, - /* quartileRange */ 6.0, + /* firstQuartileSublistMethod */ 3.0, + /* thirdQuartileSublistMethod */ 9.0, + /* quartileRangeSublistMethod */ 6.0, + /* firstQuartileFrequencyMethod */ 3.0, + /* thirdQuartileFrequencyMethod */ 9.0, + /* quartileRangeFrequencyMethod */ 6.0, /* median */ 6.0, /* sum */ 66.0, /* squaredValueSum */ 506.0); @@ -96,12 +172,12 @@ QUIZ_CASE(data_statistics) { /* 1 2 3 4 5 6 7 8 9 10 11 12 * 1 1 1 1 1 1 1 1 1 1 1 1 */ - constexpr int listLength3 = 13; - double n3[listLength3] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}; - double v3[listLength3] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; + constexpr int listLength3 = 12; + double v3[listLength3] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0}; + double n3[listLength3] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; assert_data_statictics_equal_to( - n3, v3, + n3, listLength3, /* sumOfOccurrences */ 12.0, /* maxValue */ 12.0, @@ -111,9 +187,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 11.917, /* standardDeviation */ 3.4521, /* sampleStandardDeviation */ 3.6056, - /* firstQuartile */ 3.0, - /* thirdQuartile */ 9.0, - /* quartileRange */ 6.0, + /* firstQuartileSublistMethod */ 3.5, + /* thirdQuartileSublistMethod */ 9.5, + /* quartileRangeSublistMethod */ 6.0, + /* firstQuartileFrequencyMethod */ 3.0, + /* thirdQuartileFrequencyMethod */ 9.0, + /* quartileRangeFrequencyMethod */ 6.0, /* median */ 6.5, /* sum */ 78.0, /* squaredValueSum */ 650.0); @@ -122,11 +201,11 @@ QUIZ_CASE(data_statistics) { * 0.2 0.05 0.3 0.0001 0.4499 */ constexpr int listLength4 = 5; - double n4[listLength4] = {1.0, 2.0, 3.0, 5.0, 10.0}; - double v4[listLength4] = {0.2, 0.05, 0.3, 0.0001, 0.4499}; + double v4[listLength4] = {1.0, 2.0, 3.0, 5.0, 10.0}; + double n4[listLength4] = {0.2, 0.05, 0.3, 0.0001, 0.4499}; assert_data_statictics_equal_to( - n4, v4, + n4, listLength4, /* sumOfOccurrences */ 1.0, /* maxValue */ 10.0, @@ -136,9 +215,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 15.6082, /* standardDeviation */ 3.9507, /* sampleStandardDeviation */ INFINITY, - /* firstQuartile */ 2.0, - /* thirdQuartile */ 10.0, - /* quartileRange */ 8.0, + /* firstQuartileSublistMethod */ 2.0, + /* thirdQuartileSublistMethod */ 10.0, + /* quartileRangeSublistMethod */ 8.0, + /* firstQuartileFrequencyMethod */ 2.0, + /* thirdQuartileFrequencyMethod */ 10.0, + /* quartileRangeFrequencyMethod */ 8.0, /* median */ 3.0, /* sum */ 5.6995, /* squaredValueSum */ 48.0925); @@ -147,11 +229,11 @@ QUIZ_CASE(data_statistics) { * 0.4 0.00005 0.9 0.4 0.5 */ constexpr int listLength5 = 5; - double n5[listLength5] = {1.0, -2.0, 3.0, 5.0, 10.0}; - double v5[listLength5] = {0.4, 0.00005, 0.9, 0.4, 0.5}; + double v5[listLength5] = {1.0, -2.0, 3.0, 5.0, 10.0}; + double n5[listLength5] = {0.4, 0.00005, 0.9, 0.4, 0.5}; assert_data_statictics_equal_to( - n5, v5, + n5, listLength5, /* sumOfOccurrences */ 2.2, /* maxValue */ 10.0, @@ -161,9 +243,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 10.06, /* standardDeviation */ 3.1719, /* sampleStandardDeviation */ 4.2947, - /* firstQuartile */ 3.0, - /* thirdQuartile */ 5.0, - /* quartileRange */ 2.0, + /* firstQuartileSublistMethod */ 3.0, + /* thirdQuartileSublistMethod */ 5.0, + /* quartileRangeSublistMethod */ 2.0, + /* firstQuartileFrequencyMethod */ 3.0, + /* thirdQuartileFrequencyMethod */ 5.0, + /* quartileRangeFrequencyMethod */ 2.0, /* median */ 3.0, /* sum */ 10.1, /* squaredValueSum */ 68.500); @@ -172,11 +257,11 @@ QUIZ_CASE(data_statistics) { * 4 5 3 1 9 */ constexpr int listLength6 = 6; - double n6[listLength6] = {-7.0, -10.0, 1.0, 2.0, 5.0, -2.0}; - double v6[listLength6] = {4.0, 5.0, 3.0, 0.5, 1.0, 9.0}; + double v6[listLength6] = {-7.0, -10.0, 1.0, 2.0, 5.0, -2.0}; + double n6[listLength6] = {4.0, 5.0, 3.0, 0.5, 1.0, 9.0}; assert_data_statictics_equal_to( - n6, v6, + n6, listLength6, /* sumOfOccurrences */ 22.5, /* maxValue */ 5.0, @@ -186,9 +271,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 18.9155, /* standardDeviation */ 4.3492, /* sampleStandardDeviation */ 4.4492, - /* firstQuartile */ -7.0, - /* thirdQuartile */ -2.0, - /* quartileRange */ 5.0, + /* firstQuartileSublistMethod */ -7.0, + /* thirdQuartileSublistMethod */ -2.0, + /* quartileRangeSublistMethod */ 5.0, + /* firstQuartileFrequencyMethod */ -7.0, + /* thirdQuartileFrequencyMethod */ -2.0, + /* quartileRangeFrequencyMethod */ 5.0, /* median */ -2.0, /* sum */ -87.0, /* squaredValueSum */ 762.0); @@ -197,11 +285,11 @@ QUIZ_CASE(data_statistics) { * 1 1 1 0 0 0 1 */ constexpr int listLength7 = 7; - double n7[listLength7] = {1.0, 1.0, 1.0, 10.0, 3.0, -1.0, 3.0}; - double v7[listLength7] = {1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0}; + double v7[listLength7] = {1.0, 1.0, 1.0, 10.0, 3.0, -1.0, 3.0}; + double n7[listLength7] = {1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0}; assert_data_statictics_equal_to( - n7, v7, + n7, listLength7, /* sumOfOccurrences */ 4.0, /* maxValue */ 3.0, @@ -211,9 +299,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 0.75, /* standardDeviation */ 0.866, /* sampleStandardDeviation */ 1.0, - /* firstQuartile */ 1.0, - /* thirdQuartile */ 1.0, - /* quartileRange */ 0.0, + /* firstQuartileSublistMethod */ 1.0, + /* thirdQuartileSublistMethod */ 2.0, + /* quartileRangeSublistMethod */ 1.0, + /* firstQuartileFrequencyMethod */ 1.0, + /* thirdQuartileFrequencyMethod */ 1.0, + /* quartileRangeFrequencyMethod */ 0.0, /* median */ 1.0, /* sum */ 6.0, /* squaredValueSum */ 12.0); @@ -222,11 +313,11 @@ QUIZ_CASE(data_statistics) { * 0 1 0 1 */ constexpr int listLength8 = 4; - double n8[listLength8] = {1.0, 2.0, 3.0, 4.0}; - double v8[listLength8] = {0.0, 1.0, 0.0, 1.0}; + double v8[listLength8] = {1.0, 2.0, 3.0, 4.0}; + double n8[listLength8] = {0.0, 1.0, 0.0, 1.0}; assert_data_statictics_equal_to( - n8, v8, + n8, listLength8, /* sumOfOccurrences */ 2.0, /* maxValue */ 4.0, @@ -236,12 +327,43 @@ QUIZ_CASE(data_statistics) { /* variance */ 1.0, /* standardDeviation */ 1.0, /* sampleStandardDeviation */ 1.414, - /* firstQuartile */ 2.0, - /* thirdQuartile */ 4.0, - /* quartileRange */ 2.0, + /* firstQuartileSublistMethod */ 2.0, + /* thirdQuartileSublistMethod */ 4.0, + /* quartileRangeSublistMethod */ 2.0, + /* firstQuartileFrequencyMethod */ 2.0, + /* thirdQuartileFrequencyMethod */ 4.0, + /* quartileRangeFrequencyMethod */ 2.0, /* median */ 3.0, /* sum */ 6.0, /* squaredValueSum */ 20.0); + + /* -996.85840734641 + * 9 */ + + constexpr int listLength9 = 1; + double v9[listLength9] = {-996.85840734641}; + double n9[listLength9] = {9}; + assert_data_statictics_equal_to( + v9, + n9, + listLength9, + /* sumOfOccurrences */ 9.0, + /* maxValue */ -996.85840734641, + /* minValue */ -996.85840734641, + /* range */ 0.0, + /* mean */ -996.85840734641, + /* variance */ 0.0, + /* standardDeviation */ 0.0, + /* sampleStandardDeviation */ 0.0, + /* firstQuartileSublistMethod */ -996.85840734641, + /* thirdQuartileSublistMethod */ -996.85840734641, + /* quartileRangeSublistMethod */ 0.0, + /* firstQuartileFrequencyMethod */ -996.85840734641, + /* thirdQuartileFrequencyMethod */ -996.85840734641, + /* quartileRangeFrequencyMethod */ 0.0, + /* median */ -996.85840734641, + /* sum */ -8971.72566611769, + /* squaredValueSum */ 8943540.158675); } } diff --git a/apps/title_bar_view.cpp b/apps/title_bar_view.cpp index fd1c6e9b9..a842b394e 100644 --- a/apps/title_bar_view.cpp +++ b/apps/title_bar_view.cpp @@ -163,6 +163,7 @@ void TitleBarView::refreshPreferences() { I18n::Message::Deg : (angleUnit == Preferences::AngleUnit::Radian ? I18n::Message::Rad : I18n::Message::Gon); numberOfChar += strlcpy(buffer+numberOfChar, I18n::translate(angleMessage), bufferSize - numberOfChar); + assert(numberOfChar < bufferSize-1); } m_preferenceView.setText(buffer); diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 5111cd93a..f13079c05 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -21,7 +21,10 @@ UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Pikometer" UnitDistanceAstronomicalUnit = "Astronomische Einheit" UnitDistanceLightYear = "Lichtjahr" -UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Meile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Fuß" +UnitDistanceInch = "Inch" UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramm" UnitMassGram = "Gramm" @@ -29,45 +32,31 @@ UnitMassGramMilli = "Milligramm" UnitMassGramMicro = "Mikrogramm" UnitMassGramNano = "Nanogramm" UnitDistanceImperialMenu = "US Customary" -UnitDistanceInch = "Inch" -UnitDistanceFoot = "Foot" -UnitDistanceYard = "Yard" -UnitDistanceMile = "Mile" UnitMassImperialMenu = "US Customary" -UnitMassPound = "Pound" -UnitMassOunce = "Ounce" UnitMassTon = "US Ton" UnitMassTonne = "Tonne" +UnitMassOunce = "Unze" +UnitMassPound = "Pfund" +UnitMassShortTon = "Amerikanische Tonne" +UnitMassLongTon = "Britische Tonne" UnitCurrentMenu = "Elektrischer Strom" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Mikroampere" UnitTemperatureMenu = "Temperatur" -UnitTemperatureKelvin = "Kelvin" UnitAmountMenu = "Stoffmenge" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" UnitAmountMoleMicro = "Mikromol" UnitLuminousIntensityMenu = "Lichtstärke" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequenz" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Kraft" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Druck" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hektopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energie" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" @@ -75,40 +64,38 @@ UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" UnitEnergyElectronVoltMilli = "Millielectronvolt" UnitPowerMenu = "Leistung" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Mikrowatt" UnitElectricChargeMenu = "Elektrische Ladung" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Elektrische Spannung" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Mikrovolt" UnitCapacitanceMenu = "Elektrische Kapazität" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Mikrofarad" UnitResistanceMenu = "Elektrischer Widerstand" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Elektrische Leitfähigkeit" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Magnetfeld" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Elektrische Induktivität" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Fläche" +UnitSurfaceAcre = "Morgen" UnitSurfaceHectar = "Hektar" UnitVolumeMenu = "Volumen" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deziliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" +UnitVolumeTeaspoon = "Teelöffel" +UnitVolumeTablespoon= "Esslöffel" +UnitVolumeFluidOunce = "Flüssigunze" +UnitVolumeCup = "Tasse" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallone" +UnitMetricMenu = "Metrisch" +UnitImperialMenu = "Empire" Toolbox = "Werkzeugkasten" AbsoluteValue = "Betragsfunktion" NthRoot = "n-te Wurzel" @@ -143,6 +130,12 @@ Determinant = "Determinante" Transpose = "Transponierte" Trace = "Spur" Dimension = "Größe" +RowEchelonForm = "Stufenform" +ReducedRowEchelonForm = "Reduzierte Stufenform" +Vectors = "Vektoren" +Dot = "Skalarprodukt" +Cross = "Kreuzprodukt" +NormVector = "Norm" Sort = "Sortieren aufsteigend" InvSort = "Sortieren absteigend" Maximum = "Maximalwert" @@ -164,10 +157,10 @@ RandomAndApproximation = "Zufall und Näherung" RandomFloat = "Dezimalzahl in [0,1]" RandomInteger = "Zufällige ganze Zahl in [a,b]" PrimeFactorDecomposition = "Primfaktorzerlegung" -NormCDF = "P(X +#include + +class ButtonState : public Button { +public: + using Button::Button; + void setState(bool state) { m_stateView.setState(state); } + KDSize minimalSizeForOptimalDisplay() const override; + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + // Dot right margin. + constexpr static KDCoordinate k_stateMargin = 9; + // Dot vertical position offset. + constexpr static KDCoordinate k_verticalOffset = 5; + int numberOfSubviews() const override { return 2; } + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + ToggleableDotView m_stateView; +}; + +#endif diff --git a/escher/include/escher/clipboard.h b/escher/include/escher/clipboard.h index 46a105e9a..63431faec 100644 --- a/escher/include/escher/clipboard.h +++ b/escher/include/escher/clipboard.h @@ -3,15 +3,43 @@ #include #include +#include class Clipboard { public: static Clipboard * sharedClipboard(); void store(const char * storedText, int length = -1); - const char * storedText() const { return m_textBuffer; } + const char * storedText(); void reset(); + void enterPython() { replaceCharForPython(true); } + void exitPython() { replaceCharForPython(false); } + static constexpr int k_bufferSize = TextField::maxBufferSize(); private: - char m_textBuffer[TextField::maxBufferSize()]; + char m_textBuffer[k_bufferSize]; + void replaceCharForPython(bool entersPythonApp); +}; + + +/* The order in which the text pairs are stored is important. Indeed when leaving + * python, the text stored in the buffer is converted into an input for other + * apps. Therefore if we want to convert "3**3" into "3^3", the function must + * look for "**" paterns before "*". Otherwise, we will get "3××3". */ +static constexpr int NumberOfPythonTextPairs = 12; +static constexpr UTF8Helper::TextPair PythonTextPairs[NumberOfPythonTextPairs] = { + UTF8Helper::TextPair("√(\x11)", "sqrt(\x11)", true), + UTF8Helper::TextPair("ℯ^(\x11)", "exp(\x11)", true), + UTF8Helper::TextPair("log(\x11)", "log10(\x11)", true), + UTF8Helper::TextPair("ln(\x11)", "log(\x11)", true), + UTF8Helper::TextPair("ᴇ", "e"), + UTF8Helper::TextPair("𝐢", "1j"), + /* Since textPairs are also used to pair events, we need to keep both ^2 and ^ + * to get the desired behavior in python when using power or square key*/ + UTF8Helper::TextPair("^2", "**2"), + UTF8Helper::TextPair("^", "**"), + UTF8Helper::TextPair("π", "pi"), + UTF8Helper::TextPair("×", "*"), + UTF8Helper::TextPair("·", "*"), + UTF8Helper::TextPair("][", "], ["), }; #endif diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index a79da0bdb..7ecaf57d4 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -26,6 +26,8 @@ public: bool inputViewHeightDidChange(); bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false); void setLayoutInsertionCursorEvent(Ion::Events::Event event) { m_layoutField.setInsertionCursorEvent(event); } + size_t moveCursorAndDumpContent(char * buffer, size_t bufferSize); + void restoreContent(const char * buffer, size_t size); /* View */ int numberOfSubviews() const override { return 1; } diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index a835eb273..76666fb41 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -26,6 +26,7 @@ public: KDSize minimalSizeForOptimalDisplay() const override; KDPoint drawingOrigin() const; KDPoint absoluteDrawingOrigin() const; + bool layoutHasNode() const { return Poincare::TreeNode::IsValidIdentifier(m_layout.identifier()) && !m_layout.wasErasedByException(); } protected: /* Warning: we do not need to delete the previous expression layout when * deleting object or setting a new expression layout. Indeed, the expression diff --git a/escher/include/escher/i18n.h b/escher/include/escher/i18n.h index 301b7ef9b..11de7fddb 100644 --- a/escher/include/escher/i18n.h +++ b/escher/include/escher/i18n.h @@ -5,7 +5,7 @@ namespace I18n { enum class Message : uint16_t; - enum class Language : uint16_t; + enum class Language : uint8_t; const char * translate(Message m); int numberOfLanguages(); } diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index d8c4694b1..bd36e8b14 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -32,9 +32,11 @@ public: } bool hasText() const { return layout().hasText(); } Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); } + bool layoutHasNode() const { return m_contentView.expressionView()->layoutHasNode(); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; void putCursorRightOfLayout(); void setInsertionCursorEvent(Ion::Events::Event event) { m_insertionCursorEvent = event; } + void setLayout(Poincare::Layout newLayout); // ScrollableView void setBackgroundColor(KDColor c) override { diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index 6711c5eb1..44ac41eca 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -66,6 +66,7 @@ protected: virtual bool selectSubMenu(int selectedRow); virtual bool returnToPreviousMenu(); virtual bool selectLeaf(int selectedRow) = 0; + virtual int stackRowOffset() const { return 0; } InputEventHandler * sender() { return m_sender; } virtual HighlightCell * leafCellAtIndex(int index) = 0; virtual HighlightCell * nodeCellAtIndex(int index) = 0; diff --git a/escher/include/escher/pop_up_controller.h b/escher/include/escher/pop_up_controller.h new file mode 100644 index 000000000..0374d68d2 --- /dev/null +++ b/escher/include/escher/pop_up_controller.h @@ -0,0 +1,46 @@ +#ifndef ESCHER_POP_UP_CONTROLLER_H +#define ESCHER_POP_UP_CONTROLLER_H + +#include +#include + + +class HighContrastButton : public Button { +public: + using Button::Button; + KDColor highlightedBackgroundColor() const override { return Palette::ButtonBackgroundSelectedHighContrast; } +}; + +class PopUpController : public ViewController { +public: + PopUpController(int numberOfLines, Invocation OkInvocation); + View * view() override; + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; +protected: + class ContentView : public View, public Responder { + public: + ContentView(Responder * parentResponder, int numberOfLines, Invocation okInvocation); + void drawRect(KDContext * ctx, KDRect rect) const override { ctx->fillRect(bounds(), KDColorBlack); } + void setSelectedButton(int selectedButton); + int selectedButton(); + void setMessage(int index, I18n::Message message); + private: + constexpr static KDCoordinate k_buttonMargin = 10; + constexpr static KDCoordinate k_buttonHeight = 20; + constexpr static KDCoordinate k_topMargin = 8; + constexpr static KDCoordinate k_paragraphHeight = 20; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + HighContrastButton m_cancelButton; + HighContrastButton m_okButton; + MessageTextView m_warningTextView; + const int m_numberOfLines; + constexpr static int k_maxNumberOfLines = 4; + MessageTextView m_messageTextViews[k_maxNumberOfLines]; + }; + ContentView m_contentView; +}; + +#endif diff --git a/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index 6e4fb7ac0..7e5c30a1a 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -30,6 +30,7 @@ public: void willExitResponderChain(Responder * nextFirstResponder) override; void deselectTable(bool withinTemporarySelection = false); bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool withinTemporarySelection = false); + bool selectCellAtClippedLocation(int i, int j, bool setFirstResponder = true, bool withinTemporarySelection = false); HighlightCell * selectedCell(); protected: void unhighlightSelectedCell(); diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index 29034a94b..0881139d1 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -46,7 +46,7 @@ private: class ControllerView : public View { public: ControllerView(); - void shouldDisplayStackHearders(bool shouldDisplay); + void shouldDisplayStackHeaders(bool shouldDisplay); int8_t numberOfStacks() const { return m_numberOfStacks; } void setContentView(View * view); void pushStack(Frame frame); diff --git a/escher/include/escher/switch_view.h b/escher/include/escher/switch_view.h index 482697cc5..23fa375f4 100644 --- a/escher/include/escher/switch_view.h +++ b/escher/include/escher/switch_view.h @@ -1,22 +1,21 @@ #ifndef ESCHER_SWITCH_VIEW_H #define ESCHER_SWITCH_VIEW_H -#include +#include +#include -class SwitchView : public TransparentView { + +class SwitchView final : public ToggleableView { public: - SwitchView(); - bool state(); - void setState(bool state); - void drawRect(KDContext * ctx, KDRect rect) const override; - KDSize minimalSizeForOptimalDisplay() const override; + using ToggleableView::ToggleableView; /* k_switchHeight and k_switchWidth are the dimensions of the switch * (including the outline of the switch). */ constexpr static KDCoordinate k_onOffSize = 12; constexpr static KDCoordinate k_switchHeight = 12; constexpr static KDCoordinate k_switchWidth = 22; + KDSize minimalSizeForOptimalDisplay() const override { return KDSize(k_switchWidth, k_switchHeight); } private: - bool m_state; + void drawRect(KDContext * ctx, KDRect rect) const override; }; #endif diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 41c177a41..e8aa34aa5 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -94,6 +94,9 @@ protected: size_t textLength() const { return strlen(m_buffer); } + int textLineTotal() const { + return positionAtPointer(m_buffer+textLength()).line(); + } private: char * m_buffer; size_t m_bufferSize; @@ -131,8 +134,11 @@ protected: ContentView * contentView() { return static_cast(TextInput::contentView()); } private: - void selectUpDown(bool up); + void selectUpDown(bool up, int step); TextAreaDelegate * m_delegate; + // Due to rect size limitation, the editor cannot display more than 1800 lines + constexpr static int k_maxLines = 999; + constexpr static int k_maxLineChars = 3000; }; #endif diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 34c75dcd8..0de69538c 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -98,9 +98,10 @@ protected: return const_cast(nonEditableContentView()); } virtual const ContentView * nonEditableContentView() const = 0; - bool moveCursorLeft(); - bool moveCursorRight(); - bool selectLeftRight(bool left, bool all); // While indicates if all the text on the left/right should be selected + bool moveCursorLeft(int step = 1); + bool moveCursorRight(int step = 1); + // all indicates if all the text on the left/right should be selected + bool selectLeftRight(bool left, bool all, int step = 1); private: virtual void willSetCursorLocation(const char * * location) {} virtual bool privateRemoveEndOfLine(); diff --git a/escher/include/escher/toggleable_dot_view.h b/escher/include/escher/toggleable_dot_view.h new file mode 100644 index 000000000..c6ec4d5c0 --- /dev/null +++ b/escher/include/escher/toggleable_dot_view.h @@ -0,0 +1,16 @@ +#ifndef ESCHER_TOGGLEABLE_DOT_VIEW_H +#define ESCHER_TOGGLEABLE_DOT_VIEW_H + +#include + +class ToggleableDotView final : public ToggleableView { +public: + using ToggleableView::ToggleableView; + /* k_dotSize is the dimensions of the toggle dot */ + constexpr static KDCoordinate k_dotSize = 8; + KDSize minimalSizeForOptimalDisplay() const override { return KDSize(k_dotSize, k_dotSize); } +private: + void drawRect(KDContext * ctx, KDRect rect) const override; +}; + +#endif \ No newline at end of file diff --git a/escher/include/escher/toggleable_view.h b/escher/include/escher/toggleable_view.h new file mode 100644 index 000000000..34a7ef996 --- /dev/null +++ b/escher/include/escher/toggleable_view.h @@ -0,0 +1,15 @@ +#ifndef ESCHER_TOGGLEABLE_VIEW_H +#define ESCHER_TOGGLEABLE_VIEW_H + +#include + +class ToggleableView : public TransparentView { +public: + ToggleableView() : m_state(true) {} + bool state() { return m_state; } + void setState(bool state); +protected: + bool m_state; +}; + +#endif \ No newline at end of file diff --git a/escher/include/escher/toolbox.h b/escher/include/escher/toolbox.h index e38df51ed..f128f54eb 100644 --- a/escher/include/escher/toolbox.h +++ b/escher/include/escher/toolbox.h @@ -25,9 +25,12 @@ protected: bool returnToPreviousMenu() override; virtual int maxNumberOfDisplayedRows() = 0; virtual const ToolboxMessageTree * rootModel() const = 0; + /* indexAfterFork is called when a fork-node is encountered to choose which + * of its children should be selected, based on external context. */ + virtual int indexAfterFork() const { assert(false); return 0; }; MessageTableCellWithMessage * leafCellAtIndex(int index) override = 0; MessageTableCellWithChevron * nodeCellAtIndex(int index) override = 0; - mutable ToolboxMessageTree * m_messageTreeModel; + mutable const ToolboxMessageTree * m_messageTreeModel; /* m_messageTreeModel points at the messageTree of the tree (describing the * whole model) where we are located. It enables to know which rows are leaves * and which are subtrees. */ diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index 835bccde5..ba8ac5005 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -10,36 +10,67 @@ public: label, text, (insertedText == (I18n::Message)0) ? label : insertedText, - nullptr, + static_cast(0), 0, stripInsertedText); }; template - constexpr static ToolboxMessageTree Node(I18n::Message label, const ToolboxMessageTree (&children)[N]) { + constexpr static ToolboxMessageTree Node(I18n::Message label, const ToolboxMessageTree (&children)[N], bool fork = false) { return ToolboxMessageTree( label, (I18n::Message)0, (I18n::Message)0, children, - N, + fork ? -N : N, true); } - const MessageTree * childAtIndex(int index) const override { return &m_children[index]; } + template + constexpr static ToolboxMessageTree Node(I18n::Message label, const ToolboxMessageTree * (&children)[N], bool fork = false) { + return ToolboxMessageTree( + label, + (I18n::Message)0, + (I18n::Message)0, + children, + fork ? -N : N, + true); + } + const MessageTree * childAtIndex(int index) const override { + return m_childrenConsecutive ? m_children.m_direct + index : m_children.m_indirect[index]; + } I18n::Message text() const { return m_text; } I18n::Message insertedText() const { return m_insertedText; } bool stripInsertedText() const { return m_stripInsertedText; } + bool isFork() const { return numberOfChildren() < 0; } private: constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree * children, int numberOfChildren, bool stripInsertedText) : MessageTree(label, numberOfChildren), m_children(children), m_text(text), m_insertedText(insertedText), - m_stripInsertedText(stripInsertedText) + m_stripInsertedText(stripInsertedText), + m_childrenConsecutive(true) {} - const ToolboxMessageTree * m_children; + constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree ** children, int numberOfChildren, bool stripInsertedText) : + MessageTree(label, numberOfChildren), + m_children(children), + m_text(text), + m_insertedText(insertedText), + m_stripInsertedText(stripInsertedText), + m_childrenConsecutive(false) + {} + + union Children { + public: + constexpr Children(const ToolboxMessageTree * children) : m_direct(children) {} + constexpr Children(const ToolboxMessageTree ** children) : m_indirect(children) {} + const ToolboxMessageTree * m_direct; + const ToolboxMessageTree ** m_indirect; + }; + const Children m_children; I18n::Message m_text; I18n::Message m_insertedText; bool m_stripInsertedText; + const bool m_childrenConsecutive; }; #endif diff --git a/escher/src/button_row_controller.cpp b/escher/src/button_row_controller.cpp index 0d62fd3f5..021c00038 100644 --- a/escher/src/button_row_controller.cpp +++ b/escher/src/button_row_controller.cpp @@ -72,7 +72,7 @@ void ButtonRowController::ContentView::layoutSubviews(bool force) { KDCoordinate widthMargin = 0; KDCoordinate buttonHeightMargin = 0; KDCoordinate buttonHeight = rowHeight; - if (m_style == Style::EmbossedGrey) { + if (m_style == Style::EmbossedGray) { KDCoordinate totalButtonWidth = 0; for (int i = 0; i < nbOfButtons; i++) { Button * button = buttonAtIndex(i); diff --git a/escher/src/button_state.cpp b/escher/src/button_state.cpp new file mode 100644 index 000000000..3541eb499 --- /dev/null +++ b/escher/src/button_state.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +View * ButtonState::subviewAtIndex(int index) { + assert(index >= 0 && index < 2); + if (index == 0) { + return &m_messageTextView; + } + return &m_stateView; +} + +void ButtonState::layoutSubviews(bool force) { + KDSize textSize = Button::minimalSizeForOptimalDisplay(); + KDRect textRect = KDRect(0, 0, textSize.width(), bounds().height()); + // State view will be vertically centered and aligned on the left + KDSize stateSize = m_stateView.minimalSizeForOptimalDisplay(); + KDRect stateRect = KDRect(textSize.width(), k_verticalOffset, stateSize.width(), stateSize.height()); + + m_messageTextView.setFrame(textRect, force); + m_stateView.setFrame(stateRect, force); +} + +void ButtonState::drawRect(KDContext * ctx, KDRect rect) const { + KDColor backColor = isHighlighted() ? highlightedBackgroundColor() : Palette::ButtonBackground; + ctx->fillRect(bounds(), backColor); +} + +KDSize ButtonState::minimalSizeForOptimalDisplay() const { + KDSize textSize = Button::minimalSizeForOptimalDisplay(); + KDSize stateSize = m_stateView.minimalSizeForOptimalDisplay(); + return KDSize( + textSize.width() + stateSize.width() + k_stateMargin, + std::max(textSize.height(), stateSize.height())); +} diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index a8551bc2b..2dd6e54ab 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include static Clipboard s_clipboard; @@ -9,8 +11,45 @@ Clipboard * Clipboard::sharedClipboard() { void Clipboard::store(const char * storedText, int length) { strlcpy(m_textBuffer, storedText, length == -1 ? TextField::maxBufferSize() : std::min(TextField::maxBufferSize(), length + 1)); + Ion::Clipboard::write(m_textBuffer); +} + +const char * Clipboard::storedText() { + const char * systemText = Ion::Clipboard::read(); + if (systemText) { + return systemText; + } + + /* In order to allow copy/paste of empty formulas, we need to add empty + * layouts between empty system parenthesis. This way, when the expression + * is parsed, it is recognized as a proper formula and appears with the correct + * visual layout. + * Without this process, copying an empty integral then pasting it gives : + * int((), x, (), ()) instead of drawing an empty integral. + * + * Furthermore, in case the user switches from linear to natural writing mode + * we need to add an empty layout between parenthesis to allow proper layout + * construction. */ + constexpr int numberOfPairs = 6; + constexpr UTF8Helper::TextPair textPairs[numberOfPairs] = { + UTF8Helper::TextPair("()", "(\x11)"), + UTF8Helper::TextPair("[]", "[\x11]"), + UTF8Helper::TextPair("[,", "[\x11,"), + UTF8Helper::TextPair(",,", ",\x11,"), + UTF8Helper::TextPair(",]", ",\x11]"), + UTF8Helper::TextPair("\x12\x13", "\x12\x11\x13"), + }; + + UTF8Helper::TryAndReplacePatternsInStringByPatterns(m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *) &textPairs, numberOfPairs, true); + return m_textBuffer; } void Clipboard::reset() { strlcpy(m_textBuffer, "", 1); + /* As we do not want to empty the user's computer's clipboard when entering + * exam mode, we do not reset Ion::Clipboard. */ +} + +void Clipboard::replaceCharForPython(bool entersPythonApp) { + UTF8Helper::TryAndReplacePatternsInStringByPatterns((char *)m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *)&PythonTextPairs, NumberOfPythonTextPairs, entersPythonApp); } diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp index 47eca1a43..b151e5687 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -121,3 +121,58 @@ KDCoordinate ExpressionField::inputViewHeight() const { std::min(k_maximalHeight, std::max(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); } + +size_t ExpressionField::moveCursorAndDumpContent(char * buffer, size_t bufferSize) { + size_t size; + size_t returnValue; + char * currentContent; + if (editionIsInTextField()) { + size = strlen(m_textField.draftTextBuffer()) + 1; + m_textField.setCursorLocation(m_textField.cursorLocation() + size - 1); + currentContent = m_textField.draftTextBuffer(); + /* We take advantage of the fact that draftTextBuffer is null terminated to + * use a size of 0 as a shorthand for "The buffer contains raw text instead + * of layouts", since the size of a layout is at least 32 (the size of an + * empty horizontal layout). This way, we can detect when the edition mode + * has changed and invalidate the data.*/ + returnValue = 0; + } else { + /* moveCursorAndDumpContent will be called whenever Calculation exits, + * even when an exception occurs. We thus need to check the validity of the + * layout we are dumping (m_layoutField.contentView.expressionView.layout). + * However, attempting to get a handle on a layout that has been erased + * will crash the program. We need the check to be performed on the + * original object in expressionView. */ + if (!m_layoutField.layoutHasNode()) { + buffer[0] = 0; + return 0; + } + m_layoutField.putCursorRightOfLayout(); + size = m_layoutField.layout().size(); + currentContent = reinterpret_cast(m_layoutField.layout().node()); + returnValue = size; + } + if (size > bufferSize - 1) { + buffer[0] = 0; + return 0; + } + memcpy(buffer, currentContent, size); + return returnValue; +} + +void ExpressionField::restoreContent(const char * buffer, size_t size) { + if (editionIsInTextField()) { + if (size != 0 || buffer[0] == 0) { + /* A size other than 0 means the buffer contains Layout information + * (instead of raw text) that we don't want to restore. This is most + * likely because the edition mode has been changed between use. */ + return; + } + setText(buffer); + return; + } + if (size == 0) { + return; + } + m_layoutField.setLayout(Poincare::Layout::LayoutFromAddress(buffer, size)); +} diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 77ee13275..cfdbea00c 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -43,9 +43,9 @@ bool LayoutField::ContentView::setEditing(bool isEditing) { void LayoutField::ContentView::useInsertionCursor() { if (m_insertionCursor.isDefined()) { - m_cursor.layout().removeGreySquaresFromAllMatrixAncestors(); + m_cursor.layout().removeGraySquaresFromAllMatrixAncestors(); m_cursor = m_insertionCursor; - m_cursor.layout().addGreySquaresToAllMatrixAncestors(); + m_cursor.layout().addGraySquaresToAllMatrixAncestors(); } } @@ -241,7 +241,7 @@ void LayoutField::ContentView::deleteSelection() { void LayoutField::ContentView::updateInsertionCursor() { if (!m_insertionCursor.isDefined()) { Layout l = m_cursor.layout(); - if (l.type() == LayoutNode::Type::EmptyLayout && static_cast(l).color() == EmptyLayoutNode::Color::Grey) { + if (l.type() == LayoutNode::Type::EmptyLayout && static_cast(l).color() == EmptyLayoutNode::Color::Gray) { // Don't set m_insertionCursor pointing to a layout which might disappear return; } @@ -262,7 +262,11 @@ void LayoutField::ContentView::layoutSubviews(bool force) { void LayoutField::ContentView::layoutCursorSubview(bool force) { if (!m_isEditing) { - m_cursorView.setFrame(KDRectZero, force); + /* We keep track of the cursor's position to prevent the input field from + * scrolling to the beginning when switching to the history. This way, + * when calling scrollToCursor after layoutCursorSubview, we don't lose + * sight of the cursor. */ + m_cursorView.setFrame(KDRect(cursorRect().x(), cursorRect().y(), 0, 0), force); return; } KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin(); @@ -314,6 +318,14 @@ void LayoutField::clearLayout() { reloadScroll(); // Put the scroll to offset 0 } +void LayoutField::setLayout(Poincare::Layout newLayout) { + m_contentView.clearLayout(); + KDSize previousSize = minimalSizeForOptimalDisplay(); + const_cast(m_contentView.expressionView())->setLayout(newLayout); + putCursorRightOfLayout(); + reload(previousSize); +} + Context * LayoutField::context() const { return (m_delegate != nullptr) ? m_delegate->context() : nullptr; } @@ -327,7 +339,8 @@ CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) { } void LayoutField::putCursorRightOfLayout() { - m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixAncestors(); + m_contentView.cursor()->layout().removeGraySquaresFromAllMatrixAncestors(); + m_contentView.cursor()->showEmptyLayoutIfNeeded(); m_contentView.setCursor(LayoutCursor(m_contentView.expressionView()->layout(), LayoutCursor::Position::Right)); } @@ -442,11 +455,11 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { Layout p = selectStart->parent(); assert(p == selectEnd->parent()); assert(p.type() == LayoutNode::Type::HorizontalLayout); - removedSquares = p.removeGreySquaresFromAllMatrixChildren(); + removedSquares = p.removeGraySquaresFromAllMatrixChildren(); } else { - removedSquares = selectStart->removeGreySquaresFromAllMatrixChildren(); + removedSquares = selectStart->removeGraySquaresFromAllMatrixChildren(); } - shouldRecomputeLayout = m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout; + shouldRecomputeLayout = m_contentView.cursor()->layout().removeGraySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout; } } else if (privateHandleEvent(event)) { shouldRecomputeLayout = true; @@ -595,7 +608,8 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should return true; } LayoutCursor result; - result = m_contentView.cursor()->cursorAtDirection(DirectionForMoveEvent(event), shouldRecomputeLayout); + int step = Ion::Events::repetitionFactor(); + result = m_contentView.cursor()->cursorAtDirection(DirectionForMoveEvent(event), shouldRecomputeLayout, false, step); if (result.isDefined()) { if (eventShouldUpdateInsertionCursor(event)) { m_contentView.updateInsertionCursor(); @@ -631,18 +645,26 @@ bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * s if (!IsSelectionEvent(event)) { return false; } - Layout addedSelection; - LayoutCursor result = m_contentView.cursor()->selectAtDirection( - DirectionForSelectionEvent(event), - shouldRecomputeLayout, - &addedSelection - ); - if (addedSelection.isUninitialized()) { - return false; + int step = Ion::Events::repetitionFactor(); + // Selection is handled one step at a time. Repeat selection for each step. + for (int i = 0; i < step; ++i) { + Layout addedSelection; + LayoutCursor result = m_contentView.cursor()->selectAtDirection( + DirectionForSelectionEvent(event), + shouldRecomputeLayout, + &addedSelection + ); + if (addedSelection.isUninitialized()) { + // Successful event if at least one step succeeded. + return i > 0; + } + /* TODO : addSelection() is built so that it should be called steps by steps + * It could be reworked to handle selection with steps > 1 and match + * text_input's implementation */ + m_contentView.addSelection(addedSelection); + assert(result.isDefined()); + m_contentView.setCursor(result); } - m_contentView.addSelection(addedSelection); - assert(result.isDefined()); - m_contentView.setCursor(result); return true; } @@ -735,7 +757,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Poincare::Expression corr } // Handle matrices - cursor->layout().addGreySquaresToAllMatrixAncestors(); + cursor->layout().addGraySquaresToAllMatrixAncestors(); // Handle empty layouts cursor->hideEmptyLayoutIfNeeded(); diff --git a/escher/src/modal_view_controller.cpp b/escher/src/modal_view_controller.cpp index 6fbed78b1..548f29a05 100644 --- a/escher/src/modal_view_controller.cpp +++ b/escher/src/modal_view_controller.cpp @@ -56,11 +56,13 @@ KDRect ModalViewController::ContentView::modalViewFrame() const { void ModalViewController::ContentView::layoutSubviews(bool force) { assert(m_regularView != nullptr); - m_regularView->setFrame(bounds(), force); if (m_isDisplayingModal) { assert(m_currentModalView != nullptr); - m_currentModalView->setFrame(modalViewFrame(), force); + KDRect modalFrame = modalViewFrame(); + m_regularView->setFrame(modalFrame == bounds() ? KDRectZero : bounds(), force); + m_currentModalView->setFrame(modalFrame, force); } else { + m_regularView->setFrame(bounds(), force); if (m_currentModalView) { m_currentModalView->setFrame(KDRectZero, force); } diff --git a/escher/src/modal_view_empty_controller.cpp b/escher/src/modal_view_empty_controller.cpp index 23f370a6c..99757cfda 100644 --- a/escher/src/modal_view_empty_controller.cpp +++ b/escher/src/modal_view_empty_controller.cpp @@ -30,7 +30,7 @@ void ModalViewEmptyController::ModalViewEmptyView::setMessages(I18n::Message * m void ModalViewEmptyController::ModalViewEmptyView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), k_backgroundColor); - drawBorderOfRect(ctx, bounds(), Palette::GreyBright); + drawBorderOfRect(ctx, bounds(), Palette::GrayBright); } int ModalViewEmptyController::ModalViewEmptyView::numberOfSubviews() const { diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index cd966342f..735f5a32c 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -172,7 +172,7 @@ bool NestedMenuController::selectSubMenu(int selectedRow) { bool NestedMenuController::returnToPreviousMenu() { assert(m_stack.depth() > 0); NestedMenuController::Stack::State state = m_stack.pop(); - m_listController.setFirstSelectedRow(state.selectedRow()); + m_listController.setFirstSelectedRow(state.selectedRow() + stackRowOffset()); KDPoint scroll = m_selectableTableView.contentOffset(); m_selectableTableView.setContentOffset(KDPoint(scroll.x(), state.verticalScroll())); Container::activeApp()->setFirstResponder(&m_listController); diff --git a/escher/src/palette.cpp b/escher/src/palette.cpp index c20e46fc9..5cebacaf6 100644 --- a/escher/src/palette.cpp +++ b/escher/src/palette.cpp @@ -107,11 +107,11 @@ constexpr KDColor Palette::YellowDark; // Done constexpr KDColor Palette::YellowLight; // Done constexpr KDColor Palette::PurpleBright; // Done constexpr KDColor Palette::PurpleDark; // Done -constexpr KDColor Palette::GreyWhite; // Done -constexpr KDColor Palette::GreyBright; // Done -constexpr KDColor Palette::GreyMiddle; // Done -constexpr KDColor Palette::GreyDark; // Done -constexpr KDColor Palette::GreyVeryDark; // Done +constexpr KDColor Palette::GrayWhite; // Done +constexpr KDColor Palette::GrayBright; // Done +constexpr KDColor Palette::GrayMiddle; // Done +constexpr KDColor Palette::GrayDark; // Done +constexpr KDColor Palette::GrayVeryDark; // Done constexpr KDColor Palette::Select; // Done constexpr KDColor Palette::SelectDark; // Done constexpr KDColor Palette::WallScreen; // Done diff --git a/escher/src/pop_up_controller.cpp b/escher/src/pop_up_controller.cpp new file mode 100644 index 000000000..d1afbae29 --- /dev/null +++ b/escher/src/pop_up_controller.cpp @@ -0,0 +1,107 @@ +#include +#include + +PopUpController::PopUpController(int numberOfLines, Invocation OkInvocation) : + ViewController(nullptr), + m_contentView(this, numberOfLines, OkInvocation) +{ +} + +View * PopUpController::view() { + return &m_contentView; +} + +void PopUpController::didBecomeFirstResponder() { + m_contentView.setSelectedButton(0); +} + +bool PopUpController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) { + m_contentView.setSelectedButton(0); + return true; + } + if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) { + m_contentView.setSelectedButton(1); + return true; + } + return false; +} + +PopUpController::ContentView::ContentView(Responder * parentResponder, int numberOfLines, Invocation okInvocation) : + Responder(parentResponder), + m_cancelButton( + this, I18n::Message::Cancel, + Invocation( + [](void * context, void * sender) { + Container::activeApp()->dismissModalViewController(); + return true; + }, this), + KDFont::SmallFont), + m_okButton(this, I18n::Message::Ok, okInvocation, KDFont::SmallFont), + m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack), + m_numberOfLines(numberOfLines), + m_messageTextViews{} +{ + assert(m_numberOfLines <= k_maxNumberOfLines && m_numberOfLines >= 0); + for (int i = 0; i < m_numberOfLines; i++) { + m_messageTextViews[i].setFont(KDFont::SmallFont); + m_messageTextViews[i].setAlignment(0.5f, 0.5f); + m_messageTextViews[i].setBackgroundColor(KDColorBlack); + m_messageTextViews[i].setTextColor(KDColorWhite); + } +} + +void PopUpController::ContentView::setSelectedButton(int selectedButton) { + m_cancelButton.setHighlighted(selectedButton == 0); + m_okButton.setHighlighted(selectedButton == 1); + Container::activeApp()->setFirstResponder(selectedButton == 0 ? &m_cancelButton : &m_okButton); +} + +int PopUpController::ContentView::selectedButton() { + return m_cancelButton.isHighlighted() ? 0 : 1; +} + +void PopUpController::ContentView::setMessage(int index, I18n::Message message) { + assert(index >=0 && index < m_numberOfLines); + m_messageTextViews[index].setMessage(message); +} + +int PopUpController::ContentView::numberOfSubviews() const { + // MessageTextViews + WarningTextView + CancelButton + OkButton + return m_numberOfLines + 3; +} + +View * PopUpController::ContentView::subviewAtIndex(int index) { + int totalSubviews = numberOfSubviews(); + if (index < 0 || index >= totalSubviews) { + assert(false); + return nullptr; + } + if (index == 0) { + return &m_warningTextView; + } + if (index == totalSubviews - 2) { + return &m_cancelButton; + } + if (index == totalSubviews - 1) { + return &m_okButton; + } + return &m_messageTextViews[index-1]; +} + +void PopUpController::ContentView::layoutSubviews(bool force) { + KDCoordinate height = bounds().height(); + KDCoordinate width = bounds().width(); + KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); + m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force); + + // Offset to center text vertically + const int offset = (k_maxNumberOfLines - m_numberOfLines) / 2; + + for (int i = 0; i < m_numberOfLines; i++) { + m_messageTextViews[i].setFrame(KDRect(0, k_topMargin + k_paragraphHeight + (i + 1 + offset) * textHeight, width, textHeight), force); + } + + m_cancelButton.setFrame(KDRect(k_buttonMargin, height - k_buttonMargin - k_buttonHeight, (width - 3 * k_buttonMargin) / 2, k_buttonHeight), force); + m_okButton.setFrame(KDRect(2 * k_buttonMargin + (width - 3 * k_buttonMargin) / 2, height - k_buttonMargin - k_buttonHeight, (width - 3 * k_buttonMargin) / 2, k_buttonHeight), force); +} diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp index 4653324ff..f5d8c3b97 100644 --- a/escher/src/run_loop.cpp +++ b/escher/src/run_loop.cpp @@ -1,4 +1,5 @@ #include +#include #include RunLoop::RunLoop() : @@ -64,6 +65,11 @@ bool RunLoop::step() { #endif if (event != Ion::Events::None) { +#if !PLATFORM_DEVICE + if (event == Ion::Events::ExternalText && !KDFont::CanBeWrittenWithGlyphs(event.text())) { + return true; + } +#endif dispatchEvent(event); } diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index af59594af..c198052a5 100644 --- a/escher/src/scrollable_view.cpp +++ b/escher/src/scrollable_view.cpp @@ -12,28 +12,29 @@ ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollV bool ScrollableView::handleEvent(Ion::Events::Event event) { KDPoint translation = KDPointZero; + KDCoordinate scrollStep = Ion::Events::repetitionFactor() * Metric::ScrollStep; if (event == Ion::Events::Left) { KDCoordinate movementToEdge = contentOffset().x(); if (movementToEdge > 0) { - translation = KDPoint(-std::min(Metric::ScrollStep, movementToEdge), 0); + translation = KDPoint(-std::min(scrollStep, movementToEdge), 0); } } if (event == Ion::Events::Right) { KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().width() - bounds().width() - contentOffset().x(); if (movementToEdge > 0) { - translation = KDPoint(std::min(Metric::ScrollStep, movementToEdge), 0); + translation = KDPoint(std::min(scrollStep, movementToEdge), 0); } } if (event == Ion::Events::Up) { KDCoordinate movementToEdge = contentOffset().y(); if (movementToEdge > 0) { - translation = KDPoint(0, -std::min(Metric::ScrollStep, movementToEdge)); + translation = KDPoint(0, -std::min(scrollStep, movementToEdge)); } } if (event == Ion::Events::Down) { KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().height() - bounds().height() - contentOffset().y(); if (movementToEdge > 0) { - translation = KDPoint(0, std::min(Metric::ScrollStep, movementToEdge)); + translation = KDPoint(0, std::min(scrollStep, movementToEdge)); } } if (translation != KDPointZero) { diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 27f4bd395..44804fcb5 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -125,6 +125,24 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon return true; } +bool SelectableTableView::selectCellAtClippedLocation(int i, int j, bool setFirstResponder, bool withinTemporarySelection) { + if (i < 0) { + i = 0; + } else if (i >= dataSource()->numberOfColumns()) { + i = dataSource()->numberOfColumns() - 1; + } + if (j < 0) { + j = 0; + } else if (j >= dataSource()->numberOfRows()) { + j = dataSource()->numberOfRows() - 1; + } + if (j == selectedRow() && i == selectedColumn()) { + // Cell was already selected. + return false; + } + return selectCellAtLocation(i, j, setFirstResponder, withinTemporarySelection); +} + HighlightCell * SelectableTableView::selectedCell() { if (selectedColumn() < 0 || selectedRow() < 0) { return nullptr; @@ -133,26 +151,27 @@ HighlightCell * SelectableTableView::selectedCell() { } bool SelectableTableView::handleEvent(Ion::Events::Event event) { + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::Down) { - return selectCellAtLocation(selectedColumn(), selectedRow()+1); + return selectCellAtClippedLocation(selectedColumn(), selectedRow() + step); } if ((event == Ion::Events::ShiftDown || event == Ion::Events::AlphaDown) && selectedRow() < dataSource()->numberOfRows()-1) { return selectCellAtLocation(selectedColumn(), dataSource()->numberOfRows()-1); } if (event == Ion::Events::Up) { - return selectCellAtLocation(selectedColumn(), selectedRow()-1); + return selectCellAtClippedLocation(selectedColumn(), selectedRow() - step); } if ((event == Ion::Events::ShiftUp || event == Ion::Events::AlphaUp) && selectedRow() > 0) { return selectCellAtLocation(selectedColumn(), 0); } if (event == Ion::Events::Left) { - return selectCellAtLocation(selectedColumn()-1, selectedRow()); + return selectCellAtClippedLocation(selectedColumn() - step, selectedRow()); } if ((event == Ion::Events::ShiftLeft || event == Ion::Events::AlphaLeft) && selectedColumn() > 0) { return selectCellAtLocation(0, selectedRow()); } if (event == Ion::Events::Right) { - return selectCellAtLocation(selectedColumn()+1, selectedRow()); + return selectCellAtClippedLocation(selectedColumn() + step, selectedRow()); } if ((event == Ion::Events::ShiftRight || event == Ion::Events::AlphaRight) && selectedColumn() < dataSource()->numberOfColumns()-1) { return selectCellAtLocation(dataSource()->numberOfColumns()-1, selectedRow()); diff --git a/escher/src/stack_view_controller.cpp b/escher/src/stack_view_controller.cpp index 1b32e0879..6cfbcc278 100644 --- a/escher/src/stack_view_controller.cpp +++ b/escher/src/stack_view_controller.cpp @@ -13,7 +13,7 @@ StackViewController::ControllerView::ControllerView() : { } -void StackViewController::ControllerView::shouldDisplayStackHearders(bool shouldDisplay) { +void StackViewController::ControllerView::shouldDisplayStackHeaders(bool shouldDisplay) { m_displayStackHeaders = shouldDisplay; } @@ -136,7 +136,7 @@ void StackViewController::pushModel(Frame frame) { void StackViewController::setupActiveViewController() { ViewController * vc = topViewController(); vc->setParentResponder(this); - m_view.shouldDisplayStackHearders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); + m_view.shouldDisplayStackHeaders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); m_view.setContentView(vc->view()); vc->viewWillAppear(); vc->setParentResponder(this); @@ -172,7 +172,7 @@ void StackViewController::viewWillAppear() { ViewController * vc = topViewController(); if (m_numberOfChildren > 0 && vc) { m_view.setContentView(vc->view()); - m_view.shouldDisplayStackHearders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); + m_view.shouldDisplayStackHeaders(vc->displayParameter() != ViewController::DisplayParameter::WantsMaximumSpace); vc->viewWillAppear(); } m_isVisible = true; diff --git a/escher/src/switch_view.cpp b/escher/src/switch_view.cpp index c06a26ee1..804ae87cb 100644 --- a/escher/src/switch_view.cpp +++ b/escher/src/switch_view.cpp @@ -1,5 +1,4 @@ #include -#include const uint8_t switchMask[SwitchView::k_switchHeight][SwitchView::k_switchWidth] = { {0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0xFF}, @@ -32,37 +31,28 @@ const uint8_t onOffMask[SwitchView::k_onOffSize][SwitchView::k_onOffSize] = { {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, }; -SwitchView::SwitchView() : -m_state(true) -{ -} - -bool SwitchView::state() { - return m_state; -} - -void SwitchView::setState(bool state) { - m_state = state; - markRectAsDirty(bounds()); -} - void SwitchView::drawRect(KDContext * ctx, KDRect rect) const { - /* Draw the switch aligned on the right of the view and vertically centered. + /* Draw the view aligned on the right of the view and vertically centered * The heightCenter is the coordinate of the vertical middle of the view. That - * way, (heightCenter-switchHalfHeight) indicates the top the switch. */ + * way, (heightCenter-halfHeight) indicates the top of the StateView. */ KDCoordinate width = bounds().width(); - KDCoordinate heightCenter = bounds().height()/2; - KDCoordinate switchHalfHeight = k_switchHeight/2; - KDColor switchWorkingBuffer[SwitchView::k_switchWidth*SwitchView::k_switchHeight]; + KDCoordinate heightCenter = bounds().height() / 2; + KDCoordinate halfHeight = k_switchHeight / 2; + KDColor workingBuffer[k_switchWidth * k_switchHeight]; + + KDRect frame(width - k_switchWidth, heightCenter - halfHeight, k_switchWidth, k_switchHeight); + ctx->blendRectWithMask( + frame, + m_state ? Palette::Control : Palette::ControlDisabled, + reinterpret_cast(switchMask), + workingBuffer); + - KDColor mainColor = m_state ? Palette::Control : Palette::ControlDisabled; - KDRect frame(width - k_switchWidth, heightCenter -switchHalfHeight, k_switchWidth, k_switchHeight); - ctx->blendRectWithMask(frame, mainColor, (const uint8_t *)switchMask, switchWorkingBuffer); KDCoordinate onOffX = width - (m_state ? k_onOffSize : k_switchWidth); - KDRect onOffFrame(onOffX, heightCenter -switchHalfHeight, k_onOffSize, k_onOffSize); - ctx->blendRectWithMask(onOffFrame, Palette::ListCellBackground, (const uint8_t *)onOffMask, switchWorkingBuffer); -} - -KDSize SwitchView::minimalSizeForOptimalDisplay() const { - return KDSize(k_switchWidth, k_switchHeight); -} + KDRect onOffFrame(onOffX, heightCenter - halfHeight, k_onOffSize, k_onOffSize); + ctx->blendRectWithMask( + onOffFrame, + Palette::ListCellBackground, + reinterpret_cast(onOffMask), + workingBuffer); +} \ No newline at end of file diff --git a/escher/src/table_cell.cpp b/escher/src/table_cell.cpp index ac7ad9f40..b0ce6c15d 100644 --- a/escher/src/table_cell.cpp +++ b/escher/src/table_cell.cpp @@ -101,6 +101,7 @@ void TableCell::layoutSubviews(bool force) { y = std::max(y, height - k_separatorThickness - withMargin(accessorySize.height(), k_verticalMargin) - withMargin(subAccessorySize.height(), 0)); if (subAccessory) { KDCoordinate subAccessoryHeight = std::min(subAccessorySize.height(), height - y - k_separatorThickness - k_verticalMargin); + assert(accessory); accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, subAccessoryHeight), force); y += subAccessoryHeight; } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index bb99860a0..fd2025afa 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -47,22 +47,82 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for * indentation, stop here. */ int spacesCount = 0; int totalIndentationSize = 0; - int textLen = strlen(text); + int addedTextLength = strlen(text); + size_t previousTextLength = contentView()->getText()->textLength(); char * insertionPosition = const_cast(cursorLocation()); + const char * textAreaBuffer = contentView()->text(); if (indentation) { // Compute the indentation spacesCount = indentationBeforeCursor(); - const char * textAreaBuffer = contentView()->text(); if (insertionPosition > textAreaBuffer && UTF8Helper::PreviousCodePointIs(textAreaBuffer, insertionPosition, ':')) { spacesCount += k_indentationSpaces; } // Check the text will not overflow the buffer totalIndentationSize = UTF8Helper::CountOccurrences(text, '\n') * spacesCount; - if (contentView()->getText()->textLength() + textLen + totalIndentationSize >= contentView()->getText()->bufferSize()) { + if (previousTextLength + addedTextLength + totalIndentationSize >= contentView()->getText()->bufferSize()) { return false; } } + /* KDCoordinate is a int16. We must limit the number of characters per line, + * and lines per scripts, otherwise the line rects or content rect + * height/width can overflow int16, which results in weird visual effects.*/ + // 1 - Number of Characters per line : + if (previousTextLength + addedTextLength > k_maxLineChars) { + /* Only check for long lines in long scripts. PreviousTextLength and + * addedTextLength being greater than the actual number of glyphs is not an + * issue here. After insertion, text buffer will have this structure : + * ".../n"+"before"+"inserted1"+("/n.../n")?+"inserted2"+"after"+"\n..." + * Lengths : b ib ia a + * As maxBufferSize is lower than k_maxLineChars, there is no need to check + * for inserted lines between "\n...\n" */ + static_assert(TextField::maxBufferSize() < k_maxLineChars, "Pasting text might cause content rect overflow."); + + // Counting line text lengths before and after insertion. + int b = 0; + int a = 0; + UTF8Helper::countGlyphsInLine(textAreaBuffer, &b, &a, insertionPosition); + + if (a + b + addedTextLength > k_maxLineChars) { + /* Overflow expected, depending on '/n' code point presence and position. + * Counting : Glyphs inserted before first '/n' : ib + * Glyphs inserted after last '/n' : ia + * Number of '/n' : n */ + int glyphCount[3] = {0, 0, 0}; + UTF8Helper::PerformAtCodePoints(text, '\n', + [](int, void * intArray, int, int) { + // '\n' found, Increment n + int * n = (int *)intArray + 2; + *n = *n + 1; + // Reset ia + int * ia = (int *)intArray + 1; + *ia = 0; + }, + [](int, void * intArray, int, int) { + if (((int *)intArray)[2] == 0) { + // While no '\n' found, increment ib + int * ib = (int *)intArray; + *ib = *ib + 1; + } else { + // Increment ia + int * ia = (int *)intArray + 1; + *ia = *ia + 1; + } + }, + &glyphCount, 0, 0); + // Insertion is not possible if one of the produced line is too long. + if ((glyphCount[2] == 0 && a + glyphCount[0] + b > k_maxLineChars) || b + glyphCount[0] > k_maxLineChars || a + glyphCount[1] > k_maxLineChars) { + return false; + } + } + } + + // 2 - Total number of line : + if (previousTextLength + addedTextLength > k_maxLines && contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') > k_maxLines) { + // Only check for overflowed lines in long scripts to save computation + return false; + } + // Insert the text if (!insertTextAtLocation(text, insertionPosition)) { return true; @@ -84,9 +144,9 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for UCodePointNull, true, nullptr, - insertionPosition + textLen); + insertionPosition + addedTextLength); } - const char * endOfInsertedText = insertionPosition + textLen + totalIndentationSize; + const char * endOfInsertedText = insertionPosition + addedTextLength + totalIndentationSize; const char * cursorPositionInCommand = TextInputHelpers::CursorPositionInCommand(insertionPosition, endOfInsertedText); // Remove the Empty code points @@ -106,12 +166,13 @@ bool TextArea::handleEvent(Ion::Events::Event event) { if (handleBoxEvent(event)) { return true; } + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) { - selectLeftRight(event == Ion::Events::ShiftLeft, false); + selectLeftRight(event == Ion::Events::ShiftLeft, false, step); return true; } if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) { - selectUpDown(event == Ion::Events::ShiftUp); + selectUpDown(event == Ion::Events::ShiftUp, 1); return true; } else if (event == Ion::Events::AlphaLeft) { contentView()->moveCursorGeo(-INT_MAX/2, 0); @@ -125,17 +186,17 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (event == Ion::Events::AlphaDown) { contentView()->moveCursorGeo(0, INT_MAX/2); TextInput::scrollToCursor(); - } else if (event == Ion::Events::Left) { - if (contentView()->resetSelection()) { - return true; - } - return TextInput::moveCursorLeft(); + } else if (event == Ion::Events::Left || event == Ion::Events::Right) { + selectUpDown(event == Ion::Events::ShiftUp, step); + return true; } - if (event == Ion::Events::Right) { + if (event == Ion::Events::Left || event == Ion::Events::Right) { if (contentView()->resetSelection()) { return true; } - return TextInput::moveCursorRight(); + return (event == Ion::Events::Left) ? + TextInput::moveCursorLeft(step) : + TextInput::moveCursorRight(step); } if (event.hasText()) { @@ -169,16 +230,9 @@ bool TextArea::handleEvent(Ion::Events::Event event) { deleteSelection(); return true; } - } else if (event == Ion::Events::Up) { + } else if (event == Ion::Events::Up || event == Ion::Events::Down) { contentView()->resetSelection(); - contentView()->moveCursorGeo(0, -1); - } else if (event == Ion::Events::Down) { - contentView()->resetSelection(); - contentView()->moveCursorGeo(0, 1); - } else if (event == Ion::Events::Backspace) { - return removePreviousGlyph(); - } else if (event == Ion::Events::EXE) { - return handleEventWithText("\n"); + contentView()->moveCursorGeo(0, event == Ion::Events::Up ? -step : step); } else if (event == Ion::Events::Clear) { if (!contentView()->selectionIsEmpty()) { deleteSelection(); @@ -290,9 +344,16 @@ CodePoint TextArea::Text::removePreviousGlyph(char * * position) { assert(m_buffer <= *position && *position < m_buffer + m_bufferSize); CodePoint removedCodePoint = 0; - int removedSize = UTF8Helper::RemovePreviousGlyph(m_buffer, *position, &removedCodePoint); - assert(removedSize > 0); - + int removedSize = 0; + if (UTF8Helper::PreviousCodePoint(m_buffer, *position) == '\n') { + // See comments in handleEventWithText about max number of glyphs per line + removedCodePoint = '\n'; + // removeText will handle max number of glyphs per line + removedSize = removeText(*position-1, *position); + } else { + removedSize = UTF8Helper::RemovePreviousGlyph(m_buffer, *position, &removedCodePoint); + assert(removedSize > 0); + } // Set the new cursor position *position = *position - removedSize; return removedCodePoint; @@ -310,6 +371,24 @@ size_t TextArea::Text::removeText(const char * start, const char * end) { return 0; } + /* Removing text can increase line length. See comments in handleEventWithText + * about max number of glyphs per line. */ + if (textLength() - delta >= k_maxLineChars) { + /* Only check for line length on long enough scripts. TextLength() and delta + * being greater than the actual number of glyphs is not an issue here. */ + + // Counting text lengths between previous and last '/n' (non removed). + int b = 0; + int a = 0; + UTF8Helper::countGlyphsInLine(text(), &b, &a, start, end); + + if (a + b > k_maxLineChars) { + // Resulting line would exceed limits, no text is removed + // TODO error message: Add Message to explain failure to remove text + return 0; + } + } + for (size_t index = src - m_buffer; index < m_bufferSize; index++) { *dst = *src; if (*src == 0) { @@ -573,6 +652,9 @@ KDRect TextArea::ContentView::glyphFrameAtPosition(const char * text, const char assert(found); (void) found; + // Check for KDCoordinate overflow + assert(x < KDCOORDINATE_MAX - glyphSize.width() && p.line() * glyphSize.height() < KDCOORDINATE_MAX - glyphSize.height()); + return KDRect( x, p.line() * glyphSize.height(), @@ -586,9 +668,9 @@ void TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) { setCursorLocation(m_text.pointerAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY))); } -void TextArea::selectUpDown(bool up) { +void TextArea::selectUpDown(bool up, int step) { const char * previousCursorLocation = contentView()->cursorLocation(); - contentView()->moveCursorGeo(0, up ? -1 : 1); + contentView()->moveCursorGeo(0, up ? -step : step); const char * newCursorLocation = contentView()->cursorLocation(); contentView()->addSelection(up ? newCursorLocation : previousCursorLocation, up ? previousCursorLocation : newCursorLocation); scrollToCursor(); diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 36500b007..4411f12a4 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -123,6 +123,7 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca assert(m_isEditing); size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen; + // TODO when paste fails because of a too big message, create a pop-up if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) { return false; } diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index 6e573a045..da741f62c 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -145,7 +145,9 @@ void TextInput::scrollToCursor() { * In order to avoid requiring two layouts, we allow overscrolling in * scrollToContentRect, and the last layout of the scroll view corrects the * size of the scroll view only once. */ - scrollToContentRect(contentView()->cursorRect(), true); + KDRect cursorRect = contentView()->cursorRect(); + assert(cursorRect.top() >= 0 && cursorRect.right() >= 0 && cursorRect.bottom() >= 0 && cursorRect.left() >= 0); + scrollToContentRect(cursorRect, true); } void TextInput::deleteSelection() { @@ -187,28 +189,46 @@ bool TextInput::removeEndOfLine() { return false; } -bool TextInput::moveCursorLeft() { - if (cursorLocation() <= text()) { - assert(cursorLocation() == text()); - return false; +bool TextInput::moveCursorLeft(int step) { + // Move the cursor to the left step times, or until limit is reached. + int i = 0; + bool canMove = true; + while (canMove && i < step) { + if (cursorLocation() <= text()) { + assert(cursorLocation() == text()); + canMove = false; + } else { + UTF8Decoder decoder(text(), cursorLocation()); + canMove = setCursorLocation(decoder.previousGlyphPosition()); + } + i++; } - UTF8Decoder decoder(text(), cursorLocation()); - return setCursorLocation(decoder.previousGlyphPosition()); + // true is returned if there was at least one successful cursor movement + return (i > 1 || canMove); } -bool TextInput::moveCursorRight() { - if (UTF8Helper::CodePointIs(cursorLocation(), UCodePointNull)) { - return false; +bool TextInput::moveCursorRight(int step) { + // Move the cursor to the right step times, or until limit is reached. + int i = 0; + bool canMove = true; + while (canMove && i < step) { + if (UTF8Helper::CodePointIs(cursorLocation(), UCodePointNull)) { + canMove = false; + } else { + UTF8Decoder decoder(cursorLocation()); + canMove = setCursorLocation(decoder.nextGlyphPosition()); + } + i++; } - UTF8Decoder decoder(cursorLocation()); - return setCursorLocation(decoder.nextGlyphPosition()); + // true is returned if there was at least one successful cursor movement + return (i > 1 || canMove); } -bool TextInput::selectLeftRight(bool left, bool all) { +bool TextInput::selectLeftRight(bool left, bool all, int step) { const char * cursorLoc = cursorLocation(); const char * nextCursorLoc = nullptr; if (!all) { - bool moved = left ? moveCursorLeft() : moveCursorRight(); + bool moved = left ? moveCursorLeft(step) : moveCursorRight(step); if (!moved) { return false; } diff --git a/escher/src/toggleable_dot_view.cpp b/escher/src/toggleable_dot_view.cpp new file mode 100644 index 000000000..3195ae1f4 --- /dev/null +++ b/escher/src/toggleable_dot_view.cpp @@ -0,0 +1,41 @@ +#include +#include + +const uint8_t MediumDotMask[ToggleableDotView::k_dotSize][ToggleableDotView::k_dotSize] = { + {0xFF, 0xDB, 0x53, 0x0F, 0x0F, 0x53, 0xDB, 0xFF}, + {0xD8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0xD8}, + {0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53}, + {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, + {0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, + {0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53}, + {0xD7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0xD7}, + {0xFF, 0xD8, 0x53, 0x0C, 0x0C, 0x53, 0xD8, 0xFF}, +}; + +const uint8_t MediumShallowDotMask[ToggleableDotView::k_dotSize][ToggleableDotView::k_dotSize] = { + {0xFF, 0xDB, 0x53, 0x0F, 0x0F, 0x53, 0xDB, 0xFF}, + {0xD8, 0x17, 0x90, 0xEC, 0xEC, 0x90, 0x17, 0xD8}, + {0x53, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x53}, + {0x0F, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x0C}, + {0x0F, 0xEC, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0x0C}, + {0x53, 0x90, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x53}, + {0xD7, 0x17, 0x90, 0xEF, 0xEF, 0x90, 0x17, 0xD7}, + {0xFF, 0xD8, 0x53, 0x0C, 0x0C, 0x53, 0xD8, 0xFF}, +}; + +void ToggleableDotView::drawRect(KDContext * ctx, KDRect rect) const { + /* Draw the view aligned on the right of the view and vertically centered + * The heightCenter is the coordinate of the vertical middle of the view. That + * way, (heightCenter-halfHeight) indicates the top of the StateView. */ + KDCoordinate width = bounds().width(); + KDCoordinate heightCenter = bounds().height() / 2; + KDCoordinate halfHeight = k_dotSize / 2; + KDColor workingBuffer[k_dotSize * k_dotSize]; + + KDRect frame(width - k_dotSize, heightCenter - halfHeight, k_dotSize, k_dotSize); + ctx->blendRectWithMask( + frame, + m_state ? Palette::YellowDark : Palette::GrayDark, + m_state ? reinterpret_cast(MediumDotMask) : reinterpret_cast(MediumShallowDotMask), + workingBuffer); +} \ No newline at end of file diff --git a/escher/src/toggleable_view.cpp b/escher/src/toggleable_view.cpp new file mode 100644 index 000000000..2e2660277 --- /dev/null +++ b/escher/src/toggleable_view.cpp @@ -0,0 +1,6 @@ +#include + +void ToggleableView::setState(bool state) { + m_state = state; + markRectAsDirty(bounds()); +} \ No newline at end of file diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 7c080263a..29f22a041 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -39,7 +39,7 @@ void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { } int Toolbox::typeAtLocation(int i, int j) { - MessageTree * messageTree = (MessageTree *)m_messageTreeModel->childAtIndex(j); + const MessageTree * messageTree = m_messageTreeModel->childAtIndex(j); if (messageTree->numberOfChildren() == 0) { return LeafCellType; } @@ -48,7 +48,11 @@ int Toolbox::typeAtLocation(int i, int j) { bool Toolbox::selectSubMenu(int selectedRow) { m_selectableTableView.deselectTable(); - m_messageTreeModel = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); + m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(selectedRow)); + if (m_messageTreeModel->isFork()) { + assert(m_messageTreeModel->numberOfChildren() < 0); + m_messageTreeModel = static_cast(m_messageTreeModel->childAtIndex(indexAfterFork())); + } return NestedMenuController::selectSubMenu(selectedRow); } @@ -56,15 +60,14 @@ bool Toolbox::returnToPreviousMenu() { m_selectableTableView.deselectTable(); int currentDepth = m_stack.depth(); int index = 0; - // We want to get the current ToolboxMessageTree's parent ToolboxMessageTree, - // but there is no ToolboxMessageTree::getParent() method. We thus have to - // start from the root ToolboxMessageTree and sequentially get the selected - // child until it has the wanted depth. ToolboxMessageTree * parentMessageTree = (ToolboxMessageTree *)rootModel(); Stack::State * previousState = m_stack.stateAtIndex(index++); while (currentDepth-- > 1) { parentMessageTree = (ToolboxMessageTree *)parentMessageTree->childAtIndex(previousState->selectedRow()); previousState = m_stack.stateAtIndex(index++); + if (parentMessageTree->isFork()) { + parentMessageTree = (ToolboxMessageTree *)parentMessageTree->childAtIndex(indexAfterFork()); + } } m_messageTreeModel = parentMessageTree; return NestedMenuController::returnToPreviousMenu(); diff --git a/escher/test/clipboard.cpp b/escher/test/clipboard.cpp new file mode 100644 index 000000000..510c683ef --- /dev/null +++ b/escher/test/clipboard.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +void assert_clipboard_enters_and_exits_python(const char * string, const char * stringResult) { + Clipboard * clipboard = Clipboard::sharedClipboard(); + clipboard->store(string); + clipboard->enterPython(); + quiz_assert(strcmp(clipboard->storedText(), stringResult) == 0); + clipboard->exitPython(); + quiz_assert(strcmp(clipboard->storedText(), string) == 0); +} + +QUIZ_CASE(escher_clipboard_enters_and_exits_python) { + assert_clipboard_enters_and_exits_python("4×4", "4*4"); + assert_clipboard_enters_and_exits_python("ℯ^(ln(4))", "exp(log(4))"); + assert_clipboard_enters_and_exits_python("ln(log(ln(π)))^𝐢", "log(log10(log(pi)))**1j"); + assert_clipboard_enters_and_exits_python("√(1ᴇ10)", "sqrt(1e10)"); + assert_clipboard_enters_and_exits_python("1×𝐢^2", "1*1j**2"); + assert_clipboard_enters_and_exits_python("12^(1/4)×(π/6)×(12×π)^(1/4)", "12**(1/4)*(pi/6)*(12*pi)**(1/4)"); +} + +using namespace Poincare; + +void assert_stored_text_is_parseable(Poincare::Layout layout) { + constexpr int bufferSize = 500; + char buffer[bufferSize]; + layout.serializeForParsing(buffer, bufferSize); + Clipboard * clipboard = Clipboard::sharedClipboard(); + clipboard->store(buffer); + Expression e = Expression::Parse(clipboard->storedText(), nullptr, false); + Layout result = e.createLayout(Preferences::sharedPreferences()->displayMode(), Poincare::PrintFloat::k_numberOfStoredSignificantDigits); + quiz_assert(layout.isIdenticalTo(result)); +} + +QUIZ_CASE(escher_clipboard_stored_text_is_parseable) { + Layout l = IntegralLayout::Builder(EmptyLayout::Builder(), CodePointLayout::Builder('x'), EmptyLayout::Builder(), EmptyLayout::Builder()); + assert_stored_text_is_parseable(l); + l = NthRootLayout::Builder(EmptyLayout::Builder()); + assert_stored_text_is_parseable(l); + l = MatrixLayout::Builder(CodePointLayout::Builder('1'), EmptyLayout::Builder(), EmptyLayout::Builder(), CodePointLayout::Builder('2')); + assert_stored_text_is_parseable(l); + l = SumLayout::Builder(EmptyLayout::Builder(), CodePointLayout::Builder('n'), EmptyLayout::Builder(), EmptyLayout::Builder()); + assert_stored_text_is_parseable(l); + l = SumLayout::Builder(EmptyLayout::Builder(), CodePointLayout::Builder('n'), EmptyLayout::Builder(), EmptyLayout::Builder()); + assert_stored_text_is_parseable(l);; +} \ No newline at end of file diff --git a/ion/Makefile b/ion/Makefile index 818715da0..b0e5bc80a 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -10,6 +10,8 @@ ifndef ION_KEYBOARD_LAYOUT $(error platform.mak should define ION_KEYBOARD_LAYOUT) endif SFLAGS += -Iion/include/ion/keyboard/$(ION_KEYBOARD_LAYOUT) +SFLAGS += -Iion/include/ion/keyboard/ +ion_src += ion/src/shared/keyboard/$(ION_KEYBOARD_LAYOUT)/layout_events.cpp include ion/src/$(PLATFORM)/Makefile -include ion/test/$(PLATFORM)/Makefile @@ -32,6 +34,7 @@ ion_src += $(addprefix ion/src/shared/, \ events_modifier.cpp \ platform_info.cpp \ rtc.cpp \ + stack_position.cpp \ storage.cpp \ unicode/utf8_decoder.cpp\ unicode/utf8_helper.cpp\ diff --git a/ion/include/ion.h b/ion/include/ion.h index 019402788..f0ce39b4d 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,9 @@ uint32_t random(); // Decompress data void decompress(const uint8_t * src, uint8_t * dst, int srcSize, int dstSize); +// Sets and returns address to the first object that can be allocated on stack +void * stackStart(); +void setStackStart(void *); // Tells whether the stack pointer is within acceptable bounds bool stackSafe(); diff --git a/ion/include/ion/clipboard.h b/ion/include/ion/clipboard.h new file mode 100644 index 000000000..c60b9ece6 --- /dev/null +++ b/ion/include/ion/clipboard.h @@ -0,0 +1,17 @@ +#ifndef ION_CLIPBOARD_H +#define ION_CLIPBOARD_H + +namespace Ion { +namespace Clipboard { + +/* Write the text to the system clipboard. */ +void write(const char * text); + +/* Returns the system's clipboard text if it differs from the text previously + * copied, and nullptr otherwise. */ +const char * read(); + +} +} + +#endif diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index b5340e2f2..819b7ffaf 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -36,6 +36,8 @@ public: bool isDefined() const; static constexpr int PageSize = Keyboard::NumberOfKeys; private: + const char * defaultText() const; + uint8_t m_id; }; @@ -57,8 +59,8 @@ void removeShift(); bool isShiftActive(); bool isAlphaActive(); bool isLockActive(); -void setLongRepetition(bool longRepetition); -bool isLongRepetition(); +void setLongRepetition(int longRepetition); +int repetitionFactor(); void updateModifiersFromEvent(Event e); void didPressNewKey(); @@ -275,6 +277,9 @@ constexpr Event TimerFire = Event::Special(2); constexpr Event USBEnumeration = Event::Special(3); constexpr Event USBPlug = Event::Special(4); constexpr Event BatteryCharging = Event::Special(5); +/* This event is only used in the simulator, to handle text that cannot be + * associated with a key. */ +constexpr Event ExternalText = Event::Special(6); } } diff --git a/ion/include/ion/keyboard/event_data.h b/ion/include/ion/keyboard/event_data.h index 58037d721..017711051 100644 --- a/ion/include/ion/keyboard/event_data.h +++ b/ion/include/ion/keyboard/event_data.h @@ -7,12 +7,11 @@ namespace Events { class EventData { public: static constexpr EventData Undefined() { return EventData(nullptr); } - static constexpr EventData Textless() { return EventData(k_textless); } + static constexpr EventData Textless() { return EventData(""); } static constexpr EventData Text(const char * text) { return EventData(text); } bool isDefined() const { return (m_data != nullptr); } const char * text() const; private: - static constexpr const char * k_textless = ""; constexpr EventData(const char * data) : m_data(data) {} const char * m_data; }; diff --git a/ion/include/ion/keyboard/layout_events.h b/ion/include/ion/keyboard/layout_events.h new file mode 100644 index 000000000..978752a8c --- /dev/null +++ b/ion/include/ion/keyboard/layout_events.h @@ -0,0 +1,24 @@ +#ifndef ION_KEYBOARD_LAYOUT_EVENTS_H +#define ION_KEYBOARD_LAYOUT_EVENTS_H + +#include +#include +#include "event_data.h" + +namespace Ion { +namespace Events { + +extern const EventData s_dataForEvent[4*Event::PageSize]; + +#ifndef NDEBUG +extern const char * const s_nameForEvent[255]; + +inline const char * Event::name() const { + return s_nameForEvent[m_id]; +} +#endif + +} +} + +#endif diff --git a/ion/include/ion/unicode/code_point.h b/ion/include/ion/unicode/code_point.h index 7c3690dc7..a1e6ce240 100644 --- a/ion/include/ion/unicode/code_point.h +++ b/ion/include/ion/unicode/code_point.h @@ -53,6 +53,7 @@ static constexpr CodePoint UCodePointEmpty = 0x11; // Used to static constexpr CodePoint UCodePointLeftSystemParenthesis = 0x12; // Used for serialization static constexpr CodePoint UCodePointRightSystemParenthesis = 0x13; // Used for serialization +static constexpr CodePoint UCodePointDegreeSign = 0xb0; // ° static constexpr CodePoint UCodePointMiddleDot = 0xb7; // · static constexpr CodePoint UCodePointMultiplicationSign = 0xd7; // × static constexpr CodePoint UCodePointStar = 0x2a; // * diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 17ff9385f..edc379325 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -6,6 +6,19 @@ namespace UTF8Helper { +class TextPair { +public: + constexpr TextPair(const char * firstString, const char * secondString, bool removeParenthesesExtention = false) : m_firstString(firstString), m_secondString(secondString), m_removeParenthesesExtention(removeParenthesesExtention){} + const char * firstString() { return m_firstString; } + const char * secondString() { return m_secondString; } + bool removeParenthesesExtention() { return m_removeParenthesesExtention; } + static constexpr int k_maxLength = 20; +private: + const char * m_firstString; + const char * m_secondString; + bool m_removeParenthesesExtention; +}; + // Returns the number of occurences of a code point in a string int CountOccurrences(const char * s, CodePoint c); @@ -28,6 +41,25 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP * points where removed before it. Ensure null-termination of dst. */ void RemoveCodePoint(char * buffer, CodePoint c, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr); +/* Slides a string by a number of chars. If slidingSize < 0, the string is slided + * to the left losing the first chars. Returns true if successful. + * Exemples : + * SlideStringByNumberOfChar("12345", 2, 7) gives "1212345" + * SlideStringByNumberOfChar("12345", 2, 5) gives "12123" + * SlideStringByNumberOfChar("12345", -2, 5) gives "34545"*/ +bool SlideStringByNumberOfChar(char * text, int slidingSize, size_t textMaxLength); + +/* Looks for patterns in a string. If a pattern is found, it is replaced by + * the one associated in the TextPair struct. + * - firstToSecond defines if replace the first string of a TextPair by the second + * or the other way around. + * - indexToUpdate is a pointer to a char in the string. It will be updated to + * point to the same place after calling the function. + * - stoppingPosition allows partial replacement in the string. + * + * Ensure null termination of the string or set the value of stoppingPosition*/ +void TryAndReplacePatternsInStringByPatterns(char * text, int textMaxSize, TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr); + /* Copy src into dst until end of dst or code point c, with null termination. Return the length of the copy */ size_t CopyUntilCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c); @@ -89,6 +121,9 @@ const char * BeginningOfWord(const char * text, const char * word); // Returns the position of the first following char ' ', '\n' or 0 const char * EndOfWord(const char * word); +// On a line, count number of glyphs before and after locations +void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation = nullptr); + }; #endif diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 449297498..847009957 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -3,6 +3,7 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ battery.cpp \ base64.cpp \ board.cpp \ + clipboard.cpp \ console_uart.cpp:+consoleuart \ console_dummy.cpp:-consoleuart \ crc32.cpp \ diff --git a/ion/src/device/shared/drivers/clipboard.cpp b/ion/src/device/shared/drivers/clipboard.cpp new file mode 100644 index 000000000..eaccc5bdb --- /dev/null +++ b/ion/src/device/shared/drivers/clipboard.cpp @@ -0,0 +1,11 @@ +#include + +namespace Ion { + +/* Dummy implementation + * On the device, the clipboard is fully handled by Escher::Clipboard. */ + +void Clipboard::write(const char * text) {} +const char * Clipboard::read() { return nullptr; } + +} diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index b332a2999..51fd7be06 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -53,7 +53,7 @@ size_t numberOfBitsAfterLeadingZeroes(int i) { uint8_t * SignificantExamModeAddress() { uint32_t * persitence_start_32 = (uint32_t *)&_exam_mode_buffer_start; uint32_t * persitence_end_32 = (uint32_t *)&_exam_mode_buffer_end; - assert(persitence_end_32 - persitence_start_32 % 4 == 0); + assert((persitence_end_32 - persitence_start_32) % 4 == 0); while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { // Scan by groups of 32 bits to reach first non-zero bit persitence_start_32++; diff --git a/ion/src/device/shared/events.cpp b/ion/src/device/shared/events.cpp index 6eab2b3fc..761b00413 100644 --- a/ion/src/device/shared/events.cpp +++ b/ion/src/device/shared/events.cpp @@ -6,5 +6,9 @@ namespace Events { void didPressNewKey() { } +const char * Event::text() const { + return defaultText(); +} + } } diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 2e7ae45db..3db2b7d51 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -33,6 +33,7 @@ ion_device_usb_src += $(addprefix ion/src/device/shared/usb/stack/descriptor/, \ # DFU code +ion_device_dfu_src += liba/src/abs.c ion_device_dfu_src += liba/src/assert.c ion_device_dfu_src += liba/src/strlen.c ion_device_dfu_src += liba/src/strlcpy.c diff --git a/ion/src/device/shared/usb/stack/endpoint0.cpp b/ion/src/device/shared/usb/stack/endpoint0.cpp index b97607e1b..32121076f 100644 --- a/ion/src/device/shared/usb/stack/endpoint0.cpp +++ b/ion/src/device/shared/usb/stack/endpoint0.cpp @@ -12,6 +12,9 @@ namespace USB { using namespace Regs; +constexpr int Endpoint0::k_maxPacketSize; +constexpr uint16_t Endpoint0::MaxTransferSize; + void Endpoint0::setup() { // Setup the IN direction diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index 519c4acd3..e16a37c33 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -9,7 +9,7 @@ namespace Ion { namespace Events { const char * EventData::text() const { - if (m_data == nullptr || m_data == k_textless) { + if (m_data == nullptr || m_data[0] == 0) { return nullptr; } return m_data; @@ -48,24 +48,26 @@ Event::Event(Keyboard::Key key, bool shift, bool alpha, bool lock) { assert(m_id != Events::None.m_id); } -const char * Event::text() const { +bool Event::hasText() const { + return text() != nullptr; +} + +bool Event::isDefined() const { + if (isKeyboardEvent()) { + return s_dataForEvent[m_id].isDefined(); + } else { + return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging || *this == ExternalText); + } +} + +const char * Event::defaultText() const { + /* As the ExternalText event is only available on the simulator, we save a + * comparison by not handling it on the device. */ if (m_id >= 4*PageSize) { return nullptr; } return s_dataForEvent[m_id].text(); } -bool Event::hasText() const { - return text() != nullptr; -} - -bool Event::isDefined() const { - if (isKeyboardEvent()) { - return s_dataForEvent[m_id].isDefined(); - } else { - return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); - } -} - } } diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index d579e3719..29939263d 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -25,7 +25,6 @@ bool sEventIsRepeating = 0; int sEventRepetitionCount = 0; constexpr int delayBeforeRepeat = 200; constexpr int delayBetweenRepeat = 50; -constexpr int numberOfRepetitionsBeforeLongRepetition = 20; static bool canRepeatEvent(Event e) { return e == Events::Left @@ -41,9 +40,14 @@ static bool canRepeatEvent(Event e) { Event getPlatformEvent(); +void ComputeAndSetRepetionFactor(int eventRepetitionCount) { + // The Repetition factor is increased by 4 every 20 loops in getEvent(2 sec) + setLongRepetition((eventRepetitionCount / 20) * 4 + 1); +} + void resetLongRepetition() { sEventRepetitionCount = 0; - setLongRepetition(false); + ComputeAndSetRepetionFactor(sEventRepetitionCount); } Event getEvent(int * timeout) { @@ -62,6 +66,8 @@ Event getEvent(int * timeout) { keysSeenUp |= ~state; keysSeenTransitionningFromUpToDown = keysSeenUp & state; + bool lock = isLockActive(); + if (keysSeenTransitionningFromUpToDown != 0) { sEventIsRepeating = false; resetLongRepetition(); @@ -98,7 +104,7 @@ Event getEvent(int * timeout) { } if (sleepWithTimeout(10, timeout)) { - // Timeout occured + // Timeout occurred resetLongRepetition(); return Events::None; } @@ -109,17 +115,13 @@ Event getEvent(int * timeout) { if (canRepeatEvent(sLastEvent) && state == sLastKeyboardState && sLastEventShift == state.keyDown(Keyboard::Key::Shift) - && sLastEventAlpha == state.keyDown(Keyboard::Key::Alpha)) + && sLastEventAlpha == (state.keyDown(Keyboard::Key::Alpha) || lock)) { int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); if (time >= delay) { sEventIsRepeating = true; - if (sEventRepetitionCount < numberOfRepetitionsBeforeLongRepetition) { - sEventRepetitionCount++; - if (sEventRepetitionCount == numberOfRepetitionsBeforeLongRepetition) { - setLongRepetition(true); - } - } + sEventRepetitionCount++; + ComputeAndSetRepetionFactor(sEventRepetitionCount); return sLastEvent; } } diff --git a/ion/src/shared/events_modifier.cpp b/ion/src/shared/events_modifier.cpp index db7e24d43..7cccc2877 100644 --- a/ion/src/shared/events_modifier.cpp +++ b/ion/src/shared/events_modifier.cpp @@ -5,7 +5,7 @@ namespace Ion { namespace Events { static ShiftAlphaStatus sShiftAlphaStatus = ShiftAlphaStatus::Default; -static bool sLongRepetition = false; +static int sLongRepetition = 1; ShiftAlphaStatus shiftAlphaStatus() { return sShiftAlphaStatus; @@ -33,13 +33,13 @@ bool isLockActive() { return sShiftAlphaStatus == ShiftAlphaStatus::AlphaLock || sShiftAlphaStatus == ShiftAlphaStatus::ShiftAlphaLock; } -void setLongRepetition(bool longRepetition) { +void setLongRepetition(int longRepetition) { sLongRepetition = longRepetition; } -bool isLongRepetition() { - return sLongRepetition; -} +int repetitionFactor() { + return sLongRepetition; +}; void setShiftAlphaStatus(ShiftAlphaStatus s) { sShiftAlphaStatus = s; diff --git a/ion/include/ion/keyboard/layout_B2/layout_events.h b/ion/src/shared/keyboard/layout_B2/layout_events.cpp similarity index 92% rename from ion/include/ion/keyboard/layout_B2/layout_events.h rename to ion/src/shared/keyboard/layout_B2/layout_events.cpp index 23d8f2f54..339092597 100644 --- a/ion/include/ion/keyboard/layout_B2/layout_events.h +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -1,14 +1,10 @@ -#ifndef ION_KEYBOARD_LAYOUT_B2_EVENTS_H -#define ION_KEYBOARD_LAYOUT_B2_EVENTS_H - -#include -#include "../event_data.h" +#include namespace Ion { namespace Events { static_assert('\x11' == UCodePointEmpty, "Unicode error"); -static constexpr EventData s_dataForEvent[4*Event::PageSize] = { +const EventData s_dataForEvent[4 * Event::PageSize] = { // Plain TL(), TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), @@ -53,7 +49,7 @@ static constexpr EventData s_dataForEvent[4*Event::PageSize] = { #ifndef NDEBUG -static constexpr const char * s_nameForEvent[255] = { +const char * const s_nameForEvent[255] = { // Plain "Left", "Up", "Down", "Right", "OK", "Back", "Home", "OnOff", nullptr, nullptr, nullptr, nullptr, @@ -97,13 +93,7 @@ static constexpr const char * s_nameForEvent[255] = { "None", "Termination", nullptr, nullptr, nullptr, nullptr, }; -inline const char * Event::name() const { - return s_nameForEvent[m_id]; -} - #endif } } - -#endif diff --git a/ion/include/ion/keyboard/layout_B3/layout_events.h b/ion/src/shared/keyboard/layout_B3/layout_events.cpp similarity index 92% rename from ion/include/ion/keyboard/layout_B3/layout_events.h rename to ion/src/shared/keyboard/layout_B3/layout_events.cpp index c1a697a03..2cd94ce53 100644 --- a/ion/include/ion/keyboard/layout_B3/layout_events.h +++ b/ion/src/shared/keyboard/layout_B3/layout_events.cpp @@ -1,14 +1,10 @@ -#ifndef ION_KEYBOARD_LAYOUT_B3_EVENTS_H -#define ION_KEYBOARD_LAYOUT_B3_EVENTS_H - -#include -#include "../event_data.h" +#include namespace Ion { namespace Events { static_assert('\x11' == UCodePointEmpty, "Unicode error"); -static constexpr EventData s_dataForEvent[4*Event::PageSize] = { +const EventData s_dataForEvent[4 * Event::PageSize] = { // Plain TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), TL(), U(), U(), U(), @@ -53,7 +49,7 @@ static constexpr EventData s_dataForEvent[4*Event::PageSize] = { #ifndef NDEBUG -static constexpr const char * s_nameForEvent[255] = { +const char * const s_nameForEvent[255] = { // Plain "Left", "Up", "Down", "Right", "OK", "Back", "Home", "nullptr", "OnOff", nullptr, nullptr, nullptr, @@ -97,13 +93,7 @@ static constexpr const char * s_nameForEvent[255] = { "None", "Termination", nullptr, nullptr, nullptr, nullptr, }; -inline const char * Event::name() const { - return s_nameForEvent[m_id]; -} - #endif } } - -#endif diff --git a/ion/src/shared/stack_position.cpp b/ion/src/shared/stack_position.cpp new file mode 100644 index 000000000..9cb17b2ee --- /dev/null +++ b/ion/src/shared/stack_position.cpp @@ -0,0 +1,22 @@ +#include + +namespace Ion { + +#if PLATFORM_DEVICE +// On device, stack start address is always known. TODO : Factorize address +static void * s_stackStart = reinterpret_cast(0x20000000 + 256*1024); +#else +// Stack start will be defined in ion_main. +static void * s_stackStart = nullptr; +#endif + +void * stackStart() { + assert(s_stackStart != nullptr); + return s_stackStart; +} + +void setStackStart(void * pointer) { + assert(pointer != nullptr); + s_stackStart = pointer; +} +} \ No newline at end of file diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index fd7752096..3419e3d8c 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -99,6 +99,7 @@ void Storage::log() { size_t Storage::availableSize() { /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it * is needed after calling availableSize */ + assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); } @@ -510,7 +511,7 @@ bool Storage::isBaseNameWithExtensionTaken(const char * baseName, const char * e bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { if (r == Record()) { /* If the CRC32 of fullName is 0, we want to refuse the name as it would - * interfere with our escape case in the Record contructor, when the given + * interfere with our escape case in the Record constructor, when the given * name is nullptr. */ return true; } diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index d15c897c6..9393069a6 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -132,37 +133,104 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP } void RemoveCodePoint(char * buffer, CodePoint c, const char * * pointerToUpdate, const char * stoppingPosition) { - UTF8Decoder decoder(buffer); - const char * currentPointer = buffer; - CodePoint codePoint = decoder.nextCodePoint(); - const char * initialPointerToUpdate = *pointerToUpdate; - const char * nextPointer = decoder.stringPosition(); - size_t bufferIndex = 0; + constexpr int patternMaxSize = CodePoint::MaxCodePointCharLength + 1; // +1 for null terminating char + char pattern[patternMaxSize]; int codePointCharSize = UTF8Decoder::CharSizeOfCodePoint(c); - (void)codePointCharSize; // Silence compilation warning about unused variable. + UTF8Decoder::CodePointToChars(c, pattern, codePointCharSize); + pattern[codePointCharSize] = '\0'; + TextPair pair(pattern, ""); + TryAndReplacePatternsInStringByPatterns(buffer, strlen(buffer), &pair, 1, true, pointerToUpdate, stoppingPosition); +} - while (codePoint != UCodePointNull && (stoppingPosition == nullptr || currentPointer < stoppingPosition)) { - if (codePoint != c) { - int copySize = nextPointer - currentPointer; - memmove(buffer + bufferIndex, currentPointer, copySize); - bufferIndex+= copySize; - } else if (pointerToUpdate != nullptr && currentPointer < initialPointerToUpdate) { - assert(*pointerToUpdate - buffer >= codePointCharSize); - *pointerToUpdate = *pointerToUpdate - codePointCharSize; - } - currentPointer = nextPointer; - codePoint = decoder.nextCodePoint(); - nextPointer = decoder.stringPosition(); +bool SlideStringByNumberOfChar(char * text, int slidingSize, size_t textMaxLength) { + const size_t textLen = strlen(text); + if (textLen + slidingSize > textMaxLength || textLen + slidingSize < 0) { + return false; } - if (codePoint == UCodePointNull) { - *(buffer + bufferIndex) = 0; - } else { - assert(stoppingPosition != nullptr); - // Find the null-terminating code point - const char * nullTermination = currentPointer + strlen(currentPointer); - /* Copy what remains of the buffer after the stopping position for code - * point removal */ - memmove(buffer + bufferIndex, stoppingPosition, nullTermination - stoppingPosition + 1); + if (slidingSize > 0) { + memmove(text+slidingSize, text, textLen + 1); + } else if (slidingSize < 0) { + memmove(text, text-slidingSize, textLen + 1); + } + // In case slidingSize = 0, there is nothing to do + return true; +} + +/* Replaces the first chars of a string by other ones. If the sizes are different + * the rest of the string will be moved right after the replacement chars. + * If successful returns true.*/ +static bool replaceFirstCharsByPattern(char * text, size_t lengthOfPatternToRemove, const char * replacementPattern, size_t textMaxLength) { + size_t lengthOfReplacementPattern = strlen(replacementPattern); + if (lengthOfPatternToRemove <= strlen(text) && SlideStringByNumberOfChar(text, lengthOfReplacementPattern-lengthOfPatternToRemove, textMaxLength)) { + for (size_t i = 0; i < lengthOfReplacementPattern; i++) { + text[i] = replacementPattern[i]; + } + return true; + } + return false; +} + +void TryAndReplacePatternsInStringByPatterns(char * text, int textMaxLength, TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * * pointerToUpdate, const char * stoppingPosition) { + size_t i = 0; + size_t iPrev = 0; + size_t textLength = strlen(text); + size_t lengthOfParenthesisExtention = strlen("(\x11)"); + while(i < textLength) { + iPrev = i; + bool didReplace = false; + for (int j = 0; j < numberOfPairs; j++) { + TextPair p = textPairs[j]; + size_t firstStringLength = strlen(p.firstString()); + size_t secondStringLength = strlen(p.secondString()); + /* Instead of storing TextPair("√(\x11)", "sqrt(\x11)") for the keyboard + * events and TextPair("√", "sqrt") for the copy paste, we store just the + * first and register it as "function". Therefore we can decide to remove + * the (\x11) part or not depending on the application. This process is + * repeated for all 4 function keys usable in python (√, ℯ, ln, log)*/ + if (p.removeParenthesesExtention()) { + firstStringLength -= lengthOfParenthesisExtention; + secondStringLength -= lengthOfParenthesisExtention; + } + char firstString[TextPair::k_maxLength]; + char secondString[TextPair::k_maxLength]; + // Getting rid of the eventual (\x11) part + strlcpy((char *)firstString, p.firstString(), firstStringLength+1); + strlcpy((char *)secondString, p.secondString(), secondStringLength+1); + + char * matchedString = firstToSecond ? firstString : secondString; + size_t matchedStringLength = strlen(matchedString); + char * replacingString = firstToSecond ? secondString : firstString; + size_t replacingStringLength = strlen(replacingString); + + if (strncmp(&text[i], matchedString, matchedStringLength) == 0) { + didReplace = replaceFirstCharsByPattern(&text[i], matchedStringLength, replacingString, textMaxLength - i); + if (didReplace) { + int delta = replacingStringLength - matchedStringLength; + textLength += delta; + if (pointerToUpdate != nullptr && &text[i] < *pointerToUpdate) { + // We still have to update the pointer as the modification cursor has not yet exceeded it. + *pointerToUpdate = *pointerToUpdate + delta; + } + if (stoppingPosition != nullptr) { + stoppingPosition = stoppingPosition + delta; + } + if (replacingStringLength != 0) { + i += replacingStringLength - 1; + /* When working with multiple TextPairs at the same time, it can be + * usefull to go back by one char. That is the case for empty matrixes + * Indeed, in the string ",,]", ",," is replaced by ",\x11,". + * The ",]" pattern right after would be missed if not for the -1.*/ + } + } + } + } + if (iPrev == i && !didReplace) { + // In case no pattern matched with the text, we go to the next char. + i++; + } + if ((stoppingPosition != nullptr) && (&text[i] >= stoppingPosition)) { + break; + } } } @@ -407,4 +475,18 @@ const char * EndOfWord(const char * word) { return result; } +void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation) { + UTF8Helper::CodePointAction countGlyph = [](int, void * glyphCount, int, int) { + int * castedCount = (int *) glyphCount; + *castedCount = *castedCount + 1; + }; + // Count glyphs before + UTF8Helper::PerformAtCodePoints(text, UCodePointLineFeed, nullptr, countGlyph, before, 0, 0, UCodePointLineFeed, false, beforeLocation); + if (afterLocation == nullptr) { + afterLocation = beforeLocation; + } + // Count glyphs after + UTF8Helper::PerformAtCodePoints(afterLocation, UCodePointLineFeed, nullptr, countGlyph, after, 0, 0, UCodePointLineFeed); +} + } diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index c5b90d349..fbf8d2d4d 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -8,6 +8,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/serial_number.cpp \ dummy/stack.cpp \ dummy/usb.cpp \ + clipboard.cpp \ console_stdio.cpp:-consoledisplay \ rtc.cpp \ crc32.cpp \ diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index 5134ae7cb..ca319a32b 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -6,6 +6,7 @@ ion_src += $(addprefix ion/src/simulator/android/src/cpp/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/language.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index e3496ac80..a6b114ad2 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -6,6 +6,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 256685745..1ff0ebcff 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -16,6 +16,7 @@ ion_src += $(addprefix ion/src/simulator/linux/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ + clipboard_helper.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ haptics.cpp \ diff --git a/ion/src/simulator/macos/Makefile b/ion/src/simulator/macos/Makefile index 8171ffff2..a465b83c2 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -6,6 +6,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ + clipboard_helper.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ haptics.cpp \ diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index 85e4b4f86..ed1e01d5a 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -19,7 +19,7 @@ $(simulator_app_binary): $(foreach arch,$(ARCHS),$(BUILD_DIR)/$(arch)/%.bin) | $ define rule_for_asset simulator_app_deps += $(call simulator_app_resource,$(1)) $(call simulator_app_resource,$(1)): ion/src/simulator/assets/$(1) | $$$$(@D)/. - $(call rule_label,COPY) + $$(call rule_label,COPY) $(Q) cp $$^ $$@ endef diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp new file mode 100644 index 000000000..896def716 --- /dev/null +++ b/ion/src/simulator/shared/clipboard.cpp @@ -0,0 +1,40 @@ +#include +#include "clipboard_helper.h" +#include +#include + +namespace Ion { +namespace Clipboard { + +uint32_t localClipboardVersion; + +void write(const char * text) { + /* FIXME : Handle the error if need be. */ + sendToSystemClipboard(text); + localClipboardVersion = crc32Byte(reinterpret_cast(text), strlen(text)); +} + +const char * read() { + /* The buffer size is chosen to be around the size of a typical large + * python script, allowing the user to insert most scripts into the + * simulator using the paste feature. */ + constexpr size_t bufferSize = 8192; + static char buffer[bufferSize]; + fetchFromSystemClipboard(buffer, bufferSize); + if (buffer[0] == '\0') { + return nullptr; + } + + /* If version has not changed, the user has not copied any text since the + * last call to write. A copy of the text already exists in + * Escher::Clipboard, and has been translated to best suit the current app : + * we return nullptr to use that text. */ + uint32_t version = crc32Byte(reinterpret_cast(buffer), strlen(buffer)); + if (version == localClipboardVersion) { + return nullptr; + } + return buffer; +} + +} +} diff --git a/ion/src/simulator/shared/clipboard_helper.cpp b/ion/src/simulator/shared/clipboard_helper.cpp new file mode 100644 index 000000000..dfe185c93 --- /dev/null +++ b/ion/src/simulator/shared/clipboard_helper.cpp @@ -0,0 +1,30 @@ +#include "clipboard_helper.h" +#include +#include + +/* This implementation is used for all targets but the web simulator. */ + +namespace Ion { +namespace Clipboard { + +void sendToSystemClipboard(const char * text) { + SDL_SetClipboardText(text); +} + +void fetchFromSystemClipboard(char * buffer, size_t bufferSize) { + if (!SDL_HasClipboardText()) { + buffer[0] = '\0'; + return; + } + char * text = SDL_GetClipboardText(); + if (text) { + strlcpy(buffer, text, bufferSize); + } else { + buffer[0] = '\0'; + } + SDL_free(text); + +} + +} +} diff --git a/ion/src/simulator/shared/clipboard_helper.h b/ion/src/simulator/shared/clipboard_helper.h new file mode 100644 index 000000000..529b6bd3e --- /dev/null +++ b/ion/src/simulator/shared/clipboard_helper.h @@ -0,0 +1,15 @@ +#ifndef ION_CLIPBOARD_HELPER_H +#define ION_CLIPBOARD_HELPER_H + +#include + +namespace Ion { +namespace Clipboard { + +void sendToSystemClipboard(const char * text); +void fetchFromSystemClipboard(char * buffer, size_t bufferSize); + +} +} + +#endif diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index a522fe7dd..eb3e0d220 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -1,6 +1,6 @@ -#include +#include "events.h" #include "haptics.h" -#include +#include namespace Ion { namespace Events { @@ -9,5 +9,17 @@ void didPressNewKey() { Simulator::Haptics::rumble(); } +char * sharedExternalTextBuffer() { + static char buffer[sharedExternalTextBufferSize]; + return buffer; +} + +const char * Event::text() const { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return defaultText(); +} + } } diff --git a/ion/src/simulator/shared/events.h b/ion/src/simulator/shared/events.h index 5245e0a20..1770c40ba 100644 --- a/ion/src/simulator/shared/events.h +++ b/ion/src/simulator/shared/events.h @@ -1,6 +1,9 @@ #ifndef ION_SIMULATOR_EVENTS_H #define ION_SIMULATOR_EVENTS_H +#include +#include + namespace Ion { namespace Simulator { namespace Events { @@ -9,6 +12,13 @@ void dumpEventCount(int i); void logAfter(int numberOfEvents); } +} + +namespace Events { + +static constexpr size_t sharedExternalTextBufferSize = sizeof(SDL_TextInputEvent::text); +char * sharedExternalTextBuffer(); + } } diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 26d1ba208..1087528f1 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -1,9 +1,11 @@ #include "main.h" #include "platform.h" #include "layout.h" +#include "events.h" #include #include +#include #include #include @@ -133,7 +135,7 @@ static Event eventFromSDLKeyboardEvent(SDL_KeyboardEvent event) { } static constexpr Event sEventForASCIICharAbove32[95] = { - Space, Exclamation, DoubleQuotes, None, None, None, None, None, + Space, Exclamation, DoubleQuotes, None, None, Percent, None, None, LeftParenthesis, RightParenthesis, Multiplication, Plus, Comma, Minus, Dot, Division, Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Colon, SemiColon, Lower, Equal, Greater, Question, @@ -148,20 +150,25 @@ static constexpr Event sEventForASCIICharAbove32[95] = { }; static Event eventFromSDLTextInputEvent(SDL_TextInputEvent event) { - if (strlen(event.text) != 1) { - return None; + if (strlen(event.text) == 1) { + char character = event.text[0]; + if (character >= 32 && character < 127) { + /* We remove the shift, otherwise it might stay activated when it + * shouldn't. For instance on a French keyboard, to input "1", we first + * press "Shift" (which activates the Shift modifier on the calculator), + * then we press "&", transformed by eventFromSDLTextInputEvent into the + * text "1". If we do not remove the Shift here, it would still be + * pressed afterwards. */ + Ion::Events::removeShift(); + Event res = sEventForASCIICharAbove32[character-32]; + if (res != None) { + return res; + } + } } - char character = event.text[0]; - if (character >= 32 && character < 127) { - /* We remove the shift, otherwise it might stay activated when it shouldn't. - * For instance on a French keyboard, to input "1", we first press "Shift" - * (which activates the Shift modifier on the calculator), then we press - * "&", transformed by eventFromSDLTextInputEvent into the text "1". If we - * do not remove the Shift here, it would still be pressed afterwards. */ - Ion::Events::removeShift(); - return sEventForASCIICharAbove32[character-32]; - } - return None; + Ion::Events::removeShift(); + strlcpy(sharedExternalTextBuffer(), event.text, sharedExternalTextBufferSize); + return ExternalText; } Event getPlatformEvent() { diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index e8c729371..4032ef4df 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -15,6 +15,7 @@ LDFLAGS += --pre-js ion/src/simulator/web/preamble_env.js ion_src += $(addprefix ion/src/simulator/web/, \ callback.cpp \ + clipboard_helper.cpp \ helpers.cpp \ ) diff --git a/ion/src/simulator/web/clipboard_helper.cpp b/ion/src/simulator/web/clipboard_helper.cpp new file mode 100644 index 000000000..cb38c85e9 --- /dev/null +++ b/ion/src/simulator/web/clipboard_helper.cpp @@ -0,0 +1,87 @@ +#include "../shared/clipboard_helper.h" +#include +#include +#include +#include +#include +#include + +enum class AsyncStatus : uint32_t { + Pending, + Success, + Failure +}; + +/* When using emscripten, the SDL_Clipboard methods do not interact with the + * system clipboard, but only with an internal buffer. We thus need to + * implement our own methods via the JavaScript Async Clipboard API. + * + * On most browsers, accessing the clipboard is restricted to user-gestures, ie + * it must be handled directly during certain user input events. This article + * (https://webkit.org/blog/10855/async-clipboard-api/), which describe the + * Async Clipboard API for Safari 13.1 states : "The request to write to the + * clipboard must be triggered during a user gesture. A call to clipboard.write + * or clipboard.writeText outside the scope of a user gesture (such as "click" + * or "touch" event handlers) will result in the immediate rejection of the + * promise returned by the API call." + * + * On Chrome and Chromium-based browsers, this restriction has been relaxed. + * Interacting with the clipboard is possible if a user-gesture was detected + * beforehand. + * See this article for reference : + * https://discourse.wicg.io/t/user-gesture-restrictions-and-async-code/1640 + */ + +EM_JS(void, set_clipboard_text, (const char * text, AsyncStatus * status, AsyncStatus failure, AsyncStatus success), { + try { + navigator.clipboard.writeText(UTF8ToString(text)).then( + function () { HEAP32[status>>2] = success; }, + function () { HEAP32[status>>2] = failure; } + ); + } catch (error) { + console.error(error); + HEAP32[status>>2] = failure; + } +}); + +EM_JS(void, get_clipboard_text, (char * buffer, uint32_t bufferSize, AsyncStatus * status, AsyncStatus failure, AsyncStatus success), { + try { + navigator.clipboard.readText().then( + function(text) { + var lenghtBytes = Math.min(lengthBytesUTF8(text) + 1, bufferSize); + stringToUTF8(text, buffer, lenghtBytes); + HEAP32[status>>2] = success; + }, + function(text) { HEAP32[status>>2] = failure; } + ); + } catch (error) { + console.error(error); + HEAP32[status>>2] = failure; + } +}); + +namespace Ion { +namespace Clipboard { + +void sendToSystemClipboard(const char * text) { + /* As the rest of the execution does not depend on the system clipboard being + * properly filled at this point, the call to set_clipboard_text does not + * need to be made synchronous. */ + AsyncStatus lock; + set_clipboard_text(text, &lock, AsyncStatus::Failure, AsyncStatus::Success); +} + +void fetchFromSystemClipboard(char * buffer, size_t bufferSize) { + AsyncStatus lock = AsyncStatus::Pending; + static_assert(sizeof(size_t) <= sizeof(uint32_t), "Cast from size_t to uint32_t may overflow."); + get_clipboard_text(buffer, static_cast(bufferSize), &lock, AsyncStatus::Failure, AsyncStatus::Success); + while (lock == AsyncStatus::Pending) { + emscripten_sleep_with_yield(10); + } + if (lock == AsyncStatus::Success) { + return; + } +} + +} +} diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index a057924f5..70f6dc4ca 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -7,6 +7,7 @@ ion_src += $(addprefix ion/src/simulator/windows/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) diff --git a/ion/test/storage.cpp b/ion/test/storage.cpp index 65f5b4d9f..df27baadb 100644 --- a/ion/test/storage.cpp +++ b/ion/test/storage.cpp @@ -28,22 +28,22 @@ QUIZ_CASE(ion_storage_store_and_destroy_record) { const char * baseNameRecord = "ionTestStorage"; const char * extensionRecord = "record1"; - const char * dataRecord = "This is a test to ensure one can create, retreive, modify and delete records in ion's shared storage."; + const char * dataRecord = "This is a test to ensure one can create, retrieve, modify and delete records in ion's shared storage."; // Put a record in the store Storage::Record::ErrorStatus error = putRecordInSharedStorage(baseNameRecord, extensionRecord, dataRecord); quiz_assert(error == Storage::Record::ErrorStatus::None); - // Retreive the record - Storage::Record retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + // Retrieve the record + Storage::Record retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); size_t dataRecordSize = strlen(dataRecord); - quiz_assert(retreivedRecord.value().size == dataRecordSize); - quiz_assert(strcmp(dataRecord, static_cast(retreivedRecord.value().buffer)) == 0); + quiz_assert(retrievedRecord.value().size == dataRecordSize); + quiz_assert(strcmp(dataRecord, static_cast(retrievedRecord.value().buffer)) == 0); // Destroy it - retreivedRecord.destroy(); - retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); - quiz_assert(retreivedRecord == Storage::Record()); + retrievedRecord.destroy(); + retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + quiz_assert(retrievedRecord == Storage::Record()); quiz_assert(Storage::sharedStorage()->availableSize() == initialStorageAvailableStage); } @@ -53,7 +53,7 @@ QUIZ_CASE(ion_storage_put_record_twice) { const char * baseNameRecord = "ionTestStorage"; const char * extensionRecord = "record"; - const char * dataRecord = "This is a test to ensure one can create, retreive, modify and delete records in ion's shared storage."; + const char * dataRecord = "This is a test to ensure one can create, retrieve, modify and delete records in ion's shared storage."; // Put a record in the store Storage::Record::ErrorStatus error = putRecordInSharedStorage(baseNameRecord, extensionRecord, dataRecord); @@ -63,16 +63,16 @@ QUIZ_CASE(ion_storage_put_record_twice) { error = putRecordInSharedStorage(baseNameRecord, extensionRecord, dataRecord); quiz_assert(error == Storage::Record::ErrorStatus::NameTaken); - // Retreive the record - Storage::Record retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + // Retrieve the record + Storage::Record retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); size_t dataRecordSize = strlen(dataRecord); - quiz_assert(retreivedRecord.value().size == dataRecordSize); - quiz_assert(strcmp(dataRecord, static_cast(retreivedRecord.value().buffer)) == 0); + quiz_assert(retrievedRecord.value().size == dataRecordSize); + quiz_assert(strcmp(dataRecord, static_cast(retrievedRecord.value().buffer)) == 0); // Destroy it - retreivedRecord.destroy(); - retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); - quiz_assert(retreivedRecord == Storage::Record()); + retrievedRecord.destroy(); + retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + quiz_assert(retrievedRecord == Storage::Record()); quiz_assert(Storage::sharedStorage()->availableSize() == initialStorageAvailableStage); } @@ -81,27 +81,27 @@ QUIZ_CASE(ion_storage_invalid_renaming) { const char * baseNameRecord = "ionTestStorage"; const char * extensionRecord = "record1"; - const char * dataRecord = "This is a test to ensure one can create, retreive, modify and delete records in ion's shared storage."; + const char * dataRecord = "This is a test to ensure one can create, retrieve, modify and delete records in ion's shared storage."; // Put a record in the store Storage::Record::ErrorStatus error = putRecordInSharedStorage(baseNameRecord, extensionRecord, dataRecord); quiz_assert(error == Storage::Record::ErrorStatus::None); - // Retreive the record - Storage::Record retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + // Retrieve the record + Storage::Record retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); size_t dataRecordSize = strlen(dataRecord); - quiz_assert(retreivedRecord.value().size == dataRecordSize); - quiz_assert(strcmp(dataRecord, static_cast(retreivedRecord.value().buffer)) == 0); + quiz_assert(retrievedRecord.value().size == dataRecordSize); + quiz_assert(strcmp(dataRecord, static_cast(retrievedRecord.value().buffer)) == 0); // Rename the record with an invalid name const char * fullNameRecord2 = "invalidNameWithoutDot"; - error = retreivedRecord.setName(fullNameRecord2); + error = retrievedRecord.setName(fullNameRecord2); quiz_assert(error == Storage::Record::ErrorStatus::NonCompliantName); // Destroy it - retreivedRecord.destroy(); - retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); - quiz_assert(retreivedRecord == Storage::Record()); + retrievedRecord.destroy(); + retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + quiz_assert(retrievedRecord == Storage::Record()); quiz_assert(Storage::sharedStorage()->availableSize() == initialStorageAvailableStage); } @@ -110,35 +110,35 @@ QUIZ_CASE(ion_storage_valid_renaming) { const char * baseNameRecord = "ionTestStorage"; const char * extensionRecord = "record1"; - const char * dataRecord = "This is a test to ensure one can create, retreive, modify and delete records in ion's shared storage."; + const char * dataRecord = "This is a test to ensure one can create, retrieve, modify and delete records in ion's shared storage."; // Put a record in the store Storage::Record::ErrorStatus error = putRecordInSharedStorage(baseNameRecord, extensionRecord, dataRecord); quiz_assert(error == Storage::Record::ErrorStatus::None); - // Retreive the record - Storage::Record retreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + // Retrieve the record + Storage::Record retrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); size_t dataRecordSize = strlen(dataRecord); - quiz_assert(retreivedRecord.value().size == dataRecordSize); - quiz_assert(strcmp(dataRecord, static_cast(retreivedRecord.value().buffer)) == 0); + quiz_assert(retrievedRecord.value().size == dataRecordSize); + quiz_assert(strcmp(dataRecord, static_cast(retrievedRecord.value().buffer)) == 0); // Rename the record with a valid name const char * newFullNameRecord = "testStorage.record2"; - error = retreivedRecord.setName(newFullNameRecord); + error = retrievedRecord.setName(newFullNameRecord); quiz_assert(error == Storage::Record::ErrorStatus::None); - // Retreive the previous record - Storage::Record oldRetreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); - quiz_assert(oldRetreivedRecord == Storage::Record()); + // Retrieve the previous record + Storage::Record oldRetrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + quiz_assert(oldRetrievedRecord == Storage::Record()); - // Retreive the new record - Storage::Record newRetreivedRecord = Storage::sharedStorage()->recordNamed(newFullNameRecord); - quiz_assert(strcmp(dataRecord, static_cast(newRetreivedRecord.value().buffer)) == 0); + // Retrieve the new record + Storage::Record newRetrievedRecord = Storage::sharedStorage()->recordNamed(newFullNameRecord); + quiz_assert(strcmp(dataRecord, static_cast(newRetrievedRecord.value().buffer)) == 0); // Destroy it - newRetreivedRecord.destroy(); - newRetreivedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); - quiz_assert(newRetreivedRecord == Storage::Record()); + newRetrievedRecord.destroy(); + newRetrievedRecord = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord, extensionRecord); + quiz_assert(newRetrievedRecord == Storage::Record()); quiz_assert(Storage::sharedStorage()->availableSize() == initialStorageAvailableStage); } @@ -480,23 +480,23 @@ aaaaaa)"; error = putRecordInSharedStorage(baseNameRecord4, extensionRecord, dataRecord3); quiz_assert(error == Storage::Record::ErrorStatus::None); - // Retreive the record - Storage::Record retreivedRecord1 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord1, extensionRecord); - Storage::Record retreivedRecord2 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord2, extensionRecord); - Storage::Record retreivedRecord3 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord3, extensionRecord); - Storage::Record retreivedRecord4 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord4, extensionRecord); + // Retrieve the record + Storage::Record retrievedRecord1 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord1, extensionRecord); + Storage::Record retrievedRecord2 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord2, extensionRecord); + Storage::Record retrievedRecord3 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord3, extensionRecord); + Storage::Record retrievedRecord4 = Storage::sharedStorage()->recordBaseNamedWithExtension(baseNameRecord4, extensionRecord); // Put the the available space at the end of the first record and remove it size_t availableSpace = Storage::sharedStorage()->availableSize(); uint32_t checksumBeforeChanges = Storage::sharedStorage()->checksum(); - Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(retreivedRecord1); - Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(retreivedRecord1, availableSpace); + Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(retrievedRecord1); + Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord(retrievedRecord1, availableSpace); quiz_assert(Storage::sharedStorage()->availableSize() == availableSpace); quiz_assert(Storage::sharedStorage()->checksum() == checksumBeforeChanges); // Destroy it - retreivedRecord1.destroy(); - retreivedRecord2.destroy(); - retreivedRecord3.destroy(); - retreivedRecord4.destroy(); + retrievedRecord1.destroy(); + retrievedRecord2.destroy(); + retrievedRecord3.destroy(); + retrievedRecord4.destroy(); } diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index b96f361d9..4e182142b 100644 --- a/ion/test/utf8_helper.cpp +++ b/ion/test/utf8_helper.cpp @@ -53,9 +53,8 @@ void assert_copy_and_remove_code_points_gives(char * dst, size_t dstSize, const quiz_assert(dst[i] == result[i]); } } - +static int bufferSize = 100; QUIZ_CASE(ion_utf8_copy_and_remove_code_point) { - constexpr int bufferSize = 100; char buffer[bufferSize]; const char * s = "12345"; @@ -116,7 +115,6 @@ void assert_remove_code_point_gives(char * buffer, CodePoint c, const char * * i } QUIZ_CASE(ion_utf8_remove_code_point) { - constexpr int bufferSize = 100; char buffer[bufferSize]; const char * s = "2345"; @@ -165,13 +163,77 @@ QUIZ_CASE(ion_utf8_remove_code_point) { assert_remove_code_point_gives(buffer, c, &indexToUpdate, stoppingPosition, indexToUpdateResult, result); } +void assert_slide_string_by_number_of_char_gives(const char * string, int slidingSize, bool successResult, const char * stringResult = nullptr) { + char buffer[bufferSize]; + strlcpy(buffer, string, bufferSize); + bool success = UTF8Helper::SlideStringByNumberOfChar((char *)buffer, slidingSize, bufferSize); + quiz_assert(success == successResult); + if (successResult) { + quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0); + } +} + + +QUIZ_CASE(ion_utf8_move_string_from_index_by_number_of_char) { + const char * string1 = "12345"; + assert_slide_string_by_number_of_char_gives(string1, 1, true, "112345"); + const char * string2 = "(1+3)"; + assert_slide_string_by_number_of_char_gives(string2, 3, true, "(1+(1+3)"); + assert_slide_string_by_number_of_char_gives(string2, bufferSize - strlen(string2)/2, false); + const char * string3 = "exp(3+4)"; + assert_slide_string_by_number_of_char_gives(string3, -3, true, "(3+4)"); + assert_slide_string_by_number_of_char_gives(string3, -(strlen(string3)+3), false); + assert_slide_string_by_number_of_char_gives(string3, -8, true, ""); +} + +void assert_try_and_replace_pattern_in_string_by_pattern_gives(char * buffer, int bufferSize, UTF8Helper::TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * stringResult, const char ** indexToUpdate = nullptr, const char * indexToUpdateResult = nullptr, const char * stoppingPosition = nullptr) { + UTF8Helper::TryAndReplacePatternsInStringByPatterns(buffer, bufferSize, textPairs, numberOfPairs, firstToSecond, indexToUpdate, stoppingPosition); + quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0); + if (indexToUpdateResult != nullptr) { + quiz_assert(*indexToUpdate == indexToUpdateResult); + } +} + +QUIZ_CASE(ion_utf8_try_and_replace_pattern_in_string_by_pattern) { + constexpr int numberOfPairs = 2; + constexpr UTF8Helper::TextPair textPairs[numberOfPairs] = { + UTF8Helper::TextPair("12", "2.3"), + UTF8Helper::TextPair("exp", "ln"), + }; + + char buffer[bufferSize]; + const char * string = "1234512"; + strlcpy(buffer, string, bufferSize); + const char * indexToUpdate = buffer + 3; + const char * indexToUpdateResult = indexToUpdate + 1; + const char * result = "2.33452.3"; + const char * stoppingPosition = nullptr; + assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, true, result, &indexToUpdate, indexToUpdateResult); + + string = "exp(2.3)12"; + strlcpy(buffer, string, bufferSize); + indexToUpdate = buffer + 3; + indexToUpdateResult = indexToUpdate - 1; + result = "ln(2.3)12"; + stoppingPosition = buffer + 5; + assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, true, result, &indexToUpdate, indexToUpdateResult, stoppingPosition); + + string = "12*ln(7)+ln"; + strlcpy(buffer, string, bufferSize); + indexToUpdate = buffer + 7; + indexToUpdateResult = indexToUpdate + 1; + result = "12*exp(7)+ln"; + stoppingPosition = buffer + 7; + assert_try_and_replace_pattern_in_string_by_pattern_gives(buffer, bufferSize, (UTF8Helper::TextPair *)&textPairs, numberOfPairs, false, result, &indexToUpdate, indexToUpdateResult, stoppingPosition); + +} + void assert_string_copy_until_code_point_gives(char * dst, size_t dstSize, const char * src, CodePoint c, const char * result, size_t returnedResult) { quiz_assert(UTF8Helper::CopyUntilCodePoint(dst, dstSize, src, c) == returnedResult); quiz_assert(strcmp(dst, result) == 0); } QUIZ_CASE(ion_utf8_helper_copy_until_code_point) { - constexpr int bufferSize = 100; char buffer[bufferSize]; const char * s = "1234"; @@ -217,7 +279,6 @@ void assert_string_remove_previous_glyph_gives(const char * text, char * locatio } QUIZ_CASE(ion_utf8_helper_remove_previous_glyph) { - constexpr int bufferSize = 100; char buffer[bufferSize]; // 3é4 buffer[0] = '3'; diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index 0d7966eae..ddf0ade0a 100644 Binary files a/kandinsky/fonts/LargeFont.ttf and b/kandinsky/fonts/LargeFont.ttf differ diff --git a/kandinsky/fonts/SmallFont.ttf b/kandinsky/fonts/SmallFont.ttf index 1473fd3a5..204fa7753 100644 Binary files a/kandinsky/fonts/SmallFont.ttf and b/kandinsky/fonts/SmallFont.ttf differ diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 967c5457e..5c953f2de 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -204,10 +204,10 @@ int main(int argc, char * argv[]) { fprintf(sourceFile, "static constexpr KDCoordinate glyphWidth = %d;\n\n", glyph_width); fprintf(sourceFile, "static constexpr KDCoordinate glyphHeight = %d;\n\n", glyph_height); - int greyscaleBitsPerPixel = 4; + int grayscaleBitsPerPixel = 4; - int sizeOfUncompressedGlyphBuffer = glyph_width * glyph_height * greyscaleBitsPerPixel/8; - ENSURE(8*sizeOfUncompressedGlyphBuffer == glyph_width * glyph_height * greyscaleBitsPerPixel, "Error: the glyph size (%dx%d@%dbpp) cannot fit in an integral number of bytes", glyph_width, glyph_height, greyscaleBitsPerPixel); + int sizeOfUncompressedGlyphBuffer = glyph_width * glyph_height * grayscaleBitsPerPixel/8; + ENSURE(8*sizeOfUncompressedGlyphBuffer == glyph_width * glyph_height * grayscaleBitsPerPixel, "Error: the glyph size (%dx%d@%dbpp) cannot fit in an integral number of bytes", glyph_width, glyph_height, grayscaleBitsPerPixel); uint8_t * uncompressedGlyphBuffer = (uint8_t *)malloc(sizeOfUncompressedGlyphBuffer); uint16_t glyphDataOffset[NumberOfCodePoints+1]; @@ -225,9 +225,9 @@ int main(int argc, char * argv[]) { for (int x = 0; x < glyph_width; x++) { pixel_t * pixel = (bitmap_image.pixels + (y+characterY)*bitmap_image.width + (x+characterX)); - uint8_t greyscaleValue = (0xFF - pixel->green) >> (8 - greyscaleBitsPerPixel); - accumulator = (accumulator << greyscaleBitsPerPixel) | greyscaleValue; - if (numberOfValuesAccumulated++ == (8/greyscaleBitsPerPixel)-1) { + uint8_t grayscaleValue = (0xFF - pixel->green) >> (8 - grayscaleBitsPerPixel); + accumulator = (accumulator << grayscaleBitsPerPixel) | grayscaleValue; + if (numberOfValuesAccumulated++ == (8/grayscaleBitsPerPixel)-1) { uncompressedGlyphBuffer[uncompressedGlyphBufferIndex++] = accumulator; accumulator = 0; numberOfValuesAccumulated = 0; @@ -261,7 +261,7 @@ int main(int argc, char * argv[]) { size_t initialDataSize = NumberOfCodePoints * glyph_width * glyph_height; fprintf(sourceFile, "/* Rasterized = %5zu bytes (%d glyphs x %d pixels)\n", initialDataSize, NumberOfCodePoints, glyph_width*glyph_height); - fprintf(sourceFile, " * Downsampled = %5lu bytes (1/%d of rasterized)\n", initialDataSize*greyscaleBitsPerPixel/8, 8/greyscaleBitsPerPixel); + fprintf(sourceFile, " * Downsampled = %5lu bytes (1/%d of rasterized)\n", initialDataSize*grayscaleBitsPerPixel/8, 8/grayscaleBitsPerPixel); fprintf(sourceFile, " * Compressed = %5zu bytes (%.2f%% of rasterized) */\n", finalDataSize, 100.0*finalDataSize/initialDataSize); fprintf(sourceFile, "static constexpr uint8_t glyphData[%d] = {", lastOffset); diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 73db61205..3195d0180 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -33,6 +33,8 @@ public: static constexpr const KDFont * LargeFont = &privateLargeFont; static constexpr const KDFont * SmallFont = &privateSmallFont; + static bool CanBeWrittenWithGlyphs(const char * text); + KDSize stringSize(const char * text, int textLength = -1) const { return stringSizeUntil(text, textLength < 0 ? nullptr : text + textLength); } @@ -42,10 +44,10 @@ public: public: GlyphBuffer() {} // Don't initialize either buffer KDColor * colorBuffer() { return m_colors; } - uint8_t * greyscaleBuffer() { return m_greyscales; } - uint8_t * secondaryGreyscaleBuffer() { return m_greyscales + k_maxGlyphPixelCount; } + uint8_t * grayscaleBuffer() { return m_grayscales; } + uint8_t * secondaryGrayscaleBuffer() { return m_grayscales + k_maxGlyphPixelCount; } private: - uint8_t m_greyscales[2*k_maxGlyphPixelCount]; + uint8_t m_grayscales[2*k_maxGlyphPixelCount]; KDColor m_colors[k_maxGlyphPixelCount]; }; @@ -62,8 +64,8 @@ public: static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 134; GlyphIndex indexForCodePoint(CodePoint c) const; - void setGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; - void accumulateGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; + void setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; + void accumulateGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; using RenderPalette = KDPalette<(1<; void colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffer * glyphBuffer) const; @@ -76,7 +78,7 @@ public: constexpr KDFont(size_t tableLength, const CodePointIndexPair * table, KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : m_tableLength(tableLength), m_table(table), m_glyphSize(glyphWidth, glyphHeight), m_glyphDataOffset(glyphDataOffset), m_data(data) { } private: - void fetchGreyscaleGlyphAtIndex(GlyphIndex index, uint8_t * greyscaleBuffer) const; + void fetchGrayscaleGlyphAtIndex(GlyphIndex index, uint8_t * grayscaleBuffer) const; const uint8_t * compressedGlyphData(GlyphIndex index) const { return m_data + m_glyphDataOffset[index]; diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index 2295274a1..c1fd73a6e 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -28,6 +28,7 @@ KDPoint KDContext::pushOrPullString(const char * text, KDPoint p, const KDFont * while (codePoint != UCodePointNull && (maxByteLength < 0 || codePointPointer < text + maxByteLength)) { codePointPointer = decoder.stringPosition(); if (codePoint == UCodePointLineFeed) { + assert(position.y() < KDCOORDINATE_MAX - glyphSize.height()); position = KDPoint(0, position.y() + glyphSize.height()); codePoint = decoder.nextCodePoint(); } else if (codePoint == UCodePointTabulation) { @@ -35,10 +36,10 @@ KDPoint KDContext::pushOrPullString(const char * text, KDPoint p, const KDFont * codePoint = decoder.nextCodePoint(); } else { assert(!codePoint.isCombining()); - font->setGlyphGreyscalesForCodePoint(codePoint, &glyphBuffer); + font->setGlyphGrayscalesForCodePoint(codePoint, &glyphBuffer); codePoint = decoder.nextCodePoint(); while (codePoint.isCombining()) { - font->accumulateGlyphGreyscalesForCodePoint(codePoint, &glyphBuffer); + font->accumulateGlyphGrayscalesForCodePoint(codePoint, &glyphBuffer); codePointPointer = decoder.stringPosition(); codePoint = decoder.nextCodePoint(); } diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index abc44e7cf..25d82e93a 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -30,54 +30,55 @@ KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const { currentStringPosition = decoder.stringPosition(); codePoint = decoder.nextCodePoint(); } + assert(stringSize.width() >= 0 && stringSize.height() >= 0); return stringSize; } -void KDFont::setGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { - fetchGreyscaleGlyphAtIndex(indexForCodePoint(codePoint), glyphBuffer->greyscaleBuffer()); +void KDFont::setGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { + fetchGrayscaleGlyphAtIndex(indexForCodePoint(codePoint), glyphBuffer->grayscaleBuffer()); } -void KDFont::accumulateGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { - uint8_t * greyscaleBuffer = glyphBuffer->greyscaleBuffer(); - uint8_t * accumulationGreyscaleBuffer = glyphBuffer->secondaryGreyscaleBuffer(); - fetchGreyscaleGlyphAtIndex(indexForCodePoint(codePoint), accumulationGreyscaleBuffer); +void KDFont::accumulateGlyphGrayscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { + uint8_t * grayscaleBuffer = glyphBuffer->grayscaleBuffer(); + uint8_t * accumulationGrayscaleBuffer = glyphBuffer->secondaryGrayscaleBuffer(); + fetchGrayscaleGlyphAtIndex(indexForCodePoint(codePoint), accumulationGrayscaleBuffer); for (int i=0; igreyscaleBuffer(); + uint8_t * grayscaleBuffer = glyphBuffer->grayscaleBuffer(); KDColor * colorBuffer = glyphBuffer->colorBuffer(); uint8_t mask = (0xFF >> (8-k_bitsPerPixel)); int pixelIndex = m_glyphSize.width() * m_glyphSize.height() - 1; // Let's start at the final pixel - int greyscaleByteIndex = pixelIndex * k_bitsPerPixel / 8; + int grayscaleByteIndex = pixelIndex * k_bitsPerPixel / 8; while (pixelIndex >= 0) { - assert(greyscaleByteIndex == pixelIndex * k_bitsPerPixel / 8); - uint8_t greyscaleByte = greyscaleBuffer[greyscaleByteIndex--]; // We consume a greyscale byte... + assert(grayscaleByteIndex == pixelIndex * k_bitsPerPixel / 8); + uint8_t grayscaleByte = grayscaleBuffer[grayscaleByteIndex--]; // We consume a grayscale byte... for (int j=0; j<8/k_bitsPerPixel; j++) { // .. and we'll output 8/k_bits pixels - uint8_t greyscale = greyscaleByte & mask; - greyscaleByte = greyscaleByte >> k_bitsPerPixel; + uint8_t grayscale = grayscaleByte & mask; + grayscaleByte = grayscaleByte >> k_bitsPerPixel; assert(pixelIndex >= 0); - colorBuffer[pixelIndex--] = renderPalette->colorAtIndex(greyscale); + colorBuffer[pixelIndex--] = renderPalette->colorAtIndex(grayscale); } } } @@ -158,3 +159,17 @@ KDFont::GlyphIndex KDFont::indexForCodePoint(CodePoint c) const { return IndexForReplacementCharacterCodePoint; #endif } + +bool KDFont::CanBeWrittenWithGlyphs(const char * text) { + UTF8Decoder decoder(text); + CodePoint cp = decoder.nextCodePoint(); + while(cp != UCodePointNull) { + if (LargeFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint + || SmallFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint) + { + return false; + } + cp = decoder.nextCodePoint(); + } + return true; +} diff --git a/kandinsky/src/point.cpp b/kandinsky/src/point.cpp index 796232558..f6a096de4 100644 --- a/kandinsky/src/point.cpp +++ b/kandinsky/src/point.cpp @@ -1,6 +1,10 @@ #include +#include KDPoint KDPoint::translatedBy(KDPoint other) const { + assert((other.x() >= 0 && m_x <= KDCOORDINATE_MAX - other.x()) || (other.x() < 0 && m_x >= KDCOORDINATE_MIN - other.x())); + assert((other.y() >= 0 && m_y <= KDCOORDINATE_MAX - other.y()) || (other.y() < 0 && m_y >= KDCOORDINATE_MIN - other.y())); + return KDPoint(m_x+other.x(), m_y+other.y()); } diff --git a/liba/Makefile b/liba/Makefile index 676fa88d0..639bf196d 100644 --- a/liba/Makefile +++ b/liba/Makefile @@ -3,6 +3,7 @@ SFLAGS += -Iliba/include liba_src += $(addprefix liba/src/, \ armv7m/setjmp.s \ armv7m/longjmp.s \ + abs.c \ assert.c \ bzero.c \ ctype.c \ @@ -69,6 +70,8 @@ liba_src += $(addprefix liba/src/external/openbsd/, \ s_logbf.c \ s_modf.c \ s_modff.c \ + s_nextafter.c \ + s_nextafterf.c \ s_rint.c \ s_roundf.c \ s_scalbnf.c \ diff --git a/liba/include/float.h b/liba/include/float.h index bc2084890..a541b8e88 100644 --- a/liba/include/float.h +++ b/liba/include/float.h @@ -5,7 +5,8 @@ #define FLT_MAX 1E+37f #define FLT_MIN 1E-37f -#define FLT_EPSILON 1E-5f +#define FLT_EPSILON 1.1920928955078125e-7 + #define DBL_MAX 1.79769313486231571e+308 #define DBL_MIN 2.22507385850720138e-308 #define DBL_EPSILON 2.2204460492503131e-16 diff --git a/liba/include/math.h b/liba/include/math.h index 3cb95bcb5..4c05d5bcb 100644 --- a/liba/include/math.h +++ b/liba/include/math.h @@ -70,6 +70,7 @@ float log10f(float x); float logf(float x); float modff(float x, float *iptr); float nearbyintf(float x); +float nextafterf(float from, float to); float powf(float x, float y); float roundf(float x); float scalbnf(float x, int exp); @@ -110,6 +111,7 @@ double log2(double x); double logb(double x); double modf(double x, double *iptr); double nearbyint(double x); +double nextafter(double from, double to); double pow(double x, double y); double rint(double x); double round(double x); @@ -156,6 +158,7 @@ extern int signgam; #define modff(x, iptr) __builtin_modff(x, iptr) #define nanf(tagp) __builtin_nanf(tagp) #define nearbyintf(x) __builtin_nearbyintf(x) +#define nextafterf(from, to) __builtin_nextafterf(from, to) #define powf(x, y) __builtin_powf(x, y) #define roundf(x) __builtin_roundf(x) #define scalbnf(x, exp) __builtin_scalbnf(x, exp) @@ -196,6 +199,7 @@ extern int signgam; #define modf(x, iptr) __builtin_modf(x, iptr) #define nan(tagp) __builtin_nan(tagp) #define nearbyint(x) __builtin_nearbyint(x) +#define nextafter(from, to) __builtin_nextafter(from, to) #define pow(x, y) __builtin_pow(x, y) #define rint(x) __builtin_rint(x) #define round(x) __builtin_round(x) diff --git a/liba/include/stdint.h b/liba/include/stdint.h index 1096e5ab1..73a2be5e8 100644 --- a/liba/include/stdint.h +++ b/liba/include/stdint.h @@ -26,6 +26,9 @@ typedef int64_t int_fast64_t; typedef uint8_t uint_least8_t; +#define INT8_MAX 0x7f +#define INT8_MIN (-INT8_MAX-1) + #define UINT8_MAX 0xff #define UINT16_MAX 0xffff diff --git a/liba/include/stdlib.h b/liba/include/stdlib.h index 6cf0a9654..6c27b3d6e 100644 --- a/liba/include/stdlib.h +++ b/liba/include/stdlib.h @@ -13,6 +13,8 @@ void * calloc(size_t count, size_t size); void abort(void) __attribute__((noreturn)); +int abs(int n); + LIBA_END_DECLS #endif diff --git a/liba/src/abs.c b/liba/src/abs.c new file mode 100644 index 000000000..8d0efabdc --- /dev/null +++ b/liba/src/abs.c @@ -0,0 +1,5 @@ +#include + +int abs(int n) { + return n < 0 ? -n : n; +} \ No newline at end of file diff --git a/liba/src/external/openbsd/s_nextafter.c b/liba/src/external/openbsd/s_nextafter.c new file mode 100644 index 000000000..199926cd8 --- /dev/null +++ b/liba/src/external/openbsd/s_nextafter.c @@ -0,0 +1,71 @@ +/* @(#)s_nextafter.c 5.1 93/09/24 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* IEEE functions + * nextafter(x,y) + * return the next machine floating-point number of x in the + * direction toward y. + * Special cases: + */ + +#include "math.h" +#include "math_private.h" + +double +nextafter(double x, double y) +{ + int32_t hx,hy,ix,iy; + u_int32_t lx,ly; + + EXTRACT_WORDS(hx,lx,x); + EXTRACT_WORDS(hy,ly,y); + ix = hx&0x7fffffff; /* |x| */ + iy = hy&0x7fffffff; /* |y| */ + + if(((ix>=0x7ff00000)&&((ix-0x7ff00000)|lx)!=0) || /* x is nan */ + ((iy>=0x7ff00000)&&((iy-0x7ff00000)|ly)!=0)) /* y is nan */ + return x+y; + if(x==y) return x; /* x=y, return x */ + if((ix|lx)==0) { /* x == 0 */ + INSERT_WORDS(x,hy&0x80000000,1); /* return +-minsubnormal */ + y = x*x; + if(y==x) return y; else return x; /* raise underflow flag */ + } + if(hx>=0) { /* x > 0 */ + if(hx>hy||((hx==hy)&&(lx>ly))) { /* x > y, x -= ulp */ + if(lx==0) hx -= 1; + lx -= 1; + } else { /* x < y, x += ulp */ + lx += 1; + if(lx==0) hx += 1; + } + } else { /* x < 0 */ + if(hy>=0||hx>hy||((hx==hy)&&(lx>ly))){/* x < y, x -= ulp */ + if(lx==0) hx -= 1; + lx -= 1; + } else { /* x > y, x += ulp */ + lx += 1; + if(lx==0) hx += 1; + } + } + hy = hx&0x7ff00000; + if(hy>=0x7ff00000) return x+x; /* overflow */ + if(hy<0x00100000) { /* underflow */ + y = x*x; + if(y!=x) { /* raise underflow flag */ + INSERT_WORDS(y,hx,lx); + return y; + } + } + INSERT_WORDS(x,hx,lx); + return x; +} diff --git a/liba/src/external/openbsd/s_nextafterf.c b/liba/src/external/openbsd/s_nextafterf.c new file mode 100644 index 000000000..e5c3d9f9f --- /dev/null +++ b/liba/src/external/openbsd/s_nextafterf.c @@ -0,0 +1,62 @@ +/* s_nextafterf.c -- float version of s_nextafter.c. + * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com. + */ + +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +#include "math.h" +#include "math_private.h" + +float +nextafterf(float x, float y) +{ + int32_t hx,hy,ix,iy; + + GET_FLOAT_WORD(hx,x); + GET_FLOAT_WORD(hy,y); + ix = hx&0x7fffffff; /* |x| */ + iy = hy&0x7fffffff; /* |y| */ + + if((ix>0x7f800000) || /* x is nan */ + (iy>0x7f800000)) /* y is nan */ + return x+y; + if(x==y) return x; /* x=y, return x */ + if(ix==0) { /* x == 0 */ + SET_FLOAT_WORD(x,(hy&0x80000000)|1);/* return +-minsubnormal */ + y = x*x; + if(y==x) return y; else return x; /* raise underflow flag */ + } + if(hx>=0) { /* x > 0 */ + if(hx>hy) { /* x > y, x -= ulp */ + hx -= 1; + } else { /* x < y, x += ulp */ + hx += 1; + } + } else { /* x < 0 */ + if(hy>=0||hx>hy){ /* x < y, x -= ulp */ + hx -= 1; + } else { /* x > y, x += ulp */ + hx += 1; + } + } + hy = hx&0x7f800000; + if(hy>=0x7f800000) return x+x; /* overflow */ + if(hy<0x00800000) { /* underflow */ + y = x*x; + if(y!=x) { /* raise underflow flag */ + SET_FLOAT_WORD(y,hx); + return y; + } + } + SET_FLOAT_WORD(x,hx); + return x; +} \ No newline at end of file diff --git a/libaxx/include/cmath b/libaxx/include/cmath index 9fdad6b7a..cc42319e7 100644 --- a/libaxx/include/cmath +++ b/libaxx/include/cmath @@ -39,6 +39,7 @@ #undef modff #undef nanf #undef nearbyintf +#undef nextafter #undef powf #undef roundf #undef scalbnf @@ -81,6 +82,7 @@ #undef modf #undef nan #undef nearbyint +#undef nextafter #undef pow #undef rint #undef round @@ -134,6 +136,7 @@ inline constexpr float logb(float x) { return __builtin_logbf(x); } inline constexpr float modf(float x, float *iptr) { return __builtin_modff(x, iptr); } inline constexpr float nanf(const char *tagp) { return __builtin_nanf(tagp); } inline constexpr float nearbyint(float x) { return __builtin_nearbyintf(x); } +inline constexpr float nextafter(float from, float to) { return __builtin_nextafterf(from, to); } inline constexpr float pow(float x, float y) { return __builtin_powf(x, y); } inline constexpr float round(float x) { return __builtin_roundf(x); } inline constexpr float scalbn(float x, int exp) { return __builtin_scalbnf(x, exp); } @@ -183,6 +186,7 @@ inline constexpr double logb(double x) { return __builtin_logb(x); } inline constexpr double modf(double x, double *iptr) { return __builtin_modf(x, iptr); } inline constexpr double nan(const char *tagp) { return __builtin_nan(tagp); } inline constexpr double nearbyint(double x) { return __builtin_nearbyint(x); } +inline constexpr double nextafter(double from, double to) { return __builtin_nextafter(from, to); } inline constexpr double pow(double x, double y) { return __builtin_pow(x, y); } inline constexpr double rint(double x) { return __builtin_rint(x); } inline constexpr double round(double x) { return __builtin_round(x); } diff --git a/poincare/Makefile b/poincare/Makefile index bac81600c..0e368f2f5 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -101,8 +101,12 @@ poincare_src += $(addprefix poincare/src/,\ matrix_inverse.cpp \ matrix_trace.cpp \ matrix_transpose.cpp \ + matrix_echelon_form.cpp \ + matrix_row_echelon_form.cpp \ + matrix_reduced_row_echelon_form.cpp \ multiplication.cpp \ n_ary_expression.cpp \ + n_ary_infix_expression.cpp \ naperian_logarithm.cpp \ norm_cdf.cpp \ norm_cdf2.cpp \ @@ -133,6 +137,7 @@ poincare_src += $(addprefix poincare/src/,\ solver.cpp \ square_root.cpp \ store.cpp \ + sum_and_product.cpp \ subtraction.cpp \ sum.cpp \ symbol.cpp \ @@ -148,6 +153,10 @@ poincare_src += $(addprefix poincare/src/,\ unit_convert.cpp \ unreal.cpp \ variable_context.cpp \ + vector_cross.cpp \ + vector_dot.cpp \ + vector_norm.cpp \ + zoom.cpp \ ) poincare_src += $(addprefix poincare/src/parsing/,\ @@ -162,6 +171,7 @@ tests_src += $(addprefix poincare/test/,\ arithmetic.cpp\ context.cpp\ erf_inv.cpp \ + derivative.cpp\ expression.cpp\ expression_order.cpp\ expression_properties.cpp\ @@ -170,7 +180,6 @@ tests_src += $(addprefix poincare/test/,\ function_solver.cpp\ helper.cpp\ helpers.cpp\ - ieee754.cpp\ integer.cpp\ layout.cpp\ layout_cursor.cpp\ @@ -182,6 +191,7 @@ tests_src += $(addprefix poincare/test/,\ rational.cpp\ regularized_incomplete_beta_function.cpp \ simplification.cpp\ + zoom.cpp\ ) ifeq ($(DEBUG),1) diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index 57be4dd32..f16a3508b 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -21,17 +21,18 @@ public: // Properties Type type() const override { return Type::AbsoluteValue; } Sign sign(Context * context) const override { return Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::abs(c)); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } // Layout @@ -41,6 +42,8 @@ public: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } +private: + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; }; class AbsoluteValue final : public Expression { @@ -52,6 +55,7 @@ public: static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("abs", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); }; } diff --git a/poincare/include/poincare/absolute_value_layout.h b/poincare/include/poincare/absolute_value_layout.h index d1cab825d..e0a81610f 100644 --- a/poincare/include/poincare/absolute_value_layout.h +++ b/poincare/include/poincare/absolute_value_layout.h @@ -29,7 +29,8 @@ public: private: KDCoordinate widthMargin() const override { return 2; } - virtual KDCoordinate verticalExternMargin() const override { return 1; } + KDCoordinate verticalMargin() const override { return 0; } + KDCoordinate verticalExternMargin() const override { return 1; } bool renderTopBar() const override { return false; } bool renderBottomBar() const override { return false; } }; diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index d49303723..738600510 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -2,15 +2,15 @@ #define POINCARE_ADDITION_H #include -#include +#include #include namespace Poincare { -class AdditionNode final : public NAryExpressionNode { +class AdditionNode final : public NAryInfixExpressionNode { friend class Addition; public: - using NAryExpressionNode::NAryExpressionNode; + using NAryInfixExpressionNode::NAryInfixExpressionNode; // Tree size_t size() const override { return sizeof(AdditionNode); } @@ -55,17 +55,20 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; + + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; /* Evaluation */ template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { return MatrixComplex::Undefined(); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } }; @@ -80,7 +83,8 @@ public: static Addition Builder(Expression e1, Expression e2) { return Addition::Builder({e1, e2}); } // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const; void sortChildrenInPlace(NAryExpressionNode::ExpressionOrder order, Context * context, bool canBeInterrupted) { NAryExpression::sortChildrenInPlace(order, context, true, canBeInterrupted); diff --git a/poincare/include/poincare/approximation_helper.h b/poincare/include/poincare/approximation_helper.h index 20008c0d3..2ddc6289d 100644 --- a/poincare/include/poincare/approximation_helper.h +++ b/poincare/include/poincare/approximation_helper.h @@ -9,17 +9,18 @@ namespace Poincare { namespace ApproximationHelper { - template int PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + template T Epsilon(); + template int PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, ExpressionNode::ApproximationContext approximationContext); template std::complex NeglectRealOrImaginaryPartIfNeglectable(std::complex result, std::complex input1, std::complex input2 = 1.0, bool enableNullResult = true); template using ComplexCompute = Complex(*)(const std::complex, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - template Evaluation Map(const ExpressionNode * expression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ComplexCompute compute); + template Evaluation Map(const ExpressionNode * expression, ExpressionNode::ApproximationContext approximationContext, ComplexCompute compute); template using ComplexAndComplexReduction = Complex(*)(const std::complex, const std::complex, Preferences::ComplexFormat complexFormat); template using ComplexAndMatrixReduction = MatrixComplex(*)(const std::complex c, const MatrixComplex m, Preferences::ComplexFormat complexFormat); template using MatrixAndComplexReduction = MatrixComplex(*)(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat); template using MatrixAndMatrixReduction = MatrixComplex(*)(const MatrixComplex m, const MatrixComplex n, Preferences::ComplexFormat complexFormat); - template Evaluation MapReduce(const ExpressionNode * expression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ComplexAndComplexReduction computeOnComplexes, ComplexAndMatrixReduction computeOnComplexAndMatrix, MatrixAndComplexReduction computeOnMatrixAndComplex, MatrixAndMatrixReduction computeOnMatrices); + template Evaluation MapReduce(const ExpressionNode * expression, ExpressionNode::ApproximationContext approximationContext, ComplexAndComplexReduction computeOnComplexes, ComplexAndMatrixReduction computeOnComplexAndMatrix, MatrixAndComplexReduction computeOnMatrixAndComplex, MatrixAndMatrixReduction computeOnMatrices); template MatrixComplex ElementWiseOnMatrixComplexAndComplex(const MatrixComplex n, std::complex c, Preferences::ComplexFormat complexFormat, ComplexAndComplexReduction computeOnComplexes); template MatrixComplex ElementWiseOnComplexMatrices(const MatrixComplex m, const MatrixComplex n, Preferences::ComplexFormat complexFormat, ComplexAndComplexReduction computeOnComplexes); diff --git a/poincare/include/poincare/arc_cosine.h b/poincare/include/poincare/arc_cosine.h index 43bac523f..c3267ad26 100644 --- a/poincare/include/poincare/arc_cosine.h +++ b/poincare/include/poincare/arc_cosine.h @@ -20,6 +20,7 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context) == Sign::Unknown ? Sign::Unknown : Sign::Positive; } Type type() const override { return Type::ArcCosine; } private: @@ -34,11 +35,11 @@ private: //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/arc_sine.h b/poincare/include/poincare/arc_sine.h index 2747bda13..b2a8945d6 100644 --- a/poincare/include/poincare/arc_sine.h +++ b/poincare/include/poincare/arc_sine.h @@ -20,6 +20,8 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::ArcSine; } private: // Layout @@ -32,11 +34,11 @@ private: //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/arc_tangent.h b/poincare/include/poincare/arc_tangent.h index 47b6f5638..5522d1444 100644 --- a/poincare/include/poincare/arc_tangent.h +++ b/poincare/include/poincare/arc_tangent.h @@ -20,6 +20,8 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::ArcTangent; } private: @@ -33,11 +35,11 @@ private: //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/arithmetic.h b/poincare/include/poincare/arithmetic.h index 94ee1be31..348e5806f 100644 --- a/poincare/include/poincare/arithmetic.h +++ b/poincare/include/poincare/arithmetic.h @@ -2,13 +2,20 @@ #define POINCARE_ARITHMETIC_H #include +#include namespace Poincare { class Arithmetic final { public: - static Integer LCM(const Integer & i, const Integer & j); static Integer GCD(const Integer & i, const Integer & j); + static Integer LCM(const Integer & i, const Integer & j); + static Expression GCD(const Expression & expression); + static Expression LCM(const Expression & expression); + static int GCD(int i, int j); + static int LCM(int i, int j); + template static Evaluation GCD(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); + template static Evaluation LCM(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); /* When outputCoefficients[0] is set to -1, that indicates a special case: * i could not be factorized. * Before calling PrimeFactorization, we initiate two tables of Integers diff --git a/poincare/include/poincare/array.h b/poincare/include/poincare/array.h new file mode 100644 index 000000000..d3f001a41 --- /dev/null +++ b/poincare/include/poincare/array.h @@ -0,0 +1,34 @@ +#ifndef POINCARE_ARRAY_H +#define POINCARE_ARRAY_H + +#include +#include + +namespace Poincare { + +class Array { +public: + enum class VectorType { + None, + Vertical, + Horizontal + }; + Array() : + m_numberOfRows(0), + m_numberOfColumns(0) {} + VectorType vectorType() const { return m_numberOfColumns == 1 ? VectorType::Vertical : (m_numberOfRows == 1 ? VectorType::Horizontal : VectorType::None); } + int numberOfRows() const { return m_numberOfRows; } + int numberOfColumns() const { return m_numberOfColumns; } + void setNumberOfRows(int rows) { assert(rows >= 0); m_numberOfRows = rows; } + void setNumberOfColumns(int columns) { assert(columns >= 0); m_numberOfColumns = columns; } +protected: + /* We could store 2 uint8_t but multiplying m_numberOfRows and + * m_numberOfColumns could then lead to overflow. As we are unlikely to use + * greater matrix than 100*100, uint16_t is fine. */ + uint16_t m_numberOfRows; + uint16_t m_numberOfColumns; +}; + +} + +#endif \ No newline at end of file diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index 566a334a4..4dd2627d9 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 @@ -28,16 +28,18 @@ public: // Expression subclassing Type type() const override { return Type::BasedInteger; } Sign sign(Context * context) const override { return Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return integer().isZero() ? NullStatus::Null : NullStatus::NonNull; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } 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/binom_cdf.h b/poincare/include/poincare/binom_cdf.h index 832040818..6d6ce8cc3 100644 --- a/poincare/include/poincare/binom_cdf.h +++ b/poincare/include/poincare/binom_cdf.h @@ -29,9 +29,9 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class BinomCDF final : public BinomialDistributionFunction { diff --git a/poincare/include/poincare/binom_pdf.h b/poincare/include/poincare/binom_pdf.h index c316e5aed..6f99d1180 100644 --- a/poincare/include/poincare/binom_pdf.h +++ b/poincare/include/poincare/binom_pdf.h @@ -29,9 +29,9 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class BinomPDF final : public BinomialDistributionFunction { diff --git a/poincare/include/poincare/binomial_coefficient.h b/poincare/include/poincare/binomial_coefficient.h index 992cf88d0..9d5ae2b75 100644 --- a/poincare/include/poincare/binomial_coefficient.h +++ b/poincare/include/poincare/binomial_coefficient.h @@ -30,9 +30,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; // Evaluation - 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 Complex templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Complex templatedApproximate(ApproximationContext approximationContext) const; }; class BinomialCoefficient final : public Expression { diff --git a/poincare/include/poincare/bracket_pair_layout.h b/poincare/include/poincare/bracket_pair_layout.h index f1fcd0879..acde19d3f 100644 --- a/poincare/include/poincare/bracket_pair_layout.h +++ b/poincare/include/poincare/bracket_pair_layout.h @@ -44,12 +44,18 @@ private: constexpr static KDCoordinate k_bracketWidth = 5; constexpr static KDCoordinate k_lineThickness = 1; constexpr static KDCoordinate k_verticalMargin = 1; - KDCoordinate externWidthMargin() const { return k_externWidthMargin; } + constexpr static bool k_renderTopBar = true; + constexpr static bool k_renderBottomBar = true; + constexpr static bool k_renderDoubleBar = false; + virtual KDCoordinate externWidthMargin() const { return k_externWidthMargin; } virtual KDCoordinate widthMargin() const { return k_widthMargin; } virtual KDCoordinate verticalExternMargin() const { return k_verticalExternMargin; } - virtual bool renderTopBar() const { return true; } - virtual bool renderBottomBar() const { return true; } + virtual KDCoordinate verticalMargin() const { return k_verticalMargin; } + virtual bool renderTopBar() const { return k_renderTopBar; } + virtual bool renderBottomBar() const { return k_renderBottomBar; } + virtual bool renderDoubleBar() const { return k_renderDoubleBar; } void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed) override; + static void renderWithParameters(KDSize childSize, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, KDCoordinate verticalMargin, KDCoordinate externWidthMargin, KDCoordinate verticalExternMargin, KDCoordinate widthMargin, bool renderTopBar, bool renderBottomBar, bool renderDoubleBar); }; } diff --git a/poincare/include/poincare/ceiling.h b/poincare/include/poincare/ceiling.h index ff1bc5a10..d027f23ec 100644 --- a/poincare/include/poincare/ceiling.h +++ b/poincare/include/poincare/ceiling.h @@ -19,6 +19,7 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Ceiling; } private: // Layout @@ -30,11 +31,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/complex.h b/poincare/include/poincare/complex.h index 5f4aec757..ac5be2ccb 100644 --- a/poincare/include/poincare/complex.h +++ b/poincare/include/poincare/complex.h @@ -34,6 +34,9 @@ public: Expression complexToExpression(Preferences::Preferences::ComplexFormat complexFormat) const override; std::complex trace() const override { return *this; } std::complex determinant() const override { return *this; } + Evaluation cross(Evaluation * e) const override { return Complex::Undefined(); } + std::complex dot(Evaluation * e) const override { return std::complex(NAN, NAN); } + std::complex norm() const override { return std::complex(NAN, NAN); } }; template diff --git a/poincare/include/poincare/complex_argument.h b/poincare/include/poincare/complex_argument.h index 4264428c6..2169a6013 100644 --- a/poincare/include/poincare/complex_argument.h +++ b/poincare/include/poincare/complex_argument.h @@ -31,11 +31,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/complex_cartesian.h b/poincare/include/poincare/complex_cartesian.h index f9a71c5fd..1d90a6a75 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -19,16 +19,18 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(1)->nullStatus(context) == NullStatus::Null ? childAtIndex(0)->sign(context) : Sign::Unknown; } + NullStatus nullStatus(Context * context) const override; Type type() const override { return Type::ComplexCartesian; } private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { assert(false); return Layout(); } // Evaluation - 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); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { /* leftLayoutShape is called after beautifying expression. ComplexCartesian * is transformed in another expression at beautifying. */ @@ -36,7 +38,7 @@ private: return LayoutShape::BoundaryPunctuation; }; - template Complex templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Complex templatedApproximate(ApproximationContext approximationContext) const; }; class ComplexCartesian final : public Expression { @@ -51,8 +53,8 @@ public: Expression imag() { return childAtIndex(1); } // Simplification - Expression shallowReduce(); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); // Common operations (done in-place) Expression squareNorm(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/confidence_interval.h b/poincare/include/poincare/confidence_interval.h index f0d78e2d3..889a292be 100644 --- a/poincare/include/poincare/confidence_interval.h +++ b/poincare/include/poincare/confidence_interval.h @@ -32,9 +32,9 @@ private: LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class SimplePredictionIntervalNode final : public ConfidenceIntervalNode { diff --git a/poincare/include/poincare/conjugate.h b/poincare/include/poincare/conjugate.h index 61b442fc5..c7edcbee7 100644 --- a/poincare/include/poincare/conjugate.h +++ b/poincare/include/poincare/conjugate.h @@ -19,6 +19,8 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::Conjugate; } private: // Layout @@ -31,11 +33,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index 59d1a4d48..fe05d0a51 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -28,13 +28,14 @@ public: // Expression Properties Type type() const override { return Type::Constant; } Sign sign(Context * context) const override; + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Approximation */ - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } /* Symbol properties */ bool isPi() const { return isConstantCodePoint(UCodePointGreekSmallLetterPi); } @@ -49,6 +50,9 @@ public: Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::OneLetter; }; + /* Derivation */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + private: char m_name[0]; // MUST be the last member variable @@ -69,6 +73,7 @@ public: // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: ConstantNode * node() const { return static_cast(Expression::node()); } diff --git a/poincare/include/poincare/context.h b/poincare/include/poincare/context.h index a36cc1eea..5b23367c7 100644 --- a/poincare/include/poincare/context.h +++ b/poincare/include/poincare/context.h @@ -3,6 +3,7 @@ #include #include +#include namespace Poincare { @@ -14,10 +15,11 @@ public: enum class SymbolAbstractType : uint8_t { None, Function, + Sequence, Symbol }; virtual SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) = 0; - virtual const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) = 0; + virtual const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) = 0; virtual void setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) = 0; }; diff --git a/poincare/include/poincare/context_with_parent.h b/poincare/include/poincare/context_with_parent.h index 7f628abad..6b80cd907 100644 --- a/poincare/include/poincare/context_with_parent.h +++ b/poincare/include/poincare/context_with_parent.h @@ -14,7 +14,8 @@ public: // Context SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override { return m_parentContext->expressionTypeForIdentifier(identifier, length); } void setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) override { m_parentContext->setExpressionForSymbolAbstract(expression, symbol); } - const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) override { return m_parentContext->expressionForSymbolAbstract(symbol, clone); } + const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override { return m_parentContext->expressionForSymbolAbstract(symbol, clone, unknownSymbolValue); } + private: Context * m_parentContext; }; diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index 5855d8a9d..0472af8d4 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -21,7 +21,6 @@ public: // Properties Type type() const override { return Type::Cosine; } - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Radian); @@ -34,12 +33,16 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; + // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -51,6 +54,9 @@ public: static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("cos", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index ee1b0cd5b..f1c0db848 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -42,13 +42,14 @@ public: // Properties Type type() const override { return Type::Decimal; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return unsignedMantissa().isZero() ? NullStatus::Null : NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } @@ -60,7 +61,7 @@ public: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { assert(!m_negative); return LayoutShape::Decimal; }; LayoutShape rightLayoutShape() const override { return LayoutShape::Decimal; } diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index ad622c73b..9e4b92fa2 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -34,12 +34,12 @@ private: LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; - template T approximateWithArgument(T x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - template T growthRateAroundAbscissa(T x, T h, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - template T riddersApproximation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, T x, T h, T * error) const; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; + template T approximateWithArgument(T x, ApproximationContext approximationContext) const; + template T growthRateAroundAbscissa(T x, T h, ApproximationContext approximationContext) const; + template T riddersApproximation(ApproximationContext approximationContext, T x, T h, T * error) const; // TODO: Change coefficients? constexpr static double k_maxErrorRateOnApproximation = 0.001; constexpr static double k_minInitialRate = 0.01; @@ -52,8 +52,9 @@ public: static Derivative Builder(Expression child0, Symbol child1, Expression child2) { return TreeHandle::FixedArityBuilder({child0, child1, child2}); } static Expression UntypedBuilder(Expression children); static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("diff", 3, &UntypedBuilder); + static void DerivateUnaryFunction(Expression function, Expression symbol, Expression symbolValue, ExpressionNode::ReductionContext reductionContext); - Expression shallowReduce(Context * context); + Expression shallowReduce(ExpressionNode::ReductionContext context); }; } diff --git a/poincare/include/poincare/determinant.h b/poincare/include/poincare/determinant.h index 1a1a043e4..6d0d2100f 100644 --- a/poincare/include/poincare/determinant.h +++ b/poincare/include/poincare/determinant.h @@ -28,9 +28,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } /* Approximation */ - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; diff --git a/poincare/include/poincare/division.h b/poincare/include/poincare/division.h index 4b924844d..e03369017 100644 --- a/poincare/include/poincare/division.h +++ b/poincare/include/poincare/division.h @@ -23,20 +23,25 @@ public: #endif // Properties + Sign sign(Context * context) const override; + NullStatus nullStatus(Context * context) const override { + // NonNull Status can't be returned because denominator could be infinite. + return childAtIndex(0)->nullStatus(context) == NullStatus::Null ? NullStatus::Null : NullStatus::Unknown; + } Type type() const override { return Type::Division; } int polynomialDegree(Context * context, const char * symbolName) const override; Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Approximation - virtual Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + virtual Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return ApproximationHelper::MapReduce( - this, context, complexFormat, angleUnit, compute, + this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - virtual Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + virtual Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return ApproximationHelper::MapReduce( - this, context, complexFormat, angleUnit, compute, + this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } diff --git a/poincare/include/poincare/division_quotient.h b/poincare/include/poincare/division_quotient.h index e55fcec65..7f99045c2 100644 --- a/poincare/include/poincare/division_quotient.h +++ b/poincare/include/poincare/division_quotient.h @@ -19,6 +19,7 @@ public: #endif // ExpressionNode + Sign sign(Context * context) const override; Type type() const override { return Type::DivisionQuotient; } // Simplification @@ -32,9 +33,9 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class DivisionQuotient final : public Expression { diff --git a/poincare/include/poincare/division_remainder.h b/poincare/include/poincare/division_remainder.h index 3794c370d..f5f47538e 100644 --- a/poincare/include/poincare/division_remainder.h +++ b/poincare/include/poincare/division_remainder.h @@ -20,6 +20,7 @@ public: #endif // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::DivisionRemainder; } @@ -34,9 +35,9 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class DivisionRemainder final : public Expression { diff --git a/poincare/include/poincare/empty_context.h b/poincare/include/poincare/empty_context.h index f8e7e65c5..a3fe24fa3 100644 --- a/poincare/include/poincare/empty_context.h +++ b/poincare/include/poincare/empty_context.h @@ -12,7 +12,7 @@ public: // Context SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override { return SymbolAbstractType::None; } void setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) override { assert(false); } - const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) override { return Expression(); } + const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override { return Expression(); } }; } diff --git a/poincare/include/poincare/empty_expression.h b/poincare/include/poincare/empty_expression.h index 0ac5a21e7..a069965a2 100644 --- a/poincare/include/poincare/empty_expression.h +++ b/poincare/include/poincare/empty_expression.h @@ -33,9 +33,9 @@ private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class EmptyExpression final : public Expression { diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index 8edb9c538..c55270696 100644 --- a/poincare/include/poincare/empty_layout.h +++ b/poincare/include/poincare/empty_layout.h @@ -10,7 +10,7 @@ class EmptyLayoutNode /*final*/ : public LayoutNode { public: enum class Color { Yellow, - Grey + Gray }; // Layout diff --git a/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index 6826f35ab..fa067652f 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -28,9 +28,9 @@ private: 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Equal final : public Expression { @@ -39,7 +39,7 @@ public: static Equal Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder({child0, child1}); } // For the equation A = B, create the reduced expression A-B - Expression standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Expression standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::ReductionTarget reductionTarget) const; // Expression Expression shallowReduce(); }; diff --git a/poincare/include/poincare/evaluation.h b/poincare/include/poincare/evaluation.h index 2cca0158d..d0f508123 100644 --- a/poincare/include/poincare/evaluation.h +++ b/poincare/include/poincare/evaluation.h @@ -32,6 +32,9 @@ public: virtual Expression complexToExpression(Preferences::ComplexFormat complexFormat) const = 0; virtual std::complex trace() const = 0; virtual std::complex determinant() const = 0; + virtual Evaluation cross(Evaluation * e) const = 0; + virtual std::complex dot(Evaluation * e) const = 0; + virtual std::complex norm() const = 0; }; template @@ -64,6 +67,9 @@ public: Expression complexToExpression(Preferences::ComplexFormat complexFormat) const; std::complex trace() const { return node()->trace(); } std::complex determinant() const { return node()->determinant(); } + Evaluation cross(Evaluation * e) const { return node()->cross(e); } + std::complex dot(Evaluation * e) const { return node()->dot(e); } + std::complex norm() const { return node()->norm(); } protected: Evaluation(EvaluationNode * n) : TreeHandle(n) {} }; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 6bdb4008e..d75a684cf 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace Poincare { @@ -63,6 +64,9 @@ class Expression : public TreeHandle { friend class MatrixInverse; friend class MatrixTrace; friend class MatrixTranspose; + friend class MatrixEchelonForm; + friend class MatrixRowEchelonForm; + friend class MatrixReducedRowEchelonForm; friend class Multiplication; friend class MultiplicationNode; friend class NaperianLogarithm; @@ -84,6 +88,7 @@ class Expression : public TreeHandle { friend class RealPart; friend class Round; friend class Sequence; + friend class SequenceNode; friend class SignFunction; friend class Sine; friend class SquareRoot; @@ -92,6 +97,8 @@ class Expression : public TreeHandle { friend class Subtraction; friend class SubtractionNode; friend class Sum; + friend class SumAndProduct; + friend class SumAndProductNode; friend class Symbol; friend class SymbolAbstractNode; friend class Tangent; @@ -99,6 +106,9 @@ class Expression : public TreeHandle { friend class TrigonometryCheatTable; friend class Unit; friend class UnitConvert; + friend class VectorCross; + friend class VectorDot; + friend class VectorNorm; friend class AdditionNode; friend class DerivativeNode; @@ -113,6 +123,7 @@ class Expression : public TreeHandle { friend class MatrixNode; friend class NaperianLogarithmNode; friend class NAryExpressionNode; + friend class NAryInfixExpressionNode; friend class StoreNode; friend class SymbolNode; friend class UnitNode; @@ -140,9 +151,10 @@ public: ExpressionNode::Type type() const { return node()->type(); } bool isOfType(ExpressionNode::Type * types, int length) const { return node()->isOfType(types, length); } ExpressionNode::Sign sign(Context * context) const { return node()->sign(context); } + ExpressionNode::NullStatus nullStatus(Context * context) const { return node()->nullStatus(context); } + bool isStrictly(ExpressionNode::Sign s, Context * context) const { return s == node()->sign(context) && node()->nullStatus(context) == ExpressionNode::NullStatus::NonNull; } bool isUndefined() const { return node()->type() == ExpressionNode::Type::Undefined || node()->type() == ExpressionNode::Type::Unreal; } bool isNumber() const { return node()->isNumber(); } - bool isRationalZero() const; bool isRationalOne() const; bool isRandom() const { return node()->isRandom(); } bool isParameteredExpression() const { return node()->isParameteredExpression(); } @@ -162,15 +174,6 @@ public: static bool IsRandom(const Expression e, Context * context); static bool IsMatrix(const Expression e, Context * context); static bool IsInfinity(const Expression e, Context * context); - /* 'characteristicXRange' tries to assess the range on x where the expression - * (considered as a function on x) has an interesting evolution. For example, - * the period of the function on 'x' if it is periodic. If - * the function is x-independent, the return value is 0.0f (because any - * x-range is equivalent). If the function does not have an interesting range, - * the return value is NAN. - * NB: so far, we consider that the only way of building a periodic function - * is to use sin/tan/cos(f(x)) with f a linear function. */ - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { return node()->characteristicXRange(context, angleUnit); } /* polynomialDegree returns: * - (-1) if the expression is not a polynome * - the degree of the polynome otherwise */ @@ -188,14 +191,14 @@ public: * the variables hold in 'variables'. Otherwise, it fills 'coefficients' with * the coefficients of the variables hold in 'variables' (following the same * order) and 'constant' with the constant of the expression. */ - bool getLinearCoefficients(char * variables, int maxVariableLength, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const; + bool getLinearCoefficients(char * variables, int maxVariableLength, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation) const; /* getPolynomialCoefficients fills the table coefficients with the expressions * of the first 3 polynomial coefficients and returns the polynomial degree. * It is supposed to be called on a reduced expression. * coefficients has up to 3 entries. */ static constexpr int k_maxPolynomialDegree = 2; 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; + int getPolynomialReducedCoefficients(const char * symbolName, Expression coefficients[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation) const; Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { return node()->replaceSymbolWithExpression(symbol, expression); } /* Units */ @@ -218,7 +221,7 @@ public: /* isIdenticalToWithoutParentheses behaves as isIdenticalTo, but without * taking into account parentheses: e^(0) is identical to e^0. */ bool isIdenticalToWithoutParentheses(const Expression e) const; - static bool ParsedExpressionsAreEqual(const char * e0, const char * e1, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + static bool ParsedExpressionsAreEqual(const char * e0, const char * e1, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat); /* Layout Helper */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const; @@ -239,12 +242,13 @@ public: * account the complex format required in the expression they return. * (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, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); + static Expression ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); Expression simplify(ExpressionNode::ReductionContext reductionContext); - static void ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); - void simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); + static void ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); + void simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); Expression reduce(ExpressionNode::ReductionContext context); + Expression reduceAndRemoveUnit(ExpressionNode::ReductionContext context, Expression * Unit); Expression mapOnMatrixFirstChild(ExpressionNode::ReductionContext reductionContext); /* 'ExpressionWithoutSymbols' returns an uninitialized expression if it is @@ -257,9 +261,9 @@ public: /* Approximation Helper */ // These methods reset the sApproximationEncounteredComplex flag. They should not be use to implement node approximation template static U Epsilon(); - template Expression approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - template U approximateToScalar(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - template static U ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + template Expression approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) const; + template U approximateToScalar(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) const; + template static U ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); template U approximateWithValueForSymbol(const char * symbol, U x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; /* Expression roots/extrema solver */ Coordinate2D nextMinimum(const char * symbol, double start, double step, double max, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; @@ -314,6 +318,18 @@ protected: assert(children.type() == ExpressionNode::Type::Matrix); return U::Builder(children.childAtIndex(0), children.childAtIndex(1), children.childAtIndex(2), children.childAtIndex(3)); } + template + static Expression UntypedBuilderMultipleChildren(Expression children) { + // Only with Expression classes implementing addChildAtIndexInPlace + assert(children.type() == ExpressionNode::Type::Matrix); + int childrenNumber = children.numberOfChildren(); + assert(childrenNumber > 0); + U expression = U::Builder({children.childAtIndex(0)}); + for (int i = 1; i < childrenNumber; ++i) { + expression.addChildAtIndexInPlace(children.childAtIndex(i), i, i); + } + return std::move(expression); + } template T convert() const { /* This function allows to convert Expression to derived Expressions. @@ -375,11 +391,18 @@ protected: Expression makePositiveAnyNegativeNumeralFactor(ExpressionNode::ReductionContext reductionContext); 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 shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { return node()->shallowBeautify(reductionContext); } Expression deepBeautify(ExpressionNode::ReductionContext reductionContext); // WARNING: this must be called on reduced expressions Expression setSign(ExpressionNode::Sign s, ExpressionNode::ReductionContext reductionContext); + /* Derivation */ + /* This method is used for the reduction of Derivative expressions. + * It returns whether the instance is differentiable, and differentiates it if + * able. */ + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return node()->derivate(reductionContext, symbol, symbolValue); } + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return node()->unaryFunctionDifferential(reductionContext); } + private: static constexpr int k_maxSymbolReplacementsCount = 10; static bool sSymbolReplacementsCountLock; @@ -407,9 +430,15 @@ private: Expression defaultHandleUnitsInChildren(); // Children must be reduced Expression shallowReduceUsingApproximation(ExpressionNode::ReductionContext reductionContext); Expression defaultShallowBeautify() { return *this; } + void deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + node()->deepBeautifyChildren(reductionContext); + } + void defaultDeepBeautifyChildren(ExpressionNode::ReductionContext reductionContext); + bool defaultDidDerivate() { return false; } + Expression defaultUnaryFunctionDifferential() { return *this; } /* Approximation */ - template Evaluation approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Evaluation approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) const; /* Properties */ int defaultGetPolynomialCoefficients(Context * context, const char * symbol, Expression expression[]) const; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 2d3b33a33..305c0099e 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -20,18 +20,23 @@ class ExpressionNode : public TreeNode { friend class AdditionNode; friend class DivisionNode; friend class NAryExpressionNode; + friend class NAryInfixExpressionNode; friend class PowerNode; friend class SymbolNode; public: - enum class Type : uint8_t { + // The types order is important here. + enum class Type : uint8_t { Uninitialized = 0, Undefined = 1, Unreal, Rational, BasedInteger, Decimal, + Double, Float, Infinity, + /* When merging number nodes together, we do a linear scan which stops at + * the first non-number child. */ Multiplication, Power, Addition, @@ -90,23 +95,33 @@ public: Randint, RealPart, Round, + Sequence, SignFunction, SquareRoot, Subtraction, Sum, - + VectorDot, + VectorNorm, + /* When sorting the children of an expression, we assert that the following + * nodes are at the end of the list : */ + // - Units Unit, + // - Complexes ComplexCartesian, - + // - Any kind of matrices : ConfidenceInterval, MatrixDimension, MatrixIdentity, MatrixInverse, MatrixTranspose, + MatrixRowEchelonForm, + MatrixReducedRowEchelonForm, PredictionInterval, + VectorCross, Matrix, + EmptyExpression - }; + }; /* Poor man's RTTI */ virtual Type type() const = 0; @@ -141,33 +156,82 @@ public: Unknown = 0, Positive = 1 }; + enum class NullStatus { + Unknown = -1, + NonNull = 0, + Null = 1, + }; - class ReductionContext { + class ComputationContext { public: - ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, UnitConversion unitConversion = UnitConversion::Default) : + ComputationContext( + Context * context, + Preferences::ComplexFormat complexFormat, + Preferences::AngleUnit angleUnit) : m_context(context), m_complexFormat(complexFormat), - m_angleUnit(angleUnit), - m_target(target), - m_symbolicComputation(symbolicComputation), - m_unitConversion(unitConversion) + m_angleUnit(angleUnit) {} Context * context() { return m_context; } + void setContext(Context * context) { m_context = context; } Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } Preferences::AngleUnit angleUnit() const { return m_angleUnit; } - ReductionTarget target() const { return m_target; } - SymbolicComputation symbolicComputation() const { return m_symbolicComputation; } - UnitConversion unitConversion() const { return m_unitConversion; } private: Context * m_context; Preferences::ComplexFormat m_complexFormat; Preferences::AngleUnit m_angleUnit; + }; + + class ReductionContext : public ComputationContext { + public: + ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, UnitConversion unitConversion = UnitConversion::Default) : + ComputationContext(context, complexFormat, angleUnit), + m_unitFormat(unitFormat), + m_target(target), + m_symbolicComputation(symbolicComputation), + m_unitConversion(unitConversion) + {} + static ReductionContext NonInvasiveReductionContext(ReductionContext reductionContext) { + return ReductionContext( + reductionContext.context(), + reductionContext.complexFormat(), + reductionContext.angleUnit(), + reductionContext.unitFormat(), + reductionContext.target(), + SymbolicComputation::DoNotReplaceAnySymbol, + UnitConversion::None + ); + } + Preferences::UnitFormat unitFormat() const { return m_unitFormat; } + ReductionTarget target() const { return m_target; } + SymbolicComputation symbolicComputation() const { return m_symbolicComputation; } + UnitConversion unitConversion() const { return m_unitConversion; } + private: + Preferences::UnitFormat m_unitFormat; ReductionTarget m_target; SymbolicComputation m_symbolicComputation; UnitConversion m_unitConversion; }; + class ApproximationContext : public ComputationContext { + public: + ApproximationContext( + Context * context, + Preferences::ComplexFormat complexFormat, + Preferences::AngleUnit angleUnit, + bool withinReduce = false) : + ComputationContext(context, complexFormat, angleUnit), + m_withinReduce(withinReduce) + {} + ApproximationContext(ReductionContext reductionContext, bool withinReduce) : + ApproximationContext(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), withinReduce) {} + bool withinReduce() const { return m_withinReduce; } + private: + bool m_withinReduce; + }; + virtual Sign sign(Context * context) const { return Sign::Unknown; } + virtual NullStatus nullStatus(Context * context) const { return NullStatus::Unknown; } virtual bool isNumber() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } @@ -183,7 +247,6 @@ public: /*!*/ virtual Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount); typedef bool (*isVariableTest)(const char * c, Poincare::Context * context); virtual int getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable, int nextVariableIndex) const; - virtual float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const; bool isOfType(Type * types, int length) const; virtual Expression removeUnit(Expression * unit); // Only reduced nodes should answer @@ -216,13 +279,22 @@ public: typedef float SinglePrecision; typedef double DoublePrecision; constexpr static int k_maxNumberOfSteps = 10000; - virtual Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const = 0; - virtual Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const = 0; + virtual Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const = 0; + virtual Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const = 0; /* Simplification */ /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); + /*!*/ virtual void deepBeautifyChildren(ReductionContext reductionContext); /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); - /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); + /* TODO: shallowBeautify takes a pointer to the reduction context, unlike + * other methods. The pointer is needed to allow UnitConvert to modify the + * context and prevent unit modifications (in Expression::deepBeautify, after + * calling UnitConvert::shallowBeautify). + * We should uniformize this behaviour and use pointers in other methods using + * the reduction context. */ + /*!*/ virtual Expression shallowBeautify(ReductionContext * reductionContext); + /*!*/ virtual bool derivate(ReductionContext, Expression symbol, Expression symbolValue); + virtual Expression unaryFunctionDifferential(ReductionContext reductionContext); /* Return a clone of the denominator part of the expression */ /*!*/ 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 */ diff --git a/poincare/include/poincare/factor.h b/poincare/include/poincare/factor.h index 39e1ce4d5..c9203bdf2 100644 --- a/poincare/include/poincare/factor.h +++ b/poincare/include/poincare/factor.h @@ -18,6 +18,8 @@ public: stream << "Factor"; } #endif + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::Factor; } private: /* Layout */ @@ -25,15 +27,15 @@ private: /* Serialization */ int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Simplification */ - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } /* Evaluation */ - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Factor final : public Expression { @@ -43,11 +45,11 @@ public: static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("factor", 1, &UntypedBuilderOneChild); - Multiplication createMultiplicationOfIntegerPrimeDecomposition(Integer i, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Multiplication createMultiplicationOfIntegerPrimeDecomposition(Integer i) const; // Expression Expression shallowReduce(Context * context); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); }; } diff --git a/poincare/include/poincare/factorial.h b/poincare/include/poincare/factorial.h index 212a94c57..2720bb8ff 100644 --- a/poincare/include/poincare/factorial.h +++ b/poincare/include/poincare/factorial.h @@ -19,6 +19,7 @@ public: #endif // Properties + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } Type type() const override { return Type::Factorial; } Sign sign(Context * context) const override { return Sign::Positive; } Expression setSign(Sign s, ReductionContext reductionContext) override; @@ -36,11 +37,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } #if 0 diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 40f73ffed..1c08efe3b 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -36,8 +36,9 @@ public: #endif // Properties - Type type() const override { return Type::Float; } + Type type() const override { return (sizeof(T) == sizeof(float)) ? Type::Float : Type::Double; } Sign sign(Context * context) const override { return m_value < 0 ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return m_value == 0.0 ? NullStatus::Null : NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; @@ -46,13 +47,13 @@ public: /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Evaluation */ - 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); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } private: // Simplification LayoutShape leftLayoutShape() const override { return LayoutShape::Decimal; } - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + template Evaluation templatedApproximate(ApproximationContext approximationContext) const { return Complex::Builder((U)m_value); } T m_value; diff --git a/poincare/include/poincare/floor.h b/poincare/include/poincare/floor.h index 65115adaa..d2a097a81 100644 --- a/poincare/include/poincare/floor.h +++ b/poincare/include/poincare/floor.h @@ -19,6 +19,7 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Floor; } private: @@ -31,11 +32,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/frac_part.h b/poincare/include/poincare/frac_part.h index 0158bb9e7..f9ae55cb6 100644 --- a/poincare/include/poincare/frac_part.h +++ b/poincare/include/poincare/frac_part.h @@ -19,10 +19,9 @@ public: #endif // Properties + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::FracPart; } - - private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; @@ -34,11 +33,11 @@ private: // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/fraction_layout.h b/poincare/include/poincare/fraction_layout.h index 99a054aa2..8c6c4d101 100644 --- a/poincare/include/poincare/fraction_layout.h +++ b/poincare/include/poincare/fraction_layout.h @@ -22,6 +22,7 @@ public: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; bool shouldCollapseSiblingsOnLeft() const override { return true; } bool shouldCollapseSiblingsOnRight() const override { return true; } int leftCollapsingAbsorbingChildIndex() const override { return 0; } diff --git a/poincare/include/poincare/function.h b/poincare/include/poincare/function.h index cc64a531b..24ffa6d4b 100644 --- a/poincare/include/poincare/function.h +++ b/poincare/include/poincare/function.h @@ -27,7 +27,6 @@ public: int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; int getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable, int nextVariableIndex) const override; - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; private: char m_name[0]; // MUST be the last member variable @@ -43,9 +42,9 @@ private: LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override; - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override; - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override; + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Function : public SymbolAbstract { diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index 96429d951..739b5db3a 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -1,16 +1,15 @@ #ifndef POINCARE_GREAT_COMMON_DIVISOR_H #define POINCARE_GREAT_COMMON_DIVISOR_H -#include +#include namespace Poincare { -class GreatCommonDivisorNode final : public ExpressionNode { +class GreatCommonDivisorNode final : public NAryExpressionNode { public: // TreeNode size_t size() const override { return sizeof(GreatCommonDivisorNode); } - int numberOfChildren() const override; #if POINCARE_TREE_LOG void logNodeName(std::ostream & stream) const override { stream << "GreatCommonDivisor"; @@ -18,6 +17,7 @@ public: #endif // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::GreatCommonDivisor; } private: @@ -26,22 +26,25 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Simplification Expression shallowReduce(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; -class GreatCommonDivisor final : public Expression { +class GreatCommonDivisor final : public NAryExpression { public: - GreatCommonDivisor(const GreatCommonDivisorNode * n) : Expression(n) {} - static GreatCommonDivisor Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder({child0, child1}); } - static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("gcd", 2, &UntypedBuilderTwoChildren); + GreatCommonDivisor(const GreatCommonDivisorNode * n) : NAryExpression(n) {} + static GreatCommonDivisor Builder(const Tuple & children = {}) { return TreeHandle::NAryBuilder(convert(children)); } + // Using a -2 as numberOfChildren to allow 2 or more children when parsing + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("gcd", -2, &UntypedBuilderMultipleChildren); // Expression - Expression shallowReduce(Context * context); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(Context * context); }; } diff --git a/poincare/include/poincare/grid_layout.h b/poincare/include/poincare/grid_layout.h index 9f5ec3296..6b11378e9 100644 --- a/poincare/include/poincare/grid_layout.h +++ b/poincare/include/poincare/grid_layout.h @@ -1,34 +1,30 @@ #ifndef POINCARE_GRID_LAYOUT_NODE_H #define POINCARE_GRID_LAYOUT_NODE_H +#include +#include #include #include -#include namespace Poincare { class GridLayout; class MatrixLayoutNode; -class GridLayoutNode : public LayoutNode { +class GridLayoutNode : public Array, public LayoutNode { friend class MatrixLayoutNode; friend class BinomialCoefficientLayoutNode; friend class BinomialCoefficientLayout; friend class GridLayout; public: GridLayoutNode() : - LayoutNode(), - m_numberOfRows(0), - m_numberOfColumns(0) + Array(), + LayoutNode() {} // Layout Type type() const override { return Type::GridLayout; } - int numberOfRows() const { return m_numberOfRows; } - int numberOfColumns() const { return m_numberOfColumns; } - virtual void setNumberOfRows(int numberOfRows) { m_numberOfRows = numberOfRows; } - virtual void setNumberOfColumns(int numberOfColumns) { m_numberOfColumns = numberOfColumns; } KDSize gridSize() const { return KDSize(width(), height()); } // LayoutNode @@ -70,8 +66,6 @@ protected: int rowAtChildIndex(int index) const; int columnAtChildIndex(int index) const; int indexAtRowColumn(int rowIndex, int columnIndex) const; - int m_numberOfRows; - int m_numberOfColumns; // LayoutNode KDSize computeSize() override; @@ -100,14 +94,8 @@ public: int numberOfColumns() const { return node()->numberOfColumns(); } private: virtual GridLayoutNode * node() const { return static_cast(Layout::node()); } - void setNumberOfRows(int rows) { - assert(rows >= 0); - node()->setNumberOfRows(rows); - } - void setNumberOfColumns(int columns) { - assert(columns >= 0); - node()->setNumberOfColumns(columns); - } + void setNumberOfRows(int rows) { node()->setNumberOfRows(rows); } + void setNumberOfColumns(int columns) { node()->setNumberOfColumns(columns); } }; } diff --git a/poincare/include/poincare/helpers.h b/poincare/include/poincare/helpers.h index 517a64d2a..dd44bd97f 100644 --- a/poincare/include/poincare/helpers.h +++ b/poincare/include/poincare/helpers.h @@ -8,9 +8,13 @@ namespace Poincare { namespace Helpers { +typedef void (*Swap) (int i, int j, void * context, int numberOfElements); +typedef bool (*Compare) (int i, int j, void * context, int numberOfElements); + size_t AlignedSize(size_t realSize, size_t alignment); size_t Gcd(size_t a, size_t b); bool Rotate(uint32_t * dst, uint32_t * src, size_t len); +void Sort(Swap swap, Compare compare, void * context, int numberOfElements); } diff --git a/poincare/include/poincare/hyperbolic_arc_cosine.h b/poincare/include/poincare/hyperbolic_arc_cosine.h index ac0a6aee8..0647ec030 100644 --- a/poincare/include/poincare/hyperbolic_arc_cosine.h +++ b/poincare/include/poincare/hyperbolic_arc_cosine.h @@ -21,17 +21,17 @@ public: Type type() const override { return Type::HyperbolicArcCosine; } private: // Simplification - bool isNotableValue(Expression e) const override { return e.isRationalOne(); } + bool isNotableValue(Expression e, Context * context) const override { return e.isRationalOne(); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_arc_sine.h b/poincare/include/poincare/hyperbolic_arc_sine.h index 2981ba622..b3a98bcc8 100644 --- a/poincare/include/poincare/hyperbolic_arc_sine.h +++ b/poincare/include/poincare/hyperbolic_arc_sine.h @@ -25,11 +25,11 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_arc_tangent.h b/poincare/include/poincare/hyperbolic_arc_tangent.h index d59827696..496e29228 100644 --- a/poincare/include/poincare/hyperbolic_arc_tangent.h +++ b/poincare/include/poincare/hyperbolic_arc_tangent.h @@ -25,11 +25,11 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_cosine.h b/poincare/include/poincare/hyperbolic_cosine.h index 64f2fc991..569e36361 100644 --- a/poincare/include/poincare/hyperbolic_cosine.h +++ b/poincare/include/poincare/hyperbolic_cosine.h @@ -25,13 +25,16 @@ private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -40,6 +43,9 @@ public: HyperbolicCosine(const HyperbolicCosineNode * n) : HyperbolicTrigonometricFunction(n) {} static HyperbolicCosine Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("cosh", 1, &UntypedBuilderOneChild); }; diff --git a/poincare/include/poincare/hyperbolic_sine.h b/poincare/include/poincare/hyperbolic_sine.h index 718aa5ca9..8fb1cea7c 100644 --- a/poincare/include/poincare/hyperbolic_sine.h +++ b/poincare/include/poincare/hyperbolic_sine.h @@ -23,13 +23,16 @@ private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -38,6 +41,9 @@ public: HyperbolicSine(const HyperbolicSineNode * n) : HyperbolicTrigonometricFunction(n) {} static HyperbolicSine Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("sinh", 1, &UntypedBuilderOneChild); }; diff --git a/poincare/include/poincare/hyperbolic_tangent.h b/poincare/include/poincare/hyperbolic_tangent.h index 5c42b79ba..f3bdf3540 100644 --- a/poincare/include/poincare/hyperbolic_tangent.h +++ b/poincare/include/poincare/hyperbolic_tangent.h @@ -23,13 +23,16 @@ private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -38,6 +41,9 @@ public: HyperbolicTangent(const HyperbolicTangentNode * n) : HyperbolicTrigonometricFunction(n) {} static HyperbolicTangent Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("tanh", 1, &UntypedBuilderOneChild); }; diff --git a/poincare/include/poincare/hyperbolic_trigonometric_function.h b/poincare/include/poincare/hyperbolic_trigonometric_function.h index 62a896c49..10828793e 100644 --- a/poincare/include/poincare/hyperbolic_trigonometric_function.h +++ b/poincare/include/poincare/hyperbolic_trigonometric_function.h @@ -17,7 +17,7 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } Expression shallowReduce(ReductionContext reductionContext) override; - virtual bool isNotableValue(Expression e) const { return e.isRationalZero(); } + virtual bool isNotableValue(Expression e, Context * context) const { return e.nullStatus(context) == ExpressionNode::NullStatus::Null; } virtual Expression imageOfNotableValue() const { return Rational::Builder(0); } }; diff --git a/poincare/include/poincare/ieee754.h b/poincare/include/poincare/ieee754.h index c1b3c0bcc..accda9d33 100644 --- a/poincare/include/poincare/ieee754.h +++ b/poincare/include/poincare/ieee754.h @@ -73,40 +73,12 @@ public: } return exponentBase10; } - static T next(T f) { - return nextOrPrevious(f, true); - } - - static T previous(T f) { - return nextOrPrevious(f, false); - } private: union uint_float { uint64_t ui; T f; }; - static T nextOrPrevious(T f, bool isNext) { - if (std::isinf(f) || std::isnan(f)) { - return f; - } - uint_float u; - u.ui = 0; - u.f = f; - uint64_t oneBitOnSignBit = (uint64_t)1 << (k_exponentNbBits + k_mantissaNbBits); - if ((isNext && (u.ui & oneBitOnSignBit) > 0) // next: Negative float - || (!isNext && (u.ui & oneBitOnSignBit) == 0)) { // previous: Positive float - if ((isNext && u.ui == oneBitOnSignBit) // next: -0.0 - || (!isNext && u.ui == 0.0)) { // previous: 0.0 - u.ui = isNext ? 0 : oneBitOnSignBit; - } else { - u.ui -= 1; - } - } else { // next: Positive float, previous: Negative float - u.ui += 1; - } - return u.f; - } constexpr static size_t k_signNbBits = 1; constexpr static size_t k_exponentNbBits = sizeof(T) == sizeof(float) ? 8 : 11; diff --git a/poincare/include/poincare/imaginary_part.h b/poincare/include/poincare/imaginary_part.h index 345ddbc50..373f93a34 100644 --- a/poincare/include/poincare/imaginary_part.h +++ b/poincare/include/poincare/imaginary_part.h @@ -19,9 +19,9 @@ public: #endif // Properties + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->sign(context) != Sign::Unknown ? NullStatus::Null : NullStatus::Unknown; } Type type() const override { return Type::ImaginaryPart; } - private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; @@ -34,11 +34,11 @@ private: template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::imag(c)); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/infinity.h b/poincare/include/poincare/infinity.h index 2d434f266..03740eead 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -23,19 +23,25 @@ public: // Properties Type type() const override { return Type::Infinity; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; + + /* Derivation + * Unlike Numbers that derivate to 0, Infinity derivates to Undefined. */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + private: // Simplification LayoutShape leftLayoutShape() const override { assert(!m_negative); return LayoutShape::MoreLetters; } @@ -55,6 +61,7 @@ public: static int NameSize() { return 4; } + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: InfinityNode * node() const { return static_cast(Number::node()); } }; diff --git a/poincare/include/poincare/integral.h b/poincare/include/poincare/integral.h index e55f30e3a..c72001801 100644 --- a/poincare/include/poincare/integral.h +++ b/poincare/include/poincare/integral.h @@ -31,9 +31,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; LayoutShape rightLayoutShape() const override { return LayoutShape::MoreLetters; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; template struct DetailedResult { @@ -44,10 +44,10 @@ private: #ifdef LAGRANGE_METHOD template T lagrangeGaussQuadrature(T a, T b, Context Context * context, Preferences::AngleUnit angleUnit context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; #else - template DetailedResult kronrodGaussQuadrature(T a, T b, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - template T adaptiveQuadrature(T a, T b, T eps, int numberOfIterations, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template DetailedResult kronrodGaussQuadrature(T a, T b, ApproximationContext approximationContext) const; + template T adaptiveQuadrature(T a, T b, T eps, int numberOfIterations, ApproximationContext approximationContext) const; #endif - template T functionValueAtAbscissa(T x, Context * xcontext, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template T functionValueAtAbscissa(T x, ApproximationContext approximationContext) const; }; class Integral final : public ParameteredExpression { diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 84c91eca8..ca7c56d19 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -9,7 +9,9 @@ namespace Poincare { class IntegralLayoutNode final : public LayoutNode { public: - constexpr static KDCoordinate k_symbolHeight = 4; + + // Sizes of the upper and lower curls of the integral symbol + constexpr static KDCoordinate k_symbolHeight = 9; constexpr static KDCoordinate k_symbolWidth = 4; using LayoutNode::LayoutNode; @@ -40,16 +42,18 @@ protected: // LayoutNode KDSize computeSize() override; KDCoordinate computeBaseline() override; + KDCoordinate centralArgumentHeight(); KDPoint positionOfChild(LayoutNode * child) override; + private: constexpr static int k_integrandLayoutIndex = 0; constexpr static int k_differentialLayoutIndex = 1; constexpr static const KDFont * k_font = KDFont::LargeFont; - constexpr static KDCoordinate k_boundHeightMargin = 8; - constexpr static KDCoordinate k_boundWidthMargin = 5; - constexpr static KDCoordinate k_differentialWidthMargin = 3; - constexpr static KDCoordinate k_integrandWidthMargin = 2; - constexpr static KDCoordinate k_integrandHeigthMargin = 2; + constexpr static KDCoordinate k_boundVerticalMargin = 4; + constexpr static KDCoordinate k_boundHorizontalMargin = 3; + constexpr static KDCoordinate k_differentialHorizontalMargin = 3; + constexpr static KDCoordinate k_integrandHorizontalMargin = 2; + constexpr static KDCoordinate k_integrandVerticalMargin = 3; constexpr static KDCoordinate k_lineThickness = 1; // int(f(x), x, a, b) LayoutNode * integrandLayout() { return childAtIndex(k_integrandLayoutIndex); } // f(x) @@ -57,6 +61,23 @@ private: LayoutNode * lowerBoundLayout() { return childAtIndex(2); } // a LayoutNode * upperBoundLayout() { return childAtIndex(3); } // b void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed) override; + + enum class BoundPosition : uint8_t{ + UpperBound, + LowerBound + }; + + enum class NestedPosition : uint8_t{ + Previous, + Next + }; + + LayoutNode * boundLayout(BoundPosition position) { return position == BoundPosition::UpperBound ? upperBoundLayout() : lowerBoundLayout(); } + IntegralLayoutNode * nextNestedIntegral(); + IntegralLayoutNode * previousNestedIntegral(); + IntegralLayoutNode * nestedIntegral(NestedPosition position) { return position == NestedPosition::Next ? nextNestedIntegral() : previousNestedIntegral(); } + KDCoordinate boundMaxHeight(BoundPosition position); + IntegralLayoutNode * mostNestedIntegral (NestedPosition position); }; class IntegralLayout final : public Layout { diff --git a/poincare/include/poincare/inv_binom.h b/poincare/include/poincare/inv_binom.h index c3b959d60..229aa982c 100644 --- a/poincare/include/poincare/inv_binom.h +++ b/poincare/include/poincare/inv_binom.h @@ -30,9 +30,9 @@ private: Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class InvBinom final : public BinomialDistributionFunction { diff --git a/poincare/include/poincare/inv_norm.h b/poincare/include/poincare/inv_norm.h index a06d622e8..981a01882 100644 --- a/poincare/include/poincare/inv_norm.h +++ b/poincare/include/poincare/inv_norm.h @@ -30,9 +30,9 @@ private: Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class InvNorm final : public NormalDistributionFunction { diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 074943a69..96d49ec58 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -25,6 +25,7 @@ public: assert(isUninitialized() || !TreeHandle::node()->isGhost()); return static_cast(TreeHandle::node()); } + static Layout LayoutFromAddress(const void * address, size_t size); // Properties LayoutNode::Type type() const { return node()->type(); } @@ -59,9 +60,9 @@ public: // Layout modification void deleteBeforeCursor(LayoutCursor * cursor) { return node()->deleteBeforeCursor(cursor); } - bool removeGreySquaresFromAllMatrixAncestors() { return node()->removeGreySquaresFromAllMatrixAncestors(); } - bool removeGreySquaresFromAllMatrixChildren() { return node()->removeGreySquaresFromAllMatrixChildren(); } - bool addGreySquaresToAllMatrixAncestors() { return node()->addGreySquaresToAllMatrixAncestors(); } + bool removeGraySquaresFromAllMatrixAncestors() { return node()->removeGraySquaresFromAllMatrixAncestors(); } + bool removeGraySquaresFromAllMatrixChildren() { return node()->removeGraySquaresFromAllMatrixChildren(); } + bool addGraySquaresToAllMatrixAncestors() { return node()->addGraySquaresToAllMatrixAncestors(); } Layout layoutToPointWhenInserting(Expression * correspondingExpression) { // Pointer to correspondingExpr because expression.h includes layout.h assert(correspondingExpression != nullptr); diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index f43a142d0..de552e798 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -95,15 +95,11 @@ public: void moveUnder(bool * shouldRecomputeLayout, bool forSelection = false) { layoutNode()->moveCursorDown(this, shouldRecomputeLayout, false, forSelection); } - LayoutCursor cursorAtDirection(Direction direction, bool * shouldRecomputeLayout, bool forSelection = false); + LayoutCursor cursorAtDirection(Direction direction, bool * shouldRecomputeLayout, bool forSelection = false, int step = 1); /* Select */ void select(Direction direction, bool * shouldRecomputeLayout, Layout * selection); - LayoutCursor selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection) { - LayoutCursor result = clone(); - result.select(direction, shouldRecomputeLayout, selection); - return result; - } + LayoutCursor selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection); /* Layout modification */ void addEmptyExponentialLayout(); diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index b4ef1485a..e2aaf7a89 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -40,6 +40,7 @@ public: RightParenthesisLayout, RightSquareBracketLayout, SumLayout, + VectorNormLayout, VerticalOffsetLayout }; @@ -104,9 +105,9 @@ public: // Other virtual LayoutNode * layoutToPointWhenInserting(Expression * correspondingExpression); - bool removeGreySquaresFromAllMatrixAncestors(); - bool removeGreySquaresFromAllMatrixChildren(); - bool addGreySquaresToAllMatrixAncestors(); + bool removeGraySquaresFromAllMatrixAncestors(); + bool removeGraySquaresFromAllMatrixChildren(); + bool addGraySquaresToAllMatrixAncestors(); /* A layout has text if it is not empty and it is not an horizontal layout * with no child or with one child with no text. */ virtual bool hasText() const { return true; } @@ -174,7 +175,7 @@ private: int * resultScore, bool forSelection); virtual void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed) = 0; - void changeGreySquaresOfAllMatrixRelatives(bool add, bool ancestors, bool * changedSquares); + void changeGraySquaresOfAllMatrixRelatives(bool add, bool ancestors, bool * changedSquares); }; } diff --git a/poincare/include/poincare/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index 2ca831043..169f14e4a 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -1,47 +1,50 @@ #ifndef POINCARE_LEAST_COMMON_MULTIPLE_H #define POINCARE_LEAST_COMMON_MULTIPLE_H -#include +#include namespace Poincare { -class LeastCommonMultipleNode final : public ExpressionNode { +class LeastCommonMultipleNode final : public NAryExpressionNode { public: // TreeNode size_t size() const override { return sizeof(LeastCommonMultipleNode); } - int numberOfChildren() const override; #if POINCARE_TREE_LOG void logNodeName(std::ostream & stream) const override { stream << "LeastCommonMultiple"; } #endif + // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::LeastCommonMultiple; } - private: - /* Layout */ + // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - /* Simplification */ + // Simplification Expression shallowReduce(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } - /* Evaluation */ - 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; + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; -class LeastCommonMultiple final : public Expression { +class LeastCommonMultiple final : public NAryExpression { public: - LeastCommonMultiple(const LeastCommonMultipleNode * n) : Expression(n) {} - static LeastCommonMultiple Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder({child0, child1}); } - static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("lcm", 2, &UntypedBuilderTwoChildren); + LeastCommonMultiple(const LeastCommonMultipleNode * n) : NAryExpression(n) {} + static LeastCommonMultiple Builder(const Tuple & children = {}) { return TreeHandle::NAryBuilder(convert(children)); } + // Using a -2 as numberOfChildren to allow 2 or more children when parsing + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("lcm", -2, &UntypedBuilderMultipleChildren); // Expression - Expression shallowReduce(Context * context); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(Context * context); }; } diff --git a/poincare/include/poincare/left_parenthesis_layout.h b/poincare/include/poincare/left_parenthesis_layout.h index 96c9726d1..5c738e792 100644 --- a/poincare/include/poincare/left_parenthesis_layout.h +++ b/poincare/include/poincare/left_parenthesis_layout.h @@ -16,9 +16,6 @@ public: static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); - // Layout Node - bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - // Serializable Node int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, '('); diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index 75d23f0f7..369bd41fa 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -29,9 +29,12 @@ public: // Simplification void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override; Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) { /* log has a branch cut on ]-inf, 0]: it is then multivalued on this cut. We @@ -39,9 +42,9 @@ public: * (warning: log takes the other side of the cut values on ]-inf-0i, 0-0i]). */ return Complex::Builder(std::log10(c)); } - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Logarithm final : public Expression { @@ -53,10 +56,12 @@ public: Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); private: void deepReduceChildren(ExpressionNode::ReductionContext reductionContext); - Expression simpleShallowReduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + Expression simpleShallowReduce(ExpressionNode::ReductionContext reductionContext); Integer simplifyLogarithmIntegerBaseInteger(Integer i, Integer & base, Addition & a, bool isDenominator); Expression splitLogarithmInteger(Integer i, bool isDenominator, ExpressionNode::ReductionContext reductionContext); bool parentIsAPowerOfSameBase() const; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 6d8937e3f..ee443e1bd 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -1,21 +1,16 @@ #ifndef POINCARE_MATRIX_H #define POINCARE_MATRIX_H +#include #include namespace Poincare { -class MatrixNode /*final*/ : public ExpressionNode { +class MatrixNode /*final*/ : public Array, public ExpressionNode { public: - MatrixNode() : - m_numberOfRows(0), - m_numberOfColumns(0) {} + MatrixNode() : Array() {} bool hasMatrixChild(Context * context) const; - int numberOfRows() const { return m_numberOfRows; } - int numberOfColumns() const { return m_numberOfColumns; } - virtual void setNumberOfRows(int rows) { assert(rows >= 0); m_numberOfRows = rows; } - virtual void setNumberOfColumns(int columns) { assert(columns >= 0); m_numberOfColumns = columns; } // TreeNode size_t size() const override { return sizeof(MatrixNode); } @@ -40,23 +35,18 @@ public: Expression shallowReduce(ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; private: - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - /* We could store 2 uint8_t but multiplying m_numberOfRows and - * m_numberOfColumns could then lead to overflow. As we are unlikely to use - * greater matrix than 100*100, uint16_t is fine. */ - uint16_t m_numberOfRows; - uint16_t m_numberOfColumns; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Matrix final : public Expression { @@ -67,6 +57,7 @@ public: static Matrix Builder() { return TreeHandle::NAryBuilder(); } void setDimensions(int rows, int columns); + Array::VectorType vectorType() const { return node()->vectorType(); } int numberOfRows() const { return node()->numberOfRows(); } int numberOfColumns() const { return node()->numberOfColumns(); } using TreeHandle::addChildAtIndexInPlace; @@ -74,15 +65,20 @@ public: Expression matrixChild(int i, int j) { return childAtIndex(i*numberOfColumns()+j); } /* Operation on matrix */ - int rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool inPlace = false); + int rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, 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); Matrix createTranspose() const; + Expression createRef(ExpressionNode::ReductionContext reductionContext, bool * couldComputeRef, bool reduced) const; /* createInverse can be called on any matrix, reduced or not, approximated or * not. */ Expression createInverse(ExpressionNode::ReductionContext reductionContext, bool * couldComputeInverse) const; Expression determinant(ExpressionNode::ReductionContext reductionContext, bool * couldComputeDeterminant, bool inPlace); + Expression norm(ExpressionNode::ReductionContext reductionContext) const; + Expression dot(Matrix * b, ExpressionNode::ReductionContext reductionContext) const; + Matrix cross(Matrix * b, ExpressionNode::ReductionContext reductionContext) const; // TODO: find another solution for inverse and determinant (avoid capping the matrix) static constexpr int k_maxNumberOfCoefficients = 100; @@ -91,13 +87,13 @@ public: private: MatrixNode * node() const { return static_cast(Expression::node()); } - void setNumberOfRows(int rows) { assert(rows >= 0); node()->setNumberOfRows(rows); } - void setNumberOfColumns(int columns) { assert(columns >= 0); node()->setNumberOfColumns(columns); } + void setNumberOfRows(int rows) { node()->setNumberOfRows(rows); } + void setNumberOfColumns(int columns) { node()->setNumberOfColumns(columns); } Expression computeInverseOrDeterminant(bool computeDeterminant, ExpressionNode::ReductionContext reductionContext, bool * couldCompute) const; - // rowCanonize turns a matrix in its reduced row echelon form. - Matrix rowCanonize(ExpressionNode::ReductionContext reductionContext, Expression * determinant); + // rowCanonize turns a matrix in its row echelon form, reduced or not. + Matrix rowCanonize(ExpressionNode::ReductionContext reductionContext, Expression * determinant, bool reduced = true); // Row canonize the array in place - template static void ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, T * c = nullptr); + template static void ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, T * c = nullptr, bool reduced = true); }; diff --git a/poincare/include/poincare/matrix_complex.h b/poincare/include/poincare/matrix_complex.h index b895f3151..d24dbfa3e 100644 --- a/poincare/include/poincare/matrix_complex.h +++ b/poincare/include/poincare/matrix_complex.h @@ -1,8 +1,9 @@ #ifndef POINCARE_MATRIX_COMPLEX_H #define POINCARE_MATRIX_COMPLEX_H -#include +#include #include +#include namespace Poincare { @@ -10,12 +11,11 @@ template class MatrixComplex; template -class MatrixComplexNode final : public EvaluationNode { +class MatrixComplexNode final : public Array, public EvaluationNode { public: MatrixComplexNode() : - EvaluationNode(), - m_numberOfRows(0), - m_numberOfColumns(0) + Array(), + EvaluationNode() {} std::complex complexAtIndex(int index) const; @@ -36,20 +36,16 @@ public: // EvaluationNode typename EvaluationNode::Type type() const override { return EvaluationNode::Type::MatrixComplex; } - int numberOfRows() const { return m_numberOfRows; } - int numberOfColumns() const { return m_numberOfColumns; } - virtual void setNumberOfRows(int rows) { assert(rows >= 0); m_numberOfRows = rows; } - virtual void setNumberOfColumns(int columns) { assert(columns >= 0); m_numberOfColumns = columns; } bool isUndefined() const override; Expression complexToExpression(Preferences::Preferences::ComplexFormat complexFormat) const override; std::complex trace() const override; std::complex determinant() const override; MatrixComplex inverse() const; MatrixComplex transpose() const; -private: - // See comment on Matrix - uint16_t m_numberOfRows; - uint16_t m_numberOfColumns; + MatrixComplex ref(bool reduced) const; + std::complex norm() const override; + std::complex dot(Evaluation * e) const override; + Evaluation cross(Evaluation * e) const override; }; template @@ -63,23 +59,19 @@ public: static MatrixComplex CreateIdentity(int dim); MatrixComplex inverse() const { return node()->inverse(); } MatrixComplex transpose() const { return node()->transpose(); } + MatrixComplex ref(bool reduced) const { return node()->ref(reduced); } std::complex complexAtIndex(int index) const { return node()->complexAtIndex(index); } + Array::VectorType vectorType() const { return node()->vectorType(); } int numberOfRows() const { return node()->numberOfRows(); } int numberOfColumns() const { return node()->numberOfColumns(); } void setDimensions(int rows, int columns); void addChildAtIndexInPlace(Evaluation t, int index, int currentNumberOfChildren); private: MatrixComplexNode * node() { return static_cast *>(Evaluation::node()); } - void setNumberOfRows(int rows) { - assert(rows >= 0); - node()->setNumberOfRows(rows); - } - void setNumberOfColumns(int columns) { - assert(columns >= 0); - node()->setNumberOfColumns(columns); - } + void setNumberOfRows(int rows) { node()->setNumberOfRows(rows); } + void setNumberOfColumns(int columns) { node()->setNumberOfColumns(columns); } MatrixComplexNode * node() const { return static_cast *>(Evaluation::node()); } }; diff --git a/poincare/include/poincare/matrix_dimension.h b/poincare/include/poincare/matrix_dimension.h index 07c40840d..7fbb30af3 100644 --- a/poincare/include/poincare/matrix_dimension.h +++ b/poincare/include/poincare/matrix_dimension.h @@ -29,9 +29,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class MatrixDimension final : public Expression { diff --git a/poincare/include/poincare/matrix_echelon_form.h b/poincare/include/poincare/matrix_echelon_form.h new file mode 100644 index 000000000..1dac327d9 --- /dev/null +++ b/poincare/include/poincare/matrix_echelon_form.h @@ -0,0 +1,41 @@ +#ifndef POINCARE_MATRIX_ECHELON_FORM_H +#define POINCARE_MATRIX_ECHELON_FORM_H + +#include + +namespace Poincare { + +class MatrixEchelonFormNode : public ExpressionNode { +public: + + // TreeNode + int numberOfChildren() const override; + virtual bool isFormReduced() const = 0; + static constexpr int sNumberOfChildren = 1; +private: + // Layout + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Simplification + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; + LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; + + // Properties + virtual const char * functionHelperName() const = 0; +}; + +class MatrixEchelonForm : public Expression { +public: + MatrixEchelonForm(const MatrixEchelonFormNode * n) : Expression(n) {} + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool isFormReduced() const { return static_cast(node())->isFormReduced(); } +}; + +} + +#endif diff --git a/poincare/include/poincare/matrix_identity.h b/poincare/include/poincare/matrix_identity.h index 8d50b39c3..d9a0a1ea1 100644 --- a/poincare/include/poincare/matrix_identity.h +++ b/poincare/include/poincare/matrix_identity.h @@ -27,9 +27,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class MatrixIdentity final : public Expression { diff --git a/poincare/include/poincare/matrix_inverse.h b/poincare/include/poincare/matrix_inverse.h index 53fefdf30..4eae80e32 100644 --- a/poincare/include/poincare/matrix_inverse.h +++ b/poincare/include/poincare/matrix_inverse.h @@ -28,9 +28,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class MatrixInverse final : public Expression { diff --git a/poincare/include/poincare/matrix_layout.h b/poincare/include/poincare/matrix_layout.h index 6cded4cee..31757b206 100644 --- a/poincare/include/poincare/matrix_layout.h +++ b/poincare/include/poincare/matrix_layout.h @@ -19,8 +19,8 @@ public: Type type() const override { return Type::MatrixLayout; } // MatrixLayoutNode - void addGreySquares(); - void removeGreySquares(); + void addGraySquares(); + void removeGraySquares(); // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection) override; @@ -50,7 +50,7 @@ private: void newRowOrColumnAtIndex(int index); bool isRowEmpty(int index) const; bool isColumnEmpty(int index) const; - bool hasGreySquares() const; + bool hasGraySquares() const; // LayoutNode void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed) override; @@ -64,9 +64,9 @@ public: static MatrixLayout Builder() { return TreeHandle::NAryBuilder(); } static MatrixLayout Builder(Layout l1, Layout l2, Layout l3, Layout l4); - bool hasGreySquares() const { return node()->hasGreySquares(); } - void addGreySquares() { node()->addGreySquares(); } - void removeGreySquares() { node()->removeGreySquares(); } + bool hasGraySquares() const { return node()->hasGraySquares(); } + void addGraySquares() { node()->addGraySquares(); } + void removeGraySquares() { node()->removeGraySquares(); } private: MatrixLayoutNode * node() const { return static_cast(Layout::node()); } }; diff --git a/poincare/include/poincare/matrix_reduced_row_echelon_form.h b/poincare/include/poincare/matrix_reduced_row_echelon_form.h new file mode 100644 index 000000000..2058f626e --- /dev/null +++ b/poincare/include/poincare/matrix_reduced_row_echelon_form.h @@ -0,0 +1,37 @@ +#ifndef POINCARE_MATRIX_REDUCED_ROW_ECHELON_FORM_H +#define POINCARE_MATRIX_REDUCED_ROW_ECHELON_FORM_H + +#include + +namespace Poincare { + +class MatrixReducedRowEchelonFormNode final : public MatrixEchelonFormNode { +public: + + // TreeNode + size_t size() const override { return sizeof(MatrixReducedRowEchelonFormNode); } + +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "MatrixReducedRowEchelonForm"; + } +#endif + + // Properties + Type type() const override { return Type::MatrixReducedRowEchelonForm; } +private: + const char * functionHelperName() const override; + bool isFormReduced() const override { return true; } +}; + + +class MatrixReducedRowEchelonForm final : public MatrixEchelonForm { +public: + MatrixReducedRowEchelonForm(const MatrixReducedRowEchelonFormNode * n) : MatrixEchelonForm(n) {} + static MatrixReducedRowEchelonForm Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("rref", MatrixEchelonFormNode::sNumberOfChildren, &UntypedBuilderOneChild); +}; + +} + +#endif diff --git a/poincare/include/poincare/matrix_row_echelon_form.h b/poincare/include/poincare/matrix_row_echelon_form.h new file mode 100644 index 000000000..baaa80962 --- /dev/null +++ b/poincare/include/poincare/matrix_row_echelon_form.h @@ -0,0 +1,37 @@ +#ifndef POINCARE_MATRIX_ROW_ECHELON_FORM_H +#define POINCARE_MATRIX_ROW_ECHELON_FORM_H + +#include + +namespace Poincare { + +class MatrixRowEchelonFormNode final : public MatrixEchelonFormNode { +public: + + // TreeNode + size_t size() const override { return sizeof(MatrixRowEchelonFormNode); } + +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "MatrixRowEchelonForm"; + } +#endif + + // Properties + Type type() const override { return Type::MatrixRowEchelonForm; } +private: + const char * functionHelperName() const override; + bool isFormReduced() const override { return false; } +}; + + +class MatrixRowEchelonForm final : public MatrixEchelonForm { +public: + MatrixRowEchelonForm(const MatrixRowEchelonFormNode * n) : MatrixEchelonForm(n) {} + static MatrixRowEchelonForm Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("ref", MatrixEchelonFormNode::sNumberOfChildren, &UntypedBuilderOneChild); +}; + +} + +#endif diff --git a/poincare/include/poincare/matrix_trace.h b/poincare/include/poincare/matrix_trace.h index 7805cf0d5..781ec702e 100644 --- a/poincare/include/poincare/matrix_trace.h +++ b/poincare/include/poincare/matrix_trace.h @@ -28,9 +28,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class MatrixTrace final : public Expression { diff --git a/poincare/include/poincare/matrix_transpose.h b/poincare/include/poincare/matrix_transpose.h index e404c840c..62a1e015f 100644 --- a/poincare/include/poincare/matrix_transpose.h +++ b/poincare/include/poincare/matrix_transpose.h @@ -28,9 +28,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class MatrixTranspose final : public Expression { diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 4b1c41a74..d8b80bf0b 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -2,14 +2,14 @@ #define POINCARE_MULTIPLICATION_H #include -#include +#include namespace Poincare { -class MultiplicationNode final : public NAryExpressionNode { +class MultiplicationNode final : public NAryInfixExpressionNode { friend class Addition; public: - using NAryExpressionNode::NAryExpressionNode; + using NAryInfixExpressionNode::NAryInfixExpressionNode; // Tree size_t size() const override { return sizeof(MultiplicationNode); } @@ -47,18 +47,20 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; Expression denominator(ExpressionNode::ReductionContext reductionContext) const override; + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; // Approximation template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { return ApproximationHelper::ElementWiseOnMatrixComplexAndComplex(m, c, complexFormat, compute); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } }; @@ -83,11 +85,13 @@ public: // Simplification Expression setSign(ExpressionNode::Sign s, ExpressionNode::ReductionContext reductionContext); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); Expression denominator(ExpressionNode::ReductionContext reductionContext) const; void sortChildrenInPlace(NAryExpressionNode::ExpressionOrder order, Context * context, bool canBeInterrupted) { NAryExpression::sortChildrenInPlace(order, context, false, canBeInterrupted); } + // Derivation + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: // Unit Expression removeUnit(Expression * unit); @@ -103,6 +107,7 @@ private: 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); + static bool TermsCanSafelyCombineExponents(const Expression & e1, const Expression & e2, ExpressionNode::ReductionContext reductionContext); static bool TermHasNumeralBase(const Expression & e); static bool TermHasNumeralExponent(const Expression & e); static const Expression CreateExponent(Expression e); diff --git a/poincare/include/poincare/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index 97455f3af..64dedd156 100644 --- a/poincare/include/poincare/n_ary_expression.h +++ b/poincare/include/poincare/n_ary_expression.h @@ -3,8 +3,6 @@ #include -// NAryExpressions are additions and multiplications - namespace Poincare { class NAryExpressionNode : public ExpressionNode { // TODO: VariableArityExpressionNode? @@ -20,9 +18,6 @@ public: } void eraseNumberOfChildren() override { m_numberOfChildren = 0; } - // Properties - bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; - // Comparison typedef int (*ExpressionOrder)(const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted); @@ -37,9 +32,6 @@ protected: /* With a pool of size < 120k and TreeNode of size 20, a node can't have more * than 6144 children which fit in uint16_t. */ uint16_t m_numberOfChildren; -private: - int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; - int simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; }; class NAryExpression : public Expression { @@ -63,6 +55,7 @@ protected: node()->sortChildrenInPlace(order, context, canSwapMatrices, canBeInterrupted); } NAryExpressionNode * node() const { return static_cast(Expression::node()); } + Expression checkChildrenAreRationalIntegersAndUpdate(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/n_ary_infix_expression.h b/poincare/include/poincare/n_ary_infix_expression.h new file mode 100644 index 000000000..545741474 --- /dev/null +++ b/poincare/include/poincare/n_ary_infix_expression.h @@ -0,0 +1,23 @@ +#ifndef POINCARE_N_ARY_INFIX_EXPRESSION_H +#define POINCARE_N_ARY_INFIX_EXPRESSION_H + +#include + +// NAryInfixExpressionNode are additions and multiplications + +namespace Poincare { + +class NAryInfixExpressionNode : public NAryExpressionNode { +public: + using NAryExpressionNode::NAryExpressionNode; + // Properties + bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; +protected: + // Order + int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; + int simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; +}; + +} + +#endif \ No newline at end of file diff --git a/poincare/include/poincare/naperian_logarithm.h b/poincare/include/poincare/naperian_logarithm.h index b466af839..799c58596 100644 --- a/poincare/include/poincare/naperian_logarithm.h +++ b/poincare/include/poincare/naperian_logarithm.h @@ -35,11 +35,11 @@ private: * (warning: ln takes the other side of the cut values on ]-inf-0i, 0-0i]). */ return Complex::Builder(std::log(c)); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/norm_cdf.h b/poincare/include/poincare/norm_cdf.h index ae9eee78c..511786f2e 100644 --- a/poincare/include/poincare/norm_cdf.h +++ b/poincare/include/poincare/norm_cdf.h @@ -29,9 +29,9 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class NormCDF final : public NormalDistributionFunction { diff --git a/poincare/include/poincare/norm_cdf2.h b/poincare/include/poincare/norm_cdf2.h index 2db2965a4..404d0fe68 100644 --- a/poincare/include/poincare/norm_cdf2.h +++ b/poincare/include/poincare/norm_cdf2.h @@ -31,9 +31,9 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class NormCDF2 final : public NormalDistributionFunction { diff --git a/poincare/include/poincare/norm_pdf.h b/poincare/include/poincare/norm_pdf.h index 5467a478e..ae9a2280f 100644 --- a/poincare/include/poincare/norm_pdf.h +++ b/poincare/include/poincare/norm_pdf.h @@ -29,9 +29,9 @@ private: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class NormPDF final : public NormalDistributionFunction { diff --git a/poincare/include/poincare/normal_distribution.h b/poincare/include/poincare/normal_distribution.h index fe30513f8..e9a54a273 100644 --- a/poincare/include/poincare/normal_distribution.h +++ b/poincare/include/poincare/normal_distribution.h @@ -16,11 +16,6 @@ public: * The result of the verification is *result. */ static bool ExpressionMuAndVarAreOK(bool * result, const Expression & mu, const Expression & sigma, Context * context); private: - /* For the standard normal distribution, P(X < y) > 0.99999995 for y >= 5.33 so the - * value displayed is 1. But this is dependent on the fact that we display - * only 7 decimal values! */ - static_assert(Preferences::LargeNumberOfSignificantDigits == 7, "k_boundStandardNormalDistribution is ill-defined compared to LargeNumberOfSignificantDigits"); - constexpr static double k_boundStandardNormalDistribution = 5.33; template static T StandardNormalCumulativeDistributiveFunctionAtAbscissa(T abscissa); template static T StandardNormalCumulativeDistributiveInverseForProbability(T probability); }; diff --git a/poincare/include/poincare/nth_root.h b/poincare/include/poincare/nth_root.h index b1280494a..dc29c7525 100644 --- a/poincare/include/poincare/nth_root.h +++ b/poincare/include/poincare/nth_root.h @@ -29,9 +29,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::NthRoot; }; LayoutShape rightLayoutShape() const override { return LayoutShape::Root; }; // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; diff --git a/poincare/include/poincare/number.h b/poincare/include/poincare/number.h index c8b4f1ab7..7c4f955c2 100644 --- a/poincare/include/poincare/number.h +++ b/poincare/include/poincare/number.h @@ -26,13 +26,15 @@ public: double doubleApproximation() const; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + }; class Number : public Expression { public: Number(const NumberNode * node) : Expression(node) {} /* Return either a Rational, a Decimal or an Infinity. */ - static Number ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLenght, bool exponentIsNegative, const char * exponentPart, size_t exponentLength); + static Number ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLength, bool exponentIsNegative, const char * exponentPart, size_t exponentLength); /* Return either a Decimal or an Infinity or an Undefined. */ template static Number DecimalNumber(T f); /* Return either a Float or an Infinity or an Undefined */ @@ -51,8 +53,10 @@ public: ExpressionNode::Sign sign() const { return Expression::sign(nullptr); } Number setSign(ExpressionNode::Sign s) { assert(s == ExpressionNode::Sign::Positive || s == ExpressionNode::Sign::Negative); - return Expression::setSign(s, ExpressionNode::ReductionContext(nullptr, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, ExpressionNode::ReductionTarget::User)).convert(); + return Expression::setSign(s, ExpressionNode::ReductionContext(nullptr, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, Preferences::UnitFormat::Metric, ExpressionNode::ReductionTarget::User)).convert(); } + + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); protected: Number() : Expression() {} NumberNode * node() const { return static_cast(Expression::node()); } diff --git a/poincare/include/poincare/opposite.h b/poincare/include/poincare/opposite.h index c4fc89166..87352d211 100644 --- a/poincare/include/poincare/opposite.h +++ b/poincare/include/poincare/opposite.h @@ -23,17 +23,18 @@ public: #endif // Properties + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::Opposite; } int polynomialDegree(Context * context, const char * symbolName) const override; Sign sign(Context * context) const override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, compute); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, compute); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, compute); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, compute); } // Layout diff --git a/poincare/include/poincare/parenthesis.h b/poincare/include/poincare/parenthesis.h index 66fe39ca2..e2fcd0a51 100644 --- a/poincare/include/poincare/parenthesis.h +++ b/poincare/include/poincare/parenthesis.h @@ -19,6 +19,8 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::Parenthesis; } int polynomialDegree(Context * context, const char * symbolName) const override; Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } @@ -31,10 +33,10 @@ public: LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; // Approximation - 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); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } private: - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Parenthesis final : public Expression { diff --git a/poincare/include/poincare/parenthesis_layout.h b/poincare/include/poincare/parenthesis_layout.h index 8d2330139..55681f54e 100644 --- a/poincare/include/poincare/parenthesis_layout.h +++ b/poincare/include/poincare/parenthesis_layout.h @@ -25,6 +25,7 @@ public: stream << "ParenthesisLayout"; } #endif + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; protected: KDSize computeSize() override { diff --git a/poincare/include/poincare/permute_coefficient.h b/poincare/include/poincare/permute_coefficient.h index 1bc05924b..7af4d70de 100644 --- a/poincare/include/poincare/permute_coefficient.h +++ b/poincare/include/poincare/permute_coefficient.h @@ -22,7 +22,7 @@ public: // Properties Type type() const override{ return Type::PermuteCoefficient; } - + Sign sign(Context * context) const override { return Sign::Positive; } private: // Layout @@ -33,9 +33,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class PermuteCoefficient final : public Expression { diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index 815dc065c..74e21ea8e 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -27,6 +27,10 @@ public: // Properties Type type() const override { return Type::Power; } Sign sign(Context * context) const override; + NullStatus nullStatus(Context * context) const override { + // NonNull Status can't be returned because base could be infinite. + return childAtIndex(0)->nullStatus(context) == NullStatus::Null ? NullStatus::Null : NullStatus::Unknown; + } Expression setSign(Sign s, ReductionContext reductionContext) override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; Expression removeUnit(Expression * unit) override; @@ -49,23 +53,24 @@ private: // Simplify Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { return childAtIndex(0)->leftLayoutShape(); } LayoutShape rightLayoutShape() const override { return LayoutShape::RightOfPower; } int simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; Expression denominator(ReductionContext reductionContext) const override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) 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); template static MatrixComplex computeOnMatrices(const MatrixComplex m, const MatrixComplex n, Preferences::ComplexFormat complexFormat); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Power final : public Expression { @@ -78,7 +83,8 @@ public: Expression setSign(ExpressionNode::Sign s, ExpressionNode::ReductionContext reductionContext); int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[]) const; Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: constexpr static int k_maxExactPowerMatrix = 100; diff --git a/poincare/include/poincare/prediction_interval.h b/poincare/include/poincare/prediction_interval.h index 734b2ba22..4ec7cdf58 100644 --- a/poincare/include/poincare/prediction_interval.h +++ b/poincare/include/poincare/prediction_interval.h @@ -31,9 +31,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class PredictionInterval final : public Expression { diff --git a/poincare/include/poincare/preferences.h b/poincare/include/poincare/preferences.h index dd284ca10..46950920a 100644 --- a/poincare/include/poincare/preferences.h +++ b/poincare/include/poincare/preferences.h @@ -52,6 +52,10 @@ public: Large = 0, Small = 1 }; + enum class UnitFormat : uint8_t { + Metric = 0, + Imperial = 1 + }; Preferences(); static Preferences * sharedPreferences(); AngleUnit angleUnit() const { return m_angleUnit; } diff --git a/poincare/include/poincare/product.h b/poincare/include/poincare/product.h index 140fe5c4f..04c5605e8 100644 --- a/poincare/include/poincare/product.h +++ b/poincare/include/poincare/product.h @@ -1,11 +1,11 @@ #ifndef POINCARE_PRODUCT_H #define POINCARE_PRODUCT_H -#include +#include namespace Poincare { -class ProductNode final : public SequenceNode { +class ProductNode final : public SumAndProductNode { public: // TreeNode size_t size() const override { return sizeof(ProductNode); } @@ -18,8 +18,8 @@ public: Type type() const override { return Type::Product; } private: - float emptySequenceValue() const override { return 1.0f; } - Layout createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override; + float emptySumAndProductValue() const override { return 1.0f; } + Layout createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; Evaluation evaluateWithNextTerm(DoublePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const override { return templatedApproximateWithNextTerm(a, b, complexFormat); @@ -30,10 +30,10 @@ private: template Evaluation templatedApproximateWithNextTerm(Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const; }; -class Product final : public Sequence { +class Product final : public SumAndProduct { friend class ProductNode; public: - Product(const ProductNode * n) : Sequence(n) {} + Product(const ProductNode * n) : SumAndProduct(n) {} static Product Builder(Expression child0, Symbol child1, Expression child2, Expression child3) { return TreeHandle::FixedArityBuilder({child0, child1, child2, child3}); } static Expression UntypedBuilder(Expression children); @@ -42,4 +42,4 @@ public: } -#endif +#endif \ No newline at end of file diff --git a/poincare/include/poincare/randint.h b/poincare/include/poincare/randint.h index b8f03edc3..af068d593 100644 --- a/poincare/include/poincare/randint.h +++ b/poincare/include/poincare/randint.h @@ -28,13 +28,13 @@ private: Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templateApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templateApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return templateApproximate(context, complexFormat, angleUnit); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return templateApproximate(approximationContext); } - template Evaluation templateApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool * inputIsUndefined = nullptr) const; + template Evaluation templateApproximate(ApproximationContext approximationContext, bool * inputIsUndefined = nullptr) const; // Simplification Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; diff --git a/poincare/include/poincare/random.h b/poincare/include/poincare/random.h index 564a758ed..c6a7dae9e 100644 --- a/poincare/include/poincare/random.h +++ b/poincare/include/poincare/random.h @@ -32,10 +32,10 @@ private: Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templateApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templateApproximate(); } template Evaluation templateApproximate() const; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index c1af28415..2b7bf6271 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -17,6 +17,7 @@ public: bool isNegative() const { return m_negative; } void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } + NullStatus nullStatus(Context * context) const override { return isZero() ? NullStatus::Null : NullStatus::NonNull; } // TreeNode size_t size() const override; @@ -38,8 +39,8 @@ public: Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } template T templatedApproximate() const; // Basic test @@ -57,7 +58,7 @@ public: private: int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + 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(ReductionContext reductionContext) const override; diff --git a/poincare/include/poincare/real_part.h b/poincare/include/poincare/real_part.h index ddcbd24a7..1200cb9c6 100644 --- a/poincare/include/poincare/real_part.h +++ b/poincare/include/poincare/real_part.h @@ -19,6 +19,8 @@ public: #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context) == NullStatus::Null ? NullStatus::Null : NullStatus::Unknown; } Type type() const override { return Type::RealPart; } @@ -34,11 +36,11 @@ private: template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::real(c)); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/right_parenthesis_layout.h b/poincare/include/poincare/right_parenthesis_layout.h index 1f30b6140..bcf64a2db 100644 --- a/poincare/include/poincare/right_parenthesis_layout.h +++ b/poincare/include/poincare/right_parenthesis_layout.h @@ -16,9 +16,6 @@ public: static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); - // LayoutNode - bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - // SerializableNode int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, ')'); diff --git a/poincare/include/poincare/round.h b/poincare/include/poincare/round.h index bb4a769aa..3e3dc5962 100644 --- a/poincare/include/poincare/round.h +++ b/poincare/include/poincare/round.h @@ -20,6 +20,7 @@ public: // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Round; } private: // Layout @@ -30,9 +31,9 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Round final : public Expression { diff --git a/poincare/include/poincare/sequence.h b/poincare/include/poincare/sequence.h index 0067f3790..a5e7656de 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -1,38 +1,56 @@ #ifndef POINCARE_SEQUENCE_H #define POINCARE_SEQUENCE_H -#include -#include -#include +#include namespace Poincare { -// Sequences are Product and Sum - -class SequenceNode : public ParameteredExpressionNode { +class SequenceNode : public SymbolAbstractNode { public: - int numberOfChildren() const override { return 4; } + SequenceNode(const char * newName, int length); + const char * name() const override { return m_name; } + + int numberOfChildren() const override { return 1; } +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "Sequence"; + } +#endif + + Type type() const override { return Type::Sequence; } + Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) override; + int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; + private: + char m_name[0]; + + size_t nodeSize() const override { return sizeof(SequenceNode); } + // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - virtual Layout createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const = 0; - // Simplication + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; - /* Approximation */ - 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; - virtual float emptySequenceValue() const = 0; - virtual Evaluation evaluateWithNextTerm(SinglePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const = 0; - virtual Evaluation evaluateWithNextTerm(DoublePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const = 0; + LayoutShape leftLayoutShape() const override { return strlen(m_name) > 1 ? LayoutShape::MoreLetters : LayoutShape::OneLetter; }; + LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override; + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; -class Sequence : public Expression { +class Sequence : public SymbolAbstract { +friend SequenceNode; public: - Sequence(const SequenceNode * n) : Expression(n) {} - Expression shallowReduce(Context * context); + Sequence(const SequenceNode * n) : SymbolAbstract(n) {} + static Sequence Builder(const char * name, size_t length, Expression child = Expression()); + + // Simplification + Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount); }; } -#endif +#endif \ No newline at end of file diff --git a/poincare/include/poincare/sequence_layout.h b/poincare/include/poincare/sequence_layout.h index 7f37b0ffc..b16427163 100644 --- a/poincare/include/poincare/sequence_layout.h +++ b/poincare/include/poincare/sequence_layout.h @@ -8,8 +8,8 @@ namespace Poincare { class SequenceLayoutNode : public LayoutNode { public: - constexpr static KDCoordinate k_symbolHeight = 15; - constexpr static KDCoordinate k_symbolWidth = 9; + constexpr static KDCoordinate k_symbolHeight = 29; + constexpr static KDCoordinate k_symbolWidth = 22; using LayoutNode::LayoutNode; diff --git a/poincare/include/poincare/sign_function.h b/poincare/include/poincare/sign_function.h index d5379058a..b8445938d 100644 --- a/poincare/include/poincare/sign_function.h +++ b/poincare/include/poincare/sign_function.h @@ -20,7 +20,8 @@ public: // Properties Type type() const override { return Type::SignFunction; } - Sign sign(Context * context) const override; + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Expression setSign(Sign s, ReductionContext reductionContext) override; @@ -34,11 +35,11 @@ private: LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/sine.h b/poincare/include/poincare/sine.h index 80307a048..b10e3113a 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -21,7 +21,6 @@ public: // Properties Type type() const override { return Type::Sine; } - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Radian); @@ -35,12 +34,16 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; + // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -52,6 +55,9 @@ public: static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("sin", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/square_root.h b/poincare/include/poincare/square_root.h index 6a46aee29..6f40f3f7b 100644 --- a/poincare/include/poincare/square_root.h +++ b/poincare/include/poincare/square_root.h @@ -10,6 +10,8 @@ namespace Poincare { class SquareRootNode /*final*/ : public ExpressionNode { public: // ExpressionNode + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context) == Sign::Positive ? Sign::Positive : Sign::Unknown ; } + NullStatus nullStatus(Context * context) const override { return childAtIndex(0)->nullStatus(context); } Type type() const override { return Type::SquareRoot; } // TreeNode @@ -30,11 +32,11 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::Root; }; // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; diff --git a/poincare/include/poincare/store.h b/poincare/include/poincare/store.h index 95bd3c146..cc89d40ec 100644 --- a/poincare/include/poincare/store.h +++ b/poincare/include/poincare/store.h @@ -23,9 +23,9 @@ private: // Simplification Expression shallowReduce(ReductionContext reductionContext) 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Store final : public Expression { @@ -48,7 +48,7 @@ public: Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); private: - Expression storeValueForSymbol(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + Expression storeValueForSymbol(Context * context) const; StoreNode * node() const { return static_cast(Expression::node()); } }; diff --git a/poincare/include/poincare/subtraction.h b/poincare/include/poincare/subtraction.h index 92e213ba9..ed74653d2 100644 --- a/poincare/include/poincare/subtraction.h +++ b/poincare/include/poincare/subtraction.h @@ -28,11 +28,11 @@ public: // Approximation template static Complex compute(const std::complex c, const std::complex d, Preferences::ComplexFormat complexFormat) { return Complex::Builder(c - d); } - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } /* Layout */ diff --git a/poincare/include/poincare/sum.h b/poincare/include/poincare/sum.h index af1542203..84b062a88 100644 --- a/poincare/include/poincare/sum.h +++ b/poincare/include/poincare/sum.h @@ -1,11 +1,11 @@ #ifndef POINCARE_SUM_H #define POINCARE_SUM_H -#include +#include namespace Poincare { -class SumNode final : public SequenceNode { +class SumNode final : public SumAndProductNode { public: // TreeNode size_t size() const override { return sizeof(SumNode); } @@ -18,8 +18,8 @@ public: Type type() const override { return Type::Sum; } private: - float emptySequenceValue() const override { return 0.0f; } - Layout createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override; + float emptySumAndProductValue() const override { return 0.0f; } + Layout createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; Evaluation evaluateWithNextTerm(DoublePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const override { return templatedApproximateWithNextTerm(a, b, complexFormat); @@ -30,10 +30,10 @@ private: template Evaluation templatedApproximateWithNextTerm(Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const; }; -class Sum final : public Sequence { +class Sum final : public SumAndProduct { friend class SumNode; public: - Sum(const SumNode * n) : Sequence(n) {} + Sum(const SumNode * n) : SumAndProduct(n) {} static Sum Builder(Expression child0, Symbol child1, Expression child2, Expression child3) { return TreeHandle::FixedArityBuilder({child0, child1, child2, child3}); } static Expression UntypedBuilder(Expression children); diff --git a/poincare/include/poincare/sum_and_product.h b/poincare/include/poincare/sum_and_product.h new file mode 100644 index 000000000..54f038bfe --- /dev/null +++ b/poincare/include/poincare/sum_and_product.h @@ -0,0 +1,36 @@ +#ifndef POINCARE_SUM_AND_PRODUCT_H +#define POINCARE_SUM_AND_PRODUCT_H + +#include +#include +#include + +namespace Poincare { + +class SumAndProductNode : public ParameteredExpressionNode { +public: + int numberOfChildren() const override { return 4; } +private: + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + virtual Layout createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const = 0; + // Simplication + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; + /* Approximation */ + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; + virtual float emptySumAndProductValue() const = 0; + virtual Evaluation evaluateWithNextTerm(SinglePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const = 0; + virtual Evaluation evaluateWithNextTerm(DoublePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const = 0; +}; + +class SumAndProduct : public Expression { +public: + SumAndProduct(const SumAndProductNode * n) : Expression(n) {} + Expression shallowReduce(Context * context); +}; + +} + +#endif \ No newline at end of file diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 9c78b8c43..4f18e6176 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -25,7 +25,6 @@ public: int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; int getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable, int nextVariableIndex) const override; - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; /* getUnit returns Undefined, because the symbol would have * already been replaced if it should have been.*/ @@ -37,16 +36,19 @@ public: Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) override; LayoutShape leftLayoutShape() const override; + /* Derivation */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + /* Approximation */ - 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); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } bool isUnknown() const; private: char m_name[0]; // MUST be the last member variable size_t nodeSize() const override { return sizeof(SymbolNode); } - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class Symbol final : public SymbolAbstract { @@ -69,6 +71,7 @@ public: // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression); int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const; Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount); diff --git a/poincare/include/poincare/symbol_abstract.h b/poincare/include/poincare/symbol_abstract.h index 02b2e79a8..e31ba27b4 100644 --- a/poincare/include/poincare/symbol_abstract.h +++ b/poincare/include/poincare/symbol_abstract.h @@ -61,9 +61,12 @@ class SymbolAbstract : public Expression { friend class Constant; friend class Function; friend class FunctionNode; + friend class Sequence; + friend class SequenceNode; friend class Symbol; friend class SymbolNode; friend class SymbolAbstractNode; + friend class SumAndProductNode; public: const char * name() const { return node()->name(); } bool hasSameNameAs(const SymbolAbstract & other) const; diff --git a/poincare/include/poincare/tangent.h b/poincare/include/poincare/tangent.h index ee0891038..263bc46bf 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -20,7 +20,6 @@ public: // Properties Type type() const override { return Type::Tangent; } - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; private: // Layout @@ -32,13 +31,17 @@ private: LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; + // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Radian); - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } }; @@ -50,6 +53,9 @@ public: static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("tan", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/trigonometry.h b/poincare/include/poincare/trigonometry.h index 0eac9d5c2..424f1e80f 100644 --- a/poincare/include/poincare/trigonometry.h +++ b/poincare/include/poincare/trigonometry.h @@ -13,10 +13,12 @@ public: Sine = 1, }; static double PiInAngleUnit(Preferences::AngleUnit angleUnit); - static float characteristicXRange(const Expression & e, Context * context, Preferences::AngleUnit angleUnit); static bool isDirectTrigonometryFunction(const Expression & e); static bool isInverseTrigonometryFunction(const Expression & e); static bool AreInverseFunctions(const Expression & directFunction, const Expression & inverseFunction); + /* Returns a (unreduced) division between pi in each unit, or 1 if the units + * are the same. */ + static Expression UnitConversionFactor(Preferences::AngleUnit fromUnit, Preferences::AngleUnit toUnit); static bool ExpressionIsEquivalentToTangent(const Expression & e); static Expression shallowReduceDirectFunction(Expression & e, ExpressionNode::ReductionContext reductionContext); static Expression shallowReduceInverseFunction(Expression & e, ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/undefined.h b/poincare/include/poincare/undefined.h index 90f0078e3..7dfb094f3 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -22,13 +22,17 @@ public: Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } + /* Derivation + * Unlike Numbers that derivate to 0, Undefined derivates to Undefined. */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return true; } + // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 397c1cf34..4d4eee249 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -5,22 +5,21 @@ namespace Poincare { -class UnitNode final : public ExpressionNode { -public: - /* The units having the same physical dimension are grouped together. * Each such group has a standard representative with a standard prefix. * * Each representative has * - a root symbol - * - a definition - * - a list of allowed output prefixes - * Given a Dimension, a representative in that Dimension and a Prefix - * allowed for that representative, one may get a symbol and an Expression. + * - a definition, as the conversion ratio with the SI unit of the same + * dimensions + * - informations on how the representative should be prefixed. + * + * Given an representative and a Prefix allowed for that representative, one may get + * a symbol and an Expression. * * FIXME ? - * The UnitNode class holds as members pointers to a Dimension, a - * Representative, a Prefix. Those nested classes may not be forward + * The UnitNode class holds as members pointers to a Representative and a + * Prefix. Those nested classes may not be forward * declared and must be defined in UnitNode and then aliased in Unit so as * to be used outside. That technical limitation could have been avoided if * UnitNode were itself a nested class of Unit, say Unit::Node. More @@ -29,109 +28,415 @@ public: * and scopes. */ - // There are 7 base units from which all other units are derived. - static constexpr size_t NumberOfBaseUnits = 8; +class Unit; + +class UnitNode final : public ExpressionNode { +public: + static constexpr int k_numberOfBaseUnits = 7; class Prefix { + friend class Unit; public: - constexpr Prefix(const char * symbol, int8_t exponent) : - m_symbol(symbol), - m_exponent(exponent) - {} + static constexpr int k_numberOfPrefixes = 13; + static const Prefix * Prefixes(); + static const Prefix * EmptyPrefix(); const char * symbol() const { return m_symbol; } int8_t exponent() const { return m_exponent; } int serialize(char * buffer, int bufferSize) const; private: + constexpr Prefix(const char * symbol, int8_t exponent) : + m_symbol(symbol), + m_exponent(exponent) + {} + const char * m_symbol; int8_t m_exponent; }; + template + struct Vector { + // SupportSize is defined as the number of distinct base units. + size_t supportSize() const; + static Vector FromBaseUnits(const Expression baseUnits); + const T coefficientAtIndex(size_t i) const { + assert(i < k_numberOfBaseUnits); + const T coefficients[k_numberOfBaseUnits] = {time, distance, mass, current, temperature, amountOfSubstance, luminuousIntensity}; + return coefficients[i]; + } + void setCoefficientAtIndex(size_t i, T c) { + assert(i < k_numberOfBaseUnits); + T * coefficientsAddresses[k_numberOfBaseUnits] = {&time, &distance, &mass, ¤t, &temperature, &amountOfSubstance, &luminuousIntensity}; + *(coefficientsAddresses[i]) = c; + } + bool operator==(const Vector &rhs) const { return time == rhs.time && distance == rhs.distance && mass == rhs.mass && current == rhs.current && temperature == rhs.temperature && amountOfSubstance == rhs.amountOfSubstance && luminuousIntensity == rhs.luminuousIntensity; } + bool operator!=(const Vector &rhs) const { return !(*this == rhs); } + void addAllCoefficients(const Vector other, int factor); + Expression toBaseUnits() const; + T time; + T distance; + T mass; + T current; + T temperature; + T amountOfSubstance; + T luminuousIntensity; + }; + class Representative { + friend class Unit; public: - /* The following class is meant to be an attribute determining whether a - * unit symbol is prefixable at all. If Yes, any prefix is accepted as - * input, whereas the allowed output prefixes are prescribed manually. */ enum class Prefixable { - No, - Yes + None, + PositiveLongScale, + NegativeLongScale, + Positive, + Negative, + NegativeAndKilo, + LongScale, + All, }; - template - constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix * const (&outputPrefixes)[N]) : + static constexpr int k_numberOfDimensions = 24; + static const Representative * const * DefaultRepresentatives(); + static const Representative * RepresentativeForDimension(Vector vector); + constexpr Representative(const char * rootSymbol, double ratio, Prefixable inputPrefixable, Prefixable outputPrefixable) : m_rootSymbol(rootSymbol), - m_definition(definition), - m_prefixable(prefixable), - m_outputPrefixes(outputPrefixes), - m_outputPrefixesLength(N) - { - } + m_ratio(ratio), + m_inputPrefixable(inputPrefixable), + m_outputPrefixable(outputPrefixable) + {} + + virtual const Vector dimensionVector() const { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; }; + virtual int numberOfRepresentatives() const { return 0; }; + /* representativesOfSameDimension returns a pointer to the array containing + * all representatives for this's dimension. */ + virtual const Representative * representativesOfSameDimension() const { return nullptr; }; + virtual const Prefix * basePrefix() const { return Prefix::EmptyPrefix(); } + virtual bool isBaseUnit() const { return false; } + virtual const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { return DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension(), numberOfRepresentatives(), prefix); } + /* hasSpecialAdditionalExpressions return true if the unit has special + * forms suchas as splits (for time and imperial units) or common + * conversions (such as speed and energy). */ + virtual bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return false; } + virtual int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { return 0; } + const char * rootSymbol() const { return m_rootSymbol; } - const char * definition() const { return m_definition; } - bool isPrefixable() const { return m_prefixable == Prefixable::Yes; } - const Prefix * const * outputPrefixes() const { return m_outputPrefixes; } - size_t outputPrefixesLength() const { return m_outputPrefixesLength; } - bool canParse(const char * symbol, size_t length, - const Prefix * * prefix) const; + double ratio() const { return m_ratio; } + bool isInputPrefixable() const { return m_inputPrefixable != Prefixable::None; } + bool isOutputPrefixable() const { return m_outputPrefixable != Prefixable::None; } int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; - const Prefix * bestPrefixForValue(double & value, const int exponent) const; - private: + bool canParseWithEquivalents(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix) const; + bool canParse(const char * symbol, size_t length, const Prefix * * prefix) const; + Expression toBaseUnits() const; + bool canPrefix(const Prefix * prefix, bool input) const; + const Prefix * findBestPrefix(double value, double exponent) const; + + protected: + static const Representative * DefaultFindBestRepresentative(double value, double exponent, const Representative * representatives, int length, const Prefix * * prefix); const char * m_rootSymbol; - const char * m_definition; - const Prefixable m_prefixable; - const Prefix * const * m_outputPrefixes; - const size_t m_outputPrefixesLength; + /* m_ratio is the factor used to convert a unit made of the representative + * and its base prefix into base SI units. + * ex : m_ratio for Liter is 1e-3 (as 1_L = 1e-3_m). + * m_ratio for gram is 1 (as k is its best prefix and _kg is SI) */ + const double m_ratio; + const Prefixable m_inputPrefixable; + const Prefixable m_outputPrefixable; }; - class Dimension { + class TimeRepresentative : public Representative { + friend class Unit; public: - template - struct Vector { - struct Metrics { - size_t supportSize; - T norm; - }; - Metrics metrics() const; - static Vector FromBaseUnits(const Expression baseUnits); - const T coefficientAtIndex(size_t i) const { - assert(i < NumberOfBaseUnits); - return *(reinterpret_cast(this) + i); - } - void setCoefficientAtIndex(size_t i, T c) { - assert(i < NumberOfBaseUnits); - *(reinterpret_cast(this) + i) = c; - } - T time; - T distance; - T mass; - T current; - T temperature; - T amountOfSubstance; - T luminuousIntensity; - T solidAngle; - }; - template - constexpr Dimension(Vector vector, const Representative (&representatives)[N], const Prefix * stdRepresentativePrefix) : - m_vector(vector), - m_representatives(representatives), - m_representativesUpperBound(representatives + N), - m_stdRepresentativePrefix(stdRepresentativePrefix) - { - } - const Vector * vector() const { return &m_vector; } - const Representative * stdRepresentative() const { return m_representatives; } - const Representative * representativesUpperBound() const { return m_representativesUpperBound; } - const Prefix * stdRepresentativePrefix() const { return m_stdRepresentativePrefix; } - bool canParse(const char * symbol, size_t length, - const Representative * * representative, const Prefix * * prefix) const; + constexpr static TimeRepresentative Default() { return TimeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 7; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return m_ratio * value >= representativesOfSameDimension()[1].ratio(); } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; private: - Vector m_vector; - const Representative * m_representatives; - const Representative * m_representativesUpperBound; - const Prefix * m_stdRepresentativePrefix; + using Representative::Representative; }; - UnitNode(const Dimension * dimension, const Representative * representative, const Prefix * prefix) : + class DistanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static DistanceRepresentative Default() { return DistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 8; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return unitFormat == Preferences::UnitFormat::Imperial; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + class MassRepresentative : public Representative { + friend class Unit; + public: + constexpr static MassRepresentative Default() { return MassRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 7; } + const Representative * representativesOfSameDimension() const override; + const Prefix * basePrefix() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return unitFormat == Preferences::UnitFormat::Imperial; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + class CurrentRepresentative : public Representative { + friend class Unit; + public: + constexpr static CurrentRepresentative Default() { return CurrentRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + private: + using Representative::Representative; + }; + + class TemperatureRepresentative : public Representative { + friend class Unit; + public: + static double ConvertTemperatures(double value, const Representative * source, const Representative * target); + constexpr static TemperatureRepresentative Default() { return TemperatureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 1, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 3; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return this; } + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + static constexpr double k_celsiusOrigin = 273.15; + static constexpr double k_fahrenheitOrigin = 459.67; + using Representative::Representative; + }; + + class AmountOfSubstanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static AmountOfSubstanceRepresentative Default() { return AmountOfSubstanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + private: + using Representative::Representative; + }; + + class LuminousIntensityRepresentative : public Representative { + friend class Unit; + public: + constexpr static LuminousIntensityRepresentative Default() { return LuminousIntensityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 1}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + private: + using Representative::Representative; + }; + + class FrequencyRepresentative : public Representative { + friend class Unit; + public: + constexpr static FrequencyRepresentative Default() { return FrequencyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class ForceRepresentative : public Representative { + friend class Unit; + public: + constexpr static ForceRepresentative Default() { return ForceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class PressureRepresentative : public Representative { + friend class Unit; + public: + constexpr static PressureRepresentative Default() { return PressureRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = -1, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 3; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class EnergyRepresentative : public Representative { + friend class Unit; + public: + constexpr static EnergyRepresentative Default() { return EnergyRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 2; } + const Representative * representativesOfSameDimension() const override; + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + class PowerRepresentative : public Representative { + friend class Unit; + public: + constexpr static PowerRepresentative Default() { return PowerRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class ElectricChargeRepresentative : public Representative { + friend class Unit; + public: + using Representative::Representative; + constexpr static ElectricChargeRepresentative Default() { return ElectricChargeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 1, .distance = 0, .mass = 0, .current = 1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + }; + + class ElectricPotentialRepresentative : public Representative { + friend class Unit; + public: + constexpr static ElectricPotentialRepresentative Default() { return ElectricPotentialRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class ElectricCapacitanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static ElectricCapacitanceRepresentative Default() { return ElectricCapacitanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 4, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class ElectricResistanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static ElectricResistanceRepresentative Default() { return ElectricResistanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -3, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class ElectricConductanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static ElectricConductanceRepresentative Default() { return ElectricConductanceRepresentative(nullptr, 1., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 3, .distance = -2, .mass = -1, .current = 2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class MagneticFluxRepresentative : public Representative { + friend class Unit; + public: + constexpr static MagneticFluxRepresentative Default() { return MagneticFluxRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class MagneticFieldRepresentative : public Representative { + friend class Unit; + public: + constexpr static MagneticFieldRepresentative Default() { return MagneticFieldRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 0, .mass = 1, .current = -1, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class InductanceRepresentative : public Representative { + friend class Unit; + public: + constexpr static InductanceRepresentative Default() { return InductanceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -2, .distance = 2, .mass = 1, .current = -2, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class CatalyticActivityRepresentative : public Representative { + friend class Unit; + public: + constexpr static CatalyticActivityRepresentative Default() { return CatalyticActivityRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = -1, .distance = 0, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 1, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 1; } + const Representative * representativesOfSameDimension() const override; + private: + using Representative::Representative; + }; + + class SurfaceRepresentative : public Representative { + friend class Unit; + public: + constexpr static SurfaceRepresentative Default() { return SurfaceRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 2, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 2; } + const Representative * representativesOfSameDimension() const override; + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + class VolumeRepresentative : public Representative { + friend class Unit; + public: + constexpr static VolumeRepresentative Default() { return VolumeRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const Vector dimensionVector() const override { return Vector{.time = 0, .distance = 3, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + int numberOfRepresentatives() const override { return 8; } + const Representative * representativesOfSameDimension() const override; + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override; + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + class SpeedRepresentative : public Representative { + friend class Unit; + public: + constexpr static SpeedRepresentative Default() { return SpeedRepresentative(nullptr, 0., Prefixable::None, Prefixable::None); } + const VectordimensionVector() const override { return Vector{.time = -1, .distance = 1, .mass = 0, .current = 0, .temperature = 0, .amountOfSubstance = 0, .luminuousIntensity = 0}; } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return nullptr; } + bool hasSpecialAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + UnitNode(const Representative * representative, const Prefix * prefix) : ExpressionNode(), - m_dimension(dimension), m_representative(representative), m_prefix(prefix) {} @@ -151,7 +456,8 @@ public: // Expression Properties Type type() const override { return Type::Unit; } - Sign sign(Context * context) const override; + Sign sign(Context * context) const override { return Sign::Positive; } + NullStatus nullStatus(Context * context) const override { return NullStatus::NonNull; } Expression removeUnit(Expression * unit) override; /* Layout */ @@ -159,737 +465,236 @@ public: int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Approximation */ - 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); } + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } // Comparison int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; // Simplification Expression shallowReduce(ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::OneLetter; } // TODO - const Dimension * dimension() const { return m_dimension; } const Representative * representative() const { return m_representative; } const Prefix * prefix() const { return m_prefix; } void setRepresentative(const Representative * representative) { m_representative = representative; } void setPrefix(const Prefix * prefix) { m_prefix = prefix; } private: - const Dimension * m_dimension; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; + const Representative * m_representative; const Prefix * m_prefix; - - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; -class Unit final : public Expression { +/* FIXME : This can be replaced by std::string_view when moving to C++17 */ +constexpr bool strings_equal(const char * s1, const char * s2) { return *s1 == *s2 && (*s1 == '\0' || strings_equal(s1 + 1, s2 + 1)); } + +class Unit : public Expression { friend class UnitNode; public: + /* Prefixes and Representativees defined below must be defined only once and + * all units must be constructed from their pointers. This way we can easily + * check if two Unit objects are equal by comparing pointers. This saves us + * from overloading the == operator on Prefix and Representative and saves + * time at execution. As such, their constructor are private and can only be + * accessed by their friend class Unit. */ typedef UnitNode::Prefix Prefix; - typedef UnitNode::Representative Representative; - typedef UnitNode::Dimension Dimension; - /* TODO: Prefix, Representative and Dimension defined below must be defined - * only once and all units must be constructed from their pointers. This way - * we can easily check if two Unit objects are equal by comparing pointers. - * This saves us from overloading the == operator on Prefix, Representative - * and Dimension and saves time at execution. We should assert at compilation - * that only one occurence of each is built by maybe privatizing constructors - * on these classes? */ - static constexpr const Prefix - PicoPrefix = Prefix("p", -12), - NanoPrefix = Prefix("n", -9), - MicroPrefix = Prefix("μ", -6), - MilliPrefix = Prefix("m", -3), - CentiPrefix = Prefix("c", -2), - DeciPrefix = Prefix("d", -1), - EmptyPrefix = Prefix("", 0), - DecaPrefix = Prefix("da", 1), - HectoPrefix = Prefix("h", 2), - KiloPrefix = Prefix("k", 3), - MegaPrefix = Prefix("M", 6), - GigaPrefix = Prefix("G", 9), - TeraPrefix = Prefix("T", 12); - static constexpr const Prefix * NoPrefix[] = { - &EmptyPrefix - }; - static constexpr const Prefix * NegativeLongScalePrefixes[] = { - &PicoPrefix, - &NanoPrefix, - &MicroPrefix, - &MilliPrefix, - &EmptyPrefix, - }; - static constexpr const Prefix * PositiveLongScalePrefixes[] = { - &EmptyPrefix, - &KiloPrefix, - &MegaPrefix, - &GigaPrefix, - &TeraPrefix, - }; - static constexpr const Prefix * LongScalePrefixes[] = { - &PicoPrefix, - &NanoPrefix, - &MicroPrefix, - &MilliPrefix, - &EmptyPrefix, - &KiloPrefix, - &MegaPrefix, - &GigaPrefix, - &TeraPrefix, - }; - static constexpr const Prefix * NegativePrefixes[] = { - &PicoPrefix, - &NanoPrefix, - &MicroPrefix, - &MilliPrefix, - &CentiPrefix, - &DeciPrefix, - &EmptyPrefix, - }; - static constexpr const Prefix * AllPrefixes[] = { - &PicoPrefix, - &NanoPrefix, - &MicroPrefix, - &MilliPrefix, - &CentiPrefix, - &DeciPrefix, - &EmptyPrefix, - &DecaPrefix, - &HectoPrefix, - &KiloPrefix, - &MegaPrefix, - &GigaPrefix, - &TeraPrefix, - }; - static constexpr size_t NumberOfBaseUnits = UnitNode::NumberOfBaseUnits; - static constexpr const Representative - TimeRepresentatives[] = { - Representative("s", nullptr, - Representative::Prefixable::Yes, - NegativeLongScalePrefixes), - Representative("min", "60*_s", - Representative::Prefixable::No, - NoPrefix), - Representative("h", "60*60*_s", - Representative::Prefixable::No, - NoPrefix), - Representative("day", "24*60*60*_s", - Representative::Prefixable::No, - NoPrefix), - Representative("week", "7*24*60*60*_s", - Representative::Prefixable::No, - NoPrefix), - Representative("month", "365.25/12*24*60*60*_s", - Representative::Prefixable::No, - NoPrefix), - Representative("year", "365.25*24*60*60*_s", - Representative::Prefixable::No, - NoPrefix), - }, - DistanceRepresentatives[] = { - Representative("m", nullptr, - Representative::Prefixable::Yes, - LongScalePrefixes), - Representative("au", "149587870700*_m", - Representative::Prefixable::No, - NoPrefix), - Representative("ly", "299792458*_m/_s*_year", - Representative::Prefixable::No, - NoPrefix), - Representative("pc", "180*60*60/π*_au", - Representative::Prefixable::No, - NoPrefix), - Representative("ft", nullptr, // used meters to allow for conversion - Representative::Prefixable::No, - NoPrefix), - Representative("in", "(1/12)*_ft", - Representative::Prefixable::No, - NoPrefix), - Representative("yd", "3*_ft", - Representative::Prefixable::No, - NoPrefix), - Representative("mi", "1760*_yd", - Representative::Prefixable::No, - NoPrefix), - }, - SolideAngleRepresentatives[] = { - Representative("sr", nullptr, Representative::Prefixable::No, NoPrefix), - }, - MassRepresentatives[] = { - Representative("g", nullptr, - Representative::Prefixable::Yes, - LongScalePrefixes), - Representative("t", "1000_kg", - Representative::Prefixable::Yes, - NoPrefix), - Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", - Representative::Prefixable::Yes, - NoPrefix), - Representative("lb", nullptr, - Representative::Prefixable::Yes, - NoPrefix), - Representative("oz", "(1/16)*_lb", - Representative::Prefixable::Yes, - NoPrefix), - Representative("ton", "2000*_lb", - Representative::Prefixable::Yes, - NoPrefix), - }, - CurrentRepresentatives[] = { - Representative("A", nullptr, - Representative::Prefixable::Yes, - NegativeLongScalePrefixes), - }, - TemperatureRepresentatives[] = { - Representative("K", nullptr, - Representative::Prefixable::No, - NoPrefix), - }, - AmountOfSubstanceRepresentatives[] = { - Representative("mol", nullptr, - Representative::Prefixable::Yes, - NegativeLongScalePrefixes), - }, - LuminousIntensityRepresentatives[] = { - Representative("cd", nullptr, - Representative::Prefixable::No, - NoPrefix), - }, - FrequencyRepresentatives[] = { - Representative("Hz", "_s^-1", - Representative::Prefixable::Yes, - PositiveLongScalePrefixes), - }, - LuminousFluxRepresentatives[] = { - Representative("lm", "_cd*_sr", Representative::Prefixable::No, NoPrefix), - }, - IlluminanceRepresentatives[] = { - Representative("lx", "_cd*_sr*_m^(-2)", Representative::Prefixable::No, NoPrefix), - }, - ForceRepresentatives[] = { - Representative("N", "_kg*_m*_s^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - PressureRepresentatives[] = { - Representative("Pa", "_kg*_m^-1*_s^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - Representative("bar", "1000_hPa", - Representative::Prefixable::Yes, - NoPrefix), - Representative("atm", "101325_Pa", - Representative::Prefixable::Yes, - NoPrefix), - }, - EnergyRepresentatives[] = { - Representative("J", "_kg*_m^2*_s^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - Representative("eV", "1.602176634ᴇ-19*_J", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - PowerRepresentatives[] = { - Representative("W", "_kg*_m^2*_s^-3", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - ElectricChargeRepresentatives[] = { - Representative("C", "_A*_s", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - ElectricPotentialRepresentatives[] = { - Representative("V", "_kg*_m^2*_s^-3*_A^-1", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - ElectricCapacitanceRepresentatives[] = { - Representative("F", "_A^2*_s^4*_kg^-1*_m^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - ElectricResistanceRepresentatives[] = { - Representative("Ω", "_kg*_m^2*_s^-3*_A^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - ElectricConductanceRepresentatives[] = { - Representative("S", "_A^2*_s^3*_kg^-1*_m^-2", - Representative::Prefixable::Yes, - LongScalePrefixes), - }, - MagneticFluxRepresentatives[] = { - Representative("Wb", "_kg*_m^2*_s^-2*_A^-1", - Representative::Prefixable::Yes, - NoPrefix), - }, - MagneticFieldRepresentatives[] = { - Representative("T", "_kg*_s^-2*_A^-1", - Representative::Prefixable::Yes, - NoPrefix), - }, - InductanceRepresentatives[] = { - Representative("H", "_kg*_m^2*_s^-2*_A^-2", - Representative::Prefixable::Yes, - NoPrefix), - }, - CatalyticActivityRepresentatives[] = { - Representative("kat", "_mol*_s^-1", - Representative::Prefixable::Yes, - NoPrefix), - }, - SurfaceRepresentatives[] = { - Representative("ha", "10^4*_m^2", - Representative::Prefixable::No, - NoPrefix), - }, - VolumeRepresentatives[] = { - Representative("L", "10^-3*_m^3", - Representative::Prefixable::Yes, - NegativePrefixes), - }; - // TODO: find a better way to define these pointers - static_assert(sizeof(TimeRepresentatives)/sizeof(Representative) == 7, "The Unit::SecondRepresentative, Unit::HourRepresentative and so on might require to be fixed if the TimeRepresentatives table was changed."); - static const Representative constexpr * SecondRepresentative = &TimeRepresentatives[0]; - static const Representative constexpr * MinuteRepresentative = &TimeRepresentatives[1]; - static const Representative constexpr * HourRepresentative = &TimeRepresentatives[2]; - static const Representative constexpr * DayRepresentative = &TimeRepresentatives[3]; - static const Representative constexpr * MonthRepresentative = &TimeRepresentatives[5]; - static const Representative constexpr * YearRepresentative = &TimeRepresentatives[6]; - static const Representative constexpr * MeterRepresentative = &DistanceRepresentatives[0]; - static const Representative constexpr * KilogramRepresentative = &MassRepresentatives[0]; - static const Representative constexpr * LiterRepresentative = &VolumeRepresentatives[0]; - static const Representative constexpr * WattRepresentative = &PowerRepresentatives[0]; - static_assert(sizeof(EnergyRepresentatives)/sizeof(Representative) == 2, "The Unit::ElectronVoltRepresentative might require to be fixed if the EnergyRepresentatives table was changed."); - static const Representative constexpr * ElectronVoltRepresentative = &EnergyRepresentatives[1]; - static constexpr const Dimension DimensionTable[] = { - /* The current table is sorted from most to least simple units. - * The order determines the behavior of simplification. - */ - Dimension( - Dimension::Vector { - .time = 1, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - TimeRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 1, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - DistanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - MassRepresentatives, - &KiloPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - CurrentRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 1, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - TemperatureRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 1, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - AmountOfSubstanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 1, - .solidAngle = 0, - }, - LuminousIntensityRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 1, - }, - SolideAngleRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-1, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - FrequencyRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 1, - .solidAngle = 1, - }, - LuminousFluxRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance =-2, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 1, - .solidAngle = 1, - }, - IlluminanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 1, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ForceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance =-1, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - PressureRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - EnergyRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - PowerRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 1, - .distance = 0, - .mass = 0, - .current = 1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ElectricChargeRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ElectricPotentialRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 4, - .distance =-2, - .mass =-1, - .current = 2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ElectricCapacitanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current =-2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ElectricResistanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 3, - .distance =-2, - .mass =-1, - .current = 2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - ElectricConductanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - MagneticFluxRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 0, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - MagneticFieldRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current =-2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - InductanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-1, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 1, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - CatalyticActivityRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 2, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - SurfaceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 3, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - .solidAngle = 0, - }, - VolumeRepresentatives, - &EmptyPrefix - ), + static constexpr const Prefix k_prefixes[Prefix::k_numberOfPrefixes] = { + Prefix("p", -12), + Prefix("n", -9), + Prefix("μ", -6), + Prefix("m", -3), + Prefix("c", -2), + Prefix("d", -1), + Prefix("", 0), + Prefix("da", 1), + Prefix("h", 2), + Prefix("k", 3), + Prefix("M", 6), + Prefix("G", 9), + Prefix("T", 12), }; - // TODO: find a better way to find defines these pointers - static_assert(sizeof(DimensionTable)/sizeof(Dimension) == 26, "The Unit::TimeDimension, Unit::DistanceDimension and so on might require to be fixed if the Dimension table was changed."); - static const Dimension constexpr * TimeDimension = &DimensionTable[0] ; - static const Dimension constexpr * DistanceDimension = &DimensionTable[1]; - static const Dimension constexpr * MassDimension = &DimensionTable[2]; - static const Dimension constexpr * EnergyDimension = &DimensionTable[13]; - static const Dimension constexpr * PowerDimension = &DimensionTable[14]; - static const Dimension constexpr * VolumeDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-1]; + typedef UnitNode::Representative Representative; + typedef Representative::Prefixable Prefixable; + typedef UnitNode::TimeRepresentative TimeRepresentative; + static constexpr const TimeRepresentative k_timeRepresentatives[] = { + TimeRepresentative("s", 1., Prefixable::All, Prefixable::NegativeLongScale), + TimeRepresentative("min", 60., Prefixable::None, Prefixable::None), + TimeRepresentative("h", 3600., Prefixable::None, Prefixable::None), + TimeRepresentative("day", 24.*3600., Prefixable::None, Prefixable::None), + TimeRepresentative("week", 7.*24.*3600., Prefixable::None, Prefixable::None), + TimeRepresentative("month", 365.25/12.*24.*3600., Prefixable::None, Prefixable::None), + TimeRepresentative("year", 365.25*24.*3600., Prefixable::None, Prefixable::None), + }; + typedef UnitNode::DistanceRepresentative DistanceRepresentative; + static constexpr const DistanceRepresentative k_distanceRepresentatives[] = { + DistanceRepresentative("m", 1., Prefixable::All, Prefixable::NegativeAndKilo), + DistanceRepresentative("au", 149597870700., Prefixable::None, Prefixable::None), + DistanceRepresentative("ly", 299792458.*365.25*24.*3600., Prefixable::None, Prefixable::None), + DistanceRepresentative("pc", 180.*3600./M_PI*149587870700., Prefixable::None, Prefixable::None), + DistanceRepresentative("in", 0.0254, Prefixable::None, Prefixable::None), + DistanceRepresentative("ft", 12*0.0254, Prefixable::None, Prefixable::None), + DistanceRepresentative("yd", 3*12*0.0254, Prefixable::None, Prefixable::None), + DistanceRepresentative("mi", 1760*3*12*0.0254, Prefixable::None, Prefixable::None), + }; + typedef UnitNode::MassRepresentative MassRepresentative; + static constexpr const MassRepresentative k_massRepresentatives[] = { + MassRepresentative("g", 1., Prefixable::All, Prefixable::NegativeAndKilo), + MassRepresentative("t", 1e3, Prefixable::PositiveLongScale, Prefixable::PositiveLongScale), + MassRepresentative("Da", 1/(6.02214076e26), Prefixable::All, Prefixable::All), + MassRepresentative("oz", 0.028349523125, Prefixable::None, Prefixable::None), + MassRepresentative("lb", 16*0.028349523125, Prefixable::None, Prefixable::None), + MassRepresentative("shtn", 2000*16*0.028349523125, Prefixable::None, Prefixable::None), + MassRepresentative("lgtn", 2240*16*0.028349523125, Prefixable::None, Prefixable::None), + }; + typedef UnitNode::CurrentRepresentative CurrentRepresentative; + static constexpr const CurrentRepresentative k_currentRepresentatives[] = { CurrentRepresentative("A", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::TemperatureRepresentative TemperatureRepresentative; + static constexpr const TemperatureRepresentative k_temperatureRepresentatives[] = { + TemperatureRepresentative("K", 1., Prefixable::All, Prefixable::None), + TemperatureRepresentative("°C", 1., Prefixable::None, Prefixable::None), + TemperatureRepresentative("°F", 5./9., Prefixable::None, Prefixable::None), + }; + typedef UnitNode::AmountOfSubstanceRepresentative AmountOfSubstanceRepresentative; + static constexpr const AmountOfSubstanceRepresentative k_amountOfSubstanceRepresentatives[] = { AmountOfSubstanceRepresentative("mol", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::LuminousIntensityRepresentative LuminousIntensityRepresentative; + static constexpr const LuminousIntensityRepresentative k_luminousIntensityRepresentatives[] = { LuminousIntensityRepresentative("cd", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::FrequencyRepresentative FrequencyRepresentative; + static constexpr const FrequencyRepresentative k_frequencyRepresentatives[] = { FrequencyRepresentative("Hz", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ForceRepresentative ForceRepresentative; + static constexpr const ForceRepresentative k_forceRepresentatives[] = { ForceRepresentative("N", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::PressureRepresentative PressureRepresentative; + static constexpr const PressureRepresentative k_pressureRepresentatives[] = { + PressureRepresentative("Pa", 1., Prefixable::All, Prefixable::LongScale), + PressureRepresentative("bar", 100000, Prefixable::All, Prefixable::LongScale), + PressureRepresentative("atm", 101325, Prefixable::None, Prefixable::None), + }; + typedef UnitNode::EnergyRepresentative EnergyRepresentative; + static constexpr const EnergyRepresentative k_energyRepresentatives[] = { + EnergyRepresentative("J", 1., Prefixable::All, Prefixable::LongScale), + EnergyRepresentative("eV", 1.602176634e-19, Prefixable::All, Prefixable::LongScale), + }; + typedef UnitNode::PowerRepresentative PowerRepresentative; + static constexpr const PowerRepresentative k_powerRepresentatives[] = { PowerRepresentative("W", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ElectricChargeRepresentative ElectricChargeRepresentative; + static constexpr const ElectricChargeRepresentative k_electricChargeRepresentatives[] = { ElectricChargeRepresentative("C", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ElectricPotentialRepresentative ElectricPotentialRepresentative; + static constexpr const ElectricPotentialRepresentative k_electricPotentialRepresentatives[] = { ElectricPotentialRepresentative("V", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ElectricCapacitanceRepresentative ElectricCapacitanceRepresentative; + static constexpr const ElectricCapacitanceRepresentative k_electricCapacitanceRepresentatives[] = { ElectricCapacitanceRepresentative("F", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ElectricResistanceRepresentative ElectricResistanceRepresentative; + static constexpr const ElectricResistanceRepresentative k_electricResistanceRepresentatives[] = { ElectricResistanceRepresentative("Ω", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::ElectricConductanceRepresentative ElectricConductanceRepresentative; + static constexpr const ElectricConductanceRepresentative k_electricConductanceRepresentatives[] = { ElectricConductanceRepresentative("S", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::MagneticFluxRepresentative MagneticFluxRepresentative; + static constexpr const MagneticFluxRepresentative k_magneticFluxRepresentatives[] = { MagneticFluxRepresentative("Wb", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::MagneticFieldRepresentative MagneticFieldRepresentative; + static constexpr const MagneticFieldRepresentative k_magneticFieldRepresentatives[] = { MagneticFieldRepresentative("T", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::InductanceRepresentative InductanceRepresentative; + static constexpr const InductanceRepresentative k_inductanceRepresentatives[] = { InductanceRepresentative("H", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::CatalyticActivityRepresentative CatalyticActivityRepresentative; + static constexpr const CatalyticActivityRepresentative k_catalyticActivityRepresentatives[] = { CatalyticActivityRepresentative("kat", 1., Prefixable::All, Prefixable::LongScale) }; + typedef UnitNode::SurfaceRepresentative SurfaceRepresentative; + static constexpr const SurfaceRepresentative k_surfaceRepresentatives[] = { + SurfaceRepresentative("ha", 10000., Prefixable::None, Prefixable::None), + SurfaceRepresentative("acre", 4046.8564224, Prefixable::None, Prefixable::None), + }; + typedef UnitNode::VolumeRepresentative VolumeRepresentative; + static constexpr const VolumeRepresentative k_volumeRepresentatives[] = { + VolumeRepresentative("L", 0.001, Prefixable::All, Prefixable::Negative), + VolumeRepresentative("tsp", 0.00000492892159375, Prefixable::None, Prefixable::None), + VolumeRepresentative("tbsp", 3*0.00000492892159375, Prefixable::None, Prefixable::None), + VolumeRepresentative("floz", 0.0000295735295625, Prefixable::None, Prefixable::None), + VolumeRepresentative("cup", 8*0.0000295735295625, Prefixable::None, Prefixable::None), + VolumeRepresentative("pt", 16*0.0000295735295625, Prefixable::None, Prefixable::None), + VolumeRepresentative("qt", 32*0.0000295735295625, Prefixable::None, Prefixable::None), + VolumeRepresentative("gal", 128*0.0000295735295625, Prefixable::None, Prefixable::None), + }; + /* FIXME : Some ratio are too precise too be well approximated by double. + * Maybe switch to a rationnal representation with two int. */ - static constexpr const Unit::Dimension * DimensionTableUpperBound = - DimensionTable + sizeof(DimensionTable)/sizeof(Dimension); - static bool CanParse(const char * symbol, size_t length, - const Dimension * * dimension, const Representative * * representative, const Prefix * * prefix); + /* Define access points to some prefixes and representatives. */ + static constexpr int k_emptyPrefixIndex = 6; + static_assert(k_prefixes[k_emptyPrefixIndex].m_exponent == 0, "Index for the Empty Prefix is incorrect."); + static constexpr int k_kiloPrefixIndex = 9; + static_assert(k_prefixes[k_kiloPrefixIndex].m_exponent == 3, "Index for the Kilo Prefix is incorrect."); + static constexpr int k_secondRepresentativeIndex = 0; + static_assert(strings_equal(k_timeRepresentatives[k_secondRepresentativeIndex].m_rootSymbol, "s"), "Index for the Second Representative is incorrect."); + static constexpr int k_minuteRepresentativeIndex = 1; + static_assert(strings_equal(k_timeRepresentatives[k_minuteRepresentativeIndex].m_rootSymbol, "min"), "Index for the Minute Representative is incorrect."); + static constexpr int k_hourRepresentativeIndex = 2; + static_assert(strings_equal(k_timeRepresentatives[k_hourRepresentativeIndex].m_rootSymbol, "h"), "Index for the Hour Representative is incorrect."); + static constexpr int k_dayRepresentativeIndex = 3; + static_assert(strings_equal(k_timeRepresentatives[k_dayRepresentativeIndex].m_rootSymbol, "day"), "Index for the Day Representative is incorrect."); + static constexpr int k_monthRepresentativeIndex = 5; + static_assert(strings_equal(k_timeRepresentatives[k_monthRepresentativeIndex].m_rootSymbol, "month"), "Index for the Month Representative is incorrect."); + static constexpr int k_yearRepresentativeIndex = 6; + static_assert(strings_equal(k_timeRepresentatives[k_yearRepresentativeIndex].m_rootSymbol, "year"), "Index for the Year Representative is incorrect."); + static constexpr int k_meterRepresentativeIndex = 0; + static_assert(strings_equal(k_distanceRepresentatives[k_meterRepresentativeIndex].m_rootSymbol, "m"), "Index for the Meter Representative is incorrect."); + static constexpr int k_inchRepresentativeIndex = 4; + static_assert(strings_equal(k_distanceRepresentatives[k_inchRepresentativeIndex].m_rootSymbol, "in"), "Index for the Inch Representative is incorrect."); + static constexpr int k_footRepresentativeIndex = 5; + static_assert(strings_equal(k_distanceRepresentatives[k_footRepresentativeIndex].m_rootSymbol, "ft"), "Index for the Foot Representative is incorrect."); + static constexpr int k_yardRepresentativeIndex = 6; + static_assert(strings_equal(k_distanceRepresentatives[k_yardRepresentativeIndex].m_rootSymbol, "yd"), "Index for the Yard Representative is incorrect."); + static constexpr int k_mileRepresentativeIndex = 7; + static_assert(strings_equal(k_distanceRepresentatives[k_mileRepresentativeIndex].m_rootSymbol, "mi"), "Index for the Mile Representative is incorrect."); + static constexpr int k_ounceRepresentativeIndex = 3; + static_assert(strings_equal(k_massRepresentatives[k_ounceRepresentativeIndex].m_rootSymbol, "oz"), "Index for the Ounce Representative is incorrect."); + static constexpr int k_poundRepresentativeIndex = 4; + static_assert(strings_equal(k_massRepresentatives[k_poundRepresentativeIndex].m_rootSymbol, "lb"), "Index for the Pound Representative is incorrect."); + static constexpr int k_shortTonRepresentativeIndex = 5; + static_assert(strings_equal(k_massRepresentatives[k_shortTonRepresentativeIndex].m_rootSymbol, "shtn"), "Index for the Short Ton Representative is incorrect."); + static constexpr int k_kelvinRepresentativeIndex = 0; + static_assert(strings_equal(k_temperatureRepresentatives[k_kelvinRepresentativeIndex].m_rootSymbol, "K"), "Index for the Kelvin Representative is incorrect."); + static constexpr int k_celsiusRepresentativeIndex = 1; + static_assert(strings_equal(k_temperatureRepresentatives[k_celsiusRepresentativeIndex].m_rootSymbol, "°C"), "Index for the Celsius Representative is incorrect."); + static constexpr int k_fahrenheitRepresentativeIndex = 2; + static_assert(strings_equal(k_temperatureRepresentatives[k_fahrenheitRepresentativeIndex].m_rootSymbol, "°F"), "Index for the Fahrenheit Representative is incorrect."); + static constexpr int k_jouleRepresentativeIndex = 0; + static_assert(strings_equal(k_energyRepresentatives[k_jouleRepresentativeIndex].m_rootSymbol, "J"), "Index for the Joule Representative is incorrect."); + static constexpr int k_electronVoltRepresentativeIndex = 1; + static_assert(strings_equal(k_energyRepresentatives[k_electronVoltRepresentativeIndex].m_rootSymbol, "eV"), "Index for the Electron Volt Representative is incorrect."); + static constexpr int k_wattRepresentativeIndex = 0; + static_assert(strings_equal(k_powerRepresentatives[k_wattRepresentativeIndex].m_rootSymbol, "W"), "Index for the Watt Representative is incorrect."); + static constexpr int k_hectareRepresentativeIndex = 0; + static_assert(strings_equal(k_surfaceRepresentatives[k_hectareRepresentativeIndex].m_rootSymbol, "ha"), "Index for the Hectare Representative is incorrect."); + static constexpr int k_acreRepresentativeIndex = 1; + static_assert(strings_equal(k_surfaceRepresentatives[k_acreRepresentativeIndex].m_rootSymbol, "acre"), "Index for the Acre Representative is incorrect."); + static constexpr int k_literRepresentativeIndex = 0; + static_assert(strings_equal(k_volumeRepresentatives[k_literRepresentativeIndex].m_rootSymbol, "L"), "Index for the Liter Representative is incorrect."); + static constexpr int k_cupRepresentativeIndex = 4; + static_assert(strings_equal(k_volumeRepresentatives[k_cupRepresentativeIndex].m_rootSymbol, "cup"), "Index for the Cup Representative is incorrect."); + static constexpr int k_pintRepresentativeIndex = 5; + static_assert(strings_equal(k_volumeRepresentatives[k_pintRepresentativeIndex].m_rootSymbol, "pt"), "Index for the Pint Representative is incorrect."); + static constexpr int k_quartRepresentativeIndex = 6; + static_assert(strings_equal(k_volumeRepresentatives[k_quartRepresentativeIndex].m_rootSymbol, "qt"), "Index for the Quart Representative is incorrect."); + static constexpr int k_gallonRepresentativeIndex = 7; + static_assert(strings_equal(k_volumeRepresentatives[k_gallonRepresentativeIndex].m_rootSymbol, "gal"), "Index for the Gallon Representative is incorrect."); Unit(const UnitNode * node) : Expression(node) {} - static Unit Builder(const Dimension * dimension, const Representative * representative, const Prefix * prefix); - static Unit Kilometer() { return Builder(DistanceDimension, MeterRepresentative, &KiloPrefix); } - static Unit Second() { return Builder(TimeDimension, SecondRepresentative, &EmptyPrefix); } - static Unit Minute() { return Builder(TimeDimension, MinuteRepresentative, &EmptyPrefix); } - static Unit Hour() { return Builder(TimeDimension, HourRepresentative, &EmptyPrefix); } - static Unit Day() { return Builder(TimeDimension, DayRepresentative, &EmptyPrefix); } - static Unit Month() { return Builder(TimeDimension, MonthRepresentative, &EmptyPrefix); } - static Unit Year() { return Builder(TimeDimension, YearRepresentative, &EmptyPrefix); } - static Unit Liter() { return Builder(VolumeDimension, LiterRepresentative, &EmptyPrefix); } - static Unit ElectronVolt() { return Builder(EnergyDimension, ElectronVoltRepresentative, &EmptyPrefix); } - static Unit Watt() { return Builder(PowerDimension, WattRepresentative, &EmptyPrefix); } - static Expression BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - - static bool IsSI(Expression & e); - static bool IsSISpeed(Expression & e); - static bool IsSIVolume(Expression & e); - static bool IsSIEnergy(Expression & e); - static bool IsSITime(Expression & e); - bool isMeter() const; - bool isSecond() const; - bool isKilogram() const; + static Unit Builder(const Representative * representative, const Prefix * prefix); + static bool CanParse(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix); + static void ChooseBestRepresentativeAndPrefixForValue(Expression units, double * value, ExpressionNode::ReductionContext reductionContext); + static bool ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat); + static int SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext); + static Expression BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext); + static Expression ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - static void ChooseBestRepresentativeAndPrefixForValue(Expression * units, double * value, ExpressionNode::ReductionContext reductionContext) { return ChooseBestMultipleForValue(units, value, true, reductionContext); } - static void ChooseBestPrefixForValue(Expression * units, double * value, ExpressionNode::ReductionContext reductionContext) { return ChooseBestMultipleForValue(units, value, false, reductionContext); } + Expression shallowBeautify(); + + bool isBaseUnit() const { return node()->representative()->isBaseUnit() && node()->prefix() == node()->representative()->basePrefix(); } + void chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix); + + const Representative * representative() const { return node()->representative(); } - // This could be computed from the time representatives but we save time by using constexpr double - static constexpr double SecondsPerMinute = 60.0; private: - static constexpr double MinutesPerHour = 60.0; - static constexpr double HoursPerDay = 24.0; - static constexpr double DaysPerYear = 365.25; - static constexpr double MonthPerYear = 12.0; - static constexpr double DaysPerMonth = DaysPerYear/MonthPerYear; UnitNode * node() const { return static_cast(Expression::node()); } - bool isSI() const; - static void ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); - void chooseBestMultipleForValue(double * value, const int exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); Expression removeUnit(Expression * unit); }; diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index 73379fb1c..c97eb3cdf 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -23,11 +23,12 @@ private: Expression removeUnit(Expression * unit) override; // Simplification void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override; - Expression shallowBeautify(ReductionContext reductionContext) override; + void deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext * reductionContext) 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; + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; }; class UnitConvert final : public Expression { @@ -38,7 +39,8 @@ public: // Expression void deepReduceChildren(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + void deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); private: UnitConvertNode * node() const { return static_cast(Expression::node()); } }; diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index 4b31ef182..d53facec4 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -20,13 +20,17 @@ public: Type type() const override { return Type::Unreal; } // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } + /* Derivation + * Unlike Numbers that derivate to 0, Unreal derivates to Unreal. */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return true; } + // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; diff --git a/poincare/include/poincare/variable_context.h b/poincare/include/poincare/variable_context.h index 06d9f7992..19b413607 100644 --- a/poincare/include/poincare/variable_context.h +++ b/poincare/include/poincare/variable_context.h @@ -18,7 +18,7 @@ public: // Context void setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) override; - const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) override; + const Expression expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; private: const char * m_name; diff --git a/poincare/include/poincare/vector_cross.h b/poincare/include/poincare/vector_cross.h new file mode 100644 index 000000000..8969bd8b8 --- /dev/null +++ b/poincare/include/poincare/vector_cross.h @@ -0,0 +1,48 @@ +#ifndef POINCARE_VECTOR_CROSS_H +#define POINCARE_VECTOR_CROSS_H + +#include + +namespace Poincare { + +class VectorCrossNode final : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(VectorCrossNode); } + int numberOfChildren() const override; +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "VectorCross"; + } +#endif + + // Properties + Type type() const override { return Type::VectorCross; } +private: + // Layout + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Simplification + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; + LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; +}; + +class VectorCross final : public Expression { +public: + VectorCross(const VectorCrossNode * n) : Expression(n) {} + static VectorCross Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder({child0, child1}); } + + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("cross", 2, &UntypedBuilderTwoChildren); + + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); +}; + +} + +#endif diff --git a/poincare/include/poincare/vector_dot.h b/poincare/include/poincare/vector_dot.h new file mode 100644 index 000000000..25c1aa670 --- /dev/null +++ b/poincare/include/poincare/vector_dot.h @@ -0,0 +1,48 @@ +#ifndef POINCARE_VECTOR_DOT_H +#define POINCARE_VECTOR_DOT_H + +#include + +namespace Poincare { + +class VectorDotNode final : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(VectorDotNode); } + int numberOfChildren() const override; +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "VectorDot"; + } +#endif + + // Properties + Type type() const override { return Type::VectorDot; } +private: + // Layout + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Simplification + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; + LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; +}; + +class VectorDot final : public Expression { +public: + VectorDot(const VectorDotNode * n) : Expression(n) {} + static VectorDot Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder({child0, child1}); } + + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("dot", 2, &UntypedBuilderTwoChildren); + + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); +}; + +} + +#endif diff --git a/poincare/include/poincare/vector_norm.h b/poincare/include/poincare/vector_norm.h new file mode 100644 index 000000000..10eae04f3 --- /dev/null +++ b/poincare/include/poincare/vector_norm.h @@ -0,0 +1,48 @@ +#ifndef POINCARE_VECTOR_NORM_H +#define POINCARE_VECTOR_NORM_H + +#include + +namespace Poincare { + +class VectorNormNode final : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(VectorNormNode); } + int numberOfChildren() const override; +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "VectorNorm"; + } +#endif + + // Properties + Sign sign(Context * context) const override { return Sign::Positive; } + Type type() const override { return Type::VectorNorm; } +private: + // Layout + Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + // Simplification + Expression shallowReduce(ReductionContext reductionContext) override; + LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Evaluation + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; +}; + +class VectorNorm final : public Expression { +public: + VectorNorm(const VectorNormNode * n) : Expression(n) {} + static VectorNorm Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("norm", 1, &UntypedBuilderOneChild); + + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); +}; + +} + +#endif diff --git a/poincare/include/poincare/vector_norm_layout.h b/poincare/include/poincare/vector_norm_layout.h new file mode 100644 index 000000000..e4249a3a0 --- /dev/null +++ b/poincare/include/poincare/vector_norm_layout.h @@ -0,0 +1,47 @@ +#ifndef POINCARE_VECTOR_NORM_LAYOUT_NODE_H +#define POINCARE_VECTOR_NORM_LAYOUT_NODE_H + +#include +#include +#include + +namespace Poincare { + +class VectorNormLayoutNode final : public BracketPairLayoutNode { +public: + using BracketPairLayoutNode::BracketPairLayoutNode; + + // Layout + Type type() const override { return Type::VectorNormLayout; } + + // SerializationHelperInterface + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, VectorNorm::s_functionHelper.name(), true); + } + + // TreeNode + size_t size() const override { return sizeof(VectorNormLayoutNode); } +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "VectorNormLayout"; + } +#endif + +private: + KDCoordinate widthMargin() const override { return 2; } + KDCoordinate verticalMargin() const override { return 0; } + KDCoordinate verticalExternMargin() const override { return 1; } + bool renderTopBar() const override { return false; } + bool renderBottomBar() const override { return false; } + bool renderDoubleBar() const override { return true; } +}; + +class VectorNormLayout final : public Layout { +public: + static VectorNormLayout Builder(Layout child) { return TreeHandle::FixedArityBuilder({child}); } + VectorNormLayout() = delete; +}; + +} + +#endif diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h new file mode 100644 index 000000000..56bd45871 --- /dev/null +++ b/poincare/include/poincare/zoom.h @@ -0,0 +1,85 @@ +#ifndef POINCARE_ZOOM_H +#define POINCARE_ZOOM_H + +#include +#include + +/* FIXME : This class is concerned with manipulating the ranges of graphing + * window, often represented by four float xMin, xMax, yMin, yMax. Handling + * those same four values has proven repetititve, tredious, and prone to error. + * This code could benefit from a data structure to regroup those values in a + * single object. */ + +namespace Poincare { + +class Zoom { +public: + static constexpr float k_defaultHalfRange = 10.f; + static constexpr float k_smallUnitMantissa = 1.f; + static constexpr float k_mediumUnitMantissa = 2.f; + static constexpr float k_largeUnitMantissa = 5.f; + static constexpr float k_minimalRangeLength = 1e-4f; + + typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); + + /* Find the most suitable window to display the function's points of + * interest. Return false if the X range was given a default value because + * there were no points of interest. */ + static bool InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary); + /* Find the best Y range to display the function on [xMin, xMax], but crop + * the values that are outside of the function's order of magnitude. */ + static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary); + /* Find the best window to display functions, with a specified ratio + * between X and Y. Usually used to find the most fitting orthonormal range. */ + static void RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary); + static void FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float tStep, float * fMin, float * fMax, Context * context, const void * auxiliary); + + /* Find the bounding box of the given ranges. */ + static void CombineRanges(int length, const float * mins, const float * maxs, float * minRes, float * maxRes); + /* Ensures that the window is fit for display, with all bounds being proper + * numbers, with min < max. */ + static void SanitizeRange(float * xMin, float * xMax, float * yMin, float * yMax, float normalRatio); + + /* If shrink is false, the range will be set to ratio by increasing the size + * of the smallest axis. If it is true, the longest axis will be reduced.*/ + static void SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, bool shrink = false); + static void SetZoom(float ratio, float xCenter, float yCenter, float * xMin, float * xMax, float * yMin, float * yMax); + +private: + static constexpr int k_peakNumberOfPointsOfInterest = 3; + static constexpr int k_sampleSize = Ion::Display::Width / 4; + static constexpr float k_maximalDistance = 1e5f; + static constexpr float k_minimalDistance = 1e-2f; + static constexpr float k_asymptoteThreshold = 2e-1f; + static constexpr float k_explosionThreshold = 1e1f; + static constexpr float k_stepFactor = 1.09f; + static constexpr float k_breathingRoom = 0.3f; + static constexpr float k_forceXAxisThreshold = 0.2f; + static constexpr float k_maxRatioBetweenPointsOfInterest = 100.f; + + enum class PointOfInterest : uint8_t { + None, + Bound, + Extremum, + Root + }; + + /* TODO : These methods perform checks that will also be relevant for the + * equation solver. Remember to factorize this code when integrating the new + * solver. */ + static bool BoundOfIntervalOfDefinitionIsReached(float y1, float y2) { return std::isfinite(y1) && !std::isinf(y2) && std::isnan(y2); } + static bool RootExistsOnInterval(float y1, float y2) { return ((y1 < 0.f && y2 > 0.f) || (y1 > 0.f && y2 < 0.f)); } + static bool ExtremumExistsOnInterval(float y1, float y2, float y3) { return (y1 < y2 && y2 > y3) || (y1 > y2 && y2 < y3); } + /* IsConvexAroundExtremum checks whether an interval contains an extremum or + * an asymptote, by recursively computing the slopes. In case of an extremum, + * the slope should taper off toward the center. */ + static bool IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, const void * auxiliary, int iterations = 3); + /* If the function is discontinuous between its points of interest, there + * might be a lot of empty space in the middle of the screen. In that case, + * we want to zoom out to see more of the graph. */ + static void ExpandSparseWindow(float * sample, int length, float * xMin, float * xMax, float * yMin, float * yMax); +}; + +} + +#endif diff --git a/poincare/include/poincare_layouts.h b/poincare/include/poincare_layouts.h index 071880507..72cb49ac6 100644 --- a/poincare/include/poincare_layouts.h +++ b/poincare/include/poincare_layouts.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #endif diff --git a/poincare/include/poincare_nodes.h b/poincare/include/poincare_nodes.h index 33076f227..c0d3a853e 100644 --- a/poincare/include/poincare_nodes.h +++ b/poincare/include/poincare_nodes.h @@ -54,6 +54,8 @@ #include #include #include +#include +#include #include #include #include @@ -74,6 +76,7 @@ #include #include #include +#include #include #include #include @@ -87,5 +90,8 @@ #include #include #include +#include +#include +#include #endif diff --git a/poincare/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index 6945e1fc5..a6e112232 100644 --- a/poincare/src/absolute_value.cpp +++ b/poincare/src/absolute_value.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -31,6 +33,10 @@ Expression AbsoluteValueNode::shallowReduce(ReductionContext reductionContext) { return AbsoluteValue(this).shallowReduce(reductionContext); } +bool AbsoluteValueNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return AbsoluteValue(this).derivate(reductionContext, symbol, symbolValue); +} + Expression AbsoluteValue::shallowReduce(ExpressionNode::ReductionContext reductionContext) { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -46,7 +52,7 @@ Expression AbsoluteValue::shallowReduce(ExpressionNode::ReductionContext reducti } // |x| = ±x if x is real if (c.isReal(reductionContext.context())) { - double app = c.node()->approximate(double(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()).toScalar(); + double app = c.node()->approximate(double(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); if (!std::isnan(app)) { if ((c.isNumber() && app >= 0) || app >= Expression::Epsilon()) { /* abs(a) = a with a >= 0 @@ -113,4 +119,18 @@ Expression AbsoluteValue::shallowReduce(ExpressionNode::ReductionContext reducti return *this; } +// Derivate of |f(x)| is f'(x)*sg(x) (and undef in 0) = f'(x)*(f(x)/|f(x)|) +bool AbsoluteValue::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Expression f = childAtIndex(0); + Multiplication result = Multiplication::Builder(); + result.addChildAtIndexInPlace(Derivative::Builder(f.clone(), + symbol.clone().convert(), + symbolValue.clone() + ),0,0); + result.addChildAtIndexInPlace(f.clone(),1,1); + replaceWithInPlace(result); + result.addChildAtIndexInPlace(Power::Builder(*this,Rational::Builder(-1)),2,2); + return true; +} + } diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index 0eaf6aa94..5352cd851 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -45,10 +46,15 @@ Expression AdditionNode::shallowReduce(ReductionContext reductionContext) { return Addition(this).shallowReduce(reductionContext); } -Expression AdditionNode::shallowBeautify(ReductionContext reductionContext) { +Expression AdditionNode::shallowBeautify(ReductionContext * reductionContext) { return Addition(this).shallowBeautify(reductionContext); } +// Derivation +bool AdditionNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Addition(this).derivate(reductionContext, symbol, symbolValue); +} + // Addition const Number Addition::NumeralFactor(const Expression & e) { @@ -78,7 +84,7 @@ int Addition::getPolynomialCoefficients(Context * context, const char * symbolNa return deg; } -Expression Addition::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression Addition::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { /* Beautifying AdditionNode essentially consists in adding Subtractions if * needed. * In practice, we want to turn "a+(-1)*b" into "a-b". Or, more precisely, any @@ -92,12 +98,12 @@ Expression Addition::shallowBeautify(ExpressionNode::ReductionContext reductionC /* Sort children in decreasing order: * 1+x+x^2 --> x^2+x+1 * 1+R(2) --> R(2)+1 */ - sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, false, canBeInterrupted); }, reductionContext.context(), true); + sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, false, canBeInterrupted); }, reductionContext->context(), true); int nbChildren = numberOfChildren(); for (int i = 0; i < nbChildren; i++) { // Try to make the child i positive if any negative numeral factor is found - Expression subtractant = childAtIndex(i).makePositiveAnyNegativeNumeralFactor(reductionContext); + Expression subtractant = childAtIndex(i).makePositiveAnyNegativeNumeralFactor(*reductionContext); if (subtractant.isUninitialized()) { // if subtractant is not initialized, it means the child i had no negative numeral factor @@ -124,7 +130,6 @@ Expression Addition::shallowBeautify(ExpressionNode::ReductionContext reductionC } Expression result = squashUnaryHierarchyInPlace(); - return result; } @@ -308,7 +313,7 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon newComplexCartesian.replaceChildAtIndexInPlace(1, imag); real.shallowReduce(reductionContext); imag.shallowReduce(reductionContext); - return newComplexCartesian.shallowReduce(); + return newComplexCartesian.shallowReduce(reductionContext); } /* Step 9: Let's put everything under a common denominator. @@ -322,6 +327,13 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon return result; } +bool Addition::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + for (int i = 0; i < numberOfChildren(); i++) { + replaceChildAtIndexInPlace(i, Derivative::Builder(childAtIndex(i), symbol.clone().convert(), symbolValue.clone())); + } + return true; +} + int Addition::NumberOfNonNumeralFactors(const Expression & e) { if (e.type() != ExpressionNode::Type::Multiplication) { return 1; // Or (e->type() != Type::Rational); @@ -428,6 +440,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); diff --git a/poincare/src/approximation_helper.cpp b/poincare/src/approximation_helper.cpp index 30439ad9c..77b924eb4 100644 --- a/poincare/src/approximation_helper.cpp +++ b/poincare/src/approximation_helper.cpp @@ -27,8 +27,25 @@ template < typename T> T minimalNonNullMagnitudeOfParts(std::complex c) { static inline int absInt(int x) { return x < 0 ? -x : x; } -template int ApproximationHelper::PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { - Evaluation evaluation = expression->approximate(T(), context, complexFormat, angleUnit); +/* To prevent incorrect approximations, such as cos(1.5707963267949) = 0 + * we made the neglect threshold stricter. This way, the approximation is more + * selective. + * However, when ploting functions such as e^(i.pi+x), the float approximation + * fails by giving non-real results and therefore, the function appears "undef". + * As a result we created two functions Epsilon that behave differently + * according to the number's type. When it is a double we want maximal precision + * -> precision_double = 1x10^(-15). + * When it is a float, we accept more agressive approximations + * -> precision_float = x10^(-6). */ + +template +T ApproximationHelper::Epsilon() { + static T precision = (sizeof(T) == sizeof(double)) ? 1E-15 : 1E-6f; + return precision; +} + +template int ApproximationHelper::PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, ExpressionNode::ApproximationContext approximationContext) { + Evaluation evaluation = expression->approximate(T(), approximationContext); T scalar = evaluation.toScalar(); if (std::isnan(scalar) || scalar != (int)scalar) { *isUndefined = true; @@ -52,7 +69,7 @@ template std::complex ApproximationHelper::NeglectRealOrImaginar } T magnitude1 = minimalNonNullMagnitudeOfParts(input1); T magnitude2 = minimalNonNullMagnitudeOfParts(input2); - T precision = ((T)10.0)*Expression::Epsilon(); + T precision = Epsilon(); if (isNegligeable(result.imag(), precision, magnitude1, magnitude2)) { result.imag(0); } @@ -62,41 +79,41 @@ template std::complex ApproximationHelper::NeglectRealOrImaginar return result; } -template Evaluation ApproximationHelper::Map(const ExpressionNode * expression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ComplexCompute compute) { +template Evaluation ApproximationHelper::Map(const ExpressionNode * expression, ExpressionNode::ApproximationContext approximationContext, ComplexCompute compute) { assert(expression->numberOfChildren() == 1); - Evaluation input = expression->childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation input = expression->childAtIndex(0)->approximate(T(), approximationContext); if (input.type() == EvaluationNode::Type::Complex) { - return compute(static_cast &>(input).stdComplex(), complexFormat, angleUnit); + return compute(static_cast &>(input).stdComplex(), approximationContext.complexFormat(), approximationContext.angleUnit()); } else { assert(input.type() == EvaluationNode::Type::MatrixComplex); MatrixComplex m = static_cast &>(input); MatrixComplex result = MatrixComplex::Builder(); for (int i = 0; i < m.numberOfChildren(); i++) { - result.addChildAtIndexInPlace(compute(m.complexAtIndex(i), complexFormat, angleUnit), i, i); + result.addChildAtIndexInPlace(compute(m.complexAtIndex(i), approximationContext.complexFormat(), approximationContext.angleUnit()), i, i); } result.setDimensions(m.numberOfRows(), m.numberOfColumns()); return std::move(result); } } -template Evaluation ApproximationHelper::MapReduce(const ExpressionNode * expression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ComplexAndComplexReduction computeOnComplexes, ComplexAndMatrixReduction computeOnComplexAndMatrix, MatrixAndComplexReduction computeOnMatrixAndComplex, MatrixAndMatrixReduction computeOnMatrices) { +template Evaluation ApproximationHelper::MapReduce(const ExpressionNode * expression, ExpressionNode::ApproximationContext approximationContext, ComplexAndComplexReduction computeOnComplexes, ComplexAndMatrixReduction computeOnComplexAndMatrix, MatrixAndComplexReduction computeOnMatrixAndComplex, MatrixAndMatrixReduction computeOnMatrices) { assert(expression->numberOfChildren() > 0); - Evaluation result = expression->childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation result = expression->childAtIndex(0)->approximate(T(), approximationContext); for (int i = 1; i < expression->numberOfChildren(); i++) { Evaluation intermediateResult; - Evaluation nextOperandEvaluation = expression->childAtIndex(i)->approximate(T(), context, complexFormat, angleUnit); + Evaluation nextOperandEvaluation = expression->childAtIndex(i)->approximate(T(), approximationContext); if (result.type() == EvaluationNode::Type::Complex && nextOperandEvaluation.type() == EvaluationNode::Type::Complex) { - intermediateResult = computeOnComplexes(static_cast &>(result).stdComplex(), static_cast &>(nextOperandEvaluation).stdComplex(), complexFormat); + intermediateResult = computeOnComplexes(static_cast &>(result).stdComplex(), static_cast &>(nextOperandEvaluation).stdComplex(), approximationContext.complexFormat()); } else if (result.type() == EvaluationNode::Type::Complex) { assert(nextOperandEvaluation.type() == EvaluationNode::Type::MatrixComplex); - intermediateResult = computeOnComplexAndMatrix(static_cast &>(result).stdComplex(), static_cast &>(nextOperandEvaluation), complexFormat); + intermediateResult = computeOnComplexAndMatrix(static_cast &>(result).stdComplex(), static_cast &>(nextOperandEvaluation), approximationContext.complexFormat()); } else if (nextOperandEvaluation.type() == EvaluationNode::Type::Complex) { assert(result.type() == EvaluationNode::Type::MatrixComplex); - intermediateResult = computeOnMatrixAndComplex(static_cast &>(result), static_cast &>(nextOperandEvaluation).stdComplex(), complexFormat); + intermediateResult = computeOnMatrixAndComplex(static_cast &>(result), static_cast &>(nextOperandEvaluation).stdComplex(), approximationContext.complexFormat()); } else { assert(result.node()->type() == EvaluationNode::Type::MatrixComplex); assert(nextOperandEvaluation.node()->type() == EvaluationNode::Type::MatrixComplex); - intermediateResult = computeOnMatrices(static_cast &>(result), static_cast &>(nextOperandEvaluation), complexFormat); + intermediateResult = computeOnMatrices(static_cast &>(result), static_cast &>(nextOperandEvaluation), approximationContext.complexFormat()); } result = intermediateResult; if (result.isUndefined()) { @@ -126,15 +143,16 @@ template MatrixComplex ApproximationHelper::ElementWiseOnComplexM matrix.setDimensions(m.numberOfRows(), m.numberOfColumns()); return matrix; } - -template int Poincare::ApproximationHelper::PositiveIntegerApproximationIfPossible(Poincare::ExpressionNode const*, bool*, Poincare::Context*, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit); -template int Poincare::ApproximationHelper::PositiveIntegerApproximationIfPossible(Poincare::ExpressionNode const*, bool*, Poincare::Context*, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit); +template float Poincare::ApproximationHelper::Epsilon(); +template double Poincare::ApproximationHelper::Epsilon(); +template int Poincare::ApproximationHelper::PositiveIntegerApproximationIfPossible(Poincare::ExpressionNode const*, bool*, ExpressionNode::ApproximationContext); +template int Poincare::ApproximationHelper::PositiveIntegerApproximationIfPossible(Poincare::ExpressionNode const*, bool*, ExpressionNode::ApproximationContext); template std::complex Poincare::ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::complex,std::complex,std::complex,bool); template std::complex Poincare::ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::complex,std::complex,std::complex,bool); -template Poincare::Evaluation Poincare::ApproximationHelper::Map(const Poincare::ExpressionNode * expression, Poincare::Context * context, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ApproximationHelper::ComplexCompute compute); -template Poincare::Evaluation Poincare::ApproximationHelper::Map(const Poincare::ExpressionNode * expression, Poincare::Context * context, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ApproximationHelper::ComplexCompute compute); -template Poincare::Evaluation Poincare::ApproximationHelper::MapReduce(const Poincare::ExpressionNode * expression, Poincare::Context * context, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ApproximationHelper::ComplexAndComplexReduction computeOnComplexes, Poincare::ApproximationHelper::ComplexAndMatrixReduction computeOnComplexAndMatrix, Poincare::ApproximationHelper::MatrixAndComplexReduction computeOnMatrixAndComplex, Poincare::ApproximationHelper::MatrixAndMatrixReduction computeOnMatrices); -template Poincare::Evaluation Poincare::ApproximationHelper::MapReduce(const Poincare::ExpressionNode * expression, Poincare::Context * context, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ApproximationHelper::ComplexAndComplexReduction computeOnComplexes, Poincare::ApproximationHelper::ComplexAndMatrixReduction computeOnComplexAndMatrix, Poincare::ApproximationHelper::MatrixAndComplexReduction computeOnMatrixAndComplex, Poincare::ApproximationHelper::MatrixAndMatrixReduction computeOnMatrices); +template Poincare::Evaluation Poincare::ApproximationHelper::Map(const Poincare::ExpressionNode * expression, ExpressionNode::ApproximationContext, Poincare::ApproximationHelper::ComplexCompute compute); +template Poincare::Evaluation Poincare::ApproximationHelper::Map(const Poincare::ExpressionNode * expression, ExpressionNode::ApproximationContext, Poincare::ApproximationHelper::ComplexCompute compute); +template Poincare::Evaluation Poincare::ApproximationHelper::MapReduce(const Poincare::ExpressionNode * expression, ExpressionNode::ApproximationContext, Poincare::ApproximationHelper::ComplexAndComplexReduction computeOnComplexes, Poincare::ApproximationHelper::ComplexAndMatrixReduction computeOnComplexAndMatrix, Poincare::ApproximationHelper::MatrixAndComplexReduction computeOnMatrixAndComplex, Poincare::ApproximationHelper::MatrixAndMatrixReduction computeOnMatrices); +template Poincare::Evaluation Poincare::ApproximationHelper::MapReduce(const Poincare::ExpressionNode * expression, ExpressionNode::ApproximationContext, Poincare::ApproximationHelper::ComplexAndComplexReduction computeOnComplexes, Poincare::ApproximationHelper::ComplexAndMatrixReduction computeOnComplexAndMatrix, Poincare::ApproximationHelper::MatrixAndComplexReduction computeOnMatrixAndComplex, Poincare::ApproximationHelper::MatrixAndMatrixReduction computeOnMatrices); template Poincare::MatrixComplex Poincare::ApproximationHelper::ElementWiseOnMatrixComplexAndComplex(const Poincare::MatrixComplex, const std::complex, Poincare::Preferences::ComplexFormat, Poincare::Complex (*)(std::complex, std::complex, Poincare::Preferences::ComplexFormat)); template Poincare::MatrixComplex Poincare::ApproximationHelper::ElementWiseOnMatrixComplexAndComplex(const Poincare::MatrixComplex, std::complex const, Poincare::Preferences::ComplexFormat, Poincare::Complex (*)(std::complex, std::complex, Poincare::Preferences::ComplexFormat)); template Poincare::MatrixComplex Poincare::ApproximationHelper::ElementWiseOnComplexMatrices(const Poincare::MatrixComplex, const Poincare::MatrixComplex, Poincare::Preferences::ComplexFormat, Poincare::Complex (*)(std::complex, std::complex, Poincare::Preferences::ComplexFormat)); diff --git a/poincare/src/arithmetic.cpp b/poincare/src/arithmetic.cpp index a7b50149b..79bbd7adf 100644 --- a/poincare/src/arithmetic.cpp +++ b/poincare/src/arithmetic.cpp @@ -1,31 +1,21 @@ #include #include +#include +#include namespace Poincare { -Integer Arithmetic::LCM(const Integer & a, const Integer & b) { - if (a.isZero() || b.isZero()) { - return Integer(0); - } - if (a.isEqualTo(b)) { - return a; - } - Integer signResult = Integer::Division(Integer::Multiplication(a, b), GCD(a, b)).quotient; - signResult.setNegative(false); - return signResult; -} - Integer Arithmetic::GCD(const Integer & a, const Integer & b) { if (a.isOverflow() || b.isOverflow()) { return Integer::Overflow(false); } - if (a.isEqualTo(b)) { - return a; - } Integer i = a; Integer j = b; i.setNegative(false); j.setNegative(false); + if (i.isEqualTo(j)) { + return i; + } do { if (i.isZero()) { return j; @@ -41,6 +31,113 @@ Integer Arithmetic::GCD(const Integer & a, const Integer & b) { } while(true); } +Integer Arithmetic::LCM(const Integer & a, const Integer & b) { + if (a.isZero() || b.isZero()) { + return Integer(0); + } + Integer i = a; + Integer j = b; + i.setNegative(false); + j.setNegative(false); + if (i.isEqualTo(j)) { + return i; + } + /* Using LCM(i,j) = i*(j/GCD(i,j)). Knowing that GCD(i, j) divides j, and that + * GCD(i,j) = 0 if and only if i == j == 0, which would have been escaped + * before. Division is performed before multiplication to be more efficient.*/ + return Integer::Multiplication(i, Integer::Division(j, GCD(i, j)).quotient); +} + +int Arithmetic::GCD(int a, int b) { + assert(a >= 0 && b >= 0); + if (b > a) { + int temp = b; + b = a; + a = temp; + } + int r = 0; + while(b!=0){ + r = a - (a/b)*b; + a = b; + b = r; + } + return a; +} + +int Arithmetic::LCM(int a, int b) { + assert(a >= 0 && b >= 0); + if (a * b == 0) { + return 0; + } + // Using LCM(a,b) = a * b / GCD(a,b) + return a * (b / GCD(a,b)); +} + +Integer getIntegerFromRationalExpression(Expression expression) { + // Expression must be a Rational with 1 as denominator. + assert(expression.type() == ExpressionNode::Type::Rational); + Rational r = static_cast(expression); + assert(r.isInteger()); + Integer i = r.signedIntegerNumerator(); + assert(!i.isOverflow()); + return i; +} + +Expression applyAssociativeFunctionOnChildren(const Expression & expression, Integer (*f)(const Integer &, const Integer &)) { + /* Use function associativity to compute a function of expression's children. + * The function can be GCD or LCM. The expression must have at least 1 + * child, and all its children must be integer Rationals. */ + // We define f(a) = f(a,a) = a + Integer result = getIntegerFromRationalExpression(expression.childAtIndex(0)); + // f is associative, f(a,b,c,d) = f(f(f(a,b),c),d) + for (int i = 1; i < expression.numberOfChildren(); ++i) { + result = f(result, getIntegerFromRationalExpression(expression.childAtIndex(i))); + } + return Rational::Builder(result); +} + +Expression Arithmetic::GCD(const Expression & expression) { + /* Compute GCD of expression's children. the expression must have at least 1 + * child, and all its children must be integer Rationals. */ + return applyAssociativeFunctionOnChildren(expression, Arithmetic::GCD); +} + +Expression Arithmetic::LCM(const Expression & expression) { + /* Compute LCM of expression's children. the expression must have at least 1 + * child, and all its children must be integer Rationals. */ + return applyAssociativeFunctionOnChildren(expression, Arithmetic::LCM); +} + +template +Evaluation applyAssociativeFunctionOnChildren(const ExpressionNode & expressionNode, int (*f)(int, int), ExpressionNode::ApproximationContext approximationContext) { + /* Use function associativity to compute a function of expression's children. + * The function can be GCD or LCM. */ + bool isUndefined = false; + // We define f(a) = f(a,a) = a + int a = ApproximationHelper::PositiveIntegerApproximationIfPossible(expressionNode.childAtIndex(0), &isUndefined, approximationContext); + // f is associative, f(a,b,c,d) = f(f(f(a,b),c),d) + for (int i = 1; i < expressionNode.numberOfChildren(); ++i) { + int b = ApproximationHelper::PositiveIntegerApproximationIfPossible(expressionNode.childAtIndex(i), &isUndefined, approximationContext); + if (isUndefined) { + return Complex::RealUndefined(); + } + a = f(a,b); + } + return Complex::Builder((T)a); +} + +template +Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext) { + // Evaluate GCD of expression's children + return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::GCD, approximationContext); +} + +template +Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext) { + // Evaluate LCM of expression's children + return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::LCM, approximationContext); +} + const short primeFactors[Arithmetic::k_numberOfPrimeFactors] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919}; @@ -103,4 +200,9 @@ int Arithmetic::PrimeFactorization(const Integer & n, Integer outputFactors[], I return t+1; } +template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); +template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); +template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); +template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext); + } 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/binom_cdf.cpp b/poincare/src/binom_cdf.cpp index 43e8d87f9..7a83ebf5b 100644 --- a/poincare/src/binom_cdf.cpp +++ b/poincare/src/binom_cdf.cpp @@ -24,10 +24,10 @@ int BinomCDFNode::serialize(char * buffer, int bufferSize, Preferences::PrintFlo } template -Evaluation BinomCDFNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation xEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation nEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation pEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation BinomCDFNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation xEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation nEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation pEvaluation = childAtIndex(2)->approximate(T(), approximationContext); const T x = xEvaluation.toScalar(); const T n = nEvaluation.toScalar(); diff --git a/poincare/src/binom_pdf.cpp b/poincare/src/binom_pdf.cpp index 12305f0bb..f285721d0 100644 --- a/poincare/src/binom_pdf.cpp +++ b/poincare/src/binom_pdf.cpp @@ -24,10 +24,10 @@ int BinomPDFNode::serialize(char * buffer, int bufferSize, Preferences::PrintFlo } template -Evaluation BinomPDFNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation xEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation nEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation pEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation BinomPDFNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation xEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation nEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation pEvaluation = childAtIndex(2)->approximate(T(), approximationContext); T x = xEvaluation.toScalar(); T n = nEvaluation.toScalar(); diff --git a/poincare/src/binomial_coefficient.cpp b/poincare/src/binomial_coefficient.cpp index 248c91ce3..0cdf5208a 100644 --- a/poincare/src/binomial_coefficient.cpp +++ b/poincare/src/binomial_coefficient.cpp @@ -30,9 +30,9 @@ int BinomialCoefficientNode::serialize(char * buffer, int bufferSize, Preference } template -Complex BinomialCoefficientNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation nInput = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation kInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Complex BinomialCoefficientNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation nInput = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation kInput = childAtIndex(1)->approximate(T(), approximationContext); T n = nInput.toScalar(); T k = kInput.toScalar(); return Complex::Builder(compute(k, n)); @@ -40,18 +40,23 @@ Complex BinomialCoefficientNode::templatedApproximate(Context * context, Pref template T BinomialCoefficientNode::compute(T k, T n) { - k = k > (n-k) ? n-k : k; - if (std::isnan(n) || std::isnan(k) || n != std::round(n) || k != std::round(k) || k > n || k < 0 || n < 0) { + if (std::isnan(n) || std::isnan(k) || k != std::round(k) || k < 0) { return NAN; } + // Generalized definition allows any n value + bool generalized = (n != std::round(n) || n < k); + // Take advantage of symmetry + k = (!generalized && k > (n - k)) ? n - k : k; + T result = 1; for (int i = 0; i < k; i++) { - result *= (n-(T)i)/(k-(T)i); + result *= (n - (T)i) / (k - (T)i); if (std::isinf(result) || std::isnan(result)) { return result; } } - return std::round(result); + // If not generalized, the output must be round + return generalized ? result : std::round(result); } @@ -70,45 +75,52 @@ Expression BinomialCoefficient::shallowReduce(Context * context) { return replaceWithUndefinedInPlace(); } - if (c0.type() == ExpressionNode::Type::Rational) { - Rational r0 = static_cast(c0); - if (!r0.isInteger() || r0.isNegative()) { - return replaceWithUndefinedInPlace(); - } - } - if (c1.type() == ExpressionNode::Type::Rational) { - Rational r1 = static_cast(c1); - if (!r1.isInteger() || r1.isNegative()) { - return replaceWithUndefinedInPlace(); - } - } if (c0.type() != ExpressionNode::Type::Rational || c1.type() != ExpressionNode::Type::Rational) { return *this; } + Rational r0 = static_cast(c0); Rational r1 = static_cast(c1); - Integer n = r0.signedIntegerNumerator(); - Integer k = r1.signedIntegerNumerator(); - if (n.isLowerThan(k)) { + if (!r1.isInteger() || r1.isNegative()) { return replaceWithUndefinedInPlace(); } - /* If n is too big, we do not reduce in order to avoid too long computation. - * The binomial coefficient will be approximatively evaluated later. */ - if (Integer(k_maxNValue).isLowerThan(n)) { + + if (!r0.isInteger()) { + // Generalized binomial coefficient (n is not an integer) + return *this; + } + + Integer n = r0.signedIntegerNumerator(); + Integer k = r1.signedIntegerNumerator(); + /* Check for situations where there should be no reduction in order to avoid + * too long computation and a huge result. The binomial coefficient will be + * approximatively evaluated later. */ + if (n.isLowerThan(k)) { + // Generalized binomial coefficient (n < k) + if (!n.isNegative()) { + // When n is an integer and 0 <= n < k, binomial(n,k) is 0. + return Rational::Builder(0); + } + if (Integer(k_maxNValue).isLowerThan(Integer::Subtraction(k, n))) { + return *this; + } + } else if (Integer(k_maxNValue).isLowerThan(n)) { return *this; } Rational result = Rational::Builder(1); Integer kBis = Integer::Subtraction(n, k); - k = kBis.isLowerThan(k) ? kBis : k; - int clippedK = k.extractedInt(); // Authorized because k < n < k_maxNValue + // Take advantage of symmetry if n >= k + k = !n.isLowerThan(k) && kBis.isLowerThan(k) ? kBis : k; + int clippedK = k.extractedInt(); // Authorized because k < k_maxNValue for (int i = 0; i < clippedK; i++) { Integer nMinusI = Integer::Subtraction(n, Integer(i)); Integer kMinusI = Integer::Subtraction(k, Integer(i)); Rational factor = Rational::Builder(nMinusI, kMinusI); result = Rational::Multiplication(result, factor); } - // As we cap the n < k_maxNValue = 300, result < binomial(300, 150) ~2^89 + // As we cap the n < k_maxNValue = 300, result < binomial(300, 150) ~10^89 + // If n was negative, k - n < k_maxNValue, result < binomial(-150,150) ~10^88 assert(!result.numeratorOrDenominatorIsInfinity()); replaceWithInPlace(result); return std::move(result); diff --git a/poincare/src/bracket_layout.cpp b/poincare/src/bracket_layout.cpp index fa9c08854..cd81c0bc5 100644 --- a/poincare/src/bracket_layout.cpp +++ b/poincare/src/bracket_layout.cpp @@ -59,6 +59,14 @@ KDCoordinate BracketLayoutNode::computeBaseline() { int currentNumberOfOpenBrackets = 1; KDCoordinate result = 0; + if (parentLayout->type() != LayoutNode::Type::HorizontalLayout) { + /* The bracket has no true sibling. Those that might be founded by + * numberOfSiblings = parentLayout->numberOfChildren() are likely children + * of Narylayout where the bracket is alone in one of the sublayouts. To + * prevent it from defining its height and baseline according to the other + * sublayouts, we use this escape case.*/ + return result; + } int increment = (type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) ? 1 : -1; for (int i = idxInParent + increment; i >= 0 && i < numberOfSiblings; i+=increment) { LayoutNode * sibling = parentLayout->childAtIndex(i); @@ -101,6 +109,14 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { assert(parentLayout != nullptr); KDCoordinate result = Metric::MinimalBracketAndParenthesisHeight; int idxInParent = parentLayout->indexOfChild(this); + if (parentLayout->type() != LayoutNode::Type::HorizontalLayout) { + /* The bracket has no true sibling. Those that might be founded by + * numberOfSiblings = parentLayout->numberOfChildren() are likely children + * of Narylayout where the bracket is alone in one of the sublayouts. To + * prevent it from defining its height and baseline according to the other + * sublayouts, we use this escape case.*/ + return result; + } int numberOfSiblings = parentLayout->numberOfChildren(); if ((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent < numberOfSiblings - 1 @@ -111,7 +127,6 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { * needs the superscript height, which needs the bracket height. */ return result; } - KDCoordinate maxUnderBaseline = 0; KDCoordinate maxAboveBaseline = 0; diff --git a/poincare/src/bracket_pair_layout.cpp b/poincare/src/bracket_pair_layout.cpp index b42a930b5..0163a4043 100644 --- a/poincare/src/bracket_pair_layout.cpp +++ b/poincare/src/bracket_pair_layout.cpp @@ -10,17 +10,7 @@ extern "C" { namespace Poincare { void BracketPairLayoutNode::RenderWithChildSize(KDSize childSize, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - KDCoordinate verticalBarHeight = childSize.height() + 2*k_verticalMargin; - ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+k_verticalExternMargin, k_lineThickness, verticalBarHeight), expressionColor); - ctx->fillRect(KDRect(p.x()+k_externWidthMargin+childSize.width()+2*k_widthMargin+k_lineThickness, p.y()+k_verticalExternMargin, k_lineThickness, verticalBarHeight), expressionColor); - - // Render top bar - ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+k_verticalExternMargin, k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+k_externWidthMargin+2*k_lineThickness+childSize.width()+2*k_widthMargin-k_bracketWidth, p.y()+k_verticalExternMargin, k_bracketWidth, k_lineThickness), expressionColor); - - // Render bottom bar - ctx->fillRect(KDRect(p.x()+k_externWidthMargin, p.y()+k_verticalExternMargin+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+k_externWidthMargin+2*k_lineThickness+childSize.width()+2*k_widthMargin-k_bracketWidth, p.y()+k_verticalExternMargin+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + renderWithParameters(childSize, ctx, p, expressionColor, backgroundColor, k_verticalMargin, k_externWidthMargin, k_verticalExternMargin, k_widthMargin, k_renderTopBar, k_renderBottomBar, k_renderDoubleBar); } void BracketPairLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection) { @@ -112,33 +102,66 @@ int BracketPairLayoutNode::serialize(char * buffer, int bufferSize, Preferences: } void BracketPairLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { - KDSize childSize = childLayout()->layoutSize(); - KDCoordinate verticalBarHeight = childSize.height() + 2*k_verticalMargin; - ctx->fillRect(KDRect(p.x()+externWidthMargin(), p.y()+verticalExternMargin(), k_lineThickness, verticalBarHeight), expressionColor); - ctx->fillRect(KDRect(p.x()+externWidthMargin()+childSize.width()+2*widthMargin()+k_lineThickness, p.y()+verticalExternMargin(), k_lineThickness, verticalBarHeight), expressionColor); - if (renderTopBar()) { - ctx->fillRect(KDRect(p.x()+externWidthMargin(), p.y()+verticalExternMargin(), k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+externWidthMargin()+2*k_lineThickness+childSize.width()+2*widthMargin()-k_bracketWidth, p.y() + verticalExternMargin(), k_bracketWidth, k_lineThickness), expressionColor); + renderWithParameters(childLayout()->layoutSize(), ctx, p, expressionColor, backgroundColor, verticalMargin(), externWidthMargin(), verticalExternMargin(), widthMargin(), renderTopBar(), renderBottomBar(), renderDoubleBar()); +} + +void BracketPairLayoutNode::renderWithParameters(KDSize childSize, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, KDCoordinate verticalMargin, KDCoordinate externWidthMargin, KDCoordinate verticalExternMargin, KDCoordinate widthMargin, bool renderTopBar, bool renderBottomBar, bool renderDoubleBar) { + // This function allow rendering with either static or virtual parameters + KDCoordinate verticalBarHeight = childSize.height() + 2 * verticalMargin; + KDCoordinate horizontalPosition = p.x() + externWidthMargin; + KDCoordinate verticalPosition = p.y() + verticalExternMargin; + + if (renderDoubleBar) { + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); + horizontalPosition += k_lineThickness + widthMargin; } - if (renderBottomBar()) { - ctx->fillRect(KDRect(p.x()+externWidthMargin(), p.y()+verticalExternMargin()+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); - ctx->fillRect(KDRect(p.x()+externWidthMargin()+2*k_lineThickness+childSize.width()+2*widthMargin()-k_bracketWidth, p.y()+verticalExternMargin()+verticalBarHeight-k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); + + if (renderTopBar) { + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_bracketWidth, k_lineThickness), expressionColor); + } + if (renderBottomBar) { + ctx->fillRect(KDRect(horizontalPosition, verticalPosition + verticalBarHeight - k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + } + + horizontalPosition += k_lineThickness + widthMargin + childSize.width() + widthMargin; + + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); + horizontalPosition += k_lineThickness; + + if (renderTopBar) { + ctx->fillRect(KDRect(horizontalPosition - k_bracketWidth, verticalPosition, k_bracketWidth, k_lineThickness), expressionColor); + } + if (renderBottomBar) { + ctx->fillRect(KDRect(horizontalPosition - k_bracketWidth, verticalPosition + verticalBarHeight - k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); + } + + if (renderDoubleBar) { + horizontalPosition += widthMargin; + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); } } KDSize BracketPairLayoutNode::computeSize() { KDSize childSize = childLayout()->layoutSize(); - KDSize result = KDSize(childSize.width() + 2*externWidthMargin() + 2*widthMargin() + 2*k_lineThickness, childSize.height() + 2 * k_verticalMargin + 2*verticalExternMargin()); - return result; + KDCoordinate horizontalCoordinates = childSize.width() + 2*externWidthMargin() + 2*widthMargin() + 2*k_lineThickness; + if (renderDoubleBar()) { + horizontalCoordinates += 2*k_lineThickness + 2*widthMargin(); + } + return KDSize(horizontalCoordinates, childSize.height() + 2*verticalMargin() + 2*verticalExternMargin()); } KDCoordinate BracketPairLayoutNode::computeBaseline() { - return childLayout()->baseline() + k_verticalMargin + verticalExternMargin(); + return childLayout()->baseline() + verticalMargin() + verticalExternMargin(); } KDPoint BracketPairLayoutNode::positionOfChild(LayoutNode * child) { assert(child == childLayout()); - return KDPoint(widthMargin() + externWidthMargin() + k_lineThickness, k_verticalMargin + verticalExternMargin()); + KDCoordinate horizontalCoordinates = widthMargin() + externWidthMargin() + k_lineThickness; + if (renderDoubleBar()) { + horizontalCoordinates += k_lineThickness + widthMargin(); + } + return KDPoint(horizontalCoordinates, verticalMargin() + verticalExternMargin()); } } diff --git a/poincare/src/complex_argument.cpp b/poincare/src/complex_argument.cpp index 9e1ef1bf8..2c761454b 100644 --- a/poincare/src/complex_argument.cpp +++ b/poincare/src/complex_argument.cpp @@ -51,7 +51,7 @@ Expression ComplexArgument::shallowReduce(ExpressionNode::ReductionContext reduc } bool real = c.isReal(reductionContext.context()); if (real) { - float app = c.node()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()).toScalar(); + float app = c.node()->approximate(float(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); if (!std::isnan(app) && app >= Expression::Epsilon()) { // arg(x) = 0 if x > 0 Expression result = Rational::Builder(0); diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index da5db5d74..758829eab 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -19,18 +19,27 @@ namespace Poincare { -Expression ComplexCartesianNode::shallowReduce(ReductionContext reductionContext) { - return ComplexCartesian(this).shallowReduce(); +ExpressionNode::NullStatus ComplexCartesianNode::nullStatus(Context * context) const { + ExpressionNode::NullStatus realNullStatus = childAtIndex(0)->nullStatus(context); + ExpressionNode::NullStatus imagNullStatus = childAtIndex(1)->nullStatus(context); + if (realNullStatus == ExpressionNode::NullStatus::NonNull || imagNullStatus == ExpressionNode::NullStatus::NonNull) { + return ExpressionNode::NullStatus::NonNull; + } + return (realNullStatus == ExpressionNode::NullStatus::Null && imagNullStatus == ExpressionNode::NullStatus::Null) ? ExpressionNode::NullStatus::Null : ExpressionNode::NullStatus::Unknown; } -Expression ComplexCartesianNode::shallowBeautify(ReductionContext reductionContext) { +Expression ComplexCartesianNode::shallowReduce(ReductionContext reductionContext) { + return ComplexCartesian(this).shallowReduce(reductionContext); +} + +Expression ComplexCartesianNode::shallowBeautify(ReductionContext * reductionContext) { return ComplexCartesian(this).shallowBeautify(reductionContext); } template -Complex ComplexCartesianNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation realEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation imagEvalution = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Complex ComplexCartesianNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation realEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation imagEvalution = childAtIndex(1)->approximate(T(), approximationContext); assert(realEvaluation.type() == EvaluationNode::Type::Complex); assert(imagEvalution.type() == EvaluationNode::Type::Complex); std::complex a = static_cast &>(realEvaluation).stdComplex(); @@ -52,7 +61,7 @@ Complex ComplexCartesianNode::templatedApproximate(Context * context, Prefere return Complex::Builder(a.real(), b.real()); } -Expression ComplexCartesian::shallowReduce() { +Expression ComplexCartesian::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -60,7 +69,7 @@ Expression ComplexCartesian::shallowReduce() { return e; } } - if (imag().isRationalZero()) { + if (imag().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { Expression r = real(); replaceWithInPlace(r); return r; @@ -68,11 +77,11 @@ Expression ComplexCartesian::shallowReduce() { return *this; } -Expression ComplexCartesian::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression ComplexCartesian::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { Expression a = real(); Expression b = imag(); - Expression oppositeA = a.makePositiveAnyNegativeNumeralFactor(reductionContext); - Expression oppositeB = b.makePositiveAnyNegativeNumeralFactor(reductionContext); + Expression oppositeA = a.makePositiveAnyNegativeNumeralFactor(*reductionContext); + Expression oppositeB = b.makePositiveAnyNegativeNumeralFactor(*reductionContext); a = oppositeA.isUninitialized() ? a : oppositeA; b = oppositeB.isUninitialized() ? b : oppositeB; Expression e = Expression::CreateComplexExpression(a, b, Preferences::ComplexFormat::Cartesian, @@ -128,9 +137,9 @@ Expression ComplexCartesian::squareNorm(ExpressionNode::ReductionContext reducti Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionContext) { Expression a; // Special case for pure real or pure imaginary cartesian - if (imag().isRationalZero()) { + if (imag().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { a = real(); - } else if (real().isRationalZero()) { + } else if (real().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { a = imag(); } if (!a.isUninitialized()) { @@ -149,7 +158,8 @@ Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionCont Expression ComplexCartesian::argument(ExpressionNode::ReductionContext reductionContext) { Expression a = real(); Expression b = imag(); - if (!b.isRationalZero()) { + if (b.nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown // if b != 0, argument = sign(b) * π/2 - atan(a/b) // First, compute atan(a/b) or (π/180)*atan(a/b) Expression divab = Division::Builder(a, b.clone()); @@ -242,11 +252,11 @@ ComplexCartesian ComplexCartesian::powerInteger(int n, ExpressionNode::Reduction Expression a = real(); Expression b = imag(); assert(n > 0); - assert(!b.isRationalZero()); + assert(b.nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null); // Special case: a == 0 (otherwise, we are going to introduce undefined expressions - a^0 = NAN) // (b*i)^n = b^n*i^n with i^n == i, -i, 1 or -1 - if (a.isRationalZero()) { + if (a.nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { ComplexCartesian result; Expression bpow = Power::Builder(b, Rational::Builder(n)); if (n/2%2 == 1) { diff --git a/poincare/src/confidence_interval.cpp b/poincare/src/confidence_interval.cpp index 688cee557..c4d546f43 100644 --- a/poincare/src/confidence_interval.cpp +++ b/poincare/src/confidence_interval.cpp @@ -31,9 +31,9 @@ Expression ConfidenceIntervalNode::shallowReduce(ReductionContext reductionConte } template -Evaluation ConfidenceIntervalNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation fInput = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation nInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation ConfidenceIntervalNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation fInput = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation nInput = childAtIndex(1)->approximate(T(), approximationContext); T f = static_cast &>(fInput).toScalar(); T n = static_cast &>(nInput).toScalar(); if (std::isnan(f) || std::isnan(n) || n != (int)n || n < 0 || f < 0 || f > 1) { diff --git a/poincare/src/constant.cpp b/poincare/src/constant.cpp index eb53a14d9..b21d5ba2d 100644 --- a/poincare/src/constant.cpp +++ b/poincare/src/constant.cpp @@ -78,6 +78,10 @@ Expression ConstantNode::shallowReduce(ReductionContext reductionContext) { return Constant(this).shallowReduce(reductionContext); } +bool ConstantNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Constant(this).derivate(reductionContext, symbol, symbolValue); +} + bool ConstantNode::isConstantCodePoint(CodePoint c) const { UTF8Decoder decoder(m_name); bool result = (decoder.nextCodePoint() == c); @@ -106,4 +110,9 @@ Expression Constant::shallowReduce(ExpressionNode::ReductionContext reductionCon return result; } +bool Constant::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + replaceWithInPlace(Rational::Builder(0)); + return true; +} + } diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index 2e216ce8e..6d64cf0c8 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -1,7 +1,11 @@ #include #include +#include #include +#include +#include #include +#include #include @@ -11,10 +15,6 @@ constexpr Expression::FunctionHelper Cosine::s_functionHelper; int CosineNode::numberOfChildren() const { return Cosine::s_functionHelper.numberOfChildren(); } -float CosineNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - return Trigonometry::characteristicXRange(Cosine(this), context, angleUnit); -} - template Complex CosineNode::computeOnComplex(const std::complex c, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) { std::complex angleInput = Trigonometry::ConvertToRadian(c, angleUnit); @@ -34,6 +34,14 @@ Expression CosineNode::shallowReduce(ReductionContext reductionContext) { return Cosine(this).shallowReduce(reductionContext); } +bool CosineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Cosine(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression CosineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Cosine(this).unaryFunctionDifferential(reductionContext); +} + Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); @@ -46,4 +54,13 @@ Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionConte } +bool Cosine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression Cosine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return Multiplication::Builder(Rational::Builder(-1), Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Sine::Builder(childAtIndex(0).clone())); +} + } diff --git a/poincare/src/decimal.cpp b/poincare/src/decimal.cpp index 82330919d..30ee799eb 100644 --- a/poincare/src/decimal.cpp +++ b/poincare/src/decimal.cpp @@ -115,7 +115,7 @@ Expression DecimalNode::shallowReduce(ReductionContext reductionContext) { return Decimal(this).shallowReduce(); } -Expression DecimalNode::shallowBeautify(ReductionContext reductionContext) { +Expression DecimalNode::shallowBeautify(ReductionContext * reductionContext) { return Decimal(this).shallowBeautify(); } diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index 3a9b857a8..0d139fd73 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -1,8 +1,8 @@ #include #include #include +#include #include - #include #include #include @@ -36,26 +36,26 @@ int DerivativeNode::serialize(char * buffer, int bufferSize, Preferences::PrintF } Expression DerivativeNode::shallowReduce(ReductionContext reductionContext) { - return Derivative(this).shallowReduce(reductionContext.context()); + return Derivative(this).shallowReduce(reductionContext); } template -Evaluation DerivativeNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation evaluationArgumentInput = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation DerivativeNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation evaluationArgumentInput = childAtIndex(2)->approximate(T(), approximationContext); T evaluationArgument = evaluationArgumentInput.toScalar(); - T functionValue = approximateWithArgument(evaluationArgument, context, complexFormat, angleUnit); + T functionValue = approximateWithArgument(evaluationArgument, approximationContext); // No complex/matrix version of Derivative if (std::isnan(evaluationArgument) || std::isnan(functionValue)) { return Complex::RealUndefined(); } T error = sizeof(T) == sizeof(double) ? DBL_MAX : FLT_MAX; - T result; + T result = 1.0; T h = k_minInitialRate; static T tenEpsilon = sizeof(T) == sizeof(double) ? 10.0*DBL_EPSILON : 10.0f*FLT_EPSILON; do { T currentError; - T currentResult = riddersApproximation(context, complexFormat, angleUnit, evaluationArgument, h, ¤tError); + T currentResult = riddersApproximation(approximationContext, evaluationArgument, h, ¤tError); h /= (T)10.0; if (std::isnan(currentError) || currentError > error) { continue; @@ -83,23 +83,24 @@ Evaluation DerivativeNode::templatedApproximate(Context * context, Preference } template -T DerivativeNode::approximateWithArgument(T x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +T DerivativeNode::approximateWithArgument(T x, ApproximationContext approximationContext) const { assert(childAtIndex(1)->type() == Type::Symbol); - VariableContext variableContext = VariableContext(static_cast(childAtIndex(1))->name(), context); + VariableContext variableContext = VariableContext(static_cast(childAtIndex(1))->name(), approximationContext.context()); variableContext.setApproximationForVariable(x); // Here we cannot use Expression::approximateWithValueForSymbol which would reset the sApproximationEncounteredComplex flag - return childAtIndex(0)->approximate(T(), &variableContext, complexFormat, angleUnit).toScalar(); + approximationContext.setContext(&variableContext); + return childAtIndex(0)->approximate(T(), approximationContext).toScalar(); } template -T DerivativeNode::growthRateAroundAbscissa(T x, T h, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - T expressionPlus = approximateWithArgument(x+h, context, complexFormat, angleUnit); - T expressionMinus = approximateWithArgument(x-h, context, complexFormat, angleUnit); +T DerivativeNode::growthRateAroundAbscissa(T x, T h, ApproximationContext approximationContext) const { + T expressionPlus = approximateWithArgument(x+h, approximationContext); + T expressionMinus = approximateWithArgument(x-h, approximationContext); return (expressionPlus - expressionMinus)/(h+h); } template -T DerivativeNode::riddersApproximation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, T x, T h, T * error) const { +T DerivativeNode::riddersApproximation(ApproximationContext approximationContext, T x, T h, T * error) const { /* Ridders' Algorithm * Blibliography: * - Ridders, C.J.F. 1982, Advances in Helperering Software, vol. 4, no. 2, @@ -118,7 +119,7 @@ T DerivativeNode::riddersApproximation(Context * context, Preferences::ComplexFo a[i][j] = 1; } } - a[0][0] = growthRateAroundAbscissa(x, hh, context, complexFormat, angleUnit); + a[0][0] = growthRateAroundAbscissa(x, hh, approximationContext); T ans = 0; T errt = 0; // Loop on i: change the step size @@ -127,7 +128,7 @@ T DerivativeNode::riddersApproximation(Context * context, Preferences::ComplexFo // Make hh an exactly representable number volatile T temp = x+hh; hh = temp - x; - a[0][i] = growthRateAroundAbscissa(x, hh, context, complexFormat, angleUnit); + a[0][i] = growthRateAroundAbscissa(x, hh, approximationContext); T fac = k_rateStepSize*k_rateStepSize; // Loop on j: compute extrapolation for several orders for (int j = 1; j < 10; j++) { @@ -151,7 +152,7 @@ T DerivativeNode::riddersApproximation(Context * context, Preferences::ComplexFo return ans; } -Expression Derivative::shallowReduce(Context * context) { +Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -159,12 +160,42 @@ Expression Derivative::shallowReduce(Context * context) { return e; } } + Context * context = reductionContext.context(); assert(!childAtIndex(1).deepIsMatrix(context)); if (childAtIndex(0).deepIsMatrix(context) || childAtIndex(2).deepIsMatrix(context)) { return replaceWithUndefinedInPlace(); } - // TODO: to be implemented diff(+) -> +diff() etc - return *this; + + Expression derivand = childAtIndex(0); + Symbol symbol = childAtIndex(1).convert(); + Expression symbolValue = childAtIndex(2); + + /* Since derivand is a child to the derivative node, it can be replaced in + * place without derivate having to return the derivative. */ + if (!derivand.derivate(reductionContext, symbol, symbolValue)) { + return *this; + } + /* Updates the value of derivand, because derivate may call + * replaceWithInplace on it. + * We need to reduce the derivand here before replacing the symbol : the + * general formulas used during the derivation process can create some nodes + * that are not defined for some values (e.g. log), but that would disappear + * at reduction. */ + derivand = childAtIndex(0).deepReduce(reductionContext); + /* Deep reduces the child, because derivate may not preserve its reduced + * status. */ + + derivand = derivand.replaceSymbolWithExpression(symbol, symbolValue); + derivand = derivand.deepReduce(reductionContext); + replaceWithInPlace(derivand); + return derivand; +} + +void Derivative::DerivateUnaryFunction(Expression function, Expression symbol, Expression symbolValue, ExpressionNode::ReductionContext reductionContext) { + Expression df = function.unaryFunctionDifferential(reductionContext); + Expression dg = Derivative::Builder(function.childAtIndex(0), symbol.clone().convert(), symbolValue.clone()); + function.replaceWithInPlace(Multiplication::Builder(df, dg)); + } Expression Derivative::UntypedBuilder(Expression children) { diff --git a/poincare/src/determinant.cpp b/poincare/src/determinant.cpp index f8b2e5c2d..cb8d588c2 100644 --- a/poincare/src/determinant.cpp +++ b/poincare/src/determinant.cpp @@ -26,8 +26,8 @@ int DeterminantNode::serialize(char * buffer, int bufferSize, Preferences::Print } template -Evaluation DeterminantNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation DeterminantNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); return Complex::Builder(input.determinant()); } diff --git a/poincare/src/division.cpp b/poincare/src/division.cpp index 77ff4c825..488d3f23a 100644 --- a/poincare/src/division.cpp +++ b/poincare/src/division.cpp @@ -14,6 +14,15 @@ namespace Poincare { +ExpressionNode::Sign DivisionNode::sign(Context * context) const { + ExpressionNode::Sign numeratorSign = childAtIndex(0)->sign(context); + ExpressionNode::Sign denominatorSign = childAtIndex(1)->sign(context); + if (numeratorSign == ExpressionNode::Sign::Unknown || denominatorSign == ExpressionNode::Sign::Unknown) { + return ExpressionNode::Sign::Unknown; + } + return numeratorSign == denominatorSign ? ExpressionNode::Sign::Positive : ExpressionNode::Sign::Negative; +} + int DivisionNode::polynomialDegree(Context * context, const char * symbolName) const { if (childAtIndex(1)->polynomialDegree(context, symbolName) != 0) { return -1; diff --git a/poincare/src/division_quotient.cpp b/poincare/src/division_quotient.cpp index dfbc04c3f..8a57e3099 100644 --- a/poincare/src/division_quotient.cpp +++ b/poincare/src/division_quotient.cpp @@ -12,6 +12,15 @@ constexpr Expression::FunctionHelper DivisionQuotient::s_functionHelper; int DivisionQuotientNode::numberOfChildren() const { return DivisionQuotient::s_functionHelper.numberOfChildren(); } +ExpressionNode::Sign DivisionQuotientNode::sign(Context * context) const { + ExpressionNode::Sign numeratorSign = childAtIndex(0)->sign(context); + ExpressionNode::Sign denominatorSign = childAtIndex(1)->sign(context); + if (numeratorSign == ExpressionNode::Sign::Unknown || denominatorSign == ExpressionNode::Sign::Unknown) { + return ExpressionNode::Sign::Unknown; + } + return numeratorSign == denominatorSign ? ExpressionNode::Sign::Positive : ExpressionNode::Sign::Negative; +} + Expression DivisionQuotientNode::shallowReduce(ReductionContext reductionContext) { return DivisionQuotient(this).shallowReduce(reductionContext.context()); } @@ -24,9 +33,9 @@ int DivisionQuotientNode::serialize(char * buffer, int bufferSize, Preferences:: } template -Evaluation DivisionQuotientNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation f1Input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation f2Input = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation DivisionQuotientNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation f1Input = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation f2Input = childAtIndex(1)->approximate(T(), approximationContext); T f1 = f1Input.toScalar(); T f2 = f2Input.toScalar(); if (std::isnan(f1) || std::isnan(f2) || f1 != (int)f1 || f2 != (int)f2) { diff --git a/poincare/src/division_remainder.cpp b/poincare/src/division_remainder.cpp index 842315fe1..99eb8c1dc 100644 --- a/poincare/src/division_remainder.cpp +++ b/poincare/src/division_remainder.cpp @@ -25,9 +25,9 @@ Expression DivisionRemainderNode::shallowReduce(ReductionContext reductionContex } template -Evaluation DivisionRemainderNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation f1Input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation f2Input = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation DivisionRemainderNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation f1Input = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation f2Input = childAtIndex(1)->approximate(T(), approximationContext); T f1 = f1Input.toScalar(); T f2 = f2Input.toScalar(); if (std::isnan(f1) || std::isnan(f2) || f1 != (int)f1 || f2 != (int)f2) { diff --git a/poincare/src/empty_expression.cpp b/poincare/src/empty_expression.cpp index a72775bff..d1fe21669 100644 --- a/poincare/src/empty_expression.cpp +++ b/poincare/src/empty_expression.cpp @@ -13,7 +13,7 @@ Layout EmptyExpressionNode::createLayout(Preferences::PrintFloatMode floatDispla return EmptyLayout::Builder(); } -template Evaluation EmptyExpressionNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +template Evaluation EmptyExpressionNode::templatedApproximate(ApproximationContext approximationContext) const { return Complex::Undefined(); } diff --git a/poincare/src/empty_layout.cpp b/poincare/src/empty_layout.cpp index 2bc6f97a2..3200c2d9b 100644 --- a/poincare/src/empty_layout.cpp +++ b/poincare/src/empty_layout.cpp @@ -68,7 +68,7 @@ void EmptyLayoutNode::moveCursorVertically(VerticalDirection direction, LayoutCu bool EmptyLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { EmptyLayout thisRef(this); Layout siblingRef(sibling); // Create the reference now, as the node might be moved - if (m_color == Color::Grey) { + if (m_color == Color::Gray) { /* The parent is a MatrixLayout, and the current empty row or column is * being filled in, so add a new empty row or column. */ LayoutNode * parentNode = parent(); diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index f5f16e686..4468a2e32 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -39,18 +39,14 @@ int EqualNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatM } template -Evaluation EqualNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation EqualNode::templatedApproximate(ApproximationContext approximationContext) const { return Complex::Undefined(); } -Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::ReductionTarget reductionTarget) const { 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 - * unless expanded). */ - return sub.reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForAnalysis)); + return sub.reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, reductionTarget)); } Expression Equal::shallowReduce() { diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index f9b066832..a5c5f2b8f 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -79,10 +79,6 @@ Expression Expression::childAtIndex(int i) const { /* Properties */ -bool Expression::isRationalZero() const { - return type() == ExpressionNode::Type::Rational && convert().isZero(); -} - bool Expression::isRationalOne() const { return type() == ExpressionNode::Type::Rational && convert().isOne(); } @@ -167,7 +163,7 @@ bool Expression::deepIsMatrix(Context * context) const { } bool Expression::IsApproximate(const Expression e, Context * context) { - return e.type() == ExpressionNode::Type::Decimal || e.type() == ExpressionNode::Type::Float; + return e.type() == ExpressionNode::Type::Decimal || e.type() == ExpressionNode::Type::Float || e.type() == ExpressionNode::Type::Double; } bool Expression::IsRandom(const Expression e, Context * context) { @@ -185,7 +181,10 @@ bool Expression::IsMatrix(const Expression e, Context * context) { || e.type() == ExpressionNode::Type::PredictionInterval || e.type() == ExpressionNode::Type::MatrixInverse || e.type() == ExpressionNode::Type::MatrixIdentity - || e.type() == ExpressionNode::Type::MatrixTranspose; + || e.type() == ExpressionNode::Type::MatrixTranspose + || e.type() == ExpressionNode::Type::MatrixRowEchelonForm + || e.type() == ExpressionNode::Type::MatrixReducedRowEchelonForm + || e.type() == ExpressionNode::Type::VectorCross; } bool Expression::IsInfinity(const Expression e, Context * context) { @@ -211,7 +210,7 @@ bool containsVariables(const Expression e, char * variables, int maxVariableSize return false; } -bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const { +bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation) const { assert(!recursivelyMatches(IsMatrix, context, symbolicComputation)); // variables is in fact of type char[k_maxNumberOfVariables][maxVariableSize] int index = 0; @@ -226,7 +225,7 @@ bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Ex index = 0; Expression polynomialCoefficients[k_maxNumberOfPolynomialCoefficients]; while (variables[index*maxVariableSize] != 0) { - int degree = equation.getPolynomialReducedCoefficients(&variables[index*maxVariableSize], polynomialCoefficients, context, complexFormat, angleUnit, symbolicComputation); + int degree = equation.getPolynomialReducedCoefficients(&variables[index*maxVariableSize], polynomialCoefficients, context, complexFormat, angleUnit, unitFormat, symbolicComputation); switch (degree) { case 0: coefficients[index] = Rational::Builder(0); @@ -247,7 +246,7 @@ bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Ex equation = polynomialCoefficients[0]; index++; } - constant[0] = Opposite::Builder(equation.clone()).reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation)); + constant[0] = Opposite::Builder(equation.clone()).reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation)); /* The expression can be linear on all coefficients taken one by one but * non-linear (ex: xy = 2). We delete the results and return false if one of * the coefficients contains a variable. */ @@ -286,12 +285,13 @@ bool Expression::hasDefinedComplexApproximation(Context * context, Preferences:: } /* We return true when both real and imaginary approximation are defined and * imaginary part is not null. */ - Expression imag = ImaginaryPart::Builder(*this); + Expression e = clone(); + Expression imag = ImaginaryPart::Builder(e); float b = imag.approximateToScalar(context, complexFormat, angleUnit); if (b == 0.0f || std::isinf(b) || std::isnan(b)) { return false; } - Expression real = RealPart::Builder(*this); + Expression real = RealPart::Builder(e); float a = real.approximateToScalar(context, complexFormat, angleUnit); if (std::isinf(a) || std::isnan(a)) { return false; @@ -375,7 +375,7 @@ Expression Expression::defaultHandleUnitsInChildren() { } Expression Expression::shallowReduceUsingApproximation(ExpressionNode::ReductionContext reductionContext) { - double approx = node()->approximate(double(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()).toScalar(); + double approx = node()->approximate(double(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); /* If approx is capped by the largest integer such as all smaller integers can * be exactly represented in IEEE754, approx is the exact result (no * precision were loss). */ @@ -388,6 +388,18 @@ Expression Expression::shallowReduceUsingApproximation(ExpressionNode::Reduction return *this; } +void Expression::defaultDeepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + const int nbChildren = numberOfChildren(); + for (int i = 0; i < nbChildren; i++) { + Expression child = childAtIndex(i); + child = child.deepBeautify(reductionContext); + // We add missing Parentheses after beautifying the parent and child + if (node()->childAtIndexNeedsUserParentheses(child, i)) { + replaceChildAtIndexInPlace(i, Parenthesis::Builder(child)); + } + } +} + bool Expression::SimplificationHasBeenInterrupted() { return sSimplificationHasBeenInterrupted; } @@ -445,11 +457,11 @@ Expression Expression::makePositiveAnyNegativeNumeralFactor(ExpressionNode::Redu } template -Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const { sApproximationEncounteredComplex = false; // Reset interrupting flag because some evaluation methods use it sSimplificationHasBeenInterrupted = false; - Evaluation e = node()->approximate(U(), context, complexFormat, angleUnit); + Evaluation e = node()->approximate(U(), ExpressionNode::ApproximationContext(context, complexFormat, angleUnit, withinReduce)); if (complexFormat == Preferences::ComplexFormat::Real && sApproximationEncounteredComplex) { e = Complex::Undefined(); } @@ -476,11 +488,11 @@ int Expression::defaultGetPolynomialCoefficients(Context * context, const char * return -1; } -int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expression coefficients[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const { +int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expression coefficients[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation) const { // Reset interrupting flag because we use deepReduce int degree = getPolynomialCoefficients(context, symbolName, coefficients, symbolicComputation); for (int i = 0; i <= degree; i++) { - coefficients[i] = coefficients[i].reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation)); + coefficients[i] = coefficients[i].reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation)); } return degree; } @@ -567,9 +579,9 @@ bool Expression::isIdenticalToWithoutParentheses(const Expression e) const { return ExpressionNode::SimplificationOrder(node(), e.node(), true, true, true) == 0; } -bool Expression::ParsedExpressionsAreEqual(const char * e0, const char * e1, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { - Expression exp0 = Expression::ParseAndSimplify(e0, context, complexFormat, angleUnit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - Expression exp1 = Expression::ParseAndSimplify(e1, context, complexFormat, angleUnit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); +bool Expression::ParsedExpressionsAreEqual(const char * e0, const char * e1, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat) { + Expression exp0 = Expression::ParseAndSimplify(e0, context, complexFormat, angleUnit, unitFormat, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + Expression exp1 = Expression::ParseAndSimplify(e1, context, complexFormat, angleUnit, unitFormat, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); if (exp0.isUninitialized() || exp1.isUninitialized()) { return false; } @@ -586,12 +598,12 @@ int Expression::serialize(char * buffer, int bufferSize, Preferences::PrintFloat /* Simplification */ -Expression Expression::ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { +Expression Expression::ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { Expression exp = Parse(text, context, false); if (exp.isUninitialized()) { return Undefined::Builder(); } - exp = exp.simplify(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion)); + exp = exp.simplify(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion)); /* simplify might have been interrupted, in which case the resulting * expression is uninitialized, so we need to check that. */ if (exp.isUninitialized()) { @@ -600,7 +612,7 @@ Expression Expression::ParseAndSimplify(const char * text, Context * context, Pr return exp; } -void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { +void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { assert(simplifiedExpression); Expression exp = Parse(text, context, false); if (exp.isUninitialized()) { @@ -608,7 +620,7 @@ void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * *approximateExpression = Undefined::Builder(); return; } - exp.simplifyAndApproximate(simplifiedExpression, approximateExpression, context, complexFormat, angleUnit, symbolicComputation, unitConversion); + exp.simplifyAndApproximate(simplifiedExpression, approximateExpression, context, complexFormat, angleUnit, unitFormat, symbolicComputation, unitConversion); /* simplify might have been interrupted, in which case the resulting * expression is uninitialized, so we need to check that. */ if (simplifiedExpression->isUninitialized()) { @@ -656,7 +668,7 @@ void Expression::beautifyAndApproximateScalar(Expression * simplifiedExpression, ecomplexClone.imag().deepBeautify(userReductionContext); *approximateExpression = ecomplexClone.approximate(context, complexFormat, angleUnit); } - // Step 2: create the simplied expression with the required complex format + // Step 2: create the simplified expression with the required complex format Expression ra = complexFormat == Preferences::ComplexFormat::Polar ? ecomplex.clone().convert().norm(userReductionContext).shallowReduce(userReductionContext) : ecomplex.real(); @@ -690,15 +702,15 @@ void Expression::beautifyAndApproximateScalar(Expression * simplifiedExpression, } } -void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { +void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { assert(simplifiedExpression); sSimplificationHasBeenInterrupted = false; // Step 1: we reduce the expression - ExpressionNode::ReductionContext userReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion); + ExpressionNode::ReductionContext userReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion); Expression e = clone().reduce(userReductionContext); if (sSimplificationHasBeenInterrupted) { sSimplificationHasBeenInterrupted = false; - ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation, unitConversion); + ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation, unitConversion); e = reduce(systemReductionContext); } *simplifiedExpression = Expression(); @@ -815,6 +827,12 @@ Expression Expression::angleUnitToRadian(Preferences::AngleUnit angleUnit) { return *this; } +Expression Expression::reduceAndRemoveUnit(ExpressionNode::ReductionContext reductionContext, Expression * Unit) { + /* RemoveUnit has to be called on reduced expression. reduce method is called + * instead of deepReduce to catch interrupted simplification. */ + return reduce(reductionContext).removeUnit(Unit); +} + Expression Expression::reduce(ExpressionNode::ReductionContext reductionContext) { sSimplificationHasBeenInterrupted = false; Expression result = deepReduce(reductionContext); @@ -833,16 +851,8 @@ Expression Expression::deepReduce(ExpressionNode::ReductionContext reductionCont } Expression Expression::deepBeautify(ExpressionNode::ReductionContext reductionContext) { - Expression e = shallowBeautify(reductionContext); - const int nbChildren = e.numberOfChildren(); - for (int i = 0; i < nbChildren; i++) { - Expression child = e.childAtIndex(i); - child = child.deepBeautify(reductionContext); - // We add missing Parentheses after beautifying the parent and child - if (e.node()->childAtIndexNeedsUserParentheses(child, i)) { - e.replaceChildAtIndexInPlace(i, Parenthesis::Builder(child)); - } - } + Expression e = shallowBeautify(&reductionContext); + e.deepBeautifyChildren(reductionContext); return e; } @@ -854,19 +864,18 @@ Expression Expression::setSign(ExpressionNode::Sign s, ExpressionNode::Reduction /* Evaluation */ template -Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return isUninitialized() ? Undefined::Builder() : approximateToEvaluation(context, complexFormat, angleUnit).complexToExpression(complexFormat); -} - - -template -U Expression::approximateToScalar(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return approximateToEvaluation(context, complexFormat, angleUnit).toScalar(); +Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const { + return isUninitialized() ? Undefined::Builder() : approximateToEvaluation(context, complexFormat, angleUnit, withinReduce).complexToExpression(complexFormat); } template -U Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) { - Expression exp = ParseAndSimplify(text, context, complexFormat, angleUnit, symbolicComputation); +U Expression::approximateToScalar(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const { + return approximateToEvaluation(context, complexFormat, angleUnit, withinReduce).toScalar(); +} + +template +U Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation) { + Expression exp = ParseAndSimplify(text, context, complexFormat, angleUnit, unitFormat, symbolicComputation); assert(!exp.isUninitialized()); return exp.approximateToScalar(context, complexFormat, angleUnit); } @@ -1002,6 +1011,12 @@ Coordinate2D Expression::nextMaximum(const char * symbol, double start, } double Expression::nextRoot(const char * symbol, double start, double step, double max, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + /* The algorithms used to numerically find roots require either the function + * to change sign around the root or for the root to be an extremum. Neither + * is true for the null function, which we handle here. */ + if (nullStatus(context) == ExpressionNode::NullStatus::Null) { + return start + step; + } return nextIntersectionWithExpression(symbol, start, step, max, [](double x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, const void * context1, const void * context2, const void * context3) { const Expression * expression0 = reinterpret_cast(context1); @@ -1135,18 +1150,31 @@ double Expression::nextIntersectionWithExpression(const char * symbol, double st } void Expression::bracketRoot(const char * symbol, double start, double step, double max, double result[2], Solver::ValueAtAbscissa evaluation, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, const Expression expression) const { - double a = start; - double b = start+step; - while (step > 0.0 ? b <= max : b >= max) { - double fa = evaluation(a, context, complexFormat, angleUnit, this, symbol, &expression); - double fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); - if (fa*fb <= 0) { - result[0] = a; - result[1] = b; + double b = start; + double c = start+step; + double fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); + double fa = fb; + double fc = evaluation(c, context, complexFormat, angleUnit, this, symbol, &expression); + while (step > 0.0 ? c <= max : c >= max) { + if (fb == 0. && ((fa < 0. && fc > 0.) || (fa > 0. && fc < 0.))) { + /* If fb is null, we still check that the function changes sign on ]a,c[, + * and that fa and fc are not null. Otherwise, it's more likely those + * zeroes are caused by approximation errors. */ + result[0] = b; + result[1] = c; + return; + } else if (fc != 0. && ((fb < 0.) != (fc < 0.))) { + /* The function changes sign. + * The case fc = 0 is handled in the next pass with fb = 0. */ + result[0] = b; + result[1] = c; return; } - a = b; - b = b+step; + fa = fb; + b = c; + fb = fc; + c = c+step; + fc = evaluation(c, context, complexFormat, angleUnit, this, symbol, &expression); } result[0] = NAN; result[1] = NAN; @@ -1155,17 +1183,17 @@ void Expression::bracketRoot(const char * symbol, double start, double step, dou template float Expression::Epsilon(); template double Expression::Epsilon(); -template Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; +template Expression Expression::approximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; -template float Expression::approximateToScalar(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) const; -template double Expression::approximateToScalar(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) const; +template float Expression::approximateToScalar(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; +template double Expression::approximateToScalar(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; -template float Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation); -template double Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation); +template float Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation); +template double Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation); -template Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; +template Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; template float Expression::approximateWithValueForSymbol(const char * symbol, float x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; template double Expression::approximateWithValueForSymbol(const char * symbol, double x, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index fe117da7e..08149a195 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -51,22 +51,6 @@ int ExpressionNode::getVariables(Context * context, isVariableTest isVariable, c return nextVariableIndex; } -float ExpressionNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - /* A expression has a characteristic range if at least one of its childAtIndex has - * one and the other are x-independant. We keep the biggest interesting range - * among the childAtIndex interesting ranges. */ - float range = 0.0f; - for (ExpressionNode * c : children()) { - float opRange = c->characteristicXRange(context, angleUnit); - if (std::isnan(opRange)) { - return NAN; - } else if (range < opRange) { - range = opRange; - } - } - return range; -} - int ExpressionNode::SimplificationOrder(const ExpressionNode * e1, const ExpressionNode * e2, bool ascending, bool canBeInterrupted, bool ignoreParentheses) { // Depending on ignoreParentheses, check if e1 or e2 are parenthesis ExpressionNode::Type type1 = e1->type(); @@ -119,14 +103,26 @@ void ExpressionNode::deepReduceChildren(ExpressionNode::ReductionContext reducti Expression(this).defaultDeepReduceChildren(reductionContext); } +void ExpressionNode::deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + Expression(this).defaultDeepBeautifyChildren(reductionContext); +} + Expression ExpressionNode::shallowReduce(ReductionContext reductionContext) { return Expression(this).defaultShallowReduce(); } -Expression ExpressionNode::shallowBeautify(ReductionContext reductionContext) { +Expression ExpressionNode::shallowBeautify(ReductionContext * reductionContext) { return Expression(this).defaultShallowBeautify(); } +bool ExpressionNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Expression(this).defaultDidDerivate(); +} + +Expression ExpressionNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Expression(this).defaultUnaryFunctionDifferential(); +} + bool ExpressionNode::isOfType(Type * types, int length) const { for (int i = 0; i < length; i++) { if (type() == types[i]) { diff --git a/poincare/src/factor.cpp b/poincare/src/factor.cpp index 85ee0adb5..549e62297 100644 --- a/poincare/src/factor.cpp +++ b/poincare/src/factor.cpp @@ -31,14 +31,14 @@ Expression FactorNode::shallowReduce(ReductionContext reductionContext) { return Factor(this).shallowReduce(reductionContext.context()); } -Expression FactorNode::shallowBeautify(ReductionContext reductionContext) { +Expression FactorNode::shallowBeautify(ReductionContext * reductionContext) { return Factor(this).shallowBeautify(reductionContext); } // Add tests :) template -Evaluation FactorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation e = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation FactorNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation e = childAtIndex(0)->approximate(T(), approximationContext); if (std::isnan(e.toScalar())) { return Complex::Undefined(); } @@ -46,7 +46,7 @@ Evaluation FactorNode::templatedApproximate(Context * context, Preferences::C } -Multiplication Factor::createMultiplicationOfIntegerPrimeDecomposition(Integer i, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Multiplication Factor::createMultiplicationOfIntegerPrimeDecomposition(Integer i) const { assert(!i.isZero()); assert(!i.isNegative()); Multiplication m = Multiplication::Builder(); @@ -85,7 +85,7 @@ Expression Factor::shallowReduce(Context * context) { return *this; } -Expression Factor::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression Factor::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { Expression c = childAtIndex(0); if (c.type() != ExpressionNode::Type::Rational) { return replaceWithUndefinedInPlace(); @@ -95,13 +95,13 @@ Expression Factor::shallowBeautify(ExpressionNode::ReductionContext reductionCon replaceWithInPlace(r); return std::move(r); } - Multiplication numeratorDecomp = createMultiplicationOfIntegerPrimeDecomposition(r.unsignedIntegerNumerator(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Multiplication numeratorDecomp = createMultiplicationOfIntegerPrimeDecomposition(r.unsignedIntegerNumerator()); if (numeratorDecomp.numberOfChildren() == 0) { return replaceWithUndefinedInPlace(); } Expression result = numeratorDecomp.squashUnaryHierarchyInPlace(); if (!r.isInteger()) { - Multiplication denominatorDecomp = createMultiplicationOfIntegerPrimeDecomposition(r.integerDenominator(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Multiplication denominatorDecomp = createMultiplicationOfIntegerPrimeDecomposition(r.integerDenominator()); if (denominatorDecomp.numberOfChildren() == 0) { return replaceWithUndefinedInPlace(); } diff --git a/poincare/src/float.cpp b/poincare/src/float.cpp index cdfd918d7..f26af803d 100644 --- a/poincare/src/float.cpp +++ b/poincare/src/float.cpp @@ -18,7 +18,7 @@ int FloatNode::simplificationOrderSameType(const ExpressionNode * e, bool asc if (!ascending) { return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); } - assert(e->type() == ExpressionNode::Type::Float); + assert((e->type() == ExpressionNode::Type::Float && sizeof(T) == sizeof(float)) || (e->type() == ExpressionNode::Type::Double && sizeof(T) == sizeof(double))); const FloatNode * other = static_cast *>(e); if (value() < other->value()) { return -1; diff --git a/poincare/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index 43dc8f7c3..2ecc79453 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -161,6 +161,26 @@ LayoutNode * FractionLayoutNode::layoutToPointWhenInserting(Expression * corresp return this; } +bool FractionLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis > 0) { + return true; + } + + /* We do not want to absorb a fraction if something else is already being + * absorbed. This way, the user can write a product of fractions without + * typing the × sign. */ + Layout p = Layout(parent()); + assert(!p.isUninitialized() && p.type() == LayoutNode::Type::HorizontalLayout); + int indexInParent = p.indexOfChild(Layout(this)); + int indexOfAbsorbingSibling = indexInParent + (goingLeft ? 1 : -1); + assert(indexOfAbsorbingSibling >= 0 && indexOfAbsorbingSibling < p.numberOfChildren()); + Layout absorbingSibling = p.childAtIndex(indexOfAbsorbingSibling); + if (absorbingSibling.numberOfChildren() > 0) { + absorbingSibling = absorbingSibling.childAtIndex((goingLeft) ? absorbingSibling.leftCollapsingAbsorbingChildIndex() : absorbingSibling.rightCollapsingAbsorbingChildIndex()); + } + return absorbingSibling.type() == LayoutNode::Type::HorizontalLayout && absorbingSibling.isEmpty(); +} + void FractionLayoutNode::didCollapseSiblings(LayoutCursor * cursor) { if (cursor != nullptr) { cursor->setLayoutNode(denominatorLayout()); diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index ae5764af2..16832db33 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -38,21 +38,16 @@ int FunctionNode::getPolynomialCoefficients(Context * context, const char * symb int FunctionNode::getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable, int nextVariableIndex) const { Function f(this); Expression e = SymbolAbstract::Expand(f, context, true); + /* Since templatedApproximate always evaluates the argument, some apps (such + * as Statistics and Regression) need to be aware of it even when it does not + * appear in the formula. */ + nextVariableIndex = childAtIndex(0)->getVariables(context, isVariable, variables, maxSizeVariable, nextVariableIndex); if (e.isUninitialized()) { return nextVariableIndex; } return e.node()->getVariables(context, isVariable, variables, maxSizeVariable, nextVariableIndex); } -float FunctionNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - Function f(this); - Expression e = SymbolAbstract::Expand(f,context, true); - if (e.isUninitialized()) { - return 0.0f; - } - return e.characteristicXRange(context, angleUnit); -} - Layout FunctionNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return LayoutHelper::Prefix(Function(this), floatDisplayMode, numberOfSignificantDigits, name()); } @@ -69,26 +64,26 @@ Expression FunctionNode::deepReplaceReplaceableSymbols(Context * context, bool * return Function(this).deepReplaceReplaceableSymbols(context, didReplace, replaceFunctionsOnly, parameteredAncestorsCount); } -Evaluation FunctionNode::approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return templatedApproximate(context, complexFormat, angleUnit); +Evaluation FunctionNode::approximate(SinglePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); } -Evaluation FunctionNode::approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return templatedApproximate(context, complexFormat, angleUnit); +Evaluation FunctionNode::approximate(DoublePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); } template -Evaluation FunctionNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - if (childAtIndex(0)->approximate((T)1, context, complexFormat, angleUnit).isUndefined()) { +Evaluation FunctionNode::templatedApproximate(ApproximationContext approximationContext) const { + if (childAtIndex(0)->approximate((T)1, approximationContext).isUndefined()) { return Complex::Undefined(); } Function f(this); - Expression e = SymbolAbstract::Expand(f, context, true); + Expression e = SymbolAbstract::Expand(f, approximationContext.context(), true); if (e.isUninitialized()) { return Complex::Undefined(); } - return e.node()->approximate(T(), context, complexFormat, angleUnit); + return e.node()->approximate(T(), approximationContext); } Function Function::Builder(const char * name, size_t length, Expression child) { diff --git a/poincare/src/great_common_divisor.cpp b/poincare/src/great_common_divisor.cpp index 55bdbf3c2..88637076f 100644 --- a/poincare/src/great_common_divisor.cpp +++ b/poincare/src/great_common_divisor.cpp @@ -1,19 +1,12 @@ #include - -#include #include #include -#include #include -#include -#include namespace Poincare { constexpr Expression::FunctionHelper GreatCommonDivisor::s_functionHelper; -int GreatCommonDivisorNode::numberOfChildren() const { return GreatCommonDivisor::s_functionHelper.numberOfChildren(); } - Layout GreatCommonDivisorNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return LayoutHelper::Prefix(GreatCommonDivisor(this), floatDisplayMode, numberOfSignificantDigits, GreatCommonDivisor::s_functionHelper.name()); } @@ -23,33 +16,27 @@ int GreatCommonDivisorNode::serialize(char * buffer, int bufferSize, Preferences } Expression GreatCommonDivisorNode::shallowReduce(ReductionContext reductionContext) { - return GreatCommonDivisor(this).shallowReduce(reductionContext.context()); + return GreatCommonDivisor(this).shallowReduce(reductionContext); +} + +Expression GreatCommonDivisorNode::shallowBeautify(ReductionContext * reductionContext) { + return GreatCommonDivisor(this).shallowBeautify(reductionContext->context()); } template -Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - bool isUndefined = false; - int a = ApproximationHelper::PositiveIntegerApproximationIfPossible(childAtIndex(0), &isUndefined, context, complexFormat, angleUnit); - int b = ApproximationHelper::PositiveIntegerApproximationIfPossible(childAtIndex(1), &isUndefined, context, complexFormat, angleUnit); - if (isUndefined) { - return Complex::RealUndefined(); - } - if (b > a) { - int temp = b; - b = a; - a = temp; - } - int r = 0; - while((int)b!=0){ - r = a - (a/b)*b; - a = b; - b = r; - } - return Complex::Builder(std::round((T)a)); +Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const { + return Arithmetic::GCD(*this, approximationContext); } +Expression GreatCommonDivisor::shallowBeautify(Context * context) { + /* Sort children in decreasing order: + * gcd(1,x,x^2) --> gcd(x^2,x,1) + * gcd(1,R(2)) --> gcd(R(2),1) */ + sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, false, canBeInterrupted); }, context, true, true); + return *this; +} -Expression GreatCommonDivisor::shallowReduce(Context * context) { +Expression GreatCommonDivisor::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -57,36 +44,27 @@ Expression GreatCommonDivisor::shallowReduce(Context * context) { return e; } } - Expression c0 = childAtIndex(0); - Expression c1 = childAtIndex(1); - if (c0.deepIsMatrix(context) || c1.deepIsMatrix(context)) { - return replaceWithUndefinedInPlace(); - } - if (c0.type() == ExpressionNode::Type::Rational) { - Rational r0 = static_cast(c0); - if (!r0.isInteger()) { - return replaceWithUndefinedInPlace(); - } - } - if (c1.type() == ExpressionNode::Type::Rational) { - Rational r1 = static_cast(c1); - if (!r1.isInteger()) { - return replaceWithUndefinedInPlace(); - } - } - if (c0.type() != ExpressionNode::Type::Rational || c1.type() != ExpressionNode::Type::Rational) { - return *this; - } - Rational r0 = static_cast(c0); - Rational r1 = static_cast(c1); + assert(numberOfChildren() > 0); + + // Step 0: Merge children which are GCD + mergeSameTypeChildrenInPlace(); + + // Step 1: check that all children are compatible + { + Expression checkChildren = checkChildrenAreRationalIntegersAndUpdate(reductionContext); + if (!checkChildren.isUninitialized()) { + return checkChildren; + } + } + + // Step 2: Compute GCD + Expression result = Arithmetic::GCD(*this); - Integer a = r0.signedIntegerNumerator(); - Integer b = r1.signedIntegerNumerator(); - Integer gcd = Arithmetic::GCD(a, b); - assert(!gcd.isOverflow()); - Expression result = Rational::Builder(gcd); replaceWithInPlace(result); return result; } +template Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const; + } diff --git a/poincare/src/helpers.cpp b/poincare/src/helpers.cpp index af8275f08..b8aae01e3 100644 --- a/poincare/src/helpers.cpp +++ b/poincare/src/helpers.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace Poincare { @@ -97,5 +98,15 @@ bool Rotate(uint32_t * dst, uint32_t * src, size_t len) { return true; } +void Sort(Swap swap, Compare compare, void * context, int numberOfElements) { + for (int i = 0; i < numberOfElements-1; i++) { + for (int j = 0; j < numberOfElements - i - 1; j++) { + if (compare(j, j+1, context, numberOfElements)) { + swap(j, j+1, context, numberOfElements); + } + } + } +} + } } diff --git a/poincare/src/horizontal_layout.cpp b/poincare/src/horizontal_layout.cpp index 0f743904b..7ec6f4fe2 100644 --- a/poincare/src/horizontal_layout.cpp +++ b/poincare/src/horizontal_layout.cpp @@ -217,7 +217,8 @@ int HorizontalLayoutNode::serializeChildrenBetweenIndexes(char * buffer, int buf || (nextChildType == LayoutNode::Type::NthRootLayout && !static_cast(nextChild)->isSquareRoot()) || nextChildType == LayoutNode::Type::ProductLayout - || nextChildType == LayoutNode::Type::SumLayout) + || nextChildType == LayoutNode::Type::SumLayout + || nextChildType == LayoutNode::Type::VectorNormLayout) && currentChild->canBeOmittedMultiplicationLeftFactor()) { assert(nextChildType != LayoutNode::Type::HorizontalLayout); diff --git a/poincare/src/hyperbolic_cosine.cpp b/poincare/src/hyperbolic_cosine.cpp index c963ee4ad..abbf1797f 100644 --- a/poincare/src/hyperbolic_cosine.cpp +++ b/poincare/src/hyperbolic_cosine.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -19,6 +21,24 @@ Complex HyperbolicCosineNode::computeOnComplex(const std::complex c, Prefe return Complex::Builder(ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::cosh(c), c)); } +bool HyperbolicCosineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return HyperbolicCosine(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression HyperbolicCosineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return HyperbolicCosine(this).unaryFunctionDifferential(reductionContext); +} + + +bool HyperbolicCosine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression HyperbolicCosine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return HyperbolicSine::Builder(childAtIndex(0).clone()); +} + template Complex Poincare::HyperbolicCosineNode::computeOnComplex(std::complex, Preferences::ComplexFormat, Preferences::AngleUnit); template Complex Poincare::HyperbolicCosineNode::computeOnComplex(std::complex, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit); diff --git a/poincare/src/hyperbolic_sine.cpp b/poincare/src/hyperbolic_sine.cpp index 237804747..15b477674 100644 --- a/poincare/src/hyperbolic_sine.cpp +++ b/poincare/src/hyperbolic_sine.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -19,6 +21,23 @@ Complex HyperbolicSineNode::computeOnComplex(const std::complex c, Prefere return Complex::Builder(ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::sinh(c), c)); } +bool HyperbolicSineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return HyperbolicSine(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression HyperbolicSineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return HyperbolicSine(this).unaryFunctionDifferential(reductionContext); +} + +bool HyperbolicSine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression HyperbolicSine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return HyperbolicCosine::Builder(childAtIndex(0).clone()); +} + template Complex Poincare::HyperbolicSineNode::computeOnComplex(std::complex, Preferences::ComplexFormat, Preferences::AngleUnit); template Complex Poincare::HyperbolicSineNode::computeOnComplex(std::complex, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit); diff --git a/poincare/src/hyperbolic_tangent.cpp b/poincare/src/hyperbolic_tangent.cpp index 48a5e8830..c8dff63cf 100644 --- a/poincare/src/hyperbolic_tangent.cpp +++ b/poincare/src/hyperbolic_tangent.cpp @@ -1,5 +1,8 @@ #include +#include +#include #include +#include #include namespace Poincare { @@ -19,6 +22,23 @@ Complex HyperbolicTangentNode::computeOnComplex(const std::complex c, Pref return Complex::Builder(ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::tanh(c), c)); } +bool HyperbolicTangentNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return HyperbolicTangent(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression HyperbolicTangentNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return HyperbolicTangent(this).unaryFunctionDifferential(reductionContext); +} + +bool HyperbolicTangent::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression HyperbolicTangent::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return Power::Builder(HyperbolicCosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2)); +} + template Complex Poincare::HyperbolicTangentNode::computeOnComplex(std::complex, Preferences::ComplexFormat, Preferences::AngleUnit); template Complex Poincare::HyperbolicTangentNode::computeOnComplex(std::complex, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit); diff --git a/poincare/src/hyperbolic_trigonometric_function.cpp b/poincare/src/hyperbolic_trigonometric_function.cpp index 676d6a724..34b635489 100644 --- a/poincare/src/hyperbolic_trigonometric_function.cpp +++ b/poincare/src/hyperbolic_trigonometric_function.cpp @@ -26,7 +26,7 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct } // Step 1. Notable values - if (node()->isNotableValue(c)) { + if (node()->isNotableValue(c, reductionContext.context())) { Expression result = node()->imageOfNotableValue(); replaceWithInPlace(result); return result; @@ -45,7 +45,8 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct && e.approximateToScalar( reductionContext.context(), reductionContext.complexFormat(), - reductionContext.angleUnit()) >= 1.0)) + reductionContext.angleUnit(), + true) >= 1.0)) { result = e; } @@ -58,7 +59,8 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct && e.approximateToScalar( reductionContext.context(), reductionContext.complexFormat(), - reductionContext.angleUnit()) >= 0.0)) + reductionContext.angleUnit(), + true) >= 0.0)) { result = e; } @@ -79,7 +81,8 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct && std::fabs(e.approximateToScalar( reductionContext.context(), reductionContext.complexFormat(), - reductionContext.angleUnit())) < 1.0)) + reductionContext.angleUnit(), + true)) < 1.0)) { result = e; } diff --git a/poincare/src/infinity.cpp b/poincare/src/infinity.cpp index 8a537a68e..312e74334 100644 --- a/poincare/src/infinity.cpp +++ b/poincare/src/infinity.cpp @@ -31,6 +31,10 @@ template Evaluation InfinityNode::templatedApproximate() const { return Complex::Builder(m_negative ? -INFINITY : INFINITY); } +bool InfinityNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Infinity(this).derivate(reductionContext, symbol, symbolValue); +} + Infinity Infinity::Builder(bool negative) { void * bufferNode = TreePool::sharedPool()->alloc(sizeof(InfinityNode)); InfinityNode * node = new (bufferNode) InfinityNode(negative); @@ -45,6 +49,11 @@ Expression Infinity::setSign(ExpressionNode::Sign s) { return result; } +bool Infinity::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + replaceWithUndefinedInPlace(); + return true; +} + template Evaluation InfinityNode::templatedApproximate() const; template Evaluation InfinityNode::templatedApproximate() const; } diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index 6cbe5b91c..fc67dda66 100644 --- a/poincare/src/integer.cpp +++ b/poincare/src/integer.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include extern "C" { @@ -685,9 +687,18 @@ IntegerDivision Integer::udiv(const Integer & numerator, const Integer & denomin } Expression Integer::CreateMixedFraction(const Integer & num, const Integer & denom) { - Expression quo = DivisionQuotient::Reduce(num, denom); - Expression rem = DivisionRemainder::Reduce(num, denom); - return Addition::Builder(quo, Division::Builder(rem, Rational::Builder(denom))); + Integer numPositive(num), denomPositive(denom); + numPositive.setNegative(false); + denomPositive.setNegative(false); + Expression quo = DivisionQuotient::Reduce(numPositive, denomPositive); + Expression rem = DivisionRemainder::Reduce(numPositive, denomPositive); + if (num.isNegative() == denom.isNegative()) { + return Addition::Builder(quo, Division::Builder(rem, Rational::Builder(denomPositive))); + } + return Subtraction::Builder( + /* Do not add a minus sign before a zero. */ + (NaturalOrder(numPositive, denomPositive) < 0) ? quo : Opposite::Builder(quo), + Division::Builder(rem, Rational::Builder(denomPositive))); } @@ -695,7 +706,7 @@ Expression Integer::CreateEuclideanDivision(const Integer & num, const Integer & Expression quo = DivisionQuotient::Reduce(num, denom); Expression rem = DivisionRemainder::Reduce(num, denom); Expression e = Equal::Builder(Rational::Builder(num), Addition::Builder(Multiplication::Builder(Rational::Builder(denom), quo), rem)); - ExpressionNode::ReductionContext defaultReductionContext = ExpressionNode::ReductionContext(nullptr, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Radian, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + ExpressionNode::ReductionContext defaultReductionContext = ExpressionNode::ReductionContext(nullptr, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Radian, Preferences::UnitFormat::Metric, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); e = e.deepBeautify(defaultReductionContext); return e; } diff --git a/poincare/src/integral.cpp b/poincare/src/integral.cpp index 55641980a..7c4643855 100644 --- a/poincare/src/integral.cpp +++ b/poincare/src/integral.cpp @@ -44,35 +44,36 @@ Expression IntegralNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation IntegralNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aInput = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); - Evaluation bInput = childAtIndex(3)->approximate(T(), context, complexFormat, angleUnit); +Evaluation IntegralNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aInput = childAtIndex(2)->approximate(T(), approximationContext); + Evaluation bInput = childAtIndex(3)->approximate(T(), approximationContext); T a = aInput.toScalar(); T b = bInput.toScalar(); if (std::isnan(a) || std::isnan(b)) { return Complex::RealUndefined(); } #ifdef LAGRANGE_METHOD - T result = lagrangeGaussQuadrature(a, b, context, complexFormat, angleUnit); + T result = lagrangeGaussQuadrature(a, b, approximationContext); #else - T result = adaptiveQuadrature(a, b, 0.1, k_maxNumberOfIterations, context, complexFormat, angleUnit); + T result = adaptiveQuadrature(a, b, 0.1, k_maxNumberOfIterations, approximationContext); #endif return Complex::Builder(result); } template -T IntegralNode::functionValueAtAbscissa(T x, Context * xcontext, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +T IntegralNode::functionValueAtAbscissa(T x, ApproximationContext approximationContext) const { // Here we cannot use Expression::approximateWithValueForSymbol which would reset the sApproximationEncounteredComplex flag assert(childAtIndex(1)->type() == Type::Symbol); - VariableContext variableContext = VariableContext(static_cast(childAtIndex(1))->name(), xcontext); + VariableContext variableContext = VariableContext(static_cast(childAtIndex(1))->name(), approximationContext.context()); variableContext.setApproximationForVariable(x); - return childAtIndex(0)->approximate(T(), &variableContext, complexFormat, angleUnit).toScalar(); + approximationContext.setContext(&variableContext); + return childAtIndex(0)->approximate(T(), approximationContext).toScalar(); } #ifdef LAGRANGE_METHOD template -T IntegralNode::lagrangeGaussQuadrature(T a, T b, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +T IntegralNode::lagrangeGaussQuadrature(T a, T b, ApproximationContext approximationContext) const { /* We here use Gauss-Legendre quadrature with n = 5 * Gauss-Legendre abscissae and weights can be found in * C/C++ library source code. */ @@ -86,11 +87,11 @@ T IntegralNode::lagrangeGaussQuadrature(T a, T b, Context * context, Preferences T result = 0; for (int j = 0; j < 10; j++) { T dx = xr * x[j]; - T evaluationAfterX = functionValueAtAbscissa(xm+dx, context, complexFormat, angleUnit); + T evaluationAfterX = functionValueAtAbscissa(xm+dx, approximationContext); if (std::isnan(evaluationAfterX)) { return NAN; } - T evaluationBeforeX = functionValueAtAbscissa(xm-dx, context, complexFormat, angleUnit); + T evaluationBeforeX = functionValueAtAbscissa(xm-dx, approximationContext); if (std::isnan(evaluationBeforeX)) { return NAN; } @@ -103,7 +104,7 @@ T IntegralNode::lagrangeGaussQuadrature(T a, T b, Context * context, Preferences #else template -IntegralNode::DetailedResult IntegralNode::kronrodGaussQuadrature(T a, T b, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +IntegralNode::DetailedResult IntegralNode::kronrodGaussQuadrature(T a, T b, ApproximationContext approximationContext) const { static T epsilon = sizeof(T) == sizeof(double) ? DBL_EPSILON : FLT_EPSILON; static T max = sizeof(T) == sizeof(double) ? DBL_MAX : FLT_MAX; /* We here use Kronrod-Legendre quadrature with n = 21 @@ -137,7 +138,7 @@ IntegralNode::DetailedResult IntegralNode::kronrodGaussQuadrature(T a, T b, C errorResult.absoluteError = 0; T gaussIntegral = 0; - T fCenter = functionValueAtAbscissa(center, context, complexFormat, angleUnit); + T fCenter = functionValueAtAbscissa(center, approximationContext); if (std::isnan(fCenter)) { return errorResult; } @@ -145,11 +146,11 @@ IntegralNode::DetailedResult IntegralNode::kronrodGaussQuadrature(T a, T b, C T absKronrodIntegral = std::fabs(kronrodIntegral); for (int j = 0; j < 10; j++) { T xDelta = halfLength * x[j]; - T fval1 = functionValueAtAbscissa(center - xDelta, context, complexFormat, angleUnit); + T fval1 = functionValueAtAbscissa(center - xDelta, approximationContext); if (std::isnan(fval1)) { return errorResult; } - T fval2 = functionValueAtAbscissa(center + xDelta, context, complexFormat, angleUnit); + T fval2 = functionValueAtAbscissa(center + xDelta, approximationContext); if (std::isnan(fval2)) { return errorResult; } @@ -187,17 +188,17 @@ IntegralNode::DetailedResult IntegralNode::kronrodGaussQuadrature(T a, T b, C } template -T IntegralNode::adaptiveQuadrature(T a, T b, T eps, int numberOfIterations, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +T IntegralNode::adaptiveQuadrature(T a, T b, T eps, int numberOfIterations, ApproximationContext approximationContext) const { if (Expression::ShouldStopProcessing()) { return NAN; } - DetailedResult quadKG = kronrodGaussQuadrature(a, b, context, complexFormat, angleUnit); + DetailedResult quadKG = kronrodGaussQuadrature(a, b, approximationContext); T result = quadKG.integral; if (quadKG.absoluteError <= eps) { return result; } else if (--numberOfIterations > 0) { T m = (a+b)/2; - return adaptiveQuadrature(a, m, eps/2, numberOfIterations, context, complexFormat, angleUnit) + adaptiveQuadrature(m, b, eps/2, numberOfIterations, context, complexFormat, angleUnit); + return adaptiveQuadrature(a, m, eps/2, numberOfIterations, approximationContext) + adaptiveQuadrature(m, b, eps/2, numberOfIterations, approximationContext); } else { return NAN; } diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index b9524c489..9729fb9e6 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -9,17 +9,27 @@ namespace Poincare { const uint8_t topSymbolPixel[IntegralLayoutNode::k_symbolHeight][IntegralLayoutNode::k_symbolWidth] = { - {0x00, 0x00, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x00, 0xFF}, - {0xFF, 0xFF, 0x00, 0x00}, - {0xFF, 0xFF, 0x00, 0x00}, + {0xFF, 0xD5, 0x46, 0x09}, + {0xF1, 0x1A, 0x93, 0xF0}, + {0x98, 0x54, 0xFF, 0xFF}, + {0x53, 0xA7, 0xFF, 0xFF}, + {0x29, 0xCF, 0xFF, 0xFF}, + {0x14, 0xEA, 0xFF, 0xFF}, + {0x02, 0xFC, 0xFF, 0xFF}, + {0x00, 0xFF, 0xFF, 0xFF}, + {0x00, 0xFF, 0xFF, 0xFF}, }; const uint8_t bottomSymbolPixel[IntegralLayoutNode::k_symbolHeight][IntegralLayoutNode::k_symbolWidth] = { - {0x00, 0x00, 0xFF, 0xFF}, - {0x00, 0x00, 0xFF, 0xFF}, - {0xFF, 0x00, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x00, 0x00}, + {0xFF, 0xFF, 0xFF, 0x00}, + {0xFF, 0xFF, 0xFF, 0x00}, + {0xFF, 0xFF, 0xFE, 0x03}, + {0xFF, 0xFF, 0xEA, 0x13}, + {0xFF, 0xFF, 0xCF, 0x29}, + {0xFF, 0xFF, 0xA5, 0x53}, + {0xFF, 0xFF, 0x54, 0x99}, + {0xF2, 0x95, 0x1A, 0xF1}, + {0x09, 0x46, 0xD5, 0xFF}, }; void IntegralLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool forSelection) { @@ -208,20 +218,178 @@ CodePoint IntegralLayoutNode::XNTCodePoint(int childIndex) const { return (childIndex == k_integrandLayoutIndex || childIndex == k_differentialLayoutIndex) ? CodePoint('x') : UCodePointNull; } +// Return pointer to the first or the last integral from left to right (considering multiple integrals in a row) +IntegralLayoutNode * IntegralLayoutNode::mostNestedIntegral(NestedPosition position) { + IntegralLayoutNode * p = this; + IntegralLayoutNode * integral = p->nestedIntegral(position); + while (integral != nullptr) { + p = integral; + integral = integral->nestedIntegral(position); + } + return p; +} + +// Return pointer to the integral immediately on the right. If none is found, return nullptr +IntegralLayoutNode * IntegralLayoutNode::nextNestedIntegral() { + LayoutNode * integrand = integrandLayout(); + if (integrand->type() == Type::IntegralLayout) { + // Integral can be directly in the integrand + return static_cast(integrand); + } else if (integrand->type() == Type::HorizontalLayout && integrand->numberOfChildren() == 1 && integrand->childAtIndex(0)->type() == Type::IntegralLayout) { + // Or can be in a Horizontal layout that only contains an integral + integrand = integrand->childAtIndex(0); + return static_cast(integrand); + } + return nullptr; +} + +// Return pointer to the integral immediately on the left. If none is found, return nullptr +IntegralLayoutNode * IntegralLayoutNode::previousNestedIntegral() { + assert(type() == Type::IntegralLayout); + LayoutNode * p = parent(); + if (p == nullptr) { + return nullptr; + } + if (p->type() == Type::IntegralLayout) { + // Parent is an integral. Checking if the child is its integrand or not + if (p->childAtIndex(0) == this) { + return static_cast(p); + } else { + // If this is not parent's integrand, it means that it is either a bound or differential + return nullptr; + } + } else if (p->type() == Type::HorizontalLayout) { + // Or can be a Horizontal layout himself contained in an integral + LayoutNode * prev = p->parent(); + if (prev == nullptr) { + return nullptr; + } + if (p->numberOfChildren() == 1 && prev->type() == Type::IntegralLayout) { + // We can consider the integrals in a row only if the horizontal layout just contains an integral + // The horizontal layout must be the previous integral's integrand + if (prev->childAtIndex(0) == p) { + return static_cast(prev); + } + } + } + return nullptr; +} + +/* Return the height of the tallest upper/lower bound amongst a row of integrals + * If the integral is alone, will return its upper/lower bound height*/ +KDCoordinate IntegralLayoutNode::boundMaxHeight(BoundPosition position) { + IntegralLayoutNode * p = mostNestedIntegral(NestedPosition::Next); + LayoutNode * bound = p->boundLayout(position); + KDCoordinate max = bound->layoutSize().height(); + IntegralLayoutNode * first = mostNestedIntegral(NestedPosition::Previous); + while (p != first) { + p = p->previousNestedIntegral(); + bound = p->boundLayout(position); + max = std::max(max, bound->layoutSize().height()); + } + return max; +} + +/* +Window configuration explained : +Vertical margins and offsets ++-----------------------------------------------------------------+ +| | | +| k_boundVerticalMargin | +| | | +| +------------------+ | +| | upperBoundHeight | | +| +------------------+ | +| +++ | | | +| +++ k_symbolHeight k_integrandVerticalMargin | +| +++ | | | +| | +| ||| | +| ||| | +| ||| | +| ||| | +| ||| centralArgumentHeight | +| ||| | +| ||| | +| ||| | +| ||| | +| ||| | +| | +| +++ | | | +| +++ k_symbolHeight k_integrandVerticalMargin | +| +++ | | | +| +------------------+ | +| | lowerBoundHeight | | +| +------------------+ | +| | | +| k_boundVerticalMargin | +| | | ++-----------------------------------------------------------------+ + +Horizontal margins and offsets ++-------------------------------------------------------------------------------------------------------+ +| | | +| | | |+---------------+| | | +| |k_symbolWidth|k_boundHorizontalMargin||upperBoundWidth||k_integrandHorizontalMargin| | +| | | |+---------------+| | | +| *** | +| *** | +| *** | | +| *** | +| *** | +| *** | | +| ||| | +| ||| | +| ||| | | +| ||| | +| ||| x dx | +| ||| | +| ||| | | +| ||| | +| ||| | +| ||| | | +| ||| | +| ||| | +| *** | | +| *** | +| *** | +| *** | | +| *** | +| *** | +| | | | |+---------------+| | | +| |k_symbolWidth| a |k_boundHorizontalMargin||lowerBoundWidth||k_integrandHorizontalMargin| | +| | | | |+---------------+| | | +| | ++-------------------------------------------------------------------------------------------------------+ +||| = k_lineThickness +a = k_symbolWidth - k_lineThickness +*/ + KDSize IntegralLayoutNode::computeSize() { KDSize dSize = k_font->stringSize("d"); KDSize integrandSize = integrandLayout()->layoutSize(); KDSize differentialSize = differentialLayout()->layoutSize(); KDSize lowerBoundSize = lowerBoundLayout()->layoutSize(); KDSize upperBoundSize = upperBoundLayout()->layoutSize(); - KDCoordinate width = k_symbolWidth+k_lineThickness+k_boundWidthMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin+integrandSize.width()+2*k_differentialWidthMargin+dSize.width()+differentialSize.width(); - KDCoordinate baseline = computeBaseline(); - KDCoordinate height = baseline + k_integrandHeigthMargin+std::max(integrandSize.height()-integrandLayout()->baseline(), differentialSize.height()-differentialLayout()->baseline())+lowerBoundSize.height(); + KDCoordinate width = k_symbolWidth+k_lineThickness+k_boundHorizontalMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandHorizontalMargin+integrandSize.width()+k_differentialHorizontalMargin+dSize.width()+k_differentialHorizontalMargin+differentialSize.width(); + IntegralLayoutNode * last = mostNestedIntegral(NestedPosition::Next); + KDCoordinate height; + if (this == last) { + height = k_boundVerticalMargin + boundMaxHeight(BoundPosition::UpperBound) + k_integrandVerticalMargin + centralArgumentHeight() + k_integrandVerticalMargin + boundMaxHeight(BoundPosition::LowerBound) + k_boundVerticalMargin; + } else { + height = last->layoutSize().height(); + } return KDSize(width, height); } KDCoordinate IntegralLayoutNode::computeBaseline() { - return upperBoundLayout()->layoutSize().height() + k_integrandHeigthMargin + std::max(integrandLayout()->baseline(), differentialLayout()->baseline()); + IntegralLayoutNode * last = mostNestedIntegral(NestedPosition::Next); + if (this == last) { + return k_boundVerticalMargin + boundMaxHeight(BoundPosition::UpperBound) + k_integrandVerticalMargin + std::max(integrandLayout()->baseline(), differentialLayout()->baseline()); + } else { + // If integrals are in a row, they must have the same baseline. Since the last integral has the lowest, we take this one for all the others + return last->baseline(); + } } KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { @@ -229,45 +397,64 @@ KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { KDSize upperBoundSize = upperBoundLayout()->layoutSize(); KDCoordinate x = 0; KDCoordinate y = 0; + KDCoordinate boundOffset = 2*k_symbolWidth - k_lineThickness + k_boundHorizontalMargin; if (child == lowerBoundLayout()) { - x = k_symbolWidth+k_lineThickness+k_boundWidthMargin; - y = computeSize().height()-lowerBoundSize.height(); + x = boundOffset; + y = computeSize().height() - k_boundVerticalMargin - boundMaxHeight(BoundPosition::LowerBound); } else if (child == upperBoundLayout()) { - x = k_symbolWidth+k_lineThickness+k_boundWidthMargin;; - y = 0; + x = boundOffset; + y = k_boundVerticalMargin + boundMaxHeight(BoundPosition::UpperBound) - upperBoundSize.height(); } else if (child == integrandLayout()) { - x = k_symbolWidth +k_lineThickness+ k_boundWidthMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin; + x = boundOffset + std::max(lowerBoundSize.width(), upperBoundSize.width()) + k_integrandHorizontalMargin; y = computeBaseline()-integrandLayout()->baseline(); - } else if (child == differentialLayout()) { - x = computeSize().width() - k_differentialWidthMargin - differentialLayout()->layoutSize().width(); - y = computeBaseline()-differentialLayout()->baseline(); } else { - assert(false); + assert(child == differentialLayout()); + x = computeSize().width() - differentialLayout()->layoutSize().width(); + y = computeBaseline()-differentialLayout()->baseline(); } return KDPoint(x,y); } +KDCoordinate IntegralLayoutNode::centralArgumentHeight() { + // When integrals are in a row, the last one is the tallest. We take its central argument height to define the one of the others integrals + IntegralLayoutNode * last = mostNestedIntegral(NestedPosition::Next); + if (this == last) { + KDCoordinate integrandHeight = integrandLayout()->layoutSize().height(); + KDCoordinate integrandBaseline = integrandLayout()->baseline(); + KDCoordinate differentialHeight = differentialLayout()->layoutSize().height(); + KDCoordinate differentialBaseline = differentialLayout()->baseline(); + return std::max(integrandBaseline, differentialBaseline) + std::max(integrandHeight-integrandBaseline, differentialHeight - differentialBaseline); + } else { + return last->centralArgumentHeight(); + } +} + void IntegralLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { KDSize integrandSize = integrandLayout()->layoutSize(); - KDSize differentialSize = differentialLayout()->layoutSize(); - KDSize upperBoundSize = upperBoundLayout()->layoutSize(); - KDCoordinate centralArgumentHeight = std::max(integrandLayout()->baseline(), differentialLayout()->baseline()) + std::max(integrandSize.height()-integrandLayout()->baseline(), differentialSize.height()-differentialLayout()->baseline()); - + KDCoordinate centralArgHeight = centralArgumentHeight(); KDColor workingBuffer[k_symbolWidth*k_symbolHeight]; + // Render the integral symbol - KDRect topSymbolFrame(p.x() + k_symbolWidth + k_lineThickness, p.y() + upperBoundSize.height() - k_boundHeightMargin, - k_symbolWidth, k_symbolHeight); + KDCoordinate offsetX = p.x() + k_symbolWidth; + KDCoordinate offsetY = p.y() + k_boundVerticalMargin + boundMaxHeight(BoundPosition::UpperBound) + k_integrandVerticalMargin - k_symbolHeight; + + // Upper part + KDRect topSymbolFrame(offsetX, offsetY, k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(topSymbolFrame, expressionColor, (const uint8_t *)topSymbolPixel, (KDColor *)workingBuffer); - KDRect bottomSymbolFrame(p.x(), - p.y() + upperBoundSize.height() + 2*k_integrandHeigthMargin + centralArgumentHeight + k_boundHeightMargin - k_symbolHeight, - k_symbolWidth, k_symbolHeight); + + // Central bar + offsetY = offsetY + k_symbolHeight; + ctx->fillRect(KDRect(offsetX, offsetY, k_lineThickness, centralArgHeight), expressionColor); + + // Lower part + offsetX = offsetX - k_symbolWidth + k_lineThickness; + offsetY = offsetY + centralArgHeight; + KDRect bottomSymbolFrame(offsetX,offsetY,k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(bottomSymbolFrame, expressionColor, (const uint8_t *)bottomSymbolPixel, (KDColor *)workingBuffer); - ctx->fillRect(KDRect(p.x() + k_symbolWidth, p.y() + upperBoundSize.height() - k_boundHeightMargin, k_lineThickness, - 2*k_boundHeightMargin+2*k_integrandHeigthMargin+centralArgumentHeight), expressionColor); // Render "d" - KDPoint dPosition = p.translatedBy(positionOfChild(integrandLayout())).translatedBy(KDPoint(integrandSize.width() + k_differentialWidthMargin, integrandLayout()->baseline() - k_font->glyphSize().height()/2)); + KDPoint dPosition = p.translatedBy(positionOfChild(integrandLayout())).translatedBy(KDPoint(integrandSize.width() + k_differentialHorizontalMargin, integrandLayout()->baseline() - k_font->glyphSize().height()/2)); ctx->drawString("d", dPosition, k_font, expressionColor, backgroundColor); } diff --git a/poincare/src/inv_binom.cpp b/poincare/src/inv_binom.cpp index 0b5cc6e7c..3e75b3e1c 100644 --- a/poincare/src/inv_binom.cpp +++ b/poincare/src/inv_binom.cpp @@ -26,10 +26,10 @@ Expression InvBinomNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation InvBinomNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation nEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation pEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation InvBinomNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation nEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation pEvaluation = childAtIndex(2)->approximate(T(), approximationContext); T a = aEvaluation.toScalar(); T n = nEvaluation.toScalar(); diff --git a/poincare/src/inv_norm.cpp b/poincare/src/inv_norm.cpp index 7971f8ac0..1fbbec3d7 100644 --- a/poincare/src/inv_norm.cpp +++ b/poincare/src/inv_norm.cpp @@ -26,14 +26,14 @@ Expression InvNormNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation InvNormNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation muEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation varEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation InvNormNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation muEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation sigmaEvaluation = childAtIndex(2)->approximate(T(), approximationContext); T a = aEvaluation.toScalar(); T mu = muEvaluation.toScalar(); - T sigma = std::sqrt(varEvaluation.toScalar()); + T sigma = sigmaEvaluation.toScalar(); // CumulativeDistributiveInverseForProbability handles bad mu and var values return Complex::Builder(NormalDistribution::CumulativeDistributiveInverseForProbability(a, mu, sigma)); diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 9a810cd28..bc81e3ec1 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -18,6 +18,13 @@ Layout Layout::clone() const { return cast; } +Layout Layout::LayoutFromAddress(const void * address, size_t size) { + if (address == nullptr || size == 0) { + return Layout(); + } + return Layout(static_cast(TreePool::sharedPool()->copyTreeFromAddress(address, size))); +} + int Layout::serializeParsedExpression(char * buffer, int bufferSize, Context * context) const { /* This method fixes the following problem: * Some layouts have a special serialization so they can be parsed afterwards, @@ -169,7 +176,13 @@ void Layout::addSibling(LayoutCursor * cursor, Layout sibling, bool moveCursor) assert(!p.isUninitialized()); if (p.type() == LayoutNode::Type::HorizontalLayout) { int indexInParent = p.indexOfChild(*this); - int siblingIndex = cursor->position() == LayoutCursor::Position::Left ? indexInParent : indexInParent + 1; + int indexOfCursor = p.indexOfChild(cursor->layout()); + /* indexOfCursor == -1 if cursor->layout() is not a child of p. This should + * never happen, as addSibling is only called from inside + * LayoutField::handleEventWithText, and LayoutField is supposed to keep + * its cursor up to date.*/ + assert(indexOfCursor >= 0); + int siblingIndex = cursor->position() == LayoutCursor::Position::Left ? indexOfCursor : indexOfCursor + 1; /* Special case: If the neighbour sibling is a VerticalOffsetLayout, let it * handle the insertion of the new sibling. Do not enter the special case if diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 926c17bba..d0ab32949 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -71,9 +71,30 @@ void LayoutCursor::move(Direction direction, bool * shouldRecomputeLayout, bool } } -LayoutCursor LayoutCursor::cursorAtDirection(Direction direction, bool * shouldRecomputeLayout, bool forSelection) { +LayoutCursor LayoutCursor::cursorAtDirection(Direction direction, bool * shouldRecomputeLayout, bool forSelection, int step) { LayoutCursor result = clone(); + if (step <= 0) { + return result; + } + // First step result.move(direction, shouldRecomputeLayout, forSelection); + + if (step == 1 || !result.isDefined()) { + // If first step is undefined, it is returned so the situation is handled + return result; + } + // Otherwise, as many steps as possible are performed + LayoutCursor result_temp = result; + for (int i = 1; i < step; ++i) { + result_temp.move(direction, shouldRecomputeLayout, forSelection); + if (!result_temp.isDefined()) { + // Return last successful result + return result; + } + // Update last successful result + result = result_temp; + assert(result.isDefined()); + } return result; } @@ -87,6 +108,12 @@ void LayoutCursor::select(Direction direction, bool * shouldRecomputeLayout, Lay } } +LayoutCursor LayoutCursor::selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection) { + LayoutCursor result = clone(); + result.select(direction, shouldRecomputeLayout, selection); + return result; +} + /* Layout modification */ void LayoutCursor::addEmptyExponentialLayout() { @@ -101,9 +128,9 @@ void LayoutCursor::addEmptyExponentialLayout() { void LayoutCursor::addEmptyMatrixLayout() { MatrixLayout matrixLayout = MatrixLayout::Builder( EmptyLayout::Builder(EmptyLayoutNode::Color::Yellow), - EmptyLayout::Builder(EmptyLayoutNode::Color::Grey), - EmptyLayout::Builder(EmptyLayoutNode::Color::Grey), - EmptyLayout::Builder(EmptyLayoutNode::Color::Grey)); + EmptyLayout::Builder(EmptyLayoutNode::Color::Gray), + EmptyLayout::Builder(EmptyLayoutNode::Color::Gray), + EmptyLayout::Builder(EmptyLayoutNode::Color::Gray)); m_layout.addSibling(this, matrixLayout, false); m_layout = matrixLayout.childAtIndex(0); m_position = Position::Right; @@ -222,6 +249,8 @@ void LayoutCursor::addEmptyPowerLayout() { void LayoutCursor::addEmptySquarePowerLayout() { VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Position::Superscript); privateAddEmptyPowerLayout(offsetLayout); + m_layout = offsetLayout; + m_position = Position::Right; } void LayoutCursor::addEmptyTenPowerLayout() { @@ -294,12 +323,12 @@ void LayoutCursor::insertText(const char * text, bool forceCursorRightOfText) { } if (codePoint == UCodePointMultiplicationSign) { newChild = CodePointLayout::Builder(UCodePointMultiplicationSign); - } else if (codePoint == '(') { + } else if (codePoint == '(' || codePoint == UCodePointLeftSystemParenthesis) { newChild = LeftParenthesisLayout::Builder(); if (pointedChild.isUninitialized()) { pointedChild = newChild; } - } else if (codePoint == ')') { + } else if (codePoint == ')' || codePoint == UCodePointRightSystemParenthesis) { newChild = RightParenthesisLayout::Builder(); } /* We never insert text with brackets for now. Removing this code saves the @@ -505,7 +534,7 @@ void LayoutCursor::selectUpDown(bool up, bool * shouldRecomputeLayout, Layout * * position). This ancestor will be the added selection. * * The current layout might have been detached from its parent, for instance - * if it was a grey empty layout of a matrix and the cursor move exited this + * if it was a gray empty layout of a matrix and the cursor move exited this * matrix. In this case, use the layout parent (it should still be attached to * the main layout). */ diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 6058df3f2..84b001496 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -118,21 +118,21 @@ LayoutNode * LayoutNode::layoutToPointWhenInserting(Expression * correspondingEx return numberOfChildren() > 0 ? childAtIndex(0) : this; } -bool LayoutNode::removeGreySquaresFromAllMatrixAncestors() { +bool LayoutNode::removeGraySquaresFromAllMatrixAncestors() { bool result = false; - changeGreySquaresOfAllMatrixRelatives(false, true, &result); + changeGraySquaresOfAllMatrixRelatives(false, true, &result); return result; } -bool LayoutNode::removeGreySquaresFromAllMatrixChildren() { +bool LayoutNode::removeGraySquaresFromAllMatrixChildren() { bool result = false; - changeGreySquaresOfAllMatrixRelatives(false, false, &result); + changeGraySquaresOfAllMatrixRelatives(false, false, &result); return result; } -bool LayoutNode::addGreySquaresToAllMatrixAncestors() { +bool LayoutNode::addGraySquaresToAllMatrixAncestors() { bool result = false; - changeGreySquaresOfAllMatrixRelatives(true, true, &result); + changeGraySquaresOfAllMatrixRelatives(true, true, &result); return result; } @@ -215,7 +215,7 @@ void LayoutNode::moveCursorInDescendantsVertically(VerticalDirection direction, // If there is a valid result Layout resultRef(childResult); if ((*childResultPtr) != nullptr) { - *shouldRecomputeLayout = childResult->addGreySquaresToAllMatrixAncestors(); + *shouldRecomputeLayout = childResult->addGraySquaresToAllMatrixAncestors(); // WARNING: Do not use "this" afterwards } cursor->setLayout(resultRef); @@ -260,36 +260,36 @@ void LayoutNode::scoreCursorInDescendantsVertically ( } } -bool addRemoveGreySquaresInLayoutIfNeeded(bool add, Layout * l) { +bool addRemoveGraySquaresInLayoutIfNeeded(bool add, Layout * l) { if (l->type() != LayoutNode::Type::MatrixLayout) { return false; } if (add) { - static_cast(l->node())->addGreySquares(); + static_cast(l->node())->addGraySquares(); } else { - static_cast(l->node())->removeGreySquares(); + static_cast(l->node())->removeGraySquares(); } return true; } -void LayoutNode::changeGreySquaresOfAllMatrixRelatives(bool add, bool ancestors, bool * changedSquares) { +void LayoutNode::changeGraySquaresOfAllMatrixRelatives(bool add, bool ancestors, bool * changedSquares) { if (!ancestors) { // If in children, we also change the squares for this { Layout thisLayout = Layout(this); - if (addRemoveGreySquaresInLayoutIfNeeded(add, &thisLayout)) { + if (addRemoveGraySquaresInLayoutIfNeeded(add, &thisLayout)) { *changedSquares = true; } } for (int i = 0; i < numberOfChildren(); i++) { /* We cannot use "for l : children()", as the node addresses might change, * especially the iterator stopping address. */ - childAtIndex(i)->changeGreySquaresOfAllMatrixRelatives(add, false, changedSquares); + childAtIndex(i)->changeGraySquaresOfAllMatrixRelatives(add, false, changedSquares); } } else { Layout currentAncestor = Layout(parent()); while (!currentAncestor.isUninitialized()) { - if (addRemoveGreySquaresInLayoutIfNeeded(add, ¤tAncestor)) { + if (addRemoveGraySquaresInLayoutIfNeeded(add, ¤tAncestor)) { *changedSquares = true; } currentAncestor = currentAncestor.parent(); diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 4935de290..03437d58f 100644 --- a/poincare/src/least_common_multiple.cpp +++ b/poincare/src/least_common_multiple.cpp @@ -1,19 +1,12 @@ #include -#include -#include -#include #include #include #include -#include -#include namespace Poincare { constexpr Expression::FunctionHelper LeastCommonMultiple::s_functionHelper; -int LeastCommonMultipleNode::numberOfChildren() const { return LeastCommonMultiple::s_functionHelper.numberOfChildren(); } - Layout LeastCommonMultipleNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return LayoutHelper::Prefix(LeastCommonMultiple(this), floatDisplayMode, numberOfSignificantDigits, LeastCommonMultiple::s_functionHelper.name()); } @@ -23,37 +16,27 @@ int LeastCommonMultipleNode::serialize(char * buffer, int bufferSize, Preference } Expression LeastCommonMultipleNode::shallowReduce(ReductionContext reductionContext) { - return LeastCommonMultiple(this).shallowReduce(reductionContext.context()); + return LeastCommonMultiple(this).shallowReduce(reductionContext); +} + +Expression LeastCommonMultipleNode::shallowBeautify(ReductionContext * reductionContext) { + return LeastCommonMultiple(this).shallowBeautify(reductionContext->context()); } template -Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - bool isUndefined = false; - int a = ApproximationHelper::PositiveIntegerApproximationIfPossible(childAtIndex(0), &isUndefined, context, complexFormat, angleUnit); - int b = ApproximationHelper::PositiveIntegerApproximationIfPossible(childAtIndex(1), &isUndefined, context, complexFormat, angleUnit); - if (isUndefined) { - return Complex::RealUndefined(); - } - if (a == 0 || b == 0) { - return Complex::Builder(0.0); - } - if (b > a) { - int temp = b; - b = a; - a = temp; - } - int product = a*b; - int r = 0; - while((int)b!=0){ - r = a - (a/b)*b; - a = b; - b = r; - } - return Complex::Builder(product/a); +Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const { + return Arithmetic::LCM(*this, approximationContext); } +Expression LeastCommonMultiple::shallowBeautify(Context * context) { + /* Sort children in decreasing order: + * lcm(1,x,x^2) --> lcm(x^2,x,1) + * lcm(1,R(2)) --> lcm(R(2),1) */ + sortChildrenInPlace([](const ExpressionNode * e1, const ExpressionNode * e2, bool canBeInterrupted) { return ExpressionNode::SimplificationOrder(e1, e2, false, canBeInterrupted); }, context, true, true); + return *this; +} -Expression LeastCommonMultiple::shallowReduce(Context * context) { +Expression LeastCommonMultiple::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -61,38 +44,27 @@ Expression LeastCommonMultiple::shallowReduce(Context * context) { return e; } } - Expression c0 = childAtIndex(0); - Expression c1 = childAtIndex(1); - if (c0.deepIsMatrix(context) || c1.deepIsMatrix(context)) { - return replaceWithUndefinedInPlace(); - } - if (c0.type() == ExpressionNode::Type::Rational) { - Rational r0 = static_cast(c0); - if (!r0.isInteger()) { - return replaceWithUndefinedInPlace(); - } - } - if (c1.type() == ExpressionNode::Type::Rational) { - Rational r1 = static_cast(c1); - if (!r1.isInteger()) { - return replaceWithUndefinedInPlace(); - } - } - if (c0.type() != ExpressionNode::Type::Rational || c1.type() != ExpressionNode::Type::Rational) { - return *this; - } - Rational r0 = static_cast(c0); - Rational r1 = static_cast(c1); + assert(numberOfChildren() > 0); - Integer a = r0.signedIntegerNumerator(); - Integer b = r1.signedIntegerNumerator(); - Integer lcm = Arithmetic::LCM(a, b); - if (lcm.isOverflow()) { - return *this; + // Step 0: Merge children which are LCM + mergeSameTypeChildrenInPlace(); + + // Step 1: check that all children are compatible + { + Expression checkChildren = checkChildrenAreRationalIntegersAndUpdate(reductionContext); + if (!checkChildren.isUninitialized()) { + return checkChildren; + } } - Expression result = Rational::Builder(lcm); + + // Step 2: Compute LCM + Expression result = Arithmetic::LCM(*this); + replaceWithInPlace(result); return result; } +template Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const; + } diff --git a/poincare/src/left_parenthesis_layout.cpp b/poincare/src/left_parenthesis_layout.cpp index c7454d81d..1aa3e01e0 100644 --- a/poincare/src/left_parenthesis_layout.cpp +++ b/poincare/src/left_parenthesis_layout.cpp @@ -48,14 +48,6 @@ void LeftParenthesisLayoutNode::RenderWithChildHeight(KDCoordinate childHeight, expressionColor); } -bool LeftParenthesisLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { - if (*numberOfOpenParenthesis == 0 && goingLeft) { - return false; - } - *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis - 1 : *numberOfOpenParenthesis + 1; - return true; -} - void LeftParenthesisLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { RenderWithChildHeight(ParenthesisLayoutNode::ChildHeightGivenLayoutHeight(layoutSize().height()), ctx, p, expressionColor, backgroundColor); } diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 803dfc18d..c2cd788c3 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -68,30 +70,55 @@ Expression LogarithmNode<2>::shallowReduce(ExpressionNode::ReductionContext redu return Logarithm(this).shallowReduce(reductionContext); } +template <> +bool LogarithmNode<2>::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Logarithm(this).derivate(reductionContext, symbol, symbolValue); +} + +template <> +Expression LogarithmNode<2>::unaryFunctionDifferential(ReductionContext reductionContext) { + return Logarithm(this).unaryFunctionDifferential(reductionContext); +} + +/* Those two methods will not be called, as CommonLogarithm disappears in + * reduction */ +template <> +bool LogarithmNode<1>::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + assert(false); + return false; +} + +template <> +Expression LogarithmNode<1>::unaryFunctionDifferential(ReductionContext reductionContext) { + assert(false); + return Expression(); +} +/**/ + template<> -Expression LogarithmNode<1>::shallowBeautify(ReductionContext reductionContext) { +Expression LogarithmNode<1>::shallowBeautify(ReductionContext * reductionContext) { return CommonLogarithm(this); } template<> -Expression LogarithmNode<2>::shallowBeautify(ReductionContext reductionContext) { +Expression LogarithmNode<2>::shallowBeautify(ReductionContext * reductionContext) { return Logarithm(this).shallowBeautify(); } template<> -template Evaluation LogarithmNode<1>::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); +template Evaluation LogarithmNode<1>::templatedApproximate(ApproximationContext approximationContext) const { + return ApproximationHelper::Map(this, approximationContext, computeOnComplex); } template<> -template Evaluation LogarithmNode<2>::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation x = childAtIndex(0)->approximate(U(), context, complexFormat, angleUnit); - Evaluation n = childAtIndex(1)->approximate(U(), context, complexFormat, angleUnit); +template Evaluation LogarithmNode<2>::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation x = childAtIndex(0)->approximate(U(), approximationContext); + Evaluation n = childAtIndex(1)->approximate(U(), approximationContext); std::complex result = std::complex(NAN, NAN); if (x.type() == EvaluationNode::Type::Complex && n.type() == EvaluationNode::Type::Complex) { std::complex xc = (static_cast&>(x)).stdComplex(); std::complex nc = (static_cast&>(n)).stdComplex(); - result = DivisionNode::compute(computeOnComplex(xc, complexFormat, angleUnit).stdComplex(), computeOnComplex(nc, complexFormat, angleUnit).stdComplex(), complexFormat).stdComplex(); + result = DivisionNode::compute(computeOnComplex(xc, approximationContext.complexFormat(), approximationContext.angleUnit()).stdComplex(), computeOnComplex(nc, approximationContext.complexFormat(), approximationContext.angleUnit()).stdComplex(), approximationContext.complexFormat()).stdComplex(); } return Complex::Builder(result); } @@ -145,7 +172,7 @@ Expression Logarithm::shallowReduce(ExpressionNode::ReductionContext reductionCo } return *this; } - Expression f = simpleShallowReduce(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression f = simpleShallowReduce(reductionContext); if (f.type() != ExpressionNode::Type::Logarithm) { return f; } @@ -238,59 +265,35 @@ Expression Logarithm::shallowReduce(ExpressionNode::ReductionContext reductionCo return *this; } -Expression Logarithm::simpleShallowReduce(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Expression Logarithm::simpleShallowReduce(ExpressionNode::ReductionContext reductionContext) { Expression c = childAtIndex(0); Expression b = childAtIndex(1); - // log(0,0)->Undefined - if (c.type() == ExpressionNode::Type::Rational && b.type() == ExpressionNode::Type::Rational && static_cast(b).isZero() && static_cast(c).isZero()) { - return replaceWithUndefinedInPlace(); - } - // log(x,1)->Undefined - if (b.type() == ExpressionNode::Type::Rational && static_cast(b).isOne()) { - return replaceWithUndefinedInPlace(); - } - bool infiniteArg = c.recursivelyMatches(Expression::IsInfinity, context); - // log(x,x)->1 with x != inf and log(inf,inf) = undef - if (c.isIdenticalTo(b)) { - Expression result = infiniteArg ? Undefined::Builder().convert() : Rational::Builder(1).convert(); - replaceWithInPlace(result); - return result; - } - // log(x,0)->0 with x != inf and log(inf,0) = undef - if (b.type() == ExpressionNode::Type::Rational && static_cast(b).isZero()) { - Expression result = infiniteArg ? Undefined::Builder().convert() : Rational::Builder(0).convert(); - replaceWithInPlace(result); - return result; - } + // log(x,0) = log(x,1) = undef + if (b.type() == ExpressionNode::Type::Rational && (static_cast(b).isZero() || static_cast(b).isOne())) { + return replaceWithUndefinedInPlace(); + } if (c.type() == ExpressionNode::Type::Rational) { const Rational r = static_cast(c); - // log(0, x) = -inf if x > 1 && x != inf || inf x < 1 || undef if x < 0 + // log(0,x) = undef if (r.isZero()) { - bool infiniteBase = b.recursivelyMatches(Expression::IsInfinity, context); - // Special case: log(0,inf) -> undef - if (infiniteBase) { - return replaceWithUndefinedInPlace(); - } - bool isNegative = true; - Expression result; - Evaluation baseApproximation = b.node()->approximate(1.0f, context, complexFormat, angleUnit); - std::complex logDenominator = std::log10(static_cast&>(baseApproximation).stdComplex()); - if (logDenominator.imag() != 0.0f || logDenominator.real() == 0.0f) { - result = Undefined::Builder(); - } - isNegative = logDenominator.real() > 0.0f; - result = result.isUninitialized() ? Infinity::Builder(isNegative) : result; - replaceWithInPlace(result); - return result; + return replaceWithUndefinedInPlace(); } - // log(1) = 0; + // log(1,x) = 0; if (r.isOne()) { Expression result = Rational::Builder(0); replaceWithInPlace(result); return result; } } + bool infiniteArg = c.recursivelyMatches(Expression::IsInfinity, reductionContext.context()); + // log(x,x) = 1 with x != inf, and log(inf,inf) = undef + if (c.isIdenticalTo(b)) { + Expression result = infiniteArg ? Undefined::Builder().convert() : Rational::Builder(1).convert(); + replaceWithInPlace(result); + return result; + } + return *this; } @@ -329,6 +332,34 @@ Integer Logarithm::simplifyLogarithmIntegerBaseInteger(Integer i, Integer & base return i; } +bool Logarithm::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + /* We do nothing if the base is a function of the derivation variable, as the + * log is then not an unary function anymore. + * TODO : Check whether we want to deal with the case log(..., f(x)). */ + if (childAtIndex(1).polynomialDegree(reductionContext.context(), symbol.convert().name()) != 0) { + return false; + } + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression Logarithm::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + /* log(x, b)` = (ln(x)/ln(b))` + * = 1 / (x * ln(b)) + * + * ln(x) is only defined for x > 0, but 1/x is defined for any x != 0. + * Therefore we need to restricted the domain of the derivative function by + * adding a part that is undefined for x < 0. + * The chosen solution is (√x)^2 / (x * (√x)^2), which has the advantages of : + * - not being reduced with SystemForApproximation, unlike √x/x√x + * - not adding additional undefined points, unlike ln(x)/xln(x) for x = 1 + * - not reducing precision, unlike 1/√(x^2) + * FIXME: Although the derivative function cannot be observed by the user + * directly, me may want to display it one day, which this fix doesn't allow. + */ + return Power::Builder(Multiplication::Builder(childAtIndex(0).clone(), NaperianLogarithm::Builder(childAtIndex(1).clone()), Division::Builder(Power::Builder(SquareRoot::Builder(childAtIndex(0).clone()), Rational::Builder(2)), Power::Builder(SquareRoot::Builder(childAtIndex(0).clone()), Rational::Builder(2)))), Rational::Builder(-1)); +} + Expression Logarithm::splitLogarithmInteger(Integer i, bool isDenominator, ExpressionNode::ReductionContext reductionContext) { assert(!i.isZero()); assert(!i.isNegative()); @@ -357,7 +388,7 @@ Expression Logarithm::splitLogarithmInteger(Integer i, bool isDenominator, Expre Logarithm e = clone().convert(); e.replaceChildAtIndexInPlace(0, Rational::Builder(factors[index])); Multiplication m = Multiplication::Builder(Rational::Builder(coefficients[index]), e); - e.simpleShallowReduce(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + e.simpleShallowReduce(reductionContext); a.addChildAtIndexInPlace(m, a.numberOfChildren(), a.numberOfChildren()); m.shallowReduce(reductionContext); } @@ -381,10 +412,10 @@ Expression Logarithm::shallowBeautify() { return *this; } -template Evaluation LogarithmNode<1>::templatedApproximate(Poincare::Context *, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit) const; -template Evaluation LogarithmNode<1>::templatedApproximate(Poincare::Context *, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit) const; -template Evaluation LogarithmNode<2>::templatedApproximate(Poincare::Context *, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit) const; -template Evaluation LogarithmNode<2>::templatedApproximate(Poincare::Context *, Poincare::Preferences::ComplexFormat, Poincare::Preferences::AngleUnit) const; +template Evaluation LogarithmNode<1>::templatedApproximate(ApproximationContext) const; +template Evaluation LogarithmNode<1>::templatedApproximate(ApproximationContext) const; +template Evaluation LogarithmNode<2>::templatedApproximate(ApproximationContext) const; +template Evaluation LogarithmNode<2>::templatedApproximate(ApproximationContext) const; template int LogarithmNode<1>::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const; template int LogarithmNode<2>::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const; diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index b310b1ef0..48422d6b6 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -1,12 +1,15 @@ #include +#include #include #include #include #include #include #include +#include #include #include +#include #include #include #include @@ -96,10 +99,10 @@ int MatrixNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloat } template -Evaluation MatrixNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation MatrixNode::templatedApproximate(ApproximationContext approximationContext) const { MatrixComplex matrix = MatrixComplex::Builder(); for (ExpressionNode * c : children()) { - matrix.addChildAtIndexInPlace(c->approximate(T(), context, complexFormat, angleUnit), matrix.numberOfChildren(), matrix.numberOfChildren()); + matrix.addChildAtIndexInPlace(c->approximate(T(), approximationContext), matrix.numberOfChildren(), matrix.numberOfChildren()); } matrix.setDimensions(numberOfRows(), numberOfColumns()); return std::move(matrix); @@ -125,15 +128,16 @@ void Matrix::addChildrenAsRowInPlace(TreeHandle t, int i) { setDimensions(previousNumberOfRows + 1, previousNumberOfColumns == 0 ? t.numberOfChildren() : previousNumberOfColumns); } -int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool inPlace) { +int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, bool inPlace) { Matrix m = inPlace ? *this : clone().convert(); - ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation); + ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation); m = m.rowCanonize(systemReductionContext, nullptr); int rank = m.numberOfRows(); int i = rank-1; while (i >= 0) { int j = m.numberOfColumns()-1; - while (j >= i && matrixChild(i,j).isRationalZero()) { + while (j >= i && matrixChild(i,j).nullStatus(context) == ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown j--; } if (j == i-1) { @@ -146,6 +150,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) { @@ -159,7 +173,12 @@ int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { T operands[2*k_maxNumberOfCoefficients]; for (int i = 0; i < dim; i++) { for (int j = 0; j < dim; j++) { - operands[i*2*dim+j] = array[i*numberOfColumns+j]; + T cell = array[i*numberOfColumns+j]; + // Using abs function to be compatible with both double and std::complex + if (!std::isfinite(std::abs(cell))) { + return -2; + } + operands[i*2*dim+j] = cell; } for (int j = dim; j < 2*dim; j++) { operands[i*2*dim+j] = j-dim == i ? 1.0 : 0.0; @@ -168,7 +187,8 @@ int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { ArrayRowCanonize(operands, dim, 2*dim); // Check inversibility for (int i = 0; i < dim; i++) { - if (std::abs(operands[i*2*dim+i] - (T)1.0) > Expression::Epsilon()) { + T cell = operands[i*2*dim+i]; + if (!std::isfinite(std::abs(cell)) || std::abs(cell - (T)1.0) > Expression::Epsilon()) { return -2; } } @@ -180,7 +200,7 @@ int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { return 0; } -Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Expression * determinant) { +Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Expression * determinant, bool reduced) { Expression::SetInterruption(false); // The matrix children have to be reduced to be able to spot 0 deepReduceChildren(reductionContext); @@ -194,12 +214,36 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex int k = 0; // column pivot while (h < m && k < n) { - // Find the first non-null pivot + /* In non-reduced form, the pivot selection method will affect the output. + * Here we prioritize the biggest pivot (in value) to get an output that + * does not depends on the order of the rows of the matrix. + * We could also take lowest non null pivots, or just first non null as we + * already do with reduced forms. Output would be different, but correct. */ + int iPivot_temp = h; int iPivot = h; - while (iPivot < m && matrixChild(iPivot, k).isRationalZero()) { - iPivot++; + float bestPivot = 0.0; + while (iPivot_temp < m) { + // Using float to find the biggest pivot is sufficient. + float pivot = AbsoluteValue::Builder(matrixChild(iPivot_temp, k).clone()).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true); + // Handle very low pivots + if (pivot == 0.0f && matrixChild(iPivot_temp, k).nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null) { + pivot = FLT_MIN; + } + + if (pivot > bestPivot) { + // Update best pivot + bestPivot = pivot; + iPivot = iPivot_temp; + if (reduced) { + /* In reduced form, taking the first non null pivot is enough, and + * more efficient. */ + break; + } + } + iPivot_temp++; } - if (iPivot == m) { + if (matrixChild(iPivot, k).nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown // No non-null coefficient in this column, skip k++; if (determinant) { @@ -231,8 +275,11 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex } replaceChildInPlace(divisor, Rational::Builder(1)); - /* Set to 0 all M[i][j] i != h, j > k by linear combination */ - for (int i = 0; i < m; i++) { + int l = reduced ? 0 : h + 1; + /* Set to 0 all M[i][j] i != h, j > k by linear combination. If a + * non-reduced form is computed (ref), only rows below the pivot are + * reduced, i > h as well */ + for (int i = l; i < m; i++) { if (i == h) { continue; } Expression factor = matrixChild(i, k); for (int j = k+1; j < n; j++) { @@ -255,17 +302,31 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex } template -void Matrix::ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, T * determinant) { +void Matrix::ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, T * determinant, bool reduced) { int h = 0; // row pivot int k = 0; // column pivot while (h < numberOfRows && k < numberOfColumns) { - // Find the first non-null pivot + // Find the biggest pivot (in absolute value). See comment on rowCanonize. + int iPivot_temp = h; int iPivot = h; - while (iPivot < numberOfRows && std::abs(array[iPivot*numberOfColumns+k]) < Expression::Epsilon()) { - iPivot++; + // Using double to stay accurate with any type T + double bestPivot = 0.0; + while (iPivot_temp < numberOfRows) { + double pivot = std::abs(array[iPivot_temp*numberOfColumns+k]); + if (pivot > bestPivot) { + // Update best pivot + bestPivot = pivot; + iPivot = iPivot_temp; + if (reduced) { + /* In reduced form, taking the first non null pivot is enough, and + * more efficient. */ + break; + } + } + iPivot_temp++; } - if (iPivot == numberOfRows) { + if (bestPivot < DBL_MIN) { // No non-null coefficient in this column, skip k++; // Update determinant: det *= 0 @@ -291,8 +352,11 @@ void Matrix::ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, } array[h*numberOfColumns+k] = 1; - /* Set to 0 all M[i][j] i != h, j > k by linear combination */ - for (int i = 0; i < numberOfRows; i++) { + int l = reduced ? 0 : h + 1; + /* Set to 0 all M[i][j] i != h, j > k by linear combination. If a + * non-reduced form is computed (ref), only rows below the pivot are + * reduced, i > h as well */ + for (int i = l; i < numberOfRows; i++) { if (i == h) { continue; } T factor = array[i*numberOfColumns+k]; for (int j = k+1; j < numberOfColumns; j++) { @@ -330,6 +394,36 @@ Matrix Matrix::createTranspose() const { return matrix; } +Expression Matrix::createRef(ExpressionNode::ReductionContext reductionContext, bool * couldComputeRef, bool reduced) const { + // Compute Matrix Row Echelon Form + /* If the matrix is too big, the rowCanonization might not be computed exactly + * because of a pool allocation error, but we might still be able to compute + * it approximately. We thus encapsulate the ref creation in an exception + * checkpoint. + * We can safely use an exception checkpoint here because we are sure of not + * modifying any pre-existing node in the pool. We are sure there is no Store + * in the matrix. */ + Poincare::ExceptionCheckpoint ecp; + if (ExceptionRun(ecp)) { + /* We clone the current matrix to extract its children later. We can't clone + * its children directly. Indeed, the current matrix node (this->node()) is + * located before the exception checkpoint. In order to clone its chidlren, + * we would temporary increase the reference counter of each child (also + * located before the checkpoint). If an exception is raised before + * destroying the child handle, its reference counter would be off by one + * after the long jump. */ + Matrix result = clone().convert(); + *couldComputeRef = true; + /* Reduced row echelon form is also called row canonical form. To compute the + * row echelon form (non reduced one), fewer steps are required. */ + result = result.rowCanonize(reductionContext, nullptr, reduced); + return std::move(result); + } else { + *couldComputeRef = false; + return Expression(); + } +} + Expression Matrix::createInverse(ExpressionNode::ReductionContext reductionContext, bool * couldComputeInverse) const { int dim = numberOfRows(); if (dim != numberOfColumns()) { @@ -342,6 +436,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(); @@ -398,6 +493,53 @@ Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext return result; } +Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const { + // Norm is defined on vectors only + assert(vectorType() != Array::VectorType::None); + Addition sum = Addition::Builder(); + for (int j = 0; j < numberOfChildren(); j++) { + Expression absValue = AbsoluteValue::Builder(const_cast(this)->childAtIndex(j).clone()); + Expression squaredAbsValue = Power::Builder(absValue, Rational::Builder(2)); + absValue.shallowReduce(reductionContext); + sum.addChildAtIndexInPlace(squaredAbsValue, sum.numberOfChildren(), sum.numberOfChildren()); + squaredAbsValue.shallowReduce(reductionContext); + } + Expression result = SquareRoot::Builder(sum); + sum.shallowReduce(reductionContext); + return result; +} + +Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { + // Dot product is defined between two vectors of same size and type + assert(vectorType() != Array::VectorType::None && vectorType() == b->vectorType() && numberOfChildren() == b->numberOfChildren()); + Addition sum = Addition::Builder(); + for (int j = 0; j < numberOfChildren(); j++) { + Expression product = Multiplication::Builder(const_cast(this)->childAtIndex(j).clone(), const_cast(b)->childAtIndex(j).clone()); + sum.addChildAtIndexInPlace(product, sum.numberOfChildren(), sum.numberOfChildren()); + product.shallowReduce(reductionContext); + } + return std::move(sum); +} + +Matrix Matrix::cross(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { + // Cross product is defined between two vectors of size 3 and of same type. + assert(vectorType() != Array::VectorType::None && vectorType() == b->vectorType() && numberOfChildren() == 3 && b->numberOfChildren() == 3); + Matrix matrix = Matrix::Builder(); + for (int j = 0; j < 3; j++) { + int j1 = (j+1)%3; + int j2 = (j+2)%3; + Expression a1b2 = Multiplication::Builder(const_cast(this)->childAtIndex(j1).clone(), const_cast(b)->childAtIndex(j2).clone()); + Expression a2b1 = Multiplication::Builder(const_cast(this)->childAtIndex(j2).clone(), const_cast(b)->childAtIndex(j1).clone()); + Expression difference = Subtraction::Builder(a1b2, a2b1); + a1b2.shallowReduce(reductionContext); + a2b1.shallowReduce(reductionContext); + matrix.addChildAtIndexInPlace(difference, matrix.numberOfChildren(), matrix.numberOfChildren()); + difference.shallowReduce(reductionContext); + } + matrix.setDimensions(numberOfRows(), numberOfColumns()); + return matrix; +} + Expression Matrix::shallowReduce(Context * context) { { Expression e = Expression::defaultShallowReduce(); @@ -475,11 +617,10 @@ Expression Matrix::computeInverseOrDeterminant(bool computeDeterminant, Expressi } -template int Matrix::ArrayInverse(float *, int, int); template int Matrix::ArrayInverse(double *, int, int); template int Matrix::ArrayInverse>(std::complex *, int, int); template int Matrix::ArrayInverse>(std::complex *, int, int); -template void Matrix::ArrayRowCanonize >(std::complex*, int, int, std::complex*); -template void Matrix::ArrayRowCanonize >(std::complex*, int, int, std::complex*); +template void Matrix::ArrayRowCanonize >(std::complex*, int, int, std::complex*, bool); +template void Matrix::ArrayRowCanonize >(std::complex*, int, int, std::complex*, bool); } diff --git a/poincare/src/matrix_complex.cpp b/poincare/src/matrix_complex.cpp index 09eaa3ed0..5099f7dfe 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -119,6 +119,66 @@ MatrixComplex MatrixComplexNode::transpose() const { return result; } +template +MatrixComplex MatrixComplexNode::ref(bool reduced) const { + // Compute Matrix Row Echelon Form + if (numberOfChildren() == 0 || numberOfChildren() > Matrix::k_maxNumberOfCoefficients) { + return MatrixComplex::Undefined(); + } + std::complex operandsCopy[Matrix::k_maxNumberOfCoefficients]; + for (int i = 0; i < numberOfChildren(); i++) { + operandsCopy[i] = complexAtIndex(i); // Returns complex(NAN, NAN) if Node type is not Complex + } + /* Reduced row echelon form is also called row canonical form. To compute the + * row echelon form (non reduced one), fewer steps are required. */ + Matrix::ArrayRowCanonize(operandsCopy, m_numberOfRows, m_numberOfColumns, static_cast*>(nullptr), reduced); + return MatrixComplex::Builder(operandsCopy, m_numberOfRows, m_numberOfColumns); +} + +template +std::complex MatrixComplexNode::norm() const { + if (vectorType() == Array::VectorType::None) { + return std::complex(NAN, NAN); + } + std::complex sum = 0; + for (int i = 0; i < numberOfChildren(); i++) { + sum += std::norm(complexAtIndex(i)); + } + return std::sqrt(sum); +} + +template +std::complex MatrixComplexNode::dot(Evaluation * e) const { + if (e->type() != EvaluationNode::Type::MatrixComplex) { + return std::complex(NAN, NAN); + } + MatrixComplex * b = static_cast*>(e); + if (vectorType() == Array::VectorType::None || vectorType() != b->vectorType() || numberOfChildren() != b->numberOfChildren()) { + return std::complex(NAN, NAN); + } + std::complex sum = 0; + for (int i = 0; i < numberOfChildren(); i++) { + sum += complexAtIndex(i) * b->complexAtIndex(i); + } + return sum; +} + +template +Evaluation MatrixComplexNode::cross(Evaluation * e) const { + if (e->type() != EvaluationNode::Type::MatrixComplex) { + return MatrixComplex::Undefined(); + } + MatrixComplex * b = static_cast*>(e); + if (vectorType() == Array::VectorType::None || vectorType() != b->vectorType() || numberOfChildren() != 3 || b->numberOfChildren() != 3) { + return MatrixComplex::Undefined(); + } + std::complex operandsCopy[3]; + operandsCopy[0] = complexAtIndex(1) * b->complexAtIndex(2) - complexAtIndex(2) * b->complexAtIndex(1); + operandsCopy[1] = complexAtIndex(2) * b->complexAtIndex(0) - complexAtIndex(0) * b->complexAtIndex(2); + operandsCopy[2] = complexAtIndex(0) * b->complexAtIndex(1) - complexAtIndex(1) * b->complexAtIndex(0); + return MatrixComplex::Builder(operandsCopy, numberOfRows(), numberOfColumns()); +} + // MATRIX COMPLEX REFERENCE template diff --git a/poincare/src/matrix_dimension.cpp b/poincare/src/matrix_dimension.cpp index b6514bab8..aa294244a 100644 --- a/poincare/src/matrix_dimension.cpp +++ b/poincare/src/matrix_dimension.cpp @@ -26,8 +26,8 @@ int MatrixDimensionNode::serialize(char * buffer, int bufferSize, Preferences::P } template -Evaluation MatrixDimensionNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation MatrixDimensionNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); std::complex operands[2]; if (input.type() == EvaluationNode::Type::MatrixComplex) { operands[0] = std::complex(static_cast&>(input).numberOfRows()); diff --git a/poincare/src/matrix_echelon_form.cpp b/poincare/src/matrix_echelon_form.cpp new file mode 100644 index 000000000..6f8eed893 --- /dev/null +++ b/poincare/src/matrix_echelon_form.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include + +namespace Poincare { + +int MatrixEchelonFormNode::numberOfChildren() const { return sNumberOfChildren; } + +Expression MatrixEchelonFormNode::shallowReduce(ReductionContext reductionContext) { + return MatrixEchelonForm(this).shallowReduce(reductionContext); +} + +Layout MatrixEchelonFormNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return LayoutHelper::Prefix(MatrixEchelonForm(this), floatDisplayMode, numberOfSignificantDigits, functionHelperName()); +} + +int MatrixEchelonFormNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, functionHelperName()); +} + +template +Evaluation MatrixEchelonFormNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation ref; + if (input.type() == EvaluationNode::Type::MatrixComplex) { + ref = static_cast&>(input).ref(isFormReduced()); + } else { + ref = Complex::Undefined(); + } + assert(!ref.isUninitialized()); + return ref; +} + + +Expression MatrixEchelonForm::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + } + Expression c = childAtIndex(0); + if (c.type() == ExpressionNode::Type::Matrix) { + bool couldComputeRef = false; + Expression result = static_cast(c).createRef(reductionContext, &couldComputeRef, isFormReduced()); + if (couldComputeRef) { + replaceWithInPlace(result); + return result; + } + // The matrix could not be transformed properly + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/matrix_identity.cpp b/poincare/src/matrix_identity.cpp index f9dd65b67..526e55576 100644 --- a/poincare/src/matrix_identity.cpp +++ b/poincare/src/matrix_identity.cpp @@ -28,8 +28,8 @@ int MatrixIdentityNode::serialize(char * buffer, int bufferSize, Preferences::Pr } template -Evaluation MatrixIdentityNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation MatrixIdentityNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); T r = input.toScalar(); // Undefined if the child is not real if (!std::isnan(r) && !std::isinf(r) && r > (T)0.0 // The child is defined and positive && std::ceil(r) == std::floor(r) // The child is an integer diff --git a/poincare/src/matrix_inverse.cpp b/poincare/src/matrix_inverse.cpp index ec994bb7c..bd7f79503 100644 --- a/poincare/src/matrix_inverse.cpp +++ b/poincare/src/matrix_inverse.cpp @@ -27,8 +27,8 @@ int MatrixInverseNode::serialize(char * buffer, int bufferSize, Preferences::Pri } template -Evaluation MatrixInverseNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation MatrixInverseNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); Evaluation inverse; if (input.type() == EvaluationNode::Type::MatrixComplex) { inverse = static_cast&>(input).inverse(); diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index bb1b05a01..26c90d690 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -10,16 +10,16 @@ namespace Poincare { // MatrixLayoutNode -void MatrixLayoutNode::addGreySquares() { - if (!hasGreySquares()) { +void MatrixLayoutNode::addGraySquares() { + if (!hasGraySquares()) { Layout thisRef(this); - addEmptyRow(EmptyLayoutNode::Color::Grey); - addEmptyColumn(EmptyLayoutNode::Color::Grey); + addEmptyRow(EmptyLayoutNode::Color::Gray); + addEmptyColumn(EmptyLayoutNode::Color::Gray); } } -void MatrixLayoutNode::removeGreySquares() { - if (hasGreySquares()) { +void MatrixLayoutNode::removeGraySquares() { + if (hasGraySquares()) { deleteRowAtIndex(m_numberOfRows - 1); deleteColumnAtIndex(m_numberOfColumns - 1); } @@ -33,10 +33,10 @@ void MatrixLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomp && cursor->position() == LayoutCursor::Position::Left && childIsLeftOfGrid(childIndex)) { - /* Case: Left of a child on the left of the grid. Remove the grey squares of + /* Case: Left of a child on the left of the grid. Remove the gray squares of * the grid, then go left of the grid. */ - assert(hasGreySquares()); - removeGreySquares(); + assert(hasGraySquares()); + removeGraySquares(); *shouldRecomputeLayout = true; cursor->setLayoutNode(this); return; @@ -44,10 +44,10 @@ void MatrixLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomp if (cursor->layoutNode() == this && cursor->position() == LayoutCursor::Position::Right) { - /* Case: Right. Add the grey squares to the matrix, then move to the bottom - * right non empty nor grey child. */ - assert(!hasGreySquares()); - addGreySquares(); + /* Case: Right. Add the gray squares to the matrix, then move to the bottom + * right non empty nor gray child. */ + assert(!hasGraySquares()); + addGraySquares(); *shouldRecomputeLayout = true; LayoutNode * lastChild = childAtIndex((m_numberOfColumns-1)*(m_numberOfRows-1)); cursor->setLayoutNode(lastChild); @@ -60,9 +60,9 @@ void MatrixLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecom if (cursor->layoutNode() == this && cursor->position() == LayoutCursor::Position::Left) { - // Case: Left. Add grey squares to the matrix, then go to its first entry. - assert(!hasGreySquares()); - addGreySquares(); + // Case: Left. Add gray squares to the matrix, then go to its first entry. + assert(!hasGraySquares()); + addGraySquares(); *shouldRecomputeLayout = true; assert(m_numberOfColumns*m_numberOfRows >= 1); cursor->setLayoutNode(childAtIndex(0)); @@ -73,10 +73,10 @@ void MatrixLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecom && cursor->position() == LayoutCursor::Position::Right && childIsRightOfGrid(childIndex)) { - /* Case: Right of a child on the right of the grid. Remove the grey squares + /* Case: Right of a child on the right of the grid. Remove the gray squares * of the grid, then go right of the grid. */ - assert(hasGreySquares()); - removeGreySquares(); + assert(hasGraySquares()); + removeGraySquares(); *shouldRecomputeLayout = true; cursor->setLayoutNode(this); return; @@ -92,20 +92,32 @@ void MatrixLayoutNode::willAddSiblingToEmptyChildAtIndex(int childIndex) { } void MatrixLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { - // Deleting the left empty layout of an empty row deletes the row + /* Deleting the left empty layout of an empty row deletes the row, and + * deleting the top empty layout of an empty column deletes the column. */ assert(cursor != nullptr); LayoutNode * pointedChild = cursor->layoutNode(); if (pointedChild->isEmpty()) { int indexOfPointedLayout = indexOfChild(pointedChild); - if (columnAtChildIndex(indexOfPointedLayout) == 0) { - int rowIndex = rowAtChildIndex(indexOfPointedLayout); - if (rowIndex < m_numberOfRows - 1 && isRowEmpty(rowIndex) && m_numberOfRows > 2) { + int columnIndex = columnAtChildIndex(indexOfPointedLayout); + int rowIndex = rowAtChildIndex(indexOfPointedLayout); + bool deleted = false; + if (columnIndex == 0) { + if (m_numberOfRows > 2 && rowIndex < m_numberOfRows - 1 && isRowEmpty(rowIndex)) { deleteRowAtIndex(rowIndex); + deleted = true; + } + } + if (rowIndex == 0) { + if (m_numberOfColumns > 2 && columnIndex < m_numberOfColumns - 1 && isColumnEmpty(columnIndex)) { + deleteColumnAtIndex(columnIndex); + deleted = true; + } + } + if (deleted) { assert(indexOfPointedLayout >= 0 && indexOfPointedLayout < m_numberOfColumns*m_numberOfRows); cursor->setLayoutNode(childAtIndex(indexOfPointedLayout)); cursor->setPosition(LayoutCursor::Position::Right); return; - } } } GridLayoutNode::deleteBeforeCursor(cursor); @@ -149,7 +161,7 @@ int MatrixLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Prin } // Serialize the vectors - int maxColumnIndex = hasGreySquares() ? m_numberOfColumns - 2 : m_numberOfColumns - 1; + int maxColumnIndex = hasGraySquares() ? m_numberOfColumns - 2 : m_numberOfColumns - 1; for (int i = minRowIndex; i <= maxRowIndex; i++) { numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, '['); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} @@ -183,7 +195,7 @@ KDPoint MatrixLayoutNode::positionOfChild(LayoutNode * l) { void MatrixLayoutNode::moveCursorVertically(VerticalDirection direction, LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited, bool forSelection) { MatrixLayout thisRef = MatrixLayout(this); - bool shouldRemoveGreySquares = false; + bool shouldRemoveGraySquares = false; int firstIndex = direction == VerticalDirection::Up ? 0 : numberOfChildren() - m_numberOfColumns; int lastIndex = direction == VerticalDirection::Up ? m_numberOfColumns : numberOfChildren(); int i = firstIndex; @@ -192,16 +204,16 @@ void MatrixLayoutNode::moveCursorVertically(VerticalDirection direction, LayoutC break; } if (cursor->layout().node()->hasAncestor(l, true)) { - // The cursor is leaving the matrix, so remove the grey squares. - shouldRemoveGreySquares = true; + // The cursor is leaving the matrix, so remove the gray squares. + shouldRemoveGraySquares = true; break; } i++; } GridLayoutNode::moveCursorVertically(direction, cursor, shouldRecomputeLayout, equivalentPositionVisited, forSelection); - if (cursor->isDefined() && shouldRemoveGreySquares) { - assert(thisRef.hasGreySquares()); - thisRef.removeGreySquares(); + if (cursor->isDefined() && shouldRemoveGraySquares) { + assert(thisRef.hasGraySquares()); + thisRef.removeGraySquares(); *shouldRecomputeLayout = true; } } @@ -214,7 +226,7 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { int correspondingRow = rowAtChildIndex(index); if (childIsRightOfGrid(index)) { assert(m_numberOfRows >= 2); - // Color the grey EmptyLayouts of the column in yellow. + // Color the gray EmptyLayouts of the column in yellow. int correspondingColumn = m_numberOfColumns - 1; int childIndex = correspondingColumn; int maxIndex = (m_numberOfRows - 2)*m_numberOfColumns+correspondingColumn; @@ -234,12 +246,12 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { } childIndex++; } - // Add a column of grey EmptyLayouts on the right. - addEmptyColumn(EmptyLayoutNode::Color::Grey); + // Add a column of gray EmptyLayouts on the right. + addEmptyColumn(EmptyLayoutNode::Color::Gray); } if (shouldAddNewRow) { assert(m_numberOfColumns >= 2); - // Color the grey EmptyLayouts of the row in yellow. + // Color the gray EmptyLayouts of the row in yellow. int childIndex = correspondingRow * m_numberOfColumns; int maxIndex = correspondingRow * m_numberOfColumns + m_numberOfColumns - 2; for (LayoutNode * lastLayoutOfColumn : childrenFromIndex(correspondingRow*m_numberOfColumns)) { @@ -256,8 +268,8 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { } childIndex++; } - // Add a row of grey EmptyLayouts at the bottom. - addEmptyRow(EmptyLayoutNode::Color::Grey); + // Add a row of gray EmptyLayouts at the bottom. + addEmptyRow(EmptyLayoutNode::Color::Gray); } } @@ -291,14 +303,14 @@ bool MatrixLayoutNode::isColumnEmpty(int index) const { return true; } -bool MatrixLayoutNode::hasGreySquares() const { +bool MatrixLayoutNode::hasGraySquares() const { if (numberOfChildren() == 0) { return false; } LayoutNode * lastChild = const_cast(this)->childAtIndex(m_numberOfRows * m_numberOfColumns - 1); if (lastChild->isEmpty() && lastChild->type() != Type::HorizontalLayout - && (static_cast(lastChild))->color() == EmptyLayoutNode::Color::Grey) + && (static_cast(lastChild))->color() == EmptyLayoutNode::Color::Gray) { assert(isRowEmpty(m_numberOfRows - 1)); assert(isColumnEmpty(m_numberOfColumns - 1)); @@ -314,14 +326,19 @@ void MatrixLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColo void MatrixLayoutNode::didReplaceChildAtIndex(int index, LayoutCursor * cursor, bool force) { assert(index >= 0 && index < m_numberOfColumns*m_numberOfRows); int rowIndex = rowAtChildIndex(index); + int rowIsEmpty = isRowEmpty(rowIndex); int columnIndex = columnAtChildIndex(index); bool columnIsEmpty = isColumnEmpty(columnIndex); int newIndex = index; - if (columnIsEmpty && m_numberOfColumns > 2) { + if (columnIsEmpty && m_numberOfColumns > 2 && columnIndex == m_numberOfColumns - 2) { // If the column is now empty, delete it deleteColumnAtIndex(columnIndex); newIndex -= rowIndex; } + if (rowIsEmpty && m_numberOfRows > 2 && rowIndex == m_numberOfRows - 2) { + // If the row is now empty, delete it + deleteRowAtIndex(rowIndex); + } if (cursor) { assert(newIndex >= 0 && newIndex < m_numberOfColumns*m_numberOfRows); cursor->setLayoutNode(childAtIndex(newIndex)); diff --git a/poincare/src/matrix_reduced_row_echelon_form.cpp b/poincare/src/matrix_reduced_row_echelon_form.cpp new file mode 100644 index 000000000..a06bb98d8 --- /dev/null +++ b/poincare/src/matrix_reduced_row_echelon_form.cpp @@ -0,0 +1,9 @@ +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper MatrixReducedRowEchelonForm::s_functionHelper; + +const char * MatrixReducedRowEchelonFormNode::functionHelperName() const { return MatrixReducedRowEchelonForm::s_functionHelper.name(); } + +} diff --git a/poincare/src/matrix_row_echelon_form.cpp b/poincare/src/matrix_row_echelon_form.cpp new file mode 100644 index 000000000..fe8d8ec9d --- /dev/null +++ b/poincare/src/matrix_row_echelon_form.cpp @@ -0,0 +1,9 @@ +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper MatrixRowEchelonForm::s_functionHelper; + +const char * MatrixRowEchelonFormNode::functionHelperName() const { return MatrixRowEchelonForm::s_functionHelper.name(); } + +} diff --git a/poincare/src/matrix_trace.cpp b/poincare/src/matrix_trace.cpp index ab9aefc8f..427e6c528 100644 --- a/poincare/src/matrix_trace.cpp +++ b/poincare/src/matrix_trace.cpp @@ -27,8 +27,8 @@ int MatrixTraceNode::serialize(char * buffer, int bufferSize, Preferences::Print } template -Evaluation MatrixTraceNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation MatrixTraceNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); Complex result = Complex::Builder(input.trace()); return std::move(result); } @@ -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); } diff --git a/poincare/src/matrix_transpose.cpp b/poincare/src/matrix_transpose.cpp index 8c7452b8b..ea6483868 100644 --- a/poincare/src/matrix_transpose.cpp +++ b/poincare/src/matrix_transpose.cpp @@ -25,8 +25,8 @@ int MatrixTransposeNode::serialize(char * buffer, int bufferSize, Preferences::P } template -Evaluation MatrixTransposeNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation MatrixTransposeNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); Evaluation transpose; if (input.type() == EvaluationNode::Type::MatrixComplex) { transpose = static_cast&>(input).transpose(); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 14db22469..1b95a31c0 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +59,7 @@ int MultiplicationNode::getPolynomialCoefficients(Context * context, const char } bool MultiplicationNode::childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const { - if (NAryExpressionNode::childAtIndexNeedsUserParentheses(child, childIndex)) { + if (NAryInfixExpressionNode::childAtIndexNeedsUserParentheses(child, childIndex)) { return true; } Type types[] = {Type::Subtraction, Type::Addition}; @@ -230,7 +231,7 @@ Expression MultiplicationNode::shallowReduce(ReductionContext reductionContext) return Multiplication(this).shallowReduce(reductionContext); } -Expression MultiplicationNode::shallowBeautify(ReductionContext reductionContext) { +Expression MultiplicationNode::shallowBeautify(ReductionContext * reductionContext) { return Multiplication(this).shallowBeautify(reductionContext); } @@ -238,6 +239,10 @@ Expression MultiplicationNode::denominator(ReductionContext reductionContext) co return Multiplication(this).denominator(reductionContext); } +bool MultiplicationNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Multiplication(this).derivate(reductionContext, symbol, symbolValue); +} + /* Multiplication */ int Multiplication::getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const { @@ -337,36 +342,85 @@ Expression Multiplication::shallowReduce(ExpressionNode::ReductionContext reduct } static bool CanSimplifyUnitProduct( - const Unit::Dimension::Vector &unitsExponents, Unit::Dimension::Vector::Metrics &unitsMetrics, - const Unit::Dimension::Vector *entryUnitExponents, int8_t entryUnitNorm, int8_t entryUnitExponent, - int8_t & bestUnitExponent, Unit::Dimension::Vector &bestRemainderExponents, Unit::Dimension::Vector::Metrics & bestRemainderMetrics) { + const UnitNode::Vector &unitsExponents, size_t &unitsSupportSize, + const UnitNode::Vector * entryUnitExponents, int entryUnitExponent, + int8_t &bestUnitExponent, UnitNode::Vector &bestRemainderExponents, size_t &bestRemainderSupportSize) { /* This function tries to simplify a Unit product (given as the - * 'unitsExponents' Integer array), by applying a given operation. If the + * 'unitsExponents' int array), by applying a given operation. If the * result of the operation is simpler, 'bestUnit' and * 'bestRemainder' are updated accordingly. */ - Unit::Dimension::Vector simplifiedExponents; - Integer (*operationOnExponents)(const Integer &, const Integer &) = entryUnitExponent == -1 ? Integer::Addition : Integer::Subtraction; + UnitNode::Vector simplifiedExponents; + + #if 0 + /* In the current algorithm, simplification is attempted using derived units + * with no exponents. Some good simplifications might be missed: + * For instance with _A^2*_s^2, a first attempt will be to simplify to + * _C_A_s which has a bigger supportSize and will not be kept, the output + * will stay _A^2*_s^2. + * With the commented code, this issue is solved by trying to simplify with + * the highest exponent possible, so that, in this example, _A^2*_s^2 can be + * simplified to _C^2. + * An optimization might be possible using algorithms minimizing the sum of + * absolute difference of array elements */ + int n = 0; + int best_norm; + // TODO define a norm function summing all base units exponents + int norm_temp = unitsExponents.norm(); + /* To extend this algorithm to square root simplifications, rational exponents + * can be handled, and a 1/2 step can be used (but it should be asserted that + * no square root simplification is performed if all exponents are integers.*/ + int step = 1; for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { - simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(unitsExponents.coefficientAtIndex(i), entryUnitExponents->coefficientAtIndex(i))); + // Set simplifiedExponents to unitsExponents + simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i)); } - Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); - Unit::Dimension::Vector::Metrics candidateMetrics = { - .supportSize = 1 + simplifiedMetrics.supportSize, - .norm = Integer::Addition(entryUnitNorm, simplifiedMetrics.norm) - }; - bool isSimpler = candidateMetrics.supportSize < unitsMetrics.supportSize || - (candidateMetrics.supportSize == unitsMetrics.supportSize && - candidateMetrics.norm.isLowerThan(unitsMetrics.norm)); + do { + best_norm = norm_temp; + n+= step; + for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { + // Simplify unitsExponents with base units from derived unit + simplifiedExponents.setCoefficientAtIndex(i, simplifiedExponents.coefficientAtIndex(i) - entryUnitExponent * step * entryUnitExponents->coefficientAtIndex(i)); + } + int simplifiedNorm = simplifiedExponents.norm(); + // Temp norm is derived norm (n) + simplified norm + norm_temp = n + simplifiedNorm; + } while (norm_temp < best_norm); + // Undo last step as it did not reduce the norm + n -= step; + #endif + + for (size_t i = 0; i < UnitNode::k_numberOfBaseUnits; i++) { + #if 0 + // Undo last step as it did not reduce the norm + simplifiedExponents.setCoefficientAtIndex(i, simplifiedExponents.coefficientAtIndex(i) + entryUnitExponent * step * entryUnitExponents->coefficientAtIndex(i)); + #else + // Simplify unitsExponents with base units from derived unit + simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i) - entryUnitExponent * entryUnitExponents->coefficientAtIndex(i)); + #endif + } + size_t simplifiedSupportSize = simplifiedExponents.supportSize(); + /* Note: A metric is considered simpler if the support size (number of + * symbols) is reduced. A norm taking coefficients into account is possible. + * One could use the sum of all coefficients to favor _C_s from _A_s^2. + * However, replacing _m_s^-2 with _N_kg^-1 should be avoided. */ + bool isSimpler = (1 + simplifiedSupportSize < unitsSupportSize); + if (isSimpler) { + #if 0 + bestUnitExponent = entryUnitExponent * n * step; + #else bestUnitExponent = entryUnitExponent; + #endif bestRemainderExponents = simplifiedExponents; - bestRemainderMetrics = simplifiedMetrics; - unitsMetrics = candidateMetrics; + bestRemainderSupportSize = simplifiedSupportSize; + /* unitsSupportSize is updated and will be taken into + * account in next iterations of CanSimplifyUnitProduct. */ + unitsSupportSize = 1 + simplifiedSupportSize; } return isSimpler; } -Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { /* Beautifying a Multiplication consists in several possible operations: * - Add Opposite ((-3)*x -> -(3*x), useful when printing fractions) * - Recognize derived units in the product of units @@ -374,7 +428,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu */ // Step 1: Turn -n*A into -(n*A) - Expression noNegativeNumeral = makePositiveAnyNegativeNumeralFactor(reductionContext); + Expression noNegativeNumeral = makePositiveAnyNegativeNumeralFactor(*reductionContext); // If one negative numeral factor was made positive, we turn the expression in an Opposite if (!noNegativeNumeral.isUninitialized()) { Opposite o = Opposite::Builder(); @@ -389,8 +443,9 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu // Step 2: Handle the units if (hasUnit()) { Expression units; - self = deepReduce(reductionContext); // removeUnit has to be called on reduced expression - self = removeUnit(&units); + /* removeUnit has to be called on reduced expression but we want to modify + * the least the expression so we use the uninvasive reduction context. */ + self = self.reduceAndRemoveUnit(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext), &units); if (self.isUndefined() || units.isUninitialized()) { // TODO: handle error "Invalid unit" @@ -398,7 +453,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu goto replace_by_result; } - ExpressionNode::UnitConversion unitConversionMode = reductionContext.unitConversion(); + ExpressionNode::UnitConversion unitConversionMode = reductionContext->unitConversion(); if (unitConversionMode == ExpressionNode::UnitConversion::Default) { /* Step 2a: Recognize derived units * - Look up in the table of derived units, the one which itself or its inverse simplifies 'units' the most. @@ -406,52 +461,73 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * - Repeat those steps until no more simplification is possible. */ Multiplication unitsAccu = Multiplication::Builder(); - Unit::Dimension::Vector unitsExponents = Unit::Dimension::Vector::FromBaseUnits(units); - Unit::Dimension::Vector::Metrics unitsMetrics = unitsExponents.metrics(); - Unit::Dimension::Vector bestRemainderExponents; - Unit::Dimension::Vector::Metrics bestRemainderMetrics; - while (unitsMetrics.supportSize > 1) { - const Unit::Dimension * bestDim = nullptr; + /* If exponents are not integers, FromBaseUnits will return a null + * vector, preventing any attempt at simplification. This protects us + * against undue "simplifications" such as _C^1.3 -> _C*_A^0.3*_s^0.3 */ + UnitNode::Vector unitsExponents = UnitNode::Vector::FromBaseUnits(units); + size_t unitsSupportSize = unitsExponents.supportSize(); + UnitNode::Vector bestRemainderExponents; + size_t bestRemainderSupportSize; + while (unitsSupportSize > 1) { + const UnitNode::Representative * bestDim = nullptr; int8_t bestUnitExponent = 0; - for (const Unit::Dimension * dim = Unit::DimensionTable + Unit::NumberOfBaseUnits; dim < Unit::DimensionTableUpperBound; dim++) { - const Unit::Dimension::Vector * entryUnitExponents = dim->vector(); - int8_t entryUnitNorm = entryUnitExponents->metrics().norm; + // Look up in the table of derived units. + for (int i = UnitNode::k_numberOfBaseUnits; i < UnitNode::Representative::k_numberOfDimensions - 1; i++) { + const UnitNode::Representative * dim = UnitNode::Representative::DefaultRepresentatives()[i]; + const UnitNode::Vector entryUnitExponents = dim->dimensionVector(); + // A simplification is tried by either multiplying or dividing if (CanSimplifyUnitProduct( - unitsExponents, unitsMetrics, - entryUnitExponents, entryUnitNorm, 1, - bestUnitExponent, bestRemainderExponents, bestRemainderMetrics + unitsExponents, unitsSupportSize, + &entryUnitExponents, 1, + bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize ) || CanSimplifyUnitProduct( - unitsExponents, unitsMetrics, - entryUnitExponents, entryUnitNorm, -1, - bestUnitExponent, bestRemainderExponents, bestRemainderMetrics + unitsExponents, unitsSupportSize, + &entryUnitExponents, -1, + bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize )) { + /* If successful, unitsSupportSize, bestUnitExponent, + * bestRemainderExponents and bestRemainderSupportSize have been updated*/ bestDim = dim; } } if (bestDim == nullptr) { + // No simplification could be performed break; } - Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix()); + // Build and add the best derived unit + Expression derivedUnit = Unit::Builder(bestDim->representativesOfSameDimension(), bestDim->basePrefix()); + + #if 0 + if (bestUnitExponent != 1) { + derivedUnit = Power::Builder(derivedUnit, Rational::Builder(bestUnitExponent)); + } + #else assert(bestUnitExponent == 1 || bestUnitExponent == -1); if (bestUnitExponent == -1) { derivedUnit = Power::Builder(derivedUnit, Rational::Builder(-1)); } + #endif + const int position = unitsAccu.numberOfChildren(); unitsAccu.addChildAtIndexInPlace(derivedUnit, position, position); + // Update remainder units and their exponents for next simplifications unitsExponents = bestRemainderExponents; - unitsMetrics = bestRemainderMetrics; + unitsSupportSize = bestRemainderSupportSize; } + // Apply simplifications if (unitsAccu.numberOfChildren() > 0) { - units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext); Expression newUnits; - units = units.removeUnit(&newUnits); + // Divide by derived units, separate units and generated values + units = Division::Builder(units, unitsAccu.clone()).reduceAndRemoveUnit(*reductionContext, &newUnits); + // Assemble final value Multiplication m = Multiplication::Builder(units); self.replaceWithInPlace(m); m.addChildAtIndexInPlace(self, 0, 1); self = m; + // Update units with derived and base units if (newUnits.isUninitialized()) { units = unitsAccu; } else { @@ -469,14 +545,28 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * most relevant. */ - double value = self.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + double value = self.approximateToScalar(reductionContext->context(), reductionContext->complexFormat(), reductionContext->angleUnit()); if (std::isnan(value)) { // If the value is undefined, return "undef" without any unit result = Undefined::Builder(); } else { if (unitConversionMode == ExpressionNode::UnitConversion::Default) { // Find the right unit prefix - Unit::ChooseBestRepresentativeAndPrefixForValue(&units, &value, reductionContext); + /* In most cases, unit composition works the same for imperial and + * metric units. However, in imperial, we want volumes to be displayed + * using volume units instead of cubic length. */ + const bool forceVolumeRepresentative = reductionContext->unitFormat() == Preferences::UnitFormat::Imperial && UnitNode::Vector::FromBaseUnits(units) == UnitNode::VolumeRepresentative::Default().dimensionVector(); + const UnitNode::Representative * repr; + if (forceVolumeRepresentative) { + /* The choice of representative doesn't matter, as it will be tuned to a + * system appropriate one in Step 2b. */ + repr = UnitNode::VolumeRepresentative::Default().representativesOfSameDimension(); + units = Unit::Builder(repr, UnitNode::Prefix::EmptyPrefix()); + value /= repr->ratio(); + Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, *reductionContext); + } else { + Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, *reductionContext); + } } // Build final Expression result = Multiplication::Builder(Number::FloatNumber(value), units); @@ -485,7 +575,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu } else { // Step 3: Create a Division if relevant Expression numer, denom; - splitIntoNormalForm(numer, denom, reductionContext); + splitIntoNormalForm(numer, denom, *reductionContext); if (!numer.isUninitialized()) { result = numer; } @@ -510,6 +600,26 @@ Expression Multiplication::denominator(ExpressionNode::ReductionContext reductio return denom; } +bool Multiplication::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Addition resultingAddition = Addition::Builder(); + int numberOfTerms = numberOfChildren(); + assert (numberOfTerms > 0); + Expression childI; + + for (int i = 0; i < numberOfTerms; ++i) { + childI = clone(); + childI.replaceChildAtIndexInPlace(i, Derivative::Builder( + childI.childAtIndex(i), + symbol.clone().convert(), + symbolValue.clone() + )); + resultingAddition.addChildAtIndexInPlace(childI, i, i); + } + + replaceWithInPlace(resultingAddition); + return true; +} + Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext reductionContext, bool shouldExpand, bool canBeInterrupted) { { Expression e = Expression::defaultShallowReduce(); @@ -539,7 +649,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext // Use the last matrix child as the final matrix int n = resultMatrix.numberOfRows(); int m = resultMatrix.numberOfColumns(); - /* Scan accross the children to find other matrices. The last child is the + /* Scan across the children to find other matrices. The last child is the * result matrix so we start at numberOfChildren()-2. */ int multiplicationChildIndex = numberOfChildren()-2; while (multiplicationChildIndex >= 0) { @@ -599,7 +709,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext multiplicationChildIndex--; } /* Distribute the remaining multiplication children on the matrix children, - * if there are no oether matrices (such as a non reduced confidence + * if there are no other matrices (such as a non reduced confidence * interval). */ if (multiplicationChildIndex >= 0) { @@ -630,15 +740,30 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext // Do not factorize random or randint } else if (TermsHaveIdenticalBase(oi, oi1)) { bool shouldFactorizeBase = true; - if (TermHasNumeralBase(oi)) { + + if (shouldFactorizeBase && TermHasNumeralBase(oi)) { /* Combining powers of a given rational isn't straightforward. Indeed, * there are two cases we want to deal with: * - 2*2^(1/2) or 2*2^pi, we want to keep as-is * - 2^(1/2)*2^(3/2) we want to combine. */ shouldFactorizeBase = oi.type() == ExpressionNode::Type::Power && oi1.type() == ExpressionNode::Type::Power; } + + if (shouldFactorizeBase && reductionContext.target() != ExpressionNode::ReductionTarget::User) { + /* (x^a)*(x^b)->x^(a+b) is not generally true: x*x^-1 is undefined in 0 + * This rule is not true if one of the terms can divide by zero. + * In that case, cancel terms combination. + * With a User reduction exponents are combined anyway. */ + shouldFactorizeBase = TermsCanSafelyCombineExponents(oi, oi1, reductionContext); + } + if (shouldFactorizeBase) { factorizeBase(i, i+1, reductionContext); + /* An undef term could have appeared when factorizing 1^inf and 1^-inf + * for instance. In that case, we escape and return undef. */ + if (childAtIndex(i).isUndefined()) { + return replaceWithUndefinedInPlace(); + } continue; } } else if (TermHasNumeralBase(oi) && TermHasNumeralBase(oi1) && TermsHaveIdenticalExponent(oi, oi1)) { @@ -701,6 +826,9 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext SetInterruption(true); return *this; } + if (m.isUndefined()) { + return replaceWithUndefinedInPlace(); + } replaceChildAtIndexInPlace(0, m); removeChildAtIndexInPlace(i); } else { @@ -716,7 +844,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext /* Step 8: If the first child is zero, the multiplication result is zero. We * do this after merging the rational children, because the merge takes care * of turning 0*inf into undef. We still have to check that no other child - * involves an inifity expression to avoid reducing 0*e^(inf) to 0. + * involves an infinity expression to avoid reducing 0*e^(inf) to 0. * If the first child is 1, we remove it if there are other children. */ { const Expression c = childAtIndex(0); @@ -801,7 +929,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext newComplexCartesian.replaceChildAtIndexInPlace(1, imag); real.shallowReduce(reductionContext); imag.shallowReduce(reductionContext); - return newComplexCartesian.shallowReduce(); + return newComplexCartesian.shallowReduce(reductionContext); } return result; @@ -1002,6 +1130,46 @@ bool Multiplication::TermsHaveIdenticalExponent(const Expression & e1, const Exp return e1.type() == ExpressionNode::Type::Power && e2.type() == ExpressionNode::Type::Power && (e1.childAtIndex(1).isIdenticalTo(e2.childAtIndex(1))); } +bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression & e2, ExpressionNode::ReductionContext reductionContext) { + /* Combining exponents on terms of same base (x^a)*(x^b)->x^(a+b) is safe if : + * - x cannot be null + * - a and b are strictly positive + * - a+b is negative or null + * Otherwise, although one of the term should be undefined with x=0, x^(a+b) + * would yield 0 instead of being undefined. */ + assert(TermsHaveIdenticalBase(e1,e2)); + + Expression base = Base(e1); + ExpressionNode::Sign baseSign = base.sign(reductionContext.context()); + ExpressionNode::NullStatus baseNullStatus = base.nullStatus(reductionContext.context()); + + if (baseSign != ExpressionNode::Sign::Unknown && baseNullStatus == ExpressionNode::NullStatus::NonNull) { + // x cannot be null + return true; + } + + Expression exponent1 = CreateExponent(e1); + Expression exponent2 = CreateExponent(e2); + + if (exponent1.isStrictly(ExpressionNode::Sign::Positive, reductionContext.context()) + && exponent2.isStrictly(ExpressionNode::Sign::Positive, reductionContext.context())) { + // a and b are strictly positive + return true; + } + + Expression sum = Addition::Builder(exponent1, exponent2).shallowReduce(reductionContext); + ExpressionNode::Sign sumSign = sum.sign(reductionContext.context()); + ExpressionNode::NullStatus sumNullStatus = sum.nullStatus(reductionContext.context()); + + if (sumSign == ExpressionNode::Sign::Negative || sumNullStatus == ExpressionNode::NullStatus::Null) { + // a+b is negative or null + return true; + } + + // Otherwise, exponents cannot be combined safely + return false; +} + bool Multiplication::TermHasNumeralBase(const Expression & e) { return Base(e).isNumber(); } diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index 314f4b8cb..a7fba4e9b 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -1,5 +1,5 @@ #include -#include +#include extern "C" { #include #include @@ -8,21 +8,6 @@ extern "C" { namespace Poincare { -bool NAryExpressionNode::childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const { - /* Expressions like "-2" require parentheses in Addition/Multiplication except - * when they are the first operand. */ - if (childIndex != 0 - && ((child.isNumber() && static_cast(child).sign() == Sign::Negative) - || child.type() == Type::Opposite)) - { - return true; - } - if (child.type() == Type::Conjugate) { - return childAtIndexNeedsUserParentheses(child.childAtIndex(0), childIndex); - } - return false; -} - void NAryExpressionNode::sortChildrenInPlace(ExpressionOrder order, Context * context, bool canSwapMatrices, bool canBeInterrupted) { Expression reference(this); const int childrenCount = reference.numberOfChildren(); @@ -59,44 +44,6 @@ Expression NAryExpressionNode::squashUnaryHierarchyInPlace() { return std::move(reference); } -// Private - -int NAryExpressionNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { - int m = numberOfChildren(); - int n = e->numberOfChildren(); - for (int i = 1; i <= m; i++) { - // The NULL node is the least node type. - if (n < i) { - return 1; - } - int order = SimplificationOrder(childAtIndex(m-i), e->childAtIndex(n-i), ascending, canBeInterrupted, ignoreParentheses); - if (order != 0) { - return order; - } - } - // The NULL node is the least node type. - if (n > m) { - return ascending ? -1 : 1; - } - return 0; -} - -int NAryExpressionNode::simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { - int m = numberOfChildren(); - if (m == 0) { - return -1; - } - /* Compare e to last term of hierarchy. */ - int order = SimplificationOrder(childAtIndex(m-1), e, ascending, canBeInterrupted, ignoreParentheses); - if (order != 0) { - return order; - } - if (m > 1) { - return ascending ? 1 : -1; - } - return 0; -} - void NAryExpression::mergeSameTypeChildrenInPlace() { // Multiplication is associative: a*(b*c)->a*b*c // The same goes for Addition @@ -127,4 +74,33 @@ int NAryExpression::allChildrenAreReal(Context * context) const { return result; } +Expression NAryExpression::checkChildrenAreRationalIntegersAndUpdate(ExpressionNode::ReductionContext reductionContext) { + for (int i = 0; i < numberOfChildren(); ++i) { + Expression c = childAtIndex(i); + if (c.deepIsMatrix(reductionContext.context())) { + return replaceWithUndefinedInPlace(); + } + if (c.type() != ExpressionNode::Type::Rational) { + /* Replace expression with undefined if child can be approximated to a + * complex or finite non-integer number. Otherwise, rely on template + * approximations. hasDefinedComplexApproximation is given Cartesian + * complex format to force imaginary part approximation. */ + if (!c.isReal(reductionContext.context()) && c.hasDefinedComplexApproximation(reductionContext.context(), Preferences::ComplexFormat::Cartesian, reductionContext.angleUnit())) { + return replaceWithUndefinedInPlace(); + } + // If c was complex but with a null imaginary part, real part is checked. + float app = c.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true); + if (std::isfinite(app) && app != std::round(app)) { + return replaceWithUndefinedInPlace(); + } + // Note : Child could be replaced with the approximation (if finite) here. + return *this; + } + if (!static_cast(c).isInteger()) { + return replaceWithUndefinedInPlace(); + } + } + return Expression(); +} + } diff --git a/poincare/src/n_ary_infix_expression.cpp b/poincare/src/n_ary_infix_expression.cpp new file mode 100644 index 000000000..0433c143a --- /dev/null +++ b/poincare/src/n_ary_infix_expression.cpp @@ -0,0 +1,57 @@ +#include +#include + +namespace Poincare { + +bool NAryInfixExpressionNode::childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const { + /* Expressions like "-2" require parentheses in Addition/Multiplication except + * when they are the first operand. */ + if (childIndex != 0 + && ((child.isNumber() && static_cast(child).sign() == Sign::Negative) + || child.type() == Type::Opposite)) + { + return true; + } + if (child.type() == Type::Conjugate) { + return childAtIndexNeedsUserParentheses(child.childAtIndex(0), childIndex); + } + return false; +} + +int NAryInfixExpressionNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + int m = numberOfChildren(); + int n = e->numberOfChildren(); + for (int i = 1; i <= m; i++) { + // The NULL node is the least node type. + if (n < i) { + return 1; + } + int order = SimplificationOrder(childAtIndex(m-i), e->childAtIndex(n-i), ascending, canBeInterrupted, ignoreParentheses); + if (order != 0) { + return order; + } + } + // The NULL node is the least node type. + if (n > m) { + return ascending ? -1 : 1; + } + return 0; +} + +int NAryInfixExpressionNode::simplificationOrderGreaterType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + int m = numberOfChildren(); + if (m == 0) { + return -1; + } + /* Compare e to last term of hierarchy. */ + int order = SimplificationOrder(childAtIndex(m-1), e, ascending, canBeInterrupted, ignoreParentheses); + if (order != 0) { + return order; + } + if (m > 1) { + return ascending ? 1 : -1; + } + return 0; +} + +} \ No newline at end of file diff --git a/poincare/src/norm_cdf.cpp b/poincare/src/norm_cdf.cpp index 52c8f1761..90b313524 100644 --- a/poincare/src/norm_cdf.cpp +++ b/poincare/src/norm_cdf.cpp @@ -24,14 +24,14 @@ int NormCDFNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloa } template -Evaluation NormCDFNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation muEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation varEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation NormCDFNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation muEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation sigmaEvaluation = childAtIndex(2)->approximate(T(), approximationContext); const T a = aEvaluation.toScalar(); const T mu = muEvaluation.toScalar(); - const T sigma = std::sqrt(varEvaluation.toScalar()); + const T sigma = sigmaEvaluation.toScalar(); // CumulativeDistributiveFunctionAtAbscissa handles bad mu and var values return Complex::Builder(NormalDistribution::CumulativeDistributiveFunctionAtAbscissa(a, mu, sigma)); diff --git a/poincare/src/norm_cdf2.cpp b/poincare/src/norm_cdf2.cpp index b655a10de..a1cbb63e4 100644 --- a/poincare/src/norm_cdf2.cpp +++ b/poincare/src/norm_cdf2.cpp @@ -24,16 +24,16 @@ int NormCDF2Node::serialize(char * buffer, int bufferSize, Preferences::PrintFlo } template -Evaluation NormCDF2Node::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation bEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation muEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); - Evaluation varEvaluation = childAtIndex(3)->approximate(T(), context, complexFormat, angleUnit); +Evaluation NormCDF2Node::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation bEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation muEvaluation = childAtIndex(2)->approximate(T(), approximationContext); + Evaluation sigmaEvaluation = childAtIndex(3)->approximate(T(), approximationContext); T a = aEvaluation.toScalar(); T b = bEvaluation.toScalar(); T mu = muEvaluation.toScalar(); - T sigma = std::sqrt(varEvaluation.toScalar()); + T sigma = sigmaEvaluation.toScalar(); if (std::isnan(a) || std::isnan(b) || !NormalDistribution::MuAndSigmaAreOK(mu,sigma)) { return Complex::Undefined(); diff --git a/poincare/src/norm_pdf.cpp b/poincare/src/norm_pdf.cpp index af19c69c6..5ab9011a8 100644 --- a/poincare/src/norm_pdf.cpp +++ b/poincare/src/norm_pdf.cpp @@ -24,14 +24,14 @@ int NormPDFNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloa } template -Evaluation NormPDFNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation xEvaluation = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation muEvaluation = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); - Evaluation varEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); +Evaluation NormPDFNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation xEvaluation = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation muEvaluation = childAtIndex(1)->approximate(T(), approximationContext); + Evaluation sigmaEvaluation = childAtIndex(2)->approximate(T(), approximationContext); T x = xEvaluation.toScalar(); T mu = muEvaluation.toScalar(); - T sigma = std::sqrt(varEvaluation.toScalar()); + T sigma = sigmaEvaluation.toScalar(); // EvaluateAtAbscissa handles bad mu and var values return Complex::Builder(NormalDistribution::EvaluateAtAbscissa(x, mu, sigma)); diff --git a/poincare/src/normal_distribution.cpp b/poincare/src/normal_distribution.cpp index 710dfacb8..425f90f0f 100644 --- a/poincare/src/normal_distribution.cpp +++ b/poincare/src/normal_distribution.cpp @@ -88,7 +88,7 @@ T NormalDistribution::StandardNormalCumulativeDistributiveFunctionAtAbscissa(T a if (std::isnan(abscissa)) { return NAN; } - if (std::isinf(abscissa) || std::fabs(abscissa) > k_boundStandardNormalDistribution) { + if (std::isinf(abscissa)) { return abscissa > (T)0.0 ? (T)1.0 : (T)0.0; } if (abscissa == (T)0.0) { diff --git a/poincare/src/nth_root.cpp b/poincare/src/nth_root.cpp index b5e63ca01..c1a8be9f6 100644 --- a/poincare/src/nth_root.cpp +++ b/poincare/src/nth_root.cpp @@ -34,9 +34,9 @@ Expression NthRootNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation NthRootNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation base = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation index = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation NthRootNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation base = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation index = childAtIndex(1)->approximate(T(), approximationContext); Complex result = Complex::Undefined(); if (base.type() == EvaluationNode::Type::Complex && index.type() == EvaluationNode::Type::Complex) @@ -46,14 +46,14 @@ Evaluation NthRootNode::templatedApproximate(Context * context, Preferences:: /* If the complexFormat is Real, we look for nthroot of form root(x,q) with * x real and q integer because they might have a real form which does not * correspond to the principale angle. */ - if (complexFormat == Preferences::ComplexFormat::Real && indexc.imag() == 0.0 && std::round(indexc.real()) == indexc.real()) { + if (approximationContext.complexFormat() == Preferences::ComplexFormat::Real && indexc.imag() == 0.0 && std::round(indexc.real()) == indexc.real()) { // root(x, q) with q integer and x real Complex result = PowerNode::computeNotPrincipalRealRootOfRationalPow(basec, (T)1.0, indexc.real()); if (!result.isUndefined()) { return std::move(result); } } - result = PowerNode::compute(basec, std::complex(1.0)/(indexc), complexFormat); + result = PowerNode::compute(basec, std::complex(1.0)/(indexc), approximationContext.complexFormat()); } return std::move(result); } diff --git a/poincare/src/number.cpp b/poincare/src/number.cpp index 42c1cac17..1da2d12ae 100644 --- a/poincare/src/number.cpp +++ b/poincare/src/number.cpp @@ -6,6 +6,7 @@ #include #include #include +#include extern "C" { #include #include @@ -24,12 +25,9 @@ double NumberNode::doubleApproximation() const { assert(Number(this).sign() == Sign::Negative || Number(this).sign() == Sign::Positive); return Number(this).sign() == Sign::Negative ? -INFINITY : INFINITY; case Type::Float: - if (size() == sizeof(FloatNode)) { - return static_cast *>(this)->value(); - } else { - assert(size() == sizeof(FloatNode)); - return static_cast *>(this)->value(); - } + return static_cast *>(this)->value(); + case Type::Double: + return static_cast *>(this)->value(); case Type::Rational: return static_cast(this)->templatedApproximate(); case Type::BasedInteger: @@ -40,9 +38,13 @@ double NumberNode::doubleApproximation() const { } } -Number Number::ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLenght, bool exponentIsNegative, const char * exponentPart, size_t exponentLength) { +bool NumberNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Number(this).derivate(reductionContext, symbol, symbolValue); +} + +Number Number::ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLength, bool exponentIsNegative, const char * exponentPart, size_t exponentLength) { // Integer - if (exponentLength == 0 && decimalLenght == 0) { + if (exponentLength == 0 && decimalLength == 0) { Integer i(integralPart, integralLength, false); if (!i.isOverflow()) { return BasedInteger::Builder(i, Integer::Base::Decimal); @@ -51,7 +53,7 @@ Number Number::ParseNumber(const char * integralPart, size_t integralLength, con int exp; // Avoid overflowing int if (exponentLength < Decimal::k_maxExponentLength) { - exp = Decimal::Exponent(integralPart, integralLength, decimalPart, decimalLenght, exponentPart, exponentLength, exponentIsNegative); + exp = Decimal::Exponent(integralPart, integralLength, decimalPart, decimalLength, exponentPart, exponentLength, exponentIsNegative); } else { exp = exponentIsNegative ? -1 : 1; } @@ -63,7 +65,7 @@ Number Number::ParseNumber(const char * integralPart, size_t integralLength, con return Infinity::Builder(false); } } - return Decimal::Builder(integralPart, integralLength, decimalPart, decimalLenght, exp); + return Decimal::Builder(integralPart, integralLength, decimalPart, decimalLength, exp); } template @@ -141,6 +143,11 @@ int Number::NaturalOrder(const Number & i, const Number & j) { } } +bool Number::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + replaceWithInPlace(Rational::Builder(0)); + return true; +} + template Number Number::DecimalNumber(float); template Number Number::DecimalNumber(double); } diff --git a/poincare/src/parametered_expression.cpp b/poincare/src/parametered_expression.cpp index 57bc73dcf..289c32d00 100644 --- a/poincare/src/parametered_expression.cpp +++ b/poincare/src/parametered_expression.cpp @@ -22,13 +22,16 @@ int ParameteredExpressionNode::getVariables(Context * context, isVariableTest is /* Remove the parameter symbol from the list of variable if it was added at * the previous line */ const char * parameterName = ParameteredExpression(this).parameter().name(); - for (int i = nextVariableIndex; i < numberOfVariables; i++) { - if (strcmp(parameterName, &variables[i]) == 0) { - variables[i] = 0; + for (int index = nextVariableIndex * maxSizeVariable; index < numberOfVariables * maxSizeVariable; index += maxSizeVariable) { + if (strcmp(parameterName, &variables[index]) == 0) { + memmove(&variables[index], &variables[index + maxSizeVariable], (numberOfVariables - nextVariableIndex) * maxSizeVariable); numberOfVariables--; break; } } + if (numberOfVariables < Expression::k_maxNumberOfVariables) { + variables[numberOfVariables * maxSizeVariable] = '\0'; + } nextVariableIndex = numberOfVariables; static_assert(ParameteredExpression::ParameteredChildIndex() == 0 && ParameteredExpression::ParameterChildIndex() == 1, "ParameteredExpression::getVariables might not be valid"); diff --git a/poincare/src/parenthesis.cpp b/poincare/src/parenthesis.cpp index ca514b079..cd06aeb3b 100644 --- a/poincare/src/parenthesis.cpp +++ b/poincare/src/parenthesis.cpp @@ -21,8 +21,8 @@ Expression ParenthesisNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation ParenthesisNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation ParenthesisNode::templatedApproximate(ApproximationContext approximationContext) const { + return childAtIndex(0)->approximate(T(), approximationContext); } Expression Parenthesis::shallowReduce() { diff --git a/poincare/src/parenthesis_layout.cpp b/poincare/src/parenthesis_layout.cpp index dece3d9d9..cfbcb1ae6 100644 --- a/poincare/src/parenthesis_layout.cpp +++ b/poincare/src/parenthesis_layout.cpp @@ -1,5 +1,24 @@ #include +#include namespace Poincare { +bool ParenthesisLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (goingLeft == (type() == LayoutNode::Type::RightParenthesisLayout)) { + /* This parenthesis is an opening parenthesis. */ + *numberOfOpenParenthesis = *numberOfOpenParenthesis + 1; + return true; + } + + /* This parenthesis is a closing parenthesis. We do not want to absorb it if + * there is no corresponding opening parenthesis, as the absorber should be + * enclosed by this parenthesis. */ + assert((goingLeft && type() == LayoutNode::Type::LeftParenthesisLayout) || (!goingLeft && type() == LayoutNode::Type::RightParenthesisLayout)); + if (*numberOfOpenParenthesis == 0) { + return false; + } + *numberOfOpenParenthesis = *numberOfOpenParenthesis - 1; + return true; +} + } diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index f38c57e3f..32a72fbcc 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace Poincare { @@ -346,13 +347,11 @@ void Parser::parseConstant(Expression & leftHandSide, Token::Type stoppingType) void Parser::parseUnit(Expression & leftHandSide, Token::Type stoppingType) { assert(leftHandSide.isUninitialized()); - const Unit::Dimension * unitDimension = nullptr; const Unit::Representative * unitRepresentative = nullptr; - const Unit::Prefix * unitPrefix = nullptr; leftHandSide = Constant::Builder(m_currentToken.codePoint()); - if (Unit::CanParse(m_currentToken.text(), m_currentToken.length(), - &unitDimension, &unitRepresentative, &unitPrefix)) - { - leftHandSide = Unit::Builder(unitDimension, unitRepresentative, unitPrefix); + const Unit::Prefix * unitPrefix = nullptr; + leftHandSide = Constant::Builder(m_currentToken.codePoint()); + if (Unit::CanParse(m_currentToken.text(), m_currentToken.length(), &unitRepresentative, &unitPrefix)) { + leftHandSide = Unit::Builder(unitRepresentative, unitPrefix); } else { m_status = Status::Error; // Unit does not exist return; @@ -386,14 +385,18 @@ void Parser::parseReservedFunction(Expression & leftHandSide, const Expression:: return; } int numberOfParameters = parameters.numberOfChildren(); - while (numberOfParameters > (**functionHelper).numberOfChildren()) { - functionHelper++; - if (!(functionHelper < s_reservedFunctionsUpperBound && strcmp(name, (**functionHelper).name()) == 0)) { - m_status = Status::Error; // Too many parameters provided. - return; + /* FunctionHelpers with negative numberOfChildren value expect any number of + * children greater than this value (in absolute). */ + if ((**functionHelper).numberOfChildren() >= 0) { + while (numberOfParameters > (**functionHelper).numberOfChildren()) { + functionHelper++; + if (!(functionHelper < s_reservedFunctionsUpperBound && strcmp(name, (**functionHelper).name()) == 0)) { + m_status = Status::Error; // Too many parameters provided. + return; + } } } - if (numberOfParameters < (**functionHelper).numberOfChildren()) { + if (numberOfParameters < abs((**functionHelper).numberOfChildren())) { m_status = Status::Error; // Too few parameters provided. return; } @@ -404,7 +407,7 @@ void Parser::parseReservedFunction(Expression & leftHandSide, const Expression:: } } -void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Type leftDelimiter1, Token::Type rightDelimiter1, Token::Type leftDelimiter2, Token::Type rightDelimiter2) { +void Parser::parseSequence(Expression & leftHandSide, const char * name, Token::Type leftDelimiter1, Token::Type rightDelimiter1, Token::Type leftDelimiter2, Token::Type rightDelimiter2) { bool delimiterTypeIsOne = popTokenIfType(leftDelimiter1); if (!delimiterTypeIsOne && !popTokenIfType(leftDelimiter2)) { m_status = Status::Error; // Left delimiter missing. @@ -413,17 +416,9 @@ void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Ty Expression rank = parseUntil(rightDelimiter); if (m_status != Status::Progress) { } else if (!popTokenIfType(rightDelimiter)) { - m_status = Status::Error; // Right delimiter missing. - } else if (rank.isIdenticalTo(Symbol::Builder('n'))) { - constexpr int symbolNameSize = 5; - char sym[symbolNameSize] = {name, '(', 'n', ')', 0}; - leftHandSide = Symbol::Builder(sym, symbolNameSize); - } else if (rank.isIdenticalTo(Addition::Builder(Symbol::Builder('n'), BasedInteger::Builder("1")))) { - constexpr int symbolNameSize = 7; - char sym[symbolNameSize] = {name, '(', 'n', '+', '1', ')', 0}; - leftHandSide = Symbol::Builder(sym, symbolNameSize); + m_status = Status::Error; // Right delimiter missing } else { - m_status = Status::Error; // Unexpected parameter. + leftHandSide = Sequence::Builder(name, 1, rank); } } } @@ -444,7 +439,7 @@ void Parser::parseSpecialIdentifier(Expression & leftHandSide) { /* Special case for sequences (e.g. "u(n)", "u{n}", ...) * We know that m_currentToken.text()[0] is either 'u', 'v' or 'w', so we do * not need to pass a code point to parseSequence. */ - parseSequence(leftHandSide, m_currentToken.text()[0], Token::LeftParenthesis, Token::RightParenthesis, Token::LeftBrace, Token::RightBrace); + parseSequence(leftHandSide, m_currentToken.text(), Token::LeftParenthesis, Token::RightParenthesis, Token::LeftBrace, Token::RightBrace); } } @@ -484,7 +479,7 @@ void Parser::parseCustomIdentifier(Expression & leftHandSide, const char * name, } assert(!parameter.isUninitialized()); if (parameter.numberOfChildren() != 1) { - m_status = Status::Error; // Unexpected number of paramters. + m_status = Status::Error; // Unexpected number of parameters. return; } parameter = parameter.childAtIndex(0); diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 37ff4f523..e6fe72f58 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -75,7 +75,7 @@ private: Expression parseCommaSeparatedList(); void parseReservedFunction(Expression & leftHandSide, const Expression::FunctionHelper * const * functionHelper); void parseSpecialIdentifier(Expression & leftHandSide); - void parseSequence(Expression & leftHandSide, const char name, Token::Type leftDelimiter1, Token::Type rightDelimiter1, Token::Type leftDelimiter2, Token::Type rightDelimiter2); + void parseSequence(Expression & leftHandSide, const char * name, Token::Type leftDelimiter1, Token::Type rightDelimiter1, Token::Type leftDelimiter2, Token::Type rightDelimiter2); void parseCustomIdentifier(Expression & leftHandSide, const char * name, size_t length); void defaultParseLeftParenthesis(bool isSystemParenthesis, Expression & leftHandSide, Token::Type stoppingType); @@ -110,9 +110,11 @@ private: &Conjugate::s_functionHelper, &Cosine::s_functionHelper, &HyperbolicCosine::s_functionHelper, + &VectorCross::s_functionHelper, &Determinant::s_functionHelper, &Derivative::s_functionHelper, &MatrixDimension::s_functionHelper, + &VectorDot::s_functionHelper, &Factor::s_functionHelper, &Floor::s_functionHelper, &FracPart::s_functionHelper, @@ -127,6 +129,7 @@ private: &NaperianLogarithm::s_functionHelper, &CommonLogarithm::s_functionHelper, &Logarithm::s_functionHelper, + &VectorNorm::s_functionHelper, &NormCDF::s_functionHelper, &NormCDF2::s_functionHelper, &NormPDF::s_functionHelper, @@ -138,9 +141,11 @@ private: &Randint::s_functionHelper, &Random::s_functionHelper, &RealPart::s_functionHelper, + &MatrixRowEchelonForm::s_functionHelper, &DivisionRemainder::s_functionHelper, &NthRoot::s_functionHelper, &Round::s_functionHelper, + &MatrixReducedRowEchelonForm::s_functionHelper, &SignFunction::s_functionHelper, &Sine::s_functionHelper, &HyperbolicSine::s_functionHelper, diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 4cf8dc99c..13ae60956 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -162,7 +162,7 @@ Token Tokenizer::popToken() { * reserved or custom identifier, popIdentifier is called in both cases. */ Token result(Token::Unit); - result.setString(start + 1, popIdentifier(UCodePointNull)); // + 1 for the underscore + result.setString(start + 1, popIdentifier(UCodePointDegreeSign)); // + 1 for the underscore return result; } if (c.isLatinLetter() || diff --git a/poincare/src/permute_coefficient.cpp b/poincare/src/permute_coefficient.cpp index 9207a1b1f..50121a188 100644 --- a/poincare/src/permute_coefficient.cpp +++ b/poincare/src/permute_coefficient.cpp @@ -28,9 +28,9 @@ Expression PermuteCoefficientNode::shallowReduce(ReductionContext reductionConte } template -Evaluation PermuteCoefficientNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation nInput = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation kInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation PermuteCoefficientNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation nInput = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation kInput = childAtIndex(1)->approximate(T(), approximationContext); T n = nInput.toScalar(); T k = kInput.toScalar(); if (std::isnan(n) || std::isnan(k) || n != std::round(n) || k != std::round(k) || n < 0.0f || k < 0.0f) { diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index dab4c6707..b5f8de021 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -224,7 +225,7 @@ Expression PowerNode::shallowReduce(ReductionContext reductionContext) { return Power(this).shallowReduce(reductionContext); } -Expression PowerNode::shallowBeautify(ReductionContext reductionContext) { +Expression PowerNode::shallowBeautify(ReductionContext * reductionContext) { return Power(this).shallowBeautify(reductionContext); } @@ -251,6 +252,10 @@ Expression PowerNode::denominator(ReductionContext reductionContext) const { return Power(this).denominator(reductionContext); } +bool PowerNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Power(this).derivate(reductionContext, symbol, symbolValue); +} + // Evaluation template MatrixComplex PowerNode::computeOnComplexAndMatrix(const std::complex c, const MatrixComplex n, Preferences::ComplexFormat complexFormat) { return MatrixComplex::Undefined(); @@ -288,12 +293,12 @@ template MatrixComplex PowerNode::computeOnMatrices(const MatrixC return MatrixComplex::Undefined(); } -template Evaluation PowerNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +template Evaluation PowerNode::templatedApproximate(ApproximationContext approximationContext) const { /* Special case: c^(p/q) with p, q integers * In real mode, c^(p/q) might have a real root which is not the principal * root. We return this value in that case to avoid returning "unreal". */ - if (complexFormat == Preferences::ComplexFormat::Real) { - Evaluation base = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + if (approximationContext.complexFormat() == Preferences::ComplexFormat::Real) { + Evaluation base = childAtIndex(0)->approximate(T(), approximationContext); if (base.type() != EvaluationNode::Type::Complex) { goto defaultApproximation; } @@ -329,7 +334,7 @@ template Evaluation PowerNode::templatedApproximate(Context * con } } defaultApproximation: - return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); + return ApproximationHelper::MapReduce(this, approximationContext, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } // Power @@ -413,8 +418,8 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex } assert(index == childAtIndex(1)); if (base.hasUnit()) { - if (index.type() != ExpressionNode::Type::Rational || !static_cast(index).isInteger()) { - // The exponent must be an Integer + if (index.type() != ExpressionNode::Type::Rational) { + // The exponent must be an Rational return replaceWithUndefinedInPlace(); } } @@ -496,7 +501,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex // x^0 if (rationalIndex.isZero()) { // 0^0 = undef or (±inf)^0 = undef - if (base.isRationalZero() || baseType == ExpressionNode::Type::Infinity) { + if (base.nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null || baseType == ExpressionNode::Type::Infinity) { return replaceWithUndefinedInPlace(); } // x^0 @@ -584,7 +589,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex } if (!result.isUninitialized()) { replaceWithInPlace(result); - return result.shallowReduce(); + return result.shallowReduce(reductionContext); } } } @@ -601,7 +606,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex complexIndex = indexType == ExpressionNode::Type::ComplexCartesian ? static_cast(index) : ComplexCartesian::Builder(index, Rational::Builder(0)); result = complexBase.power(complexIndex, reductionContext); replaceWithInPlace(result); - return result.shallowReduce(); + return result.shallowReduce(reductionContext); } /* Step 6: We look for square root and sum of square roots (two terms maximum @@ -774,7 +779,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex * - (a^b)^(-1) has to be reduced to avoid infinite loop discussed above; * - if a^b is unreal, a^(-b) also. */ if (!cMinusOne && reductionContext.complexFormat() == Preferences::ComplexFormat::Real) { - Expression approximation = powerBase.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression approximation = powerBase.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true); if (approximation.type() == ExpressionNode::Type::Unreal) { // The inner power is unreal, return "unreal" replaceWithInPlace(approximation); @@ -980,18 +985,18 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex return *this; } -Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression Power::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { // Step 1: X^-y -> 1/(X->shallowBeautify)^y - Expression p = denominator(reductionContext); + 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); replaceWithInPlace(d); - p.shallowReduce(reductionContext); + p.shallowReduce(*reductionContext); return d.shallowBeautify(reductionContext); } - // Step 2: Turn a^(1/n) into root(a, n) - if (childAtIndex(1).type() == ExpressionNode::Type::Rational && childAtIndex(1).convert().signedIntegerNumerator().isOne()) { + // Step 2: Turn a^(1/n) into root(a, n), unless base is a unit + if (childAtIndex(1).type() == ExpressionNode::Type::Rational && childAtIndex(1).convert().signedIntegerNumerator().isOne() && childAtIndex(0).type() != ExpressionNode::Type::Unit) { Integer index = childAtIndex(1).convert().integerDenominator(); // Special case: a^(1/2) --> sqrt(a) if (index.isEqualTo(Integer(2))) { @@ -1015,6 +1020,47 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return *this; } +bool Power::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + /* Generalized power derivation formula + * (f^g)` = (e^(g * ln(f)))` + * = (g * ln(f))` * f^g + * = (g`ln(f) + gf`/f) * f^g + * = g`ln(f)f^g + gf`f^(g-1) + * + * Valid whenever f,g are derivable and f > 0 */ + + /* We might want to be able to derivate f^n when f <= 0 and n is a positive + * integer */ + + Expression base = childAtIndex(0); + Expression exponent = childAtIndex(1); + Multiplication derivedFromBase = Multiplication::Builder(); + Multiplication derivedFromExponent = Multiplication::Builder(); + + derivedFromExponent.addChildAtIndexInPlace(NaperianLogarithm::Builder(base.clone()), 0, 0); + derivedFromExponent.addChildAtIndexInPlace(clone(), 1, 1); + derivedFromExponent.addChildAtIndexInPlace(Derivative::Builder( + exponent.clone(), + symbol.clone().convert(), + symbolValue.clone() + ), 2, 2); + + derivedFromBase.addChildAtIndexInPlace(exponent.clone() , 0, 0); + derivedFromBase.addChildAtIndexInPlace(Power::Builder( + base.clone(), + Subtraction::Builder(exponent.clone(), Rational::Builder(1)) + ), 1, 1); + derivedFromBase.addChildAtIndexInPlace(Derivative::Builder( + base.clone(), + symbol.clone().convert(), + symbolValue.clone() + ), 2, 2); + + Addition result = Addition::Builder(derivedFromBase, derivedFromExponent); + replaceWithInPlace(result); + return true; +} + // Private // Simplification @@ -1347,9 +1393,9 @@ Expression Power::CreateComplexExponent(const Expression & r, ExpressionNode::Re #if 0 const Constant iComplex = Constant::Builder(UCodePointMathematicalBoldSmallI); const Constant pi = Constant::Builder(UCodePointGreekSmallLetterPi); - Expression op = Multiplication::Builder(pi, r).shallowReduce(context, complexFormat, angleUnit, false); - Cosine cos = Cosine(op).shallowReduce(context, complexFormat, angleUnit, false);; - Sine sin = Sine(op).shallowReduce(context, complexFormat, angleUnit, false); + Expression op = Multiplication::Builder(pi, r).shallowReduce(reductionContext, false); + Cosine cos = Cosine(op).shallowReduce(reductionContext, false);; + Sine sin = Sine(op).shallowReduce(reductionContext, false); Expression m = Multiplication::Builder(iComplex, sin); Expression a = Addition::Builder(cos, m); const Expression * multExpOperands[3] = {pi, r->clone()}; @@ -1437,5 +1483,7 @@ bool Power::RationalExponentShouldNotBeReduced(const Rational & b, const Rationa template Complex PowerNode::compute(std::complex, std::complex, Preferences::ComplexFormat); template Complex PowerNode::compute(std::complex, std::complex, Preferences::ComplexFormat); +template Complex PowerNode::computeNotPrincipalRealRootOfRationalPow(std::complex, double, double); +template Complex PowerNode::computeNotPrincipalRealRootOfRationalPow(std::complex, float, float); } diff --git a/poincare/src/prediction_interval.cpp b/poincare/src/prediction_interval.cpp index 0893818e6..6aad86c95 100644 --- a/poincare/src/prediction_interval.cpp +++ b/poincare/src/prediction_interval.cpp @@ -31,9 +31,9 @@ Expression PredictionIntervalNode::shallowReduce(ReductionContext reductionConte } template -Evaluation PredictionIntervalNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation pInput = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation nInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation PredictionIntervalNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation pInput = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation nInput = childAtIndex(1)->approximate(T(), approximationContext); T p = static_cast &>(pInput).toScalar(); T n = static_cast &>(nInput).toScalar(); if (std::isnan(p) || std::isnan(n) || n != (int)n || n < 0 || p < 0 || p > 1) { diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index 0aa8e66fc..d9147a275 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -207,17 +207,29 @@ PrintFloat::TextLengths PrintFloat::ConvertFloatToTextPrivate(T f, char * buffer /* Part I: Mantissa */ - // Compute mantissa - T unroundedMantissa = f * std::pow((T)10.0, (T)(numberOfSignificantDigits - 1 - exponentInBase10)); + /* Compute mantissa + * We compute the unroundedMantissa using doubles to limit approximation errors. + * Previously when computing with floats : 0.000600000028 * 10^10 = 6000000.28 + * was rounded into 6000000.5 because of the conversion error + * Mantissa was the 6000001 instead of 6000000. + * As a result, 0.0006 was displayed as 0.0006000001 + * With doubles, 0.000600000028 * 10^10 = 6000000.2849... + * This value is then rounded into mantissa = 6000000 which yields a proper + * display of 0.0006 */ + double unroundedMantissa = static_cast(f) * std::pow(10.0, (double)(numberOfSignificantDigits - 1 - exponentInBase10)); // Round mantissa to get the right number of significant digits - T mantissa = std::round(unroundedMantissa); + double mantissa = std::round(unroundedMantissa); + + /* Since no problem of approximation was detected from using potential float + * (instead of double) in the rest of the code, we decided to leave it like + * this */ /* If (numberOfSignificantDigits - 1 - exponentInBase10) is too big (or too * small), mantissa is now inf. We handle this case by using logarithm * function. */ if (std::isnan(mantissa) || std::isinf(mantissa)) { mantissa = std::round(std::pow(10, std::log10(std::fabs(f))+(T)(numberOfSignificantDigits -1 - exponentInBase10))); - mantissa = std::copysign(mantissa, f); + mantissa = std::copysign(mantissa, static_cast(f)); } /* We update the exponent in base 10 (if 0.99999999 was rounded to 1 for * instance) @@ -225,7 +237,7 @@ PrintFloat::TextLengths PrintFloat::ConvertFloatToTextPrivate(T f, char * buffer * "exponentBase10(unroundedMantissa) != exponentBase10(mantissa)", * However, unroundedMantissa can have a different exponent than expected * (ex: f = 1E13, unroundedMantissa = 99999999.99 and mantissa = 1000000000) */ - if (f != 0 && IEEE754::exponentBase10(mantissa) - exponentInBase10 != numberOfSignificantDigits - 1 - exponentInBase10) { + if (f != 0 && IEEE754::exponentBase10(mantissa) - exponentInBase10 != numberOfSignificantDigits - 1 - exponentInBase10) { exponentInBase10++; } diff --git a/poincare/src/product.cpp b/poincare/src/product.cpp index 51759f281..d168785d9 100644 --- a/poincare/src/product.cpp +++ b/poincare/src/product.cpp @@ -13,7 +13,7 @@ namespace Poincare { constexpr Expression::FunctionHelper Product::s_functionHelper; -Layout ProductNode::createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const { +Layout ProductNode::createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const { return ProductLayout::Builder(argumentLayout, symbolLayout, subscriptLayout, superscriptLayout); } diff --git a/poincare/src/randint.cpp b/poincare/src/randint.cpp index 52d15e7f9..16864410f 100644 --- a/poincare/src/randint.cpp +++ b/poincare/src/randint.cpp @@ -28,9 +28,9 @@ int RandintNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloa return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, Randint::s_functionHelper.name()); } -template Evaluation RandintNode::templateApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool * inputIsUndefined) const { - Evaluation aInput = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation bInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +template Evaluation RandintNode::templateApproximate(ApproximationContext approximationContext, bool * inputIsUndefined) const { + Evaluation aInput = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation bInput = childAtIndex(1)->approximate(T(), approximationContext); if (inputIsUndefined) { *inputIsUndefined = aInput.isUndefined() || bInput.isUndefined(); } @@ -67,7 +67,7 @@ Expression Randint::shallowReduce(ExpressionNode::ReductionContext reductionCont return e; } bool inputIsUndefined = false; - double eval = static_cast(node())->templateApproximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), &inputIsUndefined).toScalar(); + double eval = static_cast(node())->templateApproximate(ExpressionNode::ApproximationContext(reductionContext, true), &inputIsUndefined).toScalar(); if (inputIsUndefined) { /* The input might be NAN because we are reducing a function's expression * which depends on x. We thus do not want to replace too early with diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index 58f07cc3e..6757bee85 100644 --- a/poincare/src/rational.cpp +++ b/poincare/src/rational.cpp @@ -141,7 +141,7 @@ Expression RationalNode::shallowReduce(ReductionContext reductionContext) { return Rational(this).shallowReduce(); } -Expression RationalNode::shallowBeautify(ReductionContext reductionContext) { +Expression RationalNode::shallowBeautify(ReductionContext * reductionContext) { return Rational(this).shallowBeautify(); } diff --git a/poincare/src/right_parenthesis_layout.cpp b/poincare/src/right_parenthesis_layout.cpp index 09ff93132..798e49244 100644 --- a/poincare/src/right_parenthesis_layout.cpp +++ b/poincare/src/right_parenthesis_layout.cpp @@ -48,14 +48,6 @@ void RightParenthesisLayoutNode::RenderWithChildHeight(KDCoordinate childHeight, expressionColor); } -bool RightParenthesisLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { - if (*numberOfOpenParenthesis == 0 && !goingLeft) { - return false; - } - *numberOfOpenParenthesis = goingLeft ? *numberOfOpenParenthesis + 1 : *numberOfOpenParenthesis - 1; - return true; -} - void RightParenthesisLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { RenderWithChildHeight(ParenthesisLayoutNode::ChildHeightGivenLayoutHeight(layoutSize().height()), ctx, p, expressionColor, backgroundColor); } diff --git a/poincare/src/round.cpp b/poincare/src/round.cpp index 8b370ca92..a5acc4aaf 100644 --- a/poincare/src/round.cpp +++ b/poincare/src/round.cpp @@ -27,9 +27,9 @@ Expression RoundNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation RoundNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation f1Input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation f2Input = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation RoundNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation f1Input = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation f2Input = childAtIndex(1)->approximate(T(), approximationContext); T f1 = f1Input.toScalar(); T f2 = f2Input.toScalar(); if (std::isnan(f2) || f2 != std::round(f2)) { diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 9955cacfa..bae19166c 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -1,68 +1,124 @@ #include -#include -#include -#include -extern "C" { -#include -#include -} -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace Poincare { +SequenceNode::SequenceNode(const char * newName, int length) : SymbolAbstractNode() { + strlcpy(const_cast(name()), newName, length+1); +} + +Expression SequenceNode::replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { + return Sequence(this).replaceSymbolWithExpression(symbol, expression); +} + +int SequenceNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + /* This function ensures that terms like u(n) and u(n+1), u(n) and v(n), + * u(a) and u(b) do not factorize. + * We never want to factorize. The only cases where it could be useful are + * like the following : u(n)+u(n). But thanks to the cache system, no + * computation is needed for the second term.*/ + assert(type() == e->type()); + assert(numberOfChildren() == 1); + assert(e->numberOfChildren() == 1); + ExpressionNode * seq = const_cast(e); + int delta = strcmp(name(), reinterpret_cast(seq)->name()); + if (delta == 0) { + return SimplificationOrder(childAtIndex(0), e->childAtIndex(0), ascending, canBeInterrupted, ignoreParentheses); + } + return delta; +} + Layout SequenceNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return createSequenceLayout( - childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), - childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), - childAtIndex(2)->createLayout(floatDisplayMode, numberOfSignificantDigits), - childAtIndex(3)->createLayout(floatDisplayMode, numberOfSignificantDigits) - ); + assert(name()[0] >= 'u' && name()[0] <= 'w'); + Layout rank = childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits); + return HorizontalLayout::Builder( + CodePointLayout::Builder(name()[0]), + VerticalOffsetLayout::Builder(rank, VerticalOffsetLayoutNode::Position::Subscript)); +} + +int SequenceNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, name()); } Expression SequenceNode::shallowReduce(ReductionContext reductionContext) { - return Sequence(this).shallowReduce(reductionContext.context()); + return Sequence(this).shallowReduce(reductionContext); +} + +Evaluation SequenceNode::approximate(SinglePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); +} + +Evaluation SequenceNode::approximate(DoublePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); } template -Evaluation SequenceNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation aInput = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); - Evaluation bInput = childAtIndex(3)->approximate(T(), context, complexFormat, angleUnit); - T start = aInput.toScalar(); - T end = bInput.toScalar(); - if (std::isnan(start) || std::isnan(end) || start != (int)start || end != (int)end || end - start > k_maxNumberOfSteps) { +Evaluation SequenceNode::templatedApproximate(ApproximationContext approximationContext) const { + if (approximationContext.withinReduce() || childAtIndex(0)->approximate((T)1, approximationContext).isUndefined()) { + /* If we're inside a reducing routine, we want to escape the sequence + * approximation. Indeed, in order to know that the sequence is well defined + * (especially for self-referencing or inter-dependently defined sequences), + * we need to reduce the sequence definition (done by calling + * 'expressionForSymbolAbstract'); if we're within a reduce routine, we + * would create an infinite loop. Returning a NAN approximation for + * sequences within reduce routine does not really matter: we just have + * access to less information in order to simplify (abs(u(n)) might not be + * reduced for instance). */ return Complex::Undefined(); } - VariableContext nContext = VariableContext(static_cast(childAtIndex(1))->name(), context); - Evaluation result = Complex::Builder((T)emptySequenceValue()); - for (int i = (int)start; i <= (int)end; i++) { - if (Expression::ShouldStopProcessing()) { - return Complex::Undefined(); - } - nContext.setApproximationForVariable((T)i); - result = evaluateWithNextTerm(T(), result, childAtIndex(0)->approximate(T(), &nContext, complexFormat, angleUnit), complexFormat); - if (result.isUndefined()) { - return Complex::Undefined(); - } + Expression e = approximationContext.context()->expressionForSymbolAbstract(this, false); + if (e.isUninitialized()) { + return Complex::Undefined(); } - return result; + return e.node()->approximate(T(), approximationContext); } -Expression Sequence::shallowReduce(Context * context) { - { - Expression e = Expression::defaultShallowReduce(); - e = e.defaultHandleUnitsInChildren(); - if (e.isUndefined()) { - return e; - } +Sequence Sequence::Builder(const char * name, size_t length, Expression child) { + Sequence seq = SymbolAbstract::Builder(name, length); + if (!child.isUninitialized()) { + seq.replaceChildAtIndexInPlace(0, child); } - assert(!childAtIndex(1).deepIsMatrix(context)); - if (childAtIndex(2).deepIsMatrix(context) || childAtIndex(3).deepIsMatrix(context)) { + return seq; +} + +Expression Sequence::replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { + // Replace the symbol in the child + childAtIndex(0).replaceSymbolWithExpression(symbol, expression); + if (symbol.type() == ExpressionNode::Type::Sequence && hasSameNameAs(symbol)) { + Expression value = expression.clone(); + Expression p = parent(); + if (!p.isUninitialized() && p.node()->childAtIndexNeedsUserParentheses(value, p.indexOfChild(*this))) { + value = Parenthesis::Builder(value); + } + replaceWithInPlace(value); + return value; + } + return *this; +} + +Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined) { return replaceWithUndefinedInPlace(); } return *this; } -template Evaluation SequenceNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation SequenceNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +Expression Sequence::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + return *this; +} } diff --git a/poincare/src/sign_function.cpp b/poincare/src/sign_function.cpp index ba4765413..49bea3f6b 100644 --- a/poincare/src/sign_function.cpp +++ b/poincare/src/sign_function.cpp @@ -15,10 +15,6 @@ constexpr Expression::FunctionHelper SignFunction::s_functionHelper; int SignFunctionNode::numberOfChildren() const { return SignFunction::s_functionHelper.numberOfChildren(); } -ExpressionNode::Sign SignFunctionNode::sign(Context * context) const { - return childAtIndex(0)->sign(context); -} - Expression SignFunctionNode::setSign(Sign s, ReductionContext reductionContext) { assert(s == ExpressionNode::Sign::Positive || s == ExpressionNode::Sign::Negative); SignFunction sign(this); @@ -71,7 +67,7 @@ Expression SignFunction::shallowReduce(ExpressionNode::ReductionContext reductio if (s == ExpressionNode::Sign::Negative) { resultSign = Rational::Builder(-1); } else { - Evaluation childApproximated = child.node()->approximate(1.0f, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Evaluation childApproximated = child.node()->approximate(1.0f, ExpressionNode::ApproximationContext(reductionContext, true)); assert(childApproximated.type() == EvaluationNode::Type::Complex); Complex c = static_cast&>(childApproximated); if (std::isnan(c.imag()) || std::isnan(c.real()) || c.imag() != 0) { diff --git a/poincare/src/sine.cpp b/poincare/src/sine.cpp index 3668d8a3a..96e9051ab 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include #include +#include #include #include @@ -11,10 +14,6 @@ constexpr Expression::FunctionHelper Sine::s_functionHelper; int SineNode::numberOfChildren() const { return Sine::s_functionHelper.numberOfChildren(); } -float SineNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - return Trigonometry::characteristicXRange(Sine(this), context, angleUnit); -} - template Complex SineNode::computeOnComplex(const std::complex c, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) { std::complex angleInput = Trigonometry::ConvertToRadian(c, angleUnit); @@ -34,6 +33,13 @@ Expression SineNode::shallowReduce(ReductionContext reductionContext) { return Sine(this).shallowReduce(reductionContext); } +bool SineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Sine(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression SineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Sine(this).unaryFunctionDifferential(reductionContext); +} Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { @@ -46,4 +52,13 @@ Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext return Trigonometry::shallowReduceDirectFunction(*this, reductionContext); } +bool Sine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression Sine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return Multiplication::Builder(Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Cosine::Builder(childAtIndex(0).clone())); +} + } diff --git a/poincare/src/solver.cpp b/poincare/src/solver.cpp index a5ea72bc3..d0c0d638d 100644 --- a/poincare/src/solver.cpp +++ b/poincare/src/solver.cpp @@ -196,10 +196,14 @@ Coordinate2D Solver::IncreasingFunctionRoot(double ax, double bx, double * representable double between min and max strictly. If there is, we choose * it instead, otherwise, we reached the most precise result possible. */ if (currentAbscissa == min) { - currentAbscissa = IEEE754::next(min); + if (currentAbscissa != -INFINITY) { + currentAbscissa = std::nextafter(currentAbscissa, static_cast(INFINITY)); + } } if (currentAbscissa == max) { - currentAbscissa = IEEE754::previous(max); + if (currentAbscissa != INFINITY) { + currentAbscissa = std::nextafter(currentAbscissa, -static_cast(INFINITY)); + } } if (currentAbscissa == min || currentAbscissa == max) { break; diff --git a/poincare/src/square_root.cpp b/poincare/src/square_root.cpp index 0c8361d1e..cdefee1a5 100644 --- a/poincare/src/square_root.cpp +++ b/poincare/src/square_root.cpp @@ -40,7 +40,6 @@ Expression SquareRootNode::shallowReduce(ReductionContext reductionContext) { Expression SquareRoot::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); - e = e.defaultHandleUnitsInChildren(); if (e.isUndefined()) { return e; } diff --git a/poincare/src/store.cpp b/poincare/src/store.cpp index ddd1daa64..1c0d82aa8 100644 --- a/poincare/src/store.cpp +++ b/poincare/src/store.cpp @@ -14,18 +14,18 @@ Expression StoreNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation StoreNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation StoreNode::templatedApproximate(ApproximationContext approximationContext) const { /* If we are here, it means that the store node was not shallowReduced. * Otherwise, it would have been replaced by its symbol. We thus have to * setExpressionForSymbolAbstract. */ - Expression storedExpression = Store(this).storeValueForSymbol(context, complexFormat, angleUnit); + Expression storedExpression = Store(this).storeValueForSymbol(approximationContext.context()); assert(!storedExpression.isUninitialized()); - return storedExpression.node()->approximate(T(), context, complexFormat, angleUnit); + return storedExpression.node()->approximate(T(), approximationContext); } Expression Store::shallowReduce(ExpressionNode::ReductionContext reductionContext) { // Store the expression. - Expression storedExpression = storeValueForSymbol(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + Expression storedExpression = storeValueForSymbol(reductionContext.context()); if (symbol().type() == ExpressionNode::Type::Symbol) { /* If the symbol is not a function, we want to replace the store with its @@ -46,7 +46,7 @@ Expression Store::shallowReduce(ExpressionNode::ReductionContext reductionContex return storedExpression; } -Expression Store::storeValueForSymbol(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Expression Store::storeValueForSymbol(Context * context) const { assert(!value().isUninitialized()); context->setExpressionForSymbolAbstract(value(), symbol()); Expression storedExpression = context->expressionForSymbolAbstract(symbol(), false); diff --git a/poincare/src/sum.cpp b/poincare/src/sum.cpp index ceee70feb..84757093c 100644 --- a/poincare/src/sum.cpp +++ b/poincare/src/sum.cpp @@ -13,7 +13,7 @@ namespace Poincare { constexpr Expression::FunctionHelper Sum::s_functionHelper; -Layout SumNode::createSequenceLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const { +Layout SumNode::createSumAndProductLayout(Layout argumentLayout, Layout symbolLayout, Layout subscriptLayout, Layout superscriptLayout) const { return SumLayout::Builder(argumentLayout, symbolLayout, subscriptLayout, superscriptLayout); } diff --git a/poincare/src/sum_and_product.cpp b/poincare/src/sum_and_product.cpp new file mode 100644 index 000000000..2cf0f1f4f --- /dev/null +++ b/poincare/src/sum_and_product.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +extern "C" { +#include +#include +} +#include + +namespace Poincare { + +Layout SumAndProductNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return createSumAndProductLayout( + childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), + childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), + childAtIndex(2)->createLayout(floatDisplayMode, numberOfSignificantDigits), + childAtIndex(3)->createLayout(floatDisplayMode, numberOfSignificantDigits) + ); +} + +Expression SumAndProductNode::shallowReduce(ReductionContext reductionContext) { + return SumAndProduct(this).shallowReduce(reductionContext.context()); +} + +template +Evaluation SumAndProductNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation aInput = childAtIndex(2)->approximate(T(), approximationContext); + Evaluation bInput = childAtIndex(3)->approximate(T(), approximationContext); + T start = aInput.toScalar(); + T end = bInput.toScalar(); + if (std::isnan(start) || std::isnan(end) || start != (int)start || end != (int)end || end - start > k_maxNumberOfSteps) { + return Complex::Undefined(); + } + SymbolNode * symbol = static_cast(childAtIndex(1)); + VariableContext nContext = VariableContext(symbol->name(), approximationContext.context()); + Evaluation result = Complex::Builder((T)emptySumAndProductValue()); + for (int i = (int)start; i <= (int)end; i++) { + if (Expression::ShouldStopProcessing()) { + return Complex::Undefined(); + } + nContext.setApproximationForVariable((T)i); + Expression child = Expression(childAtIndex(0)).clone(); + if (child.type() == ExpressionNode::Type::Sequence) { + /* Since we cannot get the expression of a sequence term like we would for + * a function, we replace its potential abstract rank by the value it should + * have. We can then evaluate its value */ + child.childAtIndex(0).replaceSymbolWithExpression(symbol, Float::Builder(i)); + } + approximationContext.setContext(&nContext); + result = evaluateWithNextTerm(T(), result, child.node()->approximate(T(), approximationContext), approximationContext.complexFormat()); + if (result.isUndefined()) { + return Complex::Undefined(); + } + } + return result; +} + +Expression SumAndProduct::shallowReduce(Context * context) { + { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + } + assert(!childAtIndex(1).deepIsMatrix(context)); + if (childAtIndex(2).deepIsMatrix(context) || childAtIndex(3).deepIsMatrix(context)) { + return replaceWithUndefinedInPlace(); + } + return *this; +} + +template Evaluation SumAndProductNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation SumAndProductNode::templatedApproximate(ApproximationContext approximationContext) const; + +} diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 64e27437c..269f0f267 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -3,33 +3,71 @@ #include #include + + namespace Poincare { - -const uint8_t symbolPixel[SumLayoutNode::k_symbolHeight][SumLayoutNode::k_symbolWidth] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, +// Stores a single branch of the sigma symbol +// Data is stored so that every two line a white pixel must be added. This way the branch's slope is respected +constexpr static int k_significantPixelWidth = 6; +const uint8_t symbolPixelOneBranch[((SumLayoutNode::k_symbolHeight-1)/2)*k_significantPixelWidth] = { + 0xCF, 0x10, 0xDF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x70, 0x4D, 0xFF, 0xFF, 0xFF, + 0xEF, 0x10, 0xBF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xAA, 0x20, 0xFF, 0xFF, 0xFF, + 0xFF, 0x4D, 0x7F, 0xFF, 0xFF, 0xFF, + 0xFF, 0xDF, 0x10, 0xDF, 0xFF, 0xFF, + 0xFF, 0x7F, 0x4D, 0xFF, 0xFF, 0xFF, + 0xFF, 0xEF, 0x20, 0xBF, 0xFF, 0xFF, + 0xFF, 0xAA, 0x20, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x4D, 0x7F, 0xFF, 0xFF, + 0xFF, 0xDF, 0x20, 0xDF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x7F, 0x4D, 0xFF, 0xFF, + 0xFF, 0xFF, 0x30, 0xBF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xAA, 0x30, 0xFF, 0xFF, }; - int SumLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return SequenceLayoutNode::writeDerivedClassInBuffer("sum", buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits); } void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { - // Computes sizes. + // Creates half size sigma symbol from one branch + uint8_t symbolPixel[k_symbolHeight * k_symbolWidth]; + int whiteOffset; + + // Taking care of the first line which is a black straight line at the exception of the first pixel + symbolPixel[0] = 0x30; + for (int j = 0; j < k_symbolWidth; j++) { + symbolPixel[j] = 0x00; + } + + static_assert(k_symbolHeight%2 != 0, "sum_layout : k_symbolHeight is even"); + for (int i = 1; i < (k_symbolHeight + 1)/2; i++) { + // Adding the white offset + whiteOffset = (i-1)/2; + for (int j = 0; j < whiteOffset; j++) { + symbolPixel[i*k_symbolWidth + j] = 0xFF; + } + + // Adding the actual pixels of the branch + for (int j = 0; j < k_significantPixelWidth; j++) { + symbolPixel[i*k_symbolWidth + whiteOffset + j] = symbolPixelOneBranch[(i-1)*k_significantPixelWidth + j]; + } + + // Filling the gap with white + for (int j = whiteOffset + k_significantPixelWidth ; j < k_symbolWidth; j++) { + symbolPixel[i*k_symbolWidth + j] = 0xFF; + } + } + + // Create real size sigma symbol by flipping the previous array + for (int i = k_symbolHeight/2 + 1; i < k_symbolHeight; i++) { + for (int j = 0; j < k_symbolWidth; j++) { + symbolPixel[i*k_symbolWidth + j] = symbolPixel[(k_symbolHeight-i-1)*k_symbolWidth +j]; + } + } + + // Compute sizes. KDSize upperBoundSize = upperBoundLayout()->layoutSize(); KDSize lowerBoundNEqualsSize = lowerBoundSizeWithVariableEquals(); @@ -43,5 +81,4 @@ void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, // Render the "n=" and the parentheses. SequenceLayoutNode::render(ctx, p, expressionColor, backgroundColor); } - } diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index b1f1f1687..efa74e016 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -64,29 +64,8 @@ int SymbolNode::getVariables(Context * context, isVariableTest isVariable, char return nextVariableIndex; } -float SymbolNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - return isUnknown() ? NAN : 0.0f; -} - Layout SymbolNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { assert(!isUnknown()); - // TODO return Parse(m_name).createLayout() ? - // Special case for the symbol names: u(n), u(n+1), v(n), v(n+1), w(n), w(n+1) - const char * sequenceIndex[] = {"n", "n+1"}; - for (char sequenceName = 'u'; sequenceName <= 'w'; sequenceName++) { - if (m_name[0] == sequenceName) { - for (size_t i = 0; i < sizeof(sequenceIndex)/sizeof(char *); i++) { - size_t sequenceIndexLength = strlen(sequenceIndex[i]); - if (m_name[1] == '(' && strncmp(sequenceIndex[i], m_name+2, sequenceIndexLength) == 0 && m_name[2+sequenceIndexLength] == ')' && m_name[3+sequenceIndexLength] == 0) { - return HorizontalLayout::Builder( - CodePointLayout::Builder(sequenceName), - VerticalOffsetLayout::Builder( - LayoutHelper::String(sequenceIndex[i], sequenceIndexLength), - VerticalOffsetLayoutNode::Position::Subscript)); - } - } - } - } return LayoutHelper::String(m_name, strlen(m_name)); } @@ -107,14 +86,18 @@ ExpressionNode::LayoutShape SymbolNode::leftLayoutShape() const { return LayoutShape::MoreLetters; } +bool SymbolNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Symbol(this).derivate(reductionContext, symbol, symbolValue); +} + template -Evaluation SymbolNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation SymbolNode::templatedApproximate(ApproximationContext approximationContext) const { Symbol s(this); - Expression e = SymbolAbstract::Expand(s, context, false); + Expression e = SymbolAbstract::Expand(s, approximationContext.context(), false); if (e.isUninitialized()) { return Complex::Undefined(); } - return e.node()->approximate(T(), context, complexFormat, angleUnit); + return e.node()->approximate(T(), approximationContext); } bool SymbolNode::isUnknown() const { @@ -192,6 +175,11 @@ Expression Symbol::shallowReduce(ExpressionNode::ReductionContext reductionConte return result.deepReduce(reductionContext); } +bool Symbol::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + replaceWithInPlace(Rational::Builder(strcmp(name(), symbol.convert().name()) == 0)); + return true; +} + Expression Symbol::replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { if (symbol.type() == ExpressionNode::Type::Symbol && hasSameNameAs(symbol)) { Expression value = expression.clone(); diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index 61c679767..be598f97b 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,7 @@ size_t SymbolAbstractNode::size() const { ExpressionNode::Sign SymbolAbstractNode::sign(Context * context) const { SymbolAbstract s(this); - Expression e = SymbolAbstract::Expand(s, context, false); + Expression e = SymbolAbstract::Expand(s, context, true); if (e.isUninitialized()) { return Sign::Unknown; } @@ -72,6 +73,7 @@ Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * conte { return clone ? symbol.clone() : *const_cast(&symbol); } + assert(context); Expression e = context->expressionForSymbolAbstract(symbol, clone); /* Replace all the symbols iteratively. This prevents a memory failure when * symbols are defined circularly. */ @@ -81,6 +83,8 @@ Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * conte template Constant SymbolAbstract::Builder(char const*, int); template Function SymbolAbstract::Builder(char const*, int); +template Sequence SymbolAbstract::Builder(char const*, int); template Symbol SymbolAbstract::Builder(char const*, int); + } diff --git a/poincare/src/tangent.cpp b/poincare/src/tangent.cpp index e140a4992..f62ecd67b 100644 --- a/poincare/src/tangent.cpp +++ b/poincare/src/tangent.cpp @@ -1,7 +1,10 @@ #include #include +#include #include #include +#include +#include #include #include @@ -14,10 +17,6 @@ constexpr Expression::FunctionHelper Tangent::s_functionHelper; int TangentNode::numberOfChildren() const { return Tangent::s_functionHelper.numberOfChildren(); } -float TangentNode::characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const { - return Trigonometry::characteristicXRange(Tangent(this), context, angleUnit); -} - Layout TangentNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return LayoutHelper::Prefix(Tangent(this), floatDisplayMode, numberOfSignificantDigits, Tangent::s_functionHelper.name()); } @@ -37,6 +36,13 @@ Expression TangentNode::shallowReduce(ReductionContext reductionContext) { return Tangent(this).shallowReduce(reductionContext); } +bool TangentNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Tangent(this).derivate(reductionContext, symbol, symbolValue); +} + +Expression TangentNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Tangent(this).unaryFunctionDifferential(reductionContext); +} Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { @@ -60,4 +66,13 @@ Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionCont return newExpression; } +bool Tangent::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); + return true; +} + +Expression Tangent::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { + return Multiplication::Builder(Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Power::Builder(Cosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2))); +} + } diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 477565d25..992e22cee 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -311,7 +311,7 @@ template FloorLayout TreeHandle::FixedArityBuilder template FracPart TreeHandle::FixedArityBuilder(const Tuple &); template FractionLayout TreeHandle::FixedArityBuilder(const Tuple &); template Ghost TreeHandle::FixedArityBuilder(const Tuple &); -template GreatCommonDivisor TreeHandle::FixedArityBuilder(const Tuple &); +template GreatCommonDivisor TreeHandle::NAryBuilder(const Tuple &); template HorizontalLayout TreeHandle::NAryBuilder(const Tuple &); template HyperbolicArcCosine TreeHandle::FixedArityBuilder(const Tuple &); template HyperbolicArcSine TreeHandle::FixedArityBuilder(const Tuple &); @@ -324,7 +324,7 @@ template Integral TreeHandle::FixedArityBuilder(const Tu template IntegralLayout TreeHandle::FixedArityBuilder(const Tuple &); template InvBinom TreeHandle::FixedArityBuilder(const Tuple &); template InvNorm TreeHandle::FixedArityBuilder(const Tuple &); -template LeastCommonMultiple TreeHandle::FixedArityBuilder(const Tuple &); +template LeastCommonMultiple TreeHandle::NAryBuilder(const Tuple &); template LeftParenthesisLayout TreeHandle::FixedArityBuilder(const Tuple &); template LeftSquareBracketLayout TreeHandle::FixedArityBuilder(const Tuple &); template Logarithm TreeHandle::FixedArityBuilder >(const Tuple &); @@ -336,6 +336,8 @@ template MatrixIdentity TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTrace TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTranspose TreeHandle::FixedArityBuilder(const Tuple &); +template MatrixRowEchelonForm TreeHandle::FixedArityBuilder(const Tuple &); +template MatrixReducedRowEchelonForm TreeHandle::FixedArityBuilder(const Tuple &); template Multiplication TreeHandle::NAryBuilder(const Tuple &); template NaperianLogarithm TreeHandle::FixedArityBuilder(const Tuple &); template NormCDF TreeHandle::FixedArityBuilder(const Tuple &); @@ -367,6 +369,10 @@ template Tangent TreeHandle::FixedArityBuilder(const Tuple template Undefined TreeHandle::FixedArityBuilder(const Tuple &); template UnitConvert TreeHandle::FixedArityBuilder(const Tuple &); template Unreal TreeHandle::FixedArityBuilder(const Tuple &); +template VectorCross TreeHandle::FixedArityBuilder(const Tuple &); +template VectorDot TreeHandle::FixedArityBuilder(const Tuple &); +template VectorNorm TreeHandle::FixedArityBuilder(const Tuple &); +template VectorNormLayout TreeHandle::FixedArityBuilder(const Tuple &); template MatrixLayout TreeHandle::NAryBuilder(const Tuple &); } diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index 7ece0a2cc..bdaa0a165 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -14,10 +15,12 @@ #include #include #include +#include #include #include #include #include +#include namespace Poincare { @@ -53,32 +56,6 @@ double Trigonometry::PiInAngleUnit(Preferences::AngleUnit angleUnit) { return 200.0; } -float Trigonometry::characteristicXRange(const Expression & e, Context * context, Preferences::AngleUnit angleUnit) { - assert(e.numberOfChildren() == 1); - - constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; - char x[bufferSize]; - SerializationHelper::CodePoint(x, bufferSize, UCodePointUnknown); - - int d = e.childAtIndex(0).polynomialDegree(context, x); - if (d < 0 || d > 1) { - // child(0) is not linear so we cannot easily find an interesting range - return e.childAtIndex(0).characteristicXRange(context, angleUnit); - } - // The expression e is x-independent - if (d == 0) { - return 0.0f; - } - // e has the form cos/sin/tan(ax+b) so it is periodic of period 2*π/a - assert(d == 1); - /* To compute a, the slope of the expression child(0), we compute the - * derivative of child(0) for any x value. */ - Poincare::Derivative derivative = Poincare::Derivative::Builder(e.childAtIndex(0).clone(), Symbol::Builder(x, 1), Float::Builder(1.0f)); - double a = derivative.node()->approximate(double(), context, Preferences::ComplexFormat::Real, angleUnit).toScalar(); - double pi = PiInAngleUnit(angleUnit); - return std::fabs(a) < Expression::Epsilon() ? NAN : 2.0*pi/std::fabs(a); -} - bool Trigonometry::isDirectTrigonometryFunction(const Expression & e) { return e.type() == ExpressionNode::Type::Cosine || e.type() == ExpressionNode::Type::Sine @@ -111,6 +88,14 @@ bool Trigonometry::AreInverseFunctions(const Expression & directFunction, const return inverseFunction.type() == correspondingType; } +Expression Trigonometry::UnitConversionFactor(Preferences::AngleUnit fromUnit, Preferences::AngleUnit toUnit) { + if (fromUnit == toUnit) { + // Just an optimisation to gain some time at reduction + return Rational::Builder(1); + } + return Division::Builder(piExpression(toUnit), piExpression(fromUnit)); +} + bool Trigonometry::ExpressionIsEquivalentToTangent(const Expression & e) { // We look for (cos^-1 * sin) assert(ExpressionNode::Type::Power < ExpressionNode::Type::Sine); @@ -296,7 +281,7 @@ Expression Trigonometry::shallowReduceDirectFunction(Expression & e, ExpressionN return e; } -Expression Trigonometry::shallowReduceInverseFunction(Expression & e, ExpressionNode::ReductionContext reductionContext) { +Expression Trigonometry::shallowReduceInverseFunction(Expression & e, ExpressionNode::ReductionContext reductionContext) { assert(isInverseTrigonometryFunction(e)); // Step 0. Map on matrix child if possible { @@ -310,19 +295,35 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expressio // Step 1. Look for an expression of type "acos(cos(x))", return x if (AreInverseFunctions(e.childAtIndex(0), e)) { - float trigoOp = e.childAtIndex(0).childAtIndex(0).node()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), angleUnit).toScalar(); - if ((e.type() == ExpressionNode::Type::ArcCosine && trigoOp >= 0.0f && trigoOp <= pi) || - (e.type() == ExpressionNode::Type::ArcSine && trigoOp >= -pi/2.0f && trigoOp <= pi/2.0f) || - (e.type() == ExpressionNode::Type::ArcTangent && trigoOp >= -pi/2.0f && trigoOp <= pi/2.0f)) { + float x = e.childAtIndex(0).childAtIndex(0).node()->approximate(float(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); + if (!(std::isinf(x) || std::isnan(x))) { Expression result = e.childAtIndex(0).childAtIndex(0); + // We translate the result within [-π,π] for acos(cos), [-π/2,π/2] for asin(sin) and atan(tan) + float k = (e.type() == ExpressionNode::Type::ArcCosine) ? std::floor(x/pi) : std::floor((x+pi/2.0f)/pi); + if (!std::isinf(k) && !std::isnan(k) && std::fabs(k) <= static_cast(INT_MAX)) { + int kInt = static_cast(k); + Multiplication mult = Multiplication::Builder(Rational::Builder(-kInt), piExpression(reductionContext.angleUnit())); + result = Addition::Builder(result.clone(), mult); + mult.shallowReduce(reductionContext); + if ((e.type() == ExpressionNode::Type::ArcCosine) && ((int)k%2 == 1)) { + Expression sub = Subtraction::Builder(piExpression(reductionContext.angleUnit()), result); + result.shallowReduce(reductionContext); + result = sub; + } + if ((e.type() == ExpressionNode::Type::ArcSine) && ((int)k%2 == 1)) { + Expression add = result; + result = Opposite::Builder(add); + add.shallowReduce(reductionContext); + } + } e.replaceWithInPlace(result); - return result; + return result.shallowReduce(reductionContext); } } // Step 2. Special case for atan(sin(x)/cos(x)) if (e.type() == ExpressionNode::Type::ArcTangent && ExpressionIsEquivalentToTangent(e.childAtIndex(0))) { - float trigoOp = e.childAtIndex(0).childAtIndex(1).childAtIndex(0).node()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), angleUnit).toScalar(); + float trigoOp = e.childAtIndex(0).childAtIndex(1).childAtIndex(0).node()->approximate(float(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); if (trigoOp >= -pi/2.0f && trigoOp <= pi/2.0f) { Expression result = e.childAtIndex(0).childAtIndex(1).childAtIndex(0); e.replaceWithInPlace(result); @@ -339,14 +340,16 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expressio * reduced to undef) */ if (reductionContext.target() == ExpressionNode::ReductionTarget::User || x.isNumber()) { Expression sign = SignFunction::Builder(x.clone()); - Multiplication m0 = Multiplication::Builder(Rational::Builder(1,2), sign, Constant::Builder(UCodePointGreekSmallLetterPi)); + Multiplication m0 = Multiplication::Builder(Rational::Builder(1,2), sign, piExpression(angleUnit)); sign.shallowReduce(reductionContext); e.replaceChildAtIndexInPlace(0, x); Addition a = Addition::Builder(m0); + m0.shallowReduce(reductionContext); e.replaceWithInPlace(a); Multiplication m1 = Multiplication::Builder(Rational::Builder(-1), e); e.shallowReduce(reductionContext); a.addChildAtIndexInPlace(m1, 1, 1); + m1.shallowReduce(reductionContext); return a.shallowReduce(reductionContext); } } diff --git a/poincare/src/trigonometry_cheat_table.cpp b/poincare/src/trigonometry_cheat_table.cpp index 6cf2ca3e9..60515d6c5 100644 --- a/poincare/src/trigonometry_cheat_table.cpp +++ b/poincare/src/trigonometry_cheat_table.cpp @@ -59,7 +59,7 @@ Expression TrigonometryCheatTable::simplify(const Expression e, ExpressionNode:: } // Approximate e to quickly compare it to cheat table entries - float eValue = e.node()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()).toScalar(); + float eValue = e.node()->approximate(float(), ExpressionNode::ApproximationContext(reductionContext, true)).toScalar(); if (std::isnan(eValue) || std::isinf(eValue)) { return Expression(); } diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 21c6f6d1a..35bc19e70 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -1,118 +1,116 @@ #include #include -#include #include -#include -#include +#include #include #include #include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include namespace Poincare { -static inline int absInt(int x) { return x >= 0 ? x : -x; } +constexpr const UnitNode::Prefix Unit::k_prefixes[]; +constexpr const UnitNode::TimeRepresentative Unit::k_timeRepresentatives[]; +constexpr const UnitNode::DistanceRepresentative Unit::k_distanceRepresentatives[]; +constexpr const UnitNode::MassRepresentative Unit::k_massRepresentatives[]; +constexpr const UnitNode::CurrentRepresentative Unit::k_currentRepresentatives[]; +constexpr const UnitNode::TemperatureRepresentative Unit::k_temperatureRepresentatives[]; +constexpr const UnitNode::AmountOfSubstanceRepresentative Unit::k_amountOfSubstanceRepresentatives[]; +constexpr const UnitNode::LuminousIntensityRepresentative Unit::k_luminousIntensityRepresentatives[]; +constexpr const UnitNode::FrequencyRepresentative Unit::k_frequencyRepresentatives[]; +constexpr const UnitNode::ForceRepresentative Unit::k_forceRepresentatives[]; +constexpr const UnitNode::PressureRepresentative Unit::k_pressureRepresentatives[]; +constexpr const UnitNode::EnergyRepresentative Unit::k_energyRepresentatives[]; +constexpr const UnitNode::PowerRepresentative Unit::k_powerRepresentatives[]; +constexpr const UnitNode::ElectricChargeRepresentative Unit::k_electricChargeRepresentatives[]; +constexpr const UnitNode::ElectricPotentialRepresentative Unit::k_electricPotentialRepresentatives[]; +constexpr const UnitNode::ElectricCapacitanceRepresentative Unit::k_electricCapacitanceRepresentatives[]; +constexpr const UnitNode::ElectricResistanceRepresentative Unit::k_electricResistanceRepresentatives[]; +constexpr const UnitNode::ElectricConductanceRepresentative Unit::k_electricConductanceRepresentatives[]; +constexpr const UnitNode::MagneticFluxRepresentative Unit::k_magneticFluxRepresentatives[]; +constexpr const UnitNode::MagneticFieldRepresentative Unit::k_magneticFieldRepresentatives[]; +constexpr const UnitNode::InductanceRepresentative Unit::k_inductanceRepresentatives[]; +constexpr const UnitNode::CatalyticActivityRepresentative Unit::k_catalyticActivityRepresentatives[]; +constexpr const UnitNode::SurfaceRepresentative Unit::k_surfaceRepresentatives[]; +constexpr const UnitNode::VolumeRepresentative Unit::k_volumeRepresentatives[]; -int UnitNode::Prefix::serialize(char * buffer, int bufferSize) const { - assert(bufferSize >= 0); - return std::min(strlcpy(buffer, m_symbol, bufferSize), bufferSize - 1); +constexpr const int + Unit::k_emptyPrefixIndex, + Unit::k_kiloPrefixIndex, + Unit::k_secondRepresentativeIndex, + Unit::k_minuteRepresentativeIndex, + Unit::k_hourRepresentativeIndex, + Unit::k_dayRepresentativeIndex, + Unit::k_monthRepresentativeIndex, + Unit::k_yearRepresentativeIndex, + Unit::k_meterRepresentativeIndex, + Unit::k_inchRepresentativeIndex, + Unit::k_footRepresentativeIndex, + Unit::k_yardRepresentativeIndex, + Unit::k_mileRepresentativeIndex, + Unit::k_ounceRepresentativeIndex, + Unit::k_poundRepresentativeIndex, + Unit::k_shortTonRepresentativeIndex, + Unit::k_kelvinRepresentativeIndex, + Unit::k_celsiusRepresentativeIndex, + Unit::k_fahrenheitRepresentativeIndex, + Unit::k_jouleRepresentativeIndex, + Unit::k_electronVoltRepresentativeIndex, + Unit::k_wattRepresentativeIndex, + Unit::k_hectareRepresentativeIndex, + Unit::k_acreRepresentativeIndex, + Unit::k_literRepresentativeIndex, + Unit::k_cupRepresentativeIndex, + Unit::k_pintRepresentativeIndex, + Unit::k_quartRepresentativeIndex, + Unit::k_gallonRepresentativeIndex; + +// UnitNode::Prefix +const UnitNode::Prefix * UnitNode::Prefix::Prefixes() { + return Unit::k_prefixes; } -bool UnitNode::Representative::canParse(const char * symbol, size_t length, - const Prefix * * prefix) const -{ - if (!isPrefixable()) { - *prefix = &Unit::EmptyPrefix; - return length == 0; - } - size_t numberOfPrefixes = sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix *); - for (size_t i = 0; i < numberOfPrefixes; i++) { - const Prefix * pre = Unit::AllPrefixes[i]; - const char * prefixSymbol = pre->symbol(); - if (strncmp(symbol, prefixSymbol, length) == 0 && - prefixSymbol[length] == 0) - { - *prefix = pre; - return true; - } - pre++; - } - return false; -} - -int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Prefix * prefix) const { - int length = 0; - length += prefix->serialize(buffer, bufferSize); - assert(length == 0 || isPrefixable()); - assert(length < bufferSize); - buffer += length; - bufferSize -= length; - assert(bufferSize >= 0); - length += std::min(strlcpy(buffer, m_rootSymbol, bufferSize), bufferSize - 1); - return length; -} - -const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const int exponent) const { - if (!isPrefixable()) { - return &Unit::EmptyPrefix; - } - const Prefix * bestPre = nullptr; - unsigned int diff = -1; - /* Find the 'Prefix' with the most adequate 'exponent' for the order of - * magnitude of 'value'. - */ - const int orderOfMagnitude = IEEE754::exponentBase10(std::fabs(value)); - for (size_t i = 0; i < m_outputPrefixesLength; i++) { - const Prefix * pre = m_outputPrefixes[i]; - unsigned int newDiff = absInt(orderOfMagnitude - pre->exponent() * exponent); - if (newDiff < diff) { - diff = newDiff; - bestPre = pre; - } - } - value *= std::pow(10.0, -bestPre->exponent() * exponent); - return bestPre; +const UnitNode::Prefix * UnitNode::Prefix::EmptyPrefix() { + return Prefixes() + Unit::k_emptyPrefixIndex; } +// UnitNode::Vector template<> -Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::metrics() const { +size_t UnitNode::Vector::supportSize() const { size_t supportSize = 0; - Integer norm(0); - for (const Integer * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { - Integer coefficient = *i; - if (coefficient.isZero()) { + for (int i = 0; i < k_numberOfBaseUnits; i++) { + if (coefficientAtIndex(i) == 0) { continue; } supportSize++; - coefficient.setNegative(false); - norm = Integer::Addition(norm, coefficient); } - return {.supportSize = supportSize, .norm = norm}; + return supportSize; } template<> -Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::metrics() const { - size_t supportSize = 0; - int8_t norm = 0; - for (const int8_t * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { - int8_t coefficient = *i; - if (coefficient == 0) { - continue; - } - supportSize++; - norm += coefficient > 0 ? coefficient : -coefficient; +void UnitNode::Vector::addAllCoefficients(const Vector other, int factor) { + for (int i = 0; i < UnitNode::k_numberOfBaseUnits; i++) { + setCoefficientAtIndex(i, coefficientAtIndex(i) + other.coefficientAtIndex(i) * factor); } - return {.supportSize = supportSize, .norm = norm}; } template<> -Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { - Vector vector; +UnitNode::Vector UnitNode::Vector::FromBaseUnits(const Expression baseUnits) { + /* Returns the vector of Base units with integer exponents. If rational, the + * closest integer will be used. */ + Vector vector = { + .time = 0, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }; int numberOfFactors; int factorIndex = 0; Expression factor; @@ -125,18 +123,35 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseU } do { // Get the unit's exponent - Integer exponent(1); + int exponent = 1; if (factor.type() == ExpressionNode::Type::Power) { Expression exp = factor.childAtIndex(1); - assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); - exponent = static_cast(exp).signedIntegerNumerator(); + assert(exp.type() == ExpressionNode::Type::Rational); + // Using the closest integer to the exponent. + float exponentFloat = static_cast(exp).node()->templatedApproximate(); + if (exponentFloat != std::round(exponentFloat)) { + /* If non-integer exponents are found, we round a null vector so that + * Multiplication::shallowBeautify will not attempt to find derived + * units. */ + return vector; + } + /* We limit to INT_MAX / 3 because an exponent might get bigger with + * simplification. As a worst case scenario, (_s²_m²_kg/_A²)^n should be + * simplified to (_s^5_S)^n. If 2*n is under INT_MAX, 5*n might not. */ + if (std::fabs(exponentFloat) < INT_MAX / 3) { + // Exponent can be safely casted as int + exponent = static_cast(std::round(exponentFloat)); + assert(std::fabs(exponentFloat - static_cast(exponent)) <= 0.5f); + } else { + /* Base units vector will ignore this coefficient, to avoid exponent + * overflow. In any way, shallowBeautify will conserve homogeneity. */ + exponent = 0; + } factor = factor.childAtIndex(0); } // Fill the vector with the unit's exponent assert(factor.type() == ExpressionNode::Type::Unit); - const ptrdiff_t indexInTable = static_cast(factor.node())->dimension() - Unit::DimensionTable; - assert(0 <= indexInTable && indexInTable < NumberOfBaseUnits); - vector.setCoefficientAtIndex(indexInTable, exponent); + vector.addAllCoefficients(static_cast(factor).node()->representative()->dimensionVector(), exponent); if (++factorIndex >= numberOfFactors) { break; } @@ -145,57 +160,503 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseU return vector; } -bool UnitNode::Dimension::canParse(const char * symbol, size_t length, - const Representative * * representative, const Prefix * * prefix) const -{ - const Representative * rep = m_representatives; - while (rep < m_representativesUpperBound) { - const char * rootSymbol = rep->rootSymbol(); +template<> +Expression UnitNode::Vector::toBaseUnits() const { + Expression result = Multiplication::Builder(); + int numberOfChildren = 0; + for (int i = 0; i < k_numberOfBaseUnits; i++) { + // We require the base units to be the first seven in DefaultRepresentatives + const Representative * representative = Representative::DefaultRepresentatives()[i]; + assert(representative); + const Prefix * prefix = representative->basePrefix(); + int exponent = coefficientAtIndex(i); + Expression e; + if (exponent == 0) { + continue; + } + if (exponent == 1) { + e = Unit::Builder(representative, prefix); + } else { + e = Power::Builder(Unit::Builder(representative, prefix), Rational::Builder(exponent)); + } + static_cast(result).addChildAtIndexInPlace(e, numberOfChildren, numberOfChildren); + numberOfChildren++; + } + assert(numberOfChildren > 0); + result = static_cast(result).squashUnaryHierarchyInPlace(); + return result; +} + +// UnitNode::Representative +const UnitNode::Representative * const * UnitNode::Representative::DefaultRepresentatives() { + static constexpr SpeedRepresentative defaultSpeedRepresentative = SpeedRepresentative::Default(); + static constexpr const Representative * defaultRepresentatives[k_numberOfDimensions] = { + Unit::k_timeRepresentatives, + Unit::k_distanceRepresentatives, + Unit::k_massRepresentatives, + Unit::k_currentRepresentatives, + Unit::k_temperatureRepresentatives, + Unit::k_amountOfSubstanceRepresentatives, + Unit::k_luminousIntensityRepresentatives, + Unit::k_frequencyRepresentatives, + Unit::k_forceRepresentatives, + Unit::k_pressureRepresentatives, + Unit::k_energyRepresentatives, + Unit::k_powerRepresentatives, + Unit::k_electricChargeRepresentatives, + Unit::k_electricPotentialRepresentatives, + Unit::k_electricCapacitanceRepresentatives, + Unit::k_electricResistanceRepresentatives, + Unit::k_electricConductanceRepresentatives, + Unit::k_magneticFluxRepresentatives, + Unit::k_magneticFieldRepresentatives, + Unit::k_inductanceRepresentatives, + Unit::k_catalyticActivityRepresentatives, + Unit::k_surfaceRepresentatives, + Unit::k_volumeRepresentatives, + &defaultSpeedRepresentative, + }; + return defaultRepresentatives; +} + +const UnitNode::Representative * UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector vector) { + for (int i = 0; i < k_numberOfDimensions; i++) { + const Representative * representative = Representative::DefaultRepresentatives()[i]; + if (vector == representative->dimensionVector()) { + return representative; + } + } + return nullptr; +} + +static bool compareMagnitudeOrders(float order, float otherOrder) { + /* Precision can be lost (with a year conversion for instance), so the order + * value is rounded */ + if (std::fabs(order) < Expression::Epsilon()) { + order = 0.0f; + } + if (std::fabs(otherOrder) < Expression::Epsilon()) { + otherOrder = 0.0f; + } + if (std::fabs(std::fabs(order) - std::fabs(otherOrder)) <= 3.0f + Expression::Epsilon() && order * otherOrder < 0.0f) { + /* If the two values are close, and their sign are opposed, the positive + * order is preferred */ + return (order >= 0.0f); + } + // Otherwise, the closest order to 0 is preferred + return (std::fabs(order) < std::fabs(otherOrder)); +} + +int UnitNode::Prefix::serialize(char * buffer, int bufferSize) const { + assert(bufferSize >= 0); + return std::min(strlcpy(buffer, m_symbol, bufferSize), bufferSize - 1); +} + +const UnitNode::Representative * UnitNode::Representative::DefaultFindBestRepresentative(double value, double exponent, const UnitNode::Representative * representatives, int length, const Prefix * * prefix) { + assert(length >= 1); + const Representative * res = representatives; + double acc = value / std::pow(res->ratio(), exponent); + if (*prefix) { + *prefix = res->findBestPrefix(acc, exponent); + } + if (length == 1) { + return res; + } + const Prefix * pre = Prefix::EmptyPrefix(); + const Representative * iter = res + 1; + while (iter < representatives + length) { + double temp = value / std::pow(iter->ratio(), exponent); + if (*prefix) { + pre = iter->findBestPrefix(temp, exponent); + } + if (compareMagnitudeOrders(std::log10(temp) - pre->exponent() * exponent, std::log10(acc) - ((!*prefix) ? 0 : (*prefix)->exponent() * exponent))) { + acc = temp; + res = iter; + *prefix = pre; + } + iter++; + } + if (!*prefix) { + *prefix = res->basePrefix(); + } + return res; +} + +int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Prefix * prefix) const { + int length = 0; + length += prefix->serialize(buffer, bufferSize); + assert(length == 0 || isInputPrefixable()); + assert(length < bufferSize); + buffer += length; + bufferSize -= length; + assert(bufferSize >= 0); + length += std::min(strlcpy(buffer, m_rootSymbol, bufferSize), bufferSize - 1); + return length; +} + +bool UnitNode::Representative::canParseWithEquivalents(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix) const { + const Representative * candidate = representativesOfSameDimension(); + if (!candidate) { + return false; + } + for (int i = 0; i < numberOfRepresentatives(); i++) { + const char * rootSymbol = (candidate + i)->rootSymbol(); size_t rootSymbolLength = strlen(rootSymbol); int potentialPrefixLength = length - rootSymbolLength; - if (potentialPrefixLength >= 0 && - strncmp(rootSymbol, symbol + potentialPrefixLength, rootSymbolLength) == 0 && - rep->canParse(symbol, potentialPrefixLength, prefix)) - { - *representative = rep; + if (potentialPrefixLength >= 0 + && strncmp(rootSymbol, symbol + potentialPrefixLength, rootSymbolLength) == 0 + && candidate[i].canParse(symbol, potentialPrefixLength, prefix)) { + *representative = (candidate + i); return true; } - rep++; } return false; } -ExpressionNode::Sign UnitNode::sign(Context * context) const { - return Sign::Positive; +bool UnitNode::Representative::canParse(const char * symbol, size_t length, const Prefix * * prefix) const { + if (!isInputPrefixable()) { + *prefix = Prefix::EmptyPrefix(); + return length == 0; + } + for (size_t i = 0; i < Prefix::k_numberOfPrefixes; i++) { + const Prefix * pre = Prefix::Prefixes() + i; + const char * prefixSymbol = pre->symbol(); + if (strlen(prefixSymbol) == length + && canPrefix(pre, true) + && strncmp(symbol, prefixSymbol, length) == 0) + { + *prefix = pre; + return true; + } + } + return false; } +Expression UnitNode::Representative::toBaseUnits() const { + Expression result; + if (isBaseUnit()) { + result = Unit::Builder(this, basePrefix()); + } else { + result = dimensionVector().toBaseUnits(); + } + return Multiplication::Builder(Float::Builder(m_ratio * std::pow(10., - basePrefix()->exponent())), result); +} + +bool UnitNode::Representative::canPrefix(const UnitNode::Prefix * prefix, bool input) const { + Prefixable prefixable = (input) ? m_inputPrefixable : m_outputPrefixable; + if (prefix->exponent() == 0) { + return true; + } + if (prefixable == Prefixable::None) { + return false; + } + if (prefixable == Prefixable::All) { + return true; + } + if (prefixable == Prefixable::LongScale) { + return prefix->exponent() % 3 == 0; + } + if (prefixable == Prefixable::NegativeAndKilo) { + return prefix->exponent() < 0 || prefix->exponent() == 3; + } + if (prefixable == Prefixable::NegativeLongScale) { + return prefix->exponent() < 0 && prefix->exponent() % 3 == 0; + } + if (prefixable == Prefixable::PositiveLongScale) { + return prefix->exponent() > 0 && prefix->exponent() % 3 == 0; + } + if (prefixable == Prefixable::Negative) { + return prefix->exponent() < 0; + } + if (prefixable == Prefixable::Positive) { + return prefix->exponent() > 0; + } + assert(false); + return false; +} + +const UnitNode::Prefix * UnitNode::Representative::findBestPrefix(double value, double exponent) const { + if (!isOutputPrefixable()) { + return Prefix::EmptyPrefix(); + } + if (value < Expression::Epsilon()) { + return basePrefix(); + } + const Prefix * res = basePrefix(); + const float magnitude = std::log10(std::fabs(value)); + float bestOrder = magnitude; + for (int i = 0; i < Prefix::k_numberOfPrefixes; i++) { + if (!canPrefix(Prefix::Prefixes() + i, false)) { + continue; + } + float order = magnitude - (Prefix::Prefixes()[i].exponent() - basePrefix()->exponent()) * exponent; + if (compareMagnitudeOrders(order, bestOrder)) { + bestOrder = order; + res = Prefix::Prefixes() + i; + } + } + return res; +} + +// UnitNode::___Representative +const UnitNode::Representative * UnitNode::TimeRepresentative::representativesOfSameDimension() const { return Unit::k_timeRepresentatives; } +const UnitNode::Representative * UnitNode::DistanceRepresentative::representativesOfSameDimension() const { return Unit::k_distanceRepresentatives; } +const UnitNode::Representative * UnitNode::MassRepresentative::representativesOfSameDimension() const { return Unit::k_massRepresentatives; } +const UnitNode::Representative * UnitNode::CurrentRepresentative::representativesOfSameDimension() const { return Unit::k_currentRepresentatives; } +const UnitNode::Representative * UnitNode::TemperatureRepresentative::representativesOfSameDimension() const { return Unit::k_temperatureRepresentatives; } +const UnitNode::Representative * UnitNode::AmountOfSubstanceRepresentative::representativesOfSameDimension() const { return Unit::k_amountOfSubstanceRepresentatives; } +const UnitNode::Representative * UnitNode::LuminousIntensityRepresentative::representativesOfSameDimension() const { return Unit::k_luminousIntensityRepresentatives; } +const UnitNode::Representative * UnitNode::FrequencyRepresentative::representativesOfSameDimension() const { return Unit::k_frequencyRepresentatives; } +const UnitNode::Representative * UnitNode::ForceRepresentative::representativesOfSameDimension() const { return Unit::k_forceRepresentatives; } +const UnitNode::Representative * UnitNode::PressureRepresentative::representativesOfSameDimension() const { return Unit::k_pressureRepresentatives; } +const UnitNode::Representative * UnitNode::EnergyRepresentative::representativesOfSameDimension() const { return Unit::k_energyRepresentatives; } +const UnitNode::Representative * UnitNode::PowerRepresentative::representativesOfSameDimension() const { return Unit::k_powerRepresentatives; } +const UnitNode::Representative * UnitNode::ElectricChargeRepresentative::representativesOfSameDimension() const { return Unit::k_electricChargeRepresentatives; } +const UnitNode::Representative * UnitNode::ElectricPotentialRepresentative::representativesOfSameDimension() const { return Unit::k_electricPotentialRepresentatives; } +const UnitNode::Representative * UnitNode::ElectricCapacitanceRepresentative::representativesOfSameDimension() const { return Unit::k_electricCapacitanceRepresentatives; } +const UnitNode::Representative * UnitNode::ElectricResistanceRepresentative::representativesOfSameDimension() const { return Unit::k_electricResistanceRepresentatives; } +const UnitNode::Representative * UnitNode::ElectricConductanceRepresentative::representativesOfSameDimension() const { return Unit::k_electricConductanceRepresentatives; } +const UnitNode::Representative * UnitNode::MagneticFluxRepresentative::representativesOfSameDimension() const { return Unit::k_magneticFluxRepresentatives; } +const UnitNode::Representative * UnitNode::MagneticFieldRepresentative::representativesOfSameDimension() const { return Unit::k_magneticFieldRepresentatives; } +const UnitNode::Representative * UnitNode::InductanceRepresentative::representativesOfSameDimension() const { return Unit::k_inductanceRepresentatives; } +const UnitNode::Representative * UnitNode::CatalyticActivityRepresentative::representativesOfSameDimension() const { return Unit::k_catalyticActivityRepresentatives; } +const UnitNode::Representative * UnitNode::SurfaceRepresentative::representativesOfSameDimension() const { return Unit::k_surfaceRepresentatives; } +const UnitNode::Representative * UnitNode::VolumeRepresentative::representativesOfSameDimension() const { return Unit::k_volumeRepresentatives; } + +int UnitNode::TimeRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 1); + /* Use all representatives but week */ + const Unit splitUnits[] = { + Unit::Builder(representativesOfSameDimension() + Unit::k_secondRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_minuteRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_hourRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_dayRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_monthRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_yearRepresentativeIndex, Prefix::EmptyPrefix()), + }; + dest[0] = Unit::BuildSplit(value, splitUnits, numberOfRepresentatives() - 1, reductionContext); + return 1; +} + +const UnitNode::Representative * UnitNode::DistanceRepresentative::standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { + return (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? + /* Exclude imperial units from the search. */ + DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension(), Unit::k_inchRepresentativeIndex, prefix) : + /* Exclude m form the search. */ + DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension() + 1, numberOfRepresentatives() - 1, prefix); +} + +int UnitNode::DistanceRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 1); + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + return 0; + } + const Unit splitUnits[] = { + Unit::Builder(representativesOfSameDimension() + Unit::k_inchRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_footRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_yardRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_mileRepresentativeIndex, Prefix::EmptyPrefix()), + }; + dest[0] = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); + return 1; +} + +const UnitNode::Prefix * UnitNode::MassRepresentative::basePrefix() const { + return isBaseUnit() ? Prefix::Prefixes() + Unit::k_kiloPrefixIndex : Prefix::EmptyPrefix(); +} + +const UnitNode::Representative * UnitNode::MassRepresentative::standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { + return (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? + /* Only search in g. */ + DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension(), 1, prefix) : + /* Only search in imperial units without the long ton. */ + DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension() + Unit::k_ounceRepresentativeIndex, Unit::k_shortTonRepresentativeIndex - Unit::k_ounceRepresentativeIndex + 1, prefix); +} + +int UnitNode::MassRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 1); + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + return 0; + } + const Unit splitUnits[] = { + Unit::Builder(representativesOfSameDimension() + Unit::k_ounceRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_poundRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_shortTonRepresentativeIndex, Prefix::EmptyPrefix()), + }; + dest[0] = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); + return 1; +} + +double UnitNode::TemperatureRepresentative::ConvertTemperatures(double value, const Representative * source, const Representative * target) { + assert(source->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + assert(target->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + if (source == target) { + return value; + } + constexpr double origin[] = {0, k_celsiusOrigin, k_fahrenheitOrigin}; + assert(sizeof(origin) == source->numberOfRepresentatives() * sizeof(double)); + double sourceOrigin = origin[source - source->representativesOfSameDimension()]; + double targetOrigin = origin[target - target->representativesOfSameDimension()]; + /* (T + origin) * ration converts T to Kelvin. + * T/ratio - origin converts T from Kelvin. */ + return (value + sourceOrigin) * source->ratio() / target->ratio() - targetOrigin; +} + +int UnitNode::TemperatureRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + const Representative * celsius = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_celsiusRepresentativeIndex; + const Representative * fahrenheit = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_fahrenheitRepresentativeIndex; + const Representative * kelvin = TemperatureRepresentative::Default().representativesOfSameDimension() + Unit::k_kelvinRepresentativeIndex; + const Representative * targets[] = { + reductionContext.unitFormat() == Preferences::UnitFormat::Metric ? celsius : fahrenheit, + reductionContext.unitFormat() == Preferences::UnitFormat::Metric ? fahrenheit : celsius, + kelvin}; + int numberOfExpressionsSet = 0; + int numberOfTargets = sizeof(targets) / sizeof(Representative *); + for (int i = 0; i < numberOfTargets; i++) { + if (targets[i] == this) { + continue; + } + dest[numberOfExpressionsSet++] = Multiplication::Builder( + Float::Builder(TemperatureRepresentative::ConvertTemperatures(value, this, targets[i])), + Unit::Builder(targets[i], Prefix::EmptyPrefix())); + } + assert(numberOfExpressionsSet == 2); + return numberOfExpressionsSet; +} + +int UnitNode::EnergyRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + int index = 0; + /* 1. Convert into Joules + * As J is just a shorthand for _kg_m^2_s^-2, the value is used as is. */ + const Representative * joule = representativesOfSameDimension() + Unit::k_jouleRepresentativeIndex; + const Prefix * joulePrefix = joule->findBestPrefix(value, 1.); + dest[index++] = Multiplication::Builder(Float::Builder(value * std::pow(10., -joulePrefix->exponent())), Unit::Builder(joule, joulePrefix)); + /* 2. Convert into Wh + * As value is expressed in SI units (ie _kg_m^2_s^-2), the ratio is that of + * hours to seconds. */ + const Representative * hour = TimeRepresentative::Default().representativesOfSameDimension() + Unit::k_hourRepresentativeIndex; + const Representative * watt = PowerRepresentative::Default().representativesOfSameDimension() + Unit::k_wattRepresentativeIndex; + double adjustedValue = value / hour->ratio() / watt->ratio(); + const Prefix * wattPrefix = watt->findBestPrefix(adjustedValue, 1.); + dest[index++] = Multiplication::Builder( + Float::Builder(adjustedValue * std::pow(10., -wattPrefix->exponent())), + Multiplication::Builder( + Unit::Builder(watt, wattPrefix), + Unit::Builder(hour, Prefix::EmptyPrefix()))); + /* 3. Convert into eV */ + const Representative * eV = representativesOfSameDimension() + Unit::k_electronVoltRepresentativeIndex; + adjustedValue = value / eV->ratio(); + const Prefix * eVPrefix = eV->findBestPrefix(adjustedValue, 1.); + dest[index++] = Multiplication::Builder( + Float::Builder(adjustedValue * std::pow(10., -eVPrefix->exponent())), + Unit::Builder(eV, eVPrefix)); + return index; +} + +const UnitNode::Representative * UnitNode::SurfaceRepresentative::standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { + *prefix = Prefix::EmptyPrefix(); + return representativesOfSameDimension() + (reductionContext.unitFormat() == Preferences::UnitFormat::Metric ? Unit::k_hectareRepresentativeIndex : Unit::k_acreRepresentativeIndex); +} + +int UnitNode::SurfaceRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + Expression * destMetric; + Expression * destImperial = nullptr; + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + destMetric = dest; + } else { + destImperial = dest; + destMetric = dest + 1; + } + // 1. Convert to hectares + const Representative * hectare = representativesOfSameDimension() + Unit::k_hectareRepresentativeIndex; + *destMetric = Multiplication::Builder(Float::Builder(value / hectare->ratio()), Unit::Builder(hectare, Prefix::EmptyPrefix())); + // 2. Convert to acres + if (!destImperial) { + return 1; + } + const Representative * acre = representativesOfSameDimension() + Unit::k_acreRepresentativeIndex; + *destImperial = Multiplication::Builder(Float::Builder(value / acre->ratio()), Unit::Builder(acre, Prefix::EmptyPrefix())); + return 2; +} + +const UnitNode::Representative * UnitNode::VolumeRepresentative::standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + *prefix = representativesOfSameDimension()->findBestPrefix(value, exponent); + return representativesOfSameDimension(); + } + return DefaultFindBestRepresentative(value, exponent, representativesOfSameDimension() + 1, numberOfRepresentatives() - 1, prefix); +} + +int UnitNode::VolumeRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + Expression * destMetric; + Expression * destImperial = nullptr; + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + destMetric = dest; + } else { + destImperial = dest; + destMetric = dest + 1; + } + // 1. Convert to liters + const Representative * liter = representativesOfSameDimension() + Unit::k_literRepresentativeIndex; + double adjustedValue = value / liter->ratio(); + const Prefix * literPrefix = liter->findBestPrefix(adjustedValue, 1.); + *destMetric = Multiplication::Builder( + Float::Builder(adjustedValue * pow(10., -literPrefix->exponent())), + Unit::Builder(liter, literPrefix)); + // 2. Convert to imperial volumes + if (!destImperial) { + return 1; + } + const Unit splitUnits[] = { + Unit::Builder(representativesOfSameDimension() + Unit::k_cupRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_pintRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_quartRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_gallonRepresentativeIndex, Prefix::EmptyPrefix()), + }; + *destImperial = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); + return 2; +} + +int UnitNode::SpeedRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + Expression * destMetric; + Expression * destImperial = nullptr; + if (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) { + destMetric = dest; + } else { + destImperial = dest; + destMetric = dest + 1; + } + // 1. Convert to km/h + const Representative * meter = DistanceRepresentative::Default().representativesOfSameDimension() + Unit::k_meterRepresentativeIndex; + const Representative * hour = TimeRepresentative::Default().representativesOfSameDimension() + Unit::k_hourRepresentativeIndex; + *destMetric = Multiplication::Builder( + Float::Builder(value / 1000. * hour->ratio()), + Multiplication::Builder( + Unit::Builder(meter, Prefix::Prefixes() + Unit::k_kiloPrefixIndex), + Power::Builder(Unit::Builder(hour, Prefix::EmptyPrefix()), Rational::Builder(-1)))); + // 2. Convert to mph + if (!destImperial) { + return 1; + } + const Representative * mile = DistanceRepresentative::Default().representativesOfSameDimension() + Unit::k_mileRepresentativeIndex; + *destImperial = Multiplication::Builder( + Float::Builder(value / mile->ratio() * hour->ratio()), + Multiplication::Builder( + Unit::Builder(mile, Prefix::EmptyPrefix()), + Power::Builder(Unit::Builder(hour, Prefix::EmptyPrefix()), Rational::Builder(-1)))); + return 2; +} + +// UnitNode Expression UnitNode::removeUnit(Expression * unit) { return Unit(this).removeUnit(unit); } -int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { - if (!ascending) { - return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); - } - assert(type() == e->type()); - const UnitNode * eNode = static_cast(e); - // This works because dimensions are ordered in a table - const ptrdiff_t dimdiff = eNode->dimension() - m_dimension; - if (dimdiff != 0) { - return dimdiff; - } - // This works because reprensentatives are ordered in a table - const ptrdiff_t repdiff = eNode->representative() - m_representative; - if (repdiff != 0) { - /* We order representatives in the reverse order as how they're stored in - * the representatives table. This enables to sort addition of time as: - * year + month + days + hours + minutes + seconds. */ - return -repdiff; - } - const ptrdiff_t prediff = eNode->prefix()->exponent() - m_prefix->exponent(); - return prediff; -} - Layout UnitNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { /* TODO: compute the bufferSize more precisely... So far the longest unit is * "month" of size 6 but later, we might add unicode to represent ohm or µ @@ -215,125 +676,238 @@ int UnitNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMo return underscoreLength + m_representative->serialize(buffer, bufferSize, m_prefix); } -template -Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Complex::Undefined(); +int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + if (!ascending) { + return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); + } + assert(type() == e->type()); + const UnitNode * eNode = static_cast(e); + Vector v = representative()->dimensionVector(); + Vector w = eNode->representative()->dimensionVector(); + for (int i = 0; i < k_numberOfBaseUnits; i++) { + if (v.coefficientAtIndex(i) != w.coefficientAtIndex(i)) { + return v.coefficientAtIndex(i) - w.coefficientAtIndex(i); + } + } + const ptrdiff_t representativeDiff = m_representative - eNode->representative(); + if (representativeDiff != 0) { + return representativeDiff; + } + const ptrdiff_t prediff = eNode->prefix()->exponent() - m_prefix->exponent(); + return prediff; } Expression UnitNode::shallowReduce(ReductionContext reductionContext) { return Unit(this).shallowReduce(reductionContext); } -Expression UnitNode::shallowBeautify(ReductionContext reductionContext) { - return Unit(this).shallowBeautify(reductionContext); +Expression UnitNode::shallowBeautify(ReductionContext * reductionContext) { + return Unit(this).shallowBeautify(); } -constexpr const Unit::Prefix - Unit::PicoPrefix, - Unit::NanoPrefix, - Unit::MicroPrefix, - Unit::MilliPrefix, - Unit::CentiPrefix, - Unit::DeciPrefix, - Unit::EmptyPrefix, - Unit::DecaPrefix, - Unit::HectoPrefix, - Unit::KiloPrefix, - Unit::MegaPrefix, - Unit::GigaPrefix, - Unit::TeraPrefix; -constexpr const Unit::Prefix * const Unit::NoPrefix[]; -constexpr const Unit::Prefix * const Unit::NegativeLongScalePrefixes[]; -constexpr const Unit::Prefix * const Unit::PositiveLongScalePrefixes[]; -constexpr const Unit::Prefix * const Unit::LongScalePrefixes[]; -constexpr const Unit::Prefix * const Unit::NegativePrefixes[]; -constexpr const Unit::Prefix * const Unit::AllPrefixes[]; -constexpr const Unit::Representative - Unit::TimeRepresentatives[], - Unit::DistanceRepresentatives[], - Unit::SolideAngleRepresentatives[], - Unit::MassRepresentatives[], - Unit::CurrentRepresentatives[], - Unit::TemperatureRepresentatives[], - Unit::AmountOfSubstanceRepresentatives[], - Unit::LuminousIntensityRepresentatives[], - Unit::FrequencyRepresentatives[], - Unit::LuminousFluxRepresentatives[], - Unit::IlluminanceRepresentatives[], - Unit::ForceRepresentatives[], - Unit::PressureRepresentatives[], - Unit::EnergyRepresentatives[], - Unit::PowerRepresentatives[], - Unit::ElectricChargeRepresentatives[], - Unit::ElectricPotentialRepresentatives[], - Unit::ElectricCapacitanceRepresentatives[], - Unit::ElectricResistanceRepresentatives[], - Unit::ElectricConductanceRepresentatives[], - Unit::MagneticFluxRepresentatives[], - Unit::MagneticFieldRepresentatives[], - Unit::InductanceRepresentatives[], - Unit::CatalyticActivityRepresentatives[], - Unit::SurfaceRepresentatives[], - Unit::VolumeRepresentatives[]; -const Unit::Representative constexpr * Unit::SecondRepresentative; -const Unit::Representative constexpr * Unit::HourRepresentative; -const Unit::Representative constexpr * Unit::MeterRepresentative; -constexpr const Unit::Dimension Unit::DimensionTable[]; -const Unit::Dimension constexpr * Unit::TimeDimension; -const Unit::Dimension constexpr * Unit::DistanceDimension; -constexpr const Unit::Dimension * Unit::DimensionTableUpperBound; +template +Evaluation UnitNode::templatedApproximate(ApproximationContext approximationContext) const { + return Complex::Undefined(); +} -bool Unit::CanParse(const char * symbol, size_t length, - const Dimension * * dimension, const Representative * * representative, const Prefix * * prefix) -{ - for (const Dimension * dim = DimensionTable; dim < DimensionTableUpperBound; dim++) { - if (dim->canParse(symbol, length, representative, prefix)) { - *dimension = dim; +// Unit +Unit Unit::Builder(const Unit::Representative * representative, const Prefix * prefix) { + void * bufferNode = TreePool::sharedPool()->alloc(sizeof(UnitNode)); + UnitNode * node = new (bufferNode) UnitNode(representative, prefix); + TreeHandle h = TreeHandle::BuildWithGhostChildren(node); + return static_cast(h); +} + +bool Unit::CanParse(const char * symbol, size_t length, const Unit::Representative * * representative, const Unit::Prefix * * prefix) { + for (int i = 0; i < Representative::k_numberOfDimensions; i++) { + if (Representative::DefaultRepresentatives()[i]->canParseWithEquivalents(symbol, length, representative, prefix)) { return true; } } return false; } -Unit Unit::Builder(const Dimension * dimension, const Representative * representative, const Prefix * prefix) { - void * bufferNode = TreePool::sharedPool()->alloc(sizeof(UnitNode)); - UnitNode * node = new (bufferNode) UnitNode(dimension, representative, prefix); - TreeHandle h = TreeHandle::BuildWithGhostChildren(node); - return static_cast(h); +static void chooseBestRepresentativeAndPrefixForValueOnSingleUnit(Expression unit, double * value, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix) { + double exponent = 1.f; + Expression factor = unit; + if (factor.type() == ExpressionNode::Type::Power) { + Expression childExponent = factor.childAtIndex(1); + assert(factor.childAtIndex(0).type() == ExpressionNode::Type::Unit); + assert(factor.childAtIndex(1).type() == ExpressionNode::Type::Rational); + exponent = static_cast(childExponent).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + factor = factor.childAtIndex(0); + } + assert(factor.type() == ExpressionNode::Type::Unit); + if (exponent == 0.f) { + /* Finding the best representative for a unit with exponent 0 doesn't + * really make sense, and should only happen with a weak ReductionTarget + * (such as in Graph app), that only rely on approximations. We keep the + * unit unchanged as it will approximate to undef anyway. */ + return; + } + static_cast(factor).chooseBestRepresentativeAndPrefix(value, exponent, reductionContext, optimizePrefix); +} + +void Unit::ChooseBestRepresentativeAndPrefixForValue(Expression units, double * value, ExpressionNode::ReductionContext reductionContext) { + int numberOfFactors; + Expression factor; + if (units.type() == ExpressionNode::Type::Multiplication) { + numberOfFactors = units.numberOfChildren(); + factor = units.childAtIndex(0); + } else { + numberOfFactors = 1; + factor = units; + } + chooseBestRepresentativeAndPrefixForValueOnSingleUnit(factor, value, reductionContext, true); + for (int i = 1; i < numberOfFactors; i++) { + chooseBestRepresentativeAndPrefixForValueOnSingleUnit(units.childAtIndex(i), value, reductionContext, false); + } +} + +bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat) { + if (unit.isUninitialized()) { + return false; + } + UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); + const Representative * representative = Representative::RepresentativeForDimension(vector); + + ExpressionTypeTest isNonBase = [](const Expression e, const void * context) { + return e.type() == ExpressionNode::Type::Unit && !e.convert().isBaseUnit(); + }; + + return (representative != nullptr && representative->hasSpecialAdditionalExpressions(value, unitFormat)) + || unit.hasExpression(isNonBase, nullptr); +} + +int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { + if (units.isUninitialized()) { + return 0; + } + const Representative * representative = units.type() == ExpressionNode::Type::Unit ? static_cast(units).node()->representative() : UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); + if (!representative) { + return 0; + } + return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); +} + +Expression Unit::BuildSplit(double value, const Unit * units, int length, ExpressionNode::ReductionContext reductionContext) { + assert(!std::isnan(value)); + assert(units); + assert(length > 0); + + double baseRatio = units->node()->representative()->ratio(); + double basedValue = value / baseRatio; + if (std::isinf(value) || std::fabs(value) < Expression::Epsilon()) { + return Multiplication::Builder(Number::FloatNumber(value), units[0]); + } + double err = std::pow(10.0, Poincare::PrintFloat::k_numberOfStoredSignificantDigits - 1 - std::ceil(log10(std::fabs(basedValue)))); + double remain = std::round(basedValue*err)/err; + + Addition res = Addition::Builder(); + for (int i = length - 1; i >= 0; i--) { + assert(units[i].node()->prefix() == Prefix::EmptyPrefix()); + double factor = std::round(units[i].node()->representative()->ratio() / baseRatio); + double share = remain / factor; + if (i > 0) { + share = (share > 0.0) ? std::floor(share) : std::ceil(share); + } + remain -= share * factor; + if (std::abs(share) > Expression::Epsilon()) { + res.addChildAtIndexInPlace(Multiplication::Builder(Float::Builder(share), units[i]), res.numberOfChildren(), res.numberOfChildren()); + } + } + ExpressionNode::ReductionContext keepUnitsContext( + reductionContext.context(), + reductionContext.complexFormat(), + reductionContext.angleUnit(), + reductionContext.unitFormat(), + ExpressionNode::ReductionTarget::User, + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, + ExpressionNode::UnitConversion::None); + return res.squashUnaryHierarchyInPlace().shallowBeautify(&keepUnitsContext); +} + +Expression Unit::ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext) { + const Representative * targetRepr = unit.representative(); + const Prefix * targetPrefix = unit.node()->prefix(); + assert(unit.representative()->dimensionVector() == TemperatureRepresentative::Default().dimensionVector()); + + Expression startUnit; + e = e.removeUnit(&startUnit); + if (startUnit.isUninitialized() || startUnit.type() != ExpressionNode::Type::Unit) { + return Undefined::Builder(); + } + const Representative * startRepr = static_cast(startUnit).representative(); + if (startRepr->dimensionVector() != TemperatureRepresentative::Default().dimensionVector()) { + return Undefined::Builder(); + } + + const Prefix * startPrefix = static_cast(startUnit).node()->prefix(); + double value = e.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); + return Multiplication::Builder( + Float::Builder(TemperatureRepresentative::ConvertTemperatures(value * std::pow(10., startPrefix->exponent()), startRepr, targetRepr) * std::pow(10., - targetPrefix->exponent())), + unit.clone()); } Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.unitConversion() == ExpressionNode::UnitConversion::None) { + if (reductionContext.unitConversion() == ExpressionNode::UnitConversion::None + || isBaseUnit()) { + /* We escape early if we are one of the seven base units. + * Nb : For masses, k is considered the base prefix, so kg will be escaped + * here but not g */ return *this; } + + /* Handle temperatures : Celsius and Fahrenheit should not be used in + * calculations, only in conversions and results. + * These are the seven legal forms for writing non-kelvin temperatures : + * (1) _°C + * (2) _°C->_? + * (3) 123_°C + * (4) -123_°C + * (5) 123_°C->_K + * (6) -123_°C->_K + * (7) Right member of a unit convert - this is handled above, as + * UnitConversion is set to None in this case. */ + if (node()->representative()->dimensionVector() == TemperatureRepresentative::Default().dimensionVector() && node()->representative() != k_temperatureRepresentatives + k_kelvinRepresentativeIndex) { + Expression p = parent(); + if (p.isUninitialized() || p.type() == ExpressionNode::Type::UnitConvert) { + // Form (1) and (2) + return *this; + } + if (p.type() == ExpressionNode::Type::Multiplication && p.numberOfChildren() == 2) { + Expression pp = p.parent(); + if (pp.isUninitialized() || pp.type() == UnitNode::Type::UnitConvert) { + // Form (3) and (5) + return *this; + } + Expression ppp = pp.parent(); + if (pp.type() == UnitNode::Type::Opposite && (ppp.isUninitialized() || ppp.type() == UnitNode::Type::UnitConvert)) { + // Form (4) and (6) + return *this; + } + } + return replaceWithUndefinedInPlace(); + } + UnitNode * unitNode = node(); - const Dimension * dim = unitNode->dimension(); - const Representative * rep = unitNode->representative(); - const Prefix * pre = unitNode->prefix(); - int8_t prefixMultiplier = pre->exponent(); - if (rep == dim->stdRepresentative()) { - const Prefix * stdPre = dim->stdRepresentativePrefix(); - unitNode->setPrefix(stdPre); - prefixMultiplier -= stdPre->exponent(); - } - Expression result; - if (rep->definition() == nullptr) { - result = clone(); - } else { - result = Expression::Parse(rep->definition(), nullptr, false).deepReduce(reductionContext); - } - if (prefixMultiplier != 0) { - Expression multiplier = Power::Builder(Rational::Builder(10), Rational::Builder(prefixMultiplier)).shallowReduce(reductionContext); - result = Multiplication::Builder(multiplier, result).shallowReduce(reductionContext); + const Representative * representative = unitNode->representative(); + const Prefix * prefix = unitNode->prefix(); + + Expression result = representative->toBaseUnits().deepReduce(reductionContext); + if (prefix != Prefix::EmptyPrefix()) { + Expression prefixFactor = Power::Builder(Rational::Builder(10), Rational::Builder(prefix->exponent())); + prefixFactor = prefixFactor.shallowReduce(reductionContext); + result = Multiplication::Builder(prefixFactor, result).shallowReduce(reductionContext); } replaceWithInPlace(result); return result; } -Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +Expression Unit::shallowBeautify() { // Force Float(1) in front of an orphan Unit if (parent().isUninitialized() || parent().type() == ExpressionNode::Type::Opposite) { - Multiplication m = Multiplication::Builder(Float::Builder(1.0)); + Multiplication m = Multiplication::Builder(Float::Builder(1.)); replaceWithInPlace(m); m.addChildAtIndexInPlace(*this, 1, 1); return std::move(m); @@ -341,66 +915,6 @@ Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionConte return *this; } -void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { - // Identify the first Unit factor and its exponent - Expression firstFactor = *units; - int exponent = 1; - if (firstFactor.type() == ExpressionNode::Type::Multiplication) { - firstFactor = firstFactor.childAtIndex(0); - } - if (firstFactor.type() == ExpressionNode::Type::Power) { - Expression exp = firstFactor.childAtIndex(1); - firstFactor = firstFactor.childAtIndex(0); - assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); - Integer expInt = static_cast(exp).signedIntegerNumerator(); - if (expInt.isExtractable()) { - exponent = expInt.extractedInt(); - } else { - // The exponent is too large to be extracted, so do not try to use it. - exponent = 0; - } - } - assert(firstFactor.type() == ExpressionNode::Type::Unit); - // Choose its multiple and update value accordingly - if (exponent != 0) { - static_cast(firstFactor).chooseBestMultipleForValue(value, exponent, tuneRepresentative, reductionContext); - } -} - -void Unit::chooseBestMultipleForValue(double * value, const int exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { - assert(!std::isnan(*value) && exponent != 0); - if (*value == 0 || *value == 1.0 || std::isinf(*value)) { - return; - } - UnitNode * unitNode = node(); - const Dimension * dim = unitNode->dimension(); - /* Find in the Dimension 'dim' which unit (Prefix and optionally - * Representative) make the value closer to 1. - */ - const Representative * bestRep = unitNode->representative(); - const Prefix * bestPre = unitNode->prefix(); - double bestVal = *value; - - // Test all representatives if tuneRepresentative is on. Otherwise, force current representative - const Representative * startRep = tuneRepresentative ? dim->stdRepresentative() : bestRep; - const Representative * endRep = tuneRepresentative ? dim->representativesUpperBound() : bestRep + 1; - for (const Representative * rep = startRep; rep < endRep; rep++) { - // evaluate quotient - double val = *value * std::pow(Division::Builder(clone(), Unit::Builder(dim, rep, &EmptyPrefix)).deepReduce(reductionContext).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()), exponent); - // Get the best prefix and update val accordingly - const Prefix * pre = rep->bestPrefixForValue(val, exponent); - if (std::fabs(std::log10(std::fabs(bestVal))) - std::fabs(std::log10(std::fabs(val))) > Epsilon()) { - /* At this point, val is closer to one than bestVal is.*/ - bestRep = rep; - bestPre = pre; - bestVal = val; - } - } - unitNode->setRepresentative(bestRep); - unitNode->setPrefix(bestPre); - *value = bestVal; -} - Expression Unit::removeUnit(Expression * unit) { *unit = *this; Expression one = Rational::Builder(1); @@ -408,122 +922,36 @@ Expression Unit::removeUnit(Expression * unit) { return one; } -bool Unit::isSecond() const { - // TODO: comparing pointers suffices because all time dimension are built from the same pointers. This should be asserted some way at compile-time? - return node()->dimension() == TimeDimension && node()->representative() == SecondRepresentative && node()->prefix() == &EmptyPrefix; -} +void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix) { + assert(exponent != 0.f); -bool Unit::isMeter() const { - // See comment on isSecond - return node()->dimension() == DistanceDimension && node()->representative() == MeterRepresentative && node()->prefix() == &EmptyPrefix; -} - - -bool Unit::isKilogram() const { - // See comment on isSecond - return node()->dimension() == MassDimension && node()->representative() == KilogramRepresentative && node()->prefix() == &KiloPrefix; -} - -bool Unit::isSI() const { - UnitNode * unitNode = node(); - const Dimension * dim = unitNode->dimension(); - const Representative * rep = unitNode->representative(); - return rep == dim->stdRepresentative() && - rep->definition() == nullptr && - unitNode->prefix() == dim->stdRepresentativePrefix(); -} - -bool Unit::IsSI(Expression & e) { - if (e.type() == ExpressionNode::Type::Multiplication) { - for (int i = 0; i < e.numberOfChildren(); i++) { - Expression child = e.childAtIndex(i); - assert(child.type() == ExpressionNode::Type::Power || child.type() == ExpressionNode::Type::Unit); - if (!IsSI(child)) { - return false; - } - } - return true; + if ((std::isinf(*value) || (*value == 0.0 && node()->representative()->dimensionVector() != TemperatureRepresentative::Default().dimensionVector()))) { + /* Use the base unit to represent an infinite or null value, as all units + * are equivalent. + * This is not true for temperatures (0 K != 0°C != 0°F). */ + node()->setRepresentative(node()->representative()->representativesOfSameDimension()); + node()->setPrefix(node()->representative()->basePrefix()); + return; } - if (e.type() == ExpressionNode::Type::Power) { - assert(e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isInteger()); - Expression child = e.childAtIndex(0); - assert(child.type() == ExpressionNode::Type::Unit); - return IsSI(child); + // Convert value to base units + double baseValue = *value * std::pow(node()->representative()->ratio() * std::pow(10., node()->prefix()->exponent() - node()->representative()->basePrefix()->exponent()), exponent); + const Prefix * bestPrefix = (optimizePrefix) ? Prefix::EmptyPrefix() : nullptr; + const Representative * bestRepresentative = node()->representative()->standardRepresentative(baseValue, exponent, reductionContext, &bestPrefix); + if (!optimizePrefix) { + bestPrefix = bestRepresentative->basePrefix(); } - assert(e.type() == ExpressionNode::Type::Unit); - return static_cast(e).isSI(); -} -bool Unit::IsSISpeed(Expression & e) { - // Form m*s^-1 - return e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() == 2 && - e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isMeter() && - e.childAtIndex(1).type() == ExpressionNode::Type::Power && - e.childAtIndex(1).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).childAtIndex(1).convert().isMinusOne() && - e.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(1).childAtIndex(0).convert().isSecond(); -} - -bool Unit::IsSIVolume(Expression & e) { - // Form m^3 - return e.type() == ExpressionNode::Type::Power && - e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isMeter() && - e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isThree(); -} - -bool Unit::IsSIEnergy(Expression & e) { - // Form _kg*_m^2*_s^-2 - return e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() == 3 && - e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isKilogram() && - e.childAtIndex(1).type() == ExpressionNode::Type::Power && - e.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(1).childAtIndex(0).convert().isMeter() && - e.childAtIndex(1).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).childAtIndex(1).convert().isTwo() && - e.childAtIndex(2).type() == ExpressionNode::Type::Power && - e.childAtIndex(2).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(2).childAtIndex(0).convert().isSecond() && - e.childAtIndex(2).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(2).childAtIndex(1).convert().isMinusTwo(); -} - -bool Unit::IsSITime(Expression & e) { - return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); -} - -Expression Unit::BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { - assert(!std::isnan(seconds)); - if (std::isinf(seconds) || std::fabs(seconds) < Expression::Epsilon()) { - return Multiplication::Builder(Number::FloatNumber(seconds), Unit::Second()); + if (bestRepresentative != node()->representative()) { + *value = *value * std::pow(node()->representative()->ratio() / bestRepresentative->ratio() * std::pow(10., bestRepresentative->basePrefix()->exponent() - node()->representative()->basePrefix()->exponent()), exponent); + node()->setRepresentative(bestRepresentative); } - /* Round the number of seconds to 13 significant digits - * (= k_numberOfStoredSignificantDigits - 1). - * Indeed, the user input has been converted to the most adequate unit - * which might have led to approximating the value to 14 significants - * digits. The number of seconds was then computed from this approximation. - * We thus round it to avoid displaying small numbers of seconds that are - * artifacts of the previous approximations. */ - double err = std::pow(10.0, Poincare::PrintFloat::k_numberOfStoredSignificantDigits - 1 - std::ceil(log10(std::fabs(seconds)))); - double remain = std::round(seconds*err)/err; - - constexpr static int numberOfTimeUnits = 6; - constexpr static double timeFactors[numberOfTimeUnits] = {MonthPerYear*DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, HoursPerDay*MinutesPerHour*SecondsPerMinute, MinutesPerHour*SecondsPerMinute, SecondsPerMinute, 1.0 }; - Unit units[numberOfTimeUnits] = {Unit::Year(), Unit::Month(), Unit::Day(), Unit::Hour(), Unit::Minute(), Unit::Second() }; - double valuesPerUnit[numberOfTimeUnits]; - Addition a = Addition::Builder(); - for (size_t i = 0; i < numberOfTimeUnits; i++) { - valuesPerUnit[i] = remain/timeFactors[i]; - // Keep only the floor of the values except for the last unit (seconds) - if (i < numberOfTimeUnits - 1) { - valuesPerUnit[i] = valuesPerUnit[i] >= 0.0 ? std::floor(valuesPerUnit[i]) : std::ceil(valuesPerUnit[i]); - } - remain -= valuesPerUnit[i]*timeFactors[i]; - if (std::fabs(valuesPerUnit[i]) > Expression::Epsilon()) { - Multiplication m = Multiplication::Builder(Float::Builder(valuesPerUnit[i]), units[i]); - a.addChildAtIndexInPlace(m, a.numberOfChildren(), a.numberOfChildren()); - } + if (bestPrefix != node()->prefix()) { + *value = *value * std::pow(10., exponent * (node()->prefix()->exponent() - bestPrefix->exponent())); + node()->setPrefix(bestPrefix); } - ExpressionNode::ReductionContext reductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); - // Beautify the addition into an subtraction if necessary - return a.squashUnaryHierarchyInPlace().shallowBeautify(reductionContext); } -template Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation UnitNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation UnitNode::templatedApproximate(ApproximationContext approximationContext) const; } diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index c8c2acf7c..876c33680 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -20,7 +20,7 @@ Expression UnitConvertNode::removeUnit(Expression * unit) { childAtIndex(1)->removeUnit(unit); return UnitConvert(this).replaceWithUndefinedInPlace(); } -Expression UnitConvertNode::shallowBeautify(ReductionContext reductionContext) { +Expression UnitConvertNode::shallowBeautify(ReductionContext * reductionContext) { return UnitConvert(this).shallowBeautify(reductionContext); } @@ -28,8 +28,12 @@ void UnitConvertNode::deepReduceChildren(ExpressionNode::ReductionContext reduct UnitConvert(this).deepReduceChildren(reductionContext); } +void UnitConvertNode::deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + UnitConvert(this).deepBeautifyChildren(reductionContext); +} + template -Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation UnitConvertNode::templatedApproximate(ApproximationContext approximationContext) 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. */ @@ -42,37 +46,44 @@ void UnitConvert::deepReduceChildren(ExpressionNode::ReductionContext reductionC reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, ExpressionNode::UnitConversion::None); - // Don't transform the targetted unit + // Don't transform the targeted unit childAtIndex(1).deepReduce(reductionContextKeepUnitAsIs); } -Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { +void UnitConvert::deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + ExpressionNode::ReductionContext reductionContextKeepUnitAsIs = ExpressionNode::ReductionContext( + reductionContext.context(), + reductionContext.complexFormat(), + reductionContext.angleUnit(), + reductionContext.unitFormat(), + reductionContext.target(), + reductionContext.symbolicComputation(), + ExpressionNode::UnitConversion::None); + defaultDeepBeautifyChildren(reductionContextKeepUnitAsIs); +} + +Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext * reductionContext) { // Discard cases like 4 -> _m/_km { ExpressionNode::ReductionContext reductionContextWithUnits = ExpressionNode::ReductionContext( - reductionContext.context(), - reductionContext.complexFormat(), - reductionContext.angleUnit(), - reductionContext.target(), + reductionContext->context(), + reductionContext->complexFormat(), + reductionContext->angleUnit(), + reductionContext->unitFormat(), + reductionContext->target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined); Expression unit; - Expression childWithoutUnit = childAtIndex(1).clone().deepReduce(reductionContextWithUnits).removeUnit(&unit); + Expression childWithoutUnit = childAtIndex(1).clone().reduceAndRemoveUnit(reductionContextWithUnits, &unit); if (childWithoutUnit.isUndefined() || unit.isUninitialized()) { // There is no unit on the right return replaceWithUndefinedInPlace(); } } // Find the unit - ExpressionNode::ReductionContext reductionContextWithoutUnits = ExpressionNode::ReductionContext( - reductionContext.context(), - reductionContext.complexFormat(), - reductionContext.angleUnit(), - reductionContext.target(), - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, - ExpressionNode::UnitConversion::None); Expression unit; childAtIndex(1).removeUnit(&unit); if (unit.isUninitialized()) { @@ -80,22 +91,49 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti return replaceWithUndefinedInPlace(); } + /* Handle temperatures, as converting between Kelvin, Celsius and Fahrenheit + * cannot be done with a division. */ + if (unit.type() == ExpressionNode::Type::Unit) { + Unit unitRef = static_cast(unit); + if (unitRef.representative()->dimensionVector() == Unit::TemperatureRepresentative::Default().dimensionVector()) { + Expression result = Unit::ConvertTemperatureUnits(childAtIndex(0), unitRef, *reductionContext); + replaceWithInPlace(result); + return result; + } + } + // Divide the left member by the new unit Expression division = Division::Builder(childAtIndex(0), unit.clone()); - division = division.deepReduce(reductionContext); Expression divisionUnit; - division = division.removeUnit(&divisionUnit); + division = division.reduceAndRemoveUnit(*reductionContext, &divisionUnit); if (!divisionUnit.isUninitialized()) { // The left and right members are not homogeneous return replaceWithUndefinedInPlace(); } Expression result = Multiplication::Builder(division, unit); replaceWithInPlace(result); - result.shallowReduce(reductionContextWithoutUnits); - return result.shallowBeautify(reductionContextWithoutUnits); + ExpressionNode::ReductionContext reductionContextWithoutUnits = ExpressionNode::ReductionContext( + reductionContext->context(), + reductionContext->complexFormat(), + reductionContext->angleUnit(), + reductionContext->unitFormat(), + reductionContext->target(), + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, + ExpressionNode::UnitConversion::None); + result = result.shallowReduce(reductionContextWithoutUnits); + result = result.shallowBeautify(&reductionContextWithoutUnits); + *reductionContext = ExpressionNode::ReductionContext( + reductionContext->context(), + reductionContext->complexFormat(), + reductionContext->angleUnit(), + reductionContext->unitFormat(), + reductionContext->target(), + reductionContext->symbolicComputation(), + ExpressionNode::UnitConversion::None); + return result; } -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; +template Evaluation UnitConvertNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation UnitConvertNode::templatedApproximate(ApproximationContext approximationContext) const; } diff --git a/poincare/src/unreal.cpp b/poincare/src/unreal.cpp index 10e0eb6fd..9af5e07b0 100644 --- a/poincare/src/unreal.cpp +++ b/poincare/src/unreal.cpp @@ -17,4 +17,4 @@ int UnrealNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloat return std::min(strlcpy(buffer, Unreal::Name(), bufferSize), bufferSize - 1); } -} +} \ No newline at end of file diff --git a/poincare/src/variable_context.cpp b/poincare/src/variable_context.cpp index b7899cefe..1020e3cd1 100644 --- a/poincare/src/variable_context.cpp +++ b/poincare/src/variable_context.cpp @@ -2,7 +2,6 @@ #include #include #include - #include namespace Poincare { @@ -24,14 +23,19 @@ void VariableContext::setExpressionForSymbolAbstract(const Expression & expressi } } -const Expression VariableContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { +const Expression VariableContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { if (m_name != nullptr && strcmp(symbol.name(), m_name) == 0) { if (symbol.type() == ExpressionNode::Type::Symbol) { return clone ? m_value.clone() : m_value; } return Undefined::Builder(); } else { - return ContextWithParent::expressionForSymbolAbstract(symbol, clone); + Symbol unknownSymbol = Symbol::Builder(UCodePointUnknown); + if (m_name != nullptr && strcmp(m_name, unknownSymbol.name()) == 0) { + assert(std::isnan(unknownSymbolValue)); + unknownSymbolValue = m_value.approximateToScalar(this, Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit(), true); + } + return ContextWithParent::expressionForSymbolAbstract(symbol, clone, unknownSymbolValue); } } diff --git a/poincare/src/vector_cross.cpp b/poincare/src/vector_cross.cpp new file mode 100644 index 000000000..6aeb6e7a1 --- /dev/null +++ b/poincare/src/vector_cross.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper VectorCross::s_functionHelper; + +int VectorCrossNode::numberOfChildren() const { return VectorCross::s_functionHelper.numberOfChildren(); } + +Expression VectorCrossNode::shallowReduce(ReductionContext reductionContext) { + return VectorCross(this).shallowReduce(reductionContext); +} + +Layout VectorCrossNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return LayoutHelper::Prefix(VectorCross(this), floatDisplayMode, numberOfSignificantDigits, VectorCross::s_functionHelper.name()); +} + +int VectorCrossNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, VectorCross::s_functionHelper.name()); +} + +template +Evaluation VectorCrossNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input0 = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation input1 = childAtIndex(1)->approximate(T(), approximationContext); + return input0.cross(&input1); +} + + +Expression VectorCross::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + } + Expression c0 = childAtIndex(0); + Expression c1 = childAtIndex(1); + if (c0.type() == ExpressionNode::Type::Matrix && c1.type() == ExpressionNode::Type::Matrix) { + Matrix matrixChild0 = static_cast(c0); + Matrix matrixChild1 = static_cast(c1); + // Cross product is defined between two vectors of same type and of size 3 + if (matrixChild0.vectorType() == Array::VectorType::None || matrixChild0.vectorType() != matrixChild1.vectorType() || matrixChild0.numberOfChildren() != 3 || matrixChild1.numberOfChildren() != 3) { + return replaceWithUndefinedInPlace(); + } + Expression a = matrixChild0.cross(&matrixChild1, reductionContext); + replaceWithInPlace(a); + return a.shallowReduce(reductionContext); + } + if (c0.deepIsMatrix(reductionContext.context()) && c1.deepIsMatrix(reductionContext.context())) { + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/vector_dot.cpp b/poincare/src/vector_dot.cpp new file mode 100644 index 000000000..ed8305faf --- /dev/null +++ b/poincare/src/vector_dot.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper VectorDot::s_functionHelper; + +int VectorDotNode::numberOfChildren() const { return VectorDot::s_functionHelper.numberOfChildren(); } + +Expression VectorDotNode::shallowReduce(ReductionContext reductionContext) { + return VectorDot(this).shallowReduce(reductionContext); +} + +Layout VectorDotNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return LayoutHelper::Prefix(VectorDot(this), floatDisplayMode, numberOfSignificantDigits, VectorDot::s_functionHelper.name()); +} + +int VectorDotNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, VectorDot::s_functionHelper.name()); +} + +template +Evaluation VectorDotNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input0 = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation input1 = childAtIndex(1)->approximate(T(), approximationContext); + return Complex::Builder(input0.dot(&input1)); +} + + +Expression VectorDot::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + } + Expression c0 = childAtIndex(0); + Expression c1 = childAtIndex(1); + if (c0.type() == ExpressionNode::Type::Matrix && c1.type() == ExpressionNode::Type::Matrix) { + Matrix matrixChild0 = static_cast(c0); + Matrix matrixChild1 = static_cast(c1); + // Dot product is defined between two vectors of the same dimension and type + if (matrixChild0.vectorType() == Array::VectorType::None || matrixChild0.vectorType() != matrixChild1.vectorType() || matrixChild0.numberOfChildren() != matrixChild1.numberOfChildren()) { + return replaceWithUndefinedInPlace(); + } + Expression a = matrixChild0.dot(&matrixChild1, reductionContext); + replaceWithInPlace(a); + return a.shallowReduce(reductionContext); + } + if (c0.deepIsMatrix(reductionContext.context()) && c1.deepIsMatrix(reductionContext.context())) { + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/vector_norm.cpp b/poincare/src/vector_norm.cpp new file mode 100644 index 000000000..cccd7f0bc --- /dev/null +++ b/poincare/src/vector_norm.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper VectorNorm::s_functionHelper; + +int VectorNormNode::numberOfChildren() const { return VectorNorm::s_functionHelper.numberOfChildren(); } + +Expression VectorNormNode::shallowReduce(ReductionContext reductionContext) { + return VectorNorm(this).shallowReduce(reductionContext); +} + +Layout VectorNormNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return VectorNormLayout::Builder(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits)); +} + +int VectorNormNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, VectorNorm::s_functionHelper.name()); +} + +template +Evaluation VectorNormNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); + return Complex::Builder(input.norm()); +} + + +Expression VectorNorm::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + } + Expression c = childAtIndex(0); + if (c.type() == ExpressionNode::Type::Matrix) { + Matrix matrixChild = static_cast(c); + // Norm is only defined on vectors only + if (matrixChild.vectorType() == Array::VectorType::None) { + return replaceWithUndefinedInPlace(); + } + Expression a = matrixChild.norm(reductionContext); + replaceWithInPlace(a); + return a.shallowReduce(reductionContext); + } + if (c.deepIsMatrix(reductionContext.context())) { + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 40d6571ff..1ab101e27 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -236,7 +236,7 @@ bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode // Add the Right parenthesis RightParenthesisLayout rightParenthesis = RightParenthesisLayout::Builder(); if (cursor->position() == LayoutCursor::Position::Right) { - parentRef.addChildAtIndex(rightParenthesis, idxInParent + 1, parentRef.numberOfChildren(), nullptr); + parentRef.addChildAtIndex(rightParenthesis, idxInParent + 1, parentRef.numberOfChildren(), nullptr); } else { assert(cursor->position() == LayoutCursor::Position::Left); parentRef.addChildAtIndex(rightParenthesis, idxInParent, parentRef.numberOfChildren(), nullptr); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp new file mode 100644 index 000000000..326bc9f6b --- /dev/null +++ b/poincare/src/zoom.cpp @@ -0,0 +1,487 @@ +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr int + Zoom::k_peakNumberOfPointsOfInterest, + Zoom::k_sampleSize; +constexpr float + Zoom::k_maximalDistance, + Zoom::k_minimalDistance, + Zoom::k_asymptoteThreshold, + Zoom::k_explosionThreshold, + Zoom::k_stepFactor, + Zoom::k_breathingRoom, + Zoom::k_forceXAxisThreshold, + Zoom::k_defaultHalfRange, + Zoom::k_maxRatioBetweenPointsOfInterest, + Zoom::k_smallUnitMantissa, + Zoom::k_mediumUnitMantissa, + Zoom::k_largeUnitMantissa, + Zoom::k_minimalRangeLength; + +static bool DoesNotOverestimatePrecision(float dx, float y1, float y2, float y3) { + /* The float type looses precision surprisingly fast, and cannot confidently + * hold more than 6.6 digits of precision. Results more precise than that are + * too noisy to be be of any value. */ + float yMin = std::min(y1, std::min(y2, y3)); + float yMax = std::max(y1, std::max(y2, y3)); + constexpr float maxPrecision = 2.f * FLT_EPSILON; + return (yMax - yMin) / std::fabs(dx) > maxPrecision; +} + +bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary) { + assert(xMin && xMax && yMin && yMax); + + const bool hasIntervalOfDefinition = std::isfinite(tMin) && std::isfinite(tMax); + float center, maxDistance; + if (hasIntervalOfDefinition) { + center = (tMax + tMin) / 2.f; + maxDistance = (tMax - tMin) / 2.f; + } else { + center = 0.f; + maxDistance = k_maximalDistance; + } + + float resultX[2] = {FLT_MAX, - FLT_MAX}; + float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; + float asymptote[2] = {FLT_MAX, - FLT_MAX}; + float explosion[2] = {FLT_MAX, - FLT_MAX}; + int numberOfPoints, totalNumberOfPoints = 0; + float xFallback, yFallback[2] = {NAN, NAN}; + float firstResult; + float dXOld, dXPrev, dXNext, yOld, yPrev, yNext; + + /* Look for a point of interest at the center. */ + const float a = center - k_minimalDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + k_minimalDistance + FLT_EPSILON; + const float ya = evaluation(a, context, auxiliary), yb = evaluation(b, context, auxiliary), yc = evaluation(c, context, auxiliary); + if (BoundOfIntervalOfDefinitionIsReached(ya, yc) || + BoundOfIntervalOfDefinitionIsReached(yc, ya) || + RootExistsOnInterval(ya, yc) || + ExtremumExistsOnInterval(ya, yb, yc) || ya == yc) + { + resultX[0] = resultX[1] = center; + totalNumberOfPoints++; + if (ExtremumExistsOnInterval(ya, yb, yc) && IsConvexAroundExtremum(evaluation, a, b, c, ya, yb, yc, context, auxiliary)) { + resultYMin = resultYMax = yb; + } + } + + /* We search for points of interest by exploring the function leftward from + * the center and then rightward, hence the two iterations. */ + for (int i = 0; i < 2; i++) { + /* Initialize the search parameters. */ + numberOfPoints = 0; + firstResult = NAN; + xFallback = NAN; + dXPrev = i == 0 ? - k_minimalDistance : k_minimalDistance; + dXNext = dXPrev * k_stepFactor; + yPrev = evaluation(center + dXPrev, context, auxiliary); + yNext = evaluation(center + dXNext, context, auxiliary); + + while(std::fabs(dXPrev) < maxDistance) { + /* Update the slider. */ + dXOld = dXPrev; + dXPrev = dXNext; + dXNext *= k_stepFactor; + yOld = yPrev; + yPrev = yNext; + yNext = evaluation(center + dXNext, context, auxiliary); + if (std::isinf(yNext)) { + continue; + } + /* Check for a change in the profile. */ + const PointOfInterest variation = BoundOfIntervalOfDefinitionIsReached(yPrev, yNext) ? PointOfInterest::Bound : + RootExistsOnInterval(yPrev, yNext) ? PointOfInterest::Root : + (ExtremumExistsOnInterval(yOld, yPrev, yNext) && DoesNotOverestimatePrecision(dXNext, yOld, yPrev, yNext)) ? PointOfInterest::Extremum : + PointOfInterest::None; + switch (static_cast(variation)) { + /* The fallthrough is intentional, as we only want to update the Y + * range when an extremum is detected, but need to update the X range + * in all cases. */ + case static_cast(PointOfInterest::Extremum): + if (IsConvexAroundExtremum(evaluation, center + dXOld, center + dXPrev, center + dXNext, yOld, yPrev, yNext, context, auxiliary)) { + resultYMin = std::min(resultYMin, yPrev); + resultYMax = std::max(resultYMax, yPrev); + } + case static_cast(PointOfInterest::Bound): + /* We only count extrema / discontinuities for limiting the number + * of points. This prevents cos(x) and cos(x)+2 from having different + * profiles. */ + if (++numberOfPoints == k_peakNumberOfPointsOfInterest) { + /* When too many points are encountered, we prepare their erasure by + * setting a restore point. */ + xFallback = dXNext + center; + yFallback[0] = resultYMin; + yFallback[1] = resultYMax; + } + case static_cast(PointOfInterest::Root): + asymptote[i] = i == 0 ? FLT_MAX : - FLT_MAX; + explosion[i] = i == 0 ? FLT_MAX : - FLT_MAX; + resultX[0] = std::min(resultX[0], center + (i == 0 ? dXNext : dXPrev)); + resultX[1] = std::max(resultX[1], center + (i == 1 ? dXNext : dXPrev)); + if (std::isnan(firstResult)) { + firstResult = dXNext; + } + totalNumberOfPoints++; + break; + default: + const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); + if ((std::fabs(slopeNext) < k_asymptoteThreshold) && (std::fabs(slopePrev) > k_asymptoteThreshold)) { + // Horizontal asymptote begins + asymptote[i] = (i == 0) ? std::min(asymptote[i], center + dXNext) : std::max(asymptote[i], center + dXNext); + } else if ((std::fabs(slopeNext) > k_asymptoteThreshold) && (std::fabs(slopePrev) < k_asymptoteThreshold)) { + // Horizontal asymptote invalidates : it might be an asymptote when + // going the other way. + asymptote[1 - i] = (i == 1) ? std::min(asymptote[1 - i], center + dXPrev) : std::max(asymptote[1 - i], center + dXPrev); + } + if (std::fabs(slopeNext) > k_explosionThreshold && std::fabs(slopePrev) < k_explosionThreshold) { + explosion[i] = (i == 0) ? std::min(explosion[i], center + dXNext) : std::max(explosion[i], center + dXNext); + } else if (std::fabs(slopeNext) < k_explosionThreshold && std::fabs(slopePrev) > k_explosionThreshold) { + explosion[1 - i] = (i == 1) ? std::min(explosion[1 - i], center + dXPrev) : std::max(explosion[1 - i], center + dXPrev); + } + } + } + if (std::fabs(resultX[i]) > std::fabs(firstResult) * k_maxRatioBetweenPointsOfInterest && !std::isnan(xFallback)) { + /* When there are too many points, cut them if their orders are too + * different. */ + resultX[i] = xFallback; + resultYMin = yFallback[0]; + resultYMax = yFallback[1]; + } + } + + if (totalNumberOfPoints == 1) { + float xM = (resultX[0] + resultX[1]) / 2; + resultX[0] = xM; + resultX[1] = xM; + } + /* Cut after horizontal asymptotes. */ + resultX[0] = std::min(resultX[0], asymptote[0]); + resultX[1] = std::max(resultX[1], asymptote[1]); + /* Cut after explosions if it does not reduce precision */ + float xMinWithExplosion = std::min(resultX[0], explosion[0]); + float xMaxWithExplosion = std::max(resultX[1], explosion[1]); + if (xMaxWithExplosion - xMinWithExplosion < k_maxRatioBetweenPointsOfInterest * (resultX[1] - resultX[0])) { + resultX[0] = xMinWithExplosion; + resultX[1] = xMaxWithExplosion; + } + if (resultX[0] >= resultX[1]) { + if (resultX[0] > resultX[1]) { + resultX[0] = NAN; + resultX[1] = NAN; + } + /* Fallback to default range. */ + *xMin = resultX[0]; + *xMax = resultX[1]; + *yMin = NAN; + *yMax = NAN; + return false; + } else { + /* Add breathing room around points of interest. */ + float xRange = resultX[1] - resultX[0]; + resultX[0] -= k_breathingRoom * xRange; + resultX[1] += k_breathingRoom * xRange; + } + *xMin = resultX[0]; + *xMax = resultX[1]; + *yMin = resultYMin; + *yMax = resultYMax; + + return true; +} + +void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { + /* This methods computes the Y range that will be displayed for cartesian + * functions and sequences, given an X range (xMin, xMax) and bounds yMin and + * yMax that must be inside the Y range.*/ + assert(yMin && yMax); + + float sample[k_sampleSize]; + float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; + const float step = (*xMax - *xMin) / (k_sampleSize - 1); + float x, y; + float sum = 0.f; + int pop = 0; + + sample[0] = evaluation(*xMin, context, auxiliary); + sample[k_sampleSize - 1] = evaluation(*xMax, context, auxiliary); + for (int i = 1; i < k_sampleSize - 1; i++) { + x = *xMin + i * step; + y = evaluation(x, context, auxiliary); + sample[i] = y; + if (!std::isfinite(y)) { + continue; + } + sampleYMin = std::min(sampleYMin, y); + sampleYMax = std::max(sampleYMax, y); + if (y != 0.f) { + sum += std::log(std::fabs(y)); + pop++; + } + } + + /* sum/pop is the log mean value of the function, which can be interpreted as + * its average order of magnitude. Then, bound is the value for the next + * order of magnitude and is used to cut the Y range. */ + + if (pop == 0) { + *yMin = NAN; + *yMax = NAN; + return; + } else { + float bound = std::exp(sum / pop + 1.f); + sampleYMin = std::max(sampleYMin, - bound); + sampleYMax = std::min(sampleYMax, bound); + *yMin = std::isfinite(*yMin) ? std::min(*yMin, sampleYMin) : sampleYMin; + *yMax = std::isfinite(*yMax) ? std::max(*yMax, sampleYMax) : sampleYMax; + } + /* Round out the smallest bound to 0 if it is negligible compare to the + * other one. This way, we can display the X axis for positive functions such + * as sqrt(x) even if we do not sample close to 0. */ + if (*yMin > 0.f && *yMin / *yMax < k_forceXAxisThreshold) { + *yMin = 0.f; + } else if (*yMax < 0.f && *yMax / *yMin < k_forceXAxisThreshold) { + *yMax = 0.f; + } + + ExpandSparseWindow(sample, k_sampleSize, xMin, xMax, yMin, yMax); +} + +void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { + /* The goal of this algorithm is to find the window with given ratio, that + * best suits the function. + * - The X range is centered around a point of interest of the function, or + * 0 if none exist, and uses a default width of 20. + * - The Y range's height is fixed at 20*yxRatio. Its center is chosen to + * maximize the number of visible points of the function. */ + constexpr float minimalXCoverage = 0.15f; + constexpr float minimalYCoverage = 0.3f; + constexpr int sampleSize = k_sampleSize * 2; + + float xCenter = *xMin == *xMax ? *xMin : 0.f; + *xMin = xCenter - k_defaultHalfRange; + *xMax = xCenter + k_defaultHalfRange; + float xRange = 2 * k_defaultHalfRange; + float step = xRange / (sampleSize - 1); + float sample[sampleSize]; + for (int i = 0; i < sampleSize; i++) { + sample[i] = evaluation(*xMin + i * step, context, auxiliary); + } + Helpers::Sort( + [](int i, int j, void * ctx, int size) { + float * array = static_cast(ctx); + float temp = array[i]; + array[i] = array[j]; + array[j] = temp; + }, + [](int i, int j, void * ctx, int size) { + float * array = static_cast(ctx); + return array[i] >= array[j]; + }, + sample, + sampleSize); + + /* For each value taken by the sample of the function on [xMin, xMax], given + * a fixed value for yRange, we measure the number (referred to as breadth) + * of other points that could be displayed if this value was chosen as yMin. + * In other terms, given a sorted set Y={y1,...,yn} and a length dy, we look + * for the pair 1<=i,j<=n such that : + * - yj - yi <= dy + * - i - j is maximal + * The fact that the sample is sorted makes it possible to find i and j in + * linear time. + * In case of pairs having the same breadth, we chose the pair that minimizes + * the criteria distanceFromCenter, which makes the window symmetrical when + * dealing with linear functions. */ + float yRange = yxRatio * xRange; + int j = 1; + int bestIndex = 0, bestBreadth = 0, bestDistanceToCenter; + for (int i = 0; i < sampleSize; i++) { + if (sampleSize - i < bestBreadth) { + break; + } + while (j < sampleSize && sample[j] < sample[i] + yRange) { + j++; + } + int breadth = j - i; + int distanceToCenter = std::fabs(static_cast(i + j - sampleSize)); + if (breadth > bestBreadth + || (breadth == bestBreadth + && distanceToCenter <= bestDistanceToCenter)) { + bestIndex = i; + bestBreadth = breadth; + bestDistanceToCenter = distanceToCenter; + } + } + + /* Functions with a very steep slope might only take a small portion of the + * X axis. Conversely, very flat functions may only take a small portion of + * the Y range. In those cases, the ratio is not suitable. */ + if (bestBreadth < minimalXCoverage * sampleSize + || sample[bestIndex + bestBreadth - 1] - sample[bestIndex] < minimalYCoverage * yRange) { + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; + return; + } + + float yCenter = (sample[bestIndex] + sample[bestIndex + bestBreadth - 1]) / 2.f; + *yMin = yCenter - yRange / 2.f; + *yMax = yCenter + yRange / 2.f; +} + +void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float tStep, float * fMin, float * fMax, Context * context, const void * auxiliary) { + float t = tMin; + *fMin = FLT_MAX; + *fMax = -FLT_MAX; + while (t <= tMax) { + float value = evaluation(t, context, auxiliary); + if (std::isfinite(value)) { + *fMin = std::min(*fMin, value); + *fMax = std::max(*fMax, value); + } + t += tStep; + } + if (*fMin > *fMax) { + *fMin = NAN; + *fMax = NAN; + } +} + +void Zoom::CombineRanges(int length, const float * mins, const float * maxs, float * minRes, float * maxRes) { + ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { + int index = std::round(x); + return static_cast(auxiliary)[index]; + }; + FullRange(evaluation, 0, length - 1, 1, minRes, maxRes, nullptr, mins); + float min, max; + FullRange(evaluation, 0, length - 1, 1, &min, &max, nullptr, maxs); + if (std::isfinite(min)) { + *minRes = std::min(min, *minRes); + } + if (std::isfinite(max)) { + *maxRes = std::max(max, *maxRes); + } +} + +void Zoom::SanitizeRange(float * xMin, float * xMax, float * yMin, float * yMax, float normalRatio) { + /* Axes of the window can be : + * - well-formed + * - empty (min = max) + * - ill-formed (min > max, or either bound is not finite) + * + * The general strategy to sanitize a window is as follow : + * - for all ill-formed axes, set both bounds to 0 + * - if both axes are empty, set the X axis to default bounds + * - if one axis is empty, normalize the window + * - do nothing if both axes are well-formed. */ + + if (!std::isfinite(*xMin) || !std::isfinite(*xMax) || *xMax < *xMin) { + *xMin = 0; + *xMax = 0; + } + if (!std::isfinite(*yMin) || !std::isfinite(*yMax) || *yMax < *yMin) { + *yMin = 0; + *yMax = 0; + } + + float xRange = *xMax - *xMin; + float yRange = *yMax - *yMin; + if (xRange < k_minimalRangeLength && yRange < k_minimalRangeLength) { + *xMax = *xMin + k_defaultHalfRange; + *xMin -= k_defaultHalfRange; + xRange = 2 * k_defaultHalfRange; + } + + if (xRange < k_minimalRangeLength || yRange < k_minimalRangeLength) { + SetToRatio(normalRatio, xMin, xMax, yMin, yMax, false); + } +} + +void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, bool shrink) { + float currentRatio = (*yMax - *yMin) / (*xMax - *xMin); + + float * tMin; + float * tMax; + float newRange; + if ((currentRatio < yxRatio) == shrink) { + /* Y axis too small and shrink, or Y axis too large and do not shrink + * --> we change the X axis*/ + tMin = xMin; + tMax = xMax; + newRange = (*yMax - *yMin) / yxRatio; + } else { + tMin = yMin; + tMax = yMax; + newRange = (*xMax - *xMin) * yxRatio; + } + + float center = (*tMax + *tMin) / 2.f; + *tMax = center + newRange / 2.f; + *tMin = center - newRange / 2.f; +} + +void Zoom::SetZoom(float ratio, float xCenter, float yCenter, float * xMin, float * xMax, float * yMin, float * yMax) { + float oneMinusRatio = 1.f - ratio; + *xMin = oneMinusRatio * xCenter + ratio * *xMin; + *xMax = oneMinusRatio * xCenter + ratio * *xMax; + *yMin = oneMinusRatio * yCenter + ratio * *yMin; + *yMax = oneMinusRatio * yCenter + ratio * *yMax; +} + +bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, const void * auxiliary, int iterations) { + if (iterations <= 0) { + return false; + } + float x[2] = {x1, x3}, y[2] = {y1, y3}; + float xm, ym; + for (int i = 0; i < 2; i++) { + xm = (x[i] + x2) / 2.f; + ym = evaluation(xm, context, auxiliary); + bool res = ((y[i] < ym) != (ym < y2)) ? IsConvexAroundExtremum(evaluation, x[i], xm, x2, y[i], ym, y2, context, auxiliary, iterations - 1) : std::fabs(ym - y[i]) >= std::fabs(y2 - ym); + if (!res) { + return false; + } + } + return true; +} + +void Zoom::ExpandSparseWindow(float * sample, int length, float * xMin, float * xMax, float * yMin, float * yMax) { + /* We compute the "empty center" of the window, i.e. the largest rectangle + * (with same center and shape as the window) that does not contain any + * point. If that rectangle is deemed too large, we consider that not enough + * of the curve shows up on screen and we zoom out. */ + constexpr float emptyCenterMaxSize = 0.5f; + constexpr float ratioCorrection = 4.f/3.f; + + float xCenter = (*xMax + *xMin) / 2.f; + float yCenter = (*yMax + *yMin) / 2.f; + float xRange = *xMax - *xMin; + float yRange = *yMax - *yMin; + + float emptyCenter = FLT_MAX; + float step = xRange / (length - 1); + for (int i = 0; i < length; i++) { + float x = *xMin + i * step; + float y = sample[i]; + if (std::isfinite(y)) { + /* r is the ratio between the window and the largest rectangle (with same + * center and shape as the window) that does not contain (x,y). + * i.e. the smallest zoom-in for which (x,y) is not visible. */ + float r = 2 * std::max(std::fabs(x - xCenter) / xRange, std::fabs(y - yCenter) / yRange); + emptyCenter = std::min(emptyCenter, r); + } + } + + if (emptyCenter > emptyCenterMaxSize) { + SetZoom(ratioCorrection + emptyCenter, xCenter, yCenter, xMin, xMax, yMin ,yMax); + } +} + +} diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index b48a45200..741f88a3e 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -128,20 +128,20 @@ QUIZ_CASE(poincare_approximation_power) { assert_expression_approximates_to("0^2", "0"); assert_expression_approximates_to("0^(-2)", Undefined::Name()); - assert_expression_approximates_to("(-2)^4.2", "14.8690638497+10.8030072384×𝐢", Radian, Cartesian, 12); - assert_expression_approximates_to("(-0.1)^4", "0.0001", Radian, Cartesian, 12); + assert_expression_approximates_to("(-2)^4.2", "14.8690638497+10.8030072384×𝐢", Radian, Metric, Cartesian, 12); + assert_expression_approximates_to("(-0.1)^4", "0.0001", Radian, Metric, Cartesian, 12); assert_expression_approximates_to("0^2", "0"); assert_expression_approximates_to("𝐢^𝐢", "2.0787957635076ᴇ-1"); - assert_expression_approximates_to("1.0066666666667^60", "1.48985", Radian, Cartesian, 6); - assert_expression_approximates_to("1.0066666666667^60", "1.489845708305", Radian, Cartesian, 13); + assert_expression_approximates_to("1.0066666666667^60", "1.48985", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("1.0066666666667^60", "1.489845708305", Radian, Metric, Cartesian, 13); assert_expression_approximates_to("ℯ^(𝐢×π)", "-1"); assert_expression_approximates_to("ℯ^(𝐢×π)", "-1"); - assert_expression_approximates_to("ℯ^(𝐢×π+2)", "-7.38906", Radian, Cartesian, 6); + assert_expression_approximates_to("ℯ^(𝐢×π+2)", "-7.38906", Radian, Metric, Cartesian, 6); assert_expression_approximates_to("ℯ^(𝐢×π+2)", "-7.3890560989307"); assert_expression_approximates_to("(-1)^(1/3)", "0.5+0.8660254×𝐢"); assert_expression_approximates_to("(-1)^(1/3)", "0.5+8.6602540378444ᴇ-1×𝐢"); - assert_expression_approximates_to("ℯ^(𝐢×π/3)", "0.5+0.866025×𝐢", Radian, Cartesian, 6); + assert_expression_approximates_to("ℯ^(𝐢×π/3)", "0.5+0.866025×𝐢", Radian, Metric, Cartesian, 6); assert_expression_approximates_to("ℯ^(𝐢×π/3)", "0.5+8.6602540378444ᴇ-1×𝐢"); assert_expression_approximates_to("𝐢^(2/3)", "0.5+0.8660254×𝐢"); assert_expression_approximates_to("𝐢^(2/3)", "0.5+8.6602540378444ᴇ-1×𝐢"); @@ -151,8 +151,8 @@ QUIZ_CASE(poincare_approximation_power) { assert_expression_approximates_to_scalar("[[1,2][3,4]]^2", NAN); - assert_expression_approximates_to("(-10)^0.00000001", "unreal", Radian, Real); - assert_expression_approximates_to("(-10)^0.00000001", "1+3.141593ᴇ-8×𝐢", Radian, Cartesian); + assert_expression_approximates_to("(-10)^0.00000001", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("(-10)^0.00000001", "1+3.141593ᴇ-8×𝐢", Radian, Metric, Cartesian); assert_expression_simplifies_approximates_to("3.5^2.0000001", "12.25"); assert_expression_simplifies_approximates_to("3.7^2.0000001", "13.69"); } @@ -191,6 +191,12 @@ QUIZ_CASE(poincare_approximation_division) { assert_expression_approximates_to("[[1,2][3,4]]/[[3,4][6,9]]", "[[-1,6.6666666666667ᴇ-1][1,0]]"); assert_expression_approximates_to("3/[[3,4][5,6]]", "[[-9,6][7.5,-4.5]]"); assert_expression_approximates_to("(3+4𝐢)/[[1,𝐢][3,4]]", "[[4×𝐢,1][-3×𝐢,𝐢]]"); + // assert_expression_approximates_to("(3+4𝐢)/[[3,4][1,𝐢]]", "[[1,4×𝐢][𝐢,-3×𝐢]]"); + /* TODO: this tests fails because of neglectable real or imaginary parts. + * It currently approximates to + * [[1+5.5511151231258ᴇ-17×𝐢,-2.2204460492503ᴇ-16+4×𝐢][𝐢,-3×𝐢]] or + * [[1-1.1102230246252ᴇ-16×𝐢,2.2204460492503ᴇ-16+4×𝐢] + * [-1.1102230246252ᴇ-16+𝐢,-2.2204460492503ᴇ-16-3×𝐢]] on Linux */ assert_expression_approximates_to("1ᴇ20/(1ᴇ20+1ᴇ20𝐢)", "0.5-0.5×𝐢"); assert_expression_approximates_to("1ᴇ155/(1ᴇ155+1ᴇ155𝐢)", "0.5-0.5×𝐢"); @@ -229,7 +235,7 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("abs(-1)", "1"); assert_expression_approximates_to("abs(-1)", "1"); - assert_expression_approximates_to("abs(-2.3ᴇ-39)", "2.3ᴇ-39", Degree, Cartesian, 5); + assert_expression_approximates_to("abs(-2.3ᴇ-39)", "2.3ᴇ-39", Degree, Metric, Cartesian, 5); assert_expression_approximates_to("abs(-2.3ᴇ-39)", "2.3ᴇ-39"); assert_expression_approximates_to("abs(3+2𝐢)", "3.605551"); @@ -241,22 +247,32 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("abs([[3+2𝐢,3+4𝐢][5+2𝐢,3+2𝐢]])", "[[3.605551,5][5.385165,3.605551]]"); assert_expression_approximates_to("abs([[3+2𝐢,3+4𝐢][5+2𝐢,3+2𝐢]])", "[[3.605551275464,5][5.3851648071345,3.605551275464]]"); - assert_expression_approximates_to("binomcdf(5.3, 9, 0.7)", "0.270341", Degree, Cartesian, 6); // FIXME: precision problem - assert_expression_approximates_to("binomcdf(5.3, 9, 0.7)", "0.270340902", Degree, Cartesian, 10); //FIXME precision problem + assert_expression_approximates_to("binomcdf(5.3, 9, 0.7)", "0.270341", Degree, Metric, Cartesian, 6); // FIXME: precision problem + assert_expression_approximates_to("binomcdf(5.3, 9, 0.7)", "0.270340902", Degree, Metric, Cartesian, 10); //FIXME precision problem assert_expression_approximates_to("binomial(10, 4)", "210"); assert_expression_approximates_to("binomial(10, 4)", "210"); + assert_expression_approximates_to("binomial(12, 3)", "220"); + assert_expression_approximates_to("binomial(12, 3)", "220"); + assert_expression_approximates_to("binomial(-4.6, 3)", "-28.336"); + assert_expression_approximates_to("binomial(-4.6, 3)", "-28.336"); + assert_expression_approximates_to("binomial(π, 3)", "1.280108"); + assert_expression_approximates_to("binomial(π, 3)", "1.2801081307019"); + assert_expression_approximates_to("binomial(7, 9)", "0"); + assert_expression_approximates_to("binomial(7, 9)", "0"); + assert_expression_approximates_to("binomial(-7, 9)", "-5005"); + assert_expression_approximates_to("binomial(-7, 9)", "-5005"); - assert_expression_approximates_to("binompdf(4.4, 9, 0.7)", "0.0735138", Degree, Cartesian, 6); // FIXME: precision problem + assert_expression_approximates_to("binompdf(4.4, 9, 0.7)", "0.0735138", Degree, Metric, Cartesian, 6); // FIXME: precision problem assert_expression_approximates_to("binompdf(4.4, 9, 0.7)", "0.073513818"); assert_expression_approximates_to("ceil(0.2)", "1"); assert_expression_approximates_to("ceil(0.2)", "1"); - assert_expression_approximates_to("det([[1,23,3][4,5,6][7,8,9]])", "126", Degree, Cartesian, 6); // FIXME: the determinant computation is not precised enough to be displayed with 7 significant digits + assert_expression_approximates_to("det([[1,23,3][4,5,6][7,8,9]])", "126", Degree, Metric, Cartesian, 6); // FIXME: the determinant computation is not precised enough to be displayed with 7 significant digits assert_expression_approximates_to("det([[1,23,3][4,5,6][7,8,9]])", "126"); - assert_expression_approximates_to("det([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "126-231×𝐢", Degree, Cartesian, 6); // FIXME: the determinant computation is not precised enough to be displayed with 7 significant digits + assert_expression_approximates_to("det([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "126-231×𝐢", Degree, Metric, Cartesian, 6); // FIXME: the determinant computation is not precised enough to be displayed with 7 significant digits assert_expression_approximates_to("det([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "126-231×𝐢"); assert_expression_approximates_to("diff(2×x, x, 2)", "2"); @@ -279,6 +295,12 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("gcd(-234,394)", "2"); assert_expression_approximates_to("gcd(234,-394)", "2"); assert_expression_approximates_to("gcd(-234,-394)", "2"); + assert_expression_approximates_to("gcd(-234,-394, -16)", "2"); + assert_expression_approximates_to("gcd(-234,-394, -16)", "2"); + assert_expression_approximates_to("gcd(6,15,10)", "1"); + assert_expression_approximates_to("gcd(6,15,10)", "1"); + assert_expression_approximates_to("gcd(30,105,70,42)", "1"); + assert_expression_approximates_to("gcd(30,105,70,42)", "1"); assert_expression_approximates_to("im(2+3𝐢)", "3"); assert_expression_approximates_to("im(2+3𝐢)", "3"); @@ -288,6 +310,12 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("lcm(-234,394)", "46098"); assert_expression_approximates_to("lcm(234,-394)", "46098"); assert_expression_approximates_to("lcm(-234,-394)", "46098"); + assert_expression_approximates_to("lcm(-234,-394, -16)", "368784"); + assert_expression_approximates_to("lcm(-234,-394, -16)", "368784"); + assert_expression_approximates_to("lcm(6,15,10)", "30"); + assert_expression_approximates_to("lcm(6,15,10)", "30"); + assert_expression_approximates_to("lcm(30,105,70,42)", "210"); + assert_expression_approximates_to("lcm(30,105,70,42)", "210"); assert_expression_approximates_to("int(x,x, 1, 2)", "1.5"); assert_expression_approximates_to("int(x,x, 1, 2)", "1.5"); @@ -295,8 +323,8 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("invbinom(0.9647324002, 15, 0.7)", "13"); assert_expression_approximates_to("invbinom(0.9647324002, 15, 0.7)", "13"); - assert_expression_approximates_to("invnorm(0.56, 1.3, 5.76)", "1.662326"); - //assert_expression_approximates_to("invnorm(0.56, 1.3, 5.76)", "1.6623258450088"); FIXME precision error + assert_expression_approximates_to("invnorm(0.56, 1.3, 2.4)", "1.662326"); + //assert_expression_approximates_to("invnorm(0.56, 1.3, 2.4)", "1.6623258450088"); FIXME precision error assert_expression_approximates_to("ln(2)", "0.6931472"); assert_expression_approximates_to("ln(2)", "6.9314718055995ᴇ-1"); @@ -304,17 +332,19 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("log(2)", "0.30103"); assert_expression_approximates_to("log(2)", "3.0102999566398ᴇ-1"); - assert_expression_approximates_to("normcdf(1.2, 3.4, 31.36)", "0.3472125"); - assert_expression_approximates_to("normcdf(1.2, 3.4, 31.36)", "3.4721249841587ᴇ-1"); - assert_expression_approximates_to("normcdf(-1ᴇ99,3.4,31.36)", "0"); - assert_expression_approximates_to("normcdf(1ᴇ99,3.4,31.36)", "1"); + assert_expression_approximates_to("normcdf(5, 7, 0.3162)", "1.265256ᴇ-10", Radian, Metric, Cartesian, 7); + + assert_expression_approximates_to("normcdf(1.2, 3.4, 5.6)", "0.3472125"); + assert_expression_approximates_to("normcdf(1.2, 3.4, 5.6)", "3.4721249841587ᴇ-1"); + assert_expression_approximates_to("normcdf(-1ᴇ99,3.4,5.6)", "0"); + assert_expression_approximates_to("normcdf(1ᴇ99,3.4,5.6)", "1"); assert_expression_approximates_to("normcdf(-6,0,1)", "0"); assert_expression_approximates_to("normcdf(6,0,1)", "1"); - assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 11.56)", "0.3436388"); - assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 11.56)", "3.4363881299147ᴇ-1"); + assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 3.4)", "0.3436388"); + assert_expression_approximates_to("normcdf2(0.5, 3.6, 1.3, 3.4)", "3.4363881299147ᴇ-1"); - assert_expression_approximates_to("normpdf(1.2, 3.4, 31.36)", "0.06594901"); + assert_expression_approximates_to("normpdf(1.2, 3.4, 5.6)", "0.06594901"); assert_expression_approximates_to("permute(10, 4)", "5040"); assert_expression_approximates_to("permute(10, 4)", "5040"); @@ -358,10 +388,10 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("factor(-123/24)", "-5.125"); assert_expression_approximates_to("factor(𝐢)", "undef"); - assert_expression_approximates_to("inverse([[1,2,3][4,5,-6][7,8,9]])", "[[-1.2917,-0.083333,0.375][1.0833,0.16667,-0.25][0.041667,-0.083333,0.041667]]", Degree, Cartesian, 5); // inverse is not precise enough to display 7 significative digits + assert_expression_approximates_to("inverse([[1,2,3][4,5,-6][7,8,9]])", "[[-1.2917,-0.083333,0.375][1.0833,0.16667,-0.25][0.041667,-0.083333,0.041667]]", Degree, Metric, Cartesian, 5); // inverse is not precise enough to display 7 significative digits assert_expression_approximates_to("inverse([[1,2,3][4,5,-6][7,8,9]])", "[[-1.2916666666667,-8.3333333333333ᴇ-2,0.375][1.0833333333333,1.6666666666667ᴇ-1,-0.25][4.1666666666667ᴇ-2,-8.3333333333333ᴇ-2,4.1666666666667ᴇ-2]]"); - assert_expression_approximates_to("inverse([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "[[-0.0118-0.0455×𝐢,-0.5-0.727×𝐢,0.318+0.489×𝐢][0.0409+0.00364×𝐢,0.04-0.0218×𝐢,-0.0255+0.00091×𝐢][0.00334-0.00182×𝐢,0.361+0.535×𝐢,-0.13-0.358×𝐢]]", Degree, Cartesian, 3); // inverse is not precise enough to display 7 significative digits - assert_expression_approximates_to("inverse([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "[[-0.0118289353958-0.0454959053685×𝐢,-0.500454959054-0.727024567789×𝐢,0.31847133758+0.488626023658×𝐢][0.0409463148317+3.63967242948ᴇ-3×𝐢,0.0400363967243-0.0218380345769×𝐢,-0.0254777070064+9.0991810737ᴇ-4×𝐢][3.33636639369ᴇ-3-1.81983621474ᴇ-3×𝐢,0.36093418259+0.534728541098×𝐢,-0.130118289354-0.357597816197×𝐢]]", Degree, Cartesian, 12); // FIXME: inverse is not precise enough to display 14 significative digits + assert_expression_approximates_to("inverse([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "[[-0.0118-0.0455×𝐢,-0.5-0.727×𝐢,0.318+0.489×𝐢][0.0409+0.00364×𝐢,0.04-0.0218×𝐢,-0.0255+0.00091×𝐢][0.00334-0.00182×𝐢,0.361+0.535×𝐢,-0.13-0.358×𝐢]]", Degree, Metric, Cartesian, 3); // inverse is not precise enough to display 7 significative digits + assert_expression_approximates_to("inverse([[𝐢,23-2𝐢,3×𝐢][4+𝐢,5×𝐢,6][7,8×𝐢+2,9]])", "[[-0.0118289353958-0.0454959053685×𝐢,-0.500454959054-0.727024567789×𝐢,0.31847133758+0.488626023658×𝐢][0.0409463148317+3.63967242948ᴇ-3×𝐢,0.0400363967243-0.0218380345769×𝐢,-0.0254777070064+9.0991810737ᴇ-4×𝐢][3.33636639369ᴇ-3-1.81983621474ᴇ-3×𝐢,0.36093418259+0.534728541098×𝐢,-0.130118289354-0.357597816197×𝐢]]", Degree, Metric, Cartesian, 12); // FIXME: inverse is not precise enough to display 14 significative digits assert_expression_approximates_to("prediction(0.1, 100)", "[[0,0.2]]"); assert_expression_approximates_to("prediction(0.1, 100)", "[[0,0.2]]"); @@ -378,8 +408,8 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("root(3, 3+𝐢)", "1.382007-0.1524428×𝐢"); assert_expression_approximates_to("root(3, 3+𝐢)", "1.3820069623326-0.1524427794159×𝐢"); - assert_expression_approximates_to("root(5^((-𝐢)3^9),𝐢)", "3.504", Degree, Cartesian, 4); - assert_expression_approximates_to("root(5^((-𝐢)3^9),𝐢)", "3.5039410843", Degree, Cartesian, 11); + assert_expression_approximates_to("root(5^((-𝐢)3^9),𝐢)", "3.504", Degree, Metric, Cartesian, 4); + assert_expression_approximates_to("root(5^((-𝐢)3^9),𝐢)", "3.5039410843", Degree, Metric, Cartesian, 11); assert_expression_approximates_to("√(3+𝐢)", "1.755317+0.2848488×𝐢"); assert_expression_approximates_to("√(3+𝐢)", "1.7553173018244+2.8484878459314ᴇ-1×𝐢"); @@ -403,6 +433,26 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("transpose([[1,7,5][4,2,8]])", "[[1,4][7,2][5,8]]"); assert_expression_approximates_to("transpose([[1,2][4,5][7,8]])", "[[1,4,7][2,5,8]]"); + assert_expression_approximates_to("ref([[0,2,-1][5,6,7][10,11,10]])", "[[1,1.1,1][0,1,-0.5][0,0,1]]"); + assert_expression_approximates_to("rref([[0,2,-1][5,6,7][10,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); + assert_expression_approximates_to("ref([[0,2,-1][5,6,7][10,11,10]])", "[[1,1.1,1][0,1,-0.5][0,0,1]]"); + assert_expression_approximates_to("rref([[0,2,-1][5,6,7][10,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); + + assert_expression_approximates_to("cross([[1][2][3]],[[4][7][8]])", "[[-5][4][-1]]"); + assert_expression_approximates_to("cross([[1][2][3]],[[4][7][8]])", "[[-5][4][-1]]"); + assert_expression_approximates_to("cross([[1,2,3]],[[4,7,8]])", "[[-5,4,-1]]"); + assert_expression_approximates_to("cross([[1,2,3]],[[4,7,8]])", "[[-5,4,-1]]"); + + assert_expression_approximates_to("dot([[1][2][3]],[[4][7][8]])", "42"); + assert_expression_approximates_to("dot([[1][2][3]],[[4][7][8]])", "42"); + assert_expression_approximates_to("dot([[1,2,3]],[[4,7,8]])", "42"); + assert_expression_approximates_to("dot([[1,2,3]],[[4,7,8]])", "42"); + + assert_expression_approximates_to("norm([[-5][4][-1]])", "6.480741"); + assert_expression_approximates_to("norm([[-5][4][-1]])", "6.4807406984079"); + assert_expression_approximates_to("norm([[-5,4,-1]])", "6.480741"); + assert_expression_approximates_to("norm([[-5,4,-1]])", "6.4807406984079"); + assert_expression_approximates_to("round(2.3246,3)", "2.325"); assert_expression_approximates_to("round(2.3245,3)", "2.325"); @@ -454,8 +504,8 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("cos(2×𝐢)", "1.0004935208085", Gradian); // On C assert_expression_approximates_to("cos(𝐢-4)", "-1.008625-0.8893952×𝐢", Radian); - assert_expression_approximates_to("cos(𝐢-4)", "0.997716+0.00121754×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("cos(𝐢-4)", "0.99815+0.000986352×𝐢", Gradian, Cartesian, 6); + assert_expression_approximates_to("cos(𝐢-4)", "0.997716+0.00121754×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("cos(𝐢-4)", "0.99815+0.000986352×𝐢", Gradian, Metric, Cartesian, 6); /* sin: R -> R (oscillator) * Ri -> Ri (odd) @@ -481,10 +531,10 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("sin(-3×𝐢)", "-0.05238381×𝐢", Degree); assert_expression_approximates_to("sin(-3×𝐢)", "-4.7141332771113ᴇ-2×𝐢", Gradian); // On: C - assert_expression_approximates_to("sin(𝐢-4)", "1.16781-0.768163×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("sin(𝐢-4)", "-0.0697671+0.0174117×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("sin(𝐢-4)", "-0.0627983+0.0156776×𝐢", Gradian, Cartesian, 6); - assert_expression_approximates_to("sin(1.234567890123456ᴇ-15)", "1.23457ᴇ-15", Radian, Cartesian, 6); + assert_expression_approximates_to("sin(𝐢-4)", "1.16781-0.768163×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("sin(𝐢-4)", "-0.0697671+0.0174117×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("sin(𝐢-4)", "-0.0627983+0.0156776×𝐢", Gradian, Metric, Cartesian, 6); + assert_expression_approximates_to("sin(1.234567890123456ᴇ-15)", "1.23457ᴇ-15", Radian, Metric, Cartesian, 6); /* tan: R -> R (tangent-style) * Ri -> Ri (odd) @@ -506,9 +556,9 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("tan(2×𝐢)", "0.03489241×𝐢", Degree); assert_expression_approximates_to("tan(2×𝐢)", "0.0314056×𝐢", Gradian); // On C - assert_expression_approximates_to("tan(𝐢-4)", "-0.273553+1.00281×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("tan(𝐢-4)", "-0.0699054+0.0175368×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("tan(𝐢-4)", "-0.0628991+0.0157688×𝐢", Gradian, Cartesian, 6); + assert_expression_approximates_to("tan(𝐢-4)", "-0.273553+1.00281×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("tan(𝐢-4)", "-0.0699054+0.0175368×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("tan(𝐢-4)", "-0.0628991+0.0157688×𝐢", Gradian, Metric, Cartesian, 6); /* acos: [-1,1] -> R * ]-inf,-1[ -> π+R×i (odd imaginary) @@ -523,27 +573,27 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { // On [1, inf[ assert_expression_approximates_to("acos(2)", "1.3169578969248×𝐢", Radian); assert_expression_approximates_to("acos(2)", "75.456129290217×𝐢", Degree); - assert_expression_approximates_to("acos(2)", "83.84×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(2)", "83.84×𝐢", Gradian, Metric, Cartesian, 4); // Symmetry: odd on imaginary assert_expression_approximates_to("acos(-2)", "3.1415926535898-1.3169578969248×𝐢", Radian); assert_expression_approximates_to("acos(-2)", "180-75.456129290217×𝐢", Degree); - assert_expression_approximates_to("acos(-2)", "200-83.84×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(-2)", "200-83.84×𝐢", Gradian, Metric, Cartesian, 4); // On ]-inf, -1[ - assert_expression_approximates_to("acos(-32)", "3.14159265359-4.158638853279×𝐢", Radian, Cartesian, 13); - assert_expression_approximates_to("acos(-32)", "180-238.3×𝐢", Degree, Cartesian, 4); - assert_expression_approximates_to("acos(-32)", "200-264.7×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(-32)", "3.14159265359-4.158638853279×𝐢", Radian, Metric, Cartesian, 13); + assert_expression_approximates_to("acos(-32)", "180-238.3×𝐢", Degree, Metric, Cartesian, 4); + assert_expression_approximates_to("acos(-32)", "200-264.7×𝐢", Gradian, Metric, Cartesian, 4); // On R×i - assert_expression_approximates_to("acos(3×𝐢)", "1.5708-1.8184×𝐢", Radian, Cartesian, 5); - assert_expression_approximates_to("acos(3×𝐢)", "90-104.19×𝐢", Degree, Cartesian, 5); - assert_expression_approximates_to("acos(3×𝐢)", "100-115.8×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(3×𝐢)", "1.5708-1.8184×𝐢", Radian, Metric, Cartesian, 5); + assert_expression_approximates_to("acos(3×𝐢)", "90-104.19×𝐢", Degree, Metric, Cartesian, 5); + assert_expression_approximates_to("acos(3×𝐢)", "100-115.8×𝐢", Gradian, Metric, Cartesian, 4); // Symmetry: odd on imaginary - assert_expression_approximates_to("acos(-3×𝐢)", "1.5708+1.8184×𝐢", Radian, Cartesian, 5); - assert_expression_approximates_to("acos(-3×𝐢)", "90+104.19×𝐢", Degree, Cartesian, 5); - assert_expression_approximates_to("acos(-3×𝐢)", "100+115.8×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(-3×𝐢)", "1.5708+1.8184×𝐢", Radian, Metric, Cartesian, 5); + assert_expression_approximates_to("acos(-3×𝐢)", "90+104.19×𝐢", Degree, Metric, Cartesian, 5); + assert_expression_approximates_to("acos(-3×𝐢)", "100+115.8×𝐢", Gradian, Metric, Cartesian, 4); // On C - assert_expression_approximates_to("acos(𝐢-4)", "2.8894-2.0966×𝐢", Radian, Cartesian, 5); - assert_expression_approximates_to("acos(𝐢-4)", "165.551-120.126×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("acos(𝐢-4)", "183.9-133.5×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("acos(𝐢-4)", "2.8894-2.0966×𝐢", Radian, Metric, Cartesian, 5); + assert_expression_approximates_to("acos(𝐢-4)", "165.551-120.126×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("acos(𝐢-4)", "183.9-133.5×𝐢", Gradian, Metric, Cartesian, 4); // Key values assert_expression_approximates_to("acos(0)", "90", Degree); assert_expression_approximates_to("acos(-1)", "180", Degree); @@ -561,36 +611,36 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("asin(0.5)", "0.5235987755983", Radian); assert_expression_approximates_to("asin(0.03)", "3.0004501823477ᴇ-2", Radian); assert_expression_approximates_to("asin(0.5)", "30", Degree); - assert_expression_approximates_to("asin(0.5)", "33.3333", Gradian, Cartesian, 6); + assert_expression_approximates_to("asin(0.5)", "33.3333", Gradian, Metric, Cartesian, 6); // On [1, inf[ assert_expression_approximates_to("asin(2)", "1.5707963267949-1.3169578969248×𝐢", Radian); assert_expression_approximates_to("asin(2)", "90-75.456129290217×𝐢", Degree); - assert_expression_approximates_to("asin(2)", "100-83.84×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("asin(2)", "100-83.84×𝐢", Gradian, Metric, Cartesian, 4); // Symmetry: odd assert_expression_approximates_to("asin(-2)", "-1.5707963267949+1.3169578969248×𝐢", Radian); assert_expression_approximates_to("asin(-2)", "-90+75.456129290217×𝐢", Degree); - assert_expression_approximates_to("asin(-2)", "-100+83.84×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("asin(-2)", "-100+83.84×𝐢", Gradian, Metric, Cartesian, 4); // On ]-inf, -1[ - assert_expression_approximates_to("asin(-32)", "-1.571+4.159×𝐢", Radian, Cartesian, 4); - assert_expression_approximates_to("asin(-32)", "-90+238×𝐢", Degree, Cartesian, 3); - assert_expression_approximates_to("asin(-32)", "-100+265×𝐢", Gradian, Cartesian, 3); + assert_expression_approximates_to("asin(-32)", "-1.571+4.159×𝐢", Radian, Metric, Cartesian, 4); + assert_expression_approximates_to("asin(-32)", "-90+238×𝐢", Degree, Metric, Cartesian, 3); + assert_expression_approximates_to("asin(-32)", "-100+265×𝐢", Gradian, Metric, Cartesian, 3); // On R×i assert_expression_approximates_to("asin(3×𝐢)", "1.8184464592321×𝐢", Radian); - assert_expression_approximates_to("asin(3×𝐢)", "115.8×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("asin(3×𝐢)", "115.8×𝐢", Gradian, Metric, Cartesian, 4); // Symmetry: odd assert_expression_approximates_to("asin(-3×𝐢)", "-1.8184464592321×𝐢", Radian); - assert_expression_approximates_to("asin(-3×𝐢)", "-115.8×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("asin(-3×𝐢)", "-115.8×𝐢", Gradian, Metric, Cartesian, 4); // On C - assert_expression_approximates_to("asin(𝐢-4)", "-1.3186+2.0966×𝐢", Radian, Cartesian, 5); - assert_expression_approximates_to("asin(𝐢-4)", "-75.551+120.13×𝐢", Degree, Cartesian, 5); - assert_expression_approximates_to("asin(𝐢-4)", "-83.95+133.5×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("asin(𝐢-4)", "-1.3186+2.0966×𝐢", Radian, Metric, Cartesian, 5); + assert_expression_approximates_to("asin(𝐢-4)", "-75.551+120.13×𝐢", Degree, Metric, Cartesian, 5); + assert_expression_approximates_to("asin(𝐢-4)", "-83.95+133.5×𝐢", Gradian, Metric, Cartesian, 4); // Key values assert_expression_approximates_to("asin(0)", "0", Degree); assert_expression_approximates_to("asin(0)", "0", Gradian); - assert_expression_approximates_to("asin(-1)", "-90", Degree, Cartesian, 6); - assert_expression_approximates_to("asin(-1)", "-100", Gradian, Cartesian, 6); + assert_expression_approximates_to("asin(-1)", "-90", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("asin(-1)", "-100", Gradian, Metric, Cartesian, 6); assert_expression_approximates_to("asin(1)", "90", Degree); - assert_expression_approximates_to("asin(1)", "100", Gradian, Cartesian); + assert_expression_approximates_to("asin(1)", "100", Gradian, Metric, Cartesian); /* atan: R -> R (odd) * [-𝐢,𝐢] -> R×𝐢 (odd) @@ -601,31 +651,31 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("atan(2)", "1.1071487177941", Radian); assert_expression_approximates_to("atan(0.01)", "9.9996666866652ᴇ-3", Radian); assert_expression_approximates_to("atan(2)", "63.434948822922", Degree); - assert_expression_approximates_to("atan(2)", "70.48", Gradian, Cartesian, 4); + assert_expression_approximates_to("atan(2)", "70.48", Gradian, Metric, Cartesian, 4); assert_expression_approximates_to("atan(0.5)", "0.4636476", Radian); // Symmetry: odd assert_expression_approximates_to("atan(-2)", "-1.1071487177941", Radian); assert_expression_approximates_to("atan(-2)", "-63.434948822922", Degree); // On [-𝐢, 𝐢] - assert_expression_approximates_to("atan(0.2×𝐢)", "0.202733×𝐢", Radian, Cartesian, 6); + assert_expression_approximates_to("atan(0.2×𝐢)", "0.202733×𝐢", Radian, Metric, Cartesian, 6); // Symmetry: odd - assert_expression_approximates_to("atan(-0.2×𝐢)", "-0.202733×𝐢", Radian, Cartesian, 6); + assert_expression_approximates_to("atan(-0.2×𝐢)", "-0.202733×𝐢", Radian, Metric, Cartesian, 6); // On [𝐢, inf×𝐢[ assert_expression_approximates_to("atan(26×𝐢)", "1.5707963267949+3.8480520568064ᴇ-2×𝐢", Radian); assert_expression_approximates_to("atan(26×𝐢)", "90+2.2047714220164×𝐢", Degree); - assert_expression_approximates_to("atan(26×𝐢)", "100+2.45×𝐢", Gradian, Cartesian, 3); + assert_expression_approximates_to("atan(26×𝐢)", "100+2.45×𝐢", Gradian, Metric, Cartesian, 3); // Symmetry: odd assert_expression_approximates_to("atan(-26×𝐢)", "-1.5707963267949-3.8480520568064ᴇ-2×𝐢", Radian); assert_expression_approximates_to("atan(-26×𝐢)", "-90-2.2047714220164×𝐢", Degree); - assert_expression_approximates_to("atan(-26×𝐢)", "-100-2.45×𝐢", Gradian, Cartesian, 3); + assert_expression_approximates_to("atan(-26×𝐢)", "-100-2.45×𝐢", Gradian, Metric, Cartesian, 3); // On ]-inf×𝐢, -𝐢[ assert_expression_approximates_to("atan(-3.4×𝐢)", "-1.570796-0.3030679×𝐢", Radian); - assert_expression_approximates_to("atan(-3.4×𝐢)", "-90-17.3645×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("atan(-3.4×𝐢)", "-100-19.29×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("atan(-3.4×𝐢)", "-90-17.3645×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("atan(-3.4×𝐢)", "-100-19.29×𝐢", Gradian, Metric, Cartesian, 4); // On C assert_expression_approximates_to("atan(𝐢-4)", "-1.338973+0.05578589×𝐢", Radian); - assert_expression_approximates_to("atan(𝐢-4)", "-76.7175+3.1963×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("atan(𝐢-4)", "-85.24+3.551×𝐢", Gradian, Cartesian, 4); + assert_expression_approximates_to("atan(𝐢-4)", "-76.7175+3.1963×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("atan(𝐢-4)", "-85.24+3.551×𝐢", Gradian, Metric, Cartesian, 4); // Key values assert_expression_approximates_to("atan(0)", "0", Degree); assert_expression_approximates_to("atan(0)", "0", Gradian); @@ -651,9 +701,9 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("cosh(8×π×𝐢/2)", "1", Radian); assert_expression_approximates_to("cosh(9×π×𝐢/2)", "0", Radian); // On C - assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Degree, Cartesian, 6); - assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Gradian, Cartesian, 6); + assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Degree, Metric, Cartesian, 6); + assert_expression_approximates_to("cosh(𝐢-4)", "14.7547-22.9637×𝐢", Gradian, Metric, Cartesian, 6); /* sinh: R -> R (odd) * R×𝐢 -> R×𝐢 (oscillator) @@ -673,8 +723,8 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("sinh(8×π×𝐢/2)", "0", Radian); assert_expression_approximates_to("sinh(9×π×𝐢/2)", "𝐢", Radian); // On C - assert_expression_approximates_to("sinh(𝐢-4)", "-14.7448+22.9791×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("sinh(𝐢-4)", "-14.7448+22.9791×𝐢", Degree, Cartesian, 6); + assert_expression_approximates_to("sinh(𝐢-4)", "-14.7448+22.9791×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("sinh(𝐢-4)", "-14.7448+22.9791×𝐢", Degree, Metric, Cartesian, 6); /* tanh: R -> R (odd) * R×𝐢 -> R×𝐢 (tangent-style) @@ -694,8 +744,8 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("tanh(8×π×𝐢/2)", "0", Radian); assert_expression_approximates_to("tanh(9×π×𝐢/2)", Undefined::Name(), Radian);*/ // On C - assert_expression_approximates_to("tanh(𝐢-4)", "-1.00028+0.000610241×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("tanh(𝐢-4)", "-1.00028+0.000610241×𝐢", Degree, Cartesian, 6); + assert_expression_approximates_to("tanh(𝐢-4)", "-1.00028+0.000610241×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("tanh(𝐢-4)", "-1.00028+0.000610241×𝐢", Degree, Metric, Cartesian, 6); /* acosh: [-1,1] -> R×𝐢 * ]-inf,-1[ -> π×𝐢+R (even on real) @@ -709,19 +759,19 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("acosh(2)", "1.3169578969248", Gradian); // On ]-inf, -1[ assert_expression_approximates_to("acosh(-4)", "2.0634370688956+3.1415926535898×𝐢", Radian); - assert_expression_approximates_to("acosh(-4)", "2.06344+3.14159×𝐢", Radian, Cartesian, 6); + assert_expression_approximates_to("acosh(-4)", "2.06344+3.14159×𝐢", Radian, Metric, Cartesian, 6); // On ]1,inf[: Symmetry: even on real assert_expression_approximates_to("acosh(4)", "2.0634370688956", Radian); assert_expression_approximates_to("acosh(4)", "2.063437", Radian); // On ]-inf×𝐢, 0[ assert_expression_approximates_to("acosh(-42×𝐢)", "4.4309584920805-1.5707963267949×𝐢", Radian); - assert_expression_approximates_to("acosh(-42×𝐢)", "4.431-1.571×𝐢", Radian, Cartesian, 4); + assert_expression_approximates_to("acosh(-42×𝐢)", "4.431-1.571×𝐢", Radian, Metric, Cartesian, 4); // On ]0, 𝐢×inf[: Symmetry: even on real assert_expression_approximates_to("acosh(42×𝐢)", "4.4309584920805+1.5707963267949×𝐢", Radian); - assert_expression_approximates_to("acosh(42×𝐢)", "4.431+1.571×𝐢", Radian, Cartesian, 4); + assert_expression_approximates_to("acosh(42×𝐢)", "4.431+1.571×𝐢", Radian, Metric, Cartesian, 4); // On C - assert_expression_approximates_to("acosh(𝐢-4)", "2.0966+2.8894×𝐢", Radian, Cartesian, 5); - assert_expression_approximates_to("acosh(𝐢-4)", "2.0966+2.8894×𝐢", Degree, Cartesian, 5); + assert_expression_approximates_to("acosh(𝐢-4)", "2.0966+2.8894×𝐢", Radian, Metric, Cartesian, 5); + assert_expression_approximates_to("acosh(𝐢-4)", "2.0966+2.8894×𝐢", Degree, Metric, Cartesian, 5); // Key values //assert_expression_approximates_to("acosh(-1)", "3.1415926535898×𝐢", Radian); assert_expression_approximates_to("acosh(1)", "0", Radian); @@ -749,13 +799,13 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("asinh(-0.3×𝐢)", "-0.3046927×𝐢", Degree); // On ]-inf×𝐢, -𝐢[ assert_expression_approximates_to("asinh(-22×𝐢)", "-3.7836727043295-1.5707963267949×𝐢", Radian); - assert_expression_approximates_to("asinh(-22×𝐢)", "-3.784-1.571×𝐢", Degree, Cartesian, 4); + assert_expression_approximates_to("asinh(-22×𝐢)", "-3.784-1.571×𝐢", Degree, Metric, Cartesian, 4); // On ]𝐢, inf×𝐢[, Symmetry: odd assert_expression_approximates_to("asinh(22×𝐢)", "3.7836727043295+1.5707963267949×𝐢", Radian); - assert_expression_approximates_to("asinh(22×𝐢)", "3.784+1.571×𝐢", Degree, Cartesian, 4); + assert_expression_approximates_to("asinh(22×𝐢)", "3.784+1.571×𝐢", Degree, Metric, Cartesian, 4); // On C - assert_expression_approximates_to("asinh(𝐢-4)", "-2.123+0.2383×𝐢", Radian, Cartesian, 4); - assert_expression_approximates_to("asinh(𝐢-4)", "-2.123+0.2383×𝐢", Degree, Cartesian, 4); + assert_expression_approximates_to("asinh(𝐢-4)", "-2.123+0.2383×𝐢", Radian, Metric, Cartesian, 4); + assert_expression_approximates_to("asinh(𝐢-4)", "-2.123+0.2383×𝐢", Degree, Metric, Cartesian, 4); /* atanh: [-1,1] -> R (odd) * ]-inf,-1[ -> π/2*𝐢+R (odd) @@ -782,15 +832,15 @@ QUIZ_CASE(poincare_approximation_trigonometry_functions) { assert_expression_approximates_to("atanh(-4×𝐢)", "-1.325817663668×𝐢", Radian); assert_expression_approximates_to("atanh(-4×𝐢)", "-1.325818×𝐢", Radian); // On C - assert_expression_approximates_to("atanh(𝐢-4)", "-0.238878+1.50862×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("atanh(𝐢-4)", "-0.238878+1.50862×𝐢", Degree, Cartesian, 6); + assert_expression_approximates_to("atanh(𝐢-4)", "-0.238878+1.50862×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("atanh(𝐢-4)", "-0.238878+1.50862×𝐢", Degree, Metric, Cartesian, 6); // Check that the complex part is not neglected - assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-26×𝐢)", "13+5ᴇ-16×𝐢", Radian, Cartesian, 3); - assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-60×𝐢)", "13+5ᴇ-50×𝐢", Radian, Cartesian, 3); - assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-150×𝐢)", "13+5ᴇ-140×𝐢", Radian, Cartesian, 3); - assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-250×𝐢)", "13+5ᴇ-240×𝐢", Radian, Cartesian, 3); - assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-300×𝐢)", "13+5ᴇ-290×𝐢", Radian, Cartesian, 3); + assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-26×𝐢)", "13+5ᴇ-16×𝐢", Radian, Metric, Cartesian, 3); + assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-60×𝐢)", "13+5ᴇ-50×𝐢", Radian, Metric, Cartesian, 3); + assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-150×𝐢)", "13+5ᴇ-140×𝐢", Radian, Metric, Cartesian, 3); + assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-250×𝐢)", "13+5ᴇ-240×𝐢", Radian, Metric, Cartesian, 3); + assert_expression_approximates_to("atanh(0.99999999999+1.0ᴇ-300×𝐢)", "13+5ᴇ-290×𝐢", Radian, Metric, Cartesian, 3); // WARNING: evaluate on branch cut can be multivalued assert_expression_approximates_to("acos(2)", "1.3169578969248×𝐢", Radian); @@ -832,100 +882,100 @@ QUIZ_CASE(poincare_approximation_store_matrix) { QUIZ_CASE(poincare_approximation_complex_format) { // Real - assert_expression_approximates_to("0", "0", Radian, Real); - assert_expression_approximates_to("0", "0", Radian, Real); - assert_expression_approximates_to("10", "10", Radian, Real); - assert_expression_approximates_to("-10", "-10", Radian, Real); - assert_expression_approximates_to("100", "100", Radian, Real); - assert_expression_approximates_to("0.1", "0.1", Radian, Real); - assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Real); - assert_expression_approximates_to("0.123456789012345", "1.2345678901235ᴇ-1", Radian, Real); - assert_expression_approximates_to("1+2×𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("1+𝐢-𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("1+𝐢-1", "unreal", Radian, Real); - assert_expression_approximates_to("1+𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("3+𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("3-𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("3-𝐢-3", "unreal", Radian, Real); - assert_expression_approximates_to("𝐢", "unreal", Radian, Real); - assert_expression_approximates_to("√(-1)", "unreal", Radian, Real); - assert_expression_approximates_to("√(-1)×√(-1)", "unreal", Radian, Real); - assert_expression_approximates_to("ln(-2)", "unreal", Radian, Real); + assert_expression_approximates_to("0", "0", Radian, Metric, Real); + assert_expression_approximates_to("0", "0", Radian, Metric, Real); + assert_expression_approximates_to("10", "10", Radian, Metric, Real); + assert_expression_approximates_to("-10", "-10", Radian, Metric, Real); + assert_expression_approximates_to("100", "100", Radian, Metric, Real); + assert_expression_approximates_to("0.1", "0.1", Radian, Metric, Real); + assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Metric, Real); + assert_expression_approximates_to("0.123456789012345", "1.2345678901235ᴇ-1", Radian, Metric, Real); + assert_expression_approximates_to("1+2×𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("1+𝐢-𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("1+𝐢-1", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("1+𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("3+𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("3-𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("3-𝐢-3", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("𝐢", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("√(-1)", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("√(-1)×√(-1)", "unreal", Radian, Metric, Real); + assert_expression_approximates_to("ln(-2)", "unreal", Radian, Metric, Real); // Power/Root approximates to the first REAL root in Real mode - assert_expression_simplifies_approximates_to("(-8)^(1/3)", "-2", Radian, Real); // Power have to be simplified first in order to spot the right form c^(p/q) with p, q integers - assert_expression_approximates_to("root(-8,3)", "-2", Radian, Real); // Root approximates to the first REAL root in Real mode - assert_expression_approximates_to("8^(1/3)", "2", Radian, Real); - assert_expression_simplifies_approximates_to("(-8)^(2/3)", "4", Radian, Real); // Power have to be simplified first (cf previous comment) - assert_expression_approximates_to("root(-8, 3)^2", "4", Radian, Real); - assert_expression_approximates_to("root(-8,3)", "-2", Radian, Real); + assert_expression_simplifies_approximates_to("(-8)^(1/3)", "-2", Radian, Metric, Real); // Power have to be simplified first in order to spot the right form c^(p/q) with p, q integers + assert_expression_approximates_to("root(-8,3)", "-2", Radian, Metric, Real); // Root approximates to the first REAL root in Real mode + assert_expression_approximates_to("8^(1/3)", "2", Radian, Metric, Real); + assert_expression_simplifies_approximates_to("(-8)^(2/3)", "4", Radian, Metric, Real); // Power have to be simplified first (cf previous comment) + assert_expression_approximates_to("root(-8, 3)^2", "4", Radian, Metric, Real); + assert_expression_approximates_to("root(-8,3)", "-2", Radian, Metric, Real); // Cartesian - assert_expression_approximates_to("0", "0", Radian, Cartesian); - assert_expression_approximates_to("0", "0", Radian, Cartesian); - assert_expression_approximates_to("10", "10", Radian, Cartesian); - assert_expression_approximates_to("-10", "-10", Radian, Cartesian); - assert_expression_approximates_to("100", "100", Radian, Cartesian); - assert_expression_approximates_to("0.1", "0.1", Radian, Cartesian); - assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Cartesian); - assert_expression_approximates_to("0.123456789012345", "1.2345678901235ᴇ-1", Radian, Cartesian); - assert_expression_approximates_to("1+2×𝐢", "1+2×𝐢", Radian, Cartesian); - assert_expression_approximates_to("1+𝐢-𝐢", "1", Radian, Cartesian); - assert_expression_approximates_to("1+𝐢-1", "𝐢", Radian, Cartesian); - assert_expression_approximates_to("1+𝐢", "1+𝐢", Radian, Cartesian); - assert_expression_approximates_to("3+𝐢", "3+𝐢", Radian, Cartesian); - assert_expression_approximates_to("3-𝐢", "3-𝐢", Radian, Cartesian); - assert_expression_approximates_to("3-𝐢-3", "-𝐢", Radian, Cartesian); - assert_expression_approximates_to("𝐢", "𝐢", Radian, Cartesian); - assert_expression_approximates_to("√(-1)", "𝐢", Radian, Cartesian); - assert_expression_approximates_to("√(-1)×√(-1)", "-1", Radian, Cartesian); - assert_expression_approximates_to("ln(-2)", "6.9314718055995ᴇ-1+3.1415926535898×𝐢", Radian, Cartesian); - assert_expression_approximates_to("(-8)^(1/3)", "1+1.7320508075689×𝐢", Radian, Cartesian); - assert_expression_approximates_to("(-8)^(2/3)", "-2+3.4641×𝐢", Radian, Cartesian, 6); - assert_expression_approximates_to("root(-8,3)", "1+1.7320508075689×𝐢", Radian, Cartesian); + assert_expression_approximates_to("0", "0", Radian, Metric, Cartesian); + assert_expression_approximates_to("0", "0", Radian, Metric, Cartesian); + assert_expression_approximates_to("10", "10", Radian, Metric, Cartesian); + assert_expression_approximates_to("-10", "-10", Radian, Metric, Cartesian); + assert_expression_approximates_to("100", "100", Radian, Metric, Cartesian); + assert_expression_approximates_to("0.1", "0.1", Radian, Metric, Cartesian); + assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Metric, Cartesian); + assert_expression_approximates_to("0.123456789012345", "1.2345678901235ᴇ-1", Radian, Metric, Cartesian); + assert_expression_approximates_to("1+2×𝐢", "1+2×𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("1+𝐢-𝐢", "1", Radian, Metric, Cartesian); + assert_expression_approximates_to("1+𝐢-1", "𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("1+𝐢", "1+𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("3+𝐢", "3+𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("3-𝐢", "3-𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("3-𝐢-3", "-𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("𝐢", "𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("√(-1)", "𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("√(-1)×√(-1)", "-1", Radian, Metric, Cartesian); + assert_expression_approximates_to("ln(-2)", "6.9314718055995ᴇ-1+3.1415926535898×𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("(-8)^(1/3)", "1+1.7320508075689×𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("(-8)^(2/3)", "-2+3.4641×𝐢", Radian, Metric, Cartesian, 6); + assert_expression_approximates_to("root(-8,3)", "1+1.7320508075689×𝐢", Radian, Metric, Cartesian); // Polar - assert_expression_approximates_to("0", "0", Radian, Polar); - assert_expression_approximates_to("0", "0", Radian, Polar); - assert_expression_approximates_to("10", "10", Radian, Polar); - assert_expression_approximates_to("-10", "10×ℯ^\u00123.1415926535898×𝐢\u0013", Radian, Polar); + assert_expression_approximates_to("0", "0", Radian, Metric, Polar); + assert_expression_approximates_to("0", "0", Radian, Metric, Polar); + assert_expression_approximates_to("10", "10", Radian, Metric, Polar); + assert_expression_approximates_to("-10", "10×ℯ^\u00123.1415926535898×𝐢\u0013", Radian, Metric, Polar); - assert_expression_approximates_to("100", "100", Radian, Polar); - assert_expression_approximates_to("0.1", "0.1", Radian, Polar); - assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Polar); - assert_expression_approximates_to("0.12345678", "0.12345678", Radian, Polar); + assert_expression_approximates_to("100", "100", Radian, Metric, Polar); + assert_expression_approximates_to("0.1", "0.1", Radian, Metric, Polar); + assert_expression_approximates_to("0.1234567", "0.1234567", Radian, Metric, Polar); + assert_expression_approximates_to("0.12345678", "0.12345678", Radian, Metric, Polar); - assert_expression_approximates_to("1+2×𝐢", "2.236068×ℯ^\u00121.107149×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("1+𝐢-𝐢", "1", Radian, Polar); - assert_expression_approximates_to("1+𝐢-1", "ℯ^\u00121.57079632679×𝐢\u0013", Radian, Polar, 12); - assert_expression_approximates_to("1+𝐢", "1.414214×ℯ^\u00120.7853982×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("3+𝐢", "3.16227766017×ℯ^\u00120.321750554397×𝐢\u0013", Radian, Polar,12); - assert_expression_approximates_to("3-𝐢", "3.162278×ℯ^\u0012-0.3217506×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("3-𝐢-3", "ℯ^\u0012-1.57079632679×𝐢\u0013", Radian, Polar,12); + assert_expression_approximates_to("1+2×𝐢", "2.236068×ℯ^\u00121.107149×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("1+𝐢-𝐢", "1", Radian, Metric, Polar); + assert_expression_approximates_to("1+𝐢-1", "ℯ^\u00121.57079632679×𝐢\u0013", Radian, Metric, Polar, 12); + assert_expression_approximates_to("1+𝐢", "1.414214×ℯ^\u00120.7853982×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("3+𝐢", "3.16227766017×ℯ^\u00120.321750554397×𝐢\u0013", Radian, Metric, Polar,12); + assert_expression_approximates_to("3-𝐢", "3.162278×ℯ^\u0012-0.3217506×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("3-𝐢-3", "ℯ^\u0012-1.57079632679×𝐢\u0013", Radian, Metric, Polar,12); // 2ℯ^(𝐢) has a too low precision in float on the web platform - assert_expression_approximates_to("3ℯ^(2*𝐢)", "3×ℯ^\u00122×𝐢\u0013", Radian, Polar, 4); - assert_expression_approximates_to("2ℯ^(-𝐢)", "2×ℯ^\u0012-𝐢\u0013", Radian, Polar, 9); + assert_expression_approximates_to("3ℯ^(2*𝐢)", "3×ℯ^\u00122×𝐢\u0013", Radian, Metric, Polar, 4); + assert_expression_approximates_to("2ℯ^(-𝐢)", "2×ℯ^\u0012-𝐢\u0013", Radian, Metric, Polar, 9); - assert_expression_approximates_to("𝐢", "ℯ^\u00121.570796×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("√(-1)", "ℯ^\u00121.5707963267949×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("√(-1)×√(-1)", "ℯ^\u00123.1415926535898×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("(-8)^(1/3)", "2×ℯ^\u00121.0471975511966×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("(-8)^(2/3)", "4×ℯ^\u00122.094395×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("root(-8,3)", "2×ℯ^\u00121.0471975511966×𝐢\u0013", Radian, Polar); + assert_expression_approximates_to("𝐢", "ℯ^\u00121.570796×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("√(-1)", "ℯ^\u00121.5707963267949×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("√(-1)×√(-1)", "ℯ^\u00123.1415926535898×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("(-8)^(1/3)", "2×ℯ^\u00121.0471975511966×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("(-8)^(2/3)", "4×ℯ^\u00122.094395×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("root(-8,3)", "2×ℯ^\u00121.0471975511966×𝐢\u0013", Radian, Metric, Polar); // Cartesian to Polar and vice versa - assert_expression_approximates_to("2+3×𝐢", "3.60555127546×ℯ^\u00120.982793723247×𝐢\u0013", Radian, Polar, 12); - assert_expression_approximates_to("3.60555127546×ℯ^(0.982793723247×𝐢)", "2+3×𝐢", Radian, Cartesian, 12); - assert_expression_approximates_to("12.04159457879229548012824103×ℯ^(1.4876550949×𝐢)", "1+12×𝐢", Radian, Cartesian, 5); + assert_expression_approximates_to("2+3×𝐢", "3.60555127546×ℯ^\u00120.982793723247×𝐢\u0013", Radian, Metric, Polar, 12); + assert_expression_approximates_to("3.60555127546×ℯ^(0.982793723247×𝐢)", "2+3×𝐢", Radian, Metric, Cartesian, 12); + assert_expression_approximates_to("12.04159457879229548012824103×ℯ^(1.4876550949×𝐢)", "1+12×𝐢", Radian, Metric, Cartesian, 5); // Overflow - assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "-2ᴇ20+2ᴇ20×𝐢", Radian, Cartesian); + assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "-2ᴇ20+2ᴇ20×𝐢", Radian, Metric, Cartesian); /* TODO: this test fails on the device because libm hypotf (which is called * eventually by std::abs) is not accurate enough. We might change the * embedded libm? */ - //assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "2.828427ᴇ20×ℯ^\u00122.356194×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("-2ᴇ10+2ᴇ10×𝐢", "2.828427ᴇ10×ℯ^\u00122.356194×𝐢\u0013", Radian, Polar); - assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1ᴇ155-1ᴇ155×𝐢", Radian, Cartesian); - assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1.41421356237ᴇ155×ℯ^\u0012-0.785398163397×𝐢\u0013", Radian, Polar,12); + //assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "2.828427ᴇ20×ℯ^\u00122.356194×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("-2ᴇ10+2ᴇ10×𝐢", "2.828427ᴇ10×ℯ^\u00122.356194×𝐢\u0013", Radian, Metric, Polar); + assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1ᴇ155-1ᴇ155×𝐢", Radian, Metric, Cartesian); + assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1.41421356237ᴇ155×ℯ^\u0012-0.785398163397×𝐢\u0013", Radian, Metric, Polar,12); assert_expression_approximates_to("-2ᴇ100+2ᴇ100×𝐢", Undefined::Name()); assert_expression_approximates_to("-2ᴇ360+2ᴇ360×𝐢", Undefined::Name()); assert_expression_approximates_to("-2ᴇ100+2ᴇ10×𝐢", "-inf+2ᴇ10×𝐢"); @@ -937,8 +987,8 @@ QUIZ_CASE(poincare_approximation_complex_format) { QUIZ_CASE(poincare_approximation_mix) { assert_expression_approximates_to("-2-3", "-5"); assert_expression_approximates_to("1.2×ℯ^(1)", "3.261938"); - assert_expression_approximates_to("2ℯ^(3)", "40.1711", Radian, Cartesian, 6); // WARNING: the 7th significant digit is wrong on blackbos simulator - assert_expression_approximates_to("ℯ^2×ℯ^(1)", "20.0855", Radian, Cartesian, 6); // WARNING: the 7th significant digit is wrong on simulator + assert_expression_approximates_to("2ℯ^(3)", "40.1711", Radian, Metric, Cartesian, 6); // WARNING: the 7th significant digit is wrong on blackbos simulator + assert_expression_approximates_to("ℯ^2×ℯ^(1)", "20.0855", Radian, Metric, Cartesian, 6); // WARNING: the 7th significant digit is wrong on simulator assert_expression_approximates_to("ℯ^2×ℯ^(1)", "20.085536923188"); assert_expression_approximates_to("2×3^4+2", "164"); assert_expression_approximates_to("-2×3^4+2", "-160"); @@ -951,13 +1001,13 @@ QUIZ_CASE(poincare_approximation_mix) { assert_expression_approximates_to("4/2×(2+3)", "10"); assert_expression_simplifies_and_approximates_to("1.0092^(20)", "1.2010050593402"); - assert_expression_simplifies_and_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Cartesian, 13); - assert_expression_simplifies_and_approximates_to("1.0092^(50)×ln(1.0092)", "1.447637354655ᴇ-2", Degree, Cartesian, 13); + assert_expression_simplifies_and_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Metric, Cartesian, 13); + assert_expression_simplifies_and_approximates_to("1.0092^(50)×ln(1.0092)", "1.447637354655ᴇ-2", Degree, Metric, Cartesian, 13); assert_expression_approximates_to("1.0092^(20)", "1.2010050593402"); - assert_expression_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Cartesian, 13); - assert_expression_approximates_to("1.0092^(50)×ln(1.0092)", "1.447637354655ᴇ-2", Degree, Cartesian, 13); + assert_expression_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Metric, Cartesian, 13); + assert_expression_approximates_to("1.0092^(50)×ln(1.0092)", "1.447637354655ᴇ-2", Degree, Metric, Cartesian, 13); assert_expression_simplifies_approximates_to("1.0092^(20)", "1.2010050593402"); - assert_expression_simplifies_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Cartesian, 13); + assert_expression_simplifies_approximates_to("1.0092^(50)×ln(3/2)", "0.6409373488899", Degree, Metric, Cartesian, 13); //assert_expression_approximates_to("1.0092^(20)", "1.201005"); TODO does not work assert_expression_approximates_to("1.0092^(50)×ln(3/2)", "0.6409366"); //assert_expression_simplifies_approximates_to("1.0092^(20)", "1.2010050593402"); TODO does not work diff --git a/poincare/test/arithmetic.cpp b/poincare/test/arithmetic.cpp index d8a9ad9c7..36a268b5e 100644 --- a/poincare/test/arithmetic.cpp +++ b/poincare/test/arithmetic.cpp @@ -22,6 +22,13 @@ void assert_gcd_equals_to(Integer a, Integer b, Integer c) { fill_buffer_with(failInformationBuffer, bufferSize, "gcd(", args, 2); Integer gcd = Arithmetic::GCD(a, b); quiz_assert_print_if_failure(gcd.isEqualTo(c), failInformationBuffer); + if (a.isExtractable() && b.isExtractable()) { + // Test Arithmetic::GCD(int, int) if possible + a.setNegative(false); + b.setNegative(false); + int extractedGcd = Arithmetic::GCD(a.extractedInt(), b.extractedInt()); + quiz_assert_print_if_failure(extractedGcd == c.extractedInt(), failInformationBuffer); + } } void assert_lcm_equals_to(Integer a, Integer b, Integer c) { @@ -31,6 +38,13 @@ void assert_lcm_equals_to(Integer a, Integer b, Integer c) { fill_buffer_with(failInformationBuffer, bufferSize, "lcm(", args, 2); Integer lcm = Arithmetic::LCM(a, b); quiz_assert_print_if_failure(lcm.isEqualTo(c), failInformationBuffer); + if (a.isExtractable() && b.isExtractable()) { + // Test Arithmetic::LCM(int, int) if possible + a.setNegative(false); + b.setNegative(false); + int extractedLcm = Arithmetic::LCM(a.extractedInt(), b.extractedInt()); + quiz_assert_print_if_failure(extractedLcm == c.extractedInt(), failInformationBuffer); + } } void assert_prime_factorization_equals_to(Integer a, int * factors, int * coefficients, int length) { diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp new file mode 100644 index 000000000..5cde15597 --- /dev/null +++ b/poincare/test/derivative.cpp @@ -0,0 +1,72 @@ +#include +#include "helper.h" + +using namespace Poincare; + +void assert_reduces_to_formal_expression(const char * expression, const char * result, Preferences::AngleUnit angleUnit = Radian) { + assert_parsed_expression_simplify_to(expression, result, User, angleUnit, Metric, Cartesian, ReplaceAllDefinedSymbolsWithDefinition); +} + +QUIZ_CASE(poincare_derivative_formal) { + assert_reduces_to_formal_expression("diff(undef,x,x)", Undefined::Name()); + assert_reduces_to_formal_expression("diff(unreal,x,x)", Unreal::Name()); + assert_reduces_to_formal_expression("diff(inf,x,x)", Undefined::Name()); + assert_reduces_to_formal_expression("diff(1,x,x)", "0"); + assert_reduces_to_formal_expression("diff(π,x,x)", "0"); + assert_reduces_to_formal_expression("diff(y,x,x)", "0"); + assert_reduces_to_formal_expression("diff(x,x,x)", "1"); + assert_reduces_to_formal_expression("diff(x^2,x,x)", "2×x"); + assert_reduces_to_formal_expression("diff((x-1)(x-2)(x-3),x,x)", "3×x^2-12×x+11"); + assert_reduces_to_formal_expression("diff(√(x),x,x)", "1/\u00122×√(x)\u0013"); + assert_reduces_to_formal_expression("diff(1/x,x,x)", "-1/x^2"); + assert_reduces_to_formal_expression("diff(ℯ^x,x,x)", "ℯ^x"); + assert_reduces_to_formal_expression("diff(2^x,x,x)", "2^x×ln(2)"); + assert_reduces_to_formal_expression("diff(ln(x),x,x)", "1/x"); + assert_reduces_to_formal_expression("diff(log(x),x,x)", "1/\u0012x×ln(5)+x×ln(2)\u0013"); + assert_reduces_to_formal_expression("diff(sin(x),x,x)", "cos(x)"); + assert_reduces_to_formal_expression("diff(sin(x),x,x)", "\u0012π×cos(x)\u0013/180", Degree); + assert_reduces_to_formal_expression("diff(cos(x),x,x)", "-sin(x)"); + assert_reduces_to_formal_expression("diff(cos(x),x,x)", "-\u0012π×sin(x)\u0013/200", Gradian); + assert_reduces_to_formal_expression("diff(tan(x),x,x)", "1/cos(x)^2"); + assert_reduces_to_formal_expression("diff(tan(x),x,x)", "π/\u0012180×cos(x)^2\u0013", Degree); + assert_reduces_to_formal_expression("diff(sinh(x),x,x)", "cosh(x)"); + assert_reduces_to_formal_expression("diff(cosh(x),x,x)", "sinh(x)"); + assert_reduces_to_formal_expression("diff(tanh(x),x,x)", "1/cosh(x)^2"); + assert_reduces_to_formal_expression("diff(sin(x)^2,x,x)", "2×sin(x)×cos(x)"); + assert_reduces_to_formal_expression("diff(diff(x^3,x,x),x,x)", "6×x"); + assert_reduces_to_formal_expression("diff(sinh(sin(y)),x,x)", "0"); + + assert_reduce("2→a"); + assert_reduce("-1→b"); + assert_reduce("3→c"); + assert_reduce("x/2→f"); + + assert_reduces_to_formal_expression("diff(a×x^2+b×x+c,x,x)", "4×x-1"); + assert_reduces_to_formal_expression("diff(f,x,x)", "1/2"); + assert_reduces_to_formal_expression("diff(a^2,a,x)", "2×x"); + assert_reduces_to_formal_expression("diff(a^2,a,a)", "4"); + + Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("b.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("c.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("f.exp").destroy(); + +} + +void assert_reduces_for_approximation(const char * expression, const char * result, Preferences::AngleUnit angleUnit = Radian) { + assert_parsed_expression_simplify_to(expression, result, SystemForApproximation, angleUnit, Metric, Real, ReplaceAllSymbolsWithDefinitionsOrUndefined); +} + +QUIZ_CASE(poincare_derivative_approximation) { + assert_reduces_for_approximation("diff(ln(x),x,1)", "1"); + assert_reduces_for_approximation("diff(ln(x),x,2.2)", "5/11"); + assert_reduces_for_approximation("diff(ln(x),x,0)", Undefined::Name()); + assert_reduces_for_approximation("diff(ln(x),x,-3.1)", Unreal::Name()); + assert_reduces_for_approximation("diff(log(x),x,-10)", Unreal::Name()); + + assert_reduces_for_approximation("diff(abs(x),x,123)", "1"); + assert_reduces_for_approximation("diff(abs(x),x,-2.34)", "-1"); + assert_reduces_for_approximation("diff(abs(x),x,0)", Undefined::Name()); + + assert_reduces_for_approximation("diff(1/x,x,-2)", "-1/4"); +} diff --git a/poincare/test/expression.cpp b/poincare/test/expression.cpp index bd1ae80fc..c6508847d 100644 --- a/poincare/test/expression.cpp +++ b/poincare/test/expression.cpp @@ -75,18 +75,18 @@ QUIZ_CASE(poincare_expression_rational_constructor) { } QUIZ_CASE(poincare_expression_unit_constructor) { - Unit u = Unit::Second(); + Unit u = Unit::Builder(Unit::k_timeRepresentatives, Unit::Prefix::EmptyPrefix()); assert_expression_serialize_to(u, "_s"); - u = Unit::Hour(); + u = Unit::Builder(Unit::k_timeRepresentatives + 2, Unit::Prefix::EmptyPrefix()); assert_expression_serialize_to(u, "_h"); - u = Unit::Kilometer(); + u = Unit::Builder(Unit::k_distanceRepresentatives, Unit::k_prefixes + 9); assert_expression_serialize_to(u, "_km"); - u = Unit::Liter(); + u = Unit::Builder(Unit::k_volumeRepresentatives, Unit::Prefix::EmptyPrefix()); assert_expression_serialize_to(u, "_L"); - u = Unit::Watt(); + u = Unit::Builder(Unit::k_powerRepresentatives, Unit::Prefix::EmptyPrefix()); assert_expression_serialize_to(u, "_W"); } diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 145e533af..337bcca17 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -17,6 +17,43 @@ QUIZ_CASE(poincare_properties_is_number) { quiz_assert(!Addition::Builder(Rational::Builder(1), Rational::Builder(2)).isNumber()); } +QUIZ_CASE(poincare_properties_is_number_zero) { + Shared::GlobalContext context; + quiz_assert(BasedInteger::Builder("2",Integer::Base::Binary).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("2",Integer::Base::Decimal).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("2",Integer::Base::Hexadecimal).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Binary).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Decimal).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Hexadecimal).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Decimal::Builder("2",3).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Decimal::Builder("0",0).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Float::Builder(1.0f).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Float::Builder(0.0f).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Infinity::Builder(true).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Undefined::Builder().nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Rational::Builder(2,3).nullStatus(&context) == ExpressionNode::NullStatus::NonNull ); + quiz_assert(Rational::Builder(0,1).nullStatus(&context) == ExpressionNode::NullStatus::Null ); + quiz_assert(Symbol::Builder('a').nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Multiplication::Builder(Rational::Builder(1), Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + quiz_assert(Addition::Builder(Rational::Builder(1), Rational::Builder(-1)).nullStatus(&context) == ExpressionNode::NullStatus::Unknown); + + quiz_assert(AbsoluteValue::Builder(Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(ArcSine::Builder(Rational::Builder(1,7)).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(ComplexCartesian::Builder(Rational::Builder(0), Rational::Builder(3, 2)).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(ComplexCartesian::Builder(Rational::Builder(0), Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(Conjugate::Builder(ComplexCartesian::Builder(Rational::Builder(2, 3), Rational::Builder(3, 2))).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(Factor::Builder(Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(Factorial::Builder(Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(ImaginaryPart::Builder(Rational::Builder(14)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(RealPart::Builder(Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(Parenthesis::Builder(Rational::Builder(-7)).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(SignFunction::Builder(Rational::Builder(0)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(Unit::Builder(Unit::k_powerRepresentatives, Unit::Prefix::EmptyPrefix()).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); + quiz_assert(Division::Builder(Rational::Builder(0), Rational::Builder(3,7)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(Power::Builder(Rational::Builder(0), Rational::Builder(3,7)).nullStatus(&context) == ExpressionNode::NullStatus::Null); + quiz_assert(SquareRoot::Builder(Rational::Builder(2,5)).nullStatus(&context) == ExpressionNode::NullStatus::NonNull); +} + QUIZ_CASE(poincare_properties_is_random) { quiz_assert(Random::Builder().isRandom()); quiz_assert(Randint::Builder(Rational::Builder(1), Rational::Builder(2)).isRandom()); @@ -63,6 +100,9 @@ QUIZ_CASE(poincare_properties_is_matrix) { assert_expression_has_property("inverse([[1,2][3,4]])", &context, Expression::IsMatrix); assert_expression_has_property("3*identity(4)", &context, Expression::IsMatrix); assert_expression_has_property("transpose([[1,2][3,4]])", &context, Expression::IsMatrix); + assert_expression_has_property("ref([[1,2][3,4]])", &context, Expression::IsMatrix); + assert_expression_has_property("rref([[1,2][3,4]])", &context, Expression::IsMatrix); + assert_expression_has_property("cross([[1][2][3]],[[3][4][5]])", &context, Expression::IsMatrix); assert_expression_has_not_property("2*3+1", &context, Expression::IsMatrix); } @@ -102,10 +142,10 @@ constexpr Poincare::ExpressionNode::Sign Positive = Poincare::ExpressionNode::Si constexpr Poincare::ExpressionNode::Sign Negative = Poincare::ExpressionNode::Sign::Negative; constexpr Poincare::ExpressionNode::Sign Unknown = Poincare::ExpressionNode::Sign::Unknown; -void assert_reduced_expression_sign(const char * expression, Poincare::ExpressionNode::Sign sign, Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian) { +void assert_reduced_expression_sign(const char * expression, Poincare::ExpressionNode::Sign sign, Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian, Preferences::UnitFormat unitFormat = Metric) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation)); + e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation)); quiz_assert_print_if_failure(e.sign(&globalContext) == sign, expression); } @@ -130,6 +170,33 @@ QUIZ_CASE(poincare_properties_rational_sign) { quiz_assert(Rational::Builder(0, 3).sign() == ExpressionNode::Sign::Positive); } +QUIZ_CASE(poincare_properties_expression_sign) { + Shared::GlobalContext context; + quiz_assert(ArcCosine::Builder(Rational::Builder(-1,7)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(ArcCosine::Builder(Symbol::Builder('a')).sign(&context) == ExpressionNode::Sign::Unknown); + quiz_assert(ArcSine::Builder(Rational::Builder(-1,7)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(ArcTangent::Builder(Rational::Builder(1,7)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Ceiling::Builder(Rational::Builder(7,3)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Floor::Builder(Rational::Builder(7,3)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Round::Builder(Rational::Builder(7,3), Rational::Builder(1)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Conjugate::Builder(ComplexCartesian::Builder(Rational::Builder(2, 3), BasedInteger::Builder(0, Integer::Base::Binary))).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(DivisionRemainder::Builder(Decimal::Builder(2.0), Decimal::Builder(3.0)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(AbsoluteValue::Builder(Rational::Builder(-14)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(FracPart::Builder(Rational::Builder(-7,3)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(GreatCommonDivisor::Builder({Rational::Builder(-7),Rational::Builder(-7)}).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(LeastCommonMultiple::Builder({Rational::Builder(-7),Rational::Builder(-7)}).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Opposite::Builder(Rational::Builder(7)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(Parenthesis::Builder(Rational::Builder(-7)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(PermuteCoefficient::Builder(Rational::Builder(7),Rational::Builder(8)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(RealPart::Builder(Rational::Builder(-7)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(SignFunction::Builder(Rational::Builder(-7)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(Unit::Builder(Unit::k_powerRepresentatives, Unit::Prefix::EmptyPrefix()).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(VectorNorm::Builder(BasedInteger::Builder(1)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(Division::Builder(Rational::Builder(7,3), Rational::Builder(-1)).sign(&context) == ExpressionNode::Sign::Negative); + quiz_assert(DivisionQuotient::Builder(Rational::Builder(-7), Rational::Builder(-1)).sign(&context) == ExpressionNode::Sign::Positive); + quiz_assert(ArcSine::Builder(Floor::Builder(ArcTangent::Builder(Opposite::Builder(RealPart::Builder(ArcCosine::Builder(Constant::Builder(UCodePointGreekSmallLetterPi))))))).sign(&context) == ExpressionNode::Sign::Negative); +} + QUIZ_CASE(poincare_properties_sign) { assert_reduced_expression_sign("abs(-cos(2)+I)", Positive); assert_reduced_expression_sign("2.345ᴇ-23", Positive); @@ -163,14 +230,14 @@ QUIZ_CASE(poincare_properties_sign) { void assert_expression_is_real(const char * expression) { Shared::GlobalContext context; // isReal can be call only on reduced expressions - Expression e = parse_expression(expression, &context, false).reduce(ExpressionNode::ReductionContext(&context, Cartesian, Radian, ExpressionNode::ReductionTarget::SystemForApproximation)); + Expression e = parse_expression(expression, &context, false).reduce(ExpressionNode::ReductionContext(&context, Cartesian, Radian, Metric, ExpressionNode::ReductionTarget::SystemForApproximation)); quiz_assert_print_if_failure(e.isReal(&context), expression); } void assert_expression_is_not_real(const char * expression) { Shared::GlobalContext context; // isReal can be call only on reduced expressions - Expression e = parse_expression(expression, &context, false).reduce(ExpressionNode::ReductionContext(&context, Cartesian, Radian, ExpressionNode::ReductionTarget::SystemForApproximation)); + Expression e = parse_expression(expression, &context, false).reduce(ExpressionNode::ReductionContext(&context, Cartesian, Radian, Metric, ExpressionNode::ReductionTarget::SystemForApproximation)); quiz_assert_print_if_failure(!e.isReal(&context), expression); } @@ -204,10 +271,10 @@ QUIZ_CASE(poincare_properties_is_real) { assert_expression_is_not_real("(-2)^0.4"); } -void assert_reduced_expression_polynomial_degree(const char * expression, int degree, const char * symbolName = "x", Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian) { +void assert_reduced_expression_polynomial_degree(const char * expression, int degree, const char * symbolName = "x", Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian, Preferences::UnitFormat unitFormat = Metric) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - Expression result = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForApproximation)); + Expression result = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, SystemForApproximation)); quiz_assert_print_if_failure(result.polynomialDegree(&globalContext, symbolName) == degree, expression); } @@ -216,8 +283,8 @@ QUIZ_CASE(poincare_properties_polynomial_degree) { assert_reduced_expression_polynomial_degree("x+1", 1); assert_reduced_expression_polynomial_degree("cos(2)+1", 0); assert_reduced_expression_polynomial_degree("confidence(0.2,10)+1", -1); - assert_reduced_expression_polynomial_degree("diff(3×x+x,x,2)", -1); - assert_reduced_expression_polynomial_degree("diff(3×x+x,x,x)", -1); + assert_reduced_expression_polynomial_degree("diff(3×x+x,x,2)", 0); + assert_reduced_expression_polynomial_degree("diff(3×x+x,x,x)", 0); assert_reduced_expression_polynomial_degree("diff(3×x+x,x,x)", 0, "a"); assert_reduced_expression_polynomial_degree("(3×x+2)/3", 1); assert_reduced_expression_polynomial_degree("(3×x+2)/x", -1); @@ -237,45 +304,6 @@ QUIZ_CASE(poincare_properties_polynomial_degree) { Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); } -void assert_reduced_expression_has_characteristic_range(Expression e, float range, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Degree) { - Shared::GlobalContext globalContext; - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, Preferences::ComplexFormat::Cartesian, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation)); - if (std::isnan(range)) { - quiz_assert(std::isnan(e.characteristicXRange(&globalContext, angleUnit))); - } else { - quiz_assert(std::fabs(e.characteristicXRange(&globalContext, angleUnit) - range) < 0.0000001f); - } -} - -QUIZ_CASE(poincare_properties_characteristic_range) { - // cos(x), degree - assert_reduced_expression_has_characteristic_range(Cosine::Builder(Symbol::Builder(UCodePointUnknown)), 360.0f); - // cos(-x), degree - assert_reduced_expression_has_characteristic_range(Cosine::Builder(Opposite::Builder(Symbol::Builder(UCodePointUnknown))), 360.0f); - // cos(x), radian - assert_reduced_expression_has_characteristic_range(Cosine::Builder(Symbol::Builder(UCodePointUnknown)), 2.0f*M_PI, Preferences::AngleUnit::Radian); - // cos(-x), radian - assert_reduced_expression_has_characteristic_range(Cosine::Builder(Opposite::Builder(Symbol::Builder(UCodePointUnknown))), 2.0f*M_PI, Preferences::AngleUnit::Radian); - // sin(9x+10), degree - assert_reduced_expression_has_characteristic_range(Sine::Builder(Addition::Builder(Multiplication::Builder(Rational::Builder(9),Symbol::Builder(UCodePointUnknown)),Rational::Builder(10))), 40.0f); - // sin(9x+10)+cos(x/2), degree - assert_reduced_expression_has_characteristic_range(Addition::Builder(Sine::Builder(Addition::Builder(Multiplication::Builder(Rational::Builder(9),Symbol::Builder(UCodePointUnknown)),Rational::Builder(10))),Cosine::Builder(Division::Builder(Symbol::Builder(UCodePointUnknown),Rational::Builder(2)))), 720.0f); - // sin(9x+10)+cos(x/2), radian - assert_reduced_expression_has_characteristic_range(Addition::Builder(Sine::Builder(Addition::Builder(Multiplication::Builder(Rational::Builder(9),Symbol::Builder(UCodePointUnknown)),Rational::Builder(10))),Cosine::Builder(Division::Builder(Symbol::Builder(UCodePointUnknown),Rational::Builder(2)))), 4.0f*M_PI, Preferences::AngleUnit::Radian); - // x, degree - assert_reduced_expression_has_characteristic_range(Symbol::Builder(UCodePointUnknown), NAN); - // cos(3)+2, degree - assert_reduced_expression_has_characteristic_range(Addition::Builder(Cosine::Builder(Rational::Builder(3)),Rational::Builder(2)), 0.0f); - // log(cos(40x), degree - assert_reduced_expression_has_characteristic_range(CommonLogarithm::Builder(Cosine::Builder(Multiplication::Builder(Rational::Builder(40),Symbol::Builder(UCodePointUnknown)))), 9.0f); - // cos(cos(x)), degree - assert_reduced_expression_has_characteristic_range(Cosine::Builder((Expression)Cosine::Builder(Symbol::Builder(UCodePointUnknown))), 360.0f); - // f(x) with f : x --> cos(x), degree - assert_reduce("cos(x)→f(x)"); - assert_reduced_expression_has_characteristic_range(Function::Builder("f",1,Symbol::Builder(UCodePointUnknown)), 360.0f); - Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); -} - void assert_expression_has_variables(const char * expression, const char * variables[], int trueNumberOfVariables) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); @@ -308,7 +336,7 @@ QUIZ_CASE(poincare_properties_get_variables) { const char * variableBuffer6[] = {""}; assert_expression_has_variables("a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+aa+bb+cc+dd+ee+ff+gg+hh+ii+jj+kk+ll+mm+nn+oo", variableBuffer6, -1); assert_expression_has_variables("a+b+c+d+e+f+g", variableBuffer6, -1); - // f: x→1+πx+x^2+toto + // f: x → 1+πx+x^2+toto assert_reduce("1+π×x+x^2+toto→f(x)"); const char * variableBuffer7[] = {"tata","toto", ""}; assert_expression_has_variables("f(tata)", variableBuffer7, 2); @@ -318,18 +346,43 @@ QUIZ_CASE(poincare_properties_get_variables) { assert_expression_has_variables("diff(3x,x,0)y-2", variableBuffer8, 1); const char * variableBuffer9[] = {"a", "b", "c", "d", "e", "f"}; assert_expression_has_variables("a+b+c+d+e+f", variableBuffer9, 6); + + const char * variableBuffer10[] = {"c", "z", "a", "b", ""}; + assert_expression_has_variables("int(c×x×z, x, a, b)", variableBuffer10, 4); + const char * variableBuffer11[] = {"box", "y", "z", "a", ""}; + assert_expression_has_variables("box+y×int(z,x,a,0)", variableBuffer11, 4); + + // f: x → 0 + assert_reduce("0→f(x)"); + const char * variableBuffer12[] = {"var", ""}; + assert_expression_has_variables("f(var)", variableBuffer12, 1); + Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); + // f: x → a, with a = 12 + assert_reduce("12→a"); + assert_reduce("a→f(x)"); + const char * variableBuffer13[] = {"var", ""}; + assert_expression_has_variables("f(var)", variableBuffer13, 1); + Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); + Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); + // f: x → 1, g: x → 2 + assert_reduce("1→f(x)"); + assert_reduce("2→g(x)"); + const char * variableBuffer14[] = {"x", "y", ""}; + assert_expression_has_variables("f(g(x)+y)", variableBuffer14, 2); + Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); + Ion::Storage::sharedStorage()->recordNamed("g.func").destroy(); } -void assert_reduced_expression_has_polynomial_coefficient(const char * expression, const char * symbolName, const char ** coefficients, Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian, ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition) { +void assert_reduced_expression_has_polynomial_coefficient(const char * expression, const char * symbolName, const char ** coefficients, Preferences::ComplexFormat complexFormat = Cartesian, Preferences::AngleUnit angleUnit = Radian, Preferences::UnitFormat unitFormat = Metric, ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForAnalysis, symbolicComputation)); + e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, SystemForAnalysis, symbolicComputation)); Expression coefficientBuffer[Poincare::Expression::k_maxNumberOfPolynomialCoefficients]; - int d = e.getPolynomialReducedCoefficients(symbolName, coefficientBuffer, &globalContext, complexFormat, Radian, symbolicComputation); + int d = e.getPolynomialReducedCoefficients(symbolName, coefficientBuffer, &globalContext, complexFormat, Radian, unitFormat, symbolicComputation); for (int i = 0; i <= d; i++) { Expression f = parse_expression(coefficients[i], &globalContext, false); - coefficientBuffer[i] = coefficientBuffer[i].reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForAnalysis, symbolicComputation)); - f = f.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForAnalysis, symbolicComputation)); + coefficientBuffer[i] = coefficientBuffer[i].reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, SystemForAnalysis, symbolicComputation)); + f = f.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, SystemForAnalysis, symbolicComputation)); quiz_assert_print_if_failure(coefficientBuffer[i].isIdenticalTo(f), expression); } quiz_assert_print_if_failure(coefficients[d+1] == 0, expression); @@ -360,9 +413,9 @@ 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); + assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, Metric, DoNotReplaceAnySymbol); + assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, Metric, ReplaceDefinedFunctionsWithDefinitions); + assert_reduced_expression_has_polynomial_coefficient("f(x)", "x", coefficient4, Cartesian, Radian, Metric, ReplaceDefinedFunctionsWithDefinitions); // Clear the storage Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); @@ -371,15 +424,13 @@ QUIZ_CASE(poincare_properties_get_polynomial_coefficients) { void assert_reduced_expression_unit_is(const char * expression, const char * unit) { Shared::GlobalContext globalContext; - ExpressionNode::ReductionContext redContext(&globalContext, Real, Degree, SystemForApproximation); + ExpressionNode::ReductionContext redContext(&globalContext, Real, Degree, Metric, SystemForApproximation); Expression e = parse_expression(expression, &globalContext, false); - e = e.reduce(redContext); Expression u1; - e = e.removeUnit(&u1); + e = e.reduceAndRemoveUnit(redContext, &u1); Expression e2 = parse_expression(unit, &globalContext, false); Expression u2; - e2 = e2.reduce(redContext); - e2.removeUnit(&u2); + e2.reduceAndRemoveUnit(redContext, &u2); quiz_assert_print_if_failure(u1.isUninitialized() == u2.isUninitialized() && (u1.isUninitialized() || u1.isIdenticalTo(u2)), expression); } @@ -391,51 +442,77 @@ QUIZ_CASE(poincare_properties_remove_unit) { assert_reduced_expression_unit_is("_L^2×3×_s", "_m^6×_s"); } -void assert_seconds_split_to(double totalSeconds, const char * splittedTime, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { - Expression time = Unit::BuildTimeSplit(totalSeconds, context, complexFormat, angleUnit); - constexpr static int bufferSize = 100; - char buffer[bufferSize]; - time.serialize(buffer, bufferSize, DecimalMode); - quiz_assert_print_if_failure(strcmp(buffer, splittedTime) == 0, splittedTime); -} - -Expression extract_unit(const char * expression) { +void assert_additional_results_compute_to(const char * expression, const char * * results, int length, Preferences::UnitFormat unitFormat = Metric) { Shared::GlobalContext globalContext; - ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion); - Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); - Expression unit; - e.removeUnit(&unit); - return unit; + constexpr int maxNumberOfResults = 5; + assert(length <= maxNumberOfResults); + Expression additional[maxNumberOfResults]; + ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, unitFormat, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion); + Expression units; + Expression e = parse_expression(expression, &globalContext, false).reduceAndRemoveUnit(reductionContext, &units); + double value = e.approximateToScalar(&globalContext, Cartesian, Degree); + + if (!Unit::ShouldDisplayAdditionalOutputs(value, units, unitFormat)) { + quiz_assert(length == 0); + return; + } + const int numberOfResults = Unit::SetAdditionalExpressions(units, value, additional, maxNumberOfResults, reductionContext); + + quiz_assert(numberOfResults == length); + for (int i = 0; i < length; i++) { + assert_expression_serialize_to(additional[i], results[i], Preferences::PrintFloatMode::Decimal); + } } -QUIZ_CASE(poincare_expression_unit_helper) { - // 1. Time - Expression s = extract_unit("_s"); - quiz_assert(s.type() == ExpressionNode::Type::Unit && static_cast(s).isSecond()); - quiz_assert(!static_cast(s).isMeter()); +QUIZ_CASE(poincare_expression_additional_results) { + // Time + assert_additional_results_compute_to("3×_s", nullptr, 0); + const char * array1[1] = {"1×_min+1×_s"}; + assert_additional_results_compute_to("61×_s", array1, 1); + const char * array2[1] = {"1×_day+10×_h+17×_min+36×_s"}; + assert_additional_results_compute_to("123456×_s", array2, 1); + const char * array3[1] = {"7×_day"}; + assert_additional_results_compute_to("1×_week", array3, 1); - Shared::GlobalContext globalContext; - assert_seconds_split_to(1234567890, "39×_year+1×_month+13×_day+19×_h+1×_min+30×_s", &globalContext, Cartesian, Degree); - assert_seconds_split_to(-122, "-2×_min-2×_s", &globalContext, Cartesian, Degree); + // Distance + const char * array4[1] = {"19×_mi+853×_yd+1×_ft+7×_in"}; + assert_additional_results_compute_to("1234567×_in", array4, 1, Imperial); + const char * array5[1] = {"1×_yd+7.700787×_in"}; + assert_additional_results_compute_to("1.11×_m", array5, 1, Imperial); + assert_additional_results_compute_to("1.11×_m", nullptr, 0, Metric); - // 2. Speed - Expression meterPerSecond = extract_unit("_m×_s^-1"); - quiz_assert(Unit::IsSISpeed(meterPerSecond)); + // Masses + const char * array6[1] = {"1×_shtn+240×_lb"}; + assert_additional_results_compute_to("1×_lgtn", array6, 1, Imperial); + const char * array7[1] = {"2×_lb+3.273962×_oz"}; + assert_additional_results_compute_to("1×_kg", array7, 1, Imperial); + assert_additional_results_compute_to("1×_kg", nullptr, 0, Metric); - // 3. Volume - Expression meter3 = extract_unit("_m^3"); - quiz_assert(Unit::IsSIVolume(meter3)); + // Temperatures + const char * array14[2] = {"-273.15×_°C", "-459.67×_°F"}; + assert_additional_results_compute_to("0×_K", array14, 2, Metric); + const char * array15[2] = {"-279.67×_°F", "-173.15×_°C"}; + assert_additional_results_compute_to("100×_K", array15, 2, Imperial); + const char * array16[2] = {"12.02×_°F", "262.05×_K"}; + assert_additional_results_compute_to("-11.1×_°C", array16, 2); + const char * array17[2] = {"-20×_°C", "253.15×_K"}; + assert_additional_results_compute_to("-4×_°F", array17, 2); - // 4. Energy - Expression kilogramMeter2PerSecond2 = extract_unit("_kg×_m^2×_s^-2"); - quiz_assert(Unit::IsSIEnergy(kilogramMeter2PerSecond2)); - Expression kilogramMeter3PerSecond2 = extract_unit("_kg×_m^3×_s^-2"); - quiz_assert(!Unit::IsSIEnergy(kilogramMeter3PerSecond2)); + // Energy + const char * array8[3] = {"3.6×_MJ", "1×_kW×_h", "2.246943ᴇ13×_TeV"}; + assert_additional_results_compute_to("3.6×_MN_m", array8, 3); - // 5. International System - quiz_assert(Unit::IsSI(kilogramMeter2PerSecond2)); - quiz_assert(Unit::IsSI(meter3)); - quiz_assert(Unit::IsSI(meterPerSecond)); - Expression joule = extract_unit("_J"); - quiz_assert(!Unit::IsSI(joule)); + // Volume + const char * array9[2] = {"264×_gal+1×_pt+0.7528377×_cup", "1000×_L"}; + assert_additional_results_compute_to("1×_m^3", array9, 2, Imperial); + const char * array10[2] = {"48×_gal+1×_pt+1.5625×_cup", "182.5426×_L"}; + assert_additional_results_compute_to("12345×_tbsp", array10, 2, Imperial); + const char * array11[2] = {"182.5426×_L"}; + assert_additional_results_compute_to("12345×_tbsp", array11, 1, Metric); + + // Speed + const char * array12[1] = {"3.6×_km×_h^\x12-1\x13"}; + assert_additional_results_compute_to("1×_m/_s", array12, 1, Metric); + const char * array13[2] = {"2.236936×_mi×_h^\x12-1\x13", "3.6×_km×_h^\x12-1\x13"}; + assert_additional_results_compute_to("1×_m/_s", array13, 2, Imperial); } diff --git a/poincare/test/function_solver.cpp b/poincare/test/function_solver.cpp index 4dee3c65f..11274a2b0 100644 --- a/poincare/test/function_solver.cpp +++ b/poincare/test/function_solver.cpp @@ -236,9 +236,15 @@ QUIZ_CASE(poincare_function_root) { { constexpr int numberOfRoots = 1; Coordinate2D roots[numberOfRoots] = { - Coordinate2D(99.8, 0.0)}; + Coordinate2D(99.9, 0.0)}; assert_points_of_interest_are(PointOfInterestType::Root, numberOfRoots, roots, "0", nullptr, "a", 100.0, -0.1, -1.0); } + { + constexpr int numberOfRoots = 1; + Coordinate2D roots[numberOfRoots] = { + Coordinate2D(NAN, 0.0)}; + assert_points_of_interest_are(PointOfInterestType::Root, numberOfRoots, roots, "ℯ^x", nullptr, "a", -1000.0, 0.1, -800); + } } QUIZ_CASE(poincare_function_intersection) { diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index 399cb589f..ba8298bc8 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -37,10 +37,10 @@ void quiz_assert_log_if_failure(bool test, TreeHandle tree) { quiz_assert(test); } -void assert_parsed_expression_process_to(const char * expression, const char * result, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits) { +void assert_parsed_expression_process_to(const char * expression, const char * result, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - Expression m = process(e, ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target, symbolicComputation, unitConversion)); + Expression m = process(e, ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, target, symbolicComputation, unitConversion)); constexpr int bufferSize = 500; char buffer[bufferSize]; m.serialize(buffer, bufferSize, DecimalMode, numberOfSignifiantDigits); @@ -76,23 +76,23 @@ Poincare::Expression parse_expression(const char * expression, Context * context return result; } -void assert_reduce(const char * expression, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target) { +void assert_reduce(const char * expression, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - assert_expression_reduce(e, angleUnit, complexFormat, target, expression); + assert_expression_reduce(e, angleUnit, unitFormat, complexFormat, target, expression); } -void assert_expression_reduce(Expression e, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target, const char * printIfFailure) { +void assert_expression_reduce(Expression e, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target, const char * printIfFailure) { Shared::GlobalContext globalContext; - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target)); + e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, unitFormat, target)); quiz_assert_print_if_failure(!(e.isUninitialized()), printIfFailure); } -void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, ExpressionNode::ReductionTarget target, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { - assert_parsed_expression_process_to(expression, simplifiedExpression, target, complexFormat, angleUnit, symbolicComputation, unitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { +void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, ExpressionNode::ReductionTarget target, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, Preferences::ComplexFormat complexFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { + assert_parsed_expression_process_to(expression, simplifiedExpression, target, complexFormat, angleUnit, unitFormat, symbolicComputation, unitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { Expression copy = e.clone(); if (reductionContext.target() == ExpressionNode::ReductionTarget::User) { - copy.simplifyAndApproximate(©, nullptr, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.symbolicComputation(), reductionContext.unitConversion()); + copy.simplifyAndApproximate(©, nullptr, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.unitFormat(), reductionContext.symbolicComputation(), reductionContext.unitConversion()); } else { copy = copy.simplify(reductionContext); } @@ -103,30 +103,45 @@ void assert_parsed_expression_simplify_to(const char * expression, const char * }); } +bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference) { + if (expectedValue != 0.0) { + double relativeError = std::fabs((observedValue - expectedValue) / expectedValue); + // The relative error must be smaller than the precision + return relativeError <= precision; + } + if (reference != 0.0) { + double referenceRatio = std::fabs(observedValue / reference); + // The observedValue must be negligible against the reference + return referenceRatio <= precision; + } + // The observedValue must exactly match the expectedValue + return observedValue == expectedValue; +} + template -void assert_expression_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { +void assert_expression_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, 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, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, unitFormat, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { return e.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); }, numberOfDigits); } -void assert_expression_simplifies_and_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { +void assert_expression_simplifies_and_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { int numberOfDigits = numberOfSignificantDigits > 0 ? numberOfSignificantDigits : PrintFloat::k_numberOfStoredSignificantDigits; - assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, unitFormat, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { Expression reduced; Expression approximated; - e.simplifyAndApproximate(&reduced, &approximated, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.symbolicComputation()); + e.simplifyAndApproximate(&reduced, &approximated, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.unitFormat(), reductionContext.symbolicComputation()); return approximated; }, numberOfDigits); } template -void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { +void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, 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, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, unitFormat, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { e = e.simplify(reductionContext); return e.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); }, numberOfDigits); @@ -151,7 +166,7 @@ void assert_expression_layouts_as(Poincare::Expression expression, Poincare::Lay quiz_assert(l.isIdenticalTo(layout)); } -template void assert_expression_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::ComplexFormat, int); -template void assert_expression_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::ComplexFormat, int); -template void assert_expression_simplifies_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::ComplexFormat, int); -template void assert_expression_simplifies_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::ComplexFormat, int); +template void assert_expression_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::UnitFormat, Poincare::Preferences::ComplexFormat, int); +template void assert_expression_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::UnitFormat, Poincare::Preferences::ComplexFormat, int); +template void assert_expression_simplifies_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::UnitFormat, Poincare::Preferences::ComplexFormat, int); +template void assert_expression_simplifies_approximates_to(char const*, char const *, Poincare::Preferences::AngleUnit, Poincare::Preferences::UnitFormat, Poincare::Preferences::ComplexFormat, int); diff --git a/poincare/test/helper.h b/poincare/test/helper.h index afb122eb1..13ba89acc 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -19,6 +19,8 @@ constexpr Poincare::ExpressionNode::UnitConversion InternationalSystemUnitConver constexpr Poincare::Preferences::AngleUnit Degree = Poincare::Preferences::AngleUnit::Degree; constexpr Poincare::Preferences::AngleUnit Radian = Poincare::Preferences::AngleUnit::Radian; constexpr Poincare::Preferences::AngleUnit Gradian = Poincare::Preferences::AngleUnit::Gradian; +constexpr Poincare::Preferences::UnitFormat Metric = Poincare::Preferences::UnitFormat::Metric; +constexpr Poincare::Preferences::UnitFormat Imperial = Poincare::Preferences::UnitFormat::Imperial; constexpr Poincare::Preferences::ComplexFormat Cartesian = Poincare::Preferences::ComplexFormat::Cartesian; constexpr Poincare::Preferences::ComplexFormat Polar = Poincare::Preferences::ComplexFormat::Polar; constexpr Poincare::Preferences::ComplexFormat Real = Poincare::Preferences::ComplexFormat::Real; @@ -31,7 +33,7 @@ void quiz_assert_log_if_failure(bool test, Poincare::TreeHandle tree); typedef Poincare::Expression (*ProcessExpression)(Poincare::Expression, Poincare::ExpressionNode::ReductionContext reductionContext); -void assert_parsed_expression_process_to(const char * expression, const char * result, Poincare::ExpressionNode::ReductionTarget target, Poincare::Preferences::ComplexFormat complexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ExpressionNode::SymbolicComputation symbolicComputation, Poincare::ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); +void assert_parsed_expression_process_to(const char * expression, const char * result, Poincare::ExpressionNode::ReductionTarget target, Poincare::Preferences::ComplexFormat complexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::Preferences::UnitFormat unitFormat, Poincare::ExpressionNode::SymbolicComputation symbolicComputation, Poincare::ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); // Parsing @@ -39,19 +41,23 @@ Poincare::Expression parse_expression(const char * expression, Poincare::Context // Simplification -void assert_reduce(const char * expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User); +void assert_reduce(const char * expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User); -void assert_expression_reduce(Poincare::Expression expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User, const char * printIfFailure = "Error"); +void assert_expression_reduce(Poincare::Expression expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User, const char * printIfFailure = "Error"); -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, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = DefaultUnitConversion); + +void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, Poincare::ExpressionNode::ReductionTarget target = User, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = DefaultUnitConversion); // Approximation +/* Return true if observedValue and expectedValue are approximately equal, + * according to precision and reference parameters */ +bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference); template -void assert_expression_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); -void assert_expression_simplifies_and_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); +void assert_expression_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); +void assert_expression_simplifies_and_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); template -void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); +void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::UnitFormat unitFormat = Metric, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); // Expression serializing diff --git a/poincare/test/ieee754.cpp b/poincare/test/ieee754.cpp deleted file mode 100644 index 85bdee9d4..000000000 --- a/poincare/test/ieee754.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include -#include -#include "helper.h" - -using namespace Poincare; - -template -void assert_next_and_previous_IEEE754_is(T a, T b) { - T next = IEEE754::next(a); - T previous = IEEE754::previous(b); - quiz_assert((std::isnan(next) && std::isnan(b)) || next == b); - quiz_assert((std::isnan(previous) && std::isnan(a)) || previous == a); -} - -QUIZ_CASE(ieee754_next_and_previous) { - assert_next_and_previous_IEEE754_is(0.0f, 1.4E-45f); - assert_next_and_previous_IEEE754_is(INFINITY, INFINITY); - assert_next_and_previous_IEEE754_is(NAN, NAN); - assert_next_and_previous_IEEE754_is(2.2566837E-10f, 2.2566839E-10f); - assert_next_and_previous_IEEE754_is(-INFINITY, -INFINITY); - assert_next_and_previous_IEEE754_is(-0.0f, 0.0f); - assert_next_and_previous_IEEE754_is(-1.4E-45f, -0.0f); - assert_next_and_previous_IEEE754_is(-3.4359738E10f, -3.43597363E10f); - quiz_assert(IEEE754::next(3.4028235E38f) == INFINITY); - quiz_assert(IEEE754::previous(-3.4028235E38f) == -INFINITY); - - assert_next_and_previous_IEEE754_is(0.0f, 4.94065645841246544176568792868E-324); - assert_next_and_previous_IEEE754_is(INFINITY, INFINITY); - assert_next_and_previous_IEEE754_is(NAN, NAN); - assert_next_and_previous_IEEE754_is(1.936766735060658315512927142E-282, 1.93676673506065869777770528092E-282); - assert_next_and_previous_IEEE754_is(-INFINITY, -INFINITY); - assert_next_and_previous_IEEE754_is(-0.0, 0.0); - assert_next_and_previous_IEEE754_is(-4.94065645841246544176568792868E-324, -0.0); - assert_next_and_previous_IEEE754_is(-1.38737906372912431085182213247E201, -1.38737906372912403890916981028E201); - quiz_assert(IEEE754::next(1.79769313486231570814527423732E308) == INFINITY); - quiz_assert(IEEE754::previous(-1.79769313486231570814527423732E308) == -INFINITY); - -} diff --git a/poincare/test/integer.cpp b/poincare/test/integer.cpp index 0a093c132..f653b887c 100644 --- a/poincare/test/integer.cpp +++ b/poincare/test/integer.cpp @@ -277,3 +277,29 @@ QUIZ_CASE(poincare_integer_serialize) { assert_integer_serializes_to(MaxInteger(), MaxIntegerString()); assert_integer_serializes_to(OverflowedInteger(), Infinity::Name()); } + +// Euclidian Division + +void assert_division_computes_to(int n, int m, const char * div) { + assert_expression_serialize_to(Integer::CreateEuclideanDivision(Integer(n), Integer(m)), div); +} + +QUIZ_CASE(poincare_integer_euclidian_division) { + assert_division_computes_to(47, 8, "47=8×5+7"); + assert_division_computes_to(1, 5, "1=5×0+1"); + assert_division_computes_to(12, 4, "12=4×3+0"); + assert_division_computes_to(-33, 7, "-33=7×(-5)+2"); + assert_division_computes_to(-28, 101, "-28=101×(-1)+73"); + assert_division_computes_to(-40, 2, "-40=2×(-20)+0"); +} + +void assert_mixed_fraction_computes_to(int n, int m, const char * frac) { + assert_expression_serialize_to(Integer::CreateMixedFraction(Integer(n), Integer(m)), frac); +} + +QUIZ_CASE(poincare_integer_mixed_fraction) { + assert_mixed_fraction_computes_to(47, 8, "5+7/8"); + assert_mixed_fraction_computes_to(1, 5, "0+1/5"); + assert_mixed_fraction_computes_to(-33, 7, "-4-5/7"); + assert_mixed_fraction_computes_to(-28, 101, "0-28/101"); +} diff --git a/poincare/test/layout.cpp b/poincare/test/layout.cpp index 983184abb..573ebf8cc 100644 --- a/poincare/test/layout.cpp +++ b/poincare/test/layout.cpp @@ -76,6 +76,70 @@ QUIZ_CASE(poincare_layout_fraction_create) { cursor.addFractionLayoutAndCollapseSiblings(); assert_layout_serialize_to(layout, "\u0012\u001212\u0013/\u001234\u0013\u0013+5"); quiz_assert(cursor.isEquivalentTo(LayoutCursor(layout.childAtIndex(0).childAtIndex(1), LayoutCursor::Position::Left))); + + /* + * 1 1 3 + * --- 3|4 -> "Divide" -> --- --- + * 2 2 4 + * */ + Layout l1 = HorizontalLayout::Builder( + FractionLayout::Builder( + HorizontalLayout::Builder(CodePointLayout::Builder('1')), + HorizontalLayout::Builder(CodePointLayout::Builder('2'))), + CodePointLayout::Builder('3'), + CodePointLayout::Builder('4')); + LayoutCursor c1(l1.childAtIndex(2), LayoutCursor::Position::Left); + c1.addFractionLayoutAndCollapseSiblings(); + assert_layout_serialize_to(l1, "\u0012\u00121\u0013/\u00122\u0013\u0013\u0012\u00123\u0013/\u00124\u0013\u0013"); + + /* + * sin(x)cos(x) + * sin(x)cos(x)|2 -> "Divide" -> -------------- + * 2 + * */ + + Layout l2 = LayoutHelper::String("sin(x)cos(x)2", 13); + LayoutCursor c2(l2.childAtIndex(12), LayoutCursor::Position::Left); + c2.addFractionLayoutAndCollapseSiblings(); + assert_layout_serialize_to(l2, "\u0012\u0012sin(x)cos(x)\u0013/\u00122\u0013\u0013"); +} + +QUIZ_CASE(poincare_layout_power) { + /* + * 2| + * 12| -> "Square" -> 12 | + * + * */ + Layout l1 = LayoutHelper::String("12", 2); + LayoutCursor c1(l1.childAtIndex(1), LayoutCursor::Position::Right); + c1.addEmptySquarePowerLayout(); + assert_layout_serialize_to(l1, "12^\u00122\u0013"); + + /* 2| + * 2| ( 2) | + * 1 | -> "Square" -> (1 ) | + * + * */ + Layout l2 = HorizontalLayout::Builder( + CodePointLayout::Builder('1'), + VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Position::Superscript)); + LayoutCursor c2(l2.childAtIndex(1), LayoutCursor::Position::Right); + c2.addEmptySquarePowerLayout(); + assert_layout_serialize_to(l2, "(1^\u00122\u0013)^\u00122\u0013"); + + /* ( 2|) + * ( 2)| (( 2) |) + * (1 )| -> "Left" "Square" -> ((1 ) |) + * */ + Layout l3 = HorizontalLayout::Builder( + LeftParenthesisLayout::Builder(), + CodePointLayout::Builder('1'), + VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Position::Superscript), + RightParenthesisLayout::Builder()); + LayoutCursor c3(l3.childAtIndex(3), LayoutCursor::Position::Right); + c3.moveLeft(nullptr); + c3.addEmptySquarePowerLayout(); + assert_layout_serialize_to(l3, "((1^\u00122\u0013)^\u00122\u0013)"); } QUIZ_CASE(poincare_layout_parentheses_size) { diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index 4ee1a88e1..49bd40c2f 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -286,18 +286,19 @@ QUIZ_CASE(poincare_parsing_matrices) { QUIZ_CASE(poincare_parsing_units) { // Units - for (const Unit::Dimension * dim = Unit::DimensionTable; dim < Unit::DimensionTableUpperBound; dim++) { - for (const Unit::Representative * rep = dim->stdRepresentative(); rep < dim->representativesUpperBound(); rep++) { + for (int i = 0; i < Unit::Representative::k_numberOfDimensions; i++) { + const Unit::Representative * dim = Unit::Representative::DefaultRepresentatives()[i]; + for (int j = 0; j < dim->numberOfRepresentatives(); j++) { + const Unit::Representative * rep = dim->representativesOfSameDimension() + j; static constexpr size_t bufferSize = 10; char buffer[bufferSize]; - Unit::Builder(dim, rep, &Unit::EmptyPrefix).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); + Unit::Builder(rep, Unit::Prefix::EmptyPrefix()).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); Expression unit = parse_expression(buffer, nullptr, false); quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit"); - if (rep->isPrefixable()) { - size_t numberOfPrefixes = sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix *); - for (size_t i = 0; i < numberOfPrefixes; i++) { - const Unit::Prefix * pre = Unit::AllPrefixes[i]; - Unit::Builder(dim, rep, pre).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); + if (rep->isInputPrefixable()) { + for (size_t i = 0; i < Unit::Prefix::k_numberOfPrefixes; i++) { + const Unit::Prefix * pre = Unit::Prefix::Prefixes(); + Unit::Builder(rep, pre).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); Expression unit = parse_expression(buffer, nullptr, false); quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit"); } @@ -365,30 +366,37 @@ QUIZ_CASE(poincare_parsing_identifiers) { assert_parsed_expression_is("binomial(2,1)", BinomialCoefficient::Builder(BasedInteger::Builder(2),BasedInteger::Builder(1))); assert_parsed_expression_is("ceil(1)", Ceiling::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("confidence(1,2)", ConfidenceInterval::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); + assert_parsed_expression_is("cross(1,1)", VectorCross::Builder(BasedInteger::Builder(1),BasedInteger::Builder(1))); assert_text_not_parsable("diff(1,2,3)"); assert_text_not_parsable("diff(0,_s,0)"); assert_parsed_expression_is("diff(1,x,3)", Derivative::Builder(BasedInteger::Builder(1),Symbol::Builder("x",1),BasedInteger::Builder(3))); assert_parsed_expression_is("dim(1)", MatrixDimension::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("conj(1)", Conjugate::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("det(1)", Determinant::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("dot(1,1)", VectorDot::Builder(BasedInteger::Builder(1),BasedInteger::Builder(1))); assert_parsed_expression_is("cos(1)", Cosine::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("cosh(1)", HyperbolicCosine::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("factor(1)", Factor::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("floor(1)", Floor::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("frac(1)", FracPart::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("gcd(1,2)", GreatCommonDivisor::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); + assert_parsed_expression_is("gcd(1,2,3)", GreatCommonDivisor::Builder({BasedInteger::Builder(1),BasedInteger::Builder(2),BasedInteger::Builder(3)})); + assert_text_not_parsable("gcd(1)"); + assert_text_not_parsable("gcd()"); assert_parsed_expression_is("im(1)", ImaginaryPart::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("int(1,x,2,3)", Integral::Builder(BasedInteger::Builder(1),Symbol::Builder("x",1),BasedInteger::Builder(2),BasedInteger::Builder(3))); assert_text_not_parsable("int(1,2,3,4)"); assert_text_not_parsable("int(1,_s,3,4)"); assert_parsed_expression_is("inverse(1)", MatrixInverse::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("lcm(1,2)", LeastCommonMultiple::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); + assert_parsed_expression_is("lcm(1,2,3)", LeastCommonMultiple::Builder({BasedInteger::Builder(1),BasedInteger::Builder(2),BasedInteger::Builder(3)})); + assert_text_not_parsable("lcm(1)"); + assert_text_not_parsable("lcm()"); assert_parsed_expression_is("ln(1)", NaperianLogarithm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("log(1)", CommonLogarithm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("log(1,2)", Logarithm::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("log{2}(1)", Logarithm::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("permute(2,1)", PermuteCoefficient::Builder(BasedInteger::Builder(2),BasedInteger::Builder(1))); assert_parsed_expression_is("prediction95(1,2)", PredictionInterval::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); + assert_parsed_expression_is("norm(1)", VectorNorm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("prediction(1,2)", SimplePredictionInterval::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("product(1,n,2,3)", Product::Builder(BasedInteger::Builder(1),Symbol::Builder("n",1),BasedInteger::Builder(2),BasedInteger::Builder(3))); assert_text_not_parsable("product(1,2,3,4)"); @@ -397,9 +405,11 @@ QUIZ_CASE(poincare_parsing_identifiers) { assert_parsed_expression_is("random()", Random::Builder()); assert_parsed_expression_is("randint(1,2)", Randint::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("re(1)", RealPart::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("ref(1)", MatrixRowEchelonForm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("rem(1,2)", DivisionRemainder::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("root(1,2)", NthRoot::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); assert_parsed_expression_is("round(1,2)", Round::Builder(BasedInteger::Builder(1),BasedInteger::Builder(2))); + assert_parsed_expression_is("rref(1)", MatrixReducedRowEchelonForm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("sin(1)", Sine::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("sign(1)", SignFunction::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("sinh(1)", HyperbolicSine::Builder(BasedInteger::Builder(1))); diff --git a/poincare/test/print_float.cpp b/poincare/test/print_float.cpp index 56c0aca6f..f9ac40c0b 100644 --- a/poincare/test/print_float.cpp +++ b/poincare/test/print_float.cpp @@ -30,6 +30,7 @@ QUIZ_CASE(assert_print_floats) { assert_float_prints_to(123.456f, "1.23456ᴇ2", ScientificMode, 7); assert_float_prints_to(123.456f, "123.456", DecimalMode, 7); assert_float_prints_to(123.456f, "123.456", EngineeringMode, 7); + assert_float_prints_to(0.0006f, "0.0006", DecimalMode, 7); assert_float_prints_to(123.456, "1.23456ᴇ2", ScientificMode, 14); assert_float_prints_to(123.456, "123.456", DecimalMode, 14); assert_float_prints_to(123.456, "123.456", EngineeringMode, 14); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 64175b73c..173f01643 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -67,6 +67,7 @@ QUIZ_CASE(poincare_simplification_infinity) { assert_parsed_expression_simplify_to("0/inf", "0"); assert_parsed_expression_simplify_to("inf/0", Undefined::Name()); assert_parsed_expression_simplify_to("0×inf", Undefined::Name()); + assert_parsed_expression_simplify_to("0×inf×π", Undefined::Name()); assert_parsed_expression_simplify_to("3×inf/inf", "undef"); assert_parsed_expression_simplify_to("1ᴇ1000", "inf"); assert_parsed_expression_simplify_to("-1ᴇ1000", "-inf"); @@ -105,7 +106,7 @@ QUIZ_CASE(poincare_simplification_infinity) { } QUIZ_CASE(poincare_simplification_addition) { - assert_parsed_expression_simplify_to("1/x^2+3", "\u00123×x^2+1\u0013/x^2", User, Radian, Real); + assert_parsed_expression_simplify_to("1/x^2+3", "\u00123×x^2+1\u0013/x^2", User, Radian, Metric, Real); assert_parsed_expression_simplify_to("1+x", "x+1"); assert_parsed_expression_simplify_to("1/2+1/3+1/4+1/5+1/6+1/7", "223/140"); assert_parsed_expression_simplify_to("1+x+4-i-2x", "-i-x+5"); @@ -193,6 +194,44 @@ QUIZ_CASE(poincare_simplification_multiplication) { assert_parsed_expression_simplify_to("[[1,2][3,4]]×[[1,3][5,6]]×[[2,3][4,6]]", "[[82,123][178,267]]"); assert_parsed_expression_simplify_to("π×confidence(π/5,3)[[1,2]]", "π×confidence(π/5,3)×[[1,2]]"); assert_parsed_expression_simplify_to("0*[[1,0][0,1]]^500", "0×[[1,0][0,1]]^500"); + assert_parsed_expression_simplify_to("x^5/x^3", "x^2"); + assert_parsed_expression_simplify_to("x^5*x^3", "x^8"); + assert_parsed_expression_simplify_to("x^3/x^5", "1/x^2"); + assert_parsed_expression_simplify_to("x^0", "1"); + assert_parsed_expression_simplify_to("π^5/π^3", "π^2", SystemForAnalysis); + assert_parsed_expression_simplify_to("π^5*π^3", "π^8", SystemForAnalysis); + assert_parsed_expression_simplify_to("π^3/π^5", "1/π^2", SystemForAnalysis); + assert_parsed_expression_simplify_to("π^0", "1", SystemForAnalysis); + assert_parsed_expression_simplify_to("π^π/π^(π-1)", "π", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^5/x^3", "x^5/x^3", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^5×x^3", "x^8", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^3/x^5", "1/x^2", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^0", "x^0", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^π/x^(π-1)", "x^π×x^\u0012-π+1\u0013", SystemForAnalysis); + assert_parsed_expression_simplify_to("x^π/x^(π+1)", "1/x", SystemForAnalysis); + assert_parsed_expression_simplify_to("2^x×2^(-x)", "1", SystemForAnalysis); + assert_parsed_expression_simplify_to("y^x×y^(-x)", "y^0", SystemForAnalysis); + assert_parsed_expression_simplify_to("x/√(x)", "x/√(x)", SystemForAnalysis); +} + +void assert_parsed_unit_simplify_to_with_prefixes(const Unit::Representative * representative) { + int numberOfPrefixes; + const Unit::Prefix * prefixes; + static constexpr size_t bufferSize = 12; + char buffer[bufferSize] = "1×"; + if (representative->isOutputPrefixable()) { + numberOfPrefixes = Unit::Prefix::k_numberOfPrefixes; + prefixes = Unit::k_prefixes; + } else { + numberOfPrefixes = 1; + prefixes = Unit::Prefix::EmptyPrefix(); + } + for (int i = 0; i < numberOfPrefixes; i++) { + if (representative->canPrefix(prefixes + i, true) && representative->canPrefix(prefixes + i, false)) { + Unit::Builder(representative, prefixes + i).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); + assert_parsed_expression_simplify_to(buffer, buffer); + } + } } QUIZ_CASE(poincare_simplification_units) { @@ -215,6 +254,33 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_mol^-1", "1×_mol^\u0012-1\u0013"); assert_parsed_expression_simplify_to("_cd^-1", "1×_cd^\u0012-1\u0013"); + /* Power of SI units */ + assert_parsed_expression_simplify_to("_s^3", "1×_s^3"); + assert_parsed_expression_simplify_to("_m^2", "1×_m^2"); + assert_parsed_expression_simplify_to("_m^3", "1×_m^3"); + assert_parsed_expression_simplify_to("_m^(1/2)", "1×_m^\u00121/2\u0013"); + + /* Possible improvements */ + /* Ignored derived metrics : + * -> Possible solution : Favor unities from user input. We do not want to + * favor positive exponents to avoid a Velocity being displayed as _m*_Hz + * assert_parsed_expression_simplify_to("_Hz", "_Hz"); + * assert_parsed_expression_simplify_to("_S", "_S"); + */ + /* Non unitary exponents on Derived metrics : + * -> See CanSimplifyUnitProduct in multiplication.cpp + * assert_parsed_expression_simplify_to("_C^3", "1×_C^3"); + * assert_parsed_expression_simplify_to("_N^(1/2)", "1×_N^\u00121/2\u0013"); + */ + /* Taking exponents complexity into account : + * -> See note on metrics in CanSimplifyUnitProduct in multiplication.cpp + * assert_parsed_expression_simplify_to("_C×_s", "1×_C×_s"); + * assert_parsed_expression_simplify_to("_C^10", "1×_C^10"); + * assert_parsed_expression_simplify_to("_ha", "1×_ha"); + * FIXME : int8_t norm metric overflow, only visible with a non constant norm + * assert_parsed_expression_simplify_to("_C^130", "1×_C^130"); */ + assert_parsed_expression_simplify_to("_m_s^-2", "1×_m×_s^\u0012-2\u0013"); + /* SI derived units with special names and symbols */ assert_parsed_expression_simplify_to("_kg×_m×_s^(-2)", "1×_N"); assert_parsed_expression_simplify_to("_kg×_m^(-1)×_s^(-2)", "1×_Pa"); @@ -224,44 +290,102 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-3)×_A^(-1)", "1×_V"); assert_parsed_expression_simplify_to("_m^(-2)×_kg^(-1)×_s^4×_A^2", "1×_F"); assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-3)×_A^(-2)", "1×_Ω"); - // FIXME _S should not be simplified to _Ω^(-1) - // A possible solution: a unit with exponent +1 is simpler than a unit with exponent -1. - // The same should probably go for Hz. - // assert_parsed_expression_simplify_to("_kg^(-1)×_m^(-2)×_s^3×_A^2", "1×_S"); assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-2)×_A^(-1)", "1×_Wb"); assert_parsed_expression_simplify_to("_kg×_s^(-2)×_A^(-1)", "1×_T"); assert_parsed_expression_simplify_to("_kg×_m^2×_s^(-2)×_A^(-2)", "1×_H"); assert_parsed_expression_simplify_to("_mol×_s^-1", "1×_kat"); + /* Displayed order of magnitude */ + assert_parsed_expression_simplify_to("100_kg", "100×_kg"); + assert_parsed_expression_simplify_to("1_min", "1×_min"); + assert_parsed_expression_simplify_to("0.1_m", "1×_dm"); + assert_parsed_expression_simplify_to("180_MΩ", "180×_MΩ"); + assert_parsed_expression_simplify_to("180_MH", "180×_MH"); + /* Test simplification of all possible (prefixed) unit symbols. * Some symbols are however excluded: * - At present, some units will not appear as simplification output: * t, Hz, S, ha, L. These exceptions are tested below. */ - for (const Unit::Dimension * dim = Unit::DimensionTable; dim < Unit::DimensionTableUpperBound; dim++) { - for (const Unit::Representative * rep = dim->stdRepresentative(); rep < dim->representativesUpperBound(); rep++) { - if (strcmp(rep->rootSymbol(), "t") == 0 || strcmp(rep->rootSymbol(), "Hz") == 0 || strcmp(rep->rootSymbol(), "S") == 0 || strcmp(rep->rootSymbol(), "ha") == 0 || strcmp(rep->rootSymbol(), "L") == 0) { - continue; - } - static constexpr size_t bufferSize = 12; - char buffer[bufferSize] = "1×"; - Unit::Builder(dim, rep, &Unit::EmptyPrefix).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); - assert_parsed_expression_simplify_to(buffer, buffer); - if (rep->isPrefixable()) { - for (size_t i = 0; i < rep->outputPrefixesLength(); i++) { - const Unit::Prefix * pre = rep->outputPrefixes()[i]; - Unit::Builder(dim, rep, pre).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); - assert_parsed_expression_simplify_to(buffer, buffer); - } - } - } - } + assert_parsed_expression_simplify_to("_s", "1×_s"); + assert_parsed_expression_simplify_to("_min", "1×_min"); + assert_parsed_expression_simplify_to("_h", "1×_h"); + assert_parsed_expression_simplify_to("_day", "1×_day"); + assert_parsed_expression_simplify_to("_week", "1×_week"); + assert_parsed_expression_simplify_to("_month", "1×_month"); + assert_parsed_expression_simplify_to("_year", "1×_year"); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_distanceRepresentatives); + assert_parsed_expression_simplify_to("_au", "1×_au"); + assert_parsed_expression_simplify_to("_ly", "1×_ly"); + assert_parsed_expression_simplify_to("_pc", "1×_pc"); + assert_parsed_expression_simplify_to("_in", "1×_in", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_ft", "1×_ft", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_yd", "1×_yd", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_mi", "1×_mi", User, Radian, Imperial); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_massRepresentatives); + assert_parsed_expression_simplify_to("_oz", "1×_oz", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_lb", "1×_lb", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_shtn", "1×_shtn", User, Radian, Imperial); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_currentRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_temperatureRepresentatives); + assert_parsed_expression_simplify_to("_°C", "1×_°C"); + assert_parsed_expression_simplify_to("_°F", "1×_°F"); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_amountOfSubstanceRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_luminousIntensityRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_forceRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_pressureRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_pressureRepresentatives + 1); + assert_parsed_expression_simplify_to("_atm", "1×_atm"); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_energyRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_energyRepresentatives + 1); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_powerRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricChargeRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricPotentialRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricCapacitanceRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_electricResistanceRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_magneticFieldRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_magneticFluxRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_inductanceRepresentatives); + assert_parsed_unit_simplify_to_with_prefixes(Unit::k_catalyticActivityRepresentatives); - /* Units that do not appear as output yet */ - assert_parsed_expression_simplify_to("_t", "1×_Mg"); + /* Units that do not appear as output */ + assert_parsed_expression_simplify_to("_t", "1000×_kg"); assert_parsed_expression_simplify_to("_Hz", "1×_s^\u0012-1\u0013"); assert_parsed_expression_simplify_to("_S", "1×_Ω^\u0012-1\u0013"); - assert_parsed_expression_simplify_to("_L", "0.001×_m^3"); - assert_parsed_expression_simplify_to("_ha", "0.01×_km^2"); + assert_parsed_expression_simplify_to("_L", "1×_dm^3"); + assert_parsed_expression_simplify_to("_ha", "10000×_m^2"); + + /* Imperial units */ + assert_parsed_expression_simplify_to("_lgtn", "1016.0469088×_kg"); + assert_parsed_expression_simplify_to("_lgtn", "1.12×_shtn", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_in", "2.54×_cm"); + assert_parsed_expression_simplify_to("_in", "1×_in", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_ft", "1×_ft", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_yd", "1×_yd", User, Radian, Imperial); + assert_parsed_expression_simplify_to("1_qt", "1×_qt", User, Radian, Imperial); + assert_parsed_expression_simplify_to("1_qt", "946.352946×_cm^3"); + + /* Tests for non-absolute units */ + assert_parsed_expression_simplify_to("273.15×_K→_°C", "0×_°C"); + assert_parsed_expression_simplify_to("0×_°C", "0×_°C"); + assert_parsed_expression_simplify_to("-32×_°F", "-32×_°F"); + assert_parsed_expression_simplify_to("273.16×_K", "273.16×_K"); + assert_parsed_expression_simplify_to("_cKπ23", "0.72256631032565×_K"); + assert_parsed_expression_simplify_to("100×_°C→_K", "373.15×_K"); + assert_parsed_expression_simplify_to("-100×_°C→_K", "173.15×_K"); + assert_parsed_expression_simplify_to("_°C+_°C", Undefined::Name()); + assert_parsed_expression_simplify_to("_°C+_°F", Undefined::Name()); + assert_parsed_expression_simplify_to("_K+_°F", Undefined::Name()); + assert_parsed_expression_simplify_to("2*20_°F", Undefined::Name()); + assert_parsed_expression_simplify_to("_°C^2", Undefined::Name()); + assert_parsed_expression_simplify_to("1/(-3_°C)", Undefined::Name()); + assert_parsed_expression_simplify_to("-1×100×_°C→_K", Undefined::Name()); + + /* Rational exponents */ + assert_parsed_expression_simplify_to("√(_m)", "1×_m^\u00121/2\u0013"); + assert_parsed_expression_simplify_to("√(_N)", "1×_kg^\u00121/2\u0013×_m^\u00121/2\u0013×_s^\u0012-1\u0013"); + assert_parsed_expression_simplify_to("√(_N)", "1.5527410012845×_lb^\u00121/2\u0013×_yd^\u00121/2\u0013×_s^\u0012-1\u0013", User, Radian, Imperial); + assert_parsed_expression_simplify_to("_C^0.3", "1×_A^\u00123/10\u0013×_s^\u00123/10\u0013"); + assert_parsed_expression_simplify_to("_kat_kg^-2.8", "1×_mol×_kg^\u0012-14/5\u0013×_s^\u0012-1\u0013"); /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); @@ -308,18 +432,20 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_A^2×_s^4×_kg^(-1)×_m^(-3)", "1×_F×_m^\u0012-1\u0013"); // Vacuum magnetic permeability 𝝴0 assert_parsed_expression_simplify_to("_kg×_s^(-3)×_K^(-4)", "1×_K^\u0012-4\u0013×_kg×_s^\u0012-3\u0013"); // Stefan–Boltzmann constant _W×_m^-2×_K^-4 - /* Keep units for 0, infinity float results, Remove unit for undefined + /* Keep SI units for 0, infinity float results, Remove unit for undefined * expression */ assert_parsed_expression_simplify_to("0×_s", "0×_s"); + assert_parsed_expression_simplify_to("0×_tsp", "0×_m^3"); assert_parsed_expression_simplify_to("inf×_s", "inf×_s"); - assert_parsed_expression_simplify_to("-inf×_s", "-inf×_s"); + assert_parsed_expression_simplify_to("-inf×_oz", "-inf×_kg"); assert_parsed_expression_simplify_to("2_s+3_s-5_s", "0×_s"); - assert_parsed_expression_simplify_to("normcdf(0,20,3)×_s", "0×_s"); - assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); + assert_parsed_expression_simplify_to("normcdf(0,20,3)×_s", "13.083978345207×_ps"); + assert_parsed_expression_simplify_to("log(0)×_s", "undef"); assert_parsed_expression_simplify_to("log(undef)*_s", "undef"); /* Units with invalid exponent */ - assert_parsed_expression_simplify_to("_s^(1/2)", "undef"); + assert_parsed_expression_simplify_to("_s^(_s)", "undef"); + assert_parsed_expression_simplify_to("_s^(π)", "undef"); /* Inhomogeneous expressions */ assert_parsed_expression_simplify_to("1+_s", "undef"); @@ -350,16 +476,19 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("conj(_s)", "undef"); assert_parsed_expression_simplify_to("cos(_s)", "undef"); assert_parsed_expression_simplify_to("cosh(_s)", "undef"); + assert_parsed_expression_simplify_to("cross(_s,[[1][2][3]])", "undef"); assert_parsed_expression_simplify_to("det(_s)", "undef"); assert_parsed_expression_simplify_to("diff(_s,x,0)", "undef"); assert_parsed_expression_simplify_to("diff(0,x,_s)", "undef"); assert_parsed_expression_simplify_to("dim(_s)", "undef"); + assert_parsed_expression_simplify_to("dot(_s,[[1][2][3]])", "undef"); assert_parsed_expression_simplify_to("factor(_s)", "undef"); assert_parsed_expression_simplify_to("(_s)!", "undef"); assert_parsed_expression_simplify_to("floor(_s)", "undef"); assert_parsed_expression_simplify_to("frac(_s)", "undef"); assert_parsed_expression_simplify_to("gcd(1,_s)", "undef"); assert_parsed_expression_simplify_to("gcd(_s,1)", "undef"); + assert_parsed_expression_simplify_to("gcd(1,2,3,_s)", "undef"); assert_parsed_expression_simplify_to("identity(_s)", "undef"); assert_parsed_expression_simplify_to("im(_s)", "undef"); assert_parsed_expression_simplify_to("int(_s,x,0,1)", "undef"); @@ -374,10 +503,12 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("inverse(_s)", "undef"); assert_parsed_expression_simplify_to("lcm(1,_s)", "undef"); assert_parsed_expression_simplify_to("lcm(_s,1)", "undef"); + assert_parsed_expression_simplify_to("lcm(1,2,3,_s)", "undef"); assert_parsed_expression_simplify_to("ln(_s)", "undef"); assert_parsed_expression_simplify_to("log(_s)", "undef"); assert_parsed_expression_simplify_to("log(_s,2)", "undef"); assert_parsed_expression_simplify_to("log(1,_s)", "undef"); + assert_parsed_expression_simplify_to("norm(_s)", "undef"); assert_parsed_expression_simplify_to("normcdf(_s,2,3)", "undef"); assert_parsed_expression_simplify_to("normcdf(2,_s,3)", "undef"); assert_parsed_expression_simplify_to("normcdf(2,3,_s)", "undef"); @@ -402,10 +533,12 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("randint(_s,1)", "undef"); assert_parsed_expression_simplify_to("randint(1,_s)", "undef"); assert_parsed_expression_simplify_to("re(_s)", "undef"); + assert_parsed_expression_simplify_to("ref(_s)", "undef"); assert_parsed_expression_simplify_to("rem(_s,1)", "undef"); assert_parsed_expression_simplify_to("rem(1,_s)", "undef"); assert_parsed_expression_simplify_to("round(_s,1)", "undef"); assert_parsed_expression_simplify_to("round(1,_s)", "undef"); + assert_parsed_expression_simplify_to("rref(_s)", "undef"); assert_parsed_expression_simplify_to("sign(_s)", "undef"); assert_parsed_expression_simplify_to("sin(_s)", "undef"); assert_parsed_expression_simplify_to("sinh(_s)", "undef"); @@ -416,12 +549,15 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("tanh(_s)", "undef"); assert_parsed_expression_simplify_to("trace(_s)", "undef"); assert_parsed_expression_simplify_to("transpose(_s)", "undef"); - assert_parsed_expression_simplify_to("√(_s)", "undef"); /* Valid expressions */ assert_parsed_expression_simplify_to("-2×_A", "-2×_A"); assert_parsed_expression_simplify_to("cos(1_s/1_s)", "cos(1)"); assert_parsed_expression_simplify_to("1_m+π_m+√(2)_m-cos(15)_m", "6.3154941288217×_m"); + assert_parsed_expression_simplify_to("√(16×_m^2)", "4×_m"); + assert_parsed_expression_simplify_to("1×_A_kg", "2.2046226218488×_A×_lb", User, Radian, Imperial); + assert_parsed_expression_simplify_to("2×π×_cK", "0.062831853071796×_K", User, Radian, Imperial); + } QUIZ_CASE(poincare_simplification_power) { @@ -490,11 +626,11 @@ QUIZ_CASE(poincare_simplification_power) { assert_parsed_expression_simplify_to("ℯ^(𝐢×π/3)", "1/2+√(3)/2×𝐢"); assert_parsed_expression_simplify_to("(-1)^(1/3)", "1/2+√(3)/2×𝐢"); assert_parsed_expression_simplify_to("√(-x)", "√(-x)"); - assert_parsed_expression_simplify_to("√(x)^2", "x", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("√(-3)^2", "unreal", User, Radian, Real); + assert_parsed_expression_simplify_to("√(x)^2", "x", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("√(-3)^2", "unreal", User, Radian, Metric, Real); // Principal angle of root of unity - assert_parsed_expression_simplify_to("(-5)^(-1/3)", "1/\u00122×root(5,3)\u0013-√(3)/\u00122×root(5,3)\u0013×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("1+((8+√(6))^(1/2))^-1+(8+√(6))^(1/2)", "\u0012√(√(6)+8)+√(6)+9\u0013/√(√(6)+8)", User, Radian, Real); + assert_parsed_expression_simplify_to("(-5)^(-1/3)", "1/\u00122×root(5,3)\u0013-√(3)/\u00122×root(5,3)\u0013×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("1+((8+√(6))^(1/2))^-1+(8+√(6))^(1/2)", "\u0012√(√(6)+8)+√(6)+9\u0013/√(√(6)+8)", User, Radian, Metric, Real); assert_parsed_expression_simplify_to("[[1,2][3,4]]^(-3)", "[[-59/4,27/4][81/8,-37/8]]"); assert_parsed_expression_simplify_to("[[1,2][3,4]]^3", "[[37,54][81,118]]"); assert_parsed_expression_simplify_to("(3_m^2)^3", "27×_m^6"); @@ -511,10 +647,10 @@ QUIZ_CASE(poincare_simplification_factorial) { QUIZ_CASE(poincare_simplification_logarithm) { assert_parsed_expression_simplify_to("log(0,0)", Undefined::Name()); assert_parsed_expression_simplify_to("log(0,1)", Undefined::Name()); - assert_parsed_expression_simplify_to("log(1,0)", "0"); - assert_parsed_expression_simplify_to("log(2,0)", "0"); - assert_parsed_expression_simplify_to("log(0,14)", "-inf"); - assert_parsed_expression_simplify_to("log(0,0.14)", Infinity::Name()); + assert_parsed_expression_simplify_to("log(1,0)", Undefined::Name()); + assert_parsed_expression_simplify_to("log(2,0)", Undefined::Name()); + assert_parsed_expression_simplify_to("log(0,14)", Undefined::Name()); + assert_parsed_expression_simplify_to("log(0,0.14)", Undefined::Name()); assert_parsed_expression_simplify_to("log(0,0.14+𝐢)", Undefined::Name()); assert_parsed_expression_simplify_to("log(2,1)", Undefined::Name()); assert_parsed_expression_simplify_to("log(x,1)", Undefined::Name()); @@ -581,6 +717,11 @@ QUIZ_CASE(poincare_simplification_function) { assert_parsed_expression_simplify_to("arg(1+𝐢)", "π/4"); assert_parsed_expression_simplify_to("binomial(20,3)", "1140"); assert_parsed_expression_simplify_to("binomial(20,10)", "184756"); + assert_parsed_expression_simplify_to("binomial(10,20)", "0"); + assert_parsed_expression_simplify_to("binomial(-10,10)", "92378"); + assert_parsed_expression_simplify_to("binomial(2.5,3)", "binomial(5/2,3)"); + assert_parsed_expression_simplify_to("binomial(-200,120)", "binomial(-200,120)"); + assert_parsed_expression_simplify_to("binomial(400,1)", "binomial(400,1)"); assert_parsed_expression_simplify_to("ceil(-1.3)", "-1"); assert_parsed_expression_simplify_to("ceil(2π)", "7"); assert_parsed_expression_simplify_to("ceil(123456789012345678901234567892/3)", "41152263004115226300411522631"); @@ -606,9 +747,13 @@ QUIZ_CASE(poincare_simplification_function) { assert_parsed_expression_simplify_to("frac(-1.3)", "7/10"); assert_parsed_expression_simplify_to("gcd(123,278)", "1"); assert_parsed_expression_simplify_to("gcd(11,121)", "11"); + assert_parsed_expression_simplify_to("gcd(56,112,28,91)", "7"); + assert_parsed_expression_simplify_to("gcd(-32,-32)", "32"); assert_parsed_expression_simplify_to("im(1+5×𝐢)", "5"); assert_parsed_expression_simplify_to("lcm(123,278)", "34194"); assert_parsed_expression_simplify_to("lcm(11,121)", "121"); + assert_parsed_expression_simplify_to("lcm(11,121, 3)", "363"); + assert_parsed_expression_simplify_to("lcm(-32,-32)", "32"); assert_parsed_expression_simplify_to("√(4)", "2"); assert_parsed_expression_simplify_to("re(1+5×𝐢)", "1"); assert_parsed_expression_simplify_to("root(4,3)", "root(4,3)"); @@ -632,8 +777,8 @@ QUIZ_CASE(poincare_simplification_function) { assert_parsed_expression_simplify_to("sign(2+𝐢)", "sign(2+𝐢)"); /* Test with no symbolic computation to check that n inside a sum expression * is not replaced by Undefined */ - assert_parsed_expression_simplify_to("sum(n,n,1,5)", "sum(n,n,1,5)", User, Radian, Cartesian, ReplaceAllSymbolsWithDefinitionsOrUndefined); - assert_parsed_expression_simplify_to("sum(1/n,n,1,2)", "sum(1/n,n,1,2)", User, Radian, Cartesian, ReplaceAllSymbolsWithDefinitionsOrUndefined); + assert_parsed_expression_simplify_to("sum(n,n,1,5)", "sum(n,n,1,5)", User, Radian, Metric, Cartesian, ReplaceAllSymbolsWithDefinitionsOrUndefined); + assert_parsed_expression_simplify_to("sum(1/n,n,1,2)", "sum(1/n,n,1,2)", User, Radian, Metric, Cartesian, ReplaceAllSymbolsWithDefinitionsOrUndefined); assert_parsed_expression_simplify_to("permute(99,4)", "90345024"); assert_parsed_expression_simplify_to("permute(20,-10)", Undefined::Name()); assert_parsed_expression_simplify_to("re(1/2)", "1/2"); @@ -769,7 +914,20 @@ QUIZ_CASE(poincare_simplification_trigonometry_functions) { assert_parsed_expression_simplify_to("acos(cos(3/2))", "3/2"); assert_parsed_expression_simplify_to("cos(acos(3/2))", "3/2"); assert_parsed_expression_simplify_to("cos(acos(2/3))", "2/3"); - assert_parsed_expression_simplify_to("acos(cos(12))", "acos(cos(12))"); + + assert_parsed_expression_simplify_to("acos(cos(12))", "4×π-12"); + assert_parsed_expression_simplify_to("acos(cos(2*1ᴇ10))", "20000000000"); + assert_parsed_expression_simplify_to("acos(cos(inf))", "acos(cos(inf))"); + assert_parsed_expression_simplify_to("acos(cos(9))", "-2×π+9"); + assert_parsed_expression_simplify_to("acos(cos(10^125))", "acos(cos(10^125))"); + assert_parsed_expression_simplify_to("acos(cos(1/0))", Undefined::Name()); + assert_parsed_expression_simplify_to("acos(cos(-8.8))", "\u0012-10×π+44\u0013/5"); + assert_parsed_expression_simplify_to("acos(cos(π+26))", "9×π-26"); + assert_parsed_expression_simplify_to("acos(cos(0))", "0"); + assert_parsed_expression_simplify_to("acos(cos(9π))", "π"); + assert_parsed_expression_simplify_to("acos(cos(2*1ᴇ10))", "160", User, Degree); + assert_parsed_expression_simplify_to("acos(cos(180+50))", "130", User, Degree); + assert_parsed_expression_simplify_to("acos(cos(4π/7))", "\u00124×π\u0013/7"); assert_parsed_expression_simplify_to("acos(-cos(2))", "π-2"); assert_parsed_expression_simplify_to("acos(-1/2)", "120", User, Degree); @@ -788,7 +946,16 @@ QUIZ_CASE(poincare_simplification_trigonometry_functions) { assert_parsed_expression_simplify_to("sin(asin(2/3))", "2/3"); assert_parsed_expression_simplify_to("sin(asin(3/2))", "3/2"); assert_parsed_expression_simplify_to("asin(sin(3/2))", "3/2"); - assert_parsed_expression_simplify_to("asin(sin(12))", "asin(sin(12))"); + assert_parsed_expression_simplify_to("asin(sin(3.6))", "\u00125×π-18\u0013/5"); + assert_parsed_expression_simplify_to("asin(sin(-2.23))", "\u0012-100×π+223\u0013/100"); + assert_parsed_expression_simplify_to("asin(sin(-18.39))", "\u0012600×π-1839\u0013/100"); + + + assert_parsed_expression_simplify_to("asin(sin(12))", "-4×π+12"); + assert_parsed_expression_simplify_to("asin(sin(2+π))", "-π+2"); + assert_parsed_expression_simplify_to("asin(sin(90+6800))", "50", User, Degree); + assert_parsed_expression_simplify_to("asin(sin(60-9×9×9))", "51", User, Degree); + assert_parsed_expression_simplify_to("asin(sin(-π/7))", "-π/7"); assert_parsed_expression_simplify_to("asin(sin(-√(2)))", "-√(2)"); assert_parsed_expression_simplify_to("asin(-1/2)", "-30", User, Degree); @@ -805,8 +972,7 @@ QUIZ_CASE(poincare_simplification_trigonometry_functions) { assert_parsed_expression_simplify_to("atan(tan(2/3))", "2/3"); assert_parsed_expression_simplify_to("tan(atan(2/3))", "2/3"); assert_parsed_expression_simplify_to("tan(atan(5/2))", "5/2"); - assert_parsed_expression_simplify_to("atan(tan(5/2))", "atan(tan(5/2))"); - assert_parsed_expression_simplify_to("atan(tan(5/2))", "atan(tan(5/2))"); + assert_parsed_expression_simplify_to("atan(tan(5/2))", "\u0012-2×π+5\u0013/2"); assert_parsed_expression_simplify_to("atan(tan(-π/7))", "-π/7"); assert_parsed_expression_simplify_to("atan(√(3))", "π/3"); assert_parsed_expression_simplify_to("atan(tan(-√(2)))", "-√(2)"); @@ -819,7 +985,9 @@ QUIZ_CASE(poincare_simplification_trigonometry_functions) { assert_parsed_expression_simplify_to("atan(tan(1808))", "8", User, Degree); assert_parsed_expression_simplify_to("atan(tan(-180/7))", "-180/7", User, Degree); assert_parsed_expression_simplify_to("atan(√(3))", "60", User, Degree); - assert_parsed_expression_simplify_to("atan(1/x)", "\u0012π×sign(x)-2×atan(x)\u0013/2", User, Degree); + assert_parsed_expression_simplify_to("atan(1/x)", "\u0012π×sign(x)-2×atan(x)\u0013/2"); + assert_parsed_expression_simplify_to("atan(1/x)", "90×sign(x)-atan(x)", User, Degree); + assert_parsed_expression_simplify_to("atan(1/x)", "100×sign(x)-atan(x)", User, Gradian); // cos(asin) assert_parsed_expression_simplify_to("cos(asin(x))", "√(-x^2+1)", User, Degree); @@ -897,6 +1065,49 @@ QUIZ_CASE(poincare_simplification_matrix) { assert_parsed_expression_simplify_to("transpose([[1/√(2),1/2,3][2,1,-3]])", "[[√(2)/2,2][1/2,1][3,-3]]"); assert_parsed_expression_simplify_to("transpose(√(4))", "2"); + // Ref and Rref + assert_parsed_expression_simplify_to("ref([[1,1/√(2),√(4)]])", "[[1,√(2)/2,2]]"); + assert_parsed_expression_simplify_to("rref([[1,1/√(2),√(4)]])", "[[1,√(2)/2,2]]"); + assert_parsed_expression_simplify_to("ref([[1,0,√(4)][0,1,1/√(2)][0,0,1]])", "[[1,0,2][0,1,√(2)/2][0,0,1]]"); + assert_parsed_expression_simplify_to("rref([[1,0,√(4)][0,1,1/√(2)][0,0,0]])", "[[1,0,2][0,1,√(2)/2][0,0,0]]"); + assert_parsed_expression_simplify_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,7/5,6/5,8/5][0,1,11/10,6/5][0,0,1,204/167]]"); + assert_parsed_expression_simplify_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,56/167][0,1,0,-24/167][0,0,1,204/167]]"); + assert_parsed_expression_simplify_to("ref([[1,0][5,6][0,10]])", "[[1,6/5][0,1][0,0]]"); + assert_parsed_expression_simplify_to("rref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); + assert_parsed_expression_simplify_to("ref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); + assert_parsed_expression_simplify_to("rref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); + assert_parsed_expression_simplify_to("rref([[0,1][1ᴇ-100,1]])", "[[1,0][0,1]]"); + assert_parsed_expression_simplify_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,11/12,5/6][0,1,-1/2][0,0,1]]"); + assert_parsed_expression_simplify_to("rref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); + /* Results for ref depend on the implementation. In any case : + * - Rows with only zeros must be at the bottom. + * - Leading coefficient of other rows must be to the right (strictly) of the + * - one above. + * - (Optional, but sometimes recommended) Leading coefficients must be 1. */ + assert_parsed_expression_simplify_to("ref([[3,9][2,5]])", "[[1,3][0,1]]"); + assert_parsed_expression_simplify_to("ref([[3,2][5,7]])", "[[1,7/5][0,1]]"); + assert_parsed_expression_simplify_to("ref([[3,11][5,7]])", "[[1,7/5][0,1]]"); + assert_parsed_expression_simplify_to("ref([[2,5][2,7]])", "[[1,5/2][0,1]]"); + assert_parsed_expression_simplify_to("ref([[3,12][-4,1]])", "[[1,-1/4][0,1]]"); + assert_parsed_expression_simplify_to("ref([[0,1][1ᴇ-100,1]])", "[[1,10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000][0,1]]"); + + // Cross product + assert_parsed_expression_simplify_to("cross([[0][1/√(2)][0]],[[0][0][1]])", "[[√(2)/2][0][0]]"); + assert_parsed_expression_simplify_to("cross([[1,2,3]],[[4][7][8]])", Undefined::Name()); + assert_parsed_expression_simplify_to("cross([[1,2,3]],[[4,7,8]])", "[[-5,4,-1]]"); + assert_parsed_expression_simplify_to("cross([[1,π,𝐢]],[[𝐢π,𝐢π^2,-π]])", "[[0,0,0]]"); + + // Dot product + assert_parsed_expression_simplify_to("dot([[1/√(2)][0][0]],[[1][0][0]])", "√(2)/2"); + assert_parsed_expression_simplify_to("dot([[1,1,0]],[[0][0][1]])", Undefined::Name()); + assert_parsed_expression_simplify_to("dot([[1,1,0]],[[0,0,1]])", "0"); + assert_parsed_expression_simplify_to("dot([[1,1,1]],[[0,π,𝐢]])", "π+𝐢"); + + // Vector norm + assert_parsed_expression_simplify_to("norm([[1/√(2)][0][0]])", "√(2)/2"); + assert_parsed_expression_simplify_to("norm([[1][2][3]])", "√(14)"); + assert_parsed_expression_simplify_to("norm([[1,𝐢+1,π,-5]])", "√(π^2+28)"); + // Expressions with unreduced matrix assert_reduce("confidence(cos(2)/25,3)→a"); // Check that matrices are not permuted in multiplication @@ -948,6 +1159,7 @@ QUIZ_CASE(poincare_simplification_functions_of_matrices) { assert_parsed_expression_simplify_to("gcd([[0,180]],1)", Undefined::Name()); assert_parsed_expression_simplify_to("gcd(1,[[0,180]])", Undefined::Name()); assert_parsed_expression_simplify_to("gcd([[0,180]],[[1]])", Undefined::Name()); + assert_parsed_expression_simplify_to("gcd(1,2,[[1]])", Undefined::Name()); assert_parsed_expression_simplify_to("acosh([[0,π]])", "[[acosh(0),acosh(π)]]"); assert_parsed_expression_simplify_to("asinh([[0,π]])", "[[0,asinh(π)]]"); assert_parsed_expression_simplify_to("atanh([[0,π]])", "[[0,atanh(π)]]"); @@ -1030,6 +1242,8 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("3×_m→6_km", "0.003×_km"); assert_parsed_expression_simplify_to("4×_min→_s^3/_s^2", "240×_s"); assert_parsed_expression_simplify_to("4×_N×3_N×2_N→_N^3", "24×_N^3"); + assert_parsed_expression_simplify_to("-5_cm→_m", "-0.05×_m"); + assert_parsed_expression_simplify_to("-5_cm→_m", "-0.05×_m", User, Radian, Imperial); assert_parsed_expression_simplify_to("1→2", Undefined::Name()); assert_parsed_expression_simplify_to("1→a+a", Undefined::Name()); @@ -1053,9 +1267,17 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("1→3_m", Undefined::Name()); assert_parsed_expression_simplify_to("4→_km/_m", Undefined::Name()); assert_parsed_expression_simplify_to("3×_min→_s+1-1", Undefined::Name()); + assert_parsed_expression_simplify_to("0→_K", Undefined::Name()); - assert_reduce("_m→a", Radian, Real); - assert_reduce("_m→b", Radian, Real); + assert_parsed_expression_simplify_to("0_K→_°C", "-273.15×_°C"); + assert_parsed_expression_simplify_to("0_°C→_K", "273.15×_K"); + assert_parsed_expression_simplify_to("_°C→_K", "274.15×_K"); + assert_parsed_expression_simplify_to("0_K→_°F", "-459.67×_°F"); + assert_parsed_expression_simplify_to("0_°F→_K", "255.37222222222×_K"); + assert_parsed_expression_simplify_to("_°F→_K", "255.92777777778×_K"); + + assert_reduce("_m→a", Radian, Metric, Real); + assert_reduce("_m→b", Radian, Metric, Real); assert_parsed_expression_simplify_to("1_km→a×b", Undefined::Name()); assert_reduce("2→a"); @@ -1071,166 +1293,171 @@ QUIZ_CASE(poincare_simplification_unit_convert) { QUIZ_CASE(poincare_simplification_complex_format) { // Real - assert_parsed_expression_simplify_to("𝐢", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("√(-1)", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("√(-1)×√(-1)", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("ln(-2)", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("(-8)^(2/3)", "4", User, Radian, Real); - assert_parsed_expression_simplify_to("(-8)^(2/5)", "2×root(2,5)", User, Radian, Real); - assert_parsed_expression_simplify_to("(-8)^(1/5)", "-root(8,5)", User, Radian, Real); - assert_parsed_expression_simplify_to("(-8)^(1/4)", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("(-8)^(1/3)", "-2", User, Radian, Real); - assert_parsed_expression_simplify_to("[[1,2+√(-1)]]", "unreal", User, Radian, Real); - assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Real); - assert_parsed_expression_simplify_to("atan(-2)", "-atan(2)", User, Radian, Real); + assert_parsed_expression_simplify_to("𝐢", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("√(-1)", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("√(-1)×√(-1)", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("ln(-2)", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("(-8)^(2/3)", "4", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("(-8)^(2/5)", "2×root(2,5)", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("(-8)^(1/5)", "-root(8,5)", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("(-8)^(1/4)", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("(-8)^(1/3)", "-2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("[[1,2+√(-1)]]", "unreal", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("atan(-2)", "-atan(2)", User, Radian, Metric, Real); // User defined variable - assert_parsed_expression_simplify_to("a", "a", User, Radian, Real); + assert_parsed_expression_simplify_to("a", "a", User, Radian, Metric, Real); // a = 2+i - assert_reduce("2+𝐢→a", Radian, Real); - assert_parsed_expression_simplify_to("a", "unreal", User, Radian, Real); + assert_reduce("2+𝐢→a", Radian, Metric, Real); + assert_parsed_expression_simplify_to("a", "unreal", User, Radian, Metric, Real); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); // User defined function // f : x → x+1 - assert_reduce("x+1+𝐢→f(x)", Radian, Real); - assert_parsed_expression_simplify_to("f(3)", "unreal", User, Radian, Real); + assert_reduce("x+1+𝐢→f(x)", Radian, Metric, Real); + assert_parsed_expression_simplify_to("f(3)", "unreal", User, Radian, Metric, Real); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); // Cartesian - assert_parsed_expression_simplify_to("-2.3ᴇ3", "-2300", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("3", "3", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("inf", "inf", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("1+2+𝐢", "3+𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("-(5+2×𝐢)", "-5-2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(5+2×𝐢)", "5+2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("𝐢+𝐢", "2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("-2+2×𝐢", "-2+2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(3+𝐢)-(2+4×𝐢)", "1-3×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(2+3×𝐢)×(4-2×𝐢)", "14+8×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(3+𝐢)/2", "3/2+1/2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(3+𝐢)/(2+𝐢)", "7/5-1/5×𝐢", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("-2.3ᴇ3", "-2300", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("3", "3", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("inf", "inf", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("1+2+𝐢", "3+𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("-(5+2×𝐢)", "-5-2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(5+2×𝐢)", "5+2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("𝐢+𝐢", "2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("-2+2×𝐢", "-2+2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(3+𝐢)-(2+4×𝐢)", "1-3×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(2+3×𝐢)×(4-2×𝐢)", "14+8×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(3+𝐢)/2", "3/2+1/2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(3+𝐢)/(2+𝐢)", "7/5-1/5×𝐢", User, Radian, Metric, Cartesian); // The simplification of (3+𝐢)^(2+𝐢) in a Cartesian complex form generates to many nodes - //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10×cos((-4×atan(3)+ln(2)+ln(5)+2×π)/2)×ℯ^((2×atan(3)-π)/2)+10×sin((-4×atan(3)+ln(2)+ln(5)+2×π)/2)×ℯ^((2×atan(3)-π)/2)𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "(𝐢+3)^\u0012𝐢+2\u0013", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("√(1+6𝐢)", "√(2×√(37)+2)/2+√(2×√(37)-2)/2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("(1+𝐢)^2", "2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("2×𝐢", "2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("𝐢!", "𝐢!", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("3!", "6", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("x!", "x!", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("ℯ", "ℯ", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("π", "π", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("𝐢", "𝐢", User, Radian, Cartesian); + //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10×cos((-4×atan(3)+ln(2)+ln(5)+2×π)/2)×ℯ^((2×atan(3)-π)/2)+10×sin((-4×atan(3)+ln(2)+ln(5)+2×π)/2)×ℯ^((2×atan(3)-π)/2)𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "(𝐢+3)^\u0012𝐢+2\u0013", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("√(1+6𝐢)", "√(2×√(37)+2)/2+√(2×√(37)-2)/2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("(1+𝐢)^2", "2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("2×𝐢", "2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("𝐢!", "𝐢!", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("3!", "6", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("x!", "x!", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("ℯ", "ℯ", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("π", "π", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("𝐢", "𝐢", User, Radian, Metric, Cartesian); - assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("atan(-2)", "-atan(2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("abs(-3)", "3", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("abs(-3+𝐢)", "√(10)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("atan(2+𝐢)", "atan(2+𝐢)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("binomial(10, 4)", "210", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("ceil(-1.3)", "-1", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("arg(-2)", "π", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("atan(-2)", "-atan(2)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("abs(-3)", "3", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("abs(-3+𝐢)", "√(10)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("atan(2+𝐢)", "atan(2+𝐢)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("binomial(10, 4)", "210", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("ceil(-1.3)", "-1", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("arg(-2)", "π", User, Radian, Metric, Cartesian); // TODO: confidence is not simplified yet //assert_parsed_expression_simplify_to("confidence(-2,-3)", "confidence(-2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("conj(-2)", "-2", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("conj(-2+2×𝐢+𝐢)", "-2-3×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("cos(12)", "cos(12)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("cos(12+𝐢)", "cos(12+𝐢)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("diff(3×x, x, 3)", "diff(3×x,x,3)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("quo(34,x)", "quo(34,x)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("rem(5,3)", "2", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("floor(x)", "floor(x)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("frac(x)", "frac(x)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("gcd(x,y)", "gcd(x,y)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("im(1+𝐢)", "1", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("int(x^2, x, 1, 2)", "int(x^2,x,1,2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("lcm(x,y)", "lcm(x,y)", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("conj(-2)", "-2", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("conj(-2+2×𝐢+𝐢)", "-2-3×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("cos(12)", "cos(12)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("cos(12+𝐢)", "cos(12+𝐢)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("diff(3×x, x, 3)", "3", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("quo(34,x)", "quo(34,x)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("rem(5,3)", "2", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("floor(x)", "floor(x)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("frac(x)", "frac(x)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("gcd(x,y)", "gcd(x,y)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("gcd(x,gcd(y,z))", "gcd(x,y,z)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("gcd(3, 1, 2, x, x^2)", "gcd(x^2,x,3,2,1)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("im(1+𝐢)", "1", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("int(x^2, x, 1, 2)", "int(x^2,x,1,2)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("lcm(x,y)", "lcm(x,y)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("lcm(x,lcm(y,z))", "lcm(x,y,z)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("lcm(3, 1, 2, x, x^2)", "lcm(x^2,x,3,2,1)", User, Radian, Metric, Cartesian); // TODO: dim is not simplified yet - //assert_parsed_expression_simplify_to("dim(x)", "dim(x)", User, Radian, Cartesian); + //assert_parsed_expression_simplify_to("dim(x)", "dim(x)", User, Radian, Metric, Cartesian); - assert_parsed_expression_simplify_to("root(2,𝐢)", "cos(ln(2))-sin(ln(2))×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("root(2,𝐢+1)", "√(2)×cos(\u001290×ln(2)\u0013/π)-√(2)×sin(\u001290×ln(2)\u0013/π)×𝐢", User, Degree, Cartesian); - assert_parsed_expression_simplify_to("root(2,𝐢+1)", "√(2)×cos(ln(2)/2)-√(2)×sin(ln(2)/2)×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("permute(10, 4)", "5040", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("root(2,𝐢)", "cos(ln(2))-sin(ln(2))×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("root(2,𝐢+1)", "√(2)×cos(\u001290×ln(2)\u0013/π)-√(2)×sin(\u001290×ln(2)\u0013/π)×𝐢", User, Degree, Metric, Cartesian); + assert_parsed_expression_simplify_to("root(2,𝐢+1)", "√(2)×cos(ln(2)/2)-√(2)×sin(ln(2)/2)×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("permute(10, 4)", "5040", User, Radian, Metric, Cartesian); // TODO: prediction is not simplified yet - //assert_parsed_expression_simplify_to("prediction(-2,-3)", "prediction(-2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("randint(2,2)", "2", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("random()", "random()", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("re(x)", "re(x)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("round(x,y)", "round(x,y)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("sign(x)", "sign(x)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("sin(23)", "sin(23)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("sin(23+𝐢)", "sin(23+𝐢)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("√(1-𝐢)", "√(2×√(2)+2)/2-√(2×√(2)-2)/2×𝐢", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("tan(23)", "tan(23)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("tan(23+𝐢)", "tan(23+𝐢)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("[[1,√(-1)]]", "[[1,𝐢]]", User, Radian, Cartesian); + //assert_parsed_expression_simplify_to("prediction(-2,-3)", "prediction(-2)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("randint(2,2)", "2", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("random()", "random()", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("re(x)", "re(x)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("round(x,y)", "round(x,y)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("sign(x)", "sign(x)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("sin(23)", "sin(23)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("sin(23+𝐢)", "sin(23+𝐢)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("√(1-𝐢)", "√(2×√(2)+2)/2-√(2×√(2)-2)/2×𝐢", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("tan(23)", "tan(23)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("tan(23+𝐢)", "tan(23+𝐢)", User, Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("[[1,√(-1)]]", "[[1,𝐢]]", User, Radian, Metric, Cartesian); // User defined variable - assert_parsed_expression_simplify_to("a", "a", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("a", "a", User, Radian, Metric, Cartesian); // a = 2+i - assert_reduce("2+𝐢→a", Radian, Cartesian); - assert_parsed_expression_simplify_to("a", "2+𝐢", User, Radian, Cartesian); + assert_reduce("2+𝐢→a", Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("a", "2+𝐢", User, Radian, Metric, Cartesian); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); // User defined function // f : x → x+1 - assert_reduce("x+1+𝐢→f(x)", Radian, Cartesian); - assert_parsed_expression_simplify_to("f(3)", "4+𝐢", User, Radian, Cartesian); + assert_reduce("x+1+𝐢→f(x)", Radian, Metric, Cartesian); + assert_parsed_expression_simplify_to("f(3)", "4+𝐢", User, Radian, Metric, Cartesian); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); // Polar - assert_parsed_expression_simplify_to("-2.3ᴇ3", "2300×ℯ^\u0012π×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("3", "3", User, Radian, Polar); - assert_parsed_expression_simplify_to("inf", "inf", User, Radian, Polar); - assert_parsed_expression_simplify_to("1+2+𝐢", "√(10)×ℯ^\u0012\u0012-2×atan(3)+π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("1+2+𝐢", "√(10)×ℯ^\u0012\u0012-π×atan(3)+90×π\u0013/180×𝐢\u0013", User, Degree, Polar); - assert_parsed_expression_simplify_to("-(5+2×𝐢)", "√(29)×ℯ^\u0012\u0012-2×atan(5/2)-π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(5+2×𝐢)", "√(29)×ℯ^\u0012\u0012-2×atan(5/2)+π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("𝐢+𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("𝐢+𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("-2+2×𝐢", "2×√(2)×ℯ^\u0012\u00123×π\u0013/4×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(3+𝐢)-(2+4×𝐢)", "√(10)×ℯ^\u0012\u00122×atan(1/3)-π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(2+3×𝐢)×(4-2×𝐢)", "2×√(65)×ℯ^\u0012\u0012-2×atan(7/4)+π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(3+𝐢)/2", "√(10)/2×ℯ^\u0012\u0012-2×atan(3)+π\u0013/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(3+𝐢)/(2+𝐢)", "√(2)×ℯ^\u0012\u00122×atan(7)-π\u0013/2×𝐢\u0013", User, Radian, Polar); + assert_parsed_expression_simplify_to("-2.3ᴇ3", "2300×ℯ^\u0012π×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("3", "3", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("inf", "inf", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("1+2+𝐢", "√(10)×ℯ^\u0012\u0012-2×atan(3)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("1+2+𝐢", "√(10)×ℯ^\u0012\u0012-π×atan(3)+90×π\u0013/180×𝐢\u0013", User, Degree, Metric, Polar); + assert_parsed_expression_simplify_to("-(5+2×𝐢)", "√(29)×ℯ^\u0012\u0012-2×atan(5/2)-π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(5+2×𝐢)", "√(29)×ℯ^\u0012\u0012-2×atan(5/2)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("𝐢+𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("𝐢+𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("-2+2×𝐢", "2×√(2)×ℯ^\u0012\u00123×π\u0013/4×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(3+𝐢)-(2+4×𝐢)", "√(10)×ℯ^\u0012\u00122×atan(1/3)-π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(2+3×𝐢)×(4-2×𝐢)", "2×√(65)×ℯ^\u0012\u0012-2×atan(7/4)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(3+𝐢)/2", "√(10)/2×ℯ^\u0012\u0012-2×atan(3)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(3+𝐢)/(2+𝐢)", "√(2)×ℯ^\u0012\u00122×atan(7)-π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); // TODO: simplify atan(tan(x)) = x±k×pi? - //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10ℯ^\u0012\u00122×atan(3)-π\u0013/2\u0013×ℯ^\u0012\u0012\u0012-4×atan(3)+ln(2)+ln(5)+2π\u0013/2\u0013𝐢\u0013", User, Radian, Polar); + //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10ℯ^\u0012\u00122×atan(3)-π\u0013/2\u0013×ℯ^\u0012\u0012\u0012-4×atan(3)+ln(2)+ln(5)+2π\u0013/2\u0013𝐢\u0013", User, Radian, Metric, Polar); // The simplification of (3+𝐢)^(2+𝐢) in a Polar complex form generates to many nodes - //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10ℯ^\u0012\u00122×atan(3)-π\u0013/2\u0013×ℯ^\u0012\u0012atan(tan((-4×atan(3)+ln(2)+ln(5)+2×π)/2))+π\u0013𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "(𝐢+3)^\u0012𝐢+2\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("(1+𝐢)^2", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("2×𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("3!", "6", User, Radian, Polar); - assert_parsed_expression_simplify_to("x!", "x!", User, Radian, Polar); - assert_parsed_expression_simplify_to("ℯ", "ℯ", User, Radian, Polar); - assert_parsed_expression_simplify_to("π", "π", User, Radian, Polar); - assert_parsed_expression_simplify_to("𝐢", "ℯ^\u0012π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("abs(-3)", "3", User, Radian, Polar); - assert_parsed_expression_simplify_to("abs(-3+𝐢)", "√(10)", User, Radian, Polar); - assert_parsed_expression_simplify_to("conj(2×ℯ^(𝐢×π/2))", "2×ℯ^\u0012-π/2×𝐢\u0013", User, Radian, Polar); - assert_parsed_expression_simplify_to("-2×ℯ^(𝐢×π/2)", "2×ℯ^\u0012-π/2×𝐢\u0013", User, Radian, Polar); - 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); + //assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "10ℯ^\u0012\u00122×atan(3)-π\u0013/2\u0013×ℯ^\u0012\u0012atan(tan((-4×atan(3)+ln(2)+ln(5)+2×π)/2))+π\u0013𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(3+𝐢)^(2+𝐢)", "(𝐢+3)^\u0012𝐢+2\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("(1+𝐢)^2", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("2×𝐢", "2×ℯ^\u0012π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("3!", "6", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("x!", "x!", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("ℯ", "ℯ", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("π", "π", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("𝐢", "ℯ^\u0012π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("abs(-3)", "3", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("abs(-3+𝐢)", "√(10)", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("conj(2×ℯ^(𝐢×π/2))", "2×ℯ^\u0012-π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("-2×ℯ^(𝐢×π/2)", "2×ℯ^\u0012-π/2×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("[[1,√(-1)]]", "[[1,ℯ^\u0012π/2×𝐢\u0013]]", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("atan(2)", "atan(2)", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("atan(-2)", "atan(2)×ℯ^\u0012π×𝐢\u0013", User, Radian, Metric, Polar); + assert_parsed_expression_simplify_to("cos(42π)", "-cos(42×π)×ℯ^\x12π×𝐢\x13", User, Degree, Metric, Polar); // User defined variable - assert_parsed_expression_simplify_to("a", "a", User, Radian, Polar); + assert_parsed_expression_simplify_to("a", "a", User, Radian, Metric, Polar); // a = 2 + 𝐢 - assert_reduce("2+𝐢→a", Radian, Polar); - assert_parsed_expression_simplify_to("a", "√(5)×ℯ^\u0012\u0012-2×atan(2)+π\u0013/2×𝐢\u0013", User, Radian, Polar); + assert_reduce("2+𝐢→a", Radian, Metric, Polar); + assert_parsed_expression_simplify_to("a", "√(5)×ℯ^\u0012\u0012-2×atan(2)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); // User defined function // f: x → x+1 - assert_reduce("x+1+𝐢→f(x)", Radian, Polar); - assert_parsed_expression_simplify_to("f(3)", "√(17)×ℯ^\u0012\u0012-2×atan(4)+π\u0013/2×𝐢\u0013", User, Radian, Polar); + + assert_reduce("x+1+𝐢→f(x)", Radian, Metric, Polar); + assert_parsed_expression_simplify_to("f(3)", "√(17)×ℯ^\u0012\u0012-2×atan(4)+π\u0013/2×𝐢\u0013", User, Radian, Metric, Polar); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); } @@ -1287,19 +1514,19 @@ QUIZ_CASE(poincare_simplification_reduction_target) { } QUIZ_CASE(poincare_simplification_unit_conversion) { - assert_parsed_expression_simplify_to("1000000_cm", "10×_km", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, DefaultUnitConversion); - assert_parsed_expression_simplify_to("1000000_cm", "1000000×_cm", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, NoUnitConversion); - assert_parsed_expression_simplify_to("1000000_cm", "10000×_m", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, InternationalSystemUnitConversion); + assert_parsed_expression_simplify_to("1000000_cm", "10×_km", User, Degree, Metric, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, DefaultUnitConversion); + assert_parsed_expression_simplify_to("1000000_cm", "1000000×_cm", User, Degree, Metric, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, NoUnitConversion); + assert_parsed_expression_simplify_to("1000000_cm", "10000×_m", User, Degree, Metric, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, InternationalSystemUnitConversion); } QUIZ_CASE(poincare_simplification_user_function) { // User defined function // f: x → x*1 - assert_reduce("x*3→f(x)", Radian, Polar); - assert_parsed_expression_simplify_to("f(1+1)", "6", User, Radian, Polar); + assert_reduce("x*3→f(x)", Radian, Metric, Polar); + assert_parsed_expression_simplify_to("f(1+1)", "6", User, Radian, Metric, Polar); // f: x → 3 - assert_reduce("3→f(x)", Radian, Polar); - assert_parsed_expression_simplify_to("f(1/0)", Undefined::Name(), User, Radian, Polar); + assert_reduce("3→f(x)", Radian, Metric, Polar); + assert_parsed_expression_simplify_to("f(1/0)", Undefined::Name(), User, Radian, Metric, Polar); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); } @@ -1321,7 +1548,7 @@ QUIZ_CASE(poincare_simplification_user_function_with_convert) { e = Store::Builder( UnitConvert::Builder( Rational::Builder(0), - Unit::Second()), + Unit::Builder(&Unit::k_timeRepresentatives[Unit::k_secondRepresentativeIndex], Unit::Prefix::EmptyPrefix())), Function::Builder( "f", 1, Symbol::Builder('x'))); @@ -1367,7 +1594,7 @@ QUIZ_CASE(poincare_simplification_mix) { assert_parsed_expression_simplify_to("√(-𝐢)", "√(2)/2-√(2)/2×𝐢"); assert_parsed_expression_simplify_to("A×cos(9)𝐢𝐢ln(2)", "-A×cos(9)×ln(2)"); assert_parsed_expression_simplify_to("(√(2)+√(2)×𝐢)/2(√(2)+√(2)×𝐢)/2(√(2)+√(2)×𝐢)/2", "√(2)/32-√(2)/32×𝐢"); - assert_parsed_expression_simplify_to("root(5^((-𝐢)3^9),𝐢)", "1/ℯ^atan(tan(19683×ln(5)))"); + assert_parsed_expression_simplify_to("root(5^((-𝐢)3^9),𝐢)", "ℯ^\x12-19683×ln(5)+10084×π\x13"); assert_parsed_expression_simplify_to("𝐢^𝐢", "1/ℯ^\u0012π/2\u0013"); assert_parsed_expression_simplify_to("𝐢/(1+𝐢×√(x))", "𝐢/\u0012√(x)×𝐢+1\u0013"); assert_parsed_expression_simplify_to("x+𝐢/(1+𝐢×√(x))", "\u0012x^\u00123/2\u0013×𝐢+𝐢+x\u0013/\u0012√(x)×𝐢+1\u0013"); @@ -1379,7 +1606,7 @@ QUIZ_CASE(poincare_simplification_mix) { assert_parsed_expression_simplify_to("(((√(6)-√(2))/4)/((√(6)+√(2))/4))+1", "-√(3)+3"); assert_parsed_expression_simplify_to("1/√(𝐢) × (√(2)-𝐢×√(2))", "-2×𝐢"); // TODO: get rid of complex at denominator? - assert_expression_simplifies_approximates_to("abs(√(300000.0003^23))", "9.702740901018ᴇ62", Degree, Cartesian, 13); + assert_expression_simplifies_approximates_to("abs(√(300000.0003^23))", "9.702740901018ᴇ62", Degree, Metric, Cartesian, 13); } QUIZ_CASE(poincare_hyperbolic_trigonometry) { @@ -1395,49 +1622,49 @@ QUIZ_CASE(poincare_hyperbolic_trigonometry) { assert_parsed_expression_simplify_to("acosh(cosh(3))", "3"); assert_parsed_expression_simplify_to("acosh(cosh(0.5))", "1/2"); assert_parsed_expression_simplify_to("acosh(cosh(-3))", "3"); - assert_parsed_expression_simplify_to("acosh(cosh(3))", "3", User, Radian, Real); - assert_parsed_expression_simplify_to("acosh(cosh(0.5))", "1/2", User, Radian, Real); - assert_parsed_expression_simplify_to("acosh(cosh(-3))", "3", User, Radian, Real); + assert_parsed_expression_simplify_to("acosh(cosh(3))", "3", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("acosh(cosh(0.5))", "1/2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("acosh(cosh(-3))", "3", User, Radian, Metric, Real); // cosh(acosh) assert_parsed_expression_simplify_to("cosh(acosh(3))", "3"); assert_parsed_expression_simplify_to("cosh(acosh(0.5))", "1/2"); assert_parsed_expression_simplify_to("cosh(acosh(-3))", "-3"); - assert_parsed_expression_simplify_to("cosh(acosh(3))", "3", User, Radian, Real); - assert_parsed_expression_simplify_to("cosh(acosh(0.5))", "cosh(acosh(1/2))", User, Radian, Real); - assert_parsed_expression_simplify_to("cosh(acosh(-3))", "cosh(acosh(-3))", User, Radian, Real); + assert_parsed_expression_simplify_to("cosh(acosh(3))", "3", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("cosh(acosh(0.5))", "cosh(acosh(1/2))", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("cosh(acosh(-3))", "cosh(acosh(-3))", User, Radian, Metric, Real); // sinh(asinh) assert_parsed_expression_simplify_to("sinh(asinh(3))", "3"); assert_parsed_expression_simplify_to("sinh(asinh(0.5))", "1/2"); assert_parsed_expression_simplify_to("sinh(asinh(-3))", "-3"); - assert_parsed_expression_simplify_to("sinh(asinh(3))", "3", User, Radian, Real); - assert_parsed_expression_simplify_to("sinh(asinh(0.5))", "1/2", User, Radian, Real); - assert_parsed_expression_simplify_to("sinh(asinh(-3))", "-3", User, Radian, Real); + assert_parsed_expression_simplify_to("sinh(asinh(3))", "3", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("sinh(asinh(0.5))", "1/2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("sinh(asinh(-3))", "-3", User, Radian, Metric, Real); // asinh(sinh) assert_parsed_expression_simplify_to("asinh(sinh(3))", "3"); assert_parsed_expression_simplify_to("asinh(sinh(0.5))", "1/2"); assert_parsed_expression_simplify_to("asinh(sinh(-3))", "-3"); - assert_parsed_expression_simplify_to("asinh(sinh(3))", "3", User, Radian, Real); - assert_parsed_expression_simplify_to("asinh(sinh(0.5))", "1/2", User, Radian, Real); - assert_parsed_expression_simplify_to("asinh(sinh(-3))", "-3", User, Radian, Real); + assert_parsed_expression_simplify_to("asinh(sinh(3))", "3", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("asinh(sinh(0.5))", "1/2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("asinh(sinh(-3))", "-3", User, Radian, Metric, Real); // tanh(atanh) assert_parsed_expression_simplify_to("tanh(atanh(3))", "3"); assert_parsed_expression_simplify_to("tanh(atanh(0.5))", "1/2"); assert_parsed_expression_simplify_to("tanh(atanh(-3))", "-3"); - assert_parsed_expression_simplify_to("tanh(atanh(3))", "tanh(atanh(3))", User, Radian, Real); - assert_parsed_expression_simplify_to("tanh(atanh(0.5))", "1/2", User, Radian, Real); - assert_parsed_expression_simplify_to("tanh(atanh(-3))", "-tanh(atanh(3))", User, Radian, Real); + assert_parsed_expression_simplify_to("tanh(atanh(3))", "tanh(atanh(3))", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("tanh(atanh(0.5))", "1/2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("tanh(atanh(-3))", "-tanh(atanh(3))", User, Radian, Metric, Real); // atanh(tanh) assert_parsed_expression_simplify_to("atanh(tanh(3))", "3"); assert_parsed_expression_simplify_to("atanh(tanh(0.5))", "1/2"); assert_parsed_expression_simplify_to("atanh(tanh(-3))", "-3"); - assert_parsed_expression_simplify_to("atanh(tanh(3))", "3", User, Radian, Real); - assert_parsed_expression_simplify_to("atanh(tanh(0.5))", "1/2", User, Radian, Real); - assert_parsed_expression_simplify_to("atanh(tanh(-3))", "-3", User, Radian, Real); + assert_parsed_expression_simplify_to("atanh(tanh(3))", "3", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("atanh(tanh(0.5))", "1/2", User, Radian, Metric, Real); + assert_parsed_expression_simplify_to("atanh(tanh(-3))", "-3", User, Radian, Metric, Real); } QUIZ_CASE(poincare_probability) { diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp new file mode 100644 index 000000000..2e1da2a86 --- /dev/null +++ b/poincare/test/zoom.cpp @@ -0,0 +1,289 @@ +#include +#include "helper.h" +#include +#include + +using namespace Poincare; + +// When adding the graph window margins, this ratio gives an orthonormal window +constexpr float NormalRatio = 0.442358822; +constexpr float StandardTolerance = 50.f * FLT_EPSILON; + +class ParametersPack { +public: + ParametersPack(Expression expression, const char * symbol, Preferences::AngleUnit angleUnit) : + m_expression(expression), + m_symbol(symbol), + m_angleUnit(angleUnit) + {} + + Expression expression() const { return m_expression; } + const char * symbol() const { return m_symbol; } + Preferences::AngleUnit angleUnit() const { return m_angleUnit; } + +private: + Expression m_expression; + const char * m_symbol; + Preferences::AngleUnit m_angleUnit; +}; + +float evaluate_expression(float x, Context * context, const void * auxiliary) { + const ParametersPack * pack = static_cast(auxiliary); + return pack->expression().approximateWithValueForSymbol(pack->symbol(), x, context, Real, pack->angleUnit()); +} + +bool float_equal(float a, float b, float tolerance = StandardTolerance) { + assert(std::isfinite(tolerance)); + return !(std::isnan(a) || std::isnan(b)) + && IsApproximatelyEqual(a, b, tolerance, 0.); +} + +bool range1D_matches(float min, float max, float targetMin, float targetMax, float tolerance = StandardTolerance) { + return (float_equal(min, targetMin, tolerance) && float_equal(max, targetMax, tolerance)) + || (std::isnan(min) && std::isnan(max) && std::isnan(targetMin) && std::isnan(targetMax)); +} + +bool ranges_match(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, float tolerance = StandardTolerance) { + return range1D_matches(xMin, xMax, targetXMin, targetXMax, tolerance) + && range1D_matches(yMin, yMax, targetYMin, targetYMax, tolerance); +} + +void assert_interesting_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + float xMin, xMax, yMin, yMax; + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + ParametersPack aux(e, symbol, angleUnit); + Zoom::InterestingRangesForDisplay(evaluate_expression, &xMin, &xMax, &yMin, &yMax, -INFINITY, INFINITY, &globalContext, &aux); + quiz_assert_print_if_failure(ranges_match(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); +} + +QUIZ_CASE(poincare_zoom_interesting_ranges) { + assert_interesting_range_is("0", 0, 0, NAN, NAN); + assert_interesting_range_is("1", 0, 0, NAN, NAN); + assert_interesting_range_is("-100", 0, 0, NAN, NAN); + assert_interesting_range_is("x", 0, 0, NAN, NAN); + assert_interesting_range_is("x^2", 0, 0, NAN, NAN); + assert_interesting_range_is("-(x^3)", 0, 0, NAN, NAN); + assert_interesting_range_is("10×x^4", 0, 0, NAN, NAN); + assert_interesting_range_is("ℯ^(-x)", NAN, NAN, NAN, NAN); + assert_interesting_range_is("√(x^2+1)-x", NAN, NAN, NAN, NAN); + + assert_interesting_range_is("x-21", 20.5423145, 20.5423145, NAN, NAN); + assert_interesting_range_is("-11x+100", 9.45824909, 9.45824909, NAN, NAN); + assert_interesting_range_is("x^2-1", -8.634861, 8.634861, -1, -1); + assert_interesting_range_is("3x^2+x+10", -0.179552406, -0.179552406, NAN, NAN); + assert_interesting_range_is("(x+10)(x-10)", -17.205507, 17.205507, -100, -100); + assert_interesting_range_is("x(x-1)(x-2)(x-3)(x-4)(x-5)", -1.61903656, 7.01582479, -16.8975754, 4.9766078); + assert_interesting_range_is("1/x", -3.97572827, 3.97572827, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("1/(1-x)", -2.81933546, 4.96758938, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("1/(x-10)", 5.72560453, 15.8184233, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("√(x)", -2.09669948, 9.08569717, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("ln(x)", -1.61903656, 7.01582479, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("sin(x)", -13.2858067, 13.2858067, -0.985581219, 0.985581219); + assert_interesting_range_is("cos(x)", -906.33136, 906.33136, -0.996542156, 0.989880025, Degree); + assert_interesting_range_is("tan(x)", -14.4815292, 14.4815292, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("1/tan(x)", -987.901184, 987.901184, FLT_MAX, -FLT_MAX, Degree); + assert_interesting_range_is("asin(x)", -1.67939043, 1.67939043, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("acos(x)", -1.67939043, 1.67939043, FLT_MAX, -FLT_MAX, Degree); + assert_interesting_range_is("atan(x)", -3.34629107, 3.34629107, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("x×sin(x)", -14.4815292, 14.4815292, -4.81068802, 7.47825241); + assert_interesting_range_is("x×ln(x)", -0.314885706, 1.36450469, -0.367841482, -0.367841482); + assert_interesting_range_is("root(x^3+1,3)-x", -2.732898, 2.45420456, 1.58665824, 1.58665824); + assert_interesting_range_is("x^x", -0.745449066, 3.23027921, 0.692226887, 0.692226887); +} + + +void assert_refined_range_is(const char * definition, float xMin, float xMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + float yMin = FLT_MAX, yMax = -FLT_MAX; + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + ParametersPack aux(e, symbol, angleUnit); + Zoom::RefinedYRangeForDisplay(evaluate_expression, &xMin, &xMax, &yMin, &yMax, &globalContext, &aux); + quiz_assert_print_if_failure(range1D_matches(yMin, yMax, targetYMin, targetYMax), definition); +} + +QUIZ_CASE(poincare_zoom_refined_range) { + assert_refined_range_is("1", -10, 10, 1, 1); + assert_refined_range_is("-100", -10, 10, -100, -100); + assert_refined_range_is("x", -10, 10, -9.74683571, 9.74683571); + assert_refined_range_is("x^2", -10, 10, 0, 36.5035477); + assert_refined_range_is("-(x^3)", -10, 10, -133.769241, 133.769241); + assert_refined_range_is("10×x^4", -10, 10, 0, 4902.04102); + assert_refined_range_is("ℯ^(-x)", -10, 10, 0, 2.71828127); + assert_refined_range_is("x-21", 19.126959, 21.957670, -1.48373008, 0.92183876); + assert_refined_range_is("-11x+100", 8.806580, 10.109919, -8.44783878, 2.94615173); + assert_refined_range_is("x^2-1", -8.634861, 8.634861, -0.988053143, 26.5802021); + assert_refined_range_is("(x+10)(x-10)", -17.205507, 17.205507, -99.9525681, 156.399399); + assert_refined_range_is("x(x-1)(x-2)(x-3)(x-4)(x-5)", -1.61903656, 7.01582479, -16.8871994, 67.6706848); + assert_refined_range_is("1/x", -3.97572827, 3.97572827, -1.86576664, 1.86576664); + assert_refined_range_is("1/(x-10)", 5.72560453, 15.8184233, -1.45247293, 1.45247293); + assert_refined_range_is("√(x)", -2.09669948, 9.08569717, 0, 2.99067688); + assert_refined_range_is("ln(x)", -1.61903656, 7.01582479, -2.63196707, 1.93246615); + assert_refined_range_is("sin(x)", -13.2858067, 13.2858067, -0.998738647, 0.998738587); + assert_refined_range_is("cos(x)", -906.33136, 906.33136, -0.999904931, 0.998831093, Degree); + assert_refined_range_is("tan(x)", -14.4815292, 14.4815292, -2.86643958, 2.86643958); + assert_refined_range_is("asin(x)", -1.67939043, 1.67939043, -1.131253, 1.131253); + assert_refined_range_is("acos(x)", -1.67939043, 1.67939043, 0, 177.611237, Degree); + assert_refined_range_is("atan(x)", -3.34629107, 3.34629107, -1.27329516, 1.27329516); + assert_refined_range_is("x×sin(x)", -14.4815292, 14.4815292, -7.37234354, 7.37234354); + assert_refined_range_is("x×ln(x)", -0.314885706, 1.36450469, -0.367870897, 0.396377981); + assert_refined_range_is("x!", -10, 10, NAN, NAN); + assert_refined_range_is("xℯ^(1/x)", -1.3, 2.4, -0.564221799, 5.58451653); +} + +void assert_orthonormal_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + assert((std::isnan(targetXMin) && std::isnan(targetXMax) && std::isnan(targetYMin) && std::isnan(targetYMax)) + || float_equal((targetYMax - targetYMin) / (targetXMax - targetXMin), NormalRatio)); + float xMin, xMax, yMin, yMax; + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + ParametersPack aux(e, symbol, angleUnit); + Zoom::RangeWithRatioForDisplay(evaluate_expression, NormalRatio, &xMin, &xMax, &yMin, &yMax, &globalContext, &aux); + quiz_assert_print_if_failure(ranges_match(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); +} + +QUIZ_CASE(poincare_zoom_range_with_ratio) { + assert_orthonormal_range_is("1", NAN, NAN, NAN, NAN); + assert_orthonormal_range_is("x", -10, 10, -4.360695, 4.486482); + assert_orthonormal_range_is("x^2", -10, 10, -0.0527148247, 8.7944622); + assert_orthonormal_range_is("x^3", -10, 10, -3.91881895, 4.9283576); + assert_orthonormal_range_is("ℯ^x", -10, 10, -0.439413071, 8.40776348); + assert_orthonormal_range_is("ℯ^x+4", -10, 10, 3.56058741, 12.4077644); +} + +void assert_full_range_is(const char * definition, float xMin, float xMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + float yMin, yMax; + constexpr float stepDivisor = Ion::Display::Width; + const float step = (xMax - xMin) / stepDivisor; + ParametersPack aux(e, symbol, angleUnit); + Zoom::FullRange(&evaluate_expression, xMin, xMax, step, &yMin, &yMax, &globalContext, &aux); + quiz_assert_print_if_failure(range1D_matches(yMin, yMax, targetYMin, targetYMax), definition); +} + +QUIZ_CASE(poincare_zoom_full_range) { + assert_full_range_is("1", -10, 10, 1, 1); + assert_full_range_is("x", -10, 10, -10, 10); + assert_full_range_is("x-3", -10, 10, -13, 7); + assert_full_range_is("-6x", -10, 10, -60, 60); + assert_full_range_is("x^2", -10, 10, 0, 100); + assert_full_range_is("ℯ^x", -10, 10, 0.0000453999419, 22026.459); + assert_full_range_is("sin(x)", -3600, 3600, -1, 1, Degree); + assert_full_range_is("acos(x)", -10, 10, 0, 3.1415925); +} + +void assert_ranges_combine_to(int length, float * mins, float * maxs, float targetMin, float targetMax) { + float resMin, resMax; + Zoom::CombineRanges(length, mins, maxs, &resMin, &resMax); + quiz_assert(resMin == targetMin && resMax == targetMax); +} + +void assert_sanitized_range_is(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax) { + Zoom::SanitizeRange(&xMin, &xMax, &yMin, &yMax, NormalRatio); + quiz_assert(xMin == targetXMin && xMax == targetXMax && yMin == targetYMin && yMax == targetYMax); +} + +void assert_ratio_is_set_to(float yxRatio, float xMin, float xMax, float yMin, float yMax, bool shrink, float targetXMin, float targetXMax, float targetYMin, float targetYMax) { + float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; + assert(yxRatio == (targetYMax - targetYMin) / (targetXMax - targetXMin)); + Zoom::SetToRatio(yxRatio, &tempXMin, &tempXMax, &tempYMin, &tempYMax, shrink); + quiz_assert(ranges_match(tempXMin, tempXMax, tempYMin, tempYMax, targetXMin, targetXMax, targetYMin, targetYMax)); +} + +void assert_shrinks_to(float yxRatio, float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax) { + assert_ratio_is_set_to(yxRatio, xMin, xMax, yMin, yMax, true, targetXMin, targetXMax, targetYMin, targetYMax); +} + +void assert_expands_to(float yxRatio, float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax) { + assert_ratio_is_set_to(yxRatio, xMin, xMax, yMin, yMax, false, targetXMin, targetXMax, targetYMin, targetYMax); +} + +void assert_zooms_to(float ratio, float xCenter, float yCenter, float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax) { + float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; + Zoom::SetZoom(ratio, xCenter, yCenter, &tempXMin, &tempXMax, &tempYMin, &tempYMax); + quiz_assert(ranges_match(tempXMin, tempXMax, tempYMin, tempYMax, targetXMin, targetXMax, targetYMin, targetYMax)); +} + +QUIZ_CASE(poincare_zoom_utility) { + // Ranges combinations + { + constexpr int length = 1; + float mins[length] = {-10}; + float maxs[length] = {10}; + assert_ranges_combine_to(length, mins, maxs, -10, 10); + } + { + constexpr int length = 2; + float mins[length] = {-1, -2}; + float maxs[length] = {1, 2}; + assert_ranges_combine_to(length, mins, maxs, -2, 2); + } + { + constexpr int length = 2; + float mins[length] = {-1, 9}; + float maxs[length] = {1, 11}; + assert_ranges_combine_to(length, mins, maxs, -1, 11); + } + { + constexpr int length = 3; + float mins[length] = {-3, -2, -1}; + float maxs[length] = {1, 2, 3}; + assert_ranges_combine_to(length, mins, maxs, -3, 3); + } + + // Range sanitation + assert_sanitized_range_is( + -10, 10, -10, 10, + -10, 10, -10, 10); + assert_sanitized_range_is( + -10, 10, 100, 100, + -10, 10, 95.5764083, 104.423592); + assert_sanitized_range_is( + 3, -3, -10, 10, + -22.6060829, 22.6060829, -10, 10); + assert_sanitized_range_is( + 3, -3, 2, 2, + -10, 10, -2.42358828, 6.42358828); + assert_sanitized_range_is( + NAN, NAN, NAN, NAN, + -10, 10, -4.42358828, 4.42358828); + + // Ratio + assert_shrinks_to(1.f, + -10, 10, -10, 10, + -10, 10, -10, 10); + assert_expands_to(1.f, + -10, 10, -10, 10, + -10, 10, -10, 10); + assert_shrinks_to(0.5f, + -10, 10, -10, 10, + -10, 10, -5, 5); + assert_expands_to(0.5f, + -10, 10, -10, 10, + -20, 20, -10, 10); + assert_shrinks_to(1.33f, + -10, 10, -10, 10, + -7.518797, 7.518797, -10, 10); + assert_expands_to(1.33f, + -10, 10, -10, 10, + -10, 10, -13.3, 13.3); + + // Zoom + assert_zooms_to(1.f, 0, 0, + -10, 10, -10, 10, + -10, 10, -10, 10); + assert_zooms_to(0.5f, 0, 0, + -10, 10, -5, 5, + -5, 5, -2.5, 2.5); + assert_zooms_to(3.f, 0, 0, + -10, 10, -5, 5, + -30, 30, -15, 15); + assert_zooms_to(1.5f, 10, 5, + -10, 10, -5, 5, + -20, 10, -10, 5); + assert_zooms_to(0.25f, 2, -2, + -10, 10, -5, 5, + -1, 4, -2.75, -0.25); +} + diff --git a/python/port/mod/turtle/turtle.cpp b/python/port/mod/turtle/turtle.cpp index 0b8f85c90..abba04f46 100644 --- a/python/port/mod/turtle/turtle.cpp +++ b/python/port/mod/turtle/turtle.cpp @@ -177,6 +177,9 @@ void Turtle::setVisible(bool visible) { void Turtle::write(const char * string) { // We erase the turtle to redraw it on top of the text + if (isOutOfBounds()) { + return; + } erase(); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDContext * ctx = KDIonContext::sharedContext(); @@ -195,6 +198,10 @@ void Turtle::viewDidDisappear() { m_drawn = false; } +bool Turtle::isOutOfBounds() const { + return absF(x()) > k_maxPosition || absF(y()) > k_maxPosition; +}; + // Private functions void Turtle::setHeadingPrivate(mp_float_t angle) { @@ -255,6 +262,7 @@ bool Turtle::hasDotBuffers() { } KDRect Turtle::iconRect() const { + assert(!isOutOfBounds()); KDPoint iconOffset = KDPoint(-k_iconSize/2, -k_iconSize/2); return KDRect(position().translatedBy(iconOffset), k_iconSize, k_iconSize); } @@ -262,7 +270,7 @@ KDRect Turtle::iconRect() const { bool Turtle::draw(bool force) { MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); - if ((m_speed > 0 || force) && m_visible && !m_drawn && hasUnderneathPixelBuffer()) { + if ((m_speed > 0 || force) && m_visible && !m_drawn && hasUnderneathPixelBuffer() && !isOutOfBounds()) { KDContext * ctx = KDIonContext::sharedContext(); // Get the pixels underneath the turtle @@ -344,7 +352,7 @@ bool Turtle::dot(mp_float_t x, mp_float_t y) { MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); // Draw the dot if the pen is down - if (m_penDown && hasDotBuffers()) { + if (m_penDown && hasDotBuffers() && !isOutOfBounds()) { KDContext * ctx = KDIonContext::sharedContext(); KDRect rect( position(x, y).translatedBy(KDPoint(-m_penSize/2, -m_penSize/2)), @@ -369,6 +377,7 @@ bool Turtle::dot(mp_float_t x, mp_float_t y) { void Turtle::drawPaw(PawType type, PawPosition pos) { assert(!m_drawn); assert(m_underneathPixelBuffer != nullptr); + assert(!isOutOfBounds()); KDCoordinate pawOffset = 5; constexpr float crawlOffset = 0.6f; constexpr float angles[] = {M_PI_4, 3*M_PI_4, -3*M_PI_4, -M_PI_4}; @@ -391,7 +400,7 @@ void Turtle::drawPaw(PawType type, PawPosition pos) { } void Turtle::erase() { - if (!m_drawn || m_underneathPixelBuffer == nullptr) { + if (!m_drawn || m_underneathPixelBuffer == nullptr || isOutOfBounds()) { return; } KDContext * ctx = KDIonContext::sharedContext(); diff --git a/python/port/mod/turtle/turtle.h b/python/port/mod/turtle/turtle.h index bf0045ee9..e64808396 100644 --- a/python/port/mod/turtle/turtle.h +++ b/python/port/mod/turtle/turtle.h @@ -82,6 +82,13 @@ public: void viewDidDisappear(); + /* isOutOfBounds returns true if nothing should be drawn at current position. + * We avoid drawing at extreme position (far from screen bounds) to prevent + * coordinate overflows. However, this solution makes the turtle go faster + * when out of bound, and can prevent text that would have been visible to be + * drawn. We use very large bounds to temper these effects. */ + bool isOutOfBounds() const; + private: static constexpr mp_float_t k_headingScale = M_PI / 180; /* The Y axis is oriented upwards in Turtle and downwards in Kandinsky, so we @@ -94,6 +101,7 @@ private: static constexpr KDColor k_defaultColor = KDColorBlack; static constexpr uint8_t k_defaultPenSize = 1; static constexpr const KDFont * k_font = KDFont::LargeFont; + static constexpr mp_float_t k_maxPosition = KDCOORDINATE_MAX * 0.75f; enum class PawType : uint8_t { FrontRight = 0, diff --git a/python/port/port.cpp b/python/port/port.cpp index 63a9a3deb..1f83a01dd 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -164,15 +164,16 @@ void MicroPython::init(void * heapStart, void * heapEnd) { static mp_obj_t pystack[1024]; mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); #endif - - volatile int stackTop; - void * stackTopAddress = (void *)(&stackTop); /* We delimit the stack part that will be used by Python. The stackTop is the * address of the first object that can be allocated on Python stack. This * boundaries are used: * - by gc_collect to determine where to collect roots of the objects that * must be kept on the heap - * - to check if the maximal recursion depth has been reached. */ + * - to check if the maximal recursion depth has been reached. + * Current stack pointer could go backward after initialization. A stack start + * pointer defined in main is therefore used. */ + void * stackTopAddress = Ion::stackStart(); + #if MP_PORT_USE_STACK_SYMBOLS mp_stack_set_top(stackTopAddress); size_t stackLimitInBytes = (char *)stackTopAddress - (char *)&_stack_end; @@ -218,7 +219,7 @@ void MicroPython::collectRootsAtAddress(char * address, int byteLength) { alignedByteLength += reinterpret_cast(address) & bitMaskOnes; assert(alignedAddress % ((uintptr_t)sizeof(uintptr_t)) == 0); - gc_collect_root((void **)alignedAddress, byteLength / sizeof(uintptr_t)); + gc_collect_root((void **)alignedAddress, alignedByteLength / sizeof(uintptr_t)); } KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ @@ -243,7 +244,8 @@ KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ NamedColor("pink", Palette::Pink), NamedColor("orange", Palette::Orange), NamedColor("purple", Palette::Purple), - NamedColor("grey", Palette::GreyDark), + NamedColor("gray", Palette::GrayDark), + NamedColor("grey", Palette::GrayDark), NamedColor("cyan", Palette::Cyan), NamedColor("magenta", Palette::Magenta) }; @@ -262,12 +264,12 @@ KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ return KDColor::RGB24(colorInt); } - mp_float_t greyLevel = mp_obj_float_get(mp_parse_num_decimal(color, strlen(color), false, false, NULL)); - if (greyLevel >= 0.0 && greyLevel <= 1.0) { - uint8_t color = maxColorIntensity * (float) greyLevel; + mp_float_t grayLevel = mp_obj_float_get(mp_parse_num_decimal(color, strlen(color), false, false, NULL)); + if (grayLevel >= 0.0 && grayLevel <= 1.0) { + uint8_t color = maxColorIntensity * (float) grayLevel; return KDColor::RGB888(color, color, color); } - mp_raise_ValueError("Grey levels are between 0.0 and 1.0"); + mp_raise_ValueError("Gray levels are between 0.0 and 1.0"); } else if(mp_obj_is_int(input)) { mp_raise_TypeError("Int are not colors"); //See https://github.com/numworks/epsilon/issues/1533#issuecomment-618443492 diff --git a/quiz/src/runner.cpp b/quiz/src/runner.cpp index ef4b883c3..996a7ec96 100644 --- a/quiz/src/runner.cpp +++ b/quiz/src/runner.cpp @@ -31,10 +31,16 @@ static inline void ion_main_inner() { void ion_main(int argc, const char * const argv[]) { - // Initialize the backlight Ion::Backlight::init(); - // Initialize Poincare::TreePool::sharedPool - Poincare::Init(); + Poincare::Init(); // Initialize Poincare::TreePool::sharedPool +#if !PLATFORM_DEVICE + /* s_stackStart must be defined as early as possible to ensure that there + * cannot be allocated memory pointers before. Otherwise, with MicroPython for + * example, stack pointer could go backward after initialization and allocated + * memory pointers could be overlooked during mark procedure. */ + volatile int stackTop; + Ion::setStackStart((void *)(&stackTop)); +#endif Poincare::ExceptionCheckpoint ecp; if (ExceptionRun(ecp)) { diff --git a/themes/themes_manager.py b/themes/themes_manager.py index 7690a2e74..d71399573 100644 --- a/themes/themes_manager.py +++ b/themes/themes_manager.py @@ -94,11 +94,11 @@ def write_palette_h(data, file_p): file_p.write(" constexpr static KDColor YellowLight = KDColor::RGB24(0xffcc7b);\n") file_p.write(" constexpr static KDColor PurpleBright = KDColor::RGB24(0x656975);\n") file_p.write(" constexpr static KDColor PurpleDark = KDColor::RGB24(0x414147);\n") - file_p.write(" constexpr static KDColor GreyWhite = KDColor::RGB24(0xf5f5f5);\n") - file_p.write(" constexpr static KDColor GreyBright = KDColor::RGB24(0xececec);\n") - file_p.write(" constexpr static KDColor GreyMiddle = KDColor::RGB24(0xd9d9d9);\n") - file_p.write(" constexpr static KDColor GreyDark = KDColor::RGB24(0xa7a7a7);\n") - file_p.write(" constexpr static KDColor GreyVeryDark = KDColor::RGB24(0x8c8c8c);\n") + file_p.write(" constexpr static KDColor GrayWhite = KDColor::RGB24(0xf5f5f5);\n") + file_p.write(" constexpr static KDColor GrayBright = KDColor::RGB24(0xececec);\n") + file_p.write(" constexpr static KDColor GrayMiddle = KDColor::RGB24(0xd9d9d9);\n") + file_p.write(" constexpr static KDColor GrayDark = KDColor::RGB24(0xa7a7a7);\n") + file_p.write(" constexpr static KDColor GrayVeryDark = KDColor::RGB24(0x8c8c8c);\n") file_p.write(" constexpr static KDColor Select = KDColor::RGB24(0xd4d7e0);\n") file_p.write(" constexpr static KDColor SelectDark = KDColor::RGB24(0xb0b8d8);\n") file_p.write(" constexpr static KDColor WallScreen = KDColor::RGB24(0xf7f9fa);\n")