From d7a0914d13594ac877f0258e518d060e4d4fccbf Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 20 May 2020 11:09:14 +0200 Subject: [PATCH 001/560] [Apps/CalculationStore] Changed the way to store calculations -No more limit to the number of calculations -All calculations are memoized -Added tests to test/calculation_store.cpp Change-Id: Ia10e2b009576eaf4496ba44a0c88f6e7e76f6cef --- apps/calculation/app.cpp | 4 - apps/calculation/app.h | 1 - apps/calculation/calculation.h | 4 +- apps/calculation/calculation_store.cpp | 228 ++++++++------------ apps/calculation/calculation_store.h | 68 +++--- apps/calculation/test/calculation_store.cpp | 174 ++++++++------- 6 files changed, 222 insertions(+), 257 deletions(-) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index a880ec2244a..e2a501a6211 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -34,10 +34,6 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -void App::Snapshot::tidy() { - m_calculationStore.tidy(); -} - App::App(Snapshot * snapshot) : ExpressionFieldDelegateApp(snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), diff --git a/apps/calculation/app.h b/apps/calculation/app.h index be7f73e17a2..9df67194e89 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -24,7 +24,6 @@ class App : public Shared::ExpressionFieldDelegateApp { Descriptor * descriptor() override; CalculationStore * calculationStore() { return &m_calculationStore; } private: - void tidy() override; CalculationStore m_calculationStore; }; static App * app() { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index ed6e9f2b29d..10f4e06621f 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, @@ -48,7 +49,7 @@ friend CalculationStore; * 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,7 +94,6 @@ friend CalculationStore; // Additional Information AdditionalInformationType additionalInformationType(Poincare::Context * context); private: - 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 d3968442416..be2055cedbf 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -11,58 +11,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; - } - // 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--; + // 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)); } - 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 { @@ -70,23 +59,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 @@ -109,30 +98,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 @@ -143,36 +129,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())); + if (i == 0) { + ExpiringPointer lastCalculationPointer = calculationAtIndex(0); + m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer()); + m_numberOfCalculations--; + return; + } + 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--; - 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); +// 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; } -void CalculationStore::tidy() { - if (m_slidedBuffer) { - deleteAll(); - return; - } - resetMemoizedModelsAfterCalculationIndex(-1); +// 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); @@ -191,92 +183,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 9f69d3edf93..53bc3fdf4f6 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 @@ class CalculationStore { }; 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/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index eb2045778e5..1ce0c4fe1b4 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; - - 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); + CalculationStore store(calculationBuffer,calculationBufferSize); + + 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; - - 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); + CalculationStore store(calculationBuffer,calculationBufferSize); + + 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); } From d77e924014413e03f41cf5d3227f286a17d5449b Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 20 May 2020 11:12:56 +0200 Subject: [PATCH 002/560] [Apps/Calculation] Modified the calculationStore of additional results -Additional results are now stored in smaller calculationStores Change-Id: I7d09bfb09e57cbb80e9385668adeb29a31398bc0 --- .../additional_outputs/illustrated_list_controller.cpp | 1 + .../additional_outputs/illustrated_list_controller.h | 3 +++ apps/calculation/app.cpp | 4 ++++ apps/calculation/app.h | 5 +++++ apps/calculation/edit_expression_controller.h | 1 - 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp index 370729bb204..b850ad48a5b 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 ea5b484deb9..c0137969ebb 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.h +++ b/apps/calculation/additional_outputs/illustrated_list_controller.h @@ -39,7 +39,10 @@ class IllustratedListController : public ListController, public SelectableTableV 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/app.cpp b/apps/calculation/app.cpp index e2a501a6211..08d23b4bb47 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -34,6 +34,10 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } +App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculationBufferSize) +{ +} + App::App(Snapshot * snapshot) : ExpressionFieldDelegateApp(snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), diff --git a/apps/calculation/app.h b/apps/calculation/app.h index 9df67194e89..6914c82df46 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -19,12 +19,16 @@ class App : public Shared::ExpressionFieldDelegateApp { }; class Snapshot : public ::App::Snapshot { public: + Snapshot(); App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; CalculationStore * calculationStore() { return &m_calculationStore; } private: 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]; }; static App * app() { return static_cast(Container::activeApp()); @@ -34,6 +38,7 @@ class App : public Shared::ExpressionFieldDelegateApp { bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override; // TextFieldDelegateApp bool isAcceptableExpression(const Poincare::Expression expression) override; + private: App(Snapshot * snapshot); HistoryController m_historyController; diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index eee1c4033ea..1605f8b8d12 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 { From 1d74a8f757b7d955ad116d5ac19a53e4bc0c9ec2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 26 May 2020 11:07:37 +0200 Subject: [PATCH 003/560] [poincare/power] Fixed missing template definitions Change-Id: I9e7db458c97cc1fba29689690b67c3c678976405 --- poincare/src/power.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index dab4c670720..7d6f48ed848 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -1437,5 +1437,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); } From 0454b65a94c8e353550d24717f0b33c6851f9b01 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 26 May 2020 11:08:59 +0200 Subject: [PATCH 004/560] [ion/device/usb] Fixed missing static member definitions Change-Id: Iedf6ce51d3213f9265fec247c7bdfda4e3304d3a --- ion/src/device/shared/usb/stack/endpoint0.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ion/src/device/shared/usb/stack/endpoint0.cpp b/ion/src/device/shared/usb/stack/endpoint0.cpp index b97607e1b24..32121076ffd 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 From e8505d79e723162e499f275fc55c4330d9c6a8ef Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 26 May 2020 11:09:46 +0200 Subject: [PATCH 005/560] [ion/device/exam_mode] Fixed missing parentheses in assertion Change-Id: If10b6ea98ed224d79cfa50f38f9e229331d0f9f9 --- ion/src/device/shared/drivers/exam_mode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index a4aa2014355..8efdb9d8da9 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -51,7 +51,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++; From d3727dde97fe832197332478926c93fe53d0aea7 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 19 May 2020 14:10:03 +0200 Subject: [PATCH 006/560] [regression] Add unit tests for regression metrics Change-Id: I25d7fd89053a1bd5fb369c8c145dba92d188f550 --- apps/regression/test/model.cpp | 149 ++++++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 29 deletions(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 0abbad61a09..369af466bc5 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -12,42 +12,67 @@ 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; - int series = 0; - Regression::Store store; +double relativeError(double observedValue, double expectedValue) { + return std::fabs((observedValue - expectedValue) / expectedValue); +} + +void assert_value_is(double observedValue, double expectedValue) { + double precision = 0.01; + quiz_assert(relativeError(observedValue, expectedValue) < precision); +} - // Set the points and the regression type +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); - store.set(yi[i], series, 1, 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; + + setRegressionPoints(&store, series, numberOfPoints, xi, yi); + store.setSeriesRegressionType(series, modelType); Shared::GlobalContext globalContext; RegressionContext context(&store, &globalContext); - store.setSeriesRegressionType(series, modelType); - // Compute the coefficients + // 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); + assert_value_is(coefficients[i], trueCoefficients[i]); } + + // Compute and compare r2 + double r2 = store.squaredCorrelationCoefficient(series); + assert_value_is(r2, trueR2); } 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,63 +80,72 @@ 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(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); } 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); } // No case for trigonometric regression, because it has no unique solution @@ -123,11 +157,68 @@ 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); +} + +// 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); + assert_value_is(mean, trueMean); + assert_value_is(sum, trueSum); + assert_value_is(squaredSum, trueSquaredSum); + assert_value_is(standardDeviation, trueStandardDeviation); + assert_value_is(variance, trueVariance); +} + +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); +} + +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); + + // Compute and compare the regression calculations metrics + double covariance = store.covariance(series); + double productSum = store.columnProductSum(series); + double r = store.correlationCoefficient(series); + assert_value_is(covariance, trueCovariance); + assert_value_is(productSum, trueProductSum); + assert_value_is(r, trueR); +} + +QUIZ_CASE(regression_calculation) { + double x[] = {1.0, 50.0, 34.0, 67.0, 20.0}; + double y[] = {71.860, 2775514, 979755.1, 6116830, 233832.9}; + double covariance = 4.7789036e7; + double productSum = 586591713.26; + double r = 0.919088; + assert_regression_calculations_is(x, y, 5, covariance, productSum, r); } From 5c95cbd1d36c9ebc15c1bc61833e96b2881ad663 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 19 May 2020 15:40:46 +0200 Subject: [PATCH 007/560] [regression] Add r2 to any regression stats Change-Id: I3088dd66b1c3f48973b8a9277de687bdd24e094b --- apps/regression/calculation_controller.cpp | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index f31c836bf13..1f3d97efa41 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->squaredCorrelationCoefficient(seriesNumber); + } 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) { From db69f6a6f517ed2d40857947eee6a040d6eddd01 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 20 May 2020 14:04:42 +0200 Subject: [PATCH 008/560] [regression] Implement R2 computation using evaluated values Change-Id: Iecf8cb84f092c2ec8a9bc17bc0265d7dddaac36c --- apps/regression/calculation_controller.cpp | 2 +- apps/regression/graph_controller.cpp | 2 +- apps/regression/store.cpp | 83 ++++++++++++++++------ apps/regression/store.h | 7 +- apps/regression/test/model.cpp | 2 +- 5 files changed, 69 insertions(+), 27 deletions(-) diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 1f3d97efa41..eb262a2efc5 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -237,7 +237,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int if ((modelType == Model::Type::Linear && j == numberRows - 2) || j == numberRows - 1) { double calculation; if (j == numberRows - 1) { - calculation = m_store->squaredCorrelationCoefficient(seriesNumber); + calculation = m_store->determinationCoefficientForSeries(seriesNumber, globContext); } else { calculation = m_store->correlationCoefficient(seriesNumber); } diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index b207c3ed9c3..1bb6d962584 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -200,7 +200,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); diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index cfba25903c8..8b9e438cb86 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -149,8 +149,8 @@ void Store::setDefault() { } } float range = maxX - minX; - setXMin(minX - k_displayHorizontalMarginRatio*range); - setXMax(maxX + k_displayHorizontalMarginRatio*range); + setXMin(minX - k_displayHorizontalMarginRatio * range); + setXMax(maxX + k_displayHorizontalMarginRatio * range); } /* Series */ @@ -161,7 +161,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); @@ -179,10 +179,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); } @@ -212,12 +227,13 @@ float Store::minValueOfColumn(int series, int i) const { double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) 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); } + result += value * value; } return result; } @@ -225,22 +241,24 @@ double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) const 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 { double mean = meanOfColumn(series, i, lnOfSeries); - return squaredValueSumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series) - mean*mean; + return squaredValueSumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series) - mean * mean; } double Store::standardDeviationOfColumn(int series, int i, bool lnOfSeries) const { @@ -248,7 +266,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 +288,39 @@ 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 regressions, it is equal to the square of the correlation coefficient between + * the series Y and the evaluated values from the series X and the selected model + * Computing the coefficient using the latter equality would require more calls to the evaluated + * values and would be less precise. */ + // 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 residual = m_data[series][1][k] - yValueForXValue(series, m_data[series][0][k], globalContext); + ssr += residual * residual; + // Difference between the observation and the overall observations mean + double difference = m_data[series][1][k] - mean; + sst += difference * difference; + } + return sst == 0.0 ? 1.0 : 1.0 - ssr / sst; } Model * Store::regressionModel(int index) { diff --git a/apps/regression/store.h b/apps/regression/store.h index 92acd0f8f7e..9a305f34155 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -57,7 +57,9 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai 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 squaredValueSumOfColumn(int series, int i, bool lnOfSeries = false) const; double columnProductSum(int series, bool lnOfSeries = false) const; @@ -69,9 +71,9 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai 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 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 ? @@ -90,6 +92,7 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai 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/test/model.cpp b/apps/regression/test/model.cpp index 369af466bc5..b92e1cce5ce 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -47,7 +47,7 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T } // Compute and compare r2 - double r2 = store.squaredCorrelationCoefficient(series); + double r2 = store.determinationCoefficientForSeries(series, &globalContext); assert_value_is(r2, trueR2); } From d16e49fc5f31a9115d939891defe121b335b6bdd Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Sun, 24 May 2020 15:26:26 +0200 Subject: [PATCH 009/560] [apps/regression] Improve Variance precision Change-Id: Ia9e406b14c0baec76835e226a7801ba73ff9174e --- apps/regression/store.cpp | 9 +++++++-- apps/regression/store.h | 1 + apps/regression/test/model.cpp | 21 +++++++++++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 8b9e438cb86..e5765f96f77 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -225,7 +225,7 @@ 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; const int numberOfPairs = numberOfPairsOfSeries(series); for (int k = 0; k < numberOfPairs; k++) { @@ -233,11 +233,16 @@ double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) const if (lnOfSeries) { 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++) { @@ -258,7 +263,7 @@ double Store::meanOfColumn(int series, int i, bool lnOfSeries) const { double Store::varianceOfColumn(int series, int i, bool lnOfSeries) const { 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 { diff --git a/apps/regression/store.h b/apps/regression/store.h index 9a305f34155..ddcb5dd43a0 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -61,6 +61,7 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai 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; diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index b92e1cce5ce..0684355076e 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -13,12 +13,18 @@ using namespace Regression; * then filling Y1 with the regression formula + random()/10. */ double relativeError(double observedValue, double expectedValue) { + assert(expectedValue != 0.0); return std::fabs((observedValue - expectedValue) / expectedValue); } void assert_value_is(double observedValue, double expectedValue) { - double precision = 0.01; - quiz_assert(relativeError(observedValue, expectedValue) < precision); + if (expectedValue != 0.0) { + double precision = 0.01; + quiz_assert(relativeError(observedValue, expectedValue) < precision); + } else { + // The expected value can't be null for relativeError, the exact value is then expected + quiz_assert(observedValue == expectedValue); + } } void setRegressionPoints(Regression::Store * store, int series, int numberOfPoints, double * xi, double * yi = nullptr) { @@ -199,6 +205,17 @@ QUIZ_CASE(column_calculation) { 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.8584, -996.8584, -996.8584}; + double mean = -996.8584; + double sum = -2990.5752; + double squaredSum = 2.98118000895168e6; + double standardDeviation = 0; + double variance = 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; From 29b0e86225eb0ce84d9016f6163e7fb869e87975 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 26 May 2020 16:30:25 +0200 Subject: [PATCH 010/560] [quiz] Improve test possibilities for regression and statistics Change-Id: I4414fad24e10dcbd56cd9aff1e35e00ba66dda2c --- apps/regression/test/model.cpp | 83 +++++++++------ apps/statistics/test/store.cpp | 152 +++++++++++++++++++--------- poincare/include/poincare/helpers.h | 1 + poincare/src/helpers.cpp | 17 ++++ 4 files changed, 173 insertions(+), 80 deletions(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 0684355076e..16b21611421 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -5,6 +5,7 @@ #include "../model/model.h" #include "../regression_context.h" #include "../store.h" +#include using namespace Poincare; using namespace Regression; @@ -12,21 +13,6 @@ 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. */ -double relativeError(double observedValue, double expectedValue) { - assert(expectedValue != 0.0); - return std::fabs((observedValue - expectedValue) / expectedValue); -} - -void assert_value_is(double observedValue, double expectedValue) { - if (expectedValue != 0.0) { - double precision = 0.01; - quiz_assert(relativeError(observedValue, expectedValue) < precision); - } else { - // The expected value can't be null for relativeError, the exact value is then expected - quiz_assert(observedValue == expectedValue); - } -} - 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); @@ -45,16 +31,21 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T Shared::GlobalContext globalContext; RegressionContext context(&store, &globalContext); + double precision = 1e-2; + // When trueCoefficients = 0, a DBL_EPSILON reference ensures that the only accepted errors are due to double approximations + double reference = 100.0 * DBL_EPSILON; + // Compute and compare the coefficients double * coefficients = store.coefficientsForSeries(series, &context); int numberOfCoefs = store.modelForSeries(series)->numberOfCoefficients(); for (int i = 0; i < numberOfCoefs; i++) { - assert_value_is(coefficients[i], trueCoefficients[i]); + quiz_assert(Helpers::IsApproximatelyEqual(coefficients[i], trueCoefficients[i], precision, reference)); } - // Compute and compare r2 + // Compute and check r2 value and sign double r2 = store.determinationCoefficientForSeries(series, &globalContext); - assert_value_is(r2, trueR2); + quiz_assert(r2 >= 0.0); + quiz_assert(Helpers::IsApproximatelyEqual(r2, trueR2, precision, reference)); } QUIZ_CASE(linear_regression) { @@ -188,11 +179,26 @@ void assert_column_calculations_is(double * xi, int numberOfPoints, double trueM double squaredSum = store.squaredValueSumOfColumn(series,0); double standardDeviation = store.standardDeviationOfColumn(series,0); double variance = store.varianceOfColumn(series,0); - assert_value_is(mean, trueMean); - assert_value_is(sum, trueSum); - assert_value_is(squaredSum, trueSquaredSum); - assert_value_is(standardDeviation, trueStandardDeviation); - assert_value_is(variance, trueVariance); + + // 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(Helpers::IsApproximatelyEqual(variance, trueVariance, precision, reference)); + quiz_assert(Helpers::IsApproximatelyEqual(squaredSum, trueSquaredSum, precision, reference)); + + // adapt the reference + reference = std::sqrt(trueSquaredSum); + + quiz_assert(Helpers::IsApproximatelyEqual(mean, trueMean, precision, reference)); + quiz_assert(Helpers::IsApproximatelyEqual(sum, trueSum, precision, reference)); + quiz_assert(Helpers::IsApproximatelyEqual(standardDeviation, trueStandardDeviation, precision, reference)); } QUIZ_CASE(column_calculation) { @@ -207,12 +213,12 @@ QUIZ_CASE(column_calculation) { QUIZ_CASE(constant_column_calculation) { // This data produced a negative variance before - double x[] = {-996.8584, -996.8584, -996.8584}; - double mean = -996.8584; - double sum = -2990.5752; - double squaredSum = 2.98118000895168e6; - double standardDeviation = 0; - double variance = 0; + 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); } @@ -222,18 +228,29 @@ void assert_regression_calculations_is(double * xi, double * yi, int numberOfPoi 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(Helpers::IsApproximatelyEqual(covariance, trueCovariance, precision, trueProductSum / numberOfPoints)); + quiz_assert(Helpers::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); - assert_value_is(covariance, trueCovariance); - assert_value_is(productSum, trueProductSum); - assert_value_is(r, trueR); + quiz_assert(r >= 0.0); + quiz_assert(Helpers::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, 233832.9}; + double y[] = {71.860, 2775514, 979755.1, 6116830.0, 233832.9}; double covariance = 4.7789036e7; double productSum = 586591713.26; double r = 0.919088; diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index e5a08264de9..1126dd8d9fc 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -3,41 +3,74 @@ #include #include #include "../store.h" +#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*/) + || Helpers::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) { +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 trueFirstQuartile, double trueThirdQuartile, double trueQuartileRange, 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; + // 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 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 firstQuartile = store.firstQuartile(seriesIndex); + double thirdQuartile = store.thirdQuartile(seriesIndex); + double quartileRange = store.quartileRange(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(quartileRange >= 0.0); + quiz_assert(squaredValueSum >= 0.0); + + 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(firstQuartile, trueFirstQuartile, precision, reference); + assert_value_approximately_equal_to(thirdQuartile, trueThirdQuartile, 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); + assert_value_approximately_equal_to(quartileRange, trueQuartileRange, 0.0, 0.0); } QUIZ_CASE(data_statistics) { @@ -46,11 +79,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, @@ -72,11 +105,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, @@ -96,12 +129,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, @@ -122,11 +155,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, @@ -147,11 +180,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, @@ -172,11 +205,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, @@ -197,11 +230,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, @@ -222,11 +255,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, @@ -242,6 +275,31 @@ QUIZ_CASE(data_statistics) { /* 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, + /* firstQuartile */ -996.85840734641, + /* thirdQuartile */ -996.85840734641, + /* quartileRange */ 0.0, + /* median */ -996.85840734641, + /* sum */ -8971.72566611769, + /* squaredValueSum */ 8943540.158675); } } diff --git a/poincare/include/poincare/helpers.h b/poincare/include/poincare/helpers.h index 517a64d2a2d..dab8dd8c7de 100644 --- a/poincare/include/poincare/helpers.h +++ b/poincare/include/poincare/helpers.h @@ -11,6 +11,7 @@ namespace Helpers { 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); +bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference); } diff --git a/poincare/src/helpers.cpp b/poincare/src/helpers.cpp index af8275f0867..a412693f8cc 100644 --- a/poincare/src/helpers.cpp +++ b/poincare/src/helpers.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace Poincare { @@ -97,5 +98,21 @@ bool Rotate(uint32_t * dst, uint32_t * src, size_t len) { return true; } +bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference) { + /* Return true if observedValue and expectedValue are approximately equal, according to precision and reference parameters */ + 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; +} + } } From eed1648363d9a74cfccf5802aa8bee0b84acbe00 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 26 May 2020 16:34:17 +0200 Subject: [PATCH 011/560] [apps/statistics] Improve variance accuracy for statistics Change-Id: Ie8f6f93c8e95940662e72a23619b53ac4ab0d6b6 --- apps/regression/store.cpp | 2 ++ apps/statistics/store.cpp | 13 ++++++++++--- apps/statistics/store.h | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index e5765f96f77..d49665b14d6 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -262,6 +262,8 @@ double Store::meanOfColumn(int series, int i, bool lnOfSeries) const { } 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 squaredOffsettedValueSumOfColumn(series, i, lnOfSeries, mean)/numberOfPairsOfSeries(series); } diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 059467a97ff..20733a6e903 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -153,8 +153,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 { @@ -193,10 +195,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; } diff --git a/apps/statistics/store.h b/apps/statistics/store.h index 119068da818..3342021eb06 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -43,6 +43,7 @@ class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairSt 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; From 051e6088357bfdb8cc9727d07de845ea45234ab3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 19 May 2020 11:25:59 +0200 Subject: [PATCH 012/560] [poincare] Added method didDerivate to Expression and ExpressionNode This method is to be implemented by derivable expression subclasses, for use in Derivative::shallowReduce. It performs the calculation for the derivative, and returns whether calculations happened. Change-Id: I13cdb131e2044578392f5178a9f389314c1c4c8a --- poincare/Makefile | 1 + poincare/include/poincare/derivative.h | 2 +- poincare/include/poincare/expression.h | 7 ++++++ poincare/include/poincare/expression_node.h | 1 + poincare/src/derivative.cpp | 26 +++++++++++++++++---- poincare/src/expression_node.cpp | 4 ++++ poincare/test/derivative.cpp | 18 ++++++++++++++ 7 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 poincare/test/derivative.cpp diff --git a/poincare/Makefile b/poincare/Makefile index bac81600c23..b1f4215c6d5 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -162,6 +162,7 @@ tests_src += $(addprefix poincare/test/,\ arithmetic.cpp\ context.cpp\ erf_inv.cpp \ + derivative.cpp\ expression.cpp\ expression_order.cpp\ expression_properties.cpp\ diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index ad622c73ba0..bfa1a73191e 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -53,7 +53,7 @@ class Derivative final : public ParameteredExpression { static Expression UntypedBuilder(Expression children); static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("diff", 3, &UntypedBuilder); - Expression shallowReduce(Context * context); + Expression shallowReduce(ExpressionNode::ReductionContext context); }; } diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 6bdb4008e6f..05944ba41ab 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -380,6 +380,12 @@ class Expression : public TreeHandle { // 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 didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return node()->didDerivate(reductionContext, symbol, symbolValue); } + private: static constexpr int k_maxSymbolReplacementsCount = 10; static bool sSymbolReplacementsCountLock; @@ -407,6 +413,7 @@ class Expression : public TreeHandle { Expression defaultHandleUnitsInChildren(); // Children must be reduced Expression shallowReduceUsingApproximation(ExpressionNode::ReductionContext reductionContext); Expression defaultShallowBeautify() { return *this; } + bool defaultDidDerivate() { return false; } /* Approximation */ template Evaluation approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 2d3b33a33c9..4ab8b1c644a 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -223,6 +223,7 @@ class ExpressionNode : public TreeNode { /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); + /*!*/ virtual bool didDerivate(ReductionContext, Expression symbol, Expression symbolValue); /* 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/src/derivative.cpp b/poincare/src/derivative.cpp index 3a9b857a896..c010f210867 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -36,7 +36,7 @@ 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 @@ -151,7 +151,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 +159,30 @@ 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 didDerivate having to return the derivative. */ + if (!derivand.didDerivate(reductionContext, symbol, symbolValue)) { + return *this; + } + /* Updates the value of derivand, because didDerivate may call + * replaceWithInplace on it */ + derivand = childAtIndex(0); + /* Deep reduces the child, because didDerivate may not preserve its reduced + * status. */ + derivand = derivand.deepReduce(reductionContext); + replaceWithInPlace(derivand); + return derivand; + } Expression Derivative::UntypedBuilder(Expression children) { diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index fe117da7eb3..5c02fe3e5e1 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -127,6 +127,10 @@ Expression ExpressionNode::shallowBeautify(ReductionContext reductionContext) { return Expression(this).defaultShallowBeautify(); } +bool ExpressionNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Expression(this).defaultDidDerivate(); +} + bool ExpressionNode::isOfType(Type * types, int length) const { for (int i = 0; i < length; i++) { if (type() == types[i]) { diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp new file mode 100644 index 00000000000..8fe59061bb8 --- /dev/null +++ b/poincare/test/derivative.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include +#include "helper.h" + +using namespace Poincare; + +void assert_differentiates_as(Expression expression, Expression derivative, const char * information) { + Shared::GlobalContext globalContext; + Expression expressionReduced = expression.reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); + quiz_assert_print_if_failure(expressionReduced.isIdenticalTo(derivative), information); +} + +QUIZ_CASE(poincare_differential_addition) { + +} \ No newline at end of file From c65687e9f6c75bac198ae5c9db14024f671292e2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 22 May 2020 17:58:48 +0200 Subject: [PATCH 013/560] [poincare] Implemented didDerivate methods for Number, Symbol and Addition Number's subclasses, with the exception of Undefined and Unreal, derive as zero. Symbols derive as expected. Derivation propagates as expected on additions. Change-Id: Icedbb1dfac8099347a19c14bf2537011b2e8b199 --- poincare/include/poincare/addition.h | 4 ++++ poincare/include/poincare/number.h | 4 ++++ poincare/include/poincare/symbol.h | 4 ++++ poincare/include/poincare/undefined.h | 4 ++++ poincare/include/poincare/unreal.h | 4 ++++ poincare/src/addition.cpp | 13 +++++++++++++ poincare/src/number.cpp | 10 ++++++++++ poincare/src/symbol.cpp | 9 +++++++++ poincare/src/unreal.cpp | 2 +- poincare/test/derivative.cpp | 15 ++++++++++++++- 10 files changed, 67 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index d4930372392..77dedbc7c2d 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -57,6 +57,9 @@ class AdditionNode final : public NAryExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext reductionContext) override; + // Derivation + bool didDerivate(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(); @@ -81,6 +84,7 @@ class Addition final : public NAryExpression { // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + bool didDerivate(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/number.h b/poincare/include/poincare/number.h index c8b4f1ab747..ca7821d4ab3 100644 --- a/poincare/include/poincare/number.h +++ b/poincare/include/poincare/number.h @@ -26,6 +26,8 @@ class NumberNode : public ExpressionNode { double doubleApproximation() const; + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + }; class Number : public Expression { @@ -53,6 +55,8 @@ class Number : public Expression { 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(); } + + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); protected: Number() : Expression() {} NumberNode * node() const { return static_cast(Expression::node()); } diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 9c78b8c432f..bf29727432b 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -37,6 +37,9 @@ class SymbolNode final : public SymbolAbstractNode { Expression deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) override; LayoutShape leftLayoutShape() const override; + /* Derivation */ + bool didDerivate(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); } @@ -69,6 +72,7 @@ class Symbol final : public SymbolAbstract { // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool didDerivate(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/undefined.h b/poincare/include/poincare/undefined.h index 90f0078e3c7..ae24946669c 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -29,6 +29,10 @@ class UndefinedNode : public NumberNode { return templatedApproximate(); } + /* Derivation + * Overrides NumberNode's didDerivate to revert to a non-derivable state */ + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + // 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/unreal.h b/poincare/include/poincare/unreal.h index 4b31ef18241..c36e1db83b8 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -27,6 +27,10 @@ class UnrealNode final : public UndefinedNode { return templatedApproximate(); } + /* Derivation + * Overrides NumberNode's didDerivate to revert to a non-derivable state */ + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + // 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/src/addition.cpp b/poincare/src/addition.cpp index 0eaf6aa9453..fd1e9da3474 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,11 @@ Expression AdditionNode::shallowBeautify(ReductionContext reductionContext) { return Addition(this).shallowBeautify(reductionContext); } +// Derivation +bool AdditionNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Addition(this).didDerivate(reductionContext, symbol, symbolValue); +} + // Addition const Number Addition::NumeralFactor(const Expression & e) { @@ -322,6 +328,13 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon return result; } +bool Addition::didDerivate(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); diff --git a/poincare/src/number.cpp b/poincare/src/number.cpp index 42c1cac1768..9a5fac8d00b 100644 --- a/poincare/src/number.cpp +++ b/poincare/src/number.cpp @@ -6,6 +6,7 @@ #include #include #include +#include extern "C" { #include #include @@ -40,6 +41,10 @@ double NumberNode::doubleApproximation() const { } } +bool NumberNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Number(this).didDerivate(reductionContext, symbol, symbolValue); +} + Number Number::ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLenght, bool exponentIsNegative, const char * exponentPart, size_t exponentLength) { // Integer if (exponentLength == 0 && decimalLenght == 0) { @@ -141,6 +146,11 @@ int Number::NaturalOrder(const Number & i, const Number & j) { } } +bool Number::didDerivate(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/symbol.cpp b/poincare/src/symbol.cpp index b1f1f168775..eec6f84c088 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -107,6 +107,10 @@ ExpressionNode::LayoutShape SymbolNode::leftLayoutShape() const { return LayoutShape::MoreLetters; } +bool SymbolNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Symbol(this).didDerivate(reductionContext, symbol, symbolValue); +} + template Evaluation SymbolNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { Symbol s(this); @@ -192,6 +196,11 @@ Expression Symbol::shallowReduce(ExpressionNode::ReductionContext reductionConte return result.deepReduce(reductionContext); } +bool Symbol::didDerivate(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/unreal.cpp b/poincare/src/unreal.cpp index 10e0eb6fd9f..9af5e07b0a3 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/test/derivative.cpp b/poincare/test/derivative.cpp index 8fe59061bb8..19f77e80375 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -13,6 +13,19 @@ void assert_differentiates_as(Expression expression, Expression derivative, cons quiz_assert_print_if_failure(expressionReduced.isIdenticalTo(derivative), information); } -QUIZ_CASE(poincare_differential_addition) { +void assert_parses_and_reduces_as(const char * expression, const char * derivative) { + Shared::GlobalContext globalContext; + Expression e = parse_expression(expression, &globalContext, false); + Expression eReduced = e.reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); + Expression d = parse_expression(derivative, &globalContext, false).reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); + quiz_assert_print_if_failure(eReduced.isIdenticalTo(d), expression); +} +QUIZ_CASE(poincare_differential_addition) { + assert_parses_and_reduces_as("diff(1,x,1)", "0"); + assert_parses_and_reduces_as("diff(x,x,1)", "1"); + assert_parses_and_reduces_as("diff(1+2,x,1)", "0"); + assert_parses_and_reduces_as("diff(a,x,1)", "0"); + assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); + assert_parses_and_reduces_as("diff(undef,x,1)", "undef"); } \ No newline at end of file From 6f378ef3eff88ed90a0d78ebc4f1179bbe55a386 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 19 May 2020 16:05:06 +0200 Subject: [PATCH 014/560] [poincare] Implemented didDerivate for Multiplication Derivation propagates as expected on multiplications (but not power). Some tests involving diff had to be updated to reflect that behaviour. Change-Id: Ifa32031bc37a156c18d296757bcdd6ccdb0ea43e --- poincare/include/poincare/multiplication.h | 4 ++++ poincare/src/addition.cpp | 2 +- poincare/src/derivative.cpp | 2 ++ poincare/src/multiplication.cpp | 25 ++++++++++++++++++++++ poincare/test/derivative.cpp | 8 +++++++ poincare/test/expression_properties.cpp | 4 ++-- poincare/test/simplification.cpp | 2 +- 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 4b1c41a74e1..cc6b48a9004 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -49,6 +49,8 @@ class MultiplicationNode final : public NAryExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext reductionContext) override; Expression denominator(ExpressionNode::ReductionContext reductionContext) const override; + // Derivation + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; // Approximation template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { @@ -88,6 +90,8 @@ class Multiplication : public NAryExpression { void sortChildrenInPlace(NAryExpressionNode::ExpressionOrder order, Context * context, bool canBeInterrupted) { NAryExpression::sortChildrenInPlace(order, context, false, canBeInterrupted); } + // Derivation + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: // Unit Expression removeUnit(Expression * unit); diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index fd1e9da3474..e160ed06466 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index c010f210867..e24a7148b3d 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -179,6 +179,8 @@ Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionC derivand = childAtIndex(0); /* Deep reduces the child, because didDerivate may not preserve its reduced * status. */ + + derivand = derivand.replaceSymbolWithExpression(symbol, symbolValue); derivand = derivand.deepReduce(reductionContext); replaceWithInPlace(derivand); return derivand; diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index a0cc74a7a72..027ac8bf37e 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -218,6 +219,10 @@ Expression MultiplicationNode::denominator(ReductionContext reductionContext) co return Multiplication(this).denominator(reductionContext); } +bool MultiplicationNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Multiplication(this).didDerivate(reductionContext, symbol, symbolValue); +} + /* Multiplication */ int Multiplication::getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const { @@ -490,6 +495,26 @@ Expression Multiplication::denominator(ExpressionNode::ReductionContext reductio return denom; } +bool Multiplication::didDerivate(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(); diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 19f77e80375..21be791001c 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -26,6 +26,14 @@ QUIZ_CASE(poincare_differential_addition) { assert_parses_and_reduces_as("diff(x,x,1)", "1"); assert_parses_and_reduces_as("diff(1+2,x,1)", "0"); assert_parses_and_reduces_as("diff(a,x,1)", "0"); + assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); assert_parses_and_reduces_as("diff(undef,x,1)", "undef"); + + assert_parses_and_reduces_as("diff(x+x,x,4)", "2"); + assert_parses_and_reduces_as("diff(2*x,x,1)", "2"); + assert_parses_and_reduces_as("diff(a*x,x,2)", "a"); + assert_parses_and_reduces_as("diff(a*x+b,x,x)", "a"); + + // assert_parses_and_reduces_as("diff(x*x,x,3)", "3"); } \ No newline at end of file diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 145e533afed..abdc9567dae 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -216,8 +216,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); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 64175b73c8d..efba56df38a 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1140,7 +1140,7 @@ QUIZ_CASE(poincare_simplification_complex_format) { 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("diff(3×x, x, 3)", "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); From 648cdbaa29222cda1f9fb672bfa95fa8d8a65211 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 19 May 2020 17:58:46 +0200 Subject: [PATCH 015/560] [poincare] Implemented didDerivate method for Power Derivation now propagates to powers as expected, whether the base, the exponent, or both are functions of the variable. This also makes division derive as intended. Change-Id: I51cbd5f7ec9f6aaa1df068625bbda1437941fa08 --- poincare/include/poincare/power.h | 2 ++ poincare/src/power.cpp | 46 +++++++++++++++++++++++++++++++ poincare/test/derivative.cpp | 15 +++++----- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index 815dc065c11..1d6ebe70aef 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -55,6 +55,7 @@ class PowerNode final : public ExpressionNode { 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 didDerivate(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); @@ -79,6 +80,7 @@ class Power final : public Expression { int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[]) const; Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: constexpr static int k_maxExactPowerMatrix = 100; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 7d6f48ed848..a8d801ce089 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -251,6 +252,10 @@ Expression PowerNode::denominator(ReductionContext reductionContext) const { return Power(this).denominator(reductionContext); } +bool PowerNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Power(this).didDerivate(reductionContext, symbol, symbolValue); +} + // Evaluation template MatrixComplex PowerNode::computeOnComplexAndMatrix(const std::complex c, const MatrixComplex n, Preferences::ComplexFormat complexFormat) { return MatrixComplex::Undefined(); @@ -1015,6 +1020,47 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return *this; } +bool Power::didDerivate(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 diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 21be791001c..cb16ff8b7cd 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -7,12 +7,6 @@ using namespace Poincare; -void assert_differentiates_as(Expression expression, Expression derivative, const char * information) { - Shared::GlobalContext globalContext; - Expression expressionReduced = expression.reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); - quiz_assert_print_if_failure(expressionReduced.isIdenticalTo(derivative), information); -} - void assert_parses_and_reduces_as(const char * expression, const char * derivative) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); @@ -32,8 +26,15 @@ QUIZ_CASE(poincare_differential_addition) { assert_parses_and_reduces_as("diff(x+x,x,4)", "2"); assert_parses_and_reduces_as("diff(2*x,x,1)", "2"); + assert_parses_and_reduces_as("diff(-x,x,1)", "-1"); + assert_parses_and_reduces_as("diff(3-x,x,1)", "-1"); assert_parses_and_reduces_as("diff(a*x,x,2)", "a"); assert_parses_and_reduces_as("diff(a*x+b,x,x)", "a"); - // assert_parses_and_reduces_as("diff(x*x,x,3)", "3"); + assert_parses_and_reduces_as("diff(x*x,x,3)", "6"); + assert_parses_and_reduces_as("diff(x^2,x,2)", "4"); + assert_parses_and_reduces_as("diff(2^x,x,0)", "ln(2)"); + assert_parses_and_reduces_as("diff(x^2,x,x)", "2*x"); + assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,x)", "2*a*x+b"); + assert_parses_and_reduces_as("diff(1/x,x,1)", "-1"); } \ No newline at end of file From 5cf85368eaf4f4f9cb6bd8ddbc3ff0e31aff2e3c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 20 May 2020 10:54:12 +0200 Subject: [PATCH 016/560] [poincare] Added framework to derivate unary functions, and implemented it for Sine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a method unaryFunctionDifferential to ExpressionNode and Expression, to be implemented by subclasses representing unary functions. Added a function derivateUnaryFunction to Derivative, to factor (f°g)' -> g' * f'°g. Change-Id: Id1780f1082ccd001f1282fe4ddfff2b7055d3a27 --- poincare/include/poincare/derivative.h | 1 + poincare/include/poincare/expression.h | 2 ++ poincare/include/poincare/expression_node.h | 1 + poincare/include/poincare/sine.h | 7 +++++++ poincare/src/derivative.cpp | 8 +++++++- poincare/src/expression_node.cpp | 4 ++++ poincare/src/sine.cpp | 18 ++++++++++++++++++ poincare/test/derivative.cpp | 9 ++++++++- 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index bfa1a73191e..0456c54b599 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -52,6 +52,7 @@ class Derivative final : public ParameteredExpression { 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); Expression shallowReduce(ExpressionNode::ReductionContext context); }; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 05944ba41ab..4dfc93fbf67 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -385,6 +385,7 @@ class Expression : public TreeHandle { * It returns whether the instance is differentiable, and differentiates it if * able. */ bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return node()->didDerivate(reductionContext, symbol, symbolValue); } + Expression unaryFunctionDifferential() { return node()->unaryFunctionDifferential(); } private: static constexpr int k_maxSymbolReplacementsCount = 10; @@ -414,6 +415,7 @@ class Expression : public TreeHandle { Expression shallowReduceUsingApproximation(ExpressionNode::ReductionContext reductionContext); Expression defaultShallowBeautify() { return *this; } bool defaultDidDerivate() { return false; } + Expression defaultUnaryFunctionDifferential() { return *this; } /* Approximation */ template Evaluation approximateToEvaluation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 4ab8b1c644a..99a58cfe704 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -224,6 +224,7 @@ class ExpressionNode : public TreeNode { /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); /*!*/ virtual bool didDerivate(ReductionContext, Expression symbol, Expression symbolValue); + virtual Expression unaryFunctionDifferential(); /* 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/sine.h b/poincare/include/poincare/sine.h index 80307a0489a..701288952d6 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -35,6 +35,10 @@ class SineNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential() override; + // Evaluation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); @@ -52,6 +56,9 @@ class Sine final : public Expression { static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("sin", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(); }; } diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index e24a7148b3d..1caff18b544 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -1,8 +1,8 @@ #include #include #include +#include #include - #include #include #include @@ -184,6 +184,12 @@ Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionC derivand = derivand.deepReduce(reductionContext); replaceWithInPlace(derivand); return derivand; +} + +void Derivative::DerivateUnaryFunction(Expression function, Expression symbol, Expression symbolValue) { + Expression df = function.unaryFunctionDifferential(); + Expression dg = Derivative::Builder(function.childAtIndex(0), symbol.clone().convert(), symbolValue.clone()); + function.replaceWithInPlace(Multiplication::Builder(df, dg)); } diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index 5c02fe3e5e1..91c63e59a0d 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -131,6 +131,10 @@ bool ExpressionNode::didDerivate(ReductionContext reductionContext, Expression s return Expression(this).defaultDidDerivate(); } +Expression ExpressionNode::unaryFunctionDifferential() { + 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/sine.cpp b/poincare/src/sine.cpp index 3668d8a3afb..9673c9e3d0b 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -34,6 +36,13 @@ Expression SineNode::shallowReduce(ReductionContext reductionContext) { return Sine(this).shallowReduce(reductionContext); } +bool SineNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Sine(this).didDerivate(reductionContext, symbol, symbolValue); +} + +Expression SineNode::unaryFunctionDifferential() { + return Sine(this).unaryFunctionDifferential(); +} Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { @@ -46,4 +55,13 @@ Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext return Trigonometry::shallowReduceDirectFunction(*this, reductionContext); } +bool Sine::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression Sine::unaryFunctionDifferential() { + return Cosine::Builder(childAtIndex(0).clone()); +} + } diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index cb16ff8b7cd..5567bf1a6d8 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -15,11 +15,12 @@ void assert_parses_and_reduces_as(const char * expression, const char * derivati quiz_assert_print_if_failure(eReduced.isIdenticalTo(d), expression); } -QUIZ_CASE(poincare_differential_addition) { +QUIZ_CASE(poincare_differential_operations) { assert_parses_and_reduces_as("diff(1,x,1)", "0"); assert_parses_and_reduces_as("diff(x,x,1)", "1"); assert_parses_and_reduces_as("diff(1+2,x,1)", "0"); assert_parses_and_reduces_as("diff(a,x,1)", "0"); + assert_parses_and_reduces_as("diff(diff(x^2,x,y),y,1)","2"); assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); assert_parses_and_reduces_as("diff(undef,x,1)", "undef"); @@ -37,4 +38,10 @@ QUIZ_CASE(poincare_differential_addition) { assert_parses_and_reduces_as("diff(x^2,x,x)", "2*x"); assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,x)", "2*a*x+b"); assert_parses_and_reduces_as("diff(1/x,x,1)", "-1"); +} + +QUIZ_CASE(poicare_differential_unary_functions) { + assert_parses_and_reduces_as("diff(sin(x),x,π)","-1"); + assert_parses_and_reduces_as("diff(sin(2y),y,π/12)","√(3)"); + assert_parses_and_reduces_as("diff(sin(2x)+sin(3x),x,π/6)","1"); } \ No newline at end of file From 407d4bce6eeff4aefe2c4502745e12ebf11dd271 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 20 May 2020 11:25:30 +0200 Subject: [PATCH 017/560] [poincare/cosine] Implemented didDerivate and unaryFunctionDifferential for Cosine Derivation now propagates as expected on cosines. Change-Id: I6f5af48ac7462c5b8e77ff1a6428a65bf6c0c7de --- poincare/include/poincare/cosine.h | 7 +++++++ poincare/src/cosine.cpp | 20 ++++++++++++++++++++ poincare/test/derivative.cpp | 2 ++ 3 files changed, 29 insertions(+) diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index 5855d8a9d2e..4abf0694277 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -34,6 +34,10 @@ class CosineNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential() override; + // Evaluation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); @@ -51,6 +55,9 @@ class Cosine final : public Expression { static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("cos", 1, &UntypedBuilderOneChild); Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(); }; } diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index 2e216ce8ef0..fdc195f55d8 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -1,7 +1,11 @@ #include #include +#include #include +#include +#include #include +#include #include @@ -34,6 +38,14 @@ Expression CosineNode::shallowReduce(ReductionContext reductionContext) { return Cosine(this).shallowReduce(reductionContext); } +bool CosineNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Cosine(this).didDerivate(reductionContext, symbol, symbolValue); +} + +Expression CosineNode::unaryFunctionDifferential() { + return Cosine(this).unaryFunctionDifferential(); +} + Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); @@ -45,5 +57,13 @@ Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionConte return Trigonometry::shallowReduceDirectFunction(*this, reductionContext); } +bool Cosine::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression Cosine::unaryFunctionDifferential() { + return Multiplication::Builder(Rational::Builder(-1), Sine::Builder(childAtIndex(0).clone())); +} } diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 5567bf1a6d8..66ea36cb224 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -44,4 +44,6 @@ QUIZ_CASE(poicare_differential_unary_functions) { assert_parses_and_reduces_as("diff(sin(x),x,π)","-1"); assert_parses_and_reduces_as("diff(sin(2y),y,π/12)","√(3)"); assert_parses_and_reduces_as("diff(sin(2x)+sin(3x),x,π/6)","1"); + + assert_parses_and_reduces_as("diff(cos(x),x,π/2)","-1"); } \ No newline at end of file From eab8167a566790619a3bcae3675cab2a62c103a5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 20 May 2020 12:15:13 +0200 Subject: [PATCH 018/560] [poicare/logarithm] Implemented didDerivate and unaryFunctionDifferential for Logarithm Derivation now computes as expected on logarithms, as long as their base is not a function of the derivation variable. Change-Id: Ia56da1c1151c0ddf3887be84ddb4bd02664c5188 --- poincare/include/poincare/logarithm.h | 5 +++ poincare/src/logarithm.cpp | 44 +++++++++++++++++++++++++++ poincare/test/derivative.cpp | 5 +++ 3 files changed, 54 insertions(+) diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index 75d23f0f780..c43e1dbea0a 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -32,6 +32,9 @@ class LogarithmNode final : public ExpressionNode { Expression shallowBeautify(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } + // Derivation + bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + Expression unaryFunctionDifferential() 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 @@ -53,6 +56,8 @@ class Logarithm final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(); + bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + Expression unaryFunctionDifferential(); private: void deepReduceChildren(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 803dfc18d61..4bb930f1081 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,31 @@ Expression LogarithmNode<2>::shallowReduce(ExpressionNode::ReductionContext redu return Logarithm(this).shallowReduce(reductionContext); } +template <> +bool LogarithmNode<2>::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Logarithm(this).didDerivate(reductionContext, symbol, symbolValue); +} + +template <> +Expression LogarithmNode<2>::unaryFunctionDifferential() { + return Logarithm(this).unaryFunctionDifferential(); +} + +/* Those two methods will not be called, as CommonLogarithm disappears in + * reduction */ +template <> +bool LogarithmNode<1>::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + assert(false); + return false; +} + +template <> +Expression LogarithmNode<1>::unaryFunctionDifferential() { + assert(false); + return Expression(); +} +/**/ + template<> Expression LogarithmNode<1>::shallowBeautify(ReductionContext reductionContext) { return CommonLogarithm(this); @@ -329,6 +355,24 @@ Integer Logarithm::simplifyLogarithmIntegerBaseInteger(Integer i, Integer & base return i; } +bool Logarithm::didDerivate(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); + return true; +} + +Expression Logarithm::unaryFunctionDifferential() { + /* log(x, b)` = (ln(x)/ln(b))` + * = 1 / (x * ln(b)) + */ + return Power::Builder(Multiplication::Builder(childAtIndex(0).clone(), NaperianLogarithm::Builder(childAtIndex(1).clone())), Rational::Builder(-1)); +} + Expression Logarithm::splitLogarithmInteger(Integer i, bool isDenominator, ExpressionNode::ReductionContext reductionContext) { assert(!i.isZero()); assert(!i.isNegative()); diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 66ea36cb224..57d4307a996 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -46,4 +46,9 @@ QUIZ_CASE(poicare_differential_unary_functions) { assert_parses_and_reduces_as("diff(sin(2x)+sin(3x),x,π/6)","1"); assert_parses_and_reduces_as("diff(cos(x),x,π/2)","-1"); + + assert_parses_and_reduces_as("diff(ln(x),x,x)","1/x"); + assert_parses_and_reduces_as("diff(log(x,10),x,x)","1/(x*ln(10))"); + + assert_parses_and_reduces_as("diff(ln(cos(x)),x,a)","-tan(a)"); } \ No newline at end of file From a9c94236c2587dc478c08caee11bea579379ae68 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 20 May 2020 16:47:22 +0200 Subject: [PATCH 019/560] [poincare] Implemented further derivation methods and updated tests. Derivation now works on tangents and the three hyperbolic functions. Change-Id: I62049e79c661c3c4a031be0a93c403fb936d611b --- poincare/include/poincare/addition.h | 4 +- poincare/include/poincare/cosine.h | 4 +- poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/expression_node.h | 2 +- poincare/include/poincare/hyperbolic_cosine.h | 6 ++ poincare/include/poincare/hyperbolic_sine.h | 6 ++ .../include/poincare/hyperbolic_tangent.h | 6 ++ poincare/include/poincare/logarithm.h | 4 +- poincare/include/poincare/multiplication.h | 4 +- poincare/include/poincare/number.h | 4 +- poincare/include/poincare/power.h | 4 +- poincare/include/poincare/sine.h | 4 +- poincare/include/poincare/symbol.h | 4 +- poincare/include/poincare/tangent.h | 7 ++ poincare/include/poincare/undefined.h | 4 +- poincare/include/poincare/unreal.h | 4 +- poincare/src/addition.cpp | 6 +- poincare/src/cosine.cpp | 7 +- poincare/src/derivative.cpp | 8 +- poincare/src/expression_node.cpp | 2 +- poincare/src/hyperbolic_cosine.cpp | 20 +++++ poincare/src/hyperbolic_sine.cpp | 19 +++++ poincare/src/hyperbolic_tangent.cpp | 20 +++++ poincare/src/logarithm.cpp | 8 +- poincare/src/multiplication.cpp | 6 +- poincare/src/number.cpp | 6 +- poincare/src/power.cpp | 6 +- poincare/src/sine.cpp | 6 +- poincare/src/symbol.cpp | 6 +- poincare/src/tangent.cpp | 19 +++++ poincare/test/derivative.cpp | 77 +++++++++++++------ 31 files changed, 208 insertions(+), 77 deletions(-) diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 77dedbc7c2d..37ca11506b8 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -58,7 +58,7 @@ class AdditionNode final : public NAryExpressionNode { Expression shallowBeautify(ReductionContext reductionContext) override; // Derivation - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; /* Evaluation */ template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { @@ -84,7 +84,7 @@ class Addition final : public NAryExpression { // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + 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/cosine.h b/poincare/include/poincare/cosine.h index 4abf0694277..b5c81aa4357 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -35,7 +35,7 @@ class CosineNode final : public ExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Derivation - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; Expression unaryFunctionDifferential() override; // Evaluation @@ -56,7 +56,7 @@ class Cosine final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); Expression unaryFunctionDifferential(); }; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 4dfc93fbf67..788e8955c38 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -384,7 +384,7 @@ class Expression : public TreeHandle { /* This method is used for the reduction of Derivative expressions. * It returns whether the instance is differentiable, and differentiates it if * able. */ - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return node()->didDerivate(reductionContext, symbol, symbolValue); } + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return node()->derivate(reductionContext, symbol, symbolValue); } Expression unaryFunctionDifferential() { return node()->unaryFunctionDifferential(); } private: diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 99a58cfe704..6d9a9b32751 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -223,7 +223,7 @@ class ExpressionNode : public TreeNode { /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); - /*!*/ virtual bool didDerivate(ReductionContext, Expression symbol, Expression symbolValue); + /*!*/ virtual bool derivate(ReductionContext, Expression symbol, Expression symbolValue); virtual Expression unaryFunctionDifferential(); /* Return a clone of the denominator part of the expression */ /*!*/ virtual Expression denominator(ExpressionNode::ReductionContext reductionContext) const; diff --git a/poincare/include/poincare/hyperbolic_cosine.h b/poincare/include/poincare/hyperbolic_cosine.h index 64f2fc99186..c37d90fb6e7 100644 --- a/poincare/include/poincare/hyperbolic_cosine.h +++ b/poincare/include/poincare/hyperbolic_cosine.h @@ -25,6 +25,9 @@ class HyperbolicCosineNode final : public HyperbolicTrigonometricFunctionNode { // 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() 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 { @@ -40,6 +43,9 @@ class HyperbolicCosine final : public HyperbolicTrigonometricFunction { 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(); + 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 718aa5ca93d..95ffaf72062 100644 --- a/poincare/include/poincare/hyperbolic_sine.h +++ b/poincare/include/poincare/hyperbolic_sine.h @@ -23,6 +23,9 @@ class HyperbolicSineNode final : public HyperbolicTrigonometricFunctionNode { // 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() 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 { @@ -38,6 +41,9 @@ class HyperbolicSine final : public HyperbolicTrigonometricFunction { 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(); + 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 5c42b79ba44..b69e6ec1393 100644 --- a/poincare/include/poincare/hyperbolic_tangent.h +++ b/poincare/include/poincare/hyperbolic_tangent.h @@ -23,6 +23,9 @@ class HyperbolicTangentNode final : public HyperbolicTrigonometricFunctionNode { // 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() 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 { @@ -38,6 +41,9 @@ class HyperbolicTangent final : public HyperbolicTrigonometricFunction { 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(); + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("tanh", 1, &UntypedBuilderOneChild); }; diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index c43e1dbea0a..b5fb4633068 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -33,7 +33,7 @@ class LogarithmNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Derivation - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; Expression unaryFunctionDifferential() override; // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit) { @@ -56,7 +56,7 @@ class Logarithm final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); Expression unaryFunctionDifferential(); private: diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index cc6b48a9004..4c75085a714 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -50,7 +50,7 @@ class MultiplicationNode final : public NAryExpressionNode { Expression shallowBeautify(ReductionContext reductionContext) override; Expression denominator(ExpressionNode::ReductionContext reductionContext) const override; // Derivation - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; // Approximation template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { @@ -91,7 +91,7 @@ class Multiplication : public NAryExpression { NAryExpression::sortChildrenInPlace(order, context, false, canBeInterrupted); } // Derivation - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: // Unit Expression removeUnit(Expression * unit); diff --git a/poincare/include/poincare/number.h b/poincare/include/poincare/number.h index ca7821d4ab3..4b53d35ad50 100644 --- a/poincare/include/poincare/number.h +++ b/poincare/include/poincare/number.h @@ -26,7 +26,7 @@ class NumberNode : public ExpressionNode { double doubleApproximation() const; - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; }; @@ -56,7 +56,7 @@ class Number : public Expression { return Expression::setSign(s, ExpressionNode::ReductionContext(nullptr, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, ExpressionNode::ReductionTarget::User)).convert(); } - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + 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/power.h b/poincare/include/poincare/power.h index 1d6ebe70aef..0363718a2d1 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -55,7 +55,7 @@ class PowerNode final : public ExpressionNode { 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 didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) 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); @@ -80,7 +80,7 @@ class Power final : public Expression { int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[]) const; Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); private: constexpr static int k_maxExactPowerMatrix = 100; diff --git a/poincare/include/poincare/sine.h b/poincare/include/poincare/sine.h index 701288952d6..eb3938b13cc 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -36,7 +36,7 @@ class SineNode final : public ExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Derivation - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; Expression unaryFunctionDifferential() override; // Evaluation @@ -57,7 +57,7 @@ class Sine final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); Expression unaryFunctionDifferential(); }; diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index bf29727432b..8db3794c48d 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -38,7 +38,7 @@ class SymbolNode final : public SymbolAbstractNode { LayoutShape leftLayoutShape() const override; /* Derivation */ - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; + 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); } @@ -72,7 +72,7 @@ class Symbol final : public SymbolAbstract { // Expression Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - bool didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); + 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/tangent.h b/poincare/include/poincare/tangent.h index ee089103863..7ec86acb0f9 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -32,6 +32,10 @@ class TangentNode final : public ExpressionNode { 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() 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 { @@ -50,6 +54,9 @@ class Tangent final : public Expression { 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(); }; } diff --git a/poincare/include/poincare/undefined.h b/poincare/include/poincare/undefined.h index ae24946669c..3636e6751d9 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -30,8 +30,8 @@ class UndefinedNode : public NumberNode { } /* Derivation - * Overrides NumberNode's didDerivate to revert to a non-derivable state */ - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + * Overrides NumberNode's derivate to revert to a non-derivable state */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index c36e1db83b8..6757c8a0bdb 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -28,8 +28,8 @@ class UnrealNode final : public UndefinedNode { } /* Derivation - * Overrides NumberNode's didDerivate to revert to a non-derivable state */ - bool didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + * Overrides NumberNode's derivate to revert to a non-derivable state */ + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index e160ed06466..7f868ab488a 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -51,8 +51,8 @@ Expression AdditionNode::shallowBeautify(ReductionContext reductionContext) { } // Derivation -bool AdditionNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Addition(this).didDerivate(reductionContext, symbol, symbolValue); +bool AdditionNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Addition(this).derivate(reductionContext, symbol, symbolValue); } // Addition @@ -328,7 +328,7 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon return result; } -bool Addition::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +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())); } diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index fdc195f55d8..dcf87c6e461 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -38,8 +38,8 @@ Expression CosineNode::shallowReduce(ReductionContext reductionContext) { return Cosine(this).shallowReduce(reductionContext); } -bool CosineNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Cosine(this).didDerivate(reductionContext, symbol, symbolValue); +bool CosineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Cosine(this).derivate(reductionContext, symbol, symbolValue); } Expression CosineNode::unaryFunctionDifferential() { @@ -57,7 +57,8 @@ Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionConte return Trigonometry::shallowReduceDirectFunction(*this, reductionContext); } -bool Cosine::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + +bool Cosine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); return true; } diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index 1caff18b544..aba4808b4ce 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -170,14 +170,14 @@ Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionC Expression symbolValue = childAtIndex(2); /* Since derivand is a child to the derivative node, it can be replaced in - * place without didDerivate having to return the derivative. */ - if (!derivand.didDerivate(reductionContext, symbol, symbolValue)) { + * place without derivate having to return the derivative. */ + if (!derivand.derivate(reductionContext, symbol, symbolValue)) { return *this; } - /* Updates the value of derivand, because didDerivate may call + /* Updates the value of derivand, because derivate may call * replaceWithInplace on it */ derivand = childAtIndex(0); - /* Deep reduces the child, because didDerivate may not preserve its reduced + /* Deep reduces the child, because derivate may not preserve its reduced * status. */ derivand = derivand.replaceSymbolWithExpression(symbol, symbolValue); diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index 91c63e59a0d..e74456cc946 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -127,7 +127,7 @@ Expression ExpressionNode::shallowBeautify(ReductionContext reductionContext) { return Expression(this).defaultShallowBeautify(); } -bool ExpressionNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool ExpressionNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { return Expression(this).defaultDidDerivate(); } diff --git a/poincare/src/hyperbolic_cosine.cpp b/poincare/src/hyperbolic_cosine.cpp index c963ee4ad7f..cba369cbe76 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() { + return HyperbolicCosine(this).unaryFunctionDifferential(); +} + + +bool HyperbolicCosine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression HyperbolicCosine::unaryFunctionDifferential() { + 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 237804747ac..e1e73c6a42d 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() { + return HyperbolicSine(this).unaryFunctionDifferential(); +} + +bool HyperbolicSine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression HyperbolicSine::unaryFunctionDifferential() { + 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 48a5e8830e5..c27d15a686d 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() { + return HyperbolicTangent(this).unaryFunctionDifferential(); +} + +bool HyperbolicTangent::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression HyperbolicTangent::unaryFunctionDifferential() { + 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/logarithm.cpp b/poincare/src/logarithm.cpp index 4bb930f1081..d207bc9890e 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -70,8 +70,8 @@ Expression LogarithmNode<2>::shallowReduce(ExpressionNode::ReductionContext redu } template <> -bool LogarithmNode<2>::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Logarithm(this).didDerivate(reductionContext, symbol, symbolValue); +bool LogarithmNode<2>::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Logarithm(this).derivate(reductionContext, symbol, symbolValue); } template <> @@ -82,7 +82,7 @@ Expression LogarithmNode<2>::unaryFunctionDifferential() { /* Those two methods will not be called, as CommonLogarithm disappears in * reduction */ template <> -bool LogarithmNode<1>::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool LogarithmNode<1>::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { assert(false); return false; } @@ -355,7 +355,7 @@ Integer Logarithm::simplifyLogarithmIntegerBaseInteger(Integer i, Integer & base return i; } -bool Logarithm::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +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)). */ diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 027ac8bf37e..5a44c1a7138 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -219,8 +219,8 @@ Expression MultiplicationNode::denominator(ReductionContext reductionContext) co return Multiplication(this).denominator(reductionContext); } -bool MultiplicationNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Multiplication(this).didDerivate(reductionContext, symbol, symbolValue); +bool MultiplicationNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Multiplication(this).derivate(reductionContext, symbol, symbolValue); } /* Multiplication */ @@ -495,7 +495,7 @@ Expression Multiplication::denominator(ExpressionNode::ReductionContext reductio return denom; } -bool Multiplication::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool Multiplication::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { Addition resultingAddition = Addition::Builder(); int numberOfTerms = numberOfChildren(); assert (numberOfTerms > 0); diff --git a/poincare/src/number.cpp b/poincare/src/number.cpp index 9a5fac8d00b..0bbd1c25298 100644 --- a/poincare/src/number.cpp +++ b/poincare/src/number.cpp @@ -41,8 +41,8 @@ double NumberNode::doubleApproximation() const { } } -bool NumberNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Number(this).didDerivate(reductionContext, symbol, symbolValue); +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 decimalLenght, bool exponentIsNegative, const char * exponentPart, size_t exponentLength) { @@ -146,7 +146,7 @@ int Number::NaturalOrder(const Number & i, const Number & j) { } } -bool Number::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool Number::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { replaceWithInPlace(Rational::Builder(0)); return true; } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index a8d801ce089..4b8f2f61773 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -252,8 +252,8 @@ Expression PowerNode::denominator(ReductionContext reductionContext) const { return Power(this).denominator(reductionContext); } -bool PowerNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Power(this).didDerivate(reductionContext, symbol, symbolValue); +bool PowerNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Power(this).derivate(reductionContext, symbol, symbolValue); } // Evaluation @@ -1020,7 +1020,7 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return *this; } -bool Power::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +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 diff --git a/poincare/src/sine.cpp b/poincare/src/sine.cpp index 9673c9e3d0b..7d90778b890 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -36,8 +36,8 @@ Expression SineNode::shallowReduce(ReductionContext reductionContext) { return Sine(this).shallowReduce(reductionContext); } -bool SineNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Sine(this).didDerivate(reductionContext, symbol, symbolValue); +bool SineNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Sine(this).derivate(reductionContext, symbol, symbolValue); } Expression SineNode::unaryFunctionDifferential() { @@ -55,7 +55,7 @@ Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext return Trigonometry::shallowReduceDirectFunction(*this, reductionContext); } -bool Sine::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool Sine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); return true; } diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index eec6f84c088..3cedd2daaa7 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -107,8 +107,8 @@ ExpressionNode::LayoutShape SymbolNode::leftLayoutShape() const { return LayoutShape::MoreLetters; } -bool SymbolNode::didDerivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - return Symbol(this).didDerivate(reductionContext, symbol, symbolValue); +bool SymbolNode::derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + return Symbol(this).derivate(reductionContext, symbol, symbolValue); } template @@ -196,7 +196,7 @@ Expression Symbol::shallowReduce(ExpressionNode::ReductionContext reductionConte return result.deepReduce(reductionContext); } -bool Symbol::didDerivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { +bool Symbol::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { replaceWithInPlace(Rational::Builder(strcmp(name(), symbol.convert().name()) == 0)); return true; } diff --git a/poincare/src/tangent.cpp b/poincare/src/tangent.cpp index e140a499205..9708ebd9856 100644 --- a/poincare/src/tangent.cpp +++ b/poincare/src/tangent.cpp @@ -1,7 +1,10 @@ #include #include +#include #include #include +#include +#include #include #include @@ -37,6 +40,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() { + return Tangent(this).unaryFunctionDifferential(); +} Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { @@ -60,4 +70,13 @@ Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionCont return newExpression; } +bool Tangent::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + return true; +} + +Expression Tangent::unaryFunctionDifferential() { + return Power::Builder(Cosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2)); +} + } diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 57d4307a996..29857746922 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -9,46 +9,73 @@ using namespace Poincare; void assert_parses_and_reduces_as(const char * expression, const char * derivative) { Shared::GlobalContext globalContext; + Expression d = parse_expression(derivative, &globalContext, false).reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); Expression e = parse_expression(expression, &globalContext, false); Expression eReduced = e.reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); - Expression d = parse_expression(derivative, &globalContext, false).reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); quiz_assert_print_if_failure(eReduced.isIdenticalTo(d), expression); } -QUIZ_CASE(poincare_differential_operations) { +QUIZ_CASE(poincare_derivative_literals) { assert_parses_and_reduces_as("diff(1,x,1)", "0"); - assert_parses_and_reduces_as("diff(x,x,1)", "1"); - assert_parses_and_reduces_as("diff(1+2,x,1)", "0"); + assert_parses_and_reduces_as("diff(1,x,y)", "0"); + assert_parses_and_reduces_as("diff(1,x,x)", "0"); + assert_parses_and_reduces_as("diff(a,x,1)", "0"); - assert_parses_and_reduces_as("diff(diff(x^2,x,y),y,1)","2"); + assert_parses_and_reduces_as("diff(a,x,y)", "0"); + assert_parses_and_reduces_as("diff(a,x,x)", "0"); + + assert_parses_and_reduces_as("diff(x,x,1)", "1"); + assert_parses_and_reduces_as("diff(x,x,y)", "1"); + assert_parses_and_reduces_as("diff(x,x,x)", "1"); + assert_parses_and_reduces_as("diff(undef,x,0)", "undef"); +} + +QUIZ_CASE(poincare_derivative_additions) { assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); - assert_parses_and_reduces_as("diff(undef,x,1)", "undef"); + assert_parses_and_reduces_as("diff(x+a,x,x)", "1"); + assert_parses_and_reduces_as("diff(a+b,x,y)", "0"); +} - assert_parses_and_reduces_as("diff(x+x,x,4)", "2"); - assert_parses_and_reduces_as("diff(2*x,x,1)", "2"); - assert_parses_and_reduces_as("diff(-x,x,1)", "-1"); - assert_parses_and_reduces_as("diff(3-x,x,1)", "-1"); - assert_parses_and_reduces_as("diff(a*x,x,2)", "a"); +QUIZ_CASE(poincare_derivative_multiplications) { + assert_parses_and_reduces_as("diff(2x,x,1)", "2"); + assert_parses_and_reduces_as("diff(x*a,x,y)", "a"); assert_parses_and_reduces_as("diff(a*x+b,x,x)", "a"); + assert_parses_and_reduces_as("diff(a*b+c,x,1)", "0"); + assert_parses_and_reduces_as("diff(-x,x,y)", "-1"); + assert_parses_and_reduces_as("diff(2-5x,x,x)", "-5"); +} - assert_parses_and_reduces_as("diff(x*x,x,3)", "6"); - assert_parses_and_reduces_as("diff(x^2,x,2)", "4"); - assert_parses_and_reduces_as("diff(2^x,x,0)", "ln(2)"); - assert_parses_and_reduces_as("diff(x^2,x,x)", "2*x"); - assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,x)", "2*a*x+b"); +QUIZ_CASE(poincare_derivative_powers) { + assert_parses_and_reduces_as("diff(x*x,x,1)", "2"); + assert_parses_and_reduces_as("diff(x^2,x,y)", "2y"); + assert_parses_and_reduces_as("diff(x^3/3,x,x)", "x^2"); assert_parses_and_reduces_as("diff(1/x,x,1)", "-1"); + assert_parses_and_reduces_as("diff(2^x,x,y)", "ln(2)*2^y"); + assert_parses_and_reduces_as("diff(x^(-2),x,x)", "-2/(x^3)"); + assert_parses_and_reduces_as("diff(a^b,x,1)", "0"); + assert_parses_and_reduces_as("diff(x^a,x,y)", "a*y^(a-1)"); + assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,x)", "2a*x+b"); + assert_parses_and_reduces_as("diff((1+x)(2-x),x,1)", "-1"); + assert_parses_and_reduces_as("diff(diff(x^3/6,x,y),y,z)", "z"); } -QUIZ_CASE(poicare_differential_unary_functions) { - assert_parses_and_reduces_as("diff(sin(x),x,π)","-1"); - assert_parses_and_reduces_as("diff(sin(2y),y,π/12)","√(3)"); - assert_parses_and_reduces_as("diff(sin(2x)+sin(3x),x,π/6)","1"); - - assert_parses_and_reduces_as("diff(cos(x),x,π/2)","-1"); +QUIZ_CASE(poincare_derivative_functions) { + assert_parses_and_reduces_as("diff(sin(x),x,x)", "cos(x)"); + assert_parses_and_reduces_as("diff(cos(x),x,x)", "-sin(x)"); + assert_parses_and_reduces_as("diff(tan(x),x,0)", "1"); + assert_parses_and_reduces_as("diff(sin(a)+cos(b)+tan(c),x,y)", "0"); + assert_parses_and_reduces_as("diff(sin(cos(x)),x,y)", "-sin(y)*cos(cos(y))"); - assert_parses_and_reduces_as("diff(ln(x),x,x)","1/x"); - assert_parses_and_reduces_as("diff(log(x,10),x,x)","1/(x*ln(10))"); + assert_parses_and_reduces_as("diff(ln(x),x,x)", "1/x"); + assert_parses_and_reduces_as("diff(ln(a*x),x,x)", "1/x"); + assert_parses_and_reduces_as("diff(log(x),x,x)", "(x*ln(10))^(-1)"); + assert_parses_and_reduces_as("diff(ln(cos(x)),x,x)", "-tan(x)"); + assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,x)", "1/(cos(x))^2"); + assert_parses_and_reduces_as("diff(ln(a),x,1)", "0"); - assert_parses_and_reduces_as("diff(ln(cos(x)),x,a)","-tan(a)"); + assert_parses_and_reduces_as("diff(sinh(x),x,x)", "cosh(x)"); + assert_parses_and_reduces_as("diff(cosh(x),x,x)", "sinh(x)"); + assert_parses_and_reduces_as("diff(tanh(x),x,0)", "1"); + assert_parses_and_reduces_as("diff(ln(cosh(x)),x,0)", "0"); } \ No newline at end of file From 78a1350f1518aaa70dbd7fcf26442582ddc6640b Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 25 May 2020 12:18:16 +0200 Subject: [PATCH 020/560] [poincare/test/derivative.cpp] Added context-aware tests. New tests compute derivatives while replacing symbols with their definitions or undefined, as the device does. Change-Id: I99179bf6540182ff929938fb96a00a1ed2fbcf49 --- poincare/test/derivative.cpp | 150 +++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 33 deletions(-) diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 29857746922..f4da054b258 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -7,75 +7,159 @@ using namespace Poincare; -void assert_parses_and_reduces_as(const char * expression, const char * derivative) { - Shared::GlobalContext globalContext; - Expression d = parse_expression(derivative, &globalContext, false).reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); - Expression e = parse_expression(expression, &globalContext, false); - Expression eReduced = e.reduce(ExpressionNode::ReductionContext(&globalContext, Cartesian, Radian, User)); - quiz_assert_print_if_failure(eReduced.isIdenticalTo(d), expression); +void fillGlobalContext() { + assert_reduce("-1→y"); + assert_reduce("9→a"); + assert_reduce("2→b"); + assert_reduce("3→c"); + assert_reduce("2x→f"); + assert_reduce("x^2→g"); +} + +void emptyGlobalContext() { + Ion::Storage::sharedStorage()->recordNamed("y.exp").destroy(); + 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(); + Ion::Storage::sharedStorage()->recordNamed("g.exp").destroy(); + +} + +void assert_parses_and_reduces_as(const char * expression, const char * simplifiedDerivative) { +#define SYMBOLIC 0 +#if SYMBOLIC + ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition; +#else + ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllSymbolsWithDefinitionsOrUndefined; +#endif + + assert_parsed_expression_simplify_to(expression, simplifiedDerivative, User, Radian, Cartesian, symbolicComputation); } QUIZ_CASE(poincare_derivative_literals) { + fillGlobalContext(); + + assert_parses_and_reduces_as("diff(undef,x,0)", "undef"); + assert_parses_and_reduces_as("diff(1,x,1)", "0"); assert_parses_and_reduces_as("diff(1,x,y)", "0"); - assert_parses_and_reduces_as("diff(1,x,x)", "0"); assert_parses_and_reduces_as("diff(a,x,1)", "0"); assert_parses_and_reduces_as("diff(a,x,y)", "0"); - assert_parses_and_reduces_as("diff(a,x,x)", "0"); assert_parses_and_reduces_as("diff(x,x,1)", "1"); assert_parses_and_reduces_as("diff(x,x,y)", "1"); - assert_parses_and_reduces_as("diff(x,x,x)", "1"); - assert_parses_and_reduces_as("diff(undef,x,0)", "undef"); +#if SYMBOLIC + assert_parses_and_reduces_as("diff(1,x,z)", "0"); + assert_parses_and_reduces_as("diff(a,x,z)", "0"); + assert_parses_and_reduces_as("diff(x,x,z)", "1"); +#else + assert_parses_and_reduces_as("diff(1,x,z)", "undef"); + assert_parses_and_reduces_as("diff(a,x,z)", "undef"); + assert_parses_and_reduces_as("diff(x,x,z)", "undef"); +#endif + + emptyGlobalContext(); } QUIZ_CASE(poincare_derivative_additions) { + fillGlobalContext(); + assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); - assert_parses_and_reduces_as("diff(x+a,x,x)", "1"); assert_parses_and_reduces_as("diff(a+b,x,y)", "0"); + +#if SYMBOLIC + assert_parses_and_reduces_as("diff(x+a,x,z)", "1"); +#else + assert_parses_and_reduces_as("diff(x+a,x,z)", "undef"); +#endif + + emptyGlobalContext(); } QUIZ_CASE(poincare_derivative_multiplications) { + fillGlobalContext(); + assert_parses_and_reduces_as("diff(2x,x,1)", "2"); - assert_parses_and_reduces_as("diff(x*a,x,y)", "a"); - assert_parses_and_reduces_as("diff(a*x+b,x,x)", "a"); + assert_parses_and_reduces_as("diff(x*a,x,y)", "9"); assert_parses_and_reduces_as("diff(a*b+c,x,1)", "0"); assert_parses_and_reduces_as("diff(-x,x,y)", "-1"); - assert_parses_and_reduces_as("diff(2-5x,x,x)", "-5"); + assert_parses_and_reduces_as("diff(f,x,1)", "2"); + +#if SYMBOLIC + assert_parses_and_reduces_as("diff(a*x+b,x,z)", "9"); + assert_parses_and_reduces_as("diff(2-5x,x,z)", "-5"); +#else + assert_parses_and_reduces_as("diff(a*x+b,x,z)", "undef"); + assert_parses_and_reduces_as("diff(2-5x,x,z)", "undef"); +#endif + + emptyGlobalContext(); } QUIZ_CASE(poincare_derivative_powers) { + fillGlobalContext(); + assert_parses_and_reduces_as("diff(x*x,x,1)", "2"); - assert_parses_and_reduces_as("diff(x^2,x,y)", "2y"); - assert_parses_and_reduces_as("diff(x^3/3,x,x)", "x^2"); + assert_parses_and_reduces_as("diff(x^2,x,y)", "-2"); + assert_parses_and_reduces_as("diff(x^3/3,x,y)", "1"); assert_parses_and_reduces_as("diff(1/x,x,1)", "-1"); - assert_parses_and_reduces_as("diff(2^x,x,y)", "ln(2)*2^y"); - assert_parses_and_reduces_as("diff(x^(-2),x,x)", "-2/(x^3)"); + assert_parses_and_reduces_as("diff(2^x,x,y)", "ln(2)/2"); + assert_parses_and_reduces_as("diff(x^(-2),x,2)", "-1/4"); assert_parses_and_reduces_as("diff(a^b,x,1)", "0"); - assert_parses_and_reduces_as("diff(x^a,x,y)", "a*y^(a-1)"); - assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,x)", "2a*x+b"); + assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,1)", "20"); assert_parses_and_reduces_as("diff((1+x)(2-x),x,1)", "-1"); - assert_parses_and_reduces_as("diff(diff(x^3/6,x,y),y,z)", "z"); + assert_parses_and_reduces_as("diff(g,x,3)", "6"); + +#if SYMBOLIC + assert_parses_and_reduces_as("diff(x^3/3,x,z)", "z^2"); + assert_parses_and_reduces_as("diff(x^(-2),x,z)", "-2/z^3"); + assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,z)", "18×z+2"); + assert_parses_and_reduces_as("diff(x^c,x,z)", "3×z^2"); + assert_parses_and_reduces_as("diff(diff(x^3/6,x,t),t,z)", "z"); +#else + assert_parses_and_reduces_as("diff(x^3/3,x,z)", "undef"); + assert_parses_and_reduces_as("diff(x^(-2),x,z)", "undef"); + assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,z)", "undef"); + assert_parses_and_reduces_as("diff(x^c,x,z)", "undef"); + assert_parses_and_reduces_as("diff(diff(x^3/6,x,t),t,z)", "undef"); +#endif + + emptyGlobalContext(); } QUIZ_CASE(poincare_derivative_functions) { - assert_parses_and_reduces_as("diff(sin(x),x,x)", "cos(x)"); - assert_parses_and_reduces_as("diff(cos(x),x,x)", "-sin(x)"); + fillGlobalContext(); + assert_parses_and_reduces_as("diff(tan(x),x,0)", "1"); assert_parses_and_reduces_as("diff(sin(a)+cos(b)+tan(c),x,y)", "0"); - assert_parses_and_reduces_as("diff(sin(cos(x)),x,y)", "-sin(y)*cos(cos(y))"); - - assert_parses_and_reduces_as("diff(ln(x),x,x)", "1/x"); - assert_parses_and_reduces_as("diff(ln(a*x),x,x)", "1/x"); - assert_parses_and_reduces_as("diff(log(x),x,x)", "(x*ln(10))^(-1)"); - assert_parses_and_reduces_as("diff(ln(cos(x)),x,x)", "-tan(x)"); - assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,x)", "1/(cos(x))^2"); + assert_parses_and_reduces_as("diff(sin(cos(x)),x,y)", "sin(1)×cos(cos(1))"); assert_parses_and_reduces_as("diff(ln(a),x,1)", "0"); - - assert_parses_and_reduces_as("diff(sinh(x),x,x)", "cosh(x)"); - assert_parses_and_reduces_as("diff(cosh(x),x,x)", "sinh(x)"); assert_parses_and_reduces_as("diff(tanh(x),x,0)", "1"); assert_parses_and_reduces_as("diff(ln(cosh(x)),x,0)", "0"); + assert_parses_and_reduces_as("diff(log(x,7),x,1/2)", "2/ln(7)"); + +#if SYMBOLIC + assert_parses_and_reduces_as("diff(sin(x),x,z)", "cos(z)"); + assert_parses_and_reduces_as("diff(cos(x),x,z)", "-sin(z)"); + assert_parses_and_reduces_as("diff(ln(x),x,z)", "1/z"); + assert_parses_and_reduces_as("diff(ln(c*x),x,z)", "1/z"); + assert_parses_and_reduces_as("diff(ln(cos(x)),x,z)", "-tan(z)"); + assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,z)", "1/cos(z)^2"); + assert_parses_and_reduces_as("diff(sinh(x),x,z)", "cosh(z)"); + assert_parses_and_reduces_as("diff(cosh(x),x,z)", "sinh(z)"); +#else + assert_parses_and_reduces_as("diff(sin(x),x,z)", "undef"); + assert_parses_and_reduces_as("diff(cos(x),x,z)", "undef"); + assert_parses_and_reduces_as("diff(ln(x),x,z)", "undef"); + assert_parses_and_reduces_as("diff(ln(c*x),x,z)", "undef"); + assert_parses_and_reduces_as("diff(ln(cos(x)),x,z)", "undef"); + assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,z)", "undef"); + assert_parses_and_reduces_as("diff(sinh(x),x,z)", "undef"); + assert_parses_and_reduces_as("diff(cosh(x),x,z)", "undef"); +#endif + + emptyGlobalContext(); } \ No newline at end of file From 0a2ededfcfdfad7d424f39f970473b3f0a1076f2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 4 Jun 2020 12:24:29 +0200 Subject: [PATCH 021/560] [apps/shared] Remove packed data members for RecordDataBuffer Change-Id: I04ea5ccb4c15bda975bf5af178f07092c0387312 --- apps/graph/continuous_function_store.h | 2 +- apps/sequence/sequence.h | 6 +- apps/shared/Makefile | 15 +++++ apps/shared/continuous_function.h | 4 +- apps/shared/function.cpp | 11 ++++ apps/shared/function.h | 6 +- apps/shared/range_1D.h | 9 ++- apps/shared/test/function_alignement.cpp | 80 ++++++++++++++++++++++++ 8 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 apps/shared/test/function_alignement.cpp diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h index fce291049ec..2670a08e838 100644 --- a/apps/graph/continuous_function_store.h +++ b/apps/graph/continuous_function_store.h @@ -16,8 +16,8 @@ class ContinuousFunctionStore : public Shared::FunctionStore { return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); } Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } -private: 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; diff --git a/apps/sequence/sequence.h b/apps/sequence/sequence.h index d8e2a5f6d19..41a376973bb 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -78,7 +78,7 @@ friend class SequenceStore; class RecordDataBuffer : public Shared::Function::RecordDataBuffer { public: RecordDataBuffer(KDColor color) : - Shared::Function::RecordDataBuffer(color), + Shared::Function::RecordDataBuffer(color, sizeof(RecordDataBuffer)), m_type(Type::Explicit), m_initialRank(0), m_initialConditionSizes{0,0} @@ -102,9 +102,9 @@ friend class SequenceStore; #if __EMSCRIPTEN__ // See comment about emscripten alignement 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/Makefile b/apps/shared/Makefile index 4ffcd50df16..c10dece7c8e 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -85,3 +85,18 @@ app_shared_src = $(addprefix apps/shared/,\ 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/sequence/,\ + sequence.cpp\ + sequence_store.cpp\ +) + +tests_src += $(addprefix apps/shared/test/,\ + function_alignement.cpp\ +) diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 612651231df..dbeff485b92 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -81,10 +81,10 @@ class ContinuousFunction : public Function { /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on * Shared::Function::RecordDataBuffer about packing. */ - class __attribute__((packed)) RecordDataBuffer : public Function::RecordDataBuffer { + class RecordDataBuffer : public Function::RecordDataBuffer { public: RecordDataBuffer(KDColor color) : - Function::RecordDataBuffer(color), + Function::RecordDataBuffer(color, sizeof(RecordDataBuffer)), m_plotType(PlotType::Cartesian), m_domain(-INFINITY, INFINITY), m_displayDerivative(false) diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 31b1e01e05f..7311064caaf 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -68,6 +68,17 @@ int Function::nameWithArgument(char * buffer, size_t bufferSize) { return result; } +Function::RecordDataBuffer::RecordDataBuffer(KDColor color, size_t size) { + /* Size is passed so that the entire derived RecordDataBuffer can be set to 0 + * before initializing parameters. This is done in order to ensure any padding + * bits are set to 0 and prevent storage's CRC32 from depending on junk data. */ + assert(size >= sizeof(*this)); + memset(this, 0, size); + // Members must be initialized after memset + m_color = color; + m_active = true; +} + Function::RecordDataBuffer * Function::recordData() const { assert(!isNull()); Ion::Storage::Record::Data d = value(); diff --git a/apps/shared/function.h b/apps/shared/function.h index 52bdff733f2..f884e550572 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -61,7 +61,7 @@ class Function : public ExpressionModelHandle { * creating dependency on uninitialized values. */ class RecordDataBuffer { public: - RecordDataBuffer(KDColor color) : m_color(color), m_active(true) {} + RecordDataBuffer(KDColor color, size_t size); KDColor color() const { return KDColor::RGB16(m_color); } @@ -77,9 +77,9 @@ class Function : public ExpressionModelHandle { * 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; }; diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index 1557e82b68f..dd935339191 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -11,10 +11,9 @@ namespace Shared { -/* This class is used in a DataBuffer of a Storage::Record. See comment in - * Shared::Function::RecordDataBuffer about packing. */ +// This class is used in a DataBuffer of a Storage::Record -class __attribute__((packed)) Range1D final { +class 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. */ @@ -34,8 +33,8 @@ class __attribute__((packed)) Range1D final { #if __EMSCRIPTEN__ // See comment about emscripten alignement 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/test/function_alignement.cpp b/apps/shared/test/function_alignement.cpp new file mode 100644 index 00000000000..deaf9393eb3 --- /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/sequence.h" +#include "../../sequence/sequence_store.h" + +namespace Shared { + +template +void interactWithBaseRecordMember(F * fct) { + /* Accessing Function record member m_color, which has a 2-byte alignment + * Only efffective in DEBUG=1, as there are no compiler optimizations */ + KDColor color = fct->color(); + (void) color; // Silence compilation warning about unused variable. +} + +void interactWithRecordMember(Sequence::SequenceStore * store, Ion::Storage::Record rec) { + Sequence::Sequence * seq = store->modelForRecord(rec); + /* Setting Sequence type will write record member m_initialConditionSizes, + * which has a 2-byte alignment */ + seq->setType(Sequence::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_handled_on_emscripten) { + /* This test main function is to crash if storage alignment is not handled + * properly on DEBUG and __EMSCRIPTEN__ modes only. 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 From 1879eb36d8cc4b0e0c3352bac8b4d5c0260d73f9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 4 Jun 2020 13:52:24 +0200 Subject: [PATCH 022/560] [ion/test] Typo Fix Change-Id: I49d041dea6333a7f7f1a179e2ffcff2391dbaa28 --- ion/test/storage.cpp | 102 +++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/ion/test/storage.cpp b/ion/test/storage.cpp index 65f5b4d9f6b..df27baadb28 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(); } From 241a217f5852be3f7c0dfcd4e7b48c7e000b3d6d Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 4 Jun 2020 17:10:23 +0200 Subject: [PATCH 023/560] [apps/graph] Add fast scroll for regression and sequence graph views Change-Id: I6b36e929234ec5dc161b0eefb20eb84aa360fe3b --- apps/regression/graph_controller.cpp | 6 +++++- apps/sequence/graph/graph_controller.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 1bb6d962584..eceb6906a06 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -235,7 +235,11 @@ bool GraphController::moveCursorHorizontally(int direction, bool fast) { } *m_selectedDotIndex = dotSelected; } else { - x = m_cursor->x() + direction * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + double step = direction * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + if (fast) { + step *= 5.0; + } + x = m_cursor->x() + step; y = yValue(*m_selectedSeriesIndex, x, globalContext()); } m_cursor->moveTo(x, x, y); diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index d3507710146..cad4979f89e 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -85,7 +85,7 @@ bool GraphController::moveCursorHorizontally(int direction, bool fast) { 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()) * (fast ? 5 : 1); double x = direction > 0 ? xCursorPosition + step: xCursorPosition - step; if (x < 0.0) { From b60c67ff88dd2dd3d83497cd761d3783d645e10c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 9 Jun 2020 17:42:49 +0200 Subject: [PATCH 024/560] [apps] Factorize scrolling speed for long repetition Change-Id: I5fcfaf04e418942664641c4b1cd044cda7f5aebb --- apps/graph/graph/calculation_graph_controller.cpp | 2 +- apps/graph/graph/calculation_graph_controller.h | 2 +- apps/graph/graph/graph_controller.cpp | 4 ++-- apps/graph/graph/graph_controller.h | 2 +- apps/graph/graph/graph_controller_helper.cpp | 7 ++----- apps/graph/graph/graph_controller_helper.h | 2 +- apps/graph/graph/tangent_graph_controller.cpp | 2 +- apps/graph/graph/tangent_graph_controller.h | 2 +- apps/regression/graph_controller.cpp | 7 ++----- apps/regression/graph_controller.h | 2 +- apps/sequence/graph/graph_controller.cpp | 4 ++-- apps/sequence/graph/graph_controller.h | 2 +- apps/shared/simple_interactive_curve_view_controller.cpp | 2 +- apps/shared/simple_interactive_curve_view_controller.h | 2 +- ion/include/ion/events.h | 1 + ion/src/shared/events_modifier.cpp | 4 ++++ 16 files changed, 23 insertions(+), 24 deletions(-) diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index a68dc5156fe..b5849d74dc9 100644 --- a/apps/graph/graph/calculation_graph_controller.cpp +++ b/apps/graph/graph/calculation_graph_controller.cpp @@ -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 14f5cc79760..4bf7a9fa5b2 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -31,7 +31,7 @@ class CalculationGraphController : public Shared::SimpleInteractiveCurveViewCont 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/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index cc9dbb93a9c..3ef644d219e 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -165,9 +165,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 { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 84240fcd3ee..abc05b8c86a 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -27,7 +27,7 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont 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; } diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index ad42ac727d8..90785d29ae2 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,10 +27,7 @@ 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; - } + step *= scrollSpeed; t += dir * step; t = std::max(tMin, std::min(tMax, t)); Coordinate2D xy = function->evaluateXYAtParameter(t, App::app()->localContext()); diff --git a/apps/graph/graph/graph_controller_helper.h b/apps/graph/graph/graph_controller_helper.h index dcc6daf028e..b0e131497e0 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/tangent_graph_controller.cpp b/apps/graph/graph/tangent_graph_controller.cpp index f3cacdbcbc8..afc41f94137 100644 --- a/apps/graph/graph/tangent_graph_controller.cpp +++ b/apps/graph/graph/tangent_graph_controller.cpp @@ -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 e75947a05e4..eed6d637929 100644 --- a/apps/graph/graph/tangent_graph_controller.h +++ b/apps/graph/graph/tangent_graph_controller.h @@ -24,7 +24,7 @@ class TangentGraphController : public Shared::SimpleInteractiveCurveViewControll 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/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index eceb6906a06..519713002c0 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -219,7 +219,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,10 +235,7 @@ bool GraphController::moveCursorHorizontally(int direction, bool fast) { } *m_selectedDotIndex = dotSelected; } else { - double step = direction * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; - if (fast) { - step *= 5.0; - } + double step = direction * scrollSpeed * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; x = m_cursor->x() + step; y = yValue(*m_selectedSeriesIndex, x, globalContext()); } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index d81680c18a9..73c1d99fb3c 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -25,7 +25,7 @@ class GraphController : public Shared::InteractiveCurveViewController { 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: diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index cad4979f89e..76a7d771aab 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -79,13 +79,13 @@ 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()) * (fast ? 5 : 1); + const int step = std::ceil(m_view.pixelWidth()) * scrollSpeed; double x = direction > 0 ? xCursorPosition + step: xCursorPosition - step; if (x < 0.0) { diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index 2177bc23074..b08a2b3ce2f 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -25,7 +25,7 @@ class GraphController final : public Shared::FunctionGraphController { 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()); } diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index 374ce26a9eb..bf1354c1e57 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -28,7 +28,7 @@ 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::longRepetitionScrollSpeed())) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index 7827e649b00..8bf2c771545 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -28,7 +28,7 @@ class SimpleInteractiveCurveViewController : public ZoomCurveViewController, pub /* 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/ion/include/ion/events.h b/ion/include/ion/events.h index d3ba86fa650..19e1b0c673b 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -59,6 +59,7 @@ bool isAlphaActive(); bool isLockActive(); void setLongRepetition(bool longRepetition); bool isLongRepetition(); +int longRepetitionScrollSpeed(); void updateModifiersFromEvent(Event e); void didPressNewKey(); diff --git a/ion/src/shared/events_modifier.cpp b/ion/src/shared/events_modifier.cpp index db7e24d43e7..b97b5376eda 100644 --- a/ion/src/shared/events_modifier.cpp +++ b/ion/src/shared/events_modifier.cpp @@ -41,6 +41,10 @@ bool isLongRepetition() { return sLongRepetition; } +int longRepetitionScrollSpeed() { + return sLongRepetition ? 5 : 1; +}; + void setShiftAlphaStatus(ShiftAlphaStatus s) { sShiftAlphaStatus = s; } From c9964e69c2951fe811a1e2655d3a859ec7ab6086 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 10 Jun 2020 10:09:08 +0200 Subject: [PATCH 025/560] [escher] Scroll speed increase for long repetition in scrollable views. Change-Id: Ie7671ced53da1a144068756f6b584dbb287f9e0e --- escher/src/scrollable_view.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index af59594afa7..27707bef80d 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::longRepetitionScrollSpeed() * 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) { From e35f2cc938db0297833944863220718bde96845e Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 10 Jun 2020 11:04:02 +0200 Subject: [PATCH 026/560] [escher] Scroll speed increase for long repetition in text area Change-Id: Ia468bd3ae3f247937127228a11e3d51653dd2ae2 --- escher/include/escher/text_area.h | 2 +- escher/include/escher/text_input.h | 7 +++-- escher/src/text_area.cpp | 30 ++++++++------------ escher/src/text_input.cpp | 44 +++++++++++++++++++++--------- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 41c177a41a6..51881daebb5 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -131,7 +131,7 @@ class TextArea : public TextInput, public InputEventHandler { ContentView * contentView() { return static_cast(TextInput::contentView()); } private: - void selectUpDown(bool up); + void selectUpDown(bool up, int step); TextAreaDelegate * m_delegate; }; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 34c75dcd88e..0de69538cc3 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -98,9 +98,10 @@ class TextInput : public ScrollableView, public ScrollViewDataSource { 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/src/text_area.cpp b/escher/src/text_area.cpp index ab2162a63b7..2e24fdaadf4 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -106,25 +106,22 @@ bool TextArea::handleEvent(Ion::Events::Event event) { if (handleBoxEvent(event)) { return true; } + int step = Ion::Events::longRepetitionScrollSpeed(); 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); + if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) { + selectUpDown(event == Ion::Events::ShiftUp, step); return true; } - if (event == Ion::Events::Left) { + if (event == Ion::Events::Left || event == Ion::Events::Right) { if (contentView()->resetSelection()) { return true; } - return TextInput::moveCursorLeft(); - } - if (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()) { @@ -158,12 +155,9 @@ bool TextArea::handleEvent(Ion::Events::Event event) { deleteSelection(); return true; } - } else if (event == Ion::Events::Up) { - contentView()->resetSelection(); - contentView()->moveCursorGeo(0, -1); - } else if (event == Ion::Events::Down) { + } else if (event == Ion::Events::Up || event == Ion::Events::Down) { contentView()->resetSelection(); - contentView()->moveCursorGeo(0, 1); + contentView()->moveCursorGeo(0, event == Ion::Events::Up ? -step : step); } else if (event == Ion::Events::Clear) { if (!contentView()->selectionIsEmpty()) { deleteSelection(); @@ -565,9 +559,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_input.cpp b/escher/src/text_input.cpp index 6e573a045c7..ccf943ddd75 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -187,28 +187,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 mouvement + 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 mouvement + 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; } From 70830e0b746a42a5093b5ab75c9e52b277cb5911 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 10 Jun 2020 11:31:10 +0200 Subject: [PATCH 027/560] [escher] Scroll speed increase for long repetition in selectable table Change-Id: Idfc3edcedff5a933b6f2168b475e22ea56ea0ab0 --- apps/home/controller.cpp | 20 +++++++------- apps/shared/store_selectable_table_view.cpp | 27 ++++++++++--------- apps/shared/store_selectable_table_view.h | 2 +- escher/include/escher/selectable_table_view.h | 1 + escher/src/selectable_table_view.cpp | 27 ++++++++++++++++--- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 50af28bfb20..754a367dd2a 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -60,7 +60,7 @@ 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(); - ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(selectionDataSource()->selectedRow()*k_numberOfColumns+selectionDataSource()->selectedColumn()+1); + ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1); if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); } else { @@ -72,14 +72,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; @@ -97,7 +97,7 @@ View * Controller::view() { } int Controller::numberOfRows() const { - return ((numberOfIcons()-1)/k_numberOfColumns)+1; + return ((numberOfIcons() - 1) / k_numberOfColumns) + 1; } int Controller::numberOfColumns() const { @@ -123,7 +123,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()) { appCell->setVisible(false); } else { @@ -148,7 +148,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * unvisible. This trick does not create an endless loop as we ensure not to * stay on a unvisible cell and to initialize the first cell on a visible one * (so the previous one is always visible). */ - int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; + int appIndex = (t->selectedColumn() + t->selectedRow() * k_numberOfColumns) + 1; if (appIndex >= AppsContainer::sharedAppsContainer()->numberOfApps()) { t->selectCellAtLocation(previousSelectedCellX, previousSelectedCellY); } @@ -166,8 +166,8 @@ void Controller::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t * redrawing takes time and is visible at scrolling. Here, we avoid the * background complete redrawing but the code is a bit * clumsy. */ - if (t->selectedRow() == numberOfRows()-1) { - m_view.reloadBottomRow(this, AppsContainer::sharedAppsContainer()->numberOfApps()-1, k_numberOfColumns); + if (t->selectedRow() == numberOfRows() - 1) { + m_view.reloadBottomRow(this, AppsContainer::sharedAppsContainer()->numberOfApps() - 1, k_numberOfColumns); } } diff --git a/apps/shared/store_selectable_table_view.cpp b/apps/shared/store_selectable_table_view.cpp index 83b086deb39..5d547c79d19 100644 --- a/apps/shared/store_selectable_table_view.cpp +++ b/apps/shared/store_selectable_table_view.cpp @@ -9,34 +9,35 @@ StoreSelectableTableView::StoreSelectableTableView(DoublePairStore * store, Resp } bool StoreSelectableTableView::handleEvent(Ion::Events::Event event) { + int step = Ion::Events::longRepetitionScrollSpeed(); 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); + return selectCellAtClippedLocation(i, j); } } diff --git a/apps/shared/store_selectable_table_view.h b/apps/shared/store_selectable_table_view.h index e8592d8316d..09efdf10154 100644 --- a/apps/shared/store_selectable_table_view.h +++ b/apps/shared/store_selectable_table_view.h @@ -12,7 +12,7 @@ class StoreSelectableTableView : public SelectableTableView { 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/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index 6e4fb7ac060..7e5c30a1a3e 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -30,6 +30,7 @@ class SelectableTableView : public TableView, public Responder { 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/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index b77f46011cf..854964760b0 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,17 +151,18 @@ HighlightCell * SelectableTableView::selectedCell() { } bool SelectableTableView::handleEvent(Ion::Events::Event event) { + int step = Ion::Events::longRepetitionScrollSpeed(); if (event == Ion::Events::Down) { - return selectCellAtLocation(selectedColumn(), selectedRow()+1); + return selectCellAtClippedLocation(selectedColumn(), selectedRow() + step); } if (event == Ion::Events::Up) { - return selectCellAtLocation(selectedColumn(), selectedRow()-1); + return selectCellAtClippedLocation(selectedColumn(), selectedRow() - step); } if (event == Ion::Events::Left) { - return selectCellAtLocation(selectedColumn()-1, selectedRow()); + return selectCellAtClippedLocation(selectedColumn() - step, selectedRow()); } if (event == Ion::Events::Right) { - return selectCellAtLocation(selectedColumn()+1, selectedRow()); + return selectCellAtClippedLocation(selectedColumn() + step, selectedRow()); } if (event == Ion::Events::Copy || event == Ion::Events::Cut) { HighlightCell * cell = selectedCell(); From 70cd166da53259271961f2ce4659caaa87e38d5e Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 10 Jun 2020 14:06:14 +0200 Subject: [PATCH 028/560] [escher] Scroll speed increase for long repetition in layout fields Change-Id: I9460cb44631f0b225598804334da5a0e74029915 --- escher/src/layout_field.cpp | 7 ++- poincare/include/poincare/layout_cursor.h | 8 +--- poincare/src/layout_cursor.cpp | 53 ++++++++++++++++++++++- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 4a8a5819763..870a6c33292 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -591,7 +591,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::longRepetitionScrollSpeed(); + result = m_contentView.cursor()->cursorAtDirection(DirectionForMoveEvent(event), shouldRecomputeLayout, false, step); if (result.isDefined()) { if (eventShouldUpdateInsertionCursor(event)) { m_contentView.updateInsertionCursor(); @@ -628,10 +629,12 @@ bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * s return false; } Layout addedSelection; + int step = Ion::Events::longRepetitionScrollSpeed(); LayoutCursor result = m_contentView.cursor()->selectAtDirection( DirectionForSelectionEvent(event), shouldRecomputeLayout, - &addedSelection + &addedSelection, + step ); if (addedSelection.isUninitialized()) { return false; diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index 8979d7b535e..c4ec2885d79 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -95,15 +95,11 @@ class LayoutCursor final { 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, int step = 1); /* Layout modification */ void addEmptyExponentialLayout(); diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 33096cc57ec..a8998f6d4a7 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -69,9 +69,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; } @@ -85,6 +106,36 @@ void LayoutCursor::select(Direction direction, bool * shouldRecomputeLayout, Lay } } +LayoutCursor LayoutCursor::selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection, int step) { + LayoutCursor result = clone(); + if (step <= 0) { + return result; + } + // First step + result.select(direction, shouldRecomputeLayout, selection); + + if (step == 1 || selection->isUninitialized()) { + // If first step failed, result is returned so the situation is handled + return result; + } + // Otherwise, as many steps as possible are performed + // Shallow copy to temporary variables + LayoutCursor result_temp = result; + Layout selection_temp = *selection; + for (int i = 1; i < step; ++i) { + result_temp.select(direction, shouldRecomputeLayout, &selection_temp); + if (selection_temp.isUninitialized()) { + // Return last successful result + return result; + } + // Update last successful result + result = result_temp; + *selection = selection_temp; + assert(result.isDefined()); + } + return result; +} + /* Layout modification */ void LayoutCursor::addEmptyExponentialLayout() { From af32f3314142c8f5529b7c1aee249e9feee3f424 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 10 Jun 2020 16:48:24 +0200 Subject: [PATCH 029/560] [ion] Remove unused function and rename longRepetitionScrollSpeed Change-Id: I11268eb34060c74eb8770ba0eb999f84737c76d8 --- apps/shared/simple_interactive_curve_view_controller.cpp | 2 +- apps/shared/store_selectable_table_view.cpp | 2 +- escher/src/layout_field.cpp | 4 ++-- escher/src/scrollable_view.cpp | 2 +- escher/src/selectable_table_view.cpp | 2 +- escher/src/text_area.cpp | 2 +- ion/include/ion/events.h | 3 +-- ion/src/shared/events_modifier.cpp | 6 +----- 8 files changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index bf1354c1e57..c9220f3d92b 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -28,7 +28,7 @@ bool SimpleInteractiveCurveViewController::textFieldDidReceiveEvent(TextField * bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Event event) { int direction = event == Ion::Events::Left ? -1 : 1; - if (moveCursorHorizontally(direction, Ion::Events::longRepetitionScrollSpeed())) { + if (moveCursorHorizontally(direction, Ion::Events::repetitionFactor())) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio diff --git a/apps/shared/store_selectable_table_view.cpp b/apps/shared/store_selectable_table_view.cpp index 5d547c79d19..f7545ed4c6d 100644 --- a/apps/shared/store_selectable_table_view.cpp +++ b/apps/shared/store_selectable_table_view.cpp @@ -9,7 +9,7 @@ StoreSelectableTableView::StoreSelectableTableView(DoublePairStore * store, Resp } bool StoreSelectableTableView::handleEvent(Ion::Events::Event event) { - int step = Ion::Events::longRepetitionScrollSpeed(); + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::Down) { return selectNonHiddenCellAtClippedLocation(selectedColumn(), selectedRow() + step); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 870a6c33292..73b394c28c1 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -591,7 +591,7 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should return true; } LayoutCursor result; - int step = Ion::Events::longRepetitionScrollSpeed(); + int step = Ion::Events::repetitionFactor(); result = m_contentView.cursor()->cursorAtDirection(DirectionForMoveEvent(event), shouldRecomputeLayout, false, step); if (result.isDefined()) { if (eventShouldUpdateInsertionCursor(event)) { @@ -629,7 +629,7 @@ bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * s return false; } Layout addedSelection; - int step = Ion::Events::longRepetitionScrollSpeed(); + int step = Ion::Events::repetitionFactor(); LayoutCursor result = m_contentView.cursor()->selectAtDirection( DirectionForSelectionEvent(event), shouldRecomputeLayout, diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index 27707bef80d..c198052a518 100644 --- a/escher/src/scrollable_view.cpp +++ b/escher/src/scrollable_view.cpp @@ -12,7 +12,7 @@ ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollV bool ScrollableView::handleEvent(Ion::Events::Event event) { KDPoint translation = KDPointZero; - KDCoordinate scrollStep = Ion::Events::longRepetitionScrollSpeed() * Metric::ScrollStep; + KDCoordinate scrollStep = Ion::Events::repetitionFactor() * Metric::ScrollStep; if (event == Ion::Events::Left) { KDCoordinate movementToEdge = contentOffset().x(); if (movementToEdge > 0) { diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 854964760b0..6d958ed0375 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -151,7 +151,7 @@ HighlightCell * SelectableTableView::selectedCell() { } bool SelectableTableView::handleEvent(Ion::Events::Event event) { - int step = Ion::Events::longRepetitionScrollSpeed(); + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::Down) { return selectCellAtClippedLocation(selectedColumn(), selectedRow() + step); } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 2e24fdaadf4..76e7c0386c6 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -106,7 +106,7 @@ bool TextArea::handleEvent(Ion::Events::Event event) { if (handleBoxEvent(event)) { return true; } - int step = Ion::Events::longRepetitionScrollSpeed(); + int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) { selectLeftRight(event == Ion::Events::ShiftLeft, false, step); return true; diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 19e1b0c673b..9e3d7b72fa6 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -58,8 +58,7 @@ bool isShiftActive(); bool isAlphaActive(); bool isLockActive(); void setLongRepetition(bool longRepetition); -bool isLongRepetition(); -int longRepetitionScrollSpeed(); +int repetitionFactor(); void updateModifiersFromEvent(Event e); void didPressNewKey(); diff --git a/ion/src/shared/events_modifier.cpp b/ion/src/shared/events_modifier.cpp index b97b5376eda..79be2355335 100644 --- a/ion/src/shared/events_modifier.cpp +++ b/ion/src/shared/events_modifier.cpp @@ -37,11 +37,7 @@ void setLongRepetition(bool longRepetition) { sLongRepetition = longRepetition; } -bool isLongRepetition() { - return sLongRepetition; -} - -int longRepetitionScrollSpeed() { +int repetitionFactor() { return sLongRepetition ? 5 : 1; }; From ada369bf08f5a73ebbec202e3d970410fed61811 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 18 Jun 2020 17:44:22 +0200 Subject: [PATCH 030/560] [apps/shared] Add comment for selectNonHiddenCellAtClippedLocation Change-Id: I0e0f7963274688facb467caa934f410e66c9f859 --- apps/shared/store_selectable_table_view.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/store_selectable_table_view.cpp b/apps/shared/store_selectable_table_view.cpp index f7545ed4c6d..8ceb76f6125 100644 --- a/apps/shared/store_selectable_table_view.cpp +++ b/apps/shared/store_selectable_table_view.cpp @@ -37,6 +37,7 @@ bool StoreSelectableTableView::selectNonHiddenCellAtClippedLocation(int i, int j if (j > 1 + numberOfPairsOfCurrentSeries) { j = 1 + numberOfPairsOfCurrentSeries; } + // if negative, j will be clipped in selectCellAtClippedLocation return selectCellAtClippedLocation(i, j); } From 735fc6e1bba0e63679c141a2aff0325f37c93021 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 29 May 2020 15:42:35 +0200 Subject: [PATCH 031/560] [poincare] Generalize Binomial Coefficient n to any real Change-Id: I3cda882e67db4becfe232c6428e14905e922662b --- poincare/src/binomial_coefficient.cpp | 38 +++++++++++++++------------ poincare/test/approximation.cpp | 9 +++++++ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/poincare/src/binomial_coefficient.cpp b/poincare/src/binomial_coefficient.cpp index 248c91ce3fe..7beacb5fb08 100644 --- a/poincare/src/binomial_coefficient.cpp +++ b/poincare/src/binomial_coefficient.cpp @@ -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,28 +75,27 @@ 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); + if (!r1.isInteger() || r1.isNegative()) { + return replaceWithUndefinedInPlace(); + } + + if (!r0.isInteger()) { + // Generalized binomial coefficient + return *this; + } + Integer n = r0.signedIntegerNumerator(); Integer k = r1.signedIntegerNumerator(); if (n.isLowerThan(k)) { - return replaceWithUndefinedInPlace(); + // Generalized binomial coefficient + return *this; } /* If n is too big, we do not reduce in order to avoid too long computation. * The binomial coefficient will be approximatively evaluated later. */ diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index b48a4520058..ac54076c8e0 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -426,6 +426,15 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximation_is_bounded("randint(4,45)", 4.0f, 45.0f, true); assert_expression_approximation_is_bounded("randint(4,45)", 4.0, 45.0, true); + + 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"); } QUIZ_CASE(poincare_approximation_trigonometry_functions) { From 41cf0aaac677ba2c506af649ee774a4b78f49e31 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 12 Jun 2020 16:54:07 +0200 Subject: [PATCH 032/560] [apps] Stopped battery warning when plugged Change-Id: Id4f0d5d730c6045bf69cfadd724dc8ad819494d5 --- apps/apps_container.cpp | 2 +- apps/battery_timer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index cd5f56f5a0c..d583909674b 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -290,7 +290,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 diff --git a/apps/battery_timer.cpp b/apps/battery_timer.cpp index db53d7c90c4..bde13c21cf2 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; From e03bb7432e7fb5ad732390b205c16331debc044e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 16 Jun 2020 16:11:05 +0200 Subject: [PATCH 033/560] [escher] Tweaked LayoutField event handling LayoutField does no redraw its content on every occasion anymore. This allows the input window in Calculation to not scroll to the beginning when writing an overly long formula. Change-Id: I14276828884035463b0a6438bfca4dd76c7f5057 --- escher/include/escher/layout_field.h | 2 +- escher/src/layout_field.cpp | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index d8c4694b1de..b2ebf927c96 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -56,7 +56,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E constexpr static int k_maxNumberOfLayouts = 220; static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field"); void reload(KDSize previousSize); - virtual bool privateHandleEvent(Ion::Events::Event event); + virtual bool privateHandleEvent(Ion::Events::Event event, bool * shouldScrollAndRedraw); bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); bool privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); void scrollRightOfLayout(Poincare::Layout layoutR); diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 73b394c28c1..86fd6aa1ebd 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -421,6 +421,7 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { if (!eventShouldUpdateInsertionCursor(event)) { m_contentView.invalidateInsertionCursor(); } + bool shouldScrollAndRedraw = true; if (privateHandleMoveEvent(event, &moveEventChangedLayout)) { if (!isEditing()) { setEditing(true); @@ -444,7 +445,12 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { } shouldRecomputeLayout = m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout; } - } else if (privateHandleEvent(event)) { + } else if (privateHandleEvent(event, &shouldScrollAndRedraw)) { + if (!shouldScrollAndRedraw) { + /* We escape early to avoid scrolling to the beginning of the expression, + * and to mimic the behaviour of the TextField. */ + return true; + } shouldRecomputeLayout = true; didHandleEvent = true; } @@ -492,8 +498,10 @@ static inline bool IsMoveEvent(Ion::Events::Event event) { static_cast(event) <= static_cast(Ion::Events::Right); } -bool LayoutField::privateHandleEvent(Ion::Events::Event event) { +bool LayoutField::privateHandleEvent(Ion::Events::Event event, bool * shouldScrollAndRedraw) { + assert(*shouldScrollAndRedraw); if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) { + *shouldScrollAndRedraw = false; return true; } if (handleBoxEvent(event)) { From 43fc9374698ab2c10cde73fbb3b77efefc586adc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 17 Jun 2020 17:37:46 +0200 Subject: [PATCH 034/560] [apps/sequence] SequenceTitleCell reload SequenceTitleCell::reloadCell now triggers a layoutSubviews, causing the vertical alignment to be recomputed. This solves issues regarding the alignment of a sequence's name and definition, such as : - typing u_n = v_n ; v_n would be displayed above u_n - typing u_n = (1/3)/2 then changing to u_n = 1/(2/3) without erasing the formula first. u_n would no longer be aligned with the = sign. Change-Id: I4a771d96ea79e42e2f4822e93f9f1cbbcf867610 --- apps/sequence/sequence_title_cell.cpp | 11 +++++++++++ apps/sequence/sequence_title_cell.h | 1 + 2 files changed, 12 insertions(+) diff --git a/apps/sequence/sequence_title_cell.cpp b/apps/sequence/sequence_title_cell.cpp index f1802b10aa9..aeabfec6abb 100644 --- a/apps/sequence/sequence_title_cell.cpp +++ b/apps/sequence/sequence_title_cell.cpp @@ -43,6 +43,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 +63,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/sequence/sequence_title_cell.h index b858fe1a01d..db5c7c20913 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/sequence/sequence_title_cell.h @@ -20,6 +20,7 @@ class SequenceTitleCell : public Shared::FunctionTitleCell { 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; From 9f4aafd6b6a9e499bab01acf400219558e42388a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 17 Jun 2020 17:44:45 +0200 Subject: [PATCH 035/560] [apps/graph] FunctionTitleCell reload Changed TextFieldFunctionTitleCell::reloadCell to solve an issue with the alignment of a function's name and definition when changing the expression : - typing u_n = (1/3)/2 then changing to u_n = 1/(2/3) without erasing the formula first. u_n would no longer be aligned with the = sign. Change-Id: Ib58e4220dc67c77639272c7ea784418bddffb432 --- apps/graph/list/text_field_function_title_cell.cpp | 5 +++++ apps/graph/list/text_field_function_title_cell.h | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index 38f0284f262..a4ef2a48bdf 100644 --- a/apps/graph/list/text_field_function_title_cell.cpp +++ b/apps/graph/list/text_field_function_title_cell.cpp @@ -62,6 +62,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 dd76523e527..05d6dcab7e7 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 @@ class TextFieldFunctionTitleCell : public Shared::FunctionTitleCell, public Resp return &m_textField; } void layoutSubviews(bool force = false) override; + void reloadCell() override; // Responder void didBecomeFirstResponder() override; From 3db1cad18b5e9c1ddbf67c14511d890c5b807fc0 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 24 Jun 2020 14:42:29 +0200 Subject: [PATCH 036/560] [poincare] Move IsApproximatelyEqual to a better helper Change-Id: I056a96b3721005e01c6ef3f166a80a08195ff338 --- apps/regression/test/model.cpp | 21 +++++++++++---------- apps/statistics/test/store.cpp | 3 ++- poincare/include/poincare/helpers.h | 1 - poincare/src/helpers.cpp | 16 ---------------- poincare/test/helper.cpp | 15 +++++++++++++++ poincare/test/helper.h | 3 +++ 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 16b21611421..4148288c35d 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -6,6 +6,7 @@ #include "../regression_context.h" #include "../store.h" #include +#include using namespace Poincare; using namespace Regression; @@ -39,13 +40,13 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T double * coefficients = store.coefficientsForSeries(series, &context); int numberOfCoefs = store.modelForSeries(series)->numberOfCoefficients(); for (int i = 0; i < numberOfCoefs; i++) { - quiz_assert(Helpers::IsApproximatelyEqual(coefficients[i], trueCoefficients[i], precision, reference)); + 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 >= 0.0); - quiz_assert(Helpers::IsApproximatelyEqual(r2, trueR2, precision, reference)); + quiz_assert(IsApproximatelyEqual(r2, trueR2, precision, reference)); } QUIZ_CASE(linear_regression) { @@ -190,15 +191,15 @@ void assert_column_calculations_is(double * xi, int numberOfPoints, double trueM // The least likely value to be null is trueSquaredSum double reference = trueSquaredSum; - quiz_assert(Helpers::IsApproximatelyEqual(variance, trueVariance, precision, reference)); - quiz_assert(Helpers::IsApproximatelyEqual(squaredSum, trueSquaredSum, precision, reference)); + quiz_assert(IsApproximatelyEqual(variance, trueVariance, precision, reference)); + quiz_assert(IsApproximatelyEqual(squaredSum, trueSquaredSum, precision, reference)); // adapt the reference reference = std::sqrt(trueSquaredSum); - quiz_assert(Helpers::IsApproximatelyEqual(mean, trueMean, precision, reference)); - quiz_assert(Helpers::IsApproximatelyEqual(sum, trueSum, precision, reference)); - quiz_assert(Helpers::IsApproximatelyEqual(standardDeviation, trueStandardDeviation, precision, reference)); + 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) { @@ -236,8 +237,8 @@ void assert_regression_calculations_is(double * xi, double * yi, int numberOfPoi // trueProductSum and trueCovariance are using each other as reference // By construction, they often have a close value with a numberOfPoints factor - quiz_assert(Helpers::IsApproximatelyEqual(covariance, trueCovariance, precision, trueProductSum / numberOfPoints)); - quiz_assert(Helpers::IsApproximatelyEqual(productSum, trueProductSum, precision, trueCovariance * numberOfPoints)); + 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) @@ -245,7 +246,7 @@ void assert_regression_calculations_is(double * xi, double * yi, int numberOfPoi double r = store.correlationCoefficient(series); quiz_assert(r >= 0.0); - quiz_assert(Helpers::IsApproximatelyEqual(r, trueR, precision, reference)); + quiz_assert(IsApproximatelyEqual(r, trueR, precision, reference)); } QUIZ_CASE(regression_calculation) { diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index 1126dd8d9fc..24c43f4c653 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -4,6 +4,7 @@ #include #include "../store.h" #include +#include using namespace Poincare; @@ -12,7 +13,7 @@ namespace Statistics { 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*/) - || Helpers::IsApproximatelyEqual(d1, d2, precision, reference)); + || IsApproximatelyEqual(d1, d2, precision, reference)); } 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 trueFirstQuartile, double trueThirdQuartile, double trueQuartileRange, double trueMedian, double trueSum, double trueSquaredValueSum) { diff --git a/poincare/include/poincare/helpers.h b/poincare/include/poincare/helpers.h index dab8dd8c7de..517a64d2a2d 100644 --- a/poincare/include/poincare/helpers.h +++ b/poincare/include/poincare/helpers.h @@ -11,7 +11,6 @@ namespace Helpers { 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); -bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference); } diff --git a/poincare/src/helpers.cpp b/poincare/src/helpers.cpp index a412693f8cc..57781724825 100644 --- a/poincare/src/helpers.cpp +++ b/poincare/src/helpers.cpp @@ -98,21 +98,5 @@ bool Rotate(uint32_t * dst, uint32_t * src, size_t len) { return true; } -bool IsApproximatelyEqual(double observedValue, double expectedValue, double precision, double reference) { - /* Return true if observedValue and expectedValue are approximately equal, according to precision and reference parameters */ - 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; -} - } } diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index 399cb589fb2..d940dd1086a 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -103,6 +103,21 @@ 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) { int numberOfDigits = sizeof(T) == sizeof(double) ? PrintFloat::k_numberOfStoredSignificantDigits : PrintFloat::k_numberOfPrintedSignificantDigits; diff --git a/poincare/test/helper.h b/poincare/test/helper.h index afb122eb1fe..841a5bc1126 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -47,6 +47,9 @@ void assert_parsed_expression_simplify_to(const char * expression, const char * // 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); From 0927ffca29da02b78794e9a70f5bcc497f139a93 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 11 Jun 2020 11:03:56 +0200 Subject: [PATCH 037/560] [apps/calculation] Moved input buffer to snapshot The cacheBuffer from EditExpressionController has been moved in the snapshot, to act as a zone where layout information can be kept while the app is inactive. Change-Id: If9206abf77f37d6b984826e5f09658848381a75f --- apps/calculation/app.cpp | 4 +++- apps/calculation/app.h | 4 ++++ apps/calculation/edit_expression_controller.cpp | 5 +++-- apps/calculation/edit_expression_controller.h | 16 +++++++++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 08d23b4bb47..077fcc75d92 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -27,6 +27,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() { @@ -41,7 +43,7 @@ App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculatio 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()) { } diff --git a/apps/calculation/app.h b/apps/calculation/app.h index 6914c82df46..eaa0040fa06 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -24,11 +24,15 @@ class App : public Shared::ExpressionFieldDelegateApp { void reset() override; Descriptor * descriptor() override; CalculationStore * calculationStore() { return &m_calculationStore; } + char * cacheBuffer() { return m_cacheBuffer; } + size_t * cacheBufferInformationAddress() { return &m_cacheBufferInformation; } private: 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()); diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 2ae7929a93c..060042c6ea4 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) { diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 1605f8b8d12..cea45b339da 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -14,7 +14,17 @@ 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; @@ -49,8 +59,8 @@ class EditExpressionController : public ViewController, public Shared::TextField 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; From 9527894b25f633ba43d838d3c9c6143d5b344b2e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 11 Jun 2020 11:16:01 +0200 Subject: [PATCH 038/560] [poincare] Function LayoutFromAddress Created function Layout::LayoutFromAddress, which creates in the pool a layout that has been stored in a buffer. Functionally identical to Expression::ExpressionFromAddress. Change-Id: I204e6b7d42f259ef2732563aa2a168a644060867 --- poincare/include/poincare/layout.h | 1 + poincare/src/layout.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 074943a690d..d2603139a01 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -25,6 +25,7 @@ class Layout : public TreeHandle { 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(); } diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 9a810cd2881..1caa20ce667 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, From 8ab23f66b5d7237467665da36791955405f72db2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 11 Jun 2020 11:40:42 +0200 Subject: [PATCH 039/560] [escher/expression_field] Methods on layout Added some methods to manipulate the layout of a LayoutField from an ExpressionField, like storing into and restoring from a buffer. Change-Id: I7b7b5b53626f19237a5215bd196e897a518e9577 --- escher/include/escher/expression_field.h | 2 ++ escher/include/escher/layout_field.h | 1 + escher/src/expression_field.cpp | 43 ++++++++++++++++++++++++ escher/src/layout_field.cpp | 8 +++++ 4 files changed, 54 insertions(+) diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index a79da0bdb1f..e89c1dc98c0 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -26,6 +26,8 @@ class ExpressionField : public Responder, public View { 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 dumpLayout(char * buffer, size_t bufferSize) const; + void restoreLayout(const char * buffer, size_t size); /* View */ int numberOfSubviews() const override { return 1; } diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index b2ebf927c96..a39e585babb 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -35,6 +35,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E 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/src/expression_field.cpp b/escher/src/expression_field.cpp index 300f1d039db..599f69b7f4f 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -121,3 +121,46 @@ KDCoordinate ExpressionField::inputViewHeight() const { std::min(k_maximalHeight, std::max(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); } + +size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) const { + size_t size; + size_t returnValue; + char * currentLayout; + if (editionIsInTextField()) { + size = strlen(m_textField.draftTextBuffer()) + 1; + currentLayout = 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 { + size = m_layoutField.layout().size(); + currentLayout = reinterpret_cast(m_layoutField.layout().node()); + returnValue = size; + } + if (size > bufferSize - 1) { + buffer[0] = 0; + return 0; + } + memcpy(buffer, currentLayout, size); + return returnValue; +} + +void ExpressionField::restoreLayout(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 86fd6aa1ebd..ff6b493dde6 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -314,6 +314,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; } From e295516928b31383ed7f976b06ee243c67f91ea3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 11 Jun 2020 16:32:49 +0200 Subject: [PATCH 040/560] [apps/calculation] Remember input layout When leaving the Calculation app, the layout currently being edited is now stored in a buffer, to be restored when the app resumes. Change-Id: I59c16e18f7193bdcbb36888b54cb2722897f85f5 --- apps/calculation/edit_expression_controller.cpp | 9 ++++++++- apps/calculation/edit_expression_controller.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 060042c6ea4..e72762f7f9b 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -56,9 +56,16 @@ void EditExpressionController::insertTextBody(const char * text) { void EditExpressionController::didBecomeFirstResponder() { m_contentView.mainView()->scrollToBottom(); m_contentView.expressionField()->setEditing(true, false); + m_contentView.expressionField()->restoreLayout(m_cacheBuffer, *m_cacheBufferInformation); + clearCacheBuffer(); Container::activeApp()->setFirstResponder(m_contentView.expressionField()); } +void EditExpressionController::willExitResponderChain(Responder * nextFirstResponder) { + *m_cacheBufferInformation = m_contentView.expressionField()->dumpLayout(m_cacheBuffer, k_cacheBufferSize); + ::ViewController::willExitResponderChain(nextFirstResponder); +} + void EditExpressionController::viewWillAppear() { m_historyController->viewWillAppear(); } @@ -129,7 +136,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 cea45b339da..fdef423bee5 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -27,6 +27,7 @@ class EditExpressionController : public ViewController, public Shared::TextField View * view() override { return &m_contentView; } void didBecomeFirstResponder() override; + void willExitResponderChain(Responder * nextFirstResponder) override; void viewWillAppear() override; void insertTextBody(const char * text); @@ -56,6 +57,7 @@ class EditExpressionController : public ViewController, public Shared::TextField 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); From e2e062e1288789c524aca417cb86be32ac485046 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 16 Jun 2020 10:34:41 +0200 Subject: [PATCH 041/560] [poincare/layout] Changed Fraction collapsing Fraction are now only collapsed if they are the first thing to be collapsed or if a parenthesis is open. Change-Id: Ie7f3a99498b9981dff70221efd7de6bee6d0c379 --- poincare/include/poincare/fraction_layout.h | 1 + poincare/src/fraction_layout.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/poincare/include/poincare/fraction_layout.h b/poincare/include/poincare/fraction_layout.h index 99a054aa2ad..8c6c4d10114 100644 --- a/poincare/include/poincare/fraction_layout.h +++ b/poincare/include/poincare/fraction_layout.h @@ -22,6 +22,7 @@ class FractionLayoutNode /*final*/ : public LayoutNode { 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/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index 43dc8f7c327..ac035cf1b3d 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -161,6 +161,24 @@ 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); + Layout absorbingChild = absorbingSibling.childAtIndex((goingLeft) ? absorbingSibling.leftCollapsingAbsorbingChildIndex() : absorbingSibling.rightCollapsingAbsorbingChildIndex()); + return absorbingChild.type() == LayoutNode::Type::HorizontalLayout && absorbingChild.isEmpty(); +} + void FractionLayoutNode::didCollapseSiblings(LayoutCursor * cursor) { if (cursor != nullptr) { cursor->setLayoutNode(denominatorLayout()); From 4ca4d4f45e2c971a49d5489650a6e617a98ab59f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 16 Jun 2020 10:57:40 +0200 Subject: [PATCH 042/560] [poincare/layout] Factored Parenthesis collapsing Parenthesis collapsing is no longer handled by LeftParenthesisLayout and RightParenthesisLayout, but by ParenthesisLayout itself. Change-Id: I6797cd40ee02881544e01e9b88f31a597c51d179 --- .../poincare/left_parenthesis_layout.h | 3 --- .../include/poincare/parenthesis_layout.h | 1 + .../poincare/right_parenthesis_layout.h | 3 --- poincare/src/left_parenthesis_layout.cpp | 8 -------- poincare/src/parenthesis_layout.cpp | 19 +++++++++++++++++++ poincare/src/right_parenthesis_layout.cpp | 8 -------- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/poincare/include/poincare/left_parenthesis_layout.h b/poincare/include/poincare/left_parenthesis_layout.h index 96c9726d115..5c738e7926d 100644 --- a/poincare/include/poincare/left_parenthesis_layout.h +++ b/poincare/include/poincare/left_parenthesis_layout.h @@ -16,9 +16,6 @@ class LeftParenthesisLayoutNode final : public ParenthesisLayoutNode { 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/parenthesis_layout.h b/poincare/include/poincare/parenthesis_layout.h index 8d23301394a..55681f54efe 100644 --- a/poincare/include/poincare/parenthesis_layout.h +++ b/poincare/include/poincare/parenthesis_layout.h @@ -25,6 +25,7 @@ class ParenthesisLayoutNode : public BracketLayoutNode { stream << "ParenthesisLayout"; } #endif + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; protected: KDSize computeSize() override { diff --git a/poincare/include/poincare/right_parenthesis_layout.h b/poincare/include/poincare/right_parenthesis_layout.h index 1f30b614022..bcf64a2db8d 100644 --- a/poincare/include/poincare/right_parenthesis_layout.h +++ b/poincare/include/poincare/right_parenthesis_layout.h @@ -16,9 +16,6 @@ class RightParenthesisLayoutNode final : public ParenthesisLayoutNode { 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/src/left_parenthesis_layout.cpp b/poincare/src/left_parenthesis_layout.cpp index c7454d81de1..1aa3e01e0fb 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/parenthesis_layout.cpp b/poincare/src/parenthesis_layout.cpp index dece3d9d97f..cfbcb1ae6ed 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/right_parenthesis_layout.cpp b/poincare/src/right_parenthesis_layout.cpp index 09ff93132c1..798e4924451 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); } From abb662b606c4016b81786dcc06756fd2aeb8d9f7 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 16 Jun 2020 12:12:06 +0200 Subject: [PATCH 043/560] [poincare/test] Added tests on fraction layouts Change-Id: I351d707c03b96c93e090e2e74c1841ebe9d7a691 --- poincare/test/layout.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/poincare/test/layout.cpp b/poincare/test/layout.cpp index 983184abbb5..534c2ff1f52 100644 --- a/poincare/test/layout.cpp +++ b/poincare/test/layout.cpp @@ -76,6 +76,32 @@ 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_parentheses_size) { From 653b7d0ac47653f153ac4c35213f0413ca346611 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Tue, 5 May 2020 15:50:03 +0200 Subject: [PATCH 044/560] [poincare] Bigger product and sum signs Replacing the product and sum signs with bigger and antialiased ones. --- poincare/include/poincare/sequence_layout.h | 4 +- poincare/src/sum_layout.cpp | 45 ++++++++++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/poincare/include/poincare/sequence_layout.h b/poincare/include/poincare/sequence_layout.h index 7f37b0ffc14..730931d3d1c 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 = 30; + constexpr static KDCoordinate k_symbolWidth = 22; using LayoutNode::LayoutNode; diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 64e27437cc5..25244ae71a3 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -6,21 +6,36 @@ 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}, + {0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xF8, 0x2E, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xD5, 0x15, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFE, 0x4C, 0x7A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0x1F, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0x16, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x93, 0x37, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x4B, 0x7B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x4B, 0x7A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x93, 0x36, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0x15, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0x1E, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFE, 0x4C, 0x79, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xD5, 0x15, 0xDA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xF8, 0x2E, 0x9D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; From 1e0449791db1330121e5e8f5a84d3328866d533f Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 6 May 2020 15:30:23 +0200 Subject: [PATCH 045/560] [poincare/integral_layout] Changing the integral symbol Changed the look of the integral symbol and the positions of the upper and lower bounds. Added a drawing of the window to explain the meaning of various margins. Change-Id: I3ffc07848dc9ad68dce3c0e01853d548e31dd152 --- poincare/include/poincare/integral_layout.h | 9 +- poincare/src/integral_layout.cpp | 161 ++++++++++++++++---- 2 files changed, 140 insertions(+), 30 deletions(-) diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 84c91eca88c..0099e8d3eb0 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -9,7 +9,8 @@ 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 +41,18 @@ class IntegralLayoutNode final : public LayoutNode { // 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_boundHeightMargin = 5; 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_integrandHeigthMargin = 4; constexpr static KDCoordinate k_lineThickness = 1; // int(f(x), x, a, b) LayoutNode * integrandLayout() { return childAtIndex(k_integrandLayoutIndex); } // f(x) diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index b9524c4890f..12425920c2f 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,6 +218,86 @@ CodePoint IntegralLayoutNode::XNTCodePoint(int childIndex) const { return (childIndex == k_integrandLayoutIndex || childIndex == k_differentialLayoutIndex) ? CodePoint('x') : UCodePointNull; } +/* +Window configuration explained : +Vertical margins and offsets ++-----------------------------------------------------------------+ +| | | +| k_boundHeightMargin | +| | | +| +------------------+ | +| | upperBoundHeight | | +| +------------------+ | +| | | +| k_integrandHeightMargin | +| | | +| | +| +++ | +| +++ k_symbolHeight | +| +++ | +| | +| ||| | +| ||| | +| ||| | +| ||| | +| ||| centralArgumentHeight | +| ||| | +| ||| | +| ||| | +| ||| | +| | +| +++ | +| +++ k_symbolHeight | +| +++ | +| | | +| k_integrandHeightMargin | +| | | +| +------------------+ | +| | lowerBoundHeight | | +| +------------------+ | +| | | +| k_boundHeightMargin | +| | | ++-----------------------------------------------------------------+ + +Horizontal margins and offsets ++-------------------------------------------------------------------------+ +| | | +| | +| | +------------------+ | +| |<-upperBoundLengthOffset->| upperBoundWidth | | +| | +------------------+ | +| | +| | | | +| +++ | +| | | +++ | +| +++ | +| | | | +| ||| | +| | |||| | +| ||| | +| | |||| | +|<-k_boundWidthMargin->|<-integralSymbolOffset->|||| | +| | |||| | +| ||| | +| | |||| | +| ||| | +| | | a | | +| +++ | +| | | a++| | +| +++ | +| | | a | | +| | +| | +------------------+ | +| |<-lowerBoundLengthOffset->| lowerBoundWidth | | +| | +------------------+ | +| | +| | | ++-------------------------------------------------------------------------+ +With a = k_symbolWidth + k_lineThickness +integralSymbolOffset = std::max((int)upperBoundSize.width()/2, (int)lowerBoundSize.width()/2) - k_integrandWidthMargin; +*/ + KDSize IntegralLayoutNode::computeSize() { KDSize dSize = k_font->stringSize("d"); KDSize integrandSize = integrandLayout()->layoutSize(); @@ -215,13 +305,12 @@ KDSize IntegralLayoutNode::computeSize() { 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 height = upperBoundSize.height() + k_integrandHeigthMargin + k_symbolHeight + centralArgumentHeight() + k_symbolHeight + k_integrandHeigthMargin + lowerBoundSize.height(); return KDSize(width, height); } KDCoordinate IntegralLayoutNode::computeBaseline() { - return upperBoundLayout()->layoutSize().height() + k_integrandHeigthMargin + std::max(integrandLayout()->baseline(), differentialLayout()->baseline()); + return upperBoundLayout()->layoutSize().height() + k_integrandHeigthMargin + k_symbolHeight + std::max(integrandLayout()->baseline(), differentialLayout()->baseline()); } KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { @@ -229,42 +318,60 @@ KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { KDSize upperBoundSize = upperBoundLayout()->layoutSize(); KDCoordinate x = 0; KDCoordinate y = 0; + // Offsets the integral bounds in order to center them + int difference = (upperBoundSize.width() - lowerBoundSize.width())/2; if (child == lowerBoundLayout()) { - x = k_symbolWidth+k_lineThickness+k_boundWidthMargin; - y = computeSize().height()-lowerBoundSize.height(); + x = std::max(0, difference); + y = computeSize().height() - lowerBoundSize.height(); } else if (child == upperBoundLayout()) { - x = k_symbolWidth+k_lineThickness+k_boundWidthMargin;; + x = std::max(0, -difference); y = 0; } else if (child == integrandLayout()) { - x = k_symbolWidth +k_lineThickness+ k_boundWidthMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin; + x = k_symbolWidth + k_lineThickness + k_boundWidthMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin; y = computeBaseline()-integrandLayout()->baseline(); - } else if (child == differentialLayout()) { + } else { + assert(child == differentialLayout()); x = computeSize().width() - k_differentialWidthMargin - differentialLayout()->layoutSize().width(); y = computeBaseline()-differentialLayout()->baseline(); - } else { - assert(false); } return KDPoint(x,y); } +KDCoordinate IntegralLayoutNode::centralArgumentHeight() { + 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); +} + 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()); + KDSize lowerBoundSize = lowerBoundLayout()->layoutSize(); 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 integralSymbolOffset = std::max((int)upperBoundSize.width(), (int)lowerBoundSize.width())/2 - k_integrandWidthMargin; + KDCoordinate offsetX = p.x() + integralSymbolOffset; + KDCoordinate offsetY = p.y() + upperBoundSize.height() + k_integrandHeigthMargin; + + // 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; + KDCoordinate k_centralArgumentHeight = centralArgumentHeight(); + ctx->fillRect(KDRect(offsetX, offsetY, k_lineThickness, k_centralArgumentHeight), expressionColor); + + // Lower part + offsetX = offsetX - k_symbolWidth + k_lineThickness; + offsetY = offsetY + k_centralArgumentHeight; + 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)); From 92f56445fe5238056123b24df2f438ce758cf2d6 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 6 May 2020 18:04:42 +0200 Subject: [PATCH 046/560] [poincare/sum_layout] Modified the way to draw sigma New way of drawing sigma sign. Only a single branch is stored. The full symbol is created by mirroring half a symbol. Change-Id: I0fc32393203e51578c88b01125a907d402291534 --- poincare/src/sum_layout.cpp | 93 +++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 25244ae71a3..0c1051955ab 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -3,48 +3,72 @@ #include #include -namespace Poincare { -const uint8_t symbolPixel[SumLayoutNode::k_symbolHeight][SumLayoutNode::k_symbolWidth] = { - {0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0xF8, 0x2E, 0x9E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xD5, 0x15, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFE, 0x4C, 0x7A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0x1F, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0x16, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x93, 0x37, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x4B, 0x7B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x4B, 0x7A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x93, 0x36, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD3, 0x15, 0xDB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6F, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0x1E, 0xEE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFE, 0x4C, 0x79, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xD5, 0x15, 0xDA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xF8, 0x2E, 0x9D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, -}; +namespace Poincare { +// 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/2-1)*k_significantPixelWidth] = { + 0xF8, 0x2E, 0x9E, 0xFF, 0xFF, 0xFF, + 0xFF, 0xD5, 0x15, 0xDB, 0xFF, 0xFF, + 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, + 0xFF, 0xFE, 0x4C, 0x7A, 0xFF, 0xFF, + 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, + 0xFF, 0xFF, 0xB7, 0x1F, 0xEE, 0xFF, + 0xFF, 0xFF, 0x6F, 0x56, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, + 0xFF, 0xFF, 0xD3, 0x16, 0xDB, 0xFF, + 0xFF, 0xFF, 0xFF, 0x93, 0x37, 0xFA, + 0xFF, 0xFF, 0xFE, 0x4B, 0x7B, 0xFF, + 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, + 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, +}; 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 straight line + // First pixel is antialized + symbolPixel[0] = 0x70; + // The rest of the bar is black + for (int j = 1; j < k_symbolWidth; j++) { + symbolPixel[j] = 0x00; + } + static_assert(k_symbolHeight%2 == 0, "sum_layout : k_symbolHeight is not even"); + for (int i = 1; i < k_symbolHeight/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; 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(); @@ -58,5 +82,4 @@ void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, // Render the "n=" and the parentheses. SequenceLayoutNode::render(ctx, p, expressionColor, backgroundColor); } - } From 9998f9b5789c639b133a4b8cce333d18687b9410 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 11 May 2020 12:03:07 +0200 Subject: [PATCH 047/560] [apps/shared/ok_view.cpp] Changed the look of the OK symbol New look for the ok symbol. The updated version is antialiazed. Change-Id: I1ee0e4b6680a7582f6216854a5e5cfe308801669 --- apps/shared/ok_view.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/shared/ok_view.cpp b/apps/shared/ok_view.cpp index d524e5725fd..e97d9a96e92 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, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xBC, 0x27, 0x76, 0xFC, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x76, 0x3D, 0xED, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEE, 0x3E, 0x76, 0xFF, 0xFF}, + {0xFF, 0xAB, 0x26, 0xEC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEC, 0x27, 0xAB, 0xFF}, + {0xF9, 0x1D, 0xC1, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xC2, 0x1C, 0xF9}, + {0xA4, 0x43, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0x43, 0xA3}, + {0x54, 0x98, 0xFB, 0xFB, 0xFB, 0xEC, 0x4C, 0x06, 0x3E, 0xE1, 0xFB, 0x00, 0xFB, 0xE6, 0x00, 0xFB, 0xFB, 0xFB, 0x98, 0x53}, + {0x1F, 0xD8, 0xFB, 0xFB, 0xFB, 0x5D, 0x64, 0xEF, 0x73, 0x53, 0xFB, 0x00, 0xE6, 0x2A, 0x7D, 0xFB, 0xFB, 0xFB, 0xD9, 0x1F}, + {0x07, 0xF5, 0xFB, 0xFB, 0xFB, 0x0F, 0xE9, 0xFB, 0xE8, 0x0C, 0xFB, 0x00, 0x2A, 0x69, 0xFA, 0xFB, 0xFB, 0xFB, 0xF5, 0x06}, + {0x0D, 0xEE, 0xFB, 0xFB, 0xFB, 0x0F, 0xE9, 0xFB, 0xE8, 0x0C, 0xFB, 0x00, 0x7D, 0x29, 0xC4, 0xFB, 0xFB, 0xFB, 0xF5, 0x06}, + {0x26, 0xD4, 0xFB, 0xFB, 0xFB, 0x5E, 0x62, 0xEE, 0x73, 0x53, 0xFB, 0x00, 0xFB, 0x7E, 0x6F, 0xFB, 0xFB, 0xFB, 0xD9, 0x1F}, + {0x54, 0x98, 0xFB, 0xFB, 0xFB, 0xEC, 0x4D, 0x07, 0x3F, 0xE2, 0xFB, 0x00, 0xFB, 0xD2, 0x00, 0xFB, 0xFB, 0xFB, 0x98, 0x53}, + {0xA4, 0x43, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0x43, 0xA3}, + {0xF9, 0x1D, 0xC2, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xC3, 0x1C, 0xF8}, + {0xFF, 0xAA, 0x28, 0xEC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEC, 0x27, 0xAA, 0xFF}, + {0xFF, 0xFF, 0x75, 0x3E, 0xEE, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEE, 0x3E, 0x75, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFC, 0x6C, 0x27, 0xBC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 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 { From 45db7f1d48aec1431593fd68dea54f859fbeec77 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 13 May 2020 10:44:39 +0200 Subject: [PATCH 048/560] [poincare/integral_layout.cpp] Reduced the gap between integral sign and integrand The gap between the integral sign and the integrand has been reduced. It looks more natural this way. --- poincare/src/integral_layout.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index 12425920c2f..aeaf99d45be 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -264,9 +264,9 @@ Horizontal margins and offsets +-------------------------------------------------------------------------+ | | | | | -| | +------------------+ | -| |<-upperBoundLengthOffset->| upperBoundWidth | | -| | +------------------+ | +| | +-----------------+ | +| |<-upperBoundLengthOffset->| upperBoundWidth | | +| | +-----------------+ | | | | | | | | +++ | @@ -277,20 +277,20 @@ Horizontal margins and offsets | | |||| | | ||| | | | |||| | -|<-k_boundWidthMargin->|<-integralSymbolOffset->|||| | -| | |||| | +|<-k_boundWidthMargin->|<-integralSymbolOffset->|||| dx | +| | |||| ^ | | ||| | -| | |||| | +| | |||| | | | ||| | -| | | a | | +| | | a | | | | +++ | -| | | a++| | +| | | a++| | | | +++ | -| | | a | | +| | | a | | | | | -| | +------------------+ | -| |<-lowerBoundLengthOffset->| lowerBoundWidth | | -| | +------------------+ | +| | +-----------------+ | +| |<-lowerBoundLengthOffset->| lowerBoundWidth | | +| | +-----------------+ | | | | | | +-------------------------------------------------------------------------+ @@ -304,7 +304,7 @@ KDSize IntegralLayoutNode::computeSize() { 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 width = k_symbolWidth+std::max(lowerBoundSize.width(), upperBoundSize.width())/2+integrandSize.width()+2*k_differentialWidthMargin+dSize.width()+differentialSize.width(); KDCoordinate height = upperBoundSize.height() + k_integrandHeigthMargin + k_symbolHeight + centralArgumentHeight() + k_symbolHeight + k_integrandHeigthMargin + lowerBoundSize.height(); return KDSize(width, height); } @@ -327,7 +327,7 @@ KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { x = std::max(0, -difference); y = 0; } else if (child == integrandLayout()) { - x = k_symbolWidth + k_lineThickness + k_boundWidthMargin+std::max(lowerBoundSize.width(), upperBoundSize.width())+k_integrandWidthMargin; + x = k_symbolWidth + std::max(lowerBoundSize.width(), upperBoundSize.width())/2; y = computeBaseline()-integrandLayout()->baseline(); } else { assert(child == differentialLayout()); From bd74ab97182e590d789ed81a962f1c1dae7f9504 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 13 May 2020 11:23:59 +0200 Subject: [PATCH 049/560] [poincare/sequence_layout] Modified the height of sigma and product signs Because of their previous even height, those symbols were not aligned with fractions. Now that they have an uneven size, they line up correctly. --- poincare/include/poincare/sequence_layout.h | 2 +- poincare/src/sum_layout.cpp | 43 ++++++++++----------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/poincare/include/poincare/sequence_layout.h b/poincare/include/poincare/sequence_layout.h index 730931d3d1c..b164271635a 100644 --- a/poincare/include/poincare/sequence_layout.h +++ b/poincare/include/poincare/sequence_layout.h @@ -8,7 +8,7 @@ namespace Poincare { class SequenceLayoutNode : public LayoutNode { public: - constexpr static KDCoordinate k_symbolHeight = 30; + constexpr static KDCoordinate k_symbolHeight = 29; constexpr static KDCoordinate k_symbolWidth = 22; using LayoutNode::LayoutNode; diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 0c1051955ab..74cdee9d21a 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -9,21 +9,21 @@ namespace Poincare { // 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/2-1)*k_significantPixelWidth] = { - 0xF8, 0x2E, 0x9E, 0xFF, 0xFF, 0xFF, - 0xFF, 0xD5, 0x15, 0xDB, 0xFF, 0xFF, - 0xFF, 0x95, 0x35, 0xFA, 0xFF, 0xFF, - 0xFF, 0xFE, 0x4C, 0x7A, 0xFF, 0xFF, - 0xFF, 0xE9, 0x1B, 0xC0, 0xFF, 0xFF, - 0xFF, 0xFF, 0xB7, 0x1F, 0xEE, 0xFF, - 0xFF, 0xFF, 0x6F, 0x56, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF7, 0x2E, 0x9F, 0xFF, - 0xFF, 0xFF, 0xD3, 0x16, 0xDB, 0xFF, - 0xFF, 0xFF, 0xFF, 0x93, 0x37, 0xFA, - 0xFF, 0xFF, 0xFE, 0x4B, 0x7B, 0xFF, - 0xFF, 0xFF, 0xFF, 0xE8, 0x1A, 0xC0, - 0xFF, 0xFF, 0xFF, 0xB6, 0x20, 0xEF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x6D, 0x57, +const uint8_t symbolPixelOneBranch[((SumLayoutNode::k_symbolHeight-1)/2)*k_significantPixelWidth] = { + 0xF1, 0x21, 0xB2, 0xFF, 0xFF, 0xFF, + 0xFF, 0xC5, 0x18, 0xE7, 0xFF, 0xFF, + 0xFF, 0x7F, 0x46, 0xFE, 0xFF, 0xFF, + 0xFF, 0xFC, 0x39, 0x8E, 0xFF, 0xFF, + 0xFF, 0xDF, 0x15, 0xD0, 0xFF, 0xFF, + 0xFF, 0xFF, 0xA4, 0x2A, 0xF6, 0xFF, + 0xFF, 0xFF, 0x5A, 0x69, 0xFF, 0xFF, + 0xFF, 0xFF, 0xF1, 0x21, 0xB2, 0xFF, + 0xFF, 0xFF, 0xC5, 0x18, 0xE7, 0xFF, + 0xFF, 0xFF, 0xFF, 0x7F, 0x46, 0xFE, + 0xFF, 0xFF, 0xFC, 0x3A, 0x8E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xDF, 0x15, 0xD0, + 0xFF, 0xFF, 0xFF, 0xA0, 0x20, 0xF6, + 0xFF, 0xFF, 0XFF, 0xFF, 0xAB, 0x28, }; int SumLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { @@ -35,15 +35,12 @@ void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, uint8_t symbolPixel[k_symbolHeight * k_symbolWidth]; int whiteOffset; - // Taking care of the first line which is a straight line - // First pixel is antialized - symbolPixel[0] = 0x70; - // The rest of the bar is black - for (int j = 1; j < k_symbolWidth; j++) { + // Taking care of the first line which is a black straight line + for (int j = 0; j < k_symbolWidth; j++) { symbolPixel[j] = 0x00; } - static_assert(k_symbolHeight%2 == 0, "sum_layout : k_symbolHeight is not even"); - for (int i = 1; i < k_symbolHeight/2; i++) { + 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++) { @@ -62,7 +59,7 @@ void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, } // Create real size sigma symbol by flipping the previous array - for (int i = k_symbolHeight/2; i < k_symbolHeight; i++) { + 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]; } From 02af86827411807f0b068a4dcb5362b5f70607cb Mon Sep 17 00:00:00 2001 From: Arthur Date: Fri, 15 May 2020 15:15:27 +0200 Subject: [PATCH 050/560] [poincare/sum_layout.cpp] Modified the look of the sigma symbol Change-Id: Ib79414eed96aedacc51b89dce22a14a4e7891cdf --- poincare/src/sum_layout.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 74cdee9d21a..269f0f26759 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -10,20 +10,20 @@ namespace Poincare { // 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] = { - 0xF1, 0x21, 0xB2, 0xFF, 0xFF, 0xFF, - 0xFF, 0xC5, 0x18, 0xE7, 0xFF, 0xFF, - 0xFF, 0x7F, 0x46, 0xFE, 0xFF, 0xFF, - 0xFF, 0xFC, 0x39, 0x8E, 0xFF, 0xFF, - 0xFF, 0xDF, 0x15, 0xD0, 0xFF, 0xFF, - 0xFF, 0xFF, 0xA4, 0x2A, 0xF6, 0xFF, - 0xFF, 0xFF, 0x5A, 0x69, 0xFF, 0xFF, - 0xFF, 0xFF, 0xF1, 0x21, 0xB2, 0xFF, - 0xFF, 0xFF, 0xC5, 0x18, 0xE7, 0xFF, - 0xFF, 0xFF, 0xFF, 0x7F, 0x46, 0xFE, - 0xFF, 0xFF, 0xFC, 0x3A, 0x8E, 0xFF, - 0xFF, 0xFF, 0xFF, 0xDF, 0x15, 0xD0, - 0xFF, 0xFF, 0xFF, 0xA0, 0x20, 0xF6, - 0xFF, 0xFF, 0XFF, 0xFF, 0xAB, 0x28, + 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 { @@ -35,10 +35,12 @@ void SumLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, uint8_t symbolPixel[k_symbolHeight * k_symbolWidth]; int whiteOffset; - // Taking care of the first line which is a black straight line + // 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 From 6380bf788597012c85beceae4b4dc4a1baeb8e79 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 20 May 2020 17:26:45 +0200 Subject: [PATCH 051/560] [poincare/integral_layout] Changing the integral layout and behavior Should there be multiple integrals in a row, they now all have the same height. Change-Id: I106c4e2cb2671f6518f1d916f414f6e9349f83dc --- poincare/include/poincare/integral_layout.h | 28 ++- poincare/src/integral_layout.cpp | 250 +++++++++++++------- 2 files changed, 184 insertions(+), 94 deletions(-) diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 0099e8d3eb0..ca7c56d19a1 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -9,6 +9,7 @@ namespace Poincare { class IntegralLayoutNode final : public LayoutNode { public: + // Sizes of the upper and lower curls of the integral symbol constexpr static KDCoordinate k_symbolHeight = 9; constexpr static KDCoordinate k_symbolWidth = 4; @@ -48,11 +49,11 @@ class IntegralLayoutNode final : public LayoutNode { 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 = 5; - constexpr static KDCoordinate k_boundWidthMargin = 5; - constexpr static KDCoordinate k_differentialWidthMargin = 3; - constexpr static KDCoordinate k_integrandWidthMargin = 2; - constexpr static KDCoordinate k_integrandHeigthMargin = 4; + 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) @@ -60,6 +61,23 @@ class IntegralLayoutNode final : public LayoutNode { 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/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index aeaf99d45be..253d429caec 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -218,84 +218,143 @@ 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 can be an integral + return static_cast(p); + } 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 + 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_boundHeightMargin | -| | | -| +------------------+ | -| | upperBoundHeight | | -| +------------------+ | -| | | -| k_integrandHeightMargin | -| | | -| | -| +++ | -| +++ k_symbolHeight | -| +++ | +| | | +| k_boundVerticalMargin | +| | | +| +------------------+ | +| | upperBoundHeight | | +| +------------------+ | +| +++ | | | +| +++ k_symbolHeight k_integrandVerticalMargin | +| +++ | | | | | | ||| | | ||| | | ||| | | ||| | -| ||| centralArgumentHeight | +| ||| centralArgumentHeight | +| ||| | | ||| | | ||| | | ||| | | ||| | | | -| +++ | -| +++ k_symbolHeight | -| +++ | -| | | -| k_integrandHeightMargin | -| | | -| +------------------+ | -| | lowerBoundHeight | | -| +------------------+ | -| | | -| k_boundHeightMargin | -| | | +| +++ | | | +| +++ k_symbolHeight k_integrandVerticalMargin | +| +++ | | | +| +------------------+ | +| | lowerBoundHeight | | +| +------------------+ | +| | | +| k_boundVerticalMargin | +| | | +-----------------------------------------------------------------+ Horizontal margins and offsets -+-------------------------------------------------------------------------+ -| | | -| | -| | +-----------------+ | -| |<-upperBoundLengthOffset->| upperBoundWidth | | -| | +-----------------+ | -| | -| | | | -| +++ | -| | | +++ | -| +++ | -| | | | -| ||| | -| | |||| | -| ||| | -| | |||| | -|<-k_boundWidthMargin->|<-integralSymbolOffset->|||| dx | -| | |||| ^ | -| ||| | -| | |||| | | -| ||| | -| | | a | | | -| +++ | -| | | a++| | | -| +++ | -| | | a | | | -| | -| | +-----------------+ | -| |<-lowerBoundLengthOffset->| lowerBoundWidth | | -| | +-----------------+ | -| | -| | | -+-------------------------------------------------------------------------+ -With a = k_symbolWidth + k_lineThickness -integralSymbolOffset = std::max((int)upperBoundSize.width()/2, (int)lowerBoundSize.width()/2) - k_integrandWidthMargin; ++-------------------------------------------------------------------------------------------------------+ +| | | +| | | |+---------------+| | | +| |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() { @@ -304,13 +363,25 @@ KDSize IntegralLayoutNode::computeSize() { KDSize differentialSize = differentialLayout()->layoutSize(); KDSize lowerBoundSize = lowerBoundLayout()->layoutSize(); KDSize upperBoundSize = upperBoundLayout()->layoutSize(); - KDCoordinate width = k_symbolWidth+std::max(lowerBoundSize.width(), upperBoundSize.width())/2+integrandSize.width()+2*k_differentialWidthMargin+dSize.width()+differentialSize.width(); - KDCoordinate height = upperBoundSize.height() + k_integrandHeigthMargin + k_symbolHeight + centralArgumentHeight() + k_symbolHeight + k_integrandHeigthMargin + 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 + k_symbolHeight + 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) { @@ -318,44 +389,47 @@ KDPoint IntegralLayoutNode::positionOfChild(LayoutNode * child) { KDSize upperBoundSize = upperBoundLayout()->layoutSize(); KDCoordinate x = 0; KDCoordinate y = 0; - // Offsets the integral bounds in order to center them - int difference = (upperBoundSize.width() - lowerBoundSize.width())/2; + KDCoordinate boundOffset = 2*k_symbolWidth - k_lineThickness + k_boundHorizontalMargin; if (child == lowerBoundLayout()) { - x = std::max(0, difference); - y = computeSize().height() - lowerBoundSize.height(); + x = boundOffset; + y = computeSize().height() - k_boundVerticalMargin - boundMaxHeight(BoundPosition::LowerBound); } else if (child == upperBoundLayout()) { - x = std::max(0, -difference); - y = 0; + x = boundOffset; + y = k_boundVerticalMargin + boundMaxHeight(BoundPosition::UpperBound) - upperBoundSize.height(); } else if (child == integrandLayout()) { - x = k_symbolWidth + std::max(lowerBoundSize.width(), upperBoundSize.width())/2; + x = boundOffset + std::max(lowerBoundSize.width(), upperBoundSize.width()) + k_integrandHorizontalMargin; y = computeBaseline()-integrandLayout()->baseline(); } else { assert(child == differentialLayout()); - x = computeSize().width() - k_differentialWidthMargin - differentialLayout()->layoutSize().width(); + x = computeSize().width() - differentialLayout()->layoutSize().width(); y = computeBaseline()-differentialLayout()->baseline(); } return KDPoint(x,y); } KDCoordinate IntegralLayoutNode::centralArgumentHeight() { - 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); + // 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 upperBoundSize = upperBoundLayout()->layoutSize(); - KDSize lowerBoundSize = lowerBoundLayout()->layoutSize(); - + KDCoordinate centralArgHeight = centralArgumentHeight(); KDColor workingBuffer[k_symbolWidth*k_symbolHeight]; + // Render the integral symbol - KDCoordinate integralSymbolOffset = std::max((int)upperBoundSize.width(), (int)lowerBoundSize.width())/2 - k_integrandWidthMargin; - KDCoordinate offsetX = p.x() + integralSymbolOffset; - KDCoordinate offsetY = p.y() + upperBoundSize.height() + k_integrandHeigthMargin; + 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); @@ -363,18 +437,16 @@ void IntegralLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionCo // Central bar offsetY = offsetY + k_symbolHeight; - KDCoordinate k_centralArgumentHeight = centralArgumentHeight(); - ctx->fillRect(KDRect(offsetX, offsetY, k_lineThickness, k_centralArgumentHeight), expressionColor); + ctx->fillRect(KDRect(offsetX, offsetY, k_lineThickness, centralArgHeight), expressionColor); // Lower part offsetX = offsetX - k_symbolWidth + k_lineThickness; - offsetY = offsetY + k_centralArgumentHeight; - KDRect bottomSymbolFrame(offsetX, offsetY, k_symbolWidth, k_symbolHeight); + offsetY = offsetY + centralArgHeight; + KDRect bottomSymbolFrame(offsetX,offsetY,k_symbolWidth, k_symbolHeight); ctx->blendRectWithMask(bottomSymbolFrame, expressionColor, (const uint8_t *)bottomSymbolPixel, (KDColor *)workingBuffer); - // 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); } From 776edd4dd83d60c79e43f09cc3f3570c4d3cf159 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 17 Jun 2020 16:24:28 +0200 Subject: [PATCH 052/560] [apps/shared] Extend function table view to 101 cells Increasing the lenght of graph table view to give users more possibilities Change-Id: I4023a5f81974b8302bf81b56d2610908e207241f --- apps/shared/interval.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/shared/interval.h b/apps/shared/interval.h index 95c269d689b..13e3e2cf4c6 100644 --- a/apps/shared/interval.h +++ b/apps/shared/interval.h @@ -30,8 +30,7 @@ class Interval { 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; From 082f9819e96cdba6df342df791eb1a0c23e51922 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 22 Jun 2020 11:51:29 +0200 Subject: [PATCH 053/560] [Poincare] Changed ConvertFloatToText to prevent conversion error We now compute the value of the mantissa in double (instead of float) to limit conversion error occuring for big numbers. Change-Id: Ia61c052f0bc4a9196c53e2c7900eb48e3343d39c --- poincare/src/print_float.cpp | 20 ++++++++++++++++---- poincare/test/print_float.cpp | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index 0aa8e66fc4b..d782302de25 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -207,10 +207,22 @@ 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 @@ -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/test/print_float.cpp b/poincare/test/print_float.cpp index 56c0aca6fb2..f9ac40c0b25 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); From 018dd91ca137004c320ffbbb879b07762eb0a5e3 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 22 Jun 2020 15:51:26 +0200 Subject: [PATCH 054/560] [Probability] Changed distribution parameter's type to double Plots are still rendered in float but computations are now in double Change-Id: I7e0a38effb780861b1443ee92a097cd319de3bc8 --- .../distribution/binomial_distribution.cpp | 8 ++++---- .../distribution/chi_squared_distribution.cpp | 12 ++++++------ apps/probability/distribution/distribution.h | 2 +- .../distribution/exponential_distribution.cpp | 7 ++++--- .../probability/distribution/fisher_distribution.cpp | 4 +--- .../distribution/geometric_distribution.h | 6 +++--- .../probability/distribution/normal_distribution.cpp | 6 +++--- .../distribution/one_parameter_distribution.h | 4 ++-- apps/probability/distribution/poisson_distribution.h | 4 ++-- .../distribution/student_distribution.cpp | 2 +- .../distribution/two_parameter_distribution.cpp | 2 +- .../distribution/two_parameter_distribution.h | 6 +++--- .../distribution/uniform_distribution.cpp | 12 +++++++----- 13 files changed, 38 insertions(+), 37 deletions(-) diff --git a/apps/probability/distribution/binomial_distribution.cpp b/apps/probability/distribution/binomial_distribution.cpp index 5ee9326e0a4..33c06da3eb7 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 9338c8c4b27..5fdc3c3682e 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 e7e9d5332da..4a862422c72 100644 --- a/apps/probability/distribution/distribution.h +++ b/apps/probability/distribution/distribution.h @@ -26,7 +26,7 @@ class Distribution : public Shared::CurveViewRange { 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 311c2e19741..acad075feb8 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 66201ac0c98..bd365e3ba0d 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 e3323abfa27..392b4c5885d 100644 --- a/apps/probability/distribution/geometric_distribution.h +++ b/apps/probability/distribution/geometric_distribution.h @@ -27,13 +27,13 @@ class GeometricDistribution final : public OneParameterDistribution { 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 932d97254ed..d86b0445956 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 df991c7198c..905cea080c3 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 @@ class OneParameterDistribution : public Distribution { 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 7c6654fa89f..bfbaeac3419 100644 --- a/apps/probability/distribution/poisson_distribution.h +++ b/apps/probability/distribution/poisson_distribution.h @@ -23,12 +23,12 @@ class PoissonDistribution final : public OneParameterDistribution { 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 a11640113f5..bdeb1acf84a 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 5c3197c1bcf..0c03e9742f1 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 0f8d68f44b0..332a9f92a1c 100644 --- a/apps/probability/distribution/two_parameter_distribution.h +++ b/apps/probability/distribution/two_parameter_distribution.h @@ -12,11 +12,11 @@ class TwoParameterDistribution : public Distribution { 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 0a79ae16015..94df87f3408 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; } } From 09e39ad890044fc2b0671c06d515750b4582e164 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 17 Jun 2020 16:21:04 +0200 Subject: [PATCH 055/560] [Stat+Reg] Allowing lists of values to be sorted Change-Id: I181bb55443bf87356d127eb6c56ff6140806fdea --- .../regression/store_parameter_controller.cpp | 1 + apps/regression/store_parameter_controller.h | 1 + apps/shared/double_pair_store.h | 1 + apps/shared/store_parameter_controller.cpp | 102 +++++++++++------- apps/shared/store_parameter_controller.h | 14 +-- apps/statistics/base.de.i18n | 2 + apps/statistics/base.en.i18n | 2 + apps/statistics/base.es.i18n | 2 + apps/statistics/base.fr.i18n | 2 + apps/statistics/base.it.i18n | 2 + apps/statistics/base.nl.i18n | 2 + apps/statistics/base.pt.i18n | 2 + poincare/include/poincare/helpers.h | 4 + poincare/src/helpers.cpp | 10 ++ 14 files changed, 100 insertions(+), 47 deletions(-) diff --git a/apps/regression/store_parameter_controller.cpp b/apps/regression/store_parameter_controller.cpp index e06c2b85900..1655e035bcb 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 08c532405c2..24b34158d6e 100644 --- a/apps/regression/store_parameter_controller.h +++ b/apps/regression/store_parameter_controller.h @@ -23,6 +23,7 @@ class StoreParameterController : public Shared::StoreParameterController { 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/shared/double_pair_store.h b/apps/shared/double_pair_store.h index f83e888129a..7ef5edb1811 100644 --- a/apps/shared/double_pair_store.h +++ b/apps/shared/double_pair_store.h @@ -63,6 +63,7 @@ class DoublePairStore { 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/store_parameter_controller.cpp b/apps/shared/store_parameter_controller.cpp index 752cdad264d..0bd90d14ff8 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,87 @@ 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; + } + case 1: + { + m_storeController->displayFormulaInput(); + } + 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; + }; + if (m_xColumnSelected) { + Poincare::Helpers::Sort(swapRows, compareX, (m_store->data() + m_series), m_store->numberOfPairsOfSeries(m_series)); + } else { + Poincare::Helpers::Sort(swapRows, compareY, (m_store->data() + m_series), 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; } } - return false; + assert(selectedRow() >= 0 && selectedRow() <= 2); + StackViewController * stack = static_cast(parentResponder()); + stack->pop(); + return true; } KDCoordinate StoreParameterController::cumulatedHeightFromIndex(int j) { @@ -88,8 +115,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 8480c7370f2..3198b5239ba 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -15,8 +15,10 @@ class StoreParameterController : public ViewController, public ListViewDataSourc void selectXColumn(bool xColumnSelected) { m_xColumnSelected = xColumnSelected; } void selectSeries(int series) { 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 +36,9 @@ class StoreParameterController : public ViewController, public ListViewDataSourc 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/statistics/base.de.i18n b/apps/statistics/base.de.i18n index a28b5e8f5ca..49d08c9c126 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -23,3 +23,5 @@ Range = "Spannweite" StandardDeviationSigma = "Standardabweichung σ" SampleStandardDeviationS = "Standardabweichung s" InterquartileRange = "Interquartilsabstand" +SortValues = "Nach steigenden Werten sortieren" +SortSizes = "Nach steigenden Frequenzen sortieren" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 61091e83567..492529d2c9e 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -23,3 +23,5 @@ Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" InterquartileRange = "Interquartile range" +SortValues = "Sort by increasing values" +SortSizes = "Sort by increasing sizes" \ No newline at end of file diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 80ff1fcc40e..2be9cb1b737 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -23,3 +23,5 @@ Range = "Rango" StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" InterquartileRange = "Rango intercuartilo" +SortValues = "Ordenar por valores crecientes" +SortSizes = "Ordenar por frecuencias crecientes" \ No newline at end of file diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index f90b4d60ba7..f782afbeec9 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -23,3 +23,5 @@ Range = "Étendue" StandardDeviationSigma = "Écart type" SampleStandardDeviationS = "Écart type échantillon" InterquartileRange = "Écart interquartile" +SortValues = "Trier par valeurs croissantes" +SortSizes = "Trier par effectifs croissants" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 99aacf03d06..cf286c1f24a 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -23,3 +23,5 @@ Range = "Ampiezza" StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" InterquartileRange = "Scarto interquartile" +SortValues = "Ordinare per valori crescenti" +SortSizes = "Ordinare per frequenze crescenti" \ No newline at end of file diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 3649387c149..9c023bd4acc 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -23,3 +23,5 @@ Range = "Bereik" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" InterquartileRange = "Interkwartielafstand" +SortValues = "Sorteer door waarden te verhogen" +SortSizes = "Sorteer door verhogen frequenties" \ No newline at end of file diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 3fd12f1e756..3157448790a 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -23,3 +23,5 @@ Range = "Amplitude" StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" InterquartileRange = "Amplitude interquartil" +SortValues = "Ordenar por ordem crescente" +SortSizes = "Ordenar por ordem crescente" \ No newline at end of file diff --git a/poincare/include/poincare/helpers.h b/poincare/include/poincare/helpers.h index 517a64d2a2d..dd44bd97fb9 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/src/helpers.cpp b/poincare/src/helpers.cpp index 57781724825..b8aae01e3df 100644 --- a/poincare/src/helpers.cpp +++ b/poincare/src/helpers.cpp @@ -98,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); + } + } + } +} + } } From 51f1cdb076c16c49643d7965bd637e46c1247fef Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 16 Jun 2020 16:59:16 +0200 Subject: [PATCH 056/560] [poincare] Handle rational unit exponents Change-Id: Id710702dbed19d34992da90978d5823d68abb80a --- liba/include/stdint.h | 3 ++ poincare/include/poincare/unit.h | 4 +-- poincare/src/multiplication.cpp | 4 +++ poincare/src/power.cpp | 8 ++--- poincare/src/square_root.cpp | 1 - poincare/src/unit.cpp | 54 ++++++++++++++++++-------------- poincare/test/simplification.cpp | 4 +-- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/liba/include/stdint.h b/liba/include/stdint.h index 1096e5ab16f..73a2be5e8b5 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/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 1d8a172dd1d..9ab3c0929ec 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -72,7 +72,7 @@ class UnitNode final : public ExpressionNode { bool canParse(const char * symbol, size_t length, const Prefix * * prefix) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; - const Prefix * bestPrefixForValue(double & value, const int exponent) const; + const Prefix * bestPrefixForValue(double & value, const double exponent) const; private: const char * m_rootSymbol; const char * m_definition; @@ -793,7 +793,7 @@ class Unit final : public Expression { 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); + void chooseBestMultipleForValue(double * value, const double exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); Expression removeUnit(Expression * unit); }; diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 5a44c1a7138..5df6d9d98c5 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -391,6 +391,10 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * - Repeat those steps until no more simplification is possible. */ Multiplication unitsAccu = Multiplication::Builder(); + /* If exponents are not integers, FromBaseUnits will return the closest + * representation of units with base units and integer exponents. + * It cause no problem because once the best derived units are found, + * units is divided then multiplied by them. */ Unit::Dimension::Vector unitsExponents = Unit::Dimension::Vector::FromBaseUnits(units); Unit::Dimension::Vector::Metrics unitsMetrics = unitsExponents.metrics(); Unit::Dimension::Vector bestRemainderExponents; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 4b8f2f61773..3ecb7587ea7 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -418,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(); } } @@ -995,8 +995,8 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont 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))) { diff --git a/poincare/src/square_root.cpp b/poincare/src/square_root.cpp index 0c8361d1eb0..cdefee1a520 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/unit.cpp b/poincare/src/unit.cpp index bc6cceb180d..c399e507d1d 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -16,8 +17,6 @@ namespace Poincare { -static inline int absInt(int x) { return x >= 0 ? x : -x; } - int UnitNode::Prefix::serialize(char * buffer, int bufferSize) const { assert(bufferSize >= 0); return std::min(strlcpy(buffer, m_symbol, bufferSize), bufferSize - 1); @@ -57,20 +56,20 @@ int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Pre return length; } -const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const int exponent) const { +const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const double exponent) const { if (!isPrefixable()) { return &Unit::EmptyPrefix; } const Prefix * bestPre = nullptr; - unsigned int diff = -1; + double diff = -1.0; /* Find the 'Prefix' with the most adequate 'exponent' for the order of * magnitude of 'value'. */ - const int orderOfMagnitude = IEEE754::exponentBase10(std::fabs(value)); + const double 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) { + double newDiff = std::abs(orderOfMagnitude - pre->exponent() * exponent); + if (newDiff < diff || diff < 0.0) { diff = newDiff; bestPre = pre; } @@ -112,6 +111,8 @@ Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::me template<> Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { + /* Returns the vector of Base units with integer exponents. If rational, the + * closest integer will be used. */ Vector vector; int numberOfFactors; int factorIndex = 0; @@ -128,8 +129,20 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseU Integer 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. + double exponent_double = static_cast(exp).node()->templatedApproximate(); + if (std::fabs(exponent_double) < INT_MAX / 2) { + // Exponent can be safely casted as int + exponent = (int)std::round(exponent_double); + assert(std::fabs(exponent_double - exponent.approximate()) <= 0.5); + } else { + /* Base units vector will ignore this coefficient, that could have been + * casted as int8_t in CanSimplifyUnitProduct, leading to homogeneous, + * but badly formatted units. Any way, the missing exponent won't affect + * CanSimplifyUnitProduct as homogeneity is conserved. */ + exponent = 0; + } factor = factor.childAtIndex(0); } // Fill the vector with the unit's exponent @@ -184,7 +197,7 @@ int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascendi if (dimdiff != 0) { return dimdiff; } - // This works because reprensentatives are ordered in a table + // This works because representatives 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 @@ -341,31 +354,25 @@ Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionConte 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; + double exponent = 1.0; 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(exp.type() == ExpressionNode::Type::Rational); + exponent = static_cast(exp).node()->templatedApproximate(); } assert(firstFactor.type() == ExpressionNode::Type::Unit); // Choose its multiple and update value accordingly - if (exponent != 0) { + if (exponent != 0.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); +void Unit::chooseBestMultipleForValue(double * value, const double exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { + assert(!std::isnan(*value) && exponent != 0.0); if (*value == 0 || *value == 1.0 || std::isinf(*value)) { return; } @@ -442,7 +449,8 @@ bool Unit::IsSI(Expression & e) { return true; } if (e.type() == ExpressionNode::Type::Power) { - assert(e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isInteger()); + // Rational exponents are accepted in IS system + assert(e.childAtIndex(1).type() == ExpressionNode::Type::Rational); Expression child = e.childAtIndex(0); assert(child.type() == ExpressionNode::Type::Unit); return IsSI(child); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index efba56df38a..a85529aee69 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -319,7 +319,8 @@ QUIZ_CASE(poincare_simplification_units) { 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"); @@ -416,7 +417,6 @@ 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"); From 11e41bb4dc308e538e9bfa679da38c154f8e56aa Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 16 Jun 2020 17:03:38 +0200 Subject: [PATCH 057/560] [poincare] Add comments and alternative algorithm to beautify units Change-Id: I4b1ce9528d9d6796fe08f8566ee5d1efafa1d87d --- poincare/include/poincare/unit.h | 2 + poincare/src/multiplication.cpp | 88 ++++++++++++++++++++++++++++++-- poincare/test/simplification.cpp | 31 +++++++++-- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 9ab3c0929ec..d82bbc8f831 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -85,6 +85,8 @@ class UnitNode final : public ExpressionNode { public: template struct Vector { + /* SupportSize is defined as the number of distinct base units. + * Norm is defined as the sum of all unit exponents absolute values. */ struct Metrics { size_t supportSize; T norm; diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 5df6d9d98c5..bcd32d7d45d 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -331,21 +331,81 @@ static bool CanSimplifyUnitProduct( * 'bestRemainder' are updated accordingly. */ Unit::Dimension::Vector simplifiedExponents; Integer (*operationOnExponents)(const Integer &, const Integer &) = entryUnitExponent == -1 ? Integer::Addition : Integer::Subtraction; + + #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; + Integer best_norm; + Integer norm_temp = unitsMetrics.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++) { + // Setting simplifiedExponents to unitsExponents + simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i)); + } + 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, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), step * entryUnitExponents->coefficientAtIndex(i))); + } + Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); + norm_temp = Integer::Addition(n, simplifiedMetrics.norm); + + } while (norm_temp.isLowerThan(best_norm)); + // Undo last step as it did not reduce the norm + n -= step; + + Integer derivedMetricNorm = n * step * entryUnitNorm; + #else + Integer derivedMetricNorm = entryUnitNorm; + #endif + for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { + // Simplify unitsExponents with base units from derived unit + #if 0 + simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), -step * entryUnitExponents->coefficientAtIndex(i))); + #else simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(unitsExponents.coefficientAtIndex(i), entryUnitExponents->coefficientAtIndex(i))); + #endif } Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); + // Compute metrics to evaluate the simplification + + bool isSimpler = (1 + simplifiedMetrics.supportSize < unitsMetrics.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. */ + // TODO : Remove Metrics vectors entirely Unit::Dimension::Vector::Metrics candidateMetrics = { .supportSize = 1 + simplifiedMetrics.supportSize, - .norm = Integer::Addition(entryUnitNorm, simplifiedMetrics.norm) + .norm = Integer::Addition(derivedMetricNorm, simplifiedMetrics.norm) }; - bool isSimpler = candidateMetrics.supportSize < unitsMetrics.supportSize || - (candidateMetrics.supportSize == unitsMetrics.supportSize && - candidateMetrics.norm.isLowerThan(unitsMetrics.norm)); + if (isSimpler) { + #if 0 + bestUnitExponent = entryUnitExponent * n * step; + #else bestUnitExponent = entryUnitExponent; + #endif bestRemainderExponents = simplifiedExponents; bestRemainderMetrics = simplifiedMetrics; + /* unitsMetrics (support size and norm) is updated and will be taken into + * account in next iterations of CanSimplifyUnitProduct. */ unitsMetrics = candidateMetrics; } return isSimpler; @@ -402,9 +462,11 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu while (unitsMetrics.supportSize > 1) { const Unit::Dimension * bestDim = nullptr; int8_t bestUnitExponent = 0; + // Look up in the table of derived units. 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; + // A simplification is tried by either multiplying or dividing if (CanSimplifyUnitProduct( unitsExponents, unitsMetrics, entryUnitExponents, entryUnitNorm, 1, @@ -417,30 +479,48 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu bestUnitExponent, bestRemainderExponents, bestRemainderMetrics )) { + /* If successful, unitsMetrics, bestUnitExponent, + * bestRemainderExponents and bestRemainderMetrics have been updated*/ bestDim = dim; } } if (bestDim == nullptr) { + // No simplification could be performed break; } + // Build and add the best derived unit Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix()); + + #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; } + // Apply simplifications if (unitsAccu.numberOfChildren() > 0) { + // Divide by derived units units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext); Expression newUnits; + // Separate units and generated values units = units.removeUnit(&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 { diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index a85529aee69..863dab15620 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -215,6 +215,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,10 +251,6 @@ 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"); From a5a57c40765e90b7f66e2e71459813ba3861e088 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 16 Jun 2020 17:10:00 +0200 Subject: [PATCH 058/560] [poincare] Improve prefixes for mass and inductance unities Change-Id: Ic9eb7b5adff7b172452b4c73bd7ddc5c59761219 --- poincare/include/poincare/unit.h | 15 ++++++---- poincare/src/unit.cpp | 48 ++++++++++++++++++++++---------- poincare/test/simplification.cpp | 13 +++++++-- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index d82bbc8f831..feb6f6d9a35 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -72,7 +72,7 @@ class UnitNode final : public ExpressionNode { bool canParse(const char * symbol, size_t length, const Prefix * * prefix) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; - const Prefix * bestPrefixForValue(double & value, const double exponent) const; + const Prefix * bestPrefixForValue(double & value, const float exponent) const; private: const char * m_rootSymbol; const char * m_definition; @@ -304,9 +304,12 @@ class Unit final : public Expression { NoPrefix), }, MassRepresentatives[] = { - Representative("g", nullptr, + Representative("kg", nullptr, + Representative::Prefixable::No, + NoPrefix), + Representative("g", "0.001_kg", Representative::Prefixable::Yes, - LongScalePrefixes), + NegativeLongScalePrefixes), Representative("t", "1000_kg", Representative::Prefixable::Yes, NoPrefix), @@ -406,7 +409,7 @@ class Unit final : public Expression { InductanceRepresentatives[] = { Representative("H", "_kg*_m^2*_s^-2*_A^-2", Representative::Prefixable::Yes, - NoPrefix), + LongScalePrefixes), }, CatalyticActivityRepresentatives[] = { Representative("kat", "_mol*_s^-1", @@ -478,7 +481,7 @@ class Unit final : public Expression { .luminuousIntensity = 0, }, MassRepresentatives, - &KiloPrefix + &EmptyPrefix ), Dimension( Dimension::Vector { @@ -795,7 +798,7 @@ class Unit final : public Expression { 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 double exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); + void chooseBestMultipleForValue(double * value, const float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); Expression removeUnit(Expression * unit); }; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index c399e507d1d..f9d01741d4a 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -56,21 +56,39 @@ int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Pre return length; } -const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const double exponent) const { +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::abs(order) < Expression::Epsilon()) { + order = 0.0; + } + if (std::abs(otherOrder) < Expression::Epsilon()) { + otherOrder = 0.0; + } + if (std::abs(std::abs(order) - std::abs(otherOrder)) < 3.0 && order * otherOrder < 0.0) { + /* If the two values are close, and their sign are opposed, the positive + * order is preferred */ + return (order >= 0.0); + } + // Otherwise, the closest order to 0 is preferred + return (std::abs(order) < std::abs(otherOrder)); +} + +const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const float exponent) const { if (!isPrefixable()) { return &Unit::EmptyPrefix; } + float bestOrder; const Prefix * bestPre = nullptr; - double diff = -1.0; /* Find the 'Prefix' with the most adequate 'exponent' for the order of * magnitude of 'value'. */ - const double orderOfMagnitude = IEEE754::exponentBase10(std::fabs(value)); + const float orderOfMagnitude = std::log10(std::fabs(value)); for (size_t i = 0; i < m_outputPrefixesLength; i++) { const Prefix * pre = m_outputPrefixes[i]; - double newDiff = std::abs(orderOfMagnitude - pre->exponent() * exponent); - if (newDiff < diff || diff < 0.0) { - diff = newDiff; + float order = orderOfMagnitude - pre->exponent() * exponent; + if (bestPre == nullptr || compareMagnitudeOrders(order, bestOrder)) { + bestOrder = order; bestPre = pre; } } @@ -131,11 +149,11 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseU Expression exp = factor.childAtIndex(1); assert(exp.type() == ExpressionNode::Type::Rational); // Using the closest integer to the exponent. - double exponent_double = static_cast(exp).node()->templatedApproximate(); - if (std::fabs(exponent_double) < INT_MAX / 2) { + float exponent_float = static_cast(exp).node()->templatedApproximate(); + if (std::abs(exponent_float) < INT_MAX / 2) { // Exponent can be safely casted as int - exponent = (int)std::round(exponent_double); - assert(std::fabs(exponent_double - exponent.approximate()) <= 0.5); + exponent = (int)std::round(exponent_float); + assert(std::abs(exponent_float - exponent.approximate()) <= 0.5); } else { /* Base units vector will ignore this coefficient, that could have been * casted as int8_t in CanSimplifyUnitProduct, leading to homogeneous, @@ -354,7 +372,7 @@ Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionConte void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { // Identify the first Unit factor and its exponent Expression firstFactor = *units; - double exponent = 1.0; + float exponent = 1.0; if (firstFactor.type() == ExpressionNode::Type::Multiplication) { firstFactor = firstFactor.childAtIndex(0); } @@ -362,7 +380,7 @@ void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool t Expression exp = firstFactor.childAtIndex(1); firstFactor = firstFactor.childAtIndex(0); assert(exp.type() == ExpressionNode::Type::Rational); - exponent = static_cast(exp).node()->templatedApproximate(); + exponent = static_cast(exp).node()->templatedApproximate(); } assert(firstFactor.type() == ExpressionNode::Type::Unit); // Choose its multiple and update value accordingly @@ -371,7 +389,7 @@ void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool t } } -void Unit::chooseBestMultipleForValue(double * value, const double exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { +void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { assert(!std::isnan(*value) && exponent != 0.0); if (*value == 0 || *value == 1.0 || std::isinf(*value)) { return; @@ -393,7 +411,7 @@ void Unit::chooseBestMultipleForValue(double * value, const double exponent, boo 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()) { + if (compareMagnitudeOrders(std::log10(std::fabs(val)), std::log10(std::fabs(bestVal)))) { /* At this point, val is closer to one than bestVal is.*/ bestRep = rep; bestPre = pre; @@ -425,7 +443,7 @@ bool Unit::isMeter() const { bool Unit::isKilogram() const { // See comment on isSecond - return node()->dimension() == MassDimension && node()->representative() == KilogramRepresentative && node()->prefix() == &KiloPrefix; + return node()->dimension() == MassDimension && node()->representative() == KilogramRepresentative && node()->prefix() == &EmptyPrefix; } bool Unit::isSI() const { diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 863dab15620..b0124a85aee 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -256,13 +256,21 @@ QUIZ_CASE(poincare_simplification_units) { 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("1_t", "1×_t"); + 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", "100×_mm"); + 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) { + if (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; @@ -280,11 +288,10 @@ QUIZ_CASE(poincare_simplification_units) { } /* Units that do not appear as output yet */ - assert_parsed_expression_simplify_to("_t", "1×_Mg"); 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("_ha", "10000×_m^2"); /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); From 9f2c4bccdf52c5ef4ca9d5e36dc2cd6e325adaa6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 18 Jun 2020 17:21:27 +0200 Subject: [PATCH 059/560] [apps/math_toolbox] Reorder toolbox units Change-Id: I2a599c90ce221c173bd05bfa2d31e908c6bdbbdf --- apps/math_toolbox.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 0a6e18c4b88..ad37d430986 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -232,6 +232,8 @@ const ToolboxMessageTree unitVolumeLiterChildren[] = { const ToolboxMessageTree unitChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitTimeMenu, unitTimeChildren), ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildren), + ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildren), + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeLiterChildren), ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildren), ToolboxMessageTree::Node(I18n::Message::UnitCurrentMenu, unitCurrentAmpereChildren), ToolboxMessageTree::Node(I18n::Message::UnitTemperatureMenu, unitTemperatureChildren), @@ -249,8 +251,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[] = { From 10de9708142fc5b45f3f1bff52831c6f1afc58c6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 30 Jun 2020 14:59:03 +0200 Subject: [PATCH 060/560] [poincare] Remove unnecessary unit norm code Change-Id: I7d7abf88d1dcc10fb4ae0f6b3570ebfe4b4af311 --- poincare/include/poincare/unit.h | 9 +--- poincare/src/multiplication.cpp | 86 ++++++++++++++------------------ poincare/src/unit.cpp | 53 ++++++++------------ 3 files changed, 61 insertions(+), 87 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index feb6f6d9a35..060327879cb 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -85,13 +85,8 @@ class UnitNode final : public ExpressionNode { public: template struct Vector { - /* SupportSize is defined as the number of distinct base units. - * Norm is defined as the sum of all unit exponents absolute values. */ - struct Metrics { - size_t supportSize; - T norm; - }; - Metrics metrics() const; + // 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 < NumberOfBaseUnits); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index bcd32d7d45d..3a3b5f92611 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -322,15 +322,14 @@ 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 Unit::Dimension::Vector &unitsExponents, size_t &unitsSupportSize, + const Unit::Dimension::Vector *entryUnitExponents, int8_t entryUnitExponent, + int8_t &bestUnitExponent, Unit::Dimension::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; + Unit::Dimension::Vector simplifiedExponents; #if 0 /* In the current algorithm, simplification is attempted using derived units @@ -344,14 +343,15 @@ static bool CanSimplifyUnitProduct( * An optimization might be possible using algorithms minimizing the sum of * absolute difference of array elements */ int n = 0; - Integer best_norm; - Integer norm_temp = unitsMetrics.norm; + 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++) { - // Setting simplifiedExponents to unitsExponents + // Set simplifiedExponents to unitsExponents simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i)); } do { @@ -359,42 +359,31 @@ static bool CanSimplifyUnitProduct( n+= step; for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { // Simplify unitsExponents with base units from derived unit - simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), step * entryUnitExponents->coefficientAtIndex(i))); + simplifiedExponents.setCoefficientAtIndex(i, simplifiedExponents.coefficientAtIndex(i) - entryUnitExponent * step * entryUnitExponents->coefficientAtIndex(i)); } - Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); - norm_temp = Integer::Addition(n, simplifiedMetrics.norm); - - } while (norm_temp.isLowerThan(best_norm)); + 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; - - Integer derivedMetricNorm = n * step * entryUnitNorm; - #else - Integer derivedMetricNorm = entryUnitNorm; #endif for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { - // Simplify unitsExponents with base units from derived unit #if 0 - simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(simplifiedExponents.coefficientAtIndex(i), -step * entryUnitExponents->coefficientAtIndex(i))); + // Undo last step as it did not reduce the norm + simplifiedExponents.setCoefficientAtIndex(i, simplifiedExponents.coefficientAtIndex(i) + entryUnitExponent * step * entryUnitExponents->coefficientAtIndex(i)); #else - simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(unitsExponents.coefficientAtIndex(i), entryUnitExponents->coefficientAtIndex(i))); + // Simplify unitsExponents with base units from derived unit + simplifiedExponents.setCoefficientAtIndex(i, unitsExponents.coefficientAtIndex(i) - entryUnitExponent * entryUnitExponents->coefficientAtIndex(i)); #endif } - Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); - // Compute metrics to evaluate the simplification - - bool isSimpler = (1 + simplifiedMetrics.supportSize < unitsMetrics.supportSize); - + 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. */ - // TODO : Remove Metrics vectors entirely - Unit::Dimension::Vector::Metrics candidateMetrics = { - .supportSize = 1 + simplifiedMetrics.supportSize, - .norm = Integer::Addition(derivedMetricNorm, simplifiedMetrics.norm) - }; + bool isSimpler = (1 + simplifiedSupportSize < unitsSupportSize); if (isSimpler) { #if 0 @@ -403,10 +392,10 @@ static bool CanSimplifyUnitProduct( bestUnitExponent = entryUnitExponent; #endif bestRemainderExponents = simplifiedExponents; - bestRemainderMetrics = simplifiedMetrics; - /* unitsMetrics (support size and norm) is updated and will be taken into + bestRemainderSupportSize = simplifiedSupportSize; + /* unitsSupportSize is updated and will be taken into * account in next iterations of CanSimplifyUnitProduct. */ - unitsMetrics = candidateMetrics; + unitsSupportSize = 1 + simplifiedSupportSize; } return isSimpler; } @@ -455,32 +444,31 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * representation of units with base units and integer exponents. * It cause no problem because once the best derived units are found, * units is divided then multiplied by them. */ - 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) { + Unit::Dimension::Vector unitsExponents = Unit::Dimension::Vector::FromBaseUnits(units); + size_t unitsSupportSize = unitsExponents.supportSize(); + Unit::Dimension::Vector bestRemainderExponents; + size_t bestRemainderSupportSize; + while (unitsSupportSize > 1) { const Unit::Dimension * bestDim = nullptr; int8_t bestUnitExponent = 0; // Look up in the table of derived units. 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; // 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, unitsMetrics, bestUnitExponent, - * bestRemainderExponents and bestRemainderMetrics have been updated*/ + /* If successful, unitsSupportSize, bestUnitExponent, + * bestRemainderExponents and bestRemainderSupportSize have been updated*/ bestDim = dim; } } @@ -506,7 +494,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu 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) { diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index f9d01741d4a..5400ea491ea 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -97,41 +97,31 @@ const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & v } template<> -Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::metrics() const { +size_t UnitNode::Dimension::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()) { - continue; - } - supportSize++; - coefficient.setNegative(false); - norm = Integer::Addition(norm, coefficient); - } - return {.supportSize = supportSize, .norm = norm}; -} - -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; + for (const int * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { + int coefficient = *i; if (coefficient == 0) { continue; } supportSize++; - norm += coefficient > 0 ? coefficient : -coefficient; } - return {.supportSize = supportSize, .norm = norm}; + return supportSize; } template<> -Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { +Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { /* Returns the vector of Base units with integer exponents. If rational, the * closest integer will be used. */ - Vector vector; + Vector vector = { + .time = 0, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }; int numberOfFactors; int factorIndex = 0; Expression factor; @@ -144,21 +134,22 @@ 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); // Using the closest integer to the exponent. float exponent_float = static_cast(exp).node()->templatedApproximate(); - if (std::abs(exponent_float) < INT_MAX / 2) { + /* 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::abs(exponent_float) < INT_MAX / 3) { // Exponent can be safely casted as int exponent = (int)std::round(exponent_float); - assert(std::abs(exponent_float - exponent.approximate()) <= 0.5); + assert(std::abs(exponent_float - (float)exponent) <= 0.5); } else { - /* Base units vector will ignore this coefficient, that could have been - * casted as int8_t in CanSimplifyUnitProduct, leading to homogeneous, - * but badly formatted units. Any way, the missing exponent won't affect - * CanSimplifyUnitProduct as homogeneity is conserved. */ + /* Base units vector will ignore this coefficient, to avoid exponent + * overflow. In any way, shallowBeautify will conserve homogeneity. */ exponent = 0; } factor = factor.childAtIndex(0); From 969fea7494a2cebbc6db831a1763bfb74e8f1ec7 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 3 Jul 2020 10:38:41 +0200 Subject: [PATCH 061/560] [poincare] Improve float variables usage Change-Id: I965c425cbe70c0a201c504565d5d0991618ce0b9 --- poincare/src/unit.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 5400ea491ea..2080eed3b8f 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -59,19 +59,19 @@ int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Pre 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::abs(order) < Expression::Epsilon()) { - order = 0.0; + if (std::fabs(order) < Expression::Epsilon()) { + order = 0.0f; } - if (std::abs(otherOrder) < Expression::Epsilon()) { - otherOrder = 0.0; + if (std::fabs(otherOrder) < Expression::Epsilon()) { + otherOrder = 0.0f; } - if (std::abs(std::abs(order) - std::abs(otherOrder)) < 3.0 && order * otherOrder < 0.0) { + if (std::fabs(std::fabs(order) - std::fabs(otherOrder)) < 3.0f && order * otherOrder < 0.0f) { /* If the two values are close, and their sign are opposed, the positive * order is preferred */ - return (order >= 0.0); + return (order >= 0.0f); } // Otherwise, the closest order to 0 is preferred - return (std::abs(order) < std::abs(otherOrder)); + return (std::fabs(order) < std::fabs(otherOrder)); } const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const float exponent) const { @@ -139,14 +139,14 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(con Expression exp = factor.childAtIndex(1); assert(exp.type() == ExpressionNode::Type::Rational); // Using the closest integer to the exponent. - float exponent_float = static_cast(exp).node()->templatedApproximate(); + float exponentFloat = static_cast(exp).node()->templatedApproximate(); /* 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::abs(exponent_float) < INT_MAX / 3) { + if (std::fabs(exponentFloat) < INT_MAX / 3) { // Exponent can be safely casted as int - exponent = (int)std::round(exponent_float); - assert(std::abs(exponent_float - (float)exponent) <= 0.5); + 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. */ @@ -363,7 +363,7 @@ Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionConte void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { // Identify the first Unit factor and its exponent Expression firstFactor = *units; - float exponent = 1.0; + float exponent = 1.0f; if (firstFactor.type() == ExpressionNode::Type::Multiplication) { firstFactor = firstFactor.childAtIndex(0); } @@ -375,14 +375,14 @@ void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool t } assert(firstFactor.type() == ExpressionNode::Type::Unit); // Choose its multiple and update value accordingly - if (exponent != 0.0) { + if (exponent != 0.0f) { static_cast(firstFactor).chooseBestMultipleForValue(value, exponent, tuneRepresentative, reductionContext); } } void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { - assert(!std::isnan(*value) && exponent != 0.0); - if (*value == 0 || *value == 1.0 || std::isinf(*value)) { + assert(!std::isnan(*value) && exponent != 0.0f); + if (*value == 0.0 || *value == 1.0 || std::isinf(*value)) { return; } UnitNode * unitNode = node(); From 5bc19af19661d2caf5628097bdf8e75eda04e855 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 24 Jun 2020 16:27:41 +0200 Subject: [PATCH 062/560] [apps/shared] Revert to pack record It appeared that without the packed keyword, the compiler did not handle access to unaligned record data members, which leads to crashes on the device. Change-Id: I401f075e7f62458a4733baa8d81983d4be34b730 --- apps/sequence/sequence.h | 6 +++--- apps/shared/continuous_function.h | 4 ++-- apps/shared/function.cpp | 11 ----------- apps/shared/function.h | 17 +++++++++++------ apps/shared/range_1D.h | 7 ++++--- apps/shared/test/function_alignement.cpp | 10 +++++----- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/apps/sequence/sequence.h b/apps/sequence/sequence.h index 41a376973bb..d7f10389077 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -75,10 +75,10 @@ friend class SequenceStore; /* 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, sizeof(RecordDataBuffer)), + Shared::Function::RecordDataBuffer(color), m_type(Type::Explicit), m_initialRank(0), m_initialConditionSizes{0,0} @@ -100,7 +100,7 @@ friend class SequenceStore; 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]; #else diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index dbeff485b92..612651231df 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -81,10 +81,10 @@ class ContinuousFunction : public Function { /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on * Shared::Function::RecordDataBuffer about packing. */ - class RecordDataBuffer : public Function::RecordDataBuffer { + class __attribute__((packed)) RecordDataBuffer : public Function::RecordDataBuffer { public: RecordDataBuffer(KDColor color) : - Function::RecordDataBuffer(color, sizeof(RecordDataBuffer)), + Function::RecordDataBuffer(color), m_plotType(PlotType::Cartesian), m_domain(-INFINITY, INFINITY), m_displayDerivative(false) diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 7311064caaf..31b1e01e05f 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -68,17 +68,6 @@ int Function::nameWithArgument(char * buffer, size_t bufferSize) { return result; } -Function::RecordDataBuffer::RecordDataBuffer(KDColor color, size_t size) { - /* Size is passed so that the entire derived RecordDataBuffer can be set to 0 - * before initializing parameters. This is done in order to ensure any padding - * bits are set to 0 and prevent storage's CRC32 from depending on junk data. */ - assert(size >= sizeof(*this)); - memset(this, 0, size); - // Members must be initialized after memset - m_color = color; - m_active = true; -} - Function::RecordDataBuffer * Function::recordData() const { assert(!isNull()); Ion::Storage::Record::Data d = value(); diff --git a/apps/shared/function.h b/apps/shared/function.h index f884e550572..7cfad9a11c9 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -58,10 +58,17 @@ class Function : public ExpressionModelHandle { * 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, size_t size); + RecordDataBuffer(KDColor color) : m_color(color), m_active(true) {} KDColor color() const { return KDColor::RGB16(m_color); } @@ -69,9 +76,7 @@ class Function : public ExpressionModelHandle { 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 diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index dd935339191..58ef2537fcc 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -11,9 +11,10 @@ namespace Shared { -// This class is used in a DataBuffer of a Storage::Record +/* This class is used in a DataBuffer of a Storage::Record. See comment in + * Shared::Function::RecordDataBuffer about packing. */ -class Range1D final { +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. */ @@ -31,7 +32,7 @@ class Range1D final { 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; emscripten_align1_float m_max; diff --git a/apps/shared/test/function_alignement.cpp b/apps/shared/test/function_alignement.cpp index deaf9393eb3..df9136b26e2 100644 --- a/apps/shared/test/function_alignement.cpp +++ b/apps/shared/test/function_alignement.cpp @@ -9,7 +9,7 @@ namespace Shared { template void interactWithBaseRecordMember(F * fct) { /* Accessing Function record member m_color, which has a 2-byte alignment - * Only efffective in DEBUG=1, as there are no compiler optimizations */ + * Only effective in DEBUG=1, as there are no compiler optimizations */ KDColor color = fct->color(); (void) color; // Silence compilation warning about unused variable. } @@ -68,11 +68,11 @@ void testAlignmentHandlingFor() { sharedStorage->destroyAllRecords(); } -QUIZ_CASE(alignment_handled_on_emscripten) { +QUIZ_CASE(alignment_handling) { /* This test main function is to crash if storage alignment is not handled - * properly on DEBUG and __EMSCRIPTEN__ modes only. It also ensures that the - * right test - load and store of differently-aligned objects - is performed - * (if storage/record implementations change for instance). */ + * 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(); } From 4007f4d4522599302a51c66b8401be6aba5cc97c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 13:38:24 +0200 Subject: [PATCH 063/560] [apps] Tweaked panning for better cache alignement Method InteractiveCurveViewRange::panToMakePointVisible now moves the range of a whole number of pixels when panning horizontally. This allows the cache of cartesian functions not to be invalidated. Change-Id: Idb9904fef134dd13458e1f2287b0fe5145e8aec7 --- apps/graph/graph/calculation_graph_controller.cpp | 2 +- apps/graph/graph/tangent_graph_controller.cpp | 4 ++-- apps/regression/graph_controller.cpp | 2 +- apps/sequence/graph/graph_controller.cpp | 2 +- apps/shared/function_graph_controller.cpp | 2 +- apps/shared/interactive_curve_view_controller.cpp | 5 +++-- apps/shared/interactive_curve_view_range.cpp | 9 ++++++--- apps/shared/interactive_curve_view_range.h | 2 +- apps/shared/simple_interactive_curve_view_controller.cpp | 3 ++- apps/shared/sum_graph_controller.cpp | 4 ++-- 10 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index b5849d74dc9..73c95a9e090 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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews); reloadBannerView(); } diff --git a/apps/graph/graph/tangent_graph_controller.cpp b/apps/graph/graph/tangent_graph_controller.cpp index afc41f94137..5edf674d85b 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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 519713002c0..dec921c6378 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -262,7 +262,7 @@ void GraphController::initCursorParameters() { 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_store->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); } *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 76a7d771aab..a0552361ee0 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -67,7 +67,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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); reloadBannerView(); m_view.reload(); return true; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 21b3c652b4b..04a08dec45a 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -146,7 +146,7 @@ void FunctionGraphController::initCursorParameters() { } m_cursor->moveTo(t, xy.x1(), xy.x2()); if (interactiveCurveViewRange()->yAuto()) { - interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); + interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); } selectFunctionWithCursor(functionIndex); } diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index c6b736296d7..1d2f23bae39 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -93,7 +93,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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, + curveView()->pixelWidth() ); reloadBannerView(); curveView()->reload(); @@ -220,7 +221,7 @@ bool InteractiveCurveViewController::textFieldDidFinishEditing(TextField * textF } 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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 9e733b874c5..4b09d3b97be 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -199,20 +199,23 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { } } -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; + /* 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); + 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); } } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index f6484017ae2..1c6d308a21e 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -37,7 +37,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { virtual void setTrigonometric(); 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; diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index c9220f3d92b..10d07998423 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -31,7 +31,8 @@ bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Eve if (moveCursorHorizontally(direction, Ion::Events::repetitionFactor())) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), - cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio + cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, + curveView()->pixelWidth() ); reloadBannerView(); curveView()->reload(); diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index ac5ce035e5c..5a328291446 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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_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(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); m_graphView->reload(); return true; } From 0308b184009c9dc0ed02ab51026d85d3821824d1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 13:41:22 +0200 Subject: [PATCH 064/560] [liba/float.h] Comment on FLT_EPSILON's definition Change-Id: Iac2da58d9a2f8e4b1bb131255341696db99df188 --- liba/include/float.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/liba/include/float.h b/liba/include/float.h index bc208489002..58ef7ecdf10 100644 --- a/liba/include/float.h +++ b/liba/include/float.h @@ -5,6 +5,9 @@ #define FLT_MAX 1E+37f #define FLT_MIN 1E-37f +/* TODO Redefine FLT_EPSILON to comply with the definition : + * FLT_EPSILON is the difference between 1 and the very next floating point + * number. */ #define FLT_EPSILON 1E-5f #define DBL_MAX 1.79769313486231571e+308 #define DBL_MIN 2.22507385850720138e-308 From 552dca9494f99a8b08e35ce8ed659c157584350d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 13:53:12 +0200 Subject: [PATCH 065/560] [apps/shared] Implemented function memoization ContinuousFunction now has an attribute of type ContinuousFunctionCache, implementing methods to store and retrieve 320 float values, in order to speed up function display in Graph. Change-Id: I6f7ccdf3ae3c6dd8b08b93d786c8d0be7aa4dee8 --- apps/shared/Makefile | 1 + apps/shared/continuous_function.h | 8 ++ apps/shared/continuous_function_cache.cpp | 146 ++++++++++++++++++++++ apps/shared/continuous_function_cache.h | 48 +++++++ 4 files changed, 203 insertions(+) create mode 100644 apps/shared/continuous_function_cache.cpp create mode 100644 apps/shared/continuous_function_cache.h diff --git a/apps/shared/Makefile b/apps/shared/Makefile index c10dece7c8e..08c51ef13cb 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -24,6 +24,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + continuous_function_cache.cpp \ cursor_view.cpp \ curve_view_cursor.cpp \ editable_cell_table_view_controller.cpp \ diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 612651231df..096d6ee61c7 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,6 +19,9 @@ 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); @@ -73,6 +77,9 @@ class ContinuousFunction : public Function { 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 const_cast(&m_cache); } 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); @@ -115,6 +122,7 @@ class ContinuousFunction : public Function { 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 00000000000..567452f3cae --- /dev/null +++ b/apps/shared/continuous_function_cache.cpp @@ -0,0 +1,146 @@ +#include "continuous_function_cache.h" +#include "continuous_function.h" + +namespace Shared { + +constexpr int ContinuousFunctionCache::k_sizeOfCache; +constexpr float ContinuousFunctionCache::k_cacheHitTolerance; + +// public +void ContinuousFunctionCache::PrepareCache(void * f, void * c, float tMin, float tStep) { + ContinuousFunction * function = (ContinuousFunction *)f; + Poincare::Context * context = (Poincare::Context *)c; + ContinuousFunctionCache * functionCache = function->cache(); + if (functionCache->filled() && tStep / StepFactor(function) == functionCache->step()) { + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + function->cache()->pan(function, context, tMin); + } + return; + } + functionCache->setRange(function, tMin, tStep); + functionCache->memoize(function, context); +} + +void ContinuousFunctionCache::clear() { + m_filled = false; + m_startOfCache = 0; +} + +Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, float t) const { + int iRes = indexForParameter(function, t); + /* If t does not map to an index, iRes is -1 */ + if (iRes < 0) { + return Poincare::Coordinate2D(NAN, NAN); + } + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + return Poincare::Coordinate2D(t, m_cache[iRes]); + } + assert(m_startOfCache == 0); + return Poincare::Coordinate2D(m_cache[2*iRes], m_cache[2*iRes+1]); +} + +// private +float ContinuousFunctionCache::StepFactor(ContinuousFunction * function) { + /* When drawing a parametric or polar curve, the range is first divided by + * ~10,9, creating 11 intervals which are filled by dichotomy. + * We memoize 16 values for each of the 10 big intervals. */ + return (function->plotType() == ContinuousFunction::PlotType::Cartesian) ? 1.f : 16.f; +} + +void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { + m_tMin = tMin; + m_tStep = tStep / StepFactor(function); +} + +void ContinuousFunctionCache::memoize(ContinuousFunction * function, Poincare::Context * context) { + m_filled = true; + m_startOfCache = 0; + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + memoizeYForX(function, context); + return; + } + memoizeXYForT(function, context); +} + +void ContinuousFunctionCache::memoizeYForX(ContinuousFunction * function, Poincare::Context * context) { + memoizeYForXBetweenIndices(function, context, 0, k_sizeOfCache); +} + +void ContinuousFunctionCache::memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup) { + assert(function->plotType() == ContinuousFunction::PlotType::Cartesian); + for (int i = iInf; i < iSup; i++) { + m_cache[i] = function->privateEvaluateXYAtParameter(parameterForIndex(i), context).x2(); + } +} + +void ContinuousFunctionCache::memoizeXYForT(ContinuousFunction * function, Poincare::Context * context) { + assert(function->plotType() != ContinuousFunction::PlotType::Cartesian); + for (int i = 1; i < k_sizeOfCache; i += 2) { + Poincare::Coordinate2D res = function->privateEvaluateXYAtParameter(parameterForIndex(i/2), context); + m_cache[i - 1] = res.x1(); + m_cache[i] = res.x2(); + } +} + +float ContinuousFunctionCache::parameterForIndex(int i) const { + if (i < m_startOfCache) { + i += k_sizeOfCache; + } + return m_tMin + m_tStep * (i - m_startOfCache); +} + +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 || std::abs(res - delta) > k_cacheHitTolerance) { + return -1; + } + assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0); + return (res + m_startOfCache) % k_sizeOfCache; +} + +void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Context * context, 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::abs(dT) > INT_MAX) { + memoize(function, context); + return; + } + int dI = std::round(dT); + if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::abs(dT - dI) > k_cacheHitTolerance) { + memoize(function, context); + 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) { + memoizeYForXBetweenIndices(function, context, oldStart, m_startOfCache); + } else { + memoizeYForXBetweenIndices(function, context, oldStart, k_sizeOfCache); + memoizeYForXBetweenIndices(function, context, 0, m_startOfCache); + } + } else { + if (m_startOfCache > oldStart) { + memoizeYForXBetweenIndices(function, context, m_startOfCache, k_sizeOfCache); + memoizeYForXBetweenIndices(function, context, 0, oldStart); + } else { + memoizeYForXBetweenIndices(function, context, m_startOfCache, oldStart); + } + } +} + +} diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h new file mode 100644 index 00000000000..0752f75b209 --- /dev/null +++ b/apps/shared/continuous_function_cache.h @@ -0,0 +1,48 @@ +#ifndef SHARED_CONTINUOUS_FUNCTION_CACHE_H +#define SHARED_CONTINUOUS_FUNCTION_CACHE_H + +#include +#include +#include + +namespace Shared { + +class ContinuousFunction; + +class ContinuousFunctionCache { +public: + static void PrepareCache(void * f, void * c, float tMin, float tStep); + + float step() const { return m_tStep; } + bool filled() const { return m_filled; } + void clear(); + Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, float t) const; +private: + /* The size of the cache is chosen to optimize the display of cartesian + * function */ + static constexpr int k_sizeOfCache = Ion::Display::Width; + static constexpr float k_cacheHitTolerance = 1e-3; + + static float StepFactor(ContinuousFunction * function); + + void setRange(ContinuousFunction * function, float tMin, float tStep); + void memoize(ContinuousFunction * function, Poincare::Context * context); + void memoizeYForX(ContinuousFunction * function, Poincare::Context * context); + void memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup); + void memoizeXYForT(ContinuousFunction * function, Poincare::Context * context); + float parameterForIndex(int i) const; + int indexForParameter(const ContinuousFunction * function, float t) const; + void pan(ContinuousFunction * function, Poincare::Context * context, 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; + bool m_filled; +}; + +} + +#endif From a9c633a54048b0c3499e5de8e58db40a9ba2080d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 14:00:11 +0200 Subject: [PATCH 066/560] [apps/shared] Functions cache clears on change Added several triggers to clear the cache when the function's type, range or content is changed. Change-Id: I4f49a90bb6571e335a4a601723d7715b8de1e25e --- apps/shared/continuous_function.cpp | 9 +++++++++ apps/shared/continuous_function.h | 1 + apps/shared/expression_model_handle.h | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index ebac4e160fb..042e42ea6bb 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); + cache()->clear(); + // Recompute the layouts m_model.tidy(); @@ -251,10 +253,12 @@ float ContinuousFunction::tMax() const { void ContinuousFunction::setTMin(float tMin) { recordData()->setTMin(tMin); + cache()->clear(); } void ContinuousFunction::setTMax(float tMax) { recordData()->setTMax(tMax); + cache()->clear(); } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { @@ -347,6 +351,11 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e * the derivative table. */ } +Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { + cache()->clear(); + return ExpressionModelHandle::setContent(c, context); +} + template Coordinate2D ContinuousFunction::templatedApproximateAtParameter(float, Poincare::Context *) const; template Coordinate2D ContinuousFunction::templatedApproximateAtParameter(double, Poincare::Context *) const; diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 096d6ee61c7..86769eca97a 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -80,6 +80,7 @@ class ContinuousFunction : public Function { // Cache ContinuousFunctionCache * cache() const { return const_cast(&m_cache); } + 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); diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index b07c18e3667..8d6229a1825 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -30,7 +30,7 @@ class ExpressionModelHandle : public Ion::Storage::Record { * 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()); } From c70b545ba11cdcbc2c797cf68dbc2859a8e0cab7 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 14:05:15 +0200 Subject: [PATCH 067/560] [apps/shared] Cache lookup on function evaluation When evaluating a ContinuousFunction for a float value, the function will first try to ask its cache (if it has been filled beforehand). Change-Id: I519d2d3dcf344ba63e30a0f011db2306c7141315 --- apps/shared/continuous_function.cpp | 12 ++++++++++++ apps/shared/continuous_function.h | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 042e42ea6bb..b00f69046b2 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -351,6 +351,18 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e * the derivative table. */ } +Poincare::Coordinate2D ContinuousFunction::checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const { + Poincare::Coordinate2D res(NAN, NAN); + if (cache()->filled()) { + res = cache()->valueForParameter(this, t); + } + if (std::isnan(res.x1()) || std::isnan(res.x2())) { + res = privateEvaluateXYAtParameter(t, context); + //res = Poincare::Coordinate2D(privateEvaluateXYAtParameter(t, context).x1(), 0); + } + return res; +} + Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { cache()->clear(); return ExpressionModelHandle::setContent(c, context); diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 86769eca97a..93e8c59988f 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -47,7 +47,8 @@ class ContinuousFunction : public Function { return templatedApproximateAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(float t, Poincare::Context * context) const override { - return privateEvaluateXYAtParameter(t, context); + //return privateEvaluateXYAtParameter(t, context); + return checkForCacheHitAndEvaluate(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(double t, Poincare::Context * context) const override { return privateEvaluateXYAtParameter(t, context); @@ -86,6 +87,7 @@ class ContinuousFunction : public Function { 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; + Poincare::Coordinate2D checkForCacheHitAndEvaluate(float t, 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. */ From 95fef86ec00c8cd8094ba179e58eee53cf11564f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 14:10:01 +0200 Subject: [PATCH 068/560] [apps/graph] Activated caching for graph drawing Added a default argument to CurveView::drawCurve : a function to initiate the memoization of continuous functions. The function is implemented in ContinuousFunctionCache and provided by GraphView. This should be invisible to other types of curves that rely on CurveView::drawCurve. Change-Id: I59aa55d67154b6d4bf9614b7ed87c48408773d86 --- apps/graph/graph/graph_view.cpp | 7 +++++-- apps/shared/curve_view.cpp | 7 ++++--- apps/shared/curve_view.h | 5 +++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 77fb1468dd2..cc4358d9dc2 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -62,7 +62,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }); + }, + &ContinuousFunctionCache::PrepareCache); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context()); @@ -83,7 +84,9 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), false, f->color()); + }, f.operator->(), context(), false, f->color(), + true, false, 0.0f, 0.0f, /* drawCurve's default arguments */ + &ContinuousFunctionCache::PrepareCache); } } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 77003ab4012..e32fd98817b 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -602,7 +602,8 @@ const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { constexpr static int k_maxNumberOfIterations = 10; -void CurveView::drawCurve(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 { +void CurveView::drawCurve(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, PrepareContinuousFunction functionPreparator) const { + functionPreparator(model, context, tStart, tStep); float previousT = NAN; float t = NAN; float previousX = NAN; @@ -634,7 +635,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd } while (true); } -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 { +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, PrepareContinuousFunction functionPreparator) const { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); float tStart = std::isnan(rectLeft) ? xMin : std::max(xMin, rectLeft); @@ -644,7 +645,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo return; } float tStep = pixelWidth(); - drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator); } void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 00a3119a04d..6f953a43aa7 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -19,6 +19,7 @@ class CurveView : public View { typedef Poincare::Coordinate2D (*EvaluateXYForFloatParameter)(float t, void * model, void * context); typedef Poincare::Coordinate2D (*EvaluateXYForDoubleParameter)(double t, void * model, void * context); typedef float (*EvaluateYForX)(float x, void * model, void * context); + typedef void (* PrepareContinuousFunction)(void * model, void * context, float xMin, float xStep); enum class Axis { Horizontal = 0, Vertical = 1 @@ -107,8 +108,8 @@ class CurveView : public View { void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; 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 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, float xMin, float xStep) {}) 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, float xMin, float xStep) {}) 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); From 42fcf557b820f33a9e15aac7a2b1fd319d93c40b Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 9 Jun 2020 16:19:23 +0200 Subject: [PATCH 069/560] [apps/graph] Limited number of cached functions The caches used for function values memoization are now stored in ContinuousFunctionStore : there are now only a fixed number, instead of one per function. This effectively enables caching only for the first few functions on screen, while reducing the memory usage. Change-Id: I2ade091717f73a14a756fe527c773db8e8627be7 --- apps/graph/continuous_function_store.h | 3 +++ apps/graph/graph/graph_view.cpp | 7 ++++--- apps/shared/continuous_function.cpp | 10 +++++----- apps/shared/continuous_function.h | 7 +++++-- apps/shared/continuous_function_cache.cpp | 20 ++++++++++++++------ apps/shared/continuous_function_cache.h | 6 +++++- apps/shared/curve_view.cpp | 8 ++++---- apps/shared/curve_view.h | 6 +++--- 8 files changed, 43 insertions(+), 24 deletions(-) diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h index 2670a08e838..eb506822ba6 100644 --- a/apps/graph/continuous_function_store.h +++ b/apps/graph/continuous_function_store.h @@ -16,6 +16,7 @@ class ContinuousFunctionStore : public Shared::FunctionStore { return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); } Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } + Shared::ExpiringPointer cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? Shared::ExpiringPointer(k_functionCaches + i) : nullptr; } Ion::Storage::Record::ErrorStatus addEmptyModel() override; private: const char * modelExtension() const override { return Ion::Storage::funcExtension; } @@ -26,6 +27,8 @@ class ContinuousFunctionStore : public Shared::FunctionStore { return isFunctionActive(model, context) && plotType == static_cast(model)->plotType(); } mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; + mutable Shared::ContinuousFunctionCache k_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; + }; } diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index cc4358d9dc2..226838e91d6 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -27,7 +27,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); + ExpiringPointer cch = functionStore->cacheAtIndex(i); Shared::ContinuousFunction::PlotType type = f->plotType(); Poincare::Expression e = f->expressionReduced(context()); if (e.isUndefined() || ( @@ -63,7 +64,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); }, - &ContinuousFunctionCache::PrepareCache); + &ContinuousFunctionCache::PrepareCache, cch.operator->()); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context()); @@ -86,7 +87,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { return f->evaluateXYAtParameter(t, c); }, f.operator->(), context(), false, f->color(), true, false, 0.0f, 0.0f, /* drawCurve's default arguments */ - &ContinuousFunctionCache::PrepareCache); + &ContinuousFunctionCache::PrepareCache, cch.operator->()); } } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index b00f69046b2..59a9b43d7b7 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -125,7 +125,7 @@ void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences recordData()->setPlotType(newPlotType); - cache()->clear(); + clearCache(); // Recompute the layouts m_model.tidy(); @@ -253,12 +253,12 @@ float ContinuousFunction::tMax() const { void ContinuousFunction::setTMin(float tMin) { recordData()->setTMin(tMin); - cache()->clear(); + clearCache(); } void ContinuousFunction::setTMax(float tMax) { recordData()->setTMax(tMax); - cache()->clear(); + clearCache(); } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { @@ -353,7 +353,7 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e Poincare::Coordinate2D ContinuousFunction::checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const { Poincare::Coordinate2D res(NAN, NAN); - if (cache()->filled()) { + if (cacheIsFilled()) { res = cache()->valueForParameter(this, t); } if (std::isnan(res.x1()) || std::isnan(res.x2())) { @@ -364,7 +364,7 @@ Poincare::Coordinate2D ContinuousFunction::checkForCacheHitAndEvaluate(fl } Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { - cache()->clear(); + clearCache(); return ExpressionModelHandle::setContent(c, context); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 93e8c59988f..284e967b9a4 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -80,7 +80,10 @@ class ContinuousFunction : public Function { Poincare::Expression sumBetweenBounds(double start, double end, Poincare::Context * context) const override; // Cache - ContinuousFunctionCache * cache() const { return const_cast(&m_cache); } + ContinuousFunctionCache * cache() const { return m_cache; } + void setCache(ContinuousFunctionCache * v) { m_cache = v; } + void clearCache() { m_cache = nullptr; } + bool cacheIsFilled() const { return cache() && cache()->filled(); } 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 @@ -125,7 +128,7 @@ class ContinuousFunction : public Function { RecordDataBuffer * recordData() const; template Poincare::Coordinate2D templatedApproximateAtParameter(T t, Poincare::Context * context) const; Model m_model; - ContinuousFunctionCache m_cache; + ContinuousFunctionCache * m_cache; }; } diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 567452f3cae..ce8c0873730 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -5,20 +5,28 @@ namespace Shared { constexpr int ContinuousFunctionCache::k_sizeOfCache; constexpr float ContinuousFunctionCache::k_cacheHitTolerance; +constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; // public -void ContinuousFunctionCache::PrepareCache(void * f, void * c, float tMin, float tStep) { +void ContinuousFunctionCache::PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep) { + if (!cch) { + return; + } ContinuousFunction * function = (ContinuousFunction *)f; - Poincare::Context * context = (Poincare::Context *)c; - ContinuousFunctionCache * functionCache = function->cache(); - if (functionCache->filled() && tStep / StepFactor(function) == functionCache->step()) { + Poincare::Context * context = (Poincare::Context *)ctx; + if (!function->cache()) { + ContinuousFunctionCache * cache = (ContinuousFunctionCache *)cch; + cache->clear(); + function->setCache(cache); + } + if (function->cache()->filled() && tStep / StepFactor(function) == function->cache()->step()) { if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { function->cache()->pan(function, context, tMin); } return; } - functionCache->setRange(function, tMin, tStep); - functionCache->memoize(function, context); + function->cache()->setRange(function, tMin, tStep); + function->cache()->memoize(function, context); } void ContinuousFunctionCache::clear() { diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index 0752f75b209..046357e711e 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -11,7 +11,11 @@ class ContinuousFunction; class ContinuousFunctionCache { public: - static void PrepareCache(void * f, void * c, float tMin, float tStep); + /* The size of the cache is chosen to optimize the display of cartesian + * function */ + static constexpr int k_numberOfAvailableCaches = 2; + + static void PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep); float step() const { return m_tStep; } bool filled() const { return m_filled; } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index e32fd98817b..239ea34d9a2 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -602,8 +602,8 @@ const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { constexpr static int k_maxNumberOfIterations = 10; -void CurveView::drawCurve(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, PrepareContinuousFunction functionPreparator) const { - functionPreparator(model, context, tStart, tStep); +void CurveView::drawCurve(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, PrepareContinuousFunction functionPreparator, void * cache) const { + functionPreparator(model, context, cache, tStart, tStep); float previousT = NAN; float t = NAN; float previousX = NAN; @@ -635,7 +635,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd } while (true); } -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, PrepareContinuousFunction functionPreparator) const { +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, PrepareContinuousFunction functionPreparator, void * cache) const { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); float tStart = std::isnan(rectLeft) ? xMin : std::max(xMin, rectLeft); @@ -645,7 +645,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo return; } float tStep = pixelWidth(); - drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator); + drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator, cache); } void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 6f953a43aa7..280ed488c0e 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -19,7 +19,7 @@ class CurveView : public View { typedef Poincare::Coordinate2D (*EvaluateXYForFloatParameter)(float t, void * model, void * context); typedef Poincare::Coordinate2D (*EvaluateXYForDoubleParameter)(double t, void * model, void * context); typedef float (*EvaluateYForX)(float x, void * model, void * context); - typedef void (* PrepareContinuousFunction)(void * model, void * context, float xMin, float xStep); + typedef void (* PrepareContinuousFunction)(void * model, void * context, void * cache, float xMin, float xStep); enum class Axis { Horizontal = 0, Vertical = 1 @@ -108,8 +108,8 @@ class CurveView : public View { void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, float xMin, float xStep) {}) 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, float xMin, float xStep) {}) 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,PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = 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); From 1bee23cf4f6fa545c740a52299b34aa5e1b58032 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 12 Jun 2020 12:38:35 +0200 Subject: [PATCH 070/560] [apps/graph] Reworked function caching Instead of being memoized all at once before display, functions values are now stored at evaluation time. Fixed some quirks with caching preparation. Change-Id: I5d212c271c8c41a6dc9074a15c720f0bccf8ac40 --- apps/graph/continuous_function_store.h | 4 +- apps/graph/graph/graph_view.cpp | 21 ++-- apps/shared/continuous_function.cpp | 12 --- apps/shared/continuous_function.h | 8 +- apps/shared/continuous_function_cache.cpp | 120 +++++++++------------- apps/shared/continuous_function_cache.h | 17 ++- apps/shared/curve_view.cpp | 7 +- apps/shared/curve_view.h | 5 +- 8 files changed, 83 insertions(+), 111 deletions(-) diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h index eb506822ba6..1789bc682dc 100644 --- a/apps/graph/continuous_function_store.h +++ b/apps/graph/continuous_function_store.h @@ -16,7 +16,7 @@ class ContinuousFunctionStore : public Shared::FunctionStore { return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); } Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } - Shared::ExpiringPointer cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? Shared::ExpiringPointer(k_functionCaches + i) : nullptr; } + 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; } @@ -27,7 +27,7 @@ class ContinuousFunctionStore : public Shared::FunctionStore { return isFunctionActive(model, context) && plotType == static_cast(model)->plotType(); } mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; - mutable Shared::ContinuousFunctionCache k_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; + mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches]; }; diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 226838e91d6..ac22f7c6506 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; @@ -28,7 +29,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { for (int i = 0; i < activeFunctionsCount ; i++) { Ion::Storage::Record record = functionStore->activeRecordAtIndex(i); ExpiringPointer f = functionStore->modelForRecord(record); - ExpiringPointer cch = functionStore->cacheAtIndex(i); + ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i); Shared::ContinuousFunction::PlotType type = f->plotType(); Poincare::Expression e = f->expressionReduced(context()); if (e.isUndefined() || ( @@ -52,6 +53,17 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { * how fast the function moves... */ float tstep = (tmax-tmin)/10.0938275501223f; + float tCacheMin, tCacheStep; + if (type == ContinuousFunction::PlotType::Cartesian) { + float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); + tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft); + tCacheStep = pixelWidth(); + } else { + tCacheMin = tmin; + tCacheStep = tstep; + } + ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); + // Cartesian if (type == Shared::ContinuousFunction::PlotType::Cartesian) { drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { @@ -63,8 +75,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, - &ContinuousFunctionCache::PrepareCache, cch.operator->()); + }); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context()); @@ -85,9 +96,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), false, f->color(), - true, false, 0.0f, 0.0f, /* drawCurve's default arguments */ - &ContinuousFunctionCache::PrepareCache, cch.operator->()); + }, f.operator->(), context(), false, f->color()); } } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 59a9b43d7b7..885f5f2e730 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -351,18 +351,6 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e * the derivative table. */ } -Poincare::Coordinate2D ContinuousFunction::checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const { - Poincare::Coordinate2D res(NAN, NAN); - if (cacheIsFilled()) { - res = cache()->valueForParameter(this, t); - } - if (std::isnan(res.x1()) || std::isnan(res.x2())) { - res = privateEvaluateXYAtParameter(t, context); - //res = Poincare::Coordinate2D(privateEvaluateXYAtParameter(t, context).x1(), 0); - } - return res; -} - Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { clearCache(); return ExpressionModelHandle::setContent(c, context); diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 284e967b9a4..0874941a4d5 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -26,7 +26,8 @@ class ContinuousFunction : public Function { 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; @@ -47,8 +48,7 @@ class ContinuousFunction : public Function { return templatedApproximateAtParameter(t, context); } Poincare::Coordinate2D evaluateXYAtParameter(float t, Poincare::Context * context) const override { - //return privateEvaluateXYAtParameter(t, context); - return checkForCacheHitAndEvaluate(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); @@ -83,14 +83,12 @@ class ContinuousFunction : public Function { ContinuousFunctionCache * cache() const { return m_cache; } void setCache(ContinuousFunctionCache * v) { m_cache = v; } void clearCache() { m_cache = nullptr; } - bool cacheIsFilled() const { return cache() && cache()->filled(); } 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; - Poincare::Coordinate2D checkForCacheHitAndEvaluate(float t, 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. */ diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index ce8c0873730..46418622652 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -1,5 +1,6 @@ #include "continuous_function_cache.h" #include "continuous_function.h" +#include namespace Shared { @@ -8,43 +9,38 @@ constexpr float ContinuousFunctionCache::k_cacheHitTolerance; constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; // public -void ContinuousFunctionCache::PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep) { - if (!cch) { +void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { + 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 do nothing.*/ return; } - ContinuousFunction * function = (ContinuousFunction *)f; - Poincare::Context * context = (Poincare::Context *)ctx; - if (!function->cache()) { - ContinuousFunctionCache * cache = (ContinuousFunctionCache *)cch; + + ContinuousFunction * function = static_cast(fun); + if (function->cache() != cache) { cache->clear(); function->setCache(cache); } - if (function->cache()->filled() && tStep / StepFactor(function) == function->cache()->step()) { - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - function->cache()->pan(function, context, tMin); - } - return; + + if (function->plotType() == ContinuousFunction::PlotType::Cartesian && tStep != 0) { + function->cache()->pan(function, tMin); } function->cache()->setRange(function, tMin, tStep); - function->cache()->memoize(function, context); } void ContinuousFunctionCache::clear() { - m_filled = false; m_startOfCache = 0; + m_tStep = 0; + invalidateBetween(0, k_sizeOfCache); } -Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, float t) const { - int iRes = indexForParameter(function, t); - /* If t does not map to an index, iRes is -1 */ - if (iRes < 0) { - return Poincare::Coordinate2D(NAN, NAN); +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); } - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - return Poincare::Coordinate2D(t, m_cache[iRes]); - } - assert(m_startOfCache == 0); - return Poincare::Coordinate2D(m_cache[2*iRes], m_cache[2*iRes+1]); + return valuesAtIndex(function, context, t, resIndex); } // private @@ -55,46 +51,15 @@ float ContinuousFunctionCache::StepFactor(ContinuousFunction * function) { return (function->plotType() == ContinuousFunction::PlotType::Cartesian) ? 1.f : 16.f; } -void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { - m_tMin = tMin; - m_tStep = tStep / StepFactor(function); -} - -void ContinuousFunctionCache::memoize(ContinuousFunction * function, Poincare::Context * context) { - m_filled = true; - m_startOfCache = 0; - if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { - memoizeYForX(function, context); - return; - } - memoizeXYForT(function, context); -} - -void ContinuousFunctionCache::memoizeYForX(ContinuousFunction * function, Poincare::Context * context) { - memoizeYForXBetweenIndices(function, context, 0, k_sizeOfCache); -} - -void ContinuousFunctionCache::memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup) { - assert(function->plotType() == ContinuousFunction::PlotType::Cartesian); +void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { for (int i = iInf; i < iSup; i++) { - m_cache[i] = function->privateEvaluateXYAtParameter(parameterForIndex(i), context).x2(); - } -} - -void ContinuousFunctionCache::memoizeXYForT(ContinuousFunction * function, Poincare::Context * context) { - assert(function->plotType() != ContinuousFunction::PlotType::Cartesian); - for (int i = 1; i < k_sizeOfCache; i += 2) { - Poincare::Coordinate2D res = function->privateEvaluateXYAtParameter(parameterForIndex(i/2), context); - m_cache[i - 1] = res.x1(); - m_cache[i] = res.x2(); + m_cache[i] = NAN; } } -float ContinuousFunctionCache::parameterForIndex(int i) const { - if (i < m_startOfCache) { - i += k_sizeOfCache; - } - return m_tMin + m_tStep * (i - m_startOfCache); +void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { + m_tMin = tMin; + m_tStep = tStep / StepFactor(function); } int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * function, float t) const { @@ -104,14 +69,31 @@ int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * functi } int res = std::round(delta); assert(res >= 0); - if (res >= k_sizeOfCache || std::abs(res - delta) > k_cacheHitTolerance) { + if ((res >= k_sizeOfCache && function->plotType() == ContinuousFunction::PlotType::Cartesian) + || (res >= k_sizeOfCache / 2 && function->plotType() != ContinuousFunction::PlotType::Cartesian) + || std::abs(res - delta) > k_cacheHitTolerance) { return -1; } assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0); return (res + m_startOfCache) % k_sizeOfCache; } -void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Context * context, float newTMin) { +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; @@ -120,12 +102,12 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte float dT = (newTMin - m_tMin) / m_tStep; m_tMin = newTMin; if (std::abs(dT) > INT_MAX) { - memoize(function, context); + clear(); return; } int dI = std::round(dT); if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::abs(dT - dI) > k_cacheHitTolerance) { - memoize(function, context); + clear(); return; } @@ -136,17 +118,17 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte } if (dI > 0) { if (m_startOfCache > oldStart) { - memoizeYForXBetweenIndices(function, context, oldStart, m_startOfCache); + invalidateBetween(oldStart, m_startOfCache); } else { - memoizeYForXBetweenIndices(function, context, oldStart, k_sizeOfCache); - memoizeYForXBetweenIndices(function, context, 0, m_startOfCache); + invalidateBetween(oldStart, k_sizeOfCache); + invalidateBetween(0, m_startOfCache); } } else { if (m_startOfCache > oldStart) { - memoizeYForXBetweenIndices(function, context, m_startOfCache, k_sizeOfCache); - memoizeYForXBetweenIndices(function, context, 0, oldStart); + invalidateBetween(m_startOfCache, k_sizeOfCache); + invalidateBetween(0, oldStart); } else { - memoizeYForXBetweenIndices(function, context, m_startOfCache, oldStart); + invalidateBetween(m_startOfCache, oldStart); } } } diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index 046357e711e..b48617214be 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -15,12 +15,13 @@ class ContinuousFunctionCache { * function */ static constexpr int k_numberOfAvailableCaches = 2; - static void PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep); + static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); + + ContinuousFunctionCache() { clear(); } float step() const { return m_tStep; } - bool filled() const { return m_filled; } void clear(); - Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, float t) const; + Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t); private: /* The size of the cache is chosen to optimize the display of cartesian * function */ @@ -29,14 +30,11 @@ class ContinuousFunctionCache { static float StepFactor(ContinuousFunction * function); + void invalidateBetween(int iInf, int iSup); void setRange(ContinuousFunction * function, float tMin, float tStep); - void memoize(ContinuousFunction * function, Poincare::Context * context); - void memoizeYForX(ContinuousFunction * function, Poincare::Context * context); - void memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup); - void memoizeXYForT(ContinuousFunction * function, Poincare::Context * context); - float parameterForIndex(int i) const; int indexForParameter(const ContinuousFunction * function, float t) const; - void pan(ContinuousFunction * function, Poincare::Context * context, float newTMin); + 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]; @@ -44,7 +42,6 @@ class ContinuousFunctionCache { * with cartesian functions. When dealing with parametric or polar functions, * m_startOfCache should be zero.*/ int m_startOfCache; - bool m_filled; }; } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 239ea34d9a2..77003ab4012 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -602,8 +602,7 @@ const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { constexpr static int k_maxNumberOfIterations = 10; -void CurveView::drawCurve(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, PrepareContinuousFunction functionPreparator, void * cache) const { - functionPreparator(model, context, cache, tStart, tStep); +void CurveView::drawCurve(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 { float previousT = NAN; float t = NAN; float previousX = NAN; @@ -635,7 +634,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd } while (true); } -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, PrepareContinuousFunction functionPreparator, void * cache) const { +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 { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); float tStart = std::isnan(rectLeft) ? xMin : std::max(xMin, rectLeft); @@ -645,7 +644,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo return; } float tStep = pixelWidth(); - drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator, cache); + drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 280ed488c0e..00a3119a04d 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -19,7 +19,6 @@ class CurveView : public View { typedef Poincare::Coordinate2D (*EvaluateXYForFloatParameter)(float t, void * model, void * context); typedef Poincare::Coordinate2D (*EvaluateXYForDoubleParameter)(double t, void * model, void * context); typedef float (*EvaluateYForX)(float x, void * model, void * context); - typedef void (* PrepareContinuousFunction)(void * model, void * context, void * cache, float xMin, float xStep); enum class Axis { Horizontal = 0, Vertical = 1 @@ -108,8 +107,8 @@ class CurveView : public View { void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; 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,PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = 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, PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = nullptr) 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 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); From beb228fa78dbdca5545339d058ff51c8651c3e51 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 12 Jun 2020 14:33:49 +0200 Subject: [PATCH 071/560] [apps/graph] Fixed cache step quirks Change-Id: I5b630301ab2a4b17a5a4d77c7d9a05120449e55e --- apps/graph/graph/graph_view.cpp | 16 +++------------- apps/graph/graph/graph_view.h | 13 +++++++++++++ apps/shared/continuous_function_cache.cpp | 10 ++-------- apps/shared/continuous_function_cache.h | 13 ++++++------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index ac22f7c6506..90120e8a424 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -40,18 +40,8 @@ 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; + + float tstep = (tmax-tmin)/k_graphStepDenominator; float tCacheMin, tCacheStep; if (type == ContinuousFunction::PlotType::Cartesian) { @@ -60,7 +50,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { tCacheStep = pixelWidth(); } else { tCacheMin = tmin; - tCacheStep = tstep; + tCacheStep = tstep / ContinuousFunctionCache::k_parametricStepFactor; } ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index c3e190a3abb..ded212f6db3 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/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 46418622652..df24141a9d9 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -7,6 +7,7 @@ namespace Shared { constexpr int ContinuousFunctionCache::k_sizeOfCache; constexpr float ContinuousFunctionCache::k_cacheHitTolerance; constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; +constexpr int ContinuousFunctionCache::k_parametricStepFactor; // public void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { @@ -44,13 +45,6 @@ Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const C } // private -float ContinuousFunctionCache::StepFactor(ContinuousFunction * function) { - /* When drawing a parametric or polar curve, the range is first divided by - * ~10,9, creating 11 intervals which are filled by dichotomy. - * We memoize 16 values for each of the 10 big intervals. */ - return (function->plotType() == ContinuousFunction::PlotType::Cartesian) ? 1.f : 16.f; -} - void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { for (int i = iInf; i < iSup; i++) { m_cache[i] = NAN; @@ -59,7 +53,7 @@ void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) { m_tMin = tMin; - m_tStep = tStep / StepFactor(function); + m_tStep = tStep; } int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * function, float t) const { diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index b48617214be..ee5bf598d43 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -1,6 +1,7 @@ #ifndef SHARED_CONTINUOUS_FUNCTION_CACHE_H #define SHARED_CONTINUOUS_FUNCTION_CACHE_H +#include "../graph/graph/graph_view.h" #include #include #include @@ -10,10 +11,13 @@ namespace Shared { class ContinuousFunction; class ContinuousFunctionCache { -public: +private: /* The size of the cache is chosen to optimize the display of cartesian - * function */ + * functions */ + static constexpr int k_sizeOfCache = Ion::Display::Width; +public: static constexpr int k_numberOfAvailableCaches = 2; + static constexpr int k_parametricStepFactor = k_sizeOfCache / int(Graph::GraphView::k_graphStepDenominator); static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); @@ -23,13 +27,8 @@ class ContinuousFunctionCache { void clear(); Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t); private: - /* The size of the cache is chosen to optimize the display of cartesian - * function */ - static constexpr int k_sizeOfCache = Ion::Display::Width; static constexpr float k_cacheHitTolerance = 1e-3; - static float StepFactor(ContinuousFunction * function); - void invalidateBetween(int iInf, int iSup); void setRange(ContinuousFunction * function, float tMin, float tStep); int indexForParameter(const ContinuousFunction * function, float t) const; From b0533e683649daa31ad2c287af1087f6a3e5e587 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 23 Jun 2020 17:07:55 +0200 Subject: [PATCH 072/560] [apps/graph] Changed cache tolerance This fixes a bug where, when drawing 1/x, a vertical bar would appear on 0, because of false positive cache hits. Change-Id: I2eafeac5e254c8a556769a41c8edc532db44383a --- apps/shared/continuous_function_cache.cpp | 8 ++++++++ apps/shared/continuous_function_cache.h | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index df24141a9d9..a2fac0c01b3 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -19,6 +19,14 @@ void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCa } ContinuousFunction * function = static_cast(fun); + 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->clearCache(); + return; + } if (function->cache() != cache) { cache->clear(); function->setCache(cache); diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index ee5bf598d43..60faca9b840 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -27,7 +27,15 @@ class ContinuousFunctionCache { void clear(); Poincare::Coordinate2D valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t); private: - static constexpr float k_cacheHitTolerance = 1e-3; + /* 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 * FLT_EPSILON; void invalidateBetween(int iInf, int iSup); void setRange(ContinuousFunction * function, float tMin, float tStep); From aea9176f86e0fbc6389224d6064ed8ed4ae23b44 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 23 Jun 2020 17:08:12 +0200 Subject: [PATCH 073/560] [apps/graph] Added tests for graph caching Wrote some tests to check that the caching process does not lead to aberrant values. Change-Id: I28137e66f430dbcf4417733d1274319dbadfc9af --- apps/Makefile | 2 +- apps/graph/Makefile | 10 ++- apps/graph/test/caching.cpp | 132 ++++++++++++++++++++++++++++++++++++ apps/shared/Makefile | 4 +- 4 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 apps/graph/test/caching.cpp diff --git a/apps/Makefile b/apps/Makefile index ff6c78f7866..bd71950417d 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -84,7 +84,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/graph/Makefile b/apps/graph/Makefile index 4f440fea8da..1a3fbddccbc 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,13 @@ 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\ +) + $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png)) diff --git a/apps/graph/test/caching.cpp b/apps/graph/test/caching.cpp new file mode 100644 index 00000000000..70ee6863122 --- /dev/null +++ b/apps/graph/test/caching.cpp @@ -0,0 +1,132 @@ +#include +#include "../app.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 a == b || std::abs(a - b) <= tolerance * std::abs(a + b) / 2.f || (std::isnan(a) && std::isnan(b)); +} + +ContinuousFunction * addFunction(ContinuousFunctionStore * store, ContinuousFunction::PlotType type, const char * definition, 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; +} + +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 = ((tMax - tMin) / Graph::GraphView::k_graphStepDenominator) / ContinuousFunctionCache::k_parametricStepFactor; + ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tStep); + + // 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(&functionStore, type, definition, &globalContext); + Coordinate2D origin = function->evaluateXYAtParameter(0.f, &globalContext); + cursor.moveTo(0.f, origin.x1(), origin.x2()); + + if (type == ContinuousFunction::PlotType::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(ContinuousFunction::PlotType::Cartesian, "x"); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "x^2"); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x)"); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x)", -1e6f, 2e8f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x^2)"); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "1/x"); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "1/x", -5e-5f, 5e-5f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "-ℯ^x"); + + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "1", 0.f, 360.f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "θ", 0.f, 360.f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "sin(θ)", 0.f, 360.f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "sin(θ)", 2e-4f, 1e-3f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "cos(5θ)", 0.f, 360.f); + assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "cos(5θ)", -1e8f, 1e8f); +} + +} diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 08c51ef13cb..b525a9033ef 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 \ @@ -24,9 +26,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ - continuous_function_cache.cpp \ cursor_view.cpp \ - curve_view_cursor.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ expression_model_list_controller.cpp \ From 15988f0fae547291accd6da971d5457e295161df Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 3 Jul 2020 16:41:52 +0200 Subject: [PATCH 074/560] [liba/float] Redefined FLT_EPSILON FLT_EPSILON is now compliant with the definition : the difference between 1 and the next representable float. Change-Id: I4f79c3eee93447e76893d850bd84265c9d45aca5 --- liba/include/float.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/liba/include/float.h b/liba/include/float.h index 58ef7ecdf10..a541b8e88af 100644 --- a/liba/include/float.h +++ b/liba/include/float.h @@ -5,10 +5,8 @@ #define FLT_MAX 1E+37f #define FLT_MIN 1E-37f -/* TODO Redefine FLT_EPSILON to comply with the definition : - * FLT_EPSILON is the difference between 1 and the very next floating point - * number. */ -#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 From 1de6ac702dd7b637e806aeecd2dca7ab0cd5abaa Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 3 Jul 2020 16:45:26 +0200 Subject: [PATCH 075/560] [apps/continuous_function_cache] abs -> fabs Replaced abs calls on float with fabs. Change-Id: I26eda164416ca8b2d601431a59171fd7b00ed33f --- apps/shared/continuous_function_cache.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index a2fac0c01b3..8e165b14b4f 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -73,7 +73,7 @@ int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * functi assert(res >= 0); if ((res >= k_sizeOfCache && function->plotType() == ContinuousFunction::PlotType::Cartesian) || (res >= k_sizeOfCache / 2 && function->plotType() != ContinuousFunction::PlotType::Cartesian) - || std::abs(res - delta) > k_cacheHitTolerance) { + || std::fabs(res - delta) > k_cacheHitTolerance) { return -1; } assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0); @@ -103,12 +103,12 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, float newTMin) float dT = (newTMin - m_tMin) / m_tStep; m_tMin = newTMin; - if (std::abs(dT) > INT_MAX) { + if (std::fabs(dT) > INT_MAX) { clear(); return; } int dI = std::round(dT); - if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::abs(dT - dI) > k_cacheHitTolerance) { + if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::fabs(dT - dI) > k_cacheHitTolerance) { clear(); return; } From 2bfdc482a65ad8789ce259ca21450149cd3bf60a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 3 Jul 2020 16:51:03 +0200 Subject: [PATCH 076/560] [apps/continuous_function] Cut clearCache method Inlined call to ContinuousFunction::clearCache and removed the method, as its name was confusing. Change-Id: I54454fb0b06167e01249afaa5b23932451125f2e --- apps/shared/continuous_function.cpp | 8 ++++---- apps/shared/continuous_function.h | 1 - apps/shared/continuous_function_cache.cpp | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 885f5f2e730..b2688fdc3ec 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -125,7 +125,7 @@ void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences recordData()->setPlotType(newPlotType); - clearCache(); + setCache(nullptr); // Recompute the layouts m_model.tidy(); @@ -253,12 +253,12 @@ float ContinuousFunction::tMax() const { void ContinuousFunction::setTMin(float tMin) { recordData()->setTMin(tMin); - clearCache(); + setCache(nullptr); } void ContinuousFunction::setTMax(float tMax) { recordData()->setTMax(tMax); - clearCache(); + setCache(nullptr); } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { @@ -352,7 +352,7 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e } Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) { - clearCache(); + setCache(nullptr); return ExpressionModelHandle::setContent(c, context); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 0874941a4d5..b6ea846bb67 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -82,7 +82,6 @@ class ContinuousFunction : public Function { // Cache ContinuousFunctionCache * cache() const { return m_cache; } void setCache(ContinuousFunctionCache * v) { m_cache = v; } - void clearCache() { m_cache = nullptr; } 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 diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 8e165b14b4f..24435e92683 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -24,7 +24,7 @@ void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCa * 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->clearCache(); + function->setCache(nullptr); return; } if (function->cache() != cache) { From 433639c471ee33b97217487f37d05ce7680191e1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 23 Jun 2020 15:19:51 +0200 Subject: [PATCH 077/560] [ion] Implement dynamic scroll speed profile Change-Id: Ib3ec2ef63d19bfb88b627f06bcc185fa1b232223 --- ion/include/ion/events.h | 2 +- ion/src/shared/events_keyboard.cpp | 18 +++++++++--------- ion/src/shared/events_modifier.cpp | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 9e3d7b72fa6..b587273998c 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -57,7 +57,7 @@ void removeShift(); bool isShiftActive(); bool isAlphaActive(); bool isLockActive(); -void setLongRepetition(bool longRepetition); +void setLongRepetition(int longRepetition); int repetitionFactor(); void updateModifiersFromEvent(Event e); void didPressNewKey(); diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index ad356b1267b..009939ba3b9 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) { @@ -85,7 +89,7 @@ Event getEvent(int * timeout) { } if (sleepWithTimeout(10, timeout)) { - // Timeout occured + // Timeout occurred resetLongRepetition(); return Events::None; } @@ -101,12 +105,8 @@ Event getEvent(int * timeout) { 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 79be2355335..7cccc2877a8 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,12 +33,12 @@ bool isLockActive() { return sShiftAlphaStatus == ShiftAlphaStatus::AlphaLock || sShiftAlphaStatus == ShiftAlphaStatus::ShiftAlphaLock; } -void setLongRepetition(bool longRepetition) { +void setLongRepetition(int longRepetition) { sLongRepetition = longRepetition; } int repetitionFactor() { - return sLongRepetition ? 5 : 1; + return sLongRepetition; }; void setShiftAlphaStatus(ShiftAlphaStatus s) { From e49dc0875900b943905c4f79464ee8ee3c3a8378 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 6 Jul 2020 15:09:56 +0200 Subject: [PATCH 078/560] [escher/expression_field] Fixed dumpLayout Closing the Calculation app because of a full pool would crash the device, by attempting to dump a layout with no node. Change-Id: Ic926da61ae93f47eb576848788e1de5e3f94f5bb --- escher/include/escher/expression_view.h | 1 + escher/include/escher/layout_field.h | 1 + escher/src/expression_field.cpp | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index 4e4a4b901dc..c177a48d230 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -26,6 +26,7 @@ class ExpressionView : public View { 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/layout_field.h b/escher/include/escher/layout_field.h index a39e585babb..908d27f3f8b 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -32,6 +32,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E } 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; } diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp index 599f69b7f4f..46d09b61137 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -136,6 +136,16 @@ size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) const { * has changed and invalidate the data.*/ returnValue = 0; } else { + /* dumpLayout will be called whenever Calculation exits, event when an + * exception occurs. We thus need to check the validity of the layout we + * are dumping (m_layoutField.m_contentView.m_expressionView.m_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; + } size = m_layoutField.layout().size(); currentLayout = reinterpret_cast(m_layoutField.layout().node()); returnValue = size; From 67af3a5b4e59074a695ed877bec849d763962f64 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 7 Jul 2020 11:45:30 +0200 Subject: [PATCH 079/560] [apps/calculation] Fixed grey and yellow squares When closing and reopening Calculation, if the cursor was on an EmptyLayout, this layout would be invisible, instead of being denoted by its usual yellow square. This also fixes a bug that would leave the grey squares in matrices when leaving and reopening the app, causing a failed assertion on the simulator. Change-Id: Ib723797c079f1441f5cde174aa5c492ad4226335 --- escher/include/escher/expression_field.h | 2 +- escher/src/expression_field.cpp | 3 ++- escher/src/layout_field.cpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index e89c1dc98c0..f0931d5db22 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -26,7 +26,7 @@ class ExpressionField : public Responder, public View { 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 dumpLayout(char * buffer, size_t bufferSize) const; + size_t dumpLayout(char * buffer, size_t bufferSize); void restoreLayout(const char * buffer, size_t size); /* View */ diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp index 46d09b61137..723d895437e 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -122,7 +122,7 @@ KDCoordinate ExpressionField::inputViewHeight() const { std::max(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); } -size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) const { +size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) { size_t size; size_t returnValue; char * currentLayout; @@ -146,6 +146,7 @@ size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) const { buffer[0] = 0; return 0; } + m_layoutField.putCursorRightOfLayout(); size = m_layoutField.layout().size(); currentLayout = reinterpret_cast(m_layoutField.layout().node()); returnValue = size; diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index ff6b493dde6..4ee8e166f8d 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -336,6 +336,7 @@ CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) { void LayoutField::putCursorRightOfLayout() { m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixAncestors(); + m_contentView.cursor()->showEmptyLayoutIfNeeded(); m_contentView.setCursor(LayoutCursor(m_contentView.expressionView()->layout(), LayoutCursor::Position::Right)); } From de681705e62bc9b0fd5bec900ec0b02bed4d68a2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 8 Jul 2020 11:23:51 +0200 Subject: [PATCH 080/560] [escher/expression_field] Renamed dumpLayout Changed dumpLayout's name to moveCursorAndDumpContent, as the name suggested the method was only usable on layouts (while it also works on text). The method is also no longer marked const, and the previous name could imply that it had no side effects. Likewise, renamed restoreLayout to restoreContent. Change-Id: I46b320e74c07b48e256dc2146f9ecc15af38a8e6 --- .../edit_expression_controller.cpp | 4 ++-- escher/include/escher/expression_field.h | 4 ++-- escher/src/expression_field.cpp | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index e72762f7f9b..7500389075a 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -56,13 +56,13 @@ void EditExpressionController::insertTextBody(const char * text) { void EditExpressionController::didBecomeFirstResponder() { m_contentView.mainView()->scrollToBottom(); m_contentView.expressionField()->setEditing(true, false); - m_contentView.expressionField()->restoreLayout(m_cacheBuffer, *m_cacheBufferInformation); + m_contentView.expressionField()->restoreContent(m_cacheBuffer, *m_cacheBufferInformation); clearCacheBuffer(); Container::activeApp()->setFirstResponder(m_contentView.expressionField()); } void EditExpressionController::willExitResponderChain(Responder * nextFirstResponder) { - *m_cacheBufferInformation = m_contentView.expressionField()->dumpLayout(m_cacheBuffer, k_cacheBufferSize); + *m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize); ::ViewController::willExitResponderChain(nextFirstResponder); } diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index f0931d5db22..7ecaf57d4dc 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -26,8 +26,8 @@ class ExpressionField : public Responder, public View { 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 dumpLayout(char * buffer, size_t bufferSize); - void restoreLayout(const char * buffer, size_t size); + 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/src/expression_field.cpp b/escher/src/expression_field.cpp index 723d895437e..ad75892c7ee 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -122,13 +122,14 @@ KDCoordinate ExpressionField::inputViewHeight() const { std::max(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); } -size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) { +size_t ExpressionField::moveCursorAndDumpContent(char * buffer, size_t bufferSize) { size_t size; size_t returnValue; - char * currentLayout; + char * currentContent; if (editionIsInTextField()) { size = strlen(m_textField.draftTextBuffer()) + 1; - currentLayout = m_textField.draftTextBuffer(); + 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 @@ -136,9 +137,9 @@ size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) { * has changed and invalidate the data.*/ returnValue = 0; } else { - /* dumpLayout will be called whenever Calculation exits, event when an - * exception occurs. We thus need to check the validity of the layout we - * are dumping (m_layoutField.m_contentView.m_expressionView.m_layout). + /* 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. */ @@ -148,18 +149,18 @@ size_t ExpressionField::dumpLayout(char * buffer, size_t bufferSize) { } m_layoutField.putCursorRightOfLayout(); size = m_layoutField.layout().size(); - currentLayout = reinterpret_cast(m_layoutField.layout().node()); + currentContent = reinterpret_cast(m_layoutField.layout().node()); returnValue = size; } if (size > bufferSize - 1) { buffer[0] = 0; return 0; } - memcpy(buffer, currentLayout, size); + memcpy(buffer, currentContent, size); return returnValue; } -void ExpressionField::restoreLayout(const char * buffer, size_t size) { +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 From 6072ffd84836215c409f59d51bb3e04a94dff008 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 1 Jul 2020 16:18:50 +0200 Subject: [PATCH 081/560] [poincare/normal_distribution.cpp] Fixed approximation error Removed limitation in calculation of normal_distribution. It was used to speed-up computation but yielded 0 for small values. Previously P(X<5) with X->N(7,0.3162) gave 0 as a result now it gives 1.26e-10 Change-Id: I3f1c997dfe2ba6424b372a0a82af6d9871443657 --- poincare/include/poincare/normal_distribution.h | 5 ----- poincare/src/normal_distribution.cpp | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/poincare/include/poincare/normal_distribution.h b/poincare/include/poincare/normal_distribution.h index fe30513f898..e9a54a27363 100644 --- a/poincare/include/poincare/normal_distribution.h +++ b/poincare/include/poincare/normal_distribution.h @@ -16,11 +16,6 @@ class NormalDistribution final { * 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/src/normal_distribution.cpp b/poincare/src/normal_distribution.cpp index 710dfacb80a..425f90f0f20 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) { From 997c103fbad9fef219a89ad2a14af6a7418c9759 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 1 Jul 2020 16:33:44 +0200 Subject: [PATCH 082/560] [Toolbox] Changed the required parameters for normal_distributions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed the parameter σ2 to σ. This is now consistant with the probability app Change-Id: I96101ba158cebef972e536cfa5cc1b2da71b543d --- apps/shared.universal.i18n | 8 ++++---- apps/toolbox.de.i18n | 8 ++++---- apps/toolbox.en.i18n | 8 ++++---- apps/toolbox.es.i18n | 8 ++++---- apps/toolbox.fr.i18n | 8 ++++---- apps/toolbox.it.i18n | 8 ++++---- apps/toolbox.nl.i18n | 8 ++++---- apps/toolbox.pt.i18n | 8 ++++---- poincare/src/inv_norm.cpp | 4 ++-- poincare/src/norm_cdf.cpp | 4 ++-- poincare/src/norm_cdf2.cpp | 4 ++-- poincare/src/norm_pdf.cpp | 4 ++-- poincare/test/approximation.cpp | 20 +++++++++++--------- poincare/test/simplification.cpp | 2 +- 14 files changed, 52 insertions(+), 50 deletions(-) diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index b313ff93c4d..47b8ac66d40 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -114,7 +114,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 = "λ" @@ -127,9 +127,9 @@ 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,μ,σ)" PermuteCommandWithArg = "permute(n,r)" P = "p" Prediction95CommandWithArg = "prediction95(p,n)" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index eff98dbe8ce..b55853754d8 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -155,10 +155,10 @@ RandomAndApproximation = "Zufall und Näherung" RandomFloat = "Dezimalzahl in [0,1]" RandomInteger = "Zufällige ganze Zahl in [a,b]" PrimeFactorDecomposition = "Primfaktorzerlegung" -NormCDF = "P(X 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 sigmaEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); 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/norm_cdf.cpp b/poincare/src/norm_cdf.cpp index 52c8f176128..2775cc41899 100644 --- a/poincare/src/norm_cdf.cpp +++ b/poincare/src/norm_cdf.cpp @@ -27,11 +27,11 @@ 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 sigmaEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); 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 b655a10dee6..625451406d8 100644 --- a/poincare/src/norm_cdf2.cpp +++ b/poincare/src/norm_cdf2.cpp @@ -28,12 +28,12 @@ Evaluation NormCDF2Node::templatedApproximate(Context * context, Preferences: 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 sigmaEvaluation = childAtIndex(3)->approximate(T(), context, complexFormat, angleUnit); 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 af19c69c617..bb3ccee3fdf 100644 --- a/poincare/src/norm_pdf.cpp +++ b/poincare/src/norm_pdf.cpp @@ -27,11 +27,11 @@ 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 sigmaEvaluation = childAtIndex(2)->approximate(T(), context, complexFormat, angleUnit); 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/test/approximation.cpp b/poincare/test/approximation.cpp index ac54076c8e0..7f08c06b02d 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -295,8 +295,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 +304,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"); + + 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"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index b0124a85aee..0412d55833f 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -344,7 +344,7 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("inf×_s", "inf×_s"); assert_parsed_expression_simplify_to("-inf×_s", "-inf×_s"); 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("normcdf(0,20,3)×_s", "13.083978345207×_ps"); assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); assert_parsed_expression_simplify_to("log(undef)*_s", "undef"); From 007c38652f1348a6f436bd17d4100b0e72fdecff Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 22 Jun 2020 16:46:42 +0200 Subject: [PATCH 083/560] [poincare] Add ref and rref matrix functions Change-Id: Id0e57a4f85d551ca5320c4b6e3c0baadae70946d --- apps/math_toolbox.cpp | 4 +- apps/shared.universal.i18n | 2 + apps/toolbox.de.i18n | 2 + apps/toolbox.en.i18n | 2 + apps/toolbox.es.i18n | 2 + apps/toolbox.fr.i18n | 2 + apps/toolbox.it.i18n | 2 + apps/toolbox.nl.i18n | 2 + apps/toolbox.pt.i18n | 2 + poincare/Makefile | 2 + poincare/include/poincare/expression.h | 2 + poincare/include/poincare/expression_node.h | 2 + poincare/include/poincare/matrix.h | 7 ++- poincare/include/poincare/matrix_complex.h | 2 + poincare/include/poincare/matrix_ref.h | 48 ++++++++++++++++ poincare/include/poincare/matrix_rref.h | 48 ++++++++++++++++ poincare/include/poincare_nodes.h | 2 + poincare/src/expression.cpp | 4 +- poincare/src/matrix.cpp | 52 +++++++++++++++--- poincare/src/matrix_complex.cpp | 16 ++++++ poincare/src/matrix_ref.cpp | 61 +++++++++++++++++++++ poincare/src/matrix_rref.cpp | 61 +++++++++++++++++++++ poincare/src/parsing/parser.h | 2 + poincare/src/tree_handle.cpp | 2 + poincare/test/approximation.cpp | 19 +++++++ poincare/test/expression_properties.cpp | 2 + poincare/test/parsing.cpp | 2 + poincare/test/simplification.cpp | 8 +++ 28 files changed, 349 insertions(+), 13 deletions(-) create mode 100644 poincare/include/poincare/matrix_ref.h create mode 100644 poincare/include/poincare/matrix_rref.h create mode 100644 poincare/src/matrix_ref.cpp create mode 100644 poincare/src/matrix_rref.cpp diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ad37d430986..49b9c62f9f4 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -71,7 +71,9 @@ 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) }; #if LIST_ARE_DEFINED diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 47b8ac66d40..ec4eeffd9e5 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -140,9 +140,11 @@ 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" Shift = "shift" Sigma = "σ" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index b55853754d8..8401398154b 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -134,6 +134,8 @@ Determinant = "Determinante" Transpose = "Transponierte" Trace = "Spur" Dimension = "Größe" +RowEchelonForm = "Stufenform" +ReducedRowEchelonForm = "Reduzierte Stufenform" Sort = "Sortieren aufsteigend" InvSort = "Sortieren absteigend" Maximum = "Maximalwert" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index db54e3e0fac..a6adecaf629 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -134,6 +134,8 @@ Determinant = "Determinant" Transpose = "Transpose" Trace = "Trace" Dimension = "Size" +RowEchelonForm = "Row echelon form" +ReducedRowEchelonForm = "Reduced row echelon form" Sort = "Sort ascending " InvSort = "Sort descending" Maximum = "Maximum" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 963b32cee06..b9b245577fe 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -134,6 +134,8 @@ Determinant = "Determinante" Transpose = "Transpuesta" Trace = "Traza" Dimension = "Tamaño" +RowEchelonForm = "Matriz escalonada" +ReducedRowEchelonForm = "Matriz escalonada reducida" Sort = "Clasificación ascendente" InvSort = "Clasificación descendente" Maximum = "Máximo" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 5479c896fa9..fe354064d48 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -134,6 +134,8 @@ Determinant = "Déterminant de M" Transpose = "Transposée de M" Trace = "Trace de M" Dimension = "Taille de M" +RowEchelonForm = "Forme échelonnée" +ReducedRowEchelonForm = "Forme échelonnée réduite" Sort = "Tri croissant" InvSort = "Tri décroissant" Maximum = "Maximum" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 8bd5206583f..f913db44f4e 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -134,6 +134,8 @@ Determinant = "Determinante" Transpose = "Trasposta" Trace = "Traccia" Dimension = "Dimensione" +RowEchelonForm = "Matrice a scalini" +ReducedRowEchelonForm = "Matrice ridotta a scalini" Sort = "Ordine crescente" InvSort = "Ordine decrescente" Maximum = "Massimo" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 960f42edbf3..18e25d3cc91 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -134,6 +134,8 @@ Determinant = "Determinant" Transpose = "Getransponeerde" Trace = "Spoor" Dimension = "Afmeting" +RowEchelonForm = "Echelonvorm" +ReducedRowEchelonForm = "Gereduceerde echelonvorm" Sort = "Sorteer oplopend " InvSort = "Sort aflopend" Maximum = "Maximum" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 822538a544d..00790ad3ddd 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -134,6 +134,8 @@ Determinant = "Determinante" Transpose = "Matriz transposta" Trace = "Traço" Dimension = "Dimensão" +RowEchelonForm = "Matriz escalonada" +ReducedRowEchelonForm = "Matriz escalonada reduzida" Sort = "Ordem crescente" InvSort = "Ordem decrescente" Maximum = "Máximo" diff --git a/poincare/Makefile b/poincare/Makefile index b1f4215c6d5..4826173985d 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -101,6 +101,8 @@ poincare_src += $(addprefix poincare/src/,\ matrix_inverse.cpp \ matrix_trace.cpp \ matrix_transpose.cpp \ + matrix_ref.cpp \ + matrix_rref.cpp \ multiplication.cpp \ n_ary_expression.cpp \ naperian_logarithm.cpp \ diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 788e8955c38..b6d16787b46 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -63,6 +63,8 @@ class Expression : public TreeHandle { friend class MatrixInverse; friend class MatrixTrace; friend class MatrixTranspose; + friend class MatrixRef; + friend class MatrixRref; friend class Multiplication; friend class MultiplicationNode; friend class NaperianLogarithm; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 6d9a9b32751..4ad6b91b38f 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -103,6 +103,8 @@ class ExpressionNode : public TreeNode { MatrixIdentity, MatrixInverse, MatrixTranspose, + MatrixRef, + MatrixRref, PredictionInterval, Matrix, EmptyExpression diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 6d8937e3f3d..a08843758ff 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -79,6 +79,7 @@ class Matrix final : public Expression { 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; @@ -94,10 +95,10 @@ class Matrix final : public Expression { void setNumberOfRows(int rows) { assert(rows >= 0); node()->setNumberOfRows(rows); } void setNumberOfColumns(int columns) { assert(columns >= 0); 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 b895f3151b9..ce2343b1e4f 100644 --- a/poincare/include/poincare/matrix_complex.h +++ b/poincare/include/poincare/matrix_complex.h @@ -46,6 +46,7 @@ class MatrixComplexNode final : public EvaluationNode { std::complex determinant() const override; MatrixComplex inverse() const; MatrixComplex transpose() const; + MatrixComplex ref(bool reduced) const; private: // See comment on Matrix uint16_t m_numberOfRows; @@ -63,6 +64,7 @@ class MatrixComplex final : public Evaluation { 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); } diff --git a/poincare/include/poincare/matrix_ref.h b/poincare/include/poincare/matrix_ref.h new file mode 100644 index 00000000000..a707776cedb --- /dev/null +++ b/poincare/include/poincare/matrix_ref.h @@ -0,0 +1,48 @@ +#ifndef POINCARE_MATRIX_REF_H +#define POINCARE_MATRIX_REF_H + +#include + +namespace Poincare { + +class MatrixRefNode final : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(MatrixRefNode); } + int numberOfChildren() const override; +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "MatrixRef"; + } +#endif + + // Properties + Type type() const override { return Type::MatrixRef; } +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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +}; + +class MatrixRef final : public Expression { +public: + MatrixRef(const MatrixRefNode * n) : Expression(n) {} + static MatrixRef Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("ref", 1, &UntypedBuilderOneChild); + + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); +}; + +} + +#endif diff --git a/poincare/include/poincare/matrix_rref.h b/poincare/include/poincare/matrix_rref.h new file mode 100644 index 00000000000..66d44b25767 --- /dev/null +++ b/poincare/include/poincare/matrix_rref.h @@ -0,0 +1,48 @@ +#ifndef POINCARE_MATRIX_RREF_H +#define POINCARE_MATRIX_RREF_H + +#include + +namespace Poincare { + +class MatrixRrefNode final : public ExpressionNode { +public: + + // TreeNode + size_t size() const override { return sizeof(MatrixRrefNode); } + int numberOfChildren() const override; +#if POINCARE_TREE_LOG + void logNodeName(std::ostream & stream) const override { + stream << "MatrixRref"; + } +#endif + + // Properties + Type type() const override { return Type::MatrixRref; } +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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +}; + +class MatrixRref final : public Expression { +public: + MatrixRref(const MatrixRrefNode * n) : Expression(n) {} + static MatrixRref Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } + + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("rref", 1, &UntypedBuilderOneChild); + + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); +}; + +} + +#endif diff --git a/poincare/include/poincare_nodes.h b/poincare/include/poincare_nodes.h index 33076f227d2..469013a9d46 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 diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index f9b06683280..cfde3b045e4 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -185,7 +185,9 @@ 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::MatrixRef + || e.type() == ExpressionNode::Type::MatrixRref; } bool Expression::IsInfinity(const Expression e, Context * context) { diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index b310b1ef091..c5037f4e20c 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -180,7 +180,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); @@ -231,8 +231,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,7 +258,7 @@ 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 @@ -291,8 +294,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 +336,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()) { @@ -479,7 +515,7 @@ 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 09eaa3ed073..d0fa0b68c4b 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -119,6 +119,22 @@ 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); +} + // MATRIX COMPLEX REFERENCE template diff --git a/poincare/src/matrix_ref.cpp b/poincare/src/matrix_ref.cpp new file mode 100644 index 00000000000..0feb755ca0f --- /dev/null +++ b/poincare/src/matrix_ref.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper MatrixRef::s_functionHelper; + +int MatrixRefNode::numberOfChildren() const { return MatrixRef::s_functionHelper.numberOfChildren(); } + +Expression MatrixRefNode::shallowReduce(ReductionContext reductionContext) { + return MatrixRef(this).shallowReduce(reductionContext); +} + +Layout MatrixRefNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return LayoutHelper::Prefix(MatrixRef(this), floatDisplayMode, numberOfSignificantDigits, MatrixRef::s_functionHelper.name()); +} + +int MatrixRefNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, MatrixRef::s_functionHelper.name()); +} + +template +Evaluation MatrixRefNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation ref; + if (input.type() == EvaluationNode::Type::MatrixComplex) { + ref = static_cast&>(input).ref(false); + } else { + ref = Complex::Undefined(); + } + assert(!ref.isUninitialized()); + return ref; +} + + +Expression MatrixRef::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, false); + if (couldComputeRef) { + replaceWithInPlace(result); + return result; + } + // The matrix could not be transformed properly + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/matrix_rref.cpp b/poincare/src/matrix_rref.cpp new file mode 100644 index 00000000000..d7562aa9e8d --- /dev/null +++ b/poincare/src/matrix_rref.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include + +namespace Poincare { + +constexpr Expression::FunctionHelper MatrixRref::s_functionHelper; + +int MatrixRrefNode::numberOfChildren() const { return MatrixRref::s_functionHelper.numberOfChildren(); } + +Expression MatrixRrefNode::shallowReduce(ReductionContext reductionContext) { + return MatrixRref(this).shallowReduce(reductionContext); +} + +Layout MatrixRrefNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return LayoutHelper::Prefix(MatrixRref(this), floatDisplayMode, numberOfSignificantDigits, MatrixRref::s_functionHelper.name()); +} + +int MatrixRrefNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, MatrixRref::s_functionHelper.name()); +} + +template +Evaluation MatrixRrefNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation rref; + if (input.type() == EvaluationNode::Type::MatrixComplex) { + rref = static_cast&>(input).ref(true); + } else { + rref = Complex::Undefined(); + } + assert(!rref.isUninitialized()); + return rref; +} + + +Expression MatrixRref::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 couldComputeRref = false; + Expression result = static_cast(c).createRef(reductionContext, &couldComputeRref, true); + if (couldComputeRref) { + replaceWithInPlace(result); + return result; + } + // The matrix could not be transformed properly + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 37ff4f523d3..efe0714062b 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -138,9 +138,11 @@ class Parser { &Randint::s_functionHelper, &Random::s_functionHelper, &RealPart::s_functionHelper, + &MatrixRef::s_functionHelper, &DivisionRemainder::s_functionHelper, &NthRoot::s_functionHelper, &Round::s_functionHelper, + &MatrixRref::s_functionHelper, &SignFunction::s_functionHelper, &Sine::s_functionHelper, &HyperbolicSine::s_functionHelper, diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 477565d25fc..f75d3442a2f 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -336,6 +336,8 @@ template MatrixIdentity TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTrace TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTranspose TreeHandle::FixedArityBuilder(const Tuple &); +template MatrixRef TreeHandle::FixedArityBuilder(const Tuple &); +template MatrixRref TreeHandle::FixedArityBuilder(const Tuple &); template Multiplication TreeHandle::NAryBuilder(const Tuple &); template NaperianLogarithm TreeHandle::FixedArityBuilder(const Tuple &); template NormCDF TreeHandle::FixedArityBuilder(const Tuple &); diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index 7f08c06b02d..58f2304a074 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -405,6 +405,25 @@ 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]]"); + /* 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. + * NOTE : It would be better if results for ref matched the one commented + * bellow. */ + assert_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,3,4][0,1,-1.2857142857143,-1.7142857142857][0,0,1,1.2215568862275]]"); + // --> "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.221557]]" + assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,3.3532934131737ᴇ-1][0,1,0,-0.1437125748503][0,0,1,1.2215568862275]]"); + assert_expression_approximates_to("ref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); + // --> "[[1,1.2][0,1][0,0]]" + assert_expression_approximates_to("rref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); + assert_expression_approximates_to("ref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); + assert_expression_approximates_to("rref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); + assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,1.2,1.4][0,1,-0.5][0,0,1]]"); + // --> "[[1,0.9166667,0.8333333][0,1,-0.5][0,0,1]]" + assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); + assert_expression_approximates_to("round(2.3246,3)", "2.325"); assert_expression_approximates_to("round(2.3245,3)", "2.325"); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index abdc9567dae..b9570c996d4 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -63,6 +63,8 @@ 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_not_property("2*3+1", &context, Expression::IsMatrix); } diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index 4ee1a88e1a5..db521129ceb 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -410,6 +410,8 @@ QUIZ_CASE(poincare_parsing_identifiers) { assert_parsed_expression_is("tanh(1)", HyperbolicTangent::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("trace(1)", MatrixTrace::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("transpose(1)", MatrixTranspose::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("ref(1)", MatrixRef::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("rref(1)", MatrixRref::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("√(1)", SquareRoot::Builder(BasedInteger::Builder(1))); assert_text_not_parsable("cos(1,2)"); assert_text_not_parsable("log(1,2,3)"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 0412d55833f..f9bf1c3abd3 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -447,6 +447,8 @@ 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("ref(_s)", "undef"); + assert_parsed_expression_simplify_to("rref(_s)", "undef"); /* Valid expressions */ assert_parsed_expression_simplify_to("-2×_A", "-2×_A"); @@ -927,6 +929,12 @@ 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]]"); + // Expressions with unreduced matrix assert_reduce("confidence(cos(2)/25,3)→a"); // Check that matrices are not permuted in multiplication From f00c135b6913cc720c00e436971ad4e78c72bada Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 29 Jun 2020 15:28:19 +0200 Subject: [PATCH 084/560] [apps/calculation] Add additional outputs for matrix Change-Id: Ia4b285eb0f28eaed838d32a1fdfb785d13664f65 --- apps/calculation/Makefile | 1 + .../expressions_list_controller.cpp | 28 +++-- .../expressions_list_controller.h | 8 +- .../integer_list_controller.cpp | 40 +++---- .../integer_list_controller.h | 6 +- .../matrix_list_controller.cpp | 103 ++++++++++++++++++ .../matrix_list_controller.h | 27 +++++ .../rational_list_controller.cpp | 23 ++-- .../rational_list_controller.h | 5 +- .../unit_list_controller.cpp | 78 ++++++------- .../additional_outputs/unit_list_controller.h | 5 - apps/calculation/calculation.cpp | 3 + apps/calculation/calculation.h | 1 + apps/calculation/history_controller.cpp | 5 +- apps/calculation/history_controller.h | 2 + poincare/include/poincare/based_integer.h | 3 +- poincare/include/poincare/matrix.h | 1 + poincare/src/based_integer.cpp | 9 ++ poincare/src/matrix.cpp | 11 ++ poincare/src/matrix_trace.cpp | 6 +- 20 files changed, 253 insertions(+), 112 deletions(-) create mode 100644 apps/calculation/additional_outputs/matrix_list_controller.cpp create mode 100644 apps/calculation/additional_outputs/matrix_list_controller.h diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 453e46cf0c4..43f8f92c3fd 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 2ea1b3ef3f5..de98173d222 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.cpp +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -11,7 +11,7 @@ ExpressionsListController::ExpressionsListController(EditExpressionController * ListController(editExpressionController), m_cells{} { - for (int i = 0; i < k_maxNumberOfCells; i++) { + for (int i = 0; i < k_maxNumberOfRows; i++) { m_cells[i].setParentResponder(m_listController.selectableTableView()); } } @@ -21,15 +21,17 @@ void ExpressionsListController::didEnterResponderChain(Responder * previousFirst } int ExpressionsListController::reusableCellCount(int type) { - return k_maxNumberOfCells; + return k_maxNumberOfRows; } void ExpressionsListController::viewDidDisappear() { ListController::viewDidDisappear(); - // Reset cell memoization to avoid taking extra space in the pool - for (int i = 0; i < k_maxNumberOfCells; i++) { + // Reset layout and cell memoization to avoid taking extra space in the pool + for (int i = 0; i < k_maxNumberOfRows; i++) { m_cells[i].setLayout(Layout()); + m_layouts[i] = Layout(); } + m_expression = Expression(); } HighlightCell * ExpressionsListController::reusableCell(int index, int type) { @@ -43,24 +45,34 @@ KDCoordinate ExpressionsListController::rowHeight(int j) { } void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + /* Note : To further optimize memoization space in the pool, layout + * serialization could be memoized instead, and layout would be recomputed + * here, when setting cell's layout. */ ExpressionTableCellWithPointer * myCell = static_cast(cell); myCell->setLayout(layoutAtIndex(index)); myCell->setAccessoryMessage(messageAtIndex(index)); myCell->reloadScroll(); } +int ExpressionsListController::numberOfRows() const { + int nbOfRows = 0; + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + if (!m_layouts[i].isUninitialized()) { + nbOfRows++; + } + } + return nbOfRows; +} + void ExpressionsListController::setExpression(Poincare::Expression e) { // Reinitialize memoization - for (int i = 0; i < k_maxNumberOfCells; i++) { + for (int i = 0; i < k_maxNumberOfRows; i++) { m_layouts[i] = Layout(); } m_expression = e; } Poincare::Layout ExpressionsListController::layoutAtIndex(int index) { - if (m_layouts[index].isUninitialized()) { - computeLayoutAtIndex(index); - } assert(!m_layouts[index].isUninitialized()); return m_layouts[index]; } diff --git a/apps/calculation/additional_outputs/expressions_list_controller.h b/apps/calculation/additional_outputs/expressions_list_controller.h index 4d6ade14960..f03947a260b 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.h +++ b/apps/calculation/additional_outputs/expressions_list_controller.h @@ -22,22 +22,22 @@ class ExpressionsListController : public ListController { KDCoordinate rowHeight(int j) override; int typeAtLocation(int i, int j) override { return 0; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; + int numberOfRows() const override; // IllustratedListController void setExpression(Poincare::Expression e) override; protected: - constexpr static int k_maxNumberOfCells = 4; + constexpr static int k_maxNumberOfRows = 5; int textAtIndex(char * buffer, size_t bufferSize, int index) override; Poincare::Expression m_expression; // Memoization of layouts - mutable Poincare::Layout m_layouts[k_maxNumberOfCells]; + mutable Poincare::Layout m_layouts[k_maxNumberOfRows]; private: Poincare::Layout layoutAtIndex(int index); - virtual void computeLayoutAtIndex(int index) = 0; virtual I18n::Message messageAtIndex(int index) = 0; // Cells - ExpressionTableCellWithPointer m_cells[k_maxNumberOfCells]; + ExpressionTableCellWithPointer m_cells[k_maxNumberOfRows]; }; } diff --git a/apps/calculation/additional_outputs/integer_list_controller.cpp b/apps/calculation/additional_outputs/integer_list_controller.cpp index 98deb516d22..634c24f3a96 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 b76f46d4d39..e052632f906 100644 --- a/apps/calculation/additional_outputs/integer_list_controller.h +++ b/apps/calculation/additional_outputs/integer_list_controller.h @@ -10,13 +10,11 @@ class IntegerListController : public ExpressionsListController { 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 00000000000..4eaf9e465ca --- /dev/null +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -0,0 +1,103 @@ +#include "matrix_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +void MatrixListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + assert(!m_expression.isUninitialized()); + static_assert(k_maxNumberOfRows >= k_maxNumberOfOutputRows, "k_maxNumberOfRows must be greater than k_maxNumberOfOutputRows"); + + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Poincare::Preferences::ComplexFormat currentComplexFormat = preferences->complexFormat(); + if (currentComplexFormat == Poincare::Preferences::ComplexFormat::Real) { + /* Temporary change complex format to avoid all additional expressions to be + * "unreal" (with [i] for instance). As additional results are computed from + * the output, which is built taking ComplexFormat into account, there are + * no risks of displaying additional results on an unreal output. */ + preferences->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); + } + + Context * context = App::app()->localContext(); + ExpressionNode::ReductionContext reductionContext( + context, + preferences->complexFormat(), + preferences->angleUnit(), + ExpressionNode::ReductionTarget::SystemForApproximation, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + + // The expression must be reduced to call methods such as determinant or trace + assert(m_expression.type() == ExpressionNode::Type::Matrix); + + bool mIsSquared = (static_cast(m_expression).numberOfRows() == static_cast(m_expression).numberOfColumns()); + size_t index = 0; + size_t messageIndex = 0; + // 1. Matrix determinant if square matrix + if (mIsSquared) { + /* Determinant is reduced so that a null determinant can be detected. + * However, some exceptions remain such as cos(x)^2+sin(x)^2-1 which will + * not be reduced to a rational, but will be null in theory. */ + Expression determinant = Determinant::Builder(m_expression.clone()).reduce(reductionContext); + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(determinant, context, preferences); + // 2. Matrix inverse if invertible matrix + // A squared matrix is invertible if and only if determinant is non null + if (!determinant.isUndefined() && !determinant.isRationalZero()) { + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); + } + } + // 3. Matrix row echelon form + messageIndex = 2; + Expression rowEchelonForm = MatrixRef::Builder(m_expression.clone()); + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(rowEchelonForm, context, preferences); + /* 4. Matrix reduced row echelon form + * it can be computed from row echelon form to save computation time.*/ + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixRref::Builder(rowEchelonForm), context, preferences); + // 5. Matrix trace if square matrix + if (mIsSquared) { + m_indexMessageMap[index] = messageIndex++; + m_layouts[index++] = getLayoutFromExpression(MatrixTrace::Builder(m_expression.clone()), context, preferences); + } + // Reset complex format as before + preferences->setComplexFormat(currentComplexFormat); +} + +Poincare::Layout MatrixListController::getLayoutFromExpression(Expression e, Context * context, Poincare::Preferences * preferences) { + assert(!e.isUninitialized()); + // Simplify or approximate expression + Expression approximateExpression; + Expression simplifiedExpression; + e.simplifyAndApproximate(&simplifiedExpression, &approximateExpression, context, + preferences->complexFormat(), preferences->angleUnit(), + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + // simplify might have been interrupted, in which case we use approximate + if (simplifiedExpression.isUninitialized()) { + assert(!approximateExpression.isUninitialized()); + return Shared::PoincareHelpers::CreateLayout(approximateExpression); + } + return Shared::PoincareHelpers::CreateLayout(simplifiedExpression); +} + +I18n::Message MatrixListController::messageAtIndex(int index) { + // Message index is mapped in setExpression because it depends on the Matrix. + assert(index < k_maxNumberOfOutputRows && index >=0); + I18n::Message messages[k_maxNumberOfOutputRows] = { + I18n::Message::Determinant, + I18n::Message::Inverse, + I18n::Message::RowEchelonForm, + I18n::Message::ReducedRowEchelonForm, + I18n::Message::Trace}; + return messages[m_indexMessageMap[index]]; +} + +} \ No newline at end of file diff --git a/apps/calculation/additional_outputs/matrix_list_controller.h b/apps/calculation/additional_outputs/matrix_list_controller.h new file mode 100644 index 00000000000..b0774e42c6c --- /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 3fc76295441..c55837927e0 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 62d0de5a969..5ceae4e9b32 100644 --- a/apps/calculation/additional_outputs/rational_list_controller.h +++ b/apps/calculation/additional_outputs/rational_list_controller.h @@ -10,10 +10,9 @@ class RationalListController : public ExpressionsListController { 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 45eb58c4b4f..eed7e7bb558 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -15,12 +15,15 @@ namespace Calculation { void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); - // Reinitialize m_memoizedExpressions - for (size_t i = 0; i < k_maxNumberOfCells; i++) { - m_memoizedExpressions[i] = Expression(); + static_assert(k_maxNumberOfRows >= 3, "k_maxNumberOfRows must be greater than 3"); + + Poincare::Expression expressions[k_maxNumberOfRows]; + // Initialize expressions + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + expressions[i] = Expression(); } - size_t numberOfMemoizedExpressions = 0; + size_t numberOfExpressions = 0; // 1. First rows: miscellaneous classic units for some dimensions Expression copy = m_expression.clone(); Expression units; @@ -32,7 +35,7 @@ void UnitListController::setExpression(Poincare::Expression e) { if (Unit::IsSISpeed(units)) { // 1.a. Turn speed into km/h - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Multiplication::Builder( Unit::Kilometer(), @@ -45,7 +48,7 @@ void UnitListController::setExpression(Poincare::Expression e) { requireSimplification = true; // Simplify the conversion } else if (Unit::IsSIVolume(units)) { // 1.b. Turn volume into L - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Unit::Liter() ); @@ -53,14 +56,14 @@ void UnitListController::setExpression(Poincare::Expression e) { canChangeUnitPrefix = true; // Pick best prefix (mL) } else if (Unit::IsSIEnergy(units)) { // 1.c. Turn energy into Wh - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Multiplication::Builder( Unit::Watt(), Unit::Hour() ) ); - m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( + expressions[numberOfExpressions++] = UnitConvert::Builder( m_expression.clone(), Unit::ElectronVolt() ); @@ -69,21 +72,21 @@ void UnitListController::setExpression(Poincare::Expression e) { } else if (Unit::IsSITime(units)) { // Turn time into ? year + ? month + ? day + ? h + ? min + ? s double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); - m_memoizedExpressions[numberOfMemoizedExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit()); + expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit()); } // 1.d. Simplify and tune prefix of all computed expressions size_t currentExpressionIndex = 0; - while (currentExpressionIndex < numberOfMemoizedExpressions) { - assert(!m_memoizedExpressions[currentExpressionIndex].isUninitialized()); + while (currentExpressionIndex < numberOfExpressions) { + assert(!expressions[currentExpressionIndex].isUninitialized()); if (requireSimplification) { - Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); + Shared::PoincareHelpers::Simplify(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); } if (canChangeUnitPrefix) { Expression newUnits; // Reduce to be able to removeUnit - PoincareHelpers::Reduce(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); - m_memoizedExpressions[currentExpressionIndex] = m_memoizedExpressions[currentExpressionIndex].removeUnit(&newUnits); - double value = Shared::PoincareHelpers::ApproximateToScalar(m_memoizedExpressions[currentExpressionIndex], App::app()->localContext()); + PoincareHelpers::Reduce(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); + expressions[currentExpressionIndex] = expressions[currentExpressionIndex].removeUnit(&newUnits); + double value = Shared::PoincareHelpers::ApproximateToScalar(expressions[currentExpressionIndex], App::app()->localContext()); ExpressionNode::ReductionContext reductionContext( App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), @@ -91,36 +94,36 @@ void UnitListController::setExpression(Poincare::Expression e) { ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext); - m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); + expressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); } currentExpressionIndex++; } // 2. IS units only - assert(numberOfMemoizedExpressions < k_maxNumberOfCells - 1); - m_memoizedExpressions[numberOfMemoizedExpressions] = m_expression.clone(); - Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[numberOfMemoizedExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem); - numberOfMemoizedExpressions++; + assert(numberOfExpressions < k_maxNumberOfRows - 1); + expressions[numberOfExpressions] = m_expression.clone(); + Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem); + numberOfExpressions++; // 3. Get rid of duplicates Expression reduceExpression = m_expression.clone(); - // Make m_expression compareable to m_memoizedExpressions (turn BasedInteger into Rational for instance) + // Make m_expression comparable to expressions (turn BasedInteger into Rational for instance) Shared::PoincareHelpers::Simplify(&reduceExpression, App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::None); currentExpressionIndex = 1; - while (currentExpressionIndex < numberOfMemoizedExpressions) { + while (currentExpressionIndex < numberOfExpressions) { bool duplicateFound = false; for (size_t i = 0; i < currentExpressionIndex + 1; i++) { - // Compare the currentExpression to all previous memoized expressions and to m_expression - Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : m_memoizedExpressions[i]; + // Compare the currentExpression to all previous expressions and to m_expression + Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : expressions[i]; assert(!comparedExpression.isUninitialized()); - if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) { - numberOfMemoizedExpressions--; + if (comparedExpression.isIdenticalTo(expressions[currentExpressionIndex])) { + numberOfExpressions--; // Shift next expressions - for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) { - m_memoizedExpressions[j] = m_memoizedExpressions[j+1]; + for (size_t j = currentExpressionIndex; j < numberOfExpressions; j++) { + expressions[j] = expressions[j+1]; } // Remove last expression - m_memoizedExpressions[numberOfMemoizedExpressions] = Expression(); + expressions[numberOfExpressions] = Expression(); // The current expression has been discarded, no need to increment the current index duplicateFound = true; break; @@ -131,21 +134,12 @@ void UnitListController::setExpression(Poincare::Expression e) { currentExpressionIndex++; } } -} - -int UnitListController::numberOfRows() const { - int nbOfRows = 0; - for (size_t i = 0; i < k_maxNumberOfCells; i++) { - if (!m_memoizedExpressions[i].isUninitialized()) { - nbOfRows++; + // Memoize layouts + for (size_t i = 0; i < k_maxNumberOfRows; i++) { + if (!expressions[i].isUninitialized()) { + m_layouts[i] = Shared::PoincareHelpers::CreateLayout(expressions[i]); } } - return nbOfRows; -} - -void UnitListController::computeLayoutAtIndex(int index) { - assert(!m_memoizedExpressions[index].isUninitialized()); - m_layouts[index] = Shared::PoincareHelpers::CreateLayout(m_memoizedExpressions[index]); } I18n::Message UnitListController::messageAtIndex(int index) { diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h index e3fdee036a9..58f6d1e0d90 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.h +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -12,13 +12,8 @@ class UnitListController : public ExpressionsListController { void setExpression(Poincare::Expression e) override; - //ListViewDataSource - int numberOfRows() const override; private: - void computeLayoutAtIndex(int index) override; I18n::Message messageAtIndex(int index) override; - // Memoization of expressions - mutable Poincare::Expression m_memoizedExpressions[k_maxNumberOfCells]; }; } diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 975ed02f33e..c51091768c1 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -291,6 +291,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) { return AdditionalInformationType::Complex; } + if (o.type() == ExpressionNode::Type::Matrix) { + return AdditionalInformationType::Matrix; + } return AdditionalInformationType::None; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 10f4e06621f..be7f87c9bcc 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -41,6 +41,7 @@ friend CalculationStore; Rational, Trigonometry, Unit, + Matrix, Complex }; static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; } diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 16e68704d04..6d51e7862f9 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 021a0e876b3..e289eb5fc43 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 @@ class HistoryController : public ViewController, public ListViewDataSource, publ RationalListController m_rationalController; TrigonometryListController m_trigonometryController; UnitListController m_unitController; + MatrixListController m_matrixController; }; } diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index 566a334a413..a3660e08c0a 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -17,7 +17,7 @@ class BasedIntegerNode final : public NumberNode { size_t size() const override; #if POINCARE_TREE_LOG void logNodeName(std::ostream & stream) const override { - stream << "Based Integer"; + stream << "BasedInteger"; } virtual void logAttributes(std::ostream & stream) const override; #endif @@ -38,6 +38,7 @@ class BasedIntegerNode final : public NumberNode { template T templatedApproximate() const; private: + int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return m_base == Integer::Base::Decimal ? LayoutShape::Integer : LayoutShape::BinaryHexadecimal; } Integer::Base m_base; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index a08843758ff..def7da82090 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -75,6 +75,7 @@ class Matrix final : public Expression { /* Operation on matrix */ int rank(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool inPlace = false); + Expression createTrace(); // Inverse the array in-place. Array has to be given in the form array[row_index][column_index] template static int ArrayInverse(T * array, int numberOfRows, int numberOfColumns); static Matrix CreateIdentity(int dim); diff --git a/poincare/src/based_integer.cpp b/poincare/src/based_integer.cpp index b8783745e85..1c5141bc79b 100644 --- a/poincare/src/based_integer.cpp +++ b/poincare/src/based_integer.cpp @@ -66,6 +66,15 @@ template T BasedIntegerNode::templatedApproximate() const { // Comparison +int BasedIntegerNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { + if (!ascending) { + return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); + } + assert(e->type() == ExpressionNode::Type::BasedInteger); + const BasedIntegerNode * other = static_cast(e); + return Integer::NaturalOrder(integer(), other->integer()); +} + Expression BasedIntegerNode::shallowReduce(ReductionContext reductionContext) { return BasedInteger(this).shallowReduce(); } diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index c5037f4e20c..0c5f37cd7e4 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -146,6 +146,16 @@ int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Pr return rank; } +Expression Matrix::createTrace() { + assert(numberOfRows() == numberOfColumns()); + int n = numberOfRows(); + Addition a = Addition::Builder(); + for (int i = 0; i < n; i++) { + a.addChildAtIndexInPlace(matrixChild(i,i).clone(), i, i); + } + return std::move(a); +} + template int Matrix::ArrayInverse(T * array, int numberOfRows, int numberOfColumns) { if (numberOfRows != numberOfColumns) { @@ -378,6 +388,7 @@ Expression Matrix::createInverse(ExpressionNode::ReductionContext reductionConte } Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext, bool * couldComputeDeterminant, bool inPlace) { + // Determinant must be called on a reduced matrix only. *couldComputeDeterminant = true; Matrix m = inPlace ? *this : clone().convert(); int dim = m.numberOfRows(); diff --git a/poincare/src/matrix_trace.cpp b/poincare/src/matrix_trace.cpp index ab9aefc8f14..f678e3bbee6 100644 --- a/poincare/src/matrix_trace.cpp +++ b/poincare/src/matrix_trace.cpp @@ -48,11 +48,7 @@ Expression MatrixTrace::shallowReduce(ExpressionNode::ReductionContext reduction if (matrixChild0.numberOfRows() != matrixChild0.numberOfColumns()) { return replaceWithUndefinedInPlace(); } - int n = matrixChild0.numberOfRows(); - Addition a = Addition::Builder(); - for (int i = 0; i < n; i++) { - a.addChildAtIndexInPlace(matrixChild0.matrixChild(i,i), i, i); // No need to clone - } + Expression a = matrixChild0.createTrace(); replaceWithInPlace(a); return a.shallowReduce(reductionContext); } From 3bfc0c83d87e819984b0eca45839ad786629a0ae Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 1 Jul 2020 15:42:19 +0200 Subject: [PATCH 085/560] [poincare] Factorize Echelon Form classes Change-Id: I7ec7290a4d94b9bd1224ad4c53be8b4662bd32d5 --- .../matrix_list_controller.cpp | 4 +- poincare/Makefile | 5 +- poincare/include/poincare/expression.h | 5 +- poincare/include/poincare/expression_node.h | 4 +- .../{matrix_ref.h => matrix_echelon_form.h} | 29 ++++----- .../matrix_reduced_row_echelon_form.h | 37 +++++++++++ .../poincare/matrix_row_echelon_form.h | 37 +++++++++++ poincare/include/poincare/matrix_rref.h | 48 --------------- poincare/include/poincare_nodes.h | 4 +- poincare/src/expression.cpp | 4 +- poincare/src/matrix_echelon_form.cpp | 59 ++++++++++++++++++ .../src/matrix_reduced_row_echelon_form.cpp | 9 +++ poincare/src/matrix_ref.cpp | 61 ------------------- poincare/src/matrix_row_echelon_form.cpp | 9 +++ poincare/src/matrix_rref.cpp | 61 ------------------- poincare/src/parsing/parser.h | 4 +- poincare/src/tree_handle.cpp | 4 +- poincare/test/parsing.cpp | 4 +- 18 files changed, 182 insertions(+), 206 deletions(-) rename poincare/include/poincare/{matrix_ref.h => matrix_echelon_form.h} (66%) create mode 100644 poincare/include/poincare/matrix_reduced_row_echelon_form.h create mode 100644 poincare/include/poincare/matrix_row_echelon_form.h delete mode 100644 poincare/include/poincare/matrix_rref.h create mode 100644 poincare/src/matrix_echelon_form.cpp create mode 100644 poincare/src/matrix_reduced_row_echelon_form.cpp delete mode 100644 poincare/src/matrix_ref.cpp create mode 100644 poincare/src/matrix_row_echelon_form.cpp delete mode 100644 poincare/src/matrix_rref.cpp diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 4eaf9e465ca..274c0a12596 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -56,13 +56,13 @@ void MatrixListController::setExpression(Poincare::Expression e) { } // 3. Matrix row echelon form messageIndex = 2; - Expression rowEchelonForm = MatrixRef::Builder(m_expression.clone()); + 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(MatrixRref::Builder(rowEchelonForm), context, preferences); + m_layouts[index++] = getLayoutFromExpression(MatrixReducedRowEchelonForm::Builder(rowEchelonForm), context, preferences); // 5. Matrix trace if square matrix if (mIsSquared) { m_indexMessageMap[index] = messageIndex++; diff --git a/poincare/Makefile b/poincare/Makefile index 4826173985d..71dcd46dba9 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -101,8 +101,9 @@ poincare_src += $(addprefix poincare/src/,\ matrix_inverse.cpp \ matrix_trace.cpp \ matrix_transpose.cpp \ - matrix_ref.cpp \ - matrix_rref.cpp \ + matrix_echelon_form.cpp \ + matrix_row_echelon_form.cpp \ + matrix_reduced_row_echelon_form.cpp \ multiplication.cpp \ n_ary_expression.cpp \ naperian_logarithm.cpp \ diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index b6d16787b46..e5aa5874700 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -63,8 +63,9 @@ class Expression : public TreeHandle { friend class MatrixInverse; friend class MatrixTrace; friend class MatrixTranspose; - friend class MatrixRef; - friend class MatrixRref; + friend class MatrixEchelonForm; + friend class MatrixRowEchelonForm; + friend class MatrixReducedRowEchelonForm; friend class Multiplication; friend class MultiplicationNode; friend class NaperianLogarithm; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 4ad6b91b38f..79c6eaa88f9 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -103,8 +103,8 @@ class ExpressionNode : public TreeNode { MatrixIdentity, MatrixInverse, MatrixTranspose, - MatrixRef, - MatrixRref, + MatrixRowEchelonForm, + MatrixReducedRowEchelonForm, PredictionInterval, Matrix, EmptyExpression diff --git a/poincare/include/poincare/matrix_ref.h b/poincare/include/poincare/matrix_echelon_form.h similarity index 66% rename from poincare/include/poincare/matrix_ref.h rename to poincare/include/poincare/matrix_echelon_form.h index a707776cedb..1a2ff0f7c4f 100644 --- a/poincare/include/poincare/matrix_ref.h +++ b/poincare/include/poincare/matrix_echelon_form.h @@ -1,24 +1,17 @@ -#ifndef POINCARE_MATRIX_REF_H -#define POINCARE_MATRIX_REF_H +#ifndef POINCARE_MATRIX_ECHELON_FORM_H +#define POINCARE_MATRIX_ECHELON_FORM_H #include namespace Poincare { -class MatrixRefNode final : public ExpressionNode { +class MatrixEchelonFormNode : public ExpressionNode { public: // TreeNode - size_t size() const override { return sizeof(MatrixRefNode); } int numberOfChildren() const override; -#if POINCARE_TREE_LOG - void logNodeName(std::ostream & stream) const override { - stream << "MatrixRef"; - } -#endif - - // Properties - Type type() const override { return Type::MatrixRef; } + virtual bool isFormReduced() const = 0; + static constexpr int sNumberOfChildren = 1; private: // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; @@ -31,16 +24,16 @@ class MatrixRefNode final : public ExpressionNode { 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; + + // Properties + virtual const char * functionHelperName() const = 0; }; -class MatrixRef final : public Expression { +class MatrixEchelonForm : public Expression { public: - MatrixRef(const MatrixRefNode * n) : Expression(n) {} - static MatrixRef Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } - - static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("ref", 1, &UntypedBuilderOneChild); - + MatrixEchelonForm(const MatrixEchelonFormNode * n) : Expression(n) {} Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); + bool isFormReduced() const { return static_cast(node())->isFormReduced(); } }; } 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 00000000000..2058f626e1c --- /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 00000000000..baaa809626a --- /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_rref.h b/poincare/include/poincare/matrix_rref.h deleted file mode 100644 index 66d44b25767..00000000000 --- a/poincare/include/poincare/matrix_rref.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef POINCARE_MATRIX_RREF_H -#define POINCARE_MATRIX_RREF_H - -#include - -namespace Poincare { - -class MatrixRrefNode final : public ExpressionNode { -public: - - // TreeNode - size_t size() const override { return sizeof(MatrixRrefNode); } - int numberOfChildren() const override; -#if POINCARE_TREE_LOG - void logNodeName(std::ostream & stream) const override { - stream << "MatrixRref"; - } -#endif - - // Properties - Type type() const override { return Type::MatrixRref; } -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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -}; - -class MatrixRref final : public Expression { -public: - MatrixRref(const MatrixRrefNode * n) : Expression(n) {} - static MatrixRref Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } - - static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("rref", 1, &UntypedBuilderOneChild); - - Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); -}; - -} - -#endif diff --git a/poincare/include/poincare_nodes.h b/poincare/include/poincare_nodes.h index 469013a9d46..41f1d0d9282 100644 --- a/poincare/include/poincare_nodes.h +++ b/poincare/include/poincare_nodes.h @@ -54,8 +54,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index cfde3b045e4..62592f10ce4 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -186,8 +186,8 @@ bool Expression::IsMatrix(const Expression e, Context * context) { || e.type() == ExpressionNode::Type::MatrixInverse || e.type() == ExpressionNode::Type::MatrixIdentity || e.type() == ExpressionNode::Type::MatrixTranspose - || e.type() == ExpressionNode::Type::MatrixRef - || e.type() == ExpressionNode::Type::MatrixRref; + || e.type() == ExpressionNode::Type::MatrixRowEchelonForm + || e.type() == ExpressionNode::Type::MatrixReducedRowEchelonForm; } bool Expression::IsInfinity(const Expression e, Context * context) { diff --git a/poincare/src/matrix_echelon_form.cpp b/poincare/src/matrix_echelon_form.cpp new file mode 100644 index 00000000000..cffcad69593 --- /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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + 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_reduced_row_echelon_form.cpp b/poincare/src/matrix_reduced_row_echelon_form.cpp new file mode 100644 index 00000000000..a06bb98d81e --- /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_ref.cpp b/poincare/src/matrix_ref.cpp deleted file mode 100644 index 0feb755ca0f..00000000000 --- a/poincare/src/matrix_ref.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include -#include - -namespace Poincare { - -constexpr Expression::FunctionHelper MatrixRef::s_functionHelper; - -int MatrixRefNode::numberOfChildren() const { return MatrixRef::s_functionHelper.numberOfChildren(); } - -Expression MatrixRefNode::shallowReduce(ReductionContext reductionContext) { - return MatrixRef(this).shallowReduce(reductionContext); -} - -Layout MatrixRefNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return LayoutHelper::Prefix(MatrixRef(this), floatDisplayMode, numberOfSignificantDigits, MatrixRef::s_functionHelper.name()); -} - -int MatrixRefNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, MatrixRef::s_functionHelper.name()); -} - -template -Evaluation MatrixRefNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation ref; - if (input.type() == EvaluationNode::Type::MatrixComplex) { - ref = static_cast&>(input).ref(false); - } else { - ref = Complex::Undefined(); - } - assert(!ref.isUninitialized()); - return ref; -} - - -Expression MatrixRef::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, false); - if (couldComputeRef) { - replaceWithInPlace(result); - return result; - } - // The matrix could not be transformed properly - return *this; - } - return replaceWithUndefinedInPlace(); -} - -} diff --git a/poincare/src/matrix_row_echelon_form.cpp b/poincare/src/matrix_row_echelon_form.cpp new file mode 100644 index 00000000000..fe8d8ec9d8c --- /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_rref.cpp b/poincare/src/matrix_rref.cpp deleted file mode 100644 index d7562aa9e8d..00000000000 --- a/poincare/src/matrix_rref.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include -#include - -namespace Poincare { - -constexpr Expression::FunctionHelper MatrixRref::s_functionHelper; - -int MatrixRrefNode::numberOfChildren() const { return MatrixRref::s_functionHelper.numberOfChildren(); } - -Expression MatrixRrefNode::shallowReduce(ReductionContext reductionContext) { - return MatrixRref(this).shallowReduce(reductionContext); -} - -Layout MatrixRrefNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return LayoutHelper::Prefix(MatrixRref(this), floatDisplayMode, numberOfSignificantDigits, MatrixRref::s_functionHelper.name()); -} - -int MatrixRrefNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, MatrixRref::s_functionHelper.name()); -} - -template -Evaluation MatrixRrefNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation rref; - if (input.type() == EvaluationNode::Type::MatrixComplex) { - rref = static_cast&>(input).ref(true); - } else { - rref = Complex::Undefined(); - } - assert(!rref.isUninitialized()); - return rref; -} - - -Expression MatrixRref::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 couldComputeRref = false; - Expression result = static_cast(c).createRef(reductionContext, &couldComputeRref, true); - if (couldComputeRref) { - replaceWithInPlace(result); - return result; - } - // The matrix could not be transformed properly - return *this; - } - return replaceWithUndefinedInPlace(); -} - -} diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index efe0714062b..5064d12f7eb 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -138,11 +138,11 @@ class Parser { &Randint::s_functionHelper, &Random::s_functionHelper, &RealPart::s_functionHelper, - &MatrixRef::s_functionHelper, + &MatrixRowEchelonForm::s_functionHelper, &DivisionRemainder::s_functionHelper, &NthRoot::s_functionHelper, &Round::s_functionHelper, - &MatrixRref::s_functionHelper, + &MatrixReducedRowEchelonForm::s_functionHelper, &SignFunction::s_functionHelper, &Sine::s_functionHelper, &HyperbolicSine::s_functionHelper, diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index f75d3442a2f..54d66340179 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -336,8 +336,8 @@ template MatrixIdentity TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTrace TreeHandle::FixedArityBuilder(const Tuple &); template MatrixTranspose TreeHandle::FixedArityBuilder(const Tuple &); -template MatrixRef TreeHandle::FixedArityBuilder(const Tuple &); -template MatrixRref 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 &); diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index db521129ceb..f8bed5cc474 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -410,8 +410,8 @@ QUIZ_CASE(poincare_parsing_identifiers) { assert_parsed_expression_is("tanh(1)", HyperbolicTangent::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("trace(1)", MatrixTrace::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("transpose(1)", MatrixTranspose::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("ref(1)", MatrixRef::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("rref(1)", MatrixRref::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("ref(1)", MatrixRowEchelonForm::Builder(BasedInteger::Builder(1))); + assert_parsed_expression_is("rref(1)", MatrixReducedRowEchelonForm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("√(1)", SquareRoot::Builder(BasedInteger::Builder(1))); assert_text_not_parsable("cos(1,2)"); assert_text_not_parsable("log(1,2,3)"); From c4018d06486de4cca3dd610a5a3bc878603ffbcf Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 2 Jul 2020 14:47:54 +0200 Subject: [PATCH 086/560] [poincare] Update rowCanonize pivot selection for more consistent ref results Change-Id: Id7e856f57ccd3d990077b0f6753089bc6edcc03b --- poincare/src/matrix.cpp | 55 ++++++++++++++++++++++++++------ poincare/test/approximation.cpp | 23 +++++++------ poincare/test/simplification.cpp | 1 + 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 0c5f37cd7e4..cabbb2992a4 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -204,12 +205,35 @@ 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()); + // Handle very low pivots + if (pivot == 0.0f && !matrixChild(iPivot_temp, k).isRationalZero()) { + 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).isRationalZero()) { // No non-null coefficient in this column, skip k++; if (determinant) { @@ -273,12 +297,26 @@ void Matrix::ArrayRowCanonize(T * array, int numberOfRows, int numberOfColumns, 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 @@ -522,7 +560,6 @@ 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); diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index 58f2304a074..2e294d1f62a 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -191,6 +191,9 @@ 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×𝐢,𝐢]]"); + // TODO: get rid of the neglectable real or imaginary parts + assert_expression_approximates_to("(3+4𝐢)/[[3,4][1,𝐢]]", "[[1+5.5511151231258ᴇ-17×𝐢,-2.2204460492503ᴇ-16+4×𝐢][𝐢,-3×𝐢]]"); + // [[1,4×𝐢][𝐢,-3×𝐢]] is expected 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×𝐢"); @@ -409,20 +412,22 @@ QUIZ_CASE(poincare_approximation_function) { * - 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. - * NOTE : It would be better if results for ref matched the one commented - * bellow. */ - assert_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,3,4][0,1,-1.2857142857143,-1.7142857142857][0,0,1,1.2215568862275]]"); - // --> "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.221557]]" + * - (Optional, but sometimes recommended) Leading coefficients must be 1. */ + assert_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.2215568862275]]"); assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,3.3532934131737ᴇ-1][0,1,0,-0.1437125748503][0,0,1,1.2215568862275]]"); - assert_expression_approximates_to("ref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); - // --> "[[1,1.2][0,1][0,0]]" + assert_expression_approximates_to("ref([[1,0][5,6][0,10]])", "[[1,1.2][0,1][0,0]]"); assert_expression_approximates_to("rref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); assert_expression_approximates_to("ref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); assert_expression_approximates_to("rref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); - assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,1.2,1.4][0,1,-0.5][0,0,1]]"); - // --> "[[1,0.9166667,0.8333333][0,1,-0.5][0,0,1]]" + assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,9.1666666666667ᴇ-1,8.3333333333333ᴇ-1][0,1,-0.5][0,0,1]]"); assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); + assert_expression_approximates_to("ref([[3,9][2,5]])", "[[1,3][0,1]]"); + assert_expression_approximates_to("ref([[3,2][5,7]])", "[[1,1.4][0,1]]"); + assert_expression_approximates_to("ref([[3,11][5,7]])", "[[1,1.4][0,1]]"); + assert_expression_approximates_to("ref([[2,5][2,7]])", "[[1,2.5][0,1]]"); + assert_expression_approximates_to("ref([[3,12][-4,1]])", "[[1,-0.25][0,1]]"); + assert_expression_approximates_to("ref([[0,1][1ᴇ-100,1]])", "[[1,1ᴇ100][0,1]]"); + assert_expression_approximates_to("rref([[0,1][1ᴇ-100,1]])", "[[1,0][0,1]]"); assert_expression_approximates_to("round(2.3246,3)", "2.325"); assert_expression_approximates_to("round(2.3245,3)", "2.325"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index f9bf1c3abd3..ebf10432375 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -934,6 +934,7 @@ QUIZ_CASE(poincare_simplification_matrix) { 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]]"); // Expressions with unreduced matrix assert_reduce("confidence(cos(2)/25,3)→a"); From 4331686818912187fe243ff3bc9a308d7e6ef6f3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 7 Jul 2020 18:05:17 +0200 Subject: [PATCH 087/560] [apps/toolbox] Update toolbox and additional result text for matrices Change-Id: I59e1561dcb97c75e57f0c24cfe1953ff594daf92 --- .../additional_outputs/matrix_list_controller.cpp | 10 +++++----- apps/calculation/base.de.i18n | 5 +++++ apps/calculation/base.en.i18n | 5 +++++ apps/calculation/base.es.i18n | 5 +++++ apps/calculation/base.fr.i18n | 5 +++++ apps/calculation/base.it.i18n | 5 +++++ apps/calculation/base.nl.i18n | 5 +++++ apps/calculation/base.pt.i18n | 5 +++++ apps/toolbox.fr.i18n | 4 ++-- 9 files changed, 42 insertions(+), 7 deletions(-) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 274c0a12596..6ab5e6a6e71 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -92,11 +92,11 @@ I18n::Message MatrixListController::messageAtIndex(int index) { // Message index is mapped in setExpression because it depends on the Matrix. assert(index < k_maxNumberOfOutputRows && index >=0); I18n::Message messages[k_maxNumberOfOutputRows] = { - I18n::Message::Determinant, - I18n::Message::Inverse, - I18n::Message::RowEchelonForm, - I18n::Message::ReducedRowEchelonForm, - I18n::Message::Trace}; + 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/base.de.i18n b/apps/calculation/base.de.i18n index 18aab037429..406f27e8499 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 399532fb791..0e54f24cfd4 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 25c3c503587..057481a0dbd 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 ca8e2873924..0e155e29474 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 6544c66227f..6e60f096491 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binario" PrimeFactors = "Fattori primi" 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 0ae59c24aa3..742f8bdabc3 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binaire" PrimeFactors = "Priemfactoren" 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 b8e8717aa44..2f5720e5e61 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -7,3 +7,8 @@ BinaryBase = "Binário" PrimeFactors = "Fatores primos" 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/toolbox.fr.i18n b/apps/toolbox.fr.i18n index fe354064d48..06c358f9466 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -134,8 +134,8 @@ Determinant = "Déterminant de M" Transpose = "Transposée de M" Trace = "Trace de M" Dimension = "Taille de M" -RowEchelonForm = "Forme échelonnée" -ReducedRowEchelonForm = "Forme échelonnée réduite" +RowEchelonForm = "Forme échelonnée de M" +ReducedRowEchelonForm = "Forme échelonnée réduite de M" Sort = "Tri croissant" InvSort = "Tri décroissant" Maximum = "Maximum" From e27c668c401e3f4cd5d638414f8cda36a13bf858 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 20 Jul 2020 17:40:56 +0200 Subject: [PATCH 088/560] [apps,escher,kandinsky,poincare,python] Replace every "grey" with "gray" Change-Id: I60a232849dce90f70e6977b6024d6e9b1ce1b731 --- .../additional_outputs/complex_graph_cell.cpp | 4 +- .../additional_outputs/illustration_cell.cpp | 2 +- .../trigonometry_graph_cell.cpp | 2 +- apps/code/app.cpp | 2 +- apps/code/catalog.de.i18n | 2 +- apps/code/catalog.en.i18n | 2 +- apps/code/catalog.es.i18n | 2 +- apps/code/catalog.fr.i18n | 2 +- apps/code/catalog.it.i18n | 2 +- apps/code/catalog.nl.i18n | 2 +- apps/code/catalog.pt.i18n | 2 +- apps/code/catalog.universal.i18n | 2 +- apps/code/console_line_cell.h | 2 +- apps/code/editor_view.cpp | 2 +- apps/code/python_toolbox.cpp | 6 +- apps/code/script_node_cell.cpp | 4 +- apps/code/variable_box_controller.cpp | 2 +- apps/graph/app.cpp | 2 +- apps/graph/graph/graph_view.cpp | 2 +- apps/graph/list/list_controller.cpp | 4 +- apps/hardware_test/keyboard_view.cpp | 2 +- apps/probability/calculation_cell.cpp | 2 +- apps/probability/calculation_controller.cpp | 2 +- apps/probability/cell.cpp | 8 +-- apps/probability/distribution_controller.h | 2 +- apps/probability/distribution_curve_view.cpp | 2 +- apps/probability/parameters_controller.cpp | 2 +- apps/probability/responder_image_cell.cpp | 2 +- apps/regression/calculation_controller.cpp | 4 +- apps/regression/store_controller.cpp | 2 +- apps/sequence/app.cpp | 2 +- apps/sequence/list/list_controller.cpp | 4 +- apps/settings/sub_menu/about_controller.cpp | 2 +- apps/shared/banner_view.h | 2 +- .../buffer_text_view_with_text_field.cpp | 2 +- apps/shared/button_with_separator.cpp | 14 ++-- apps/shared/curve_view.cpp | 4 +- apps/shared/range_parameter_controller.cpp | 2 +- .../scrollable_multiple_expressions_view.cpp | 4 +- apps/shared/sum_graph_controller.cpp | 8 +-- apps/shared/zoom_parameter_controller.cpp | 4 +- apps/solver/app.cpp | 2 +- apps/statistics/box_view.cpp | 6 +- apps/statistics/calculation_controller.cpp | 2 +- apps/statistics/histogram_view.h | 2 +- apps/statistics/store_controller.cpp | 2 +- escher/include/escher/button_row_controller.h | 2 +- escher/include/escher/palette.h | 12 ++-- escher/include/escher/stack_view_controller.h | 6 +- escher/src/button_row_controller.cpp | 28 ++++---- escher/src/expression_field.cpp | 2 +- .../expression_table_cell_with_expression.cpp | 2 +- .../expression_table_cell_with_pointer.cpp | 2 +- escher/src/gauge_view.cpp | 2 +- escher/src/layout_field.cpp | 16 ++--- ...age_table_cell_with_chevron_and_buffer.cpp | 2 +- ...table_cell_with_chevron_and_expression.cpp | 2 +- ...ge_table_cell_with_chevron_and_message.cpp | 2 +- .../message_table_cell_with_expression.cpp | 2 +- escher/src/modal_view_empty_controller.cpp | 2 +- escher/src/palette.cpp | 12 ++-- escher/src/scroll_view_indicator.cpp | 4 +- escher/src/switch_view.cpp | 2 +- escher/src/table_cell.cpp | 2 +- escher/src/toolbox.cpp | 2 +- kandinsky/fonts/rasterizer.c | 14 ++-- kandinsky/include/kandinsky/font.h | 12 ++-- kandinsky/src/context_text.cpp | 4 +- kandinsky/src/font.cpp | 40 +++++------ poincare/include/poincare/empty_layout.h | 2 +- poincare/include/poincare/layout.h | 6 +- poincare/include/poincare/layout_node.h | 8 +-- poincare/include/poincare/matrix_layout.h | 12 ++-- poincare/src/empty_layout.cpp | 4 +- poincare/src/layout_cursor.cpp | 8 +-- poincare/src/layout_node.cpp | 28 ++++---- poincare/src/matrix_layout.cpp | 68 +++++++++---------- python/port/port.cpp | 12 ++-- 78 files changed, 231 insertions(+), 231 deletions(-) diff --git a/apps/calculation/additional_outputs/complex_graph_cell.cpp b/apps/calculation/additional_outputs/complex_graph_cell.cpp index bc8a687b363..3bd48645dde 100644 --- a/apps/calculation/additional_outputs/complex_graph_cell.cpp +++ b/apps/calculation/additional_outputs/complex_graph_cell.cpp @@ -25,7 +25,7 @@ void ComplexGraphView::drawRect(KDContext * ctx, KDRect rect) const { assert(!std::isnan(real) && !std::isnan(imag) && !std::isinf(real) && !std::isinf(imag)); // Draw the segment from the origin to the dot (real, imag) - drawSegment(ctx, rect, 0.0f, 0.0f, m_complex->real(), m_complex->imag(), Palette::GreyDark, false); + drawSegment(ctx, rect, 0.0f, 0.0f, m_complex->real(), m_complex->imag(), Palette::GrayDark, false); /* Draw the partial ellipse indicating the angle θ * - the ellipse parameters are a = |real|/5 and b = |imag|/5, @@ -58,7 +58,7 @@ void ComplexGraphView::drawRect(KDContext * ctx, KDRect rect) const { float a = parameters.real(); float b = parameters.imag(); return Poincare::Coordinate2D(a*std::cos(t*th), b*std::sin(t*th)); - }, ¶meters, &th, false, Palette::GreyDark, false); + }, ¶meters, &th, false, Palette::GrayDark, false); // Draw dashed segment to indicate real and imaginary drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, real, 0.0f, imag, Palette::Red, 1, 3); diff --git a/apps/calculation/additional_outputs/illustration_cell.cpp b/apps/calculation/additional_outputs/illustration_cell.cpp index bd2471710d2..eb61b037b77 100644 --- a/apps/calculation/additional_outputs/illustration_cell.cpp +++ b/apps/calculation/additional_outputs/illustration_cell.cpp @@ -10,7 +10,7 @@ void IllustrationCell::layoutSubviews(bool force) { } void IllustrationCell::drawRect(KDContext * ctx, KDRect rect) const { - drawBorderOfRect(ctx, bounds(), Palette::GreyBright); + drawBorderOfRect(ctx, bounds(), Palette::GrayBright); } } diff --git a/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp index f28c107f493..903674ce5ed 100644 --- a/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp +++ b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp @@ -20,7 +20,7 @@ void TrigonometryGraphView::drawRect(KDContext * ctx, KDRect rect) const { // Draw the circle drawCurve(ctx, rect, 0.0f, 2.0f*M_PI, M_PI/180.0f, [](float t, void * model, void * context) { return Poincare::Coordinate2D(std::cos(t), std::sin(t)); - }, nullptr, nullptr, true, Palette::GreyDark, false); + }, nullptr, nullptr, true, Palette::GrayDark, false); // Draw dashed segment to indicate sine and cosine drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, c, 0.0f, s, Palette::Red, 1, 3); drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, s, 0.0f, c, Palette::Red, 1, 3); diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 3a54ba988fd..5ed4e204b38 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -80,7 +80,7 @@ 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()) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 465fc8a06f6..71fa1de14fa 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" +PythonColorGray = "Gray 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 e158cda8b27..0b015a9298d 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" +PythonColorGray = "Gray 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 8503f7ed7b3..c474c30d6af 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" PythonColorGreen = "Green color" -PythonColorGrey = "Grey color" +PythonColorGray = "Gray 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 8a263636c91..20ccfe09e0f 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Couleur noire" PythonColorBlue = "Couleur bleue" PythonColorBrown = "Couleur marron" PythonColorGreen = "Couleur verte" -PythonColorGrey = "Couleur grise" +PythonColorGray = "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 893feb52518..ae8b928fac5 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Colore nero" PythonColorBlue = "Colore blu" PythonColorBrown = "Colore marrone" PythonColorGreen = "Colore verde" -PythonColorGrey = "Colore grigio" +PythonColorGray = "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 843d9b3ab86..ae344f98f64 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Zwarte kleur" PythonColorBlue = "Blauwe kleur" PythonColorBrown = "Bruine kleur" PythonColorGreen = "Groene kleur" -PythonColorGrey = "Grijze kleur" +PythonColorGray = "Grijze kleur" PythonColorOrange = "Oranje kleur" PythonColorPink = "Roze kleur" PythonColorPurple = "Paarse kleur" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 7d11881efcd..b5b807535a3 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -31,7 +31,7 @@ PythonColorBlack = "Cor preta" PythonColorBlue = "Cor azul" PythonColorBrown = "Cor castanha" PythonColorGreen = "Cor verde" -PythonColorGrey = "Cor cinzenta" +PythonColorGray = "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 f6910c4ed55..c1c68373b4c 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -33,7 +33,7 @@ PythonCommandColorBlack = "'black'" PythonCommandColorBlue = "'blue'" PythonCommandColorBrown = "'brown'" PythonCommandColorGreen = "'green'" -PythonCommandColorGrey = "'grey'" +PythonCommandColorGray = "'gray'" PythonCommandColorOrange = "'orange'" PythonCommandColorPink = "'pink'" PythonCommandColorPurple = "'purple'" diff --git a/apps/code/console_line_cell.h b/apps/code/console_line_cell.h index 8a740b9137f..fc49f61876f 100644 --- a/apps/code/console_line_cell.h +++ b/apps/code/console_line_cell.h @@ -53,7 +53,7 @@ class ConsoleLineCell : public HighlightCell, public Responder { ConsoleLineView m_consoleLineView; }; static KDColor textColor(ConsoleLine * line) { - return line->isFromCurrentSession() ? KDColorBlack : Palette::GreyDark; + return line->isFromCurrentSession() ? KDColorBlack : Palette::GrayDark; } MessageTextView m_promptView; ScrollableConsoleLineView m_scrollableView; diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 0af73b6139d..86a29ba660e 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -57,7 +57,7 @@ void EditorView::layoutSubviews(bool force) { /* EditorView::GutterView */ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { - KDColor textColor = Palette::BlueishGrey; + KDColor textColor = Palette::BlueishGray; KDColor backgroundColor = KDColor::RGB24(0xE4E6E7); ctx->fillRect(rect, backgroundColor); diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index aecd3bfbcb4..3f8b21d6bf1 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[] = { @@ -332,7 +332,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, 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_node_cell.cpp b/apps/code/script_node_cell.cpp index 6ea5465966a..21c23f756b7 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 @@ -32,7 +32,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons const char * sourceName = m_scriptNode->nodeSourceName(); if (sourceName != nullptr) { KDSize sourceNameSize = k_font->stringSize(sourceName); - ctx->drawString(sourceName, KDPoint(m_frame.width() - sourceNameSize.width(), nodeNameY), k_font, Palette::GreyDark, backgroundColor); + ctx->drawString(sourceName, KDPoint(m_frame.width() - sourceNameSize.width(), nodeNameY), k_font, Palette::GrayDark, backgroundColor); } } diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 9881b213d68..c329fb7f17c 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::BlueishGray); } } diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 464966cbaf0..9845cecc1a8 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -51,7 +51,7 @@ 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), diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 90120e8a424..d455cdb9d5e 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -73,7 +73,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { // To represent the tangent, we draw segment from and to abscissas at the extremity of the drawn rect float minAbscissa = pixelToFloat(Axis::Horizontal, rect.left()); float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right()); - drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GreyVeryDark, false); + drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GrayVeryDark, false); } continue; } diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index 224673d54f6..e211d3cc6cc 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -167,7 +167,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { // Set name and color if the name is not being edited ExpiringPointer function = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); setFunctionNameInTextField(function, titleCell->textField()); - KDColor functionNameColor = function->isActive() ? function->color() : Palette::GreyDark; + KDColor functionNameColor = function->isActive() ? function->color() : Palette::GrayDark; titleCell->setColor(functionNameColor); } } @@ -178,7 +178,7 @@ void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int Shared::FunctionListController::willDisplayExpressionCellAtIndex(cell, j); FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; ExpiringPointer f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); - KDColor textColor = f->isActive() ? KDColorBlack : Palette::GreyDark; + KDColor textColor = f->isActive() ? KDColorBlack : Palette::GrayDark; myCell->setTextColor(textColor); } diff --git a/apps/hardware_test/keyboard_view.cpp b/apps/hardware_test/keyboard_view.cpp index 1b15f28dff7..0b7d69cefe7 100644 --- a/apps/hardware_test/keyboard_view.cpp +++ b/apps/hardware_test/keyboard_view.cpp @@ -59,7 +59,7 @@ void KeyboardView::drawKey(int keyIndex, KDContext * ctx, KDRect rect) const { KDColor KeyboardView::keyColor(Ion::Keyboard::Key key) const { if (!m_keyboardModel.belongsToTestedKeysSubset(key)) { - return Palette::GreyBright; + return Palette::GrayBright; } if (m_keyboardModel.testedKey() == key) { return KDColorBlue; diff --git a/apps/probability/calculation_cell.cpp b/apps/probability/calculation_cell.cpp index e8d35cf215f..ea686f9d9f4 100644 --- a/apps/probability/calculation_cell.cpp +++ b/apps/probability/calculation_cell.cpp @@ -41,7 +41,7 @@ void CalculationCell::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), KDColorWhite); if (m_isResponder) { KDSize textSize = m_text.minimalSizeForOptimalDisplay(); - ctx->strokeRect(KDRect(2*k_margin+textSize.width(), 0, calculationCellWidth()+2*ResponderImageCell::k_outline, ImageCell::k_height+2*ResponderImageCell::k_outline), Palette::GreyMiddle); + ctx->strokeRect(KDRect(2*k_margin+textSize.width(), 0, calculationCellWidth()+2*ResponderImageCell::k_outline, ImageCell::k_height+2*ResponderImageCell::k_outline), Palette::GrayMiddle); } } diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 2869f5abfd0..9ab3ed81261 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -26,7 +26,7 @@ namespace Probability { constexpr int CalculationController::k_titleBufferSize; CalculationController::ContentView::ContentView(SelectableTableView * selectableTableView, Distribution * distribution, Calculation * calculation) : - m_titleView(KDFont::SmallFont, I18n::Message::ComputeProbability, 0.5f, 0.5f, Palette::GreyDark, Palette::WallScreen), + m_titleView(KDFont::SmallFont, I18n::Message::ComputeProbability, 0.5f, 0.5f, Palette::GrayDark, Palette::WallScreen), m_selectableTableView(selectableTableView), m_distributionCurveView(distribution, calculation) { diff --git a/apps/probability/cell.cpp b/apps/probability/cell.cpp index 5e9a80b033c..8083fbb5bab 100644 --- a/apps/probability/cell.cpp +++ b/apps/probability/cell.cpp @@ -59,10 +59,10 @@ void Cell::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate height = bounds().height(); KDColor backgroundColor = isHighlighted() ? Palette::Select : KDColorWhite; ctx->fillRect(KDRect(1, 1, width-2, height-1), backgroundColor); - ctx->fillRect(KDRect(0, 0, width, 1), Palette::GreyBright); - ctx->fillRect(KDRect(0, 1, 1, height-1), Palette::GreyBright); - ctx->fillRect(KDRect(width-1, 1, 1, height-1), Palette::GreyBright); - ctx->fillRect(KDRect(0, height-1, width, 1), Palette::GreyBright); + ctx->fillRect(KDRect(0, 0, width, 1), Palette::GrayBright); + ctx->fillRect(KDRect(0, 1, 1, height-1), Palette::GrayBright); + ctx->fillRect(KDRect(width-1, 1, 1, height-1), Palette::GrayBright); + ctx->fillRect(KDRect(0, height-1, width, 1), Palette::GrayBright); } } diff --git a/apps/probability/distribution_controller.h b/apps/probability/distribution_controller.h index 22a097ff4f0..db276e2492b 100644 --- a/apps/probability/distribution_controller.h +++ b/apps/probability/distribution_controller.h @@ -26,7 +26,7 @@ class DistributionController : public ViewController, public SimpleListViewDataS class ContentView : public View { public: ContentView(SelectableTableView * selectableTableView) : - m_titleView(KDFont::SmallFont, I18n::Message::ChooseDistribution, 0.5f, 0.5f, Palette::GreyDark, Palette::WallScreen), + m_titleView(KDFont::SmallFont, I18n::Message::ChooseDistribution, 0.5f, 0.5f, Palette::GrayDark, Palette::WallScreen), m_selectableTableView(selectableTableView) {} constexpr static KDCoordinate k_titleMargin = 8; diff --git a/apps/probability/distribution_curve_view.cpp b/apps/probability/distribution_curve_view.cpp index fc545fe5934..eb82d5a9cdb 100644 --- a/apps/probability/distribution_curve_view.cpp +++ b/apps/probability/distribution_curve_view.cpp @@ -31,7 +31,7 @@ void DistributionCurveView::drawRect(KDContext * ctx, KDRect rect) const { if (m_distribution->isContinuous()) { drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, EvaluateXYAtAbscissa, m_distribution, nullptr, Palette::YellowDark, true, true, lowerBound, upperBound); } else { - drawHistogram(ctx, rect, EvaluateAtAbscissa, m_distribution, nullptr, 0, 1, false, Palette::GreyMiddle, Palette::YellowDark, lowerBound, upperBound+0.5f); + drawHistogram(ctx, rect, EvaluateAtAbscissa, m_distribution, nullptr, 0, 1, false, Palette::GrayMiddle, Palette::YellowDark, lowerBound, upperBound+0.5f); } } diff --git a/apps/probability/parameters_controller.cpp b/apps/probability/parameters_controller.cpp index 694a3207a48..53e3c981821 100644 --- a/apps/probability/parameters_controller.cpp +++ b/apps/probability/parameters_controller.cpp @@ -8,7 +8,7 @@ namespace Probability { ParametersController::ContentView::ContentView(SelectableTableView * selectableTableView) : m_numberOfParameters(1), - m_titleView(KDFont::SmallFont, I18n::Message::ChooseParameters, 0.5f, 0.5f, Palette::GreyDark, Palette::WallScreen), + m_titleView(KDFont::SmallFont, I18n::Message::ChooseParameters, 0.5f, 0.5f, Palette::GrayDark, Palette::WallScreen), m_firstParameterDefinition(KDFont::SmallFont, (I18n::Message)0, 0.5f, 0.5f, KDColorBlack, Palette::WallScreen), m_secondParameterDefinition(KDFont::SmallFont, (I18n::Message)0, 0.5f, 0.5f, KDColorBlack, Palette::WallScreen), m_selectableTableView(selectableTableView) diff --git a/apps/probability/responder_image_cell.cpp b/apps/probability/responder_image_cell.cpp index 263c9369d63..50bde318feb 100644 --- a/apps/probability/responder_image_cell.cpp +++ b/apps/probability/responder_image_cell.cpp @@ -12,7 +12,7 @@ ResponderImageCell::ResponderImageCell(Responder * parentResponder, Distribution void ResponderImageCell::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), KDColorWhite); - ctx->strokeRect(KDRect(0, 0, ImageCell::k_width+2*k_outline, ImageCell::k_height+2*k_outline), Palette::GreyMiddle); + ctx->strokeRect(KDRect(0, 0, ImageCell::k_width+2*k_outline, ImageCell::k_height+2*k_outline), Palette::GrayMiddle); } KDSize ResponderImageCell::minimalSizeForOptimalDisplay() const { diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index eb262a2efc5..1beae9b8189 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -33,11 +33,11 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_columnTitleCells[i].setParentResponder(&m_selectableTableView); } for (int i = 0; i < k_numberOfDoubleCalculationCells; i++) { - m_doubleCalculationCells[i].setTextColor(Palette::GreyDark); + m_doubleCalculationCells[i].setTextColor(Palette::GrayDark); m_doubleCalculationCells[i].setParentResponder(&m_selectableTableView); } for (int i = 0; i < k_numberOfCalculationCells;i++) { - m_calculationCells[i].setTextColor(Palette::GreyDark); + m_calculationCells[i].setTextColor(Palette::GrayDark); } for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { m_titleCells[i].setMessageFont(KDFont::SmallFont); diff --git a/apps/regression/store_controller.cpp b/apps/regression/store_controller.cpp index cef9c8a72b9..703d08d1fb6 100644 --- a/apps/regression/store_controller.cpp +++ b/apps/regression/store_controller.cpp @@ -38,7 +38,7 @@ void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int bool isValuesColumn = i%Store::k_numberOfColumnsPerSeries == 0; mytitleCell->setSeparatorLeft(isValuesColumn && i > 0); int seriesIndex = i/Store::k_numberOfColumnsPerSeries; - mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GreyDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GreyDark with graph/list_controller and statistics/store_controller + mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GrayDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GrayDark with graph/list_controller and statistics/store_controller char name[] = {isValuesColumn ? 'X' : 'Y', static_cast('1' + seriesIndex), 0}; mytitleCell->setText(name); } diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index 505f0c9fe80..9ee1e34f3b0 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -48,7 +48,7 @@ App::App(Snapshot * snapshot) : FunctionApp(snapshot, &m_inputViewController), m_sequenceContext(AppsContainer::sharedAppsContainer()->globalContext(), snapshot->functionStore()), 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), diff --git a/apps/sequence/list/list_controller.cpp b/apps/sequence/list/list_controller.cpp index f066c4ecb29..a0eedda1da1 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -190,7 +190,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { myCell->setLayout(sequence->secondInitialConditionName()); } // Set the color - KDColor nameColor = sequence->isActive() ? sequence->color() : Palette::GreyDark; + KDColor nameColor = sequence->isActive() ? sequence->color() : Palette::GrayDark; myCell->setColor(nameColor); } @@ -208,7 +208,7 @@ void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int myCell->setLayout(sequence->secondInitialConditionLayout()); } bool active = sequence->isActive(); - KDColor textColor = active ? KDColorBlack : Palette::GreyDark; + KDColor textColor = active ? KDColorBlack : Palette::GrayDark; myCell->setTextColor(textColor); } diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 2f93ebf75fe..790a36382ed 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -11,7 +11,7 @@ AboutController::AboutController(Responder * parentResponder) : for (int i = 0; i < k_totalNumberOfCell; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); m_cells[i].setAccessoryFont(KDFont::SmallFont); - m_cells[i].setAccessoryTextColor(Palette::GreyDark); + m_cells[i].setAccessoryTextColor(Palette::GrayDark); } } diff --git a/apps/shared/banner_view.h b/apps/shared/banner_view.h index 86f00cbea8e..601f0e9c851 100644 --- a/apps/shared/banner_view.h +++ b/apps/shared/banner_view.h @@ -14,7 +14,7 @@ class BannerView : public View { void reload() { layoutSubviews(); } static constexpr const KDFont * Font() { return KDFont::SmallFont; } static constexpr KDColor TextColor() { return KDColorBlack; } - static constexpr KDColor BackgroundColor() { return Palette::GreyMiddle; } + static constexpr KDColor BackgroundColor() { return Palette::GrayMiddle; } private: static constexpr KDCoordinate LineSpacing = 2; int numberOfSubviews() const override = 0; diff --git a/apps/shared/buffer_text_view_with_text_field.cpp b/apps/shared/buffer_text_view_with_text_field.cpp index 2bf40a8731b..30008741d81 100644 --- a/apps/shared/buffer_text_view_with_text_field.cpp +++ b/apps/shared/buffer_text_view_with_text_field.cpp @@ -35,7 +35,7 @@ void BufferTextViewWithTextField::drawRect(KDContext * ctx, KDRect rect) const { // Draw the text field border KDRect borderRect = KDRect(textFieldRect.x()-k_borderWidth, textFieldRect.y()-k_borderWidth, textFieldRect.width()+2*k_borderWidth, textFieldRect.height()+2*k_borderWidth); - ctx->strokeRect(borderRect, Palette::GreyMiddle); + ctx->strokeRect(borderRect, Palette::GrayMiddle); } void BufferTextViewWithTextField::didBecomeFirstResponder() { diff --git a/apps/shared/button_with_separator.cpp b/apps/shared/button_with_separator.cpp index faf1fe013cb..0dabce4249c 100644 --- a/apps/shared/button_with_separator.cpp +++ b/apps/shared/button_with_separator.cpp @@ -8,15 +8,15 @@ ButtonWithSeparator::ButtonWithSeparator(Responder * parentResponder, I18n::Mess void ButtonWithSeparator::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate width = bounds().width(); KDCoordinate height = bounds().height(); - ctx->fillRect(KDRect(0, 0, width, k_lineThickness), Palette::GreyBright); + ctx->fillRect(KDRect(0, 0, width, k_lineThickness), Palette::GrayBright); ctx->fillRect(KDRect(0, k_lineThickness, width, k_margin-k_lineThickness), Palette::WallScreen); // Draw rectangle around cell - ctx->fillRect(KDRect(0, k_margin, width, k_lineThickness), Palette::GreyBright); - ctx->fillRect(KDRect(0, k_margin+k_lineThickness, k_lineThickness, height-k_margin), Palette::GreyBright); - ctx->fillRect(KDRect(width-k_lineThickness, k_lineThickness+k_margin, k_lineThickness, height-k_margin), Palette::GreyBright); - ctx->fillRect(KDRect(0, height-3*k_lineThickness, width, k_lineThickness), Palette::GreyWhite); - ctx->fillRect(KDRect(0, height-2*k_lineThickness, width, k_lineThickness), Palette::GreyBright); - ctx->fillRect(KDRect(k_lineThickness, height-k_lineThickness, width-2*k_lineThickness, k_lineThickness), Palette::GreyMiddle); + ctx->fillRect(KDRect(0, k_margin, width, k_lineThickness), Palette::GrayBright); + ctx->fillRect(KDRect(0, k_margin+k_lineThickness, k_lineThickness, height-k_margin), Palette::GrayBright); + ctx->fillRect(KDRect(width-k_lineThickness, k_lineThickness+k_margin, k_lineThickness, height-k_margin), Palette::GrayBright); + ctx->fillRect(KDRect(0, height-3*k_lineThickness, width, k_lineThickness), Palette::GrayWhite); + ctx->fillRect(KDRect(0, height-2*k_lineThickness, width, k_lineThickness), Palette::GrayBright); + ctx->fillRect(KDRect(k_lineThickness, height-k_lineThickness, width-2*k_lineThickness, k_lineThickness), Palette::GrayMiddle); } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 77003ab4012..dd594964ae5 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -536,8 +536,8 @@ void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float } void CurveView::drawGrid(KDContext * ctx, KDRect rect) const { - KDColor boldColor = Palette::GreyMiddle; - KDColor lightColor = Palette::GreyWhite; + KDColor boldColor = Palette::GrayMiddle; + KDColor lightColor = Palette::GrayWhite; drawGridLines(ctx, rect, Axis::Vertical, m_curveViewRange->xGridUnit(), boldColor, lightColor); drawGridLines(ctx, rect, Axis::Horizontal, m_curveViewRange->yGridUnit(), boldColor, lightColor); } diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index ecfb92b16aa..04225346673 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -57,7 +57,7 @@ void RangeParameterController::willDisplayCellForIndex(HighlightCell * cell, int 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}; myCell->setMessage(labels[index]); - KDColor yColor = m_tempInteractiveRange.yAuto() ? Palette::GreyDark : KDColorBlack; + KDColor yColor = m_tempInteractiveRange.yAuto() ? Palette::GrayDark : KDColorBlack; KDColor colors[k_numberOfTextCell+1] = {KDColorBlack, KDColorBlack, KDColorBlack, yColor, yColor}; myCell->setTextColor(colors[index]); FloatParameterController::willDisplayCellForIndex(cell, index); diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index ed9bb5db766..1ea21525c20 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -8,7 +8,7 @@ namespace Shared { AbstractScrollableMultipleExpressionsView::ContentCell::ContentCell() : m_rightExpressionView(), - m_approximateSign(k_font, k_defaultApproximateMessage, 0.5f, 0.5f, Palette::GreyVeryDark), + m_approximateSign(k_font, k_defaultApproximateMessage, 0.5f, 0.5f, Palette::GrayVeryDark), m_centeredExpressionView(), m_selectedSubviewPosition(SubviewPosition::Center), m_displayCenter(true) @@ -48,7 +48,7 @@ void AbstractScrollableMultipleExpressionsView::ContentCell::setEven(bool even) void AbstractScrollableMultipleExpressionsView::ContentCell::reloadTextColor() { if (displayCenter()) { - m_rightExpressionView.setTextColor(Palette::GreyVeryDark); + m_rightExpressionView.setTextColor(Palette::GrayVeryDark); } else { m_rightExpressionView.setTextColor(KDColorBlack); } diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 5a328291446..02b42800afd 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -155,16 +155,16 @@ void SumGraphController::reloadBannerView() { /* Legend View */ SumGraphController::LegendView::LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol) : - m_sum(0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), - m_legend(k_font, I18n::Message::Default, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), - m_editableZone(controller, m_textBuffer, k_editableZoneBufferSize, TextField::maxBufferSize(), inputEventHandlerDelegate, controller, k_font, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), + 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::GreyMiddle); + ctx->fillRect(bounds(), Palette::GrayMiddle); } KDSize SumGraphController::LegendView::minimalSizeForOptimalDisplay() const { diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index 5f45669fb1f..f468aff72d4 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -86,7 +86,7 @@ ZoomParameterController::ContentView::LegendView::LegendView() for (int i = 0; i < k_numberOfLegends; i++) { m_legends[i].setFont(KDFont::SmallFont); m_legends[i].setMessage(messages[i]); - m_legends[i].setBackgroundColor(Palette::GreyBright); + m_legends[i].setBackgroundColor(Palette::GrayBright); m_legends[i].setAlignment(horizontalAlignments[i], 0.5f); } KeyView::Type tokenTypes[k_numberOfTokens] = {KeyView::Type::Up, KeyView::Type::Down, KeyView::Type::Left, KeyView::Type::Right, KeyView::Type::Plus, KeyView::Type::Minus}; @@ -96,7 +96,7 @@ ZoomParameterController::ContentView::LegendView::LegendView() } void ZoomParameterController::ContentView::LegendView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight), Palette::GreyBright); + ctx->fillRect(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight), Palette::GrayBright); } int ZoomParameterController::ContentView::LegendView::numberOfSubviews() const { diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp index c97ee104857..bce24a51816 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -48,7 +48,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) { diff --git a/apps/statistics/box_view.cpp b/apps/statistics/box_view.cpp index 41496430543..eb7d0088635 100644 --- a/apps/statistics/box_view.cpp +++ b/apps/statistics/box_view.cpp @@ -63,7 +63,7 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { double thirdQuart = m_store->thirdQuartile(m_series); double maxVal = m_store->maxValue(m_series); - KDColor boxColor = isMainViewSelected() ? m_selectedHistogramLightColor : Palette::GreyWhite; + KDColor boxColor = isMainViewSelected() ? m_selectedHistogramLightColor : Palette::GrayWhite; // Draw the main box KDCoordinate firstQuartilePixels = std::round(floatToPixel(Axis::Horizontal, firstQuart)); KDCoordinate thirdQuartilePixels = std::round(floatToPixel(Axis::Horizontal, thirdQuart)); @@ -71,7 +71,7 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { upBoundPixel-lowBoundPixel), boxColor); // Draw the horizontal lines linking the box to the extreme bounds - KDColor horizontalColor = isMainViewSelected() ? m_selectedHistogramColor : Palette::GreyDark; + KDColor horizontalColor = isMainViewSelected() ? m_selectedHistogramColor : Palette::GrayDark; float segmentOrd = (lowBound + upBound)/ 2.0f; drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, segmentOrd, minVal, firstQuart, horizontalColor); drawHorizontalOrVerticalSegment(ctx, rect, Axis::Horizontal, segmentOrd, thirdQuart, maxVal, horizontalColor); @@ -83,7 +83,7 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { * lines. This solution could hide the highlighted line by coloring the next * quantile if it has the same value. */ for (int k = 0; k < 5; k++) { - drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::GreyMiddle, k_quantileBarWidth); + drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::GrayMiddle, k_quantileBarWidth); } if (isMainViewSelected()) { drawHorizontalOrVerticalSegment(ctx, rect, Axis::Vertical, calculations[(int)*m_selectedQuantile], lowBound, upBound, Palette::YellowDark, k_quantileBarWidth); diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index de553cf1b2f..2a0f52e0a05 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -30,7 +30,7 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_calculationTitleCells[i].setMessageFont(KDFont::SmallFont); } for (int i = 0; i < k_numberOfCalculationCells; i++) { - m_calculationCells[i].setTextColor(Palette::GreyDark); + m_calculationCells[i].setTextColor(Palette::GrayDark); } m_hideableCell.setHide(true); } diff --git a/apps/statistics/histogram_view.h b/apps/statistics/histogram_view.h index f72cbac1a22..a2b050c4647 100644 --- a/apps/statistics/histogram_view.h +++ b/apps/statistics/histogram_view.h @@ -13,7 +13,7 @@ class HistogramController; class HistogramView : public Shared::HorizontallyLabeledCurveView { public: - HistogramView(HistogramController * controller, Store * store, int series, Shared::BannerView * bannerView, KDColor selectedHistogramColor = Palette::Select, KDColor notSelectedHistogramColor = Palette::GreyMiddle, KDColor selectedBarColor = Palette::YellowDark); + HistogramView(HistogramController * controller, Store * store, int series, Shared::BannerView * bannerView, KDColor selectedHistogramColor = Palette::Select, KDColor notSelectedHistogramColor = Palette::GrayMiddle, KDColor selectedBarColor = Palette::YellowDark); int series() const { return m_series; } void reload() override; void reloadSelectedBar(); diff --git a/apps/statistics/store_controller.cpp b/apps/statistics/store_controller.cpp index 7c87285a5a6..ad7ae5a49a4 100644 --- a/apps/statistics/store_controller.cpp +++ b/apps/statistics/store_controller.cpp @@ -49,7 +49,7 @@ void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int I18n::Message sizesMessages[] = {I18n::Message::Sizes1, I18n::Message::Sizes2, I18n::Message::Sizes3}; mytitleCell->setText(I18n::translate(sizesMessages[seriesIndex])); } - mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GreyDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GreyDark with graph/list_controller + mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GrayDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GrayDark with graph/list_controller } HighlightCell * StoreController::titleCells(int index) { diff --git a/escher/include/escher/button_row_controller.h b/escher/include/escher/button_row_controller.h index 1206033396e..9f0d75f80fe 100644 --- a/escher/include/escher/button_row_controller.h +++ b/escher/include/escher/button_row_controller.h @@ -18,7 +18,7 @@ class ButtonRowController : public ViewController { }; enum class Style { PlainWhite, - EmbossedGrey + EmbossedGray }; enum class Size { Small, diff --git a/escher/include/escher/palette.h b/escher/include/escher/palette.h index c50b276b774..7923409f8b3 100644 --- a/escher/include/escher/palette.h +++ b/escher/include/escher/palette.h @@ -10,11 +10,11 @@ class Palette { constexpr static KDColor YellowLight = KDColor::RGB24(0xffcc7b); constexpr static KDColor PurpleBright = KDColor::RGB24(0x656975); constexpr static KDColor PurpleDark = KDColor::RGB24(0x414147); - constexpr static KDColor GreyWhite = KDColor::RGB24(0xf5f5f5); - constexpr static KDColor GreyBright = KDColor::RGB24(0xececec); - constexpr static KDColor GreyMiddle = KDColor::RGB24(0xd9d9d9); - constexpr static KDColor GreyDark = KDColor::RGB24(0xa7a7a7); - constexpr static KDColor GreyVeryDark = KDColor::RGB24(0x8c8c8c); + constexpr static KDColor GrayWhite = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor GrayBright = KDColor::RGB24(0xececec); + constexpr static KDColor GrayMiddle = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor GrayDark = KDColor::RGB24(0xa7a7a7); + constexpr static KDColor GrayVeryDark = KDColor::RGB24(0x8c8c8c); constexpr static KDColor Select = KDColor::RGB24(0xd4d7e0); constexpr static KDColor SelectDark = KDColor::RGB24(0xb0b8d8); constexpr static KDColor WallScreen = KDColor::RGB24(0xf7f9fa); @@ -33,7 +33,7 @@ class Palette { constexpr static KDColor GreenLight = KDColor::RGB24(0x52db8f); constexpr static KDColor Brown = KDColor::RGB24(0x8d7350); constexpr static KDColor Purple = KDColor::RGB24(0x6e2d79); - constexpr static KDColor BlueishGrey = KDColor::RGB24(0x919ea4); + constexpr static KDColor BlueishGray = KDColor::RGB24(0x919ea4); constexpr static KDColor Cyan = KDColor::RGB24(0x00ffff); constexpr static KDColor DataColor[] = {Red, Blue, Green, YellowDark, Magenta, Turquoise, Pink, Orange}; constexpr static KDColor DataColorLight[] = {RedLight, BlueLight, GreenLight, YellowLight}; diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index 48bf9a114e3..d13d4fb9d3a 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -10,10 +10,10 @@ constexpr uint8_t kMaxNumberOfStacks = 4; class StackViewController : public ViewController { public: StackViewController(Responder * parentResponder, ViewController * rootViewController, - KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GreyBright); + KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GrayBright); /* Push creates a new StackView and adds it */ - void push(ViewController * vc, KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GreyBright); + void push(ViewController * vc, KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GrayBright); void pop(); int depth() const { return m_numberOfChildren; } @@ -28,7 +28,7 @@ class StackViewController : public ViewController { private: class Frame { public: - Frame(ViewController * viewController = nullptr, KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GreyBright) : + Frame(ViewController * viewController = nullptr, KDColor textColor = Palette::SubTab, KDColor backgroundColor = KDColorWhite, KDColor separatorColor = Palette::GrayBright) : m_viewController(viewController), m_textColor(textColor), m_backgroundColor(backgroundColor), diff --git a/escher/src/button_row_controller.cpp b/escher/src/button_row_controller.cpp index 4a063376425..95471ee0da9 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); @@ -96,28 +96,28 @@ void ButtonRowController::ContentView::layoutSubviews(bool force) { void ButtonRowController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { if (numberOfButtons() == 0) { if (m_position == Position::Top) { - ctx->fillRect(KDRect(0, 0, bounds().width(), 1), Palette::GreyWhite); + ctx->fillRect(KDRect(0, 0, bounds().width(), 1), Palette::GrayWhite); } return; } if (m_style == Style::PlainWhite) { if (m_position == Position::Top) { ctx->fillRect(KDRect(0, 0, bounds().width(), k_plainStyleHeight), KDColorWhite); - ctx->fillRect(KDRect(0, k_plainStyleHeight, bounds().width(), 1), Palette::GreyWhite); + ctx->fillRect(KDRect(0, k_plainStyleHeight, bounds().width(), 1), Palette::GrayWhite); } else { ctx->fillRect(KDRect(0, bounds().height() - k_plainStyleHeight, bounds().width(), k_plainStyleHeight), KDColorWhite); - ctx->fillRect(KDRect(0, bounds().height() - k_plainStyleHeight-1, bounds().width(), 1), Palette::GreyWhite); + ctx->fillRect(KDRect(0, bounds().height() - k_plainStyleHeight-1, bounds().width(), 1), Palette::GrayWhite); } return; } int buttonHeight = m_size == Size::Small ? k_embossedStyleHeightSmall : k_embossedStyleHeightLarge; int buttonMargin = m_size == Size::Small ? k_embossedStyleHeightMarginSmall : k_embossedStyleHeightMarginLarge; if (m_position == Position::Top) { - ctx->fillRect(KDRect(0, 0, bounds().width(), buttonHeight), Palette::GreyWhite); - ctx->fillRect(KDRect(0, buttonHeight, bounds().width(), 1), Palette::GreyMiddle); + ctx->fillRect(KDRect(0, 0, bounds().width(), buttonHeight), Palette::GrayWhite); + ctx->fillRect(KDRect(0, buttonHeight, bounds().width(), 1), Palette::GrayMiddle); } else { - ctx->fillRect(KDRect(0, bounds().height() - buttonHeight, bounds().width(), buttonHeight), Palette::GreyWhite); - ctx->fillRect(KDRect(0, bounds().height() - buttonHeight-1, bounds().width(), 1), Palette::GreyMiddle); + ctx->fillRect(KDRect(0, bounds().height() - buttonHeight, bounds().width(), buttonHeight), Palette::GrayWhite); + ctx->fillRect(KDRect(0, bounds().height() - buttonHeight-1, bounds().width(), 1), Palette::GrayMiddle); } KDCoordinate y0 = m_position == Position::Top ? buttonMargin-1 : bounds().height()-buttonHeight+buttonMargin-1; KDCoordinate y1 = m_position == Position::Top ? buttonHeight-buttonMargin-2 : bounds().height()-buttonMargin; @@ -132,12 +132,12 @@ void ButtonRowController::ContentView::drawRect(KDContext * ctx, KDRect rect) co for (int i = 0; i < numberOfButtons(); i++) { Button * button = buttonAtIndex(i); KDCoordinate buttonWidth = button->minimalSizeForOptimalDisplay().width(); - ctx->fillRect(KDRect(currentXOrigin, y0, 1, y1-y0+1), Palette::GreyMiddle); - ctx->fillRect(KDRect(currentXOrigin-1, y0, 1, y1-y0+2), Palette::GreyDark); - ctx->fillRect(KDRect(currentXOrigin, y0, buttonWidth+2, 1), Palette::GreyMiddle); - ctx->fillRect(KDRect(currentXOrigin, y1, buttonWidth+2, 1), Palette::GreyMiddle); - ctx->fillRect(KDRect(currentXOrigin, y1+1, buttonWidth+2, 1), Palette::GreyDark); - ctx->fillRect(KDRect(currentXOrigin+1+buttonWidth, y0, 1, y1-y0+1), Palette::GreyMiddle); + ctx->fillRect(KDRect(currentXOrigin, y0, 1, y1-y0+1), Palette::GrayMiddle); + ctx->fillRect(KDRect(currentXOrigin-1, y0, 1, y1-y0+2), Palette::GrayDark); + ctx->fillRect(KDRect(currentXOrigin, y0, buttonWidth+2, 1), Palette::GrayMiddle); + ctx->fillRect(KDRect(currentXOrigin, y1, buttonWidth+2, 1), Palette::GrayMiddle); + ctx->fillRect(KDRect(currentXOrigin, y1+1, buttonWidth+2, 1), Palette::GrayDark); + ctx->fillRect(KDRect(currentXOrigin+1+buttonWidth, y0, 1, y1-y0+1), Palette::GrayMiddle); currentXOrigin += buttonWidth + widthMargin; } } diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp index ad75892c7ee..bd23d662e28 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -77,7 +77,7 @@ void ExpressionField::layoutSubviews(bool force) { void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const { // Draw the separator - ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::GreyMiddle); + ctx->fillRect(KDRect(0, 0, bounds().width(), k_separatorThickness), Palette::GrayMiddle); } bool ExpressionField::handleEvent(Ion::Events::Event event) { diff --git a/escher/src/expression_table_cell_with_expression.cpp b/escher/src/expression_table_cell_with_expression.cpp index 8e5e8b196ea..3d5ca3ff229 100644 --- a/escher/src/expression_table_cell_with_expression.cpp +++ b/escher/src/expression_table_cell_with_expression.cpp @@ -5,7 +5,7 @@ ExpressionTableCellWithExpression::ExpressionTableCellWithExpression(Responder * parentResponder) : ExpressionTableCell(parentResponder, Layout::HorizontalLeftOverlap), - m_accessoryExpressionView(this, k_horizontalMargin, 0, 1.0f, 0.5f, Palette::GreyDark, KDColorWhite) + m_accessoryExpressionView(this, k_horizontalMargin, 0, 1.0f, 0.5f, Palette::GrayDark, KDColorWhite) {} View * ExpressionTableCellWithExpression::accessoryView() const { diff --git a/escher/src/expression_table_cell_with_pointer.cpp b/escher/src/expression_table_cell_with_pointer.cpp index 2f51962e088..b0d1d3a793f 100644 --- a/escher/src/expression_table_cell_with_pointer.cpp +++ b/escher/src/expression_table_cell_with_pointer.cpp @@ -4,7 +4,7 @@ ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(Responder * parentResponder, I18n::Message accessoryMessage, Layout layout) : ExpressionTableCell(parentResponder, layout), - m_accessoryView(KDFont::SmallFont, accessoryMessage, 0.0f, 0.5f, Palette::GreyDark, KDColorWhite) + m_accessoryView(KDFont::SmallFont, accessoryMessage, 0.0f, 0.5f, Palette::GrayDark, KDColorWhite) { if (layout != Layout::Vertical) { m_accessoryView.setAlignment(1.0f, 0.5f); diff --git a/escher/src/gauge_view.cpp b/escher/src/gauge_view.cpp index 71c0dc05d18..4f06bbd32b0 100644 --- a/escher/src/gauge_view.cpp +++ b/escher/src/gauge_view.cpp @@ -48,7 +48,7 @@ void GaugeView::drawRect(KDContext * ctx, KDRect rect) const { KDColor gaugeIndicatorWorkingBuffer[GaugeView::k_indicatorDiameter*GaugeView::k_indicatorDiameter]; ctx->fillRect(KDRect(k_indicatorDiameter/2, (height-k_thickness)/2, width*m_level, k_thickness), Palette::YellowDark); - ctx->fillRect(KDRect(k_indicatorDiameter/2+width*m_level, (height-k_thickness)/2, width*(1.0f-m_level), k_thickness), Palette::GreyDark); + ctx->fillRect(KDRect(k_indicatorDiameter/2+width*m_level, (height-k_thickness)/2, width*(1.0f-m_level), k_thickness), Palette::GrayDark); KDRect frame(width*m_level, (height-k_indicatorDiameter)/2, k_indicatorDiameter, k_indicatorDiameter); ctx->blendRectWithMask(frame, Palette::YellowDark, (const uint8_t *)gaugeIndicatorMask, gaugeIndicatorWorkingBuffer); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 4ee8e166f8d..26f46006118 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; } @@ -335,7 +335,7 @@ 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)); } @@ -448,11 +448,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, &shouldScrollAndRedraw)) { if (!shouldScrollAndRedraw) { @@ -751,7 +751,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/message_table_cell_with_chevron_and_buffer.cpp b/escher/src/message_table_cell_with_chevron_and_buffer.cpp index 507ac5e8218..ea1f87d1c5b 100644 --- a/escher/src/message_table_cell_with_chevron_and_buffer.cpp +++ b/escher/src/message_table_cell_with_chevron_and_buffer.cpp @@ -3,7 +3,7 @@ MessageTableCellWithChevronAndBuffer::MessageTableCellWithChevronAndBuffer(const KDFont * labelFont, const KDFont * subAccessoryFont) : MessageTableCellWithChevron((I18n::Message)0, labelFont), - m_subAccessoryView(subAccessoryFont, 1.0f, 0.5f, Palette::GreyDark) + m_subAccessoryView(subAccessoryFont, 1.0f, 0.5f, Palette::GrayDark) { } diff --git a/escher/src/message_table_cell_with_chevron_and_expression.cpp b/escher/src/message_table_cell_with_chevron_and_expression.cpp index 01af387ddf3..9fd58e52de8 100644 --- a/escher/src/message_table_cell_with_chevron_and_expression.cpp +++ b/escher/src/message_table_cell_with_chevron_and_expression.cpp @@ -4,7 +4,7 @@ MessageTableCellWithChevronAndExpression::MessageTableCellWithChevronAndExpression(I18n::Message message, const KDFont * font) : MessageTableCellWithChevron(message, font), - m_subtitleView(1.0f, 0.5f, Palette::GreyDark) + m_subtitleView(1.0f, 0.5f, Palette::GrayDark) { m_subtitleView.setHorizontalMargin(Metric::ExpressionViewHorizontalMargin); } diff --git a/escher/src/message_table_cell_with_chevron_and_message.cpp b/escher/src/message_table_cell_with_chevron_and_message.cpp index ff4d8a9523f..cbca095dfb3 100644 --- a/escher/src/message_table_cell_with_chevron_and_message.cpp +++ b/escher/src/message_table_cell_with_chevron_and_message.cpp @@ -3,7 +3,7 @@ MessageTableCellWithChevronAndMessage::MessageTableCellWithChevronAndMessage(const KDFont * labelFont, const KDFont * contentFont) : MessageTableCellWithChevron((I18n::Message)0, labelFont), - m_subtitleView(contentFont, (I18n::Message)0, 1.0f, 0.5f, Palette::GreyDark) + m_subtitleView(contentFont, (I18n::Message)0, 1.0f, 0.5f, Palette::GrayDark) { } diff --git a/escher/src/message_table_cell_with_expression.cpp b/escher/src/message_table_cell_with_expression.cpp index 144fa5c812a..f36244177bb 100644 --- a/escher/src/message_table_cell_with_expression.cpp +++ b/escher/src/message_table_cell_with_expression.cpp @@ -3,7 +3,7 @@ MessageTableCellWithExpression::MessageTableCellWithExpression(I18n::Message message, const KDFont * font) : MessageTableCell(message, font), - m_subtitleView(1.0f, 0.5f, Palette::GreyDark) + m_subtitleView(1.0f, 0.5f, Palette::GrayDark) { } diff --git a/escher/src/modal_view_empty_controller.cpp b/escher/src/modal_view_empty_controller.cpp index 23f370a6c12..99757cfda28 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/palette.cpp b/escher/src/palette.cpp index ac7cd727930..6e5e611d722 100644 --- a/escher/src/palette.cpp +++ b/escher/src/palette.cpp @@ -5,11 +5,11 @@ constexpr KDColor Palette::YellowDark; constexpr KDColor Palette::YellowLight; constexpr KDColor Palette::PurpleBright; constexpr KDColor Palette::PurpleDark; -constexpr KDColor Palette::GreyWhite; -constexpr KDColor Palette::GreyBright; -constexpr KDColor Palette::GreyMiddle; -constexpr KDColor Palette::GreyDark; -constexpr KDColor Palette::GreyVeryDark; +constexpr KDColor Palette::GrayWhite; +constexpr KDColor Palette::GrayBright; +constexpr KDColor Palette::GrayMiddle; +constexpr KDColor Palette::GrayDark; +constexpr KDColor Palette::GrayVeryDark; constexpr KDColor Palette::Select; constexpr KDColor Palette::SelectDark; constexpr KDColor Palette::WallScreen; @@ -29,7 +29,7 @@ constexpr KDColor Palette::GreenLight; constexpr KDColor Palette::Brown; constexpr KDColor Palette::Purple; constexpr KDColor Palette::Cyan; -constexpr KDColor Palette::BlueishGrey; +constexpr KDColor Palette::BlueishGray; constexpr KDColor Palette::DataColor[]; constexpr KDColor Palette::DataColorLight[]; diff --git a/escher/src/scroll_view_indicator.cpp b/escher/src/scroll_view_indicator.cpp index 536e54d3302..3a4ec4bb3ae 100644 --- a/escher/src/scroll_view_indicator.cpp +++ b/escher/src/scroll_view_indicator.cpp @@ -8,7 +8,7 @@ extern "C" { ScrollViewIndicator::ScrollViewIndicator() : View(), - m_color(Palette::GreyDark), + m_color(Palette::GrayDark), m_margin(Metric::CommonTopMargin) { } @@ -17,7 +17,7 @@ ScrollViewBar::ScrollViewBar() : ScrollViewIndicator(), m_offset(0), m_visibleLength(0), - m_trackColor(Palette::GreyMiddle) + m_trackColor(Palette::GrayMiddle) { } diff --git a/escher/src/switch_view.cpp b/escher/src/switch_view.cpp index b06d51620b5..e336d30cf15 100644 --- a/escher/src/switch_view.cpp +++ b/escher/src/switch_view.cpp @@ -55,7 +55,7 @@ void SwitchView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate switchHalfHeight = k_switchHeight/2; KDColor switchWorkingBuffer[SwitchView::k_switchWidth*SwitchView::k_switchHeight]; - KDColor mainColor = m_state ? Palette::YellowDark : Palette::GreyDark; + KDColor mainColor = m_state ? Palette::YellowDark : Palette::GrayDark; 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); diff --git a/escher/src/table_cell.cpp b/escher/src/table_cell.cpp index 0740a5630a2..1bf94944860 100644 --- a/escher/src/table_cell.cpp +++ b/escher/src/table_cell.cpp @@ -166,5 +166,5 @@ void TableCell::layoutSubviews(bool force) { void TableCell::drawRect(KDContext * ctx, KDRect rect) const { KDColor backColor = isHighlighted() ? Palette::Select : backgroundColor(); drawInnerRect(ctx, bounds(), backColor); - drawBorderOfRect(ctx, bounds(), Palette::GreyBright); + drawBorderOfRect(ctx, bounds(), Palette::GrayBright); } diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 0066dd4bf2c..e4848e8bd28 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -30,7 +30,7 @@ void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; myCell->setMessage(messageTree->label()); myCell->setAccessoryMessage(messageTree->text()); - myCell->setAccessoryTextColor(Palette::GreyDark); + myCell->setAccessoryTextColor(Palette::GrayDark); return; } MessageTableCell * myCell = (MessageTableCell *)cell; diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 967c5457e35..5c953f2de36 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 68905120bcb..a405370f326 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -42,10 +42,10 @@ class KDFont { 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 +62,8 @@ class KDFont { static constexpr GlyphIndex IndexForReplacementCharacterCodePoint = 133; 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 +76,7 @@ class KDFont { 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 2295274a1d4..8f36b6247e3 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -35,10 +35,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 abc44e7cf41..ce4fb81fd55 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -33,51 +33,51 @@ KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const { 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); } } } diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index 8edb9c538f4..c5527069661 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/layout.h b/poincare/include/poincare/layout.h index d2603139a01..96d49ec58f4 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -60,9 +60,9 @@ class Layout : public TreeHandle { // 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_node.h b/poincare/include/poincare/layout_node.h index afad9aae1dc..a6a0f670fbb 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -103,9 +103,9 @@ class LayoutNode : public TreeNode { // 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; } @@ -173,7 +173,7 @@ class LayoutNode : public TreeNode { 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/matrix_layout.h b/poincare/include/poincare/matrix_layout.h index 6cded4ceed1..31757b20671 100644 --- a/poincare/include/poincare/matrix_layout.h +++ b/poincare/include/poincare/matrix_layout.h @@ -19,8 +19,8 @@ class MatrixLayoutNode final : public GridLayoutNode { 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 @@ class MatrixLayoutNode final : public GridLayoutNode { 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 @@ class MatrixLayout /*final*/ : public GridLayout { 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/src/empty_layout.cpp b/poincare/src/empty_layout.cpp index bb95828562d..e937767c897 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(); @@ -88,7 +88,7 @@ bool EmptyLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling void EmptyLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart, Layout * selectionEnd, KDColor selectionColor) { if (m_isVisible) { - KDColor fillColor = m_color == Color::Yellow ? Palette::YellowDark : Palette::GreyBright; + KDColor fillColor = m_color == Color::Yellow ? Palette::YellowDark : Palette::GrayBright; ctx->fillRect(KDRect(p.x()+(m_margins ? k_marginWidth : 0), p.y()+(m_margins ? k_marginHeight : 0), width(), height()), fillColor); ctx->fillRect(KDRect(p.x()+(m_margins ? k_marginWidth : 0), p.y()+(m_margins ? k_marginHeight : 0), width(), height()), fillColor); } diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index a8998f6d4a7..d77e152c397 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -150,9 +150,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; @@ -432,7 +432,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 6058df3f2be..84b00149693 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/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index bb1b05a0177..e946fe98a8c 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; @@ -149,7 +149,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 +183,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 +192,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 +214,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 +234,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 +256,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 +291,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)); diff --git a/python/port/port.cpp b/python/port/port.cpp index 1a4eb61dff9..9a73bd4b903 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -201,10 +201,10 @@ 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("cyan", Palette::Cyan), NamedColor("magenta", Palette::Magenta) - }; +}; for (NamedColor p : pairs) { if (strcmp(p.name(), color) == 0) { return p.color(); @@ -220,12 +220,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 From 641186d89cb560cf637c8947d778843a01a53a83 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 20 Jul 2020 17:45:09 +0200 Subject: [PATCH 089/560] [python] Accept "grey" color Change-Id: Ia9b70eb3b4205ae9571eb4269410c30cfbdd1214 --- python/port/port.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/port/port.cpp b/python/port/port.cpp index 9a73bd4b903..49ecf055c07 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -202,9 +202,10 @@ KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ NamedColor("orange", Palette::Orange), NamedColor("purple", Palette::Purple), NamedColor("gray", Palette::GrayDark), + NamedColor("grey", Palette::GrayDark), NamedColor("cyan", Palette::Cyan), NamedColor("magenta", Palette::Magenta) -}; + }; for (NamedColor p : pairs) { if (strcmp(p.name(), color) == 0) { return p.color(); From 1f9d0b5810e3ef1d579615035091e7cfa0e9e4f1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 20 Jul 2020 18:01:39 +0200 Subject: [PATCH 090/560] [apps/code] Reorder rows due to color name change Change-Id: I1c8f3e9d015cf55ec8fc38082f60e957b19b196d --- apps/code/catalog.de.i18n | 2 +- apps/code/catalog.en.i18n | 2 +- apps/code/catalog.es.i18n | 2 +- apps/code/catalog.fr.i18n | 2 +- apps/code/catalog.it.i18n | 2 +- apps/code/catalog.nl.i18n | 2 +- apps/code/catalog.pt.i18n | 2 +- apps/code/catalog.universal.i18n | 2 +- apps/code/python_toolbox.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 71fa1de14fa..ab998c81bc6 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" -PythonColorGreen = "Green color" PythonColorGray = "Gray color" +PythonColorGreen = "Green 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 0b015a9298d..6926b3ed215 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -30,8 +30,8 @@ PythonColor = "Define a rgb color" PythonColorBlack = "Black color" PythonColorBlue = "Blue color" PythonColorBrown = "Brown color" -PythonColorGreen = "Green color" PythonColorGray = "Gray color" +PythonColorGreen = "Green 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 c474c30d6af..eaffc09a7b9 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" -PythonColorGreen = "Green color" PythonColorGray = "Gray color" +PythonColorGreen = "Green 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 20ccfe09e0f..f98473ed823 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" -PythonColorGreen = "Couleur verte" PythonColorGray = "Couleur grise" +PythonColorGreen = "Couleur verte" PythonColorOrange = "Couleur orange" PythonColorPink = "Couleur rose" PythonColorPurple = "Couleur violette" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index ae8b928fac5..51223cbdcfb 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" -PythonColorGreen = "Colore verde" PythonColorGray = "Colore grigio" +PythonColorGreen = "Colore verde" PythonColorOrange = "Colore arancione" PythonColorPink = "Colore rosa" PythonColorPurple = "Colore viola" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index ae344f98f64..4efbbee5037 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -30,8 +30,8 @@ PythonColor = "Definieer een rgb kleur" PythonColorBlack = "Zwarte kleur" PythonColorBlue = "Blauwe kleur" PythonColorBrown = "Bruine kleur" -PythonColorGreen = "Groene kleur" PythonColorGray = "Grijze kleur" +PythonColorGreen = "Groene kleur" PythonColorOrange = "Oranje kleur" PythonColorPink = "Roze kleur" PythonColorPurple = "Paarse kleur" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index b5b807535a3..4f50cadaab4 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" -PythonColorGreen = "Cor verde" PythonColorGray = "Cor cinzenta" +PythonColorGreen = "Cor verde" PythonColorOrange = "Cor laranja" PythonColorPink = "Cor rosa" PythonColorPurple = "Cor roxa" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index c1c68373b4c..ff0cfed7a3f 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'" -PythonCommandColorGreen = "'green'" PythonCommandColorGray = "'gray'" +PythonCommandColorGreen = "'green'" PythonCommandColorOrange = "'orange'" PythonCommandColorPink = "'pink'" PythonCommandColorPurple = "'purple'" diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 3f8b21d6bf1..bcc0bef7b57 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -331,8 +331,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::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, 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), From 5569ba92a2c5a4cebd695b1041dda078faf92007 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 11 Jun 2020 11:57:57 +0200 Subject: [PATCH 091/560] [poincare/approximation_helper] Changed the way to approximate To prevent incorrect approximations, such as cos(1.5707963267949) = 0, we lowered the precision value. This way, the approximation is more selective. However, when ploting functions such as e^(i.pi+x), the float approximation fails 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 a maximal precision -> epsilon_double = 1x10^(-15), and when it is a float, we accept more agressive approximations -> epsilon_float = 10 x 1x10^(-7). Change-Id: I844ac52ade665f51fe6888db38f4485c193286d9 --- .../include/poincare/approximation_helper.h | 1 + poincare/src/approximation_helper.cpp | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/approximation_helper.h b/poincare/include/poincare/approximation_helper.h index 20008c0d3fd..b168699482f 100644 --- a/poincare/include/poincare/approximation_helper.h +++ b/poincare/include/poincare/approximation_helper.h @@ -9,6 +9,7 @@ namespace Poincare { namespace ApproximationHelper { + template T Epsilon(); template int PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); template std::complex NeglectRealOrImaginaryPartIfNeglectable(std::complex result, std::complex input1, std::complex input2 = 1.0, bool enableNullResult = true); diff --git a/poincare/src/approximation_helper.cpp b/poincare/src/approximation_helper.cpp index 30439ad9c82..8224024fa6c 100644 --- a/poincare/src/approximation_helper.cpp +++ b/poincare/src/approximation_helper.cpp @@ -27,6 +27,23 @@ template < typename T> T minimalNonNullMagnitudeOfParts(std::complex c) { static inline int absInt(int x) { return x < 0 ? -x : x; } +/* 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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { Evaluation evaluation = expression->approximate(T(), context, complexFormat, angleUnit); T scalar = evaluation.toScalar(); @@ -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); } @@ -126,7 +143,8 @@ template MatrixComplex ApproximationHelper::ElementWiseOnComplexM matrix.setDimensions(m.numberOfRows(), m.numberOfColumns()); return matrix; } - +template float Poincare::ApproximationHelper::Epsilon(); +template double Poincare::ApproximationHelper::Epsilon(); 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 std::complex Poincare::ApproximationHelper::NeglectRealOrImaginaryPartIfNeglectable(std::complex,std::complex,std::complex,bool); From b7bfc253ebe3015851f24d35c9750bff351cb023 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 15 Jun 2020 11:11:12 +0200 Subject: [PATCH 092/560] [poincare/trigonometry] Modified shallowReduceInverseFunction This fixes issues #1541. Formulas as asin(sin(X)) with X a large number were failling to simplify themselves into the image interval of asin ex : previous version asin(sin(6)) = 6 new version asin(sin(6)) = -2pi+6 Change-Id: Ia6200b67914224cecd2cd943bcf9bc2ff6e0447a --- poincare/src/trigonometry.cpp | 30 ++++++++++++++++++++++++------ poincare/test/simplification.cpp | 31 ++++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index 7ece0a2cc0a..a466f11a62f 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -14,10 +14,12 @@ #include #include #include +#include #include #include #include #include +#include namespace Poincare { @@ -296,7 +298,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,13 +312,29 @@ 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(), reductionContext.context(), reductionContext.complexFormat(), angleUnit).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); } } diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index ebf10432375..4e3c9cfaab1 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -801,7 +801,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); @@ -820,7 +833,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); @@ -837,8 +859,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)"); @@ -1406,7 +1427,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"); From f9a1f1a1b7c0be965c2343fe955422df9f42a06d Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 22 Jul 2020 12:19:20 +0200 Subject: [PATCH 093/560] [Poincare/test/approximation.cpp] Fixed broken test Change-Id: I6d851abf9626d282133f143afa2a16ca2d8bec7e --- poincare/test/approximation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index 2e294d1f62a..d8ab186ae28 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -307,7 +307,7 @@ 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(5, 7, 0.3162)", "1.265256ᴇ-10"); + assert_expression_approximates_to("normcdf(5, 7, 0.3162)", "1.265256ᴇ-10", Radian, 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"); From 1e82d5012b6b140590670fd066495b6bb771caf7 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 23 Jul 2020 14:18:04 +0200 Subject: [PATCH 094/560] [Stats] Moved sort messages to shared+NL translation This allows compilation without stats app Change-Id: I0f564ee46756420f0cff5da91ed77677b22cdee0 --- apps/shared.de.i18n | 2 ++ apps/shared.en.i18n | 2 ++ apps/shared.es.i18n | 2 ++ apps/shared.fr.i18n | 2 ++ apps/shared.it.i18n | 2 ++ apps/shared.nl.i18n | 2 ++ apps/shared.pt.i18n | 2 ++ apps/statistics/base.de.i18n | 2 -- apps/statistics/base.en.i18n | 4 +--- apps/statistics/base.es.i18n | 4 +--- apps/statistics/base.fr.i18n | 2 -- apps/statistics/base.it.i18n | 4 +--- apps/statistics/base.nl.i18n | 4 +--- apps/statistics/base.pt.i18n | 4 +--- 14 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 5ed8c210185..b868b539563 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -58,6 +58,8 @@ Rad = "rad" Rename = "Umbenennen" RoundAbscissa = "Ganzzahl" Sci = "wiss" +SortValues = "Nach steigenden Werten sortieren" +SortSizes = "Nach steigenden Frequenzen sortieren" SquareSum = "Quadratsumme" StatTab = "Stats" StandardDeviation = "Standardabweichung" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 4adfebb8dcd..2ddbf867b64 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -58,6 +58,8 @@ Rad = "rad" Rename = "Rename" RoundAbscissa = "Integer" Sci = "sci" +SortValues = "Sort by increasing values" +SortSizes = "Sort by increasing sizes" SquareSum = "Sum of squares" StandardDeviation = "Standard deviation" StoreExpressionNotAllowed = "'store' is not allowed" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index e0a1ab4de28..339be457f93 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -58,6 +58,8 @@ 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" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 67729237d80..002e27297bd 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -58,6 +58,8 @@ 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" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index cd058299bc5..b18957f671e 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -58,6 +58,8 @@ 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" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index f8f1acd980a..a2f65a93524 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -58,6 +58,8 @@ 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" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 0fc30061ef9..d3cfd3249a1 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -58,6 +58,8 @@ 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" diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index 49d08c9c126..a28b5e8f5ca 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -23,5 +23,3 @@ Range = "Spannweite" StandardDeviationSigma = "Standardabweichung σ" SampleStandardDeviationS = "Standardabweichung s" InterquartileRange = "Interquartilsabstand" -SortValues = "Nach steigenden Werten sortieren" -SortSizes = "Nach steigenden Frequenzen sortieren" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 492529d2c9e..839ab4624a8 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -22,6 +22,4 @@ TotalSize = "Total size" Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" -InterquartileRange = "Interquartile range" -SortValues = "Sort by increasing values" -SortSizes = "Sort by increasing sizes" \ No newline at end of file +InterquartileRange = "Interquartile range" \ No newline at end of file diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 2be9cb1b737..e10be87dc58 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -22,6 +22,4 @@ TotalSize = "Población" Range = "Rango" StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" -InterquartileRange = "Rango intercuartilo" -SortValues = "Ordenar por valores crecientes" -SortSizes = "Ordenar por frecuencias crecientes" \ No newline at end of file +InterquartileRange = "Rango intercuartilo" \ No newline at end of file diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index f782afbeec9..f90b4d60ba7 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -23,5 +23,3 @@ Range = "Étendue" StandardDeviationSigma = "Écart type" SampleStandardDeviationS = "Écart type échantillon" InterquartileRange = "Écart interquartile" -SortValues = "Trier par valeurs croissantes" -SortSizes = "Trier par effectifs croissants" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index cf286c1f24a..1603707e9b3 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -22,6 +22,4 @@ TotalSize = "Dimensione totale" Range = "Ampiezza" StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" -InterquartileRange = "Scarto interquartile" -SortValues = "Ordinare per valori crescenti" -SortSizes = "Ordinare per frequenze crescenti" \ No newline at end of file +InterquartileRange = "Scarto interquartile" \ No newline at end of file diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 9c023bd4acc..0af49417e0c 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -22,6 +22,4 @@ TotalSize = "Totale omvang" Range = "Bereik" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" -InterquartileRange = "Interkwartielafstand" -SortValues = "Sorteer door waarden te verhogen" -SortSizes = "Sorteer door verhogen frequenties" \ No newline at end of file +InterquartileRange = "Interkwartielafstand" \ No newline at end of file diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 3157448790a..b2a64d1a36e 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -22,6 +22,4 @@ TotalSize = "Dimensão" Range = "Amplitude" StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" -InterquartileRange = "Amplitude interquartil" -SortValues = "Ordenar por ordem crescente" -SortSizes = "Ordenar por ordem crescente" \ No newline at end of file +InterquartileRange = "Amplitude interquartil" \ No newline at end of file From 891366c51d6efacfd110ba5da80ad64aada6e787 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 17 Jul 2020 10:15:25 +0200 Subject: [PATCH 095/560] [poincare/expression_node] Added Double type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Float and Float used to share the same expression type (Float), and the distinction was made by comparing their size. However, due to padding, their size could be the same, leading to some issues : - On the simulator, in Calculation, type 1_mm^3 and go to the additional outputs. One of the results would be -0.0003081979_µm^3. Change-Id: Ic8f9328bf462104776fbab636c34d0d152cd7e58 --- poincare/include/poincare/expression_node.h | 1 + poincare/include/poincare/float.h | 2 +- poincare/src/expression.cpp | 2 +- poincare/src/float.cpp | 2 +- poincare/src/number.cpp | 9 +++------ 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 79c6eaa88f9..853355a896a 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -30,6 +30,7 @@ class ExpressionNode : public TreeNode { Rational, BasedInteger, Decimal, + Double, Float, Infinity, Multiplication, diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 40f73ffedf9..400e3cc48ac 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -36,7 +36,7 @@ class FloatNode final : public NumberNode { #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; } Expression setSign(Sign s, ReductionContext reductionContext) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 62592f10ce4..a9032a621f8 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -167,7 +167,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) { diff --git a/poincare/src/float.cpp b/poincare/src/float.cpp index cdfd918d705..f26af803dfc 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/number.cpp b/poincare/src/number.cpp index 0bbd1c25298..32e4f58f2ae 100644 --- a/poincare/src/number.cpp +++ b/poincare/src/number.cpp @@ -25,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: From 44da25dd64f1907e05f91322fc8e9f9d0fe5ef35 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 17 Jul 2020 10:25:07 +0200 Subject: [PATCH 096/560] [poincare/number] Fixed typo Change-Id: Id691ade099f09c8479582751aaab6dbd7f077711 --- poincare/include/poincare/number.h | 2 +- poincare/src/number.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poincare/include/poincare/number.h b/poincare/include/poincare/number.h index 4b53d35ad50..ea2b48f2add 100644 --- a/poincare/include/poincare/number.h +++ b/poincare/include/poincare/number.h @@ -34,7 +34,7 @@ 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 */ diff --git a/poincare/src/number.cpp b/poincare/src/number.cpp index 32e4f58f2ae..1da2d12aef7 100644 --- a/poincare/src/number.cpp +++ b/poincare/src/number.cpp @@ -42,9 +42,9 @@ bool NumberNode::derivate(ReductionContext reductionContext, Expression symbol, return Number(this).derivate(reductionContext, symbol, symbolValue); } -Number Number::ParseNumber(const char * integralPart, size_t integralLength, const char * decimalPart, size_t decimalLenght, bool exponentIsNegative, const char * exponentPart, size_t exponentLength) { +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); @@ -53,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; } @@ -65,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 From d027d99597a7ed8416d44f44301bbe49e9cf0488 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 22 Jul 2020 10:52:22 +0200 Subject: [PATCH 097/560] [apps/calculation] Reset highlighted cells in additional results Change-Id: I517e7d3dd025413f405fed605631dad830540e76 --- .../additional_outputs/expressions_list_controller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/calculation/additional_outputs/expressions_list_controller.cpp b/apps/calculation/additional_outputs/expressions_list_controller.cpp index de98173d222..be02125612f 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.cpp +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -29,6 +29,9 @@ void ExpressionsListController::viewDidDisappear() { // 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(); From 9e3470436a128a4b9e501871548d2857cbb3d794 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 23 Jul 2020 11:19:19 +0200 Subject: [PATCH 098/560] [apps/calculation] Move input memoization Calculation's input is now only saved and restored when the app truly closes or opens, instead of whenever the EditExpressionController enters or leaves the responder chain (which also happens when the user switches to the history or the toolbox, or triggers a syntax error). Change-Id: I8404f1bc5619bcbc03272b406962d284fe25f7e2 --- apps/calculation/app.cpp | 10 ++++++++++ apps/calculation/app.h | 2 ++ apps/calculation/edit_expression_controller.cpp | 8 +++++--- apps/calculation/edit_expression_controller.h | 3 ++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 077fcc75d92..cb8990b2350 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -71,4 +71,14 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) { return !expression.isUninitialized(); } +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 eaa0040fa06..0c188181e64 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -46,6 +46,8 @@ class App : public Shared::ExpressionFieldDelegateApp { private: App(Snapshot * snapshot); HistoryController m_historyController; + void didBecomeActive(Window * window) override; + void willBecomeInactive() override; EditExpressionController m_editExpressionController; }; diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 7500389075a..7f747d3598f 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -56,14 +56,16 @@ void EditExpressionController::insertTextBody(const char * text) { void EditExpressionController::didBecomeFirstResponder() { m_contentView.mainView()->scrollToBottom(); m_contentView.expressionField()->setEditing(true, false); + Container::activeApp()->setFirstResponder(m_contentView.expressionField()); +} + +void EditExpressionController::restoreInput() { m_contentView.expressionField()->restoreContent(m_cacheBuffer, *m_cacheBufferInformation); clearCacheBuffer(); - Container::activeApp()->setFirstResponder(m_contentView.expressionField()); } -void EditExpressionController::willExitResponderChain(Responder * nextFirstResponder) { +void EditExpressionController::memoizeInput() { *m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize); - ::ViewController::willExitResponderChain(nextFirstResponder); } void EditExpressionController::viewWillAppear() { diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index fdef423bee5..32a6ec84cb2 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -27,9 +27,10 @@ class EditExpressionController : public ViewController, public Shared::TextField View * view() override { return &m_contentView; } void didBecomeFirstResponder() override; - void willExitResponderChain(Responder * nextFirstResponder) override; void viewWillAppear() override; void insertTextBody(const char * text); + void restoreInput(); + void memoizeInput(); /* TextFieldDelegate */ bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; From c04a7af22f66f7e6a38eaaf844e3a4b6d1eaf233 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 23 Jul 2020 12:19:37 +0200 Subject: [PATCH 099/560] [escher/layout_field] Change cursor behavior Some code to prevent the input field from scrolling when switching to the history in layout mode causes problems : - With a calculation in the history, type an empty matrix or fraction. Then with the cursor on an empty square, go to the history. The square will appear shifted to the right. Change-Id: I72ebee675215dc3c3a3f8432890f6fee820ef5c9 --- escher/include/escher/layout_field.h | 2 +- escher/src/layout_field.cpp | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index 908d27f3f8b..bd36e8b14a3 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -58,7 +58,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E constexpr static int k_maxNumberOfLayouts = 220; static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field"); void reload(KDSize previousSize); - virtual bool privateHandleEvent(Ion::Events::Event event, bool * shouldScrollAndRedraw); + virtual bool privateHandleEvent(Ion::Events::Event event); bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); bool privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); void scrollRightOfLayout(Poincare::Layout layoutR); diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 26f46006118..a4c4c3a0721 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -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(); @@ -430,7 +434,6 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { if (!eventShouldUpdateInsertionCursor(event)) { m_contentView.invalidateInsertionCursor(); } - bool shouldScrollAndRedraw = true; if (privateHandleMoveEvent(event, &moveEventChangedLayout)) { if (!isEditing()) { setEditing(true); @@ -454,12 +457,7 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { } shouldRecomputeLayout = m_contentView.cursor()->layout().removeGraySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout; } - } else if (privateHandleEvent(event, &shouldScrollAndRedraw)) { - if (!shouldScrollAndRedraw) { - /* We escape early to avoid scrolling to the beginning of the expression, - * and to mimic the behaviour of the TextField. */ - return true; - } + } else if (privateHandleEvent(event)) { shouldRecomputeLayout = true; didHandleEvent = true; } @@ -507,10 +505,8 @@ static inline bool IsMoveEvent(Ion::Events::Event event) { static_cast(event) <= static_cast(Ion::Events::Right); } -bool LayoutField::privateHandleEvent(Ion::Events::Event event, bool * shouldScrollAndRedraw) { - assert(*shouldScrollAndRedraw); +bool LayoutField::privateHandleEvent(Ion::Events::Event event) { if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) { - *shouldScrollAndRedraw = false; return true; } if (handleBoxEvent(event)) { From bd2609bcba6610b3f45c1715d3562dae394ce262 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 24 Jul 2020 14:44:52 +0200 Subject: [PATCH 100/560] [apps/graph] Remove horizontal margins when panning on some graph calculations Change-Id: I1a28555c5b4f04986b223617ab15110711a74841 --- .../graph/graph/calculation_graph_controller.cpp | 2 +- apps/graph/graph/extremum_graph_controller.h | 6 ++++++ apps/graph/graph/intersection_graph_controller.h | 3 +++ apps/graph/graph/root_graph_controller.h | 3 +++ apps/graph/graph/tangent_graph_controller.cpp | 4 ++-- apps/regression/graph_controller.cpp | 2 +- apps/sequence/graph/graph_controller.cpp | 2 +- apps/shared/function_graph_controller.cpp | 2 +- .../shared/interactive_curve_view_controller.cpp | 16 ++++++++-------- .../simple_interactive_curve_view_controller.cpp | 2 +- .../simple_interactive_curve_view_controller.h | 4 ++-- apps/shared/sum_graph_controller.cpp | 4 ++-- 12 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index 73c95a9e090..3580395a558 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, curveView()->pixelWidth()); + m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews); reloadBannerView(); } diff --git a/apps/graph/graph/extremum_graph_controller.h b/apps/graph/graph/extremum_graph_controller.h index dc93bfc9629..cadfbeb6804 100644 --- a/apps/graph/graph/extremum_graph_controller.h +++ b/apps/graph/graph/extremum_graph_controller.h @@ -12,6 +12,9 @@ class MinimumGraphController : public CalculationGraphController { 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 @@ class MaximumGraphController : public CalculationGraphController { 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/intersection_graph_controller.h b/apps/graph/graph/intersection_graph_controller.h index dfd69232c84..46bce552a95 100644 --- a/apps/graph/graph/intersection_graph_controller.h +++ b/apps/graph/graph/intersection_graph_controller.h @@ -13,6 +13,9 @@ class IntersectionGraphController : public CalculationGraphController { 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 6bb7264a2a9..b808cb9201f 100644 --- a/apps/graph/graph/root_graph_controller.h +++ b/apps/graph/graph/root_graph_controller.h @@ -12,6 +12,9 @@ class RootGraphController : public CalculationGraphController { 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 5edf674d85b..af70b1b4224 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, curveView()->pixelWidth()); + 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, curveView()->pixelWidth()); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index dec921c6378..919bd0396fa 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -262,7 +262,7 @@ void GraphController::initCursorParameters() { 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, curveView()->pixelWidth()); + m_store->panToMakePointVisible(x, y, cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); } *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index a0552361ee0..e2e4fe52fb6 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -67,7 +67,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, curveView()->pixelWidth()); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); m_view.reload(); return true; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 04a08dec45a..9f791969ce7 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -146,7 +146,7 @@ void FunctionGraphController::initCursorParameters() { } m_cursor->moveTo(t, xy.x1(), xy.x2()); if (interactiveCurveViewRange()->yAuto()) { - interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio, curveView()->pixelWidth()); + interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); } selectFunctionWithCursor(functionIndex); } diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 1d2f23bae39..25aa9149d73 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -54,8 +54,8 @@ float InteractiveCurveViewController::addMargin(float y, float range, bool isVer * bottomMarginRatio = bottomMargin / viewHeight. * The same goes horizontally. */ - float topMarginRatio = isVertical ? cursorTopMarginRatio() : k_cursorRightMarginRatio; - float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : k_cursorLeftMarginRatio; + float topMarginRatio = isVertical ? cursorTopMarginRatio() : cursorRightMarginRatio(); + float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : cursorLeftMarginRatio(); assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; float ratio = isMin ? -bottomMarginRatio : topMarginRatio; @@ -93,7 +93,7 @@ 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(); @@ -221,7 +221,7 @@ bool InteractiveCurveViewController::textFieldDidFinishEditing(TextField * textF } 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, curveView()->pixelWidth()); + interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); reloadBannerView(); curveView()->reload(); return true; @@ -247,10 +247,10 @@ 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 { diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index 10d07998423..3169adb59a2 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -31,7 +31,7 @@ bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Eve 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(); diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index 8bf2c771545..ab34fc3e45d 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -15,8 +15,8 @@ class SimpleInteractiveCurveViewController : public ZoomCurveViewController, pub 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; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 02b42800afd..423582e900c 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, curveView()->pixelWidth()); + 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, curveView()->pixelWidth()); + m_graphRange->panToMakePointVisible(x, y, cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); m_graphView->reload(); return true; } From 9e12b61849d56709da05b6d9903b737fbfe53992 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 9 Jul 2020 10:14:59 +0200 Subject: [PATCH 101/560] [apps/graph] Round x before evaluating graph cursor on scroll Change-Id: I13500669963eb8130e188a898bed0bf63655add6 --- apps/graph/graph/graph_controller_helper.cpp | 7 +++-- apps/shared/function_banner_delegate.cpp | 30 ++++++++++++++++--- apps/shared/function_banner_delegate.h | 8 +++++ .../function_go_to_parameter_controller.cpp | 5 ++++ .../interactive_curve_view_controller.cpp | 5 ++++ poincare/src/print_float.cpp | 2 +- 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index 90785d29ae2..249ef8bd198 100644 --- a/apps/graph/graph/graph_controller_helper.cpp +++ b/apps/graph/graph/graph_controller_helper.cpp @@ -27,8 +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; - step *= scrollSpeed; - 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/shared/function_banner_delegate.cpp b/apps/shared/function_banner_delegate.cpp index 87e0f953bd0..29c3d4f3b73 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 f02a1f000de..30acdcf1f33 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 c5b21cb4347..bcb827923fc 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/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 25aa9149d73..9e4c2ebed9e 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 @@ -219,6 +220,10 @@ 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(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index d782302de25..d9147a275c9 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -229,7 +229,7 @@ PrintFloat::TextLengths PrintFloat::ConvertFloatToTextPrivate(T f, char * buffer * 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) From 22a15f5ed67bd7577cf51fe1bcb0e0b2a5a7c277 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 29 Jul 2020 16:05:08 +0200 Subject: [PATCH 102/560] [Shared/store_parameter_controler] Fixed a bug In stats when selecting an action on a column, all actions listed after the one selected were applied. Adding breaks to the switch solved the issue Change-Id: I4a2f8a41f734a209abb17e76388eed551bf1769c --- apps/shared/store_parameter_controller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/shared/store_parameter_controller.cpp b/apps/shared/store_parameter_controller.cpp index 0bd90d14ff8..50212fee23f 100644 --- a/apps/shared/store_parameter_controller.cpp +++ b/apps/shared/store_parameter_controller.cpp @@ -51,10 +51,12 @@ bool StoreParameterController::handleEvent(Ion::Events::Event event) { } else { m_store->resetColumn(m_series, !m_xColumnSelected); } + break; } case 1: { m_storeController->displayFormulaInput(); + break; } case 2: { @@ -85,6 +87,7 @@ bool StoreParameterController::handleEvent(Ion::Events::Event event) { } else { Poincare::Helpers::Sort(swapRows, compareY, (m_store->data() + m_series), m_store->numberOfPairsOfSeries(m_series)); } + break; } } assert(selectedRow() >= 0 && selectedRow() <= 2); From 0322a1a6a72598901a6560921a556283bacc34eb Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 28 Jul 2020 17:14:40 +0200 Subject: [PATCH 103/560] [apps/shared] Fix draw curve issues when tStep << tStart Change-Id: Iaf054cff1f78f3c800564a0e6c133192bdfab260 --- apps/shared/curve_view.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index dd594964ae5..f4b8683fa5a 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -610,6 +610,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; @@ -618,9 +619,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; @@ -631,7 +634,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 { From cafb1c1c0596d9d4ff872cd6b40de2f40a81cb78 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 17 Jul 2020 12:08:05 +0200 Subject: [PATCH 104/560] [poincare] LCM and GCD accept set of numbers Change-Id: I367ff5e48fa2856e976aa020ac0d172216f3a421 --- poincare/include/poincare/arithmetic.h | 9 +- poincare/include/poincare/expression.h | 12 ++ .../include/poincare/great_common_divisor.h | 16 ++- .../include/poincare/least_common_multiple.h | 24 ++-- poincare/include/poincare/n_ary_expression.h | 1 + poincare/src/arithmetic.cpp | 123 ++++++++++++++++-- poincare/src/great_common_divisor.cpp | 75 ++++------- poincare/src/least_common_multiple.cpp | 81 ++++-------- poincare/src/n_ary_expression.cpp | 17 +++ poincare/src/parsing/parser.cpp | 18 ++- poincare/src/tree_handle.cpp | 4 +- poincare/test/parsing.cpp | 8 +- 12 files changed, 240 insertions(+), 148 deletions(-) diff --git a/poincare/include/poincare/arithmetic.h b/poincare/include/poincare/arithmetic.h index 94ee1be315a..edfb361db4d 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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + template static Evaluation LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); /* 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/expression.h b/poincare/include/poincare/expression.h index e5aa5874700..8c07ace962f 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -317,6 +317,18 @@ class Expression : public TreeHandle { 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. diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index 96429d951a2..f01fc5dcfea 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"; @@ -26,6 +25,7 @@ class GreatCommonDivisorNode final : public ExpressionNode { 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 @@ -34,14 +34,16 @@ class GreatCommonDivisorNode final : public ExpressionNode { template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) 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 shallowBeautify(Context * context); }; } diff --git a/poincare/include/poincare/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index 2ca831043fe..c65550cebe6 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -1,47 +1,49 @@ #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 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 Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; -class 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 shallowBeautify(Context * context); }; } diff --git a/poincare/include/poincare/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index 97455f3aff1..b91095cbbd1 100644 --- a/poincare/include/poincare/n_ary_expression.h +++ b/poincare/include/poincare/n_ary_expression.h @@ -63,6 +63,7 @@ class NAryExpression : public Expression { node()->sortChildrenInPlace(order, context, canSwapMatrices, canBeInterrupted); } NAryExpressionNode * node() const { return static_cast(Expression::node()); } + Expression checkChildrenAreRationalIntegers(Context * context); }; } diff --git a/poincare/src/arithmetic.cpp b/poincare/src/arithmetic.cpp index a7b50149b53..67319546990 100644 --- a/poincare/src/arithmetic.cpp +++ b/poincare/src/arithmetic.cpp @@ -1,20 +1,10 @@ #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); @@ -41,6 +31,110 @@ 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); + } + if (a.isEqualTo(b)) { + return a; + } + /* Using LCM(a,b) = a*(b/GCD(a,b)). Knowing that GCD(a, b) divides b, + * division is performed before multiplication to be more efficient. */ + Integer signResult = Integer::Multiplication(a, Integer::Division(b, GCD(a, b)).quotient); + signResult.setNegative(false); + return signResult; +} + +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), Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + /* 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, context, complexFormat, angleUnit); + // 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, context, complexFormat, angleUnit); + if (isUndefined) { + return Complex::RealUndefined(); + } + a = f(a,b); + } + return Complex::Builder((T)a); +} + +template +Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + // Evaluate GCD of expression's children + return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::GCD, context, complexFormat, angleUnit); +} + +template +Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + // Evaluate LCM of expression's children + return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::LCM, context, complexFormat, angleUnit); +} + 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 +197,9 @@ int Arithmetic::PrimeFactorization(const Integer & n, Integer outputFactors[], I return t+1; } +template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); +template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); +template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); +template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + } diff --git a/poincare/src/great_common_divisor.cpp b/poincare/src/great_common_divisor.cpp index 55bdbf3c201..35f2aacfcc1 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()); } @@ -26,28 +19,22 @@ Expression GreatCommonDivisorNode::shallowReduce(ReductionContext reductionConte return GreatCommonDivisor(this).shallowReduce(reductionContext.context()); } +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)); + return Arithmetic::GCD(*this, context, complexFormat, angleUnit); } +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) { { @@ -57,34 +44,22 @@ 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(); + assert(numberOfChildren() > 0); + + // Step 0: Merge children which are GCD + mergeSameTypeChildrenInPlace(); + + // Step 1: check that all children are compatible + { + Expression checkChildren = checkChildrenAreRationalIntegers(context); + if (!checkChildren.isUninitialized()) { + return checkChildren; } } - if (c0.type() != ExpressionNode::Type::Rational || c1.type() != ExpressionNode::Type::Rational) { - return *this; - } - Rational r0 = static_cast(c0); - Rational r1 = static_cast(c1); - Integer a = r0.signedIntegerNumerator(); - Integer b = r1.signedIntegerNumerator(); - Integer gcd = Arithmetic::GCD(a, b); - assert(!gcd.isOverflow()); - Expression result = Rational::Builder(gcd); + // Step 2: Compute GCD + Expression result = Arithmetic::GCD(*this); + replaceWithInPlace(result); return result; } diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 4935de290b6..47ffa466070 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()); } @@ -26,32 +19,22 @@ Expression LeastCommonMultipleNode::shallowReduce(ReductionContext reductionCont return LeastCommonMultiple(this).shallowReduce(reductionContext.context()); } +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); + return Arithmetic::LCM(*this, context, complexFormat, angleUnit); } +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) { { @@ -61,36 +44,22 @@ 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(); + assert(numberOfChildren() > 0); + + // Step 0: Merge children which are LCM + mergeSameTypeChildrenInPlace(); + + // Step 1: check that all children are compatible + { + Expression checkChildren = checkChildrenAreRationalIntegers(context); + if (!checkChildren.isUninitialized()) { + return checkChildren; } } - if (c0.type() != ExpressionNode::Type::Rational || c1.type() != ExpressionNode::Type::Rational) { - return *this; - } - Rational r0 = static_cast(c0); - Rational r1 = static_cast(c1); - Integer a = r0.signedIntegerNumerator(); - Integer b = r1.signedIntegerNumerator(); - Integer lcm = Arithmetic::LCM(a, b); - if (lcm.isOverflow()) { - return *this; - } - Expression result = Rational::Builder(lcm); + // Step 2: Compute LCM + Expression result = Arithmetic::LCM(*this); + replaceWithInPlace(result); return result; } diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index 314f4b8cb3e..e0fdc1cd758 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -1,5 +1,6 @@ #include #include +#include extern "C" { #include #include @@ -127,4 +128,20 @@ int NAryExpression::allChildrenAreReal(Context * context) const { return result; } +Expression NAryExpression::checkChildrenAreRationalIntegers(Context * context) { + for (int i = 0; i < numberOfChildren(); ++i) { + Expression c = childAtIndex(i); + if (c.deepIsMatrix(context)) { + return replaceWithUndefinedInPlace(); + } + if (c.type() != ExpressionNode::Type::Rational) { + return *this; + } + if (!static_cast(c).isInteger()) { + return replaceWithUndefinedInPlace(); + } + } + return Expression(); +} + } diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index f38c57e3f68..1ce66ee3202 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -386,14 +386,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; } @@ -484,7 +488,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/tree_handle.cpp b/poincare/src/tree_handle.cpp index 54d66340179..1b70f0b74f2 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 &); diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index f8bed5cc474..b62ab6571eb 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -376,13 +376,17 @@ QUIZ_CASE(poincare_parsing_identifiers) { 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))); From cce1fa0c9050752de01815a7fd2bcf406a882e42 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 17 Jul 2020 12:10:57 +0200 Subject: [PATCH 105/560] [poincare/test] Add tests for LCM and GCD Change-Id: If91c1ef863c9810e1ab87525a5ed2b4c7ed45656 --- poincare/test/approximation.cpp | 12 ++++++++++++ poincare/test/arithmetic.cpp | 14 ++++++++++++++ poincare/test/simplification.cpp | 9 +++++++++ 3 files changed, 35 insertions(+) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index d8ab186ae28..b1307d5953d 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -282,6 +282,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"); @@ -291,6 +297,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"); diff --git a/poincare/test/arithmetic.cpp b/poincare/test/arithmetic.cpp index d8a9ad9c727..36a268b5e89 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/simplification.cpp b/poincare/test/simplification.cpp index 4e3c9cfaab1..27b79630319 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -391,6 +391,7 @@ QUIZ_CASE(poincare_simplification_units) { 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"); @@ -405,6 +406,7 @@ 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"); @@ -638,9 +640,11 @@ 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("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("√(4)", "2"); assert_parsed_expression_simplify_to("re(1+5×𝐢)", "1"); assert_parsed_expression_simplify_to("root(4,3)", "root(4,3)"); @@ -1008,6 +1012,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(π)]]"); @@ -1206,9 +1211,13 @@ QUIZ_CASE(poincare_simplification_complex_format) { 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("gcd(x,gcd(y,z))", "gcd(x,y,z)", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("gcd(3, 1, 2, x, x^2)", "gcd(x^2,x,3,2,1)", 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("lcm(x,lcm(y,z))", "lcm(x,y,z)", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("lcm(3, 1, 2, x, x^2)", "lcm(x^2,x,3,2,1)", User, Radian, Cartesian); // TODO: dim is not simplified yet //assert_parsed_expression_simplify_to("dim(x)", "dim(x)", User, Radian, Cartesian); From cbd26e69d176e0caf062a1da94b562ff682f4329 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 30 Jul 2020 15:07:22 +0200 Subject: [PATCH 106/560] [apps/graph/list] Apply changes on confirm with function interval selection Change-Id: I0ec1ae54788f1c72fc8277544ed51a85ba4474d5 --- .../list/domain_parameter_controller.cpp | 45 +++++++++++++------ apps/graph/list/domain_parameter_controller.h | 13 ++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/apps/graph/list/domain_parameter_controller.cpp b/apps/graph/list/domain_parameter_controller.cpp index 326a7eb8560..b4c70627c9e 100644 --- a/apps/graph/list/domain_parameter_controller.cpp +++ b/apps/graph/list/domain_parameter_controller.cpp @@ -10,7 +10,8 @@ namespace Graph { DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : FloatParameterController(parentResponder), m_domainCells{}, - m_record() + m_record(), + m_tempDomain() { for (int i = 0; i < k_totalNumberOfCell; i++) { m_domainCells[i].setParentResponder(&m_selectableTableView); @@ -18,12 +19,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 +55,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]; @@ -74,16 +69,38 @@ bool DomainParameterController::handleEvent(Ion::Events::Event event) { } 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(function()->tMin() == m_tempDomain.min()); + assert(function()->tMax() == m_tempDomain.max()); } 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(function()->tMin() == m_tempDomain.min()); + assert(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 1b8da37121f..5efe1c19318 100644 --- a/apps/graph/list/domain_parameter_controller.h +++ b/apps/graph/list/domain_parameter_controller.h @@ -6,6 +6,7 @@ #include "../../shared/continuous_function.h" #include "../../shared/expiring_pointer.h" #include "../../shared/float_parameter_controller.h" +#include "../../shared/range_1D.h" namespace Graph { @@ -14,17 +15,18 @@ class DomainParameterController : public Shared::FloatParameterController 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 +34,13 @@ class DomainParameterController : public Shared::FloatParameterController 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(); MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell]; Ion::Storage::Record m_record; + Shared::Range1D m_tempDomain; }; } From 0a92499dd797f15c6bda7a75ac1b6c869d92ae2b Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 3 Aug 2020 10:35:52 +0200 Subject: [PATCH 107/560] [Poincare/unit] Changing the value of AU The value of astronomical unit was wrong. This fixes issue #1637 Change-Id: I175c8fae9044beb1e863ddbf3a260224a9c60bd6 --- poincare/include/poincare/unit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 060327879cb..9109eff034d 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -288,7 +288,7 @@ class Unit final : public Expression { Representative("m", nullptr, Representative::Prefixable::Yes, LongScalePrefixes), - Representative("au", "149587870700*_m", + Representative("au", "149597870700*_m", Representative::Prefixable::No, NoPrefix), Representative("ly", "299792458*_m/_s*_year", From 53e204db3afa5ea95e87293a39801bf3eb4282eb Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 4 Aug 2020 18:16:44 +0200 Subject: [PATCH 108/560] [liba] Add abs function for int Change-Id: Ic133ff2f34c7ff24ecbec69d5f31e447a72ae4fe --- ion/src/device/shared/usb/Makefile | 1 + liba/Makefile | 1 + liba/include/stdlib.h | 2 ++ liba/src/abs.c | 5 +++++ 4 files changed, 9 insertions(+) create mode 100644 liba/src/abs.c diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 2e7ae45dbf3..3db2b7d519c 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/liba/Makefile b/liba/Makefile index 676fa88d08d..3639ceb91dc 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 \ diff --git a/liba/include/stdlib.h b/liba/include/stdlib.h index 6cf0a96547a..6c27b3d6e1e 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 00000000000..8d0efabdcef --- /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 From 56a9e8d74b0c1e1a55513a44423096f910b951be Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 4 Aug 2020 18:20:10 +0200 Subject: [PATCH 109/560] [poincare] Fix LCM GCD bug on device compilation Change-Id: I5ac11b7e72ede335db503b1d0f9c0be00710cd0b --- poincare/include/poincare/expression.h | 1 + poincare/src/great_common_divisor.cpp | 3 +++ poincare/src/least_common_multiple.cpp | 3 +++ poincare/src/parsing/parser.cpp | 1 + 4 files changed, 8 insertions(+) diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 8c07ace962f..d74be3a9f8e 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -9,6 +9,7 @@ #include #include #include +#include namespace Poincare { diff --git a/poincare/src/great_common_divisor.cpp b/poincare/src/great_common_divisor.cpp index 35f2aacfcc1..95aa3205e5d 100644 --- a/poincare/src/great_common_divisor.cpp +++ b/poincare/src/great_common_divisor.cpp @@ -64,4 +64,7 @@ Expression GreatCommonDivisor::shallowReduce(Context * context) { return result; } +template Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + } diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 47ffa466070..9a1e5005620 100644 --- a/poincare/src/least_common_multiple.cpp +++ b/poincare/src/least_common_multiple.cpp @@ -64,4 +64,7 @@ Expression LeastCommonMultiple::shallowReduce(Context * context) { return result; } +template Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + } diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 1ce66ee3202..782e41757a5 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace Poincare { From 8688fd5b959ec06f16424f900d814f213f8769ef Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 6 Aug 2020 13:47:53 +0200 Subject: [PATCH 110/560] [apps/shared] Add template for privateEvaluateXYAtParameter Change-Id: I97b4165307f57e52924e321cb85154cb32ba1008 --- apps/shared/continuous_function.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index b2688fdc3ec..265c9dd974e 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -359,4 +359,7 @@ Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, 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; + } From ca91b7c43dd1a4ccd5d765779eb741ee8e7a62c1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 21 Jul 2020 10:40:21 +0200 Subject: [PATCH 111/560] [apps/toolbox] Add Vector operation toolbox menus Change-Id: Iab8faa45a3f30be979f7d705ac169e8f2b801da9 --- apps/math_toolbox.cpp | 7 +++++++ apps/shared.universal.i18n | 3 +++ apps/toolbox.de.i18n | 4 ++++ apps/toolbox.en.i18n | 4 ++++ apps/toolbox.es.i18n | 4 ++++ apps/toolbox.fr.i18n | 4 ++++ apps/toolbox.it.i18n | 4 ++++ apps/toolbox.nl.i18n | 4 ++++ apps/toolbox.pt.i18n | 4 ++++ 9 files changed, 38 insertions(+) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 49b9c62f9f4..ec1d8a8cc86 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -76,6 +76,12 @@ const ToolboxMessageTree matricesChildren[] = { 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 const ToolboxMessageTree listsChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::SortCommandWithArg, I18n::Message::Sort), @@ -286,6 +292,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 diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index ec4eeffd9e5..7542b995e44 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -94,12 +94,14 @@ CodeApp = "Python" ConfidenceCommandWithArg = "confidence(f,n)" ConjCommandWithArg = "conj(z)" CoshCommandWithArg = "cosh(x)" +CrossCommandWithArg = "cross(u,v)" D = "d" 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)" @@ -130,6 +132,7 @@ N = "n" NormCDFCommandWithArg = "normcdf(a,μ,σ)" NormCDF2CommandWithArg = "normcdf2(a,b,μ,σ)" NormPDFCommandWithArg = "normpdf(x,μ,σ)" +NormVectorCommandWithArg = "norm(u)" PermuteCommandWithArg = "permute(n,r)" P = "p" Prediction95CommandWithArg = "prediction95(p,n)" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 8401398154b..8e410a7f1fe 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -136,6 +136,10 @@ 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" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index a6adecaf629..c8c295608d6 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -136,6 +136,10 @@ Trace = "Trace" Dimension = "Size" RowEchelonForm = "Row echelon form" ReducedRowEchelonForm = "Reduced row echelon form" +Vectors = "Vectors" +Dot = "Dot product" +Cross = "Cross product" +NormVector = "Norm" Sort = "Sort ascending " InvSort = "Sort descending" Maximum = "Maximum" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index b9b245577fe..73410e367ed 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -136,6 +136,10 @@ Trace = "Traza" Dimension = "Tamaño" RowEchelonForm = "Matriz escalonada" ReducedRowEchelonForm = "Matriz escalonada reducida" +Vectors = "Vectores" +Dot = "Producto escalar" +Cross = "Producto vectorial" +NormVector = "Norma" Sort = "Clasificación ascendente" InvSort = "Clasificación descendente" Maximum = "Máximo" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 06c358f9466..88b640b11ea 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -136,6 +136,10 @@ Trace = "Trace de M" Dimension = "Taille de M" RowEchelonForm = "Forme échelonnée de M" ReducedRowEchelonForm = "Forme échelonnée réduite de M" +Vectors = "Vecteurs" +Dot = "Produit scalaire" +Cross = "Produit vectoriel" +NormVector = "Norme" Sort = "Tri croissant" InvSort = "Tri décroissant" Maximum = "Maximum" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index f913db44f4e..1790bed866d 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -136,6 +136,10 @@ Trace = "Traccia" Dimension = "Dimensione" RowEchelonForm = "Matrice a scalini" ReducedRowEchelonForm = "Matrice ridotta a scalini" +Vectors = "Vettori" +Dot = "Prodotto scalare" +Cross = "Prodotto vettoriale" +NormVector = "Norma" Sort = "Ordine crescente" InvSort = "Ordine decrescente" Maximum = "Massimo" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 18e25d3cc91..02be8f21b77 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -136,6 +136,10 @@ Trace = "Spoor" Dimension = "Afmeting" RowEchelonForm = "Echelonvorm" ReducedRowEchelonForm = "Gereduceerde echelonvorm" +Vectors = "Vectoren" +Dot = "Inwendig product" +Cross = "Kruisproduct" +NormVector = "Norm" Sort = "Sorteer oplopend " InvSort = "Sort aflopend" Maximum = "Maximum" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 00790ad3ddd..54f95ee42d7 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -136,6 +136,10 @@ Trace = "Traço" Dimension = "Dimensão" RowEchelonForm = "Matriz escalonada" ReducedRowEchelonForm = "Matriz escalonada reduzida" +Vectors = "Vetores" +Dot = "Produto escalar" +Cross = "Produto vetorial" +NormVector = "Norma" Sort = "Ordem crescente" InvSort = "Ordem decrescente" Maximum = "Máximo" From 6db02ea1221e45b4b15e4cf24c6b0e2b9ba3b840 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 21 Jul 2020 17:44:15 +0200 Subject: [PATCH 112/560] [poincare/vectors] Implement Vectors operations Change-Id: Ib5aa2f5951f4eabe3b04078eab8b7d3a4d3769e7 --- poincare/Makefile | 3 + poincare/include/poincare/complex.h | 3 + poincare/include/poincare/evaluation.h | 6 ++ poincare/include/poincare/expression.h | 3 + poincare/include/poincare/expression_node.h | 18 ++++-- poincare/include/poincare/matrix.h | 3 + poincare/include/poincare/matrix_complex.h | 3 + poincare/include/poincare/vector_cross.h | 48 ++++++++++++++++ poincare/include/poincare/vector_dot.h | 48 ++++++++++++++++ poincare/include/poincare/vector_norm.h | 48 ++++++++++++++++ poincare/include/poincare_nodes.h | 3 + poincare/src/expression.cpp | 3 +- poincare/src/matrix.cpp | 48 ++++++++++++++++ poincare/src/matrix_complex.cpp | 44 +++++++++++++++ poincare/src/parsing/parser.h | 3 + poincare/src/tree_handle.cpp | 3 + poincare/src/vector_cross.cpp | 61 +++++++++++++++++++++ poincare/src/vector_dot.cpp | 61 +++++++++++++++++++++ poincare/src/vector_norm.cpp | 58 ++++++++++++++++++++ 19 files changed, 462 insertions(+), 5 deletions(-) create mode 100644 poincare/include/poincare/vector_cross.h create mode 100644 poincare/include/poincare/vector_dot.h create mode 100644 poincare/include/poincare/vector_norm.h create mode 100644 poincare/src/vector_cross.cpp create mode 100644 poincare/src/vector_dot.cpp create mode 100644 poincare/src/vector_norm.cpp diff --git a/poincare/Makefile b/poincare/Makefile index 71dcd46dba9..a6c0524b981 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -151,6 +151,9 @@ poincare_src += $(addprefix poincare/src/,\ unit_convert.cpp \ unreal.cpp \ variable_context.cpp \ + vector_cross.cpp \ + vector_dot.cpp \ + vector_norm.cpp \ ) poincare_src += $(addprefix poincare/src/parsing/,\ diff --git a/poincare/include/poincare/complex.h b/poincare/include/poincare/complex.h index 5f4aec757a4..ac5be2ccb94 100644 --- a/poincare/include/poincare/complex.h +++ b/poincare/include/poincare/complex.h @@ -34,6 +34,9 @@ class ComplexNode final : public EvaluationNode, public std::complex { 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/evaluation.h b/poincare/include/poincare/evaluation.h index 2cca0158d08..d0f50812361 100644 --- a/poincare/include/poincare/evaluation.h +++ b/poincare/include/poincare/evaluation.h @@ -32,6 +32,9 @@ class EvaluationNode : public TreeNode { 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 @@ class Evaluation : public TreeHandle { 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 d74be3a9f8e..6e71cc117a7 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -103,6 +103,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; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 853355a896a..8e56f312697 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -23,7 +23,8 @@ class ExpressionNode : public TreeNode { 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, @@ -33,6 +34,8 @@ class ExpressionNode : public TreeNode { Double, Float, Infinity, + /* When merging number nodes together, we do a linear scan which stops at + * the first non-number child. */ Multiplication, Power, Addition, @@ -95,10 +98,15 @@ class ExpressionNode : public TreeNode { 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, @@ -107,9 +115,11 @@ class ExpressionNode : public TreeNode { MatrixRowEchelonForm, MatrixReducedRowEchelonForm, PredictionInterval, + VectorCross, Matrix, + EmptyExpression - }; + }; /* Poor man's RTTI */ virtual Type type() const = 0; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index def7da82090..30ea4f5d467 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -85,6 +85,9 @@ class Matrix final : public Expression { * 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; diff --git a/poincare/include/poincare/matrix_complex.h b/poincare/include/poincare/matrix_complex.h index ce2343b1e4f..275785cd4ae 100644 --- a/poincare/include/poincare/matrix_complex.h +++ b/poincare/include/poincare/matrix_complex.h @@ -47,6 +47,9 @@ class MatrixComplexNode final : public EvaluationNode { MatrixComplex inverse() const; MatrixComplex transpose() const; MatrixComplex ref(bool reduced) const; + std::complex norm() const override; + std::complex dot(Evaluation * e) const override; + Evaluation cross(Evaluation * e) const override; private: // See comment on Matrix uint16_t m_numberOfRows; diff --git a/poincare/include/poincare/vector_cross.h b/poincare/include/poincare/vector_cross.h new file mode 100644 index 00000000000..e1f75e03eb0 --- /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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +}; + +class 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 00000000000..3b9c8034224 --- /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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } + template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +}; + +class 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 00000000000..f0572ada4f3 --- /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 + 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::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; +}; + +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_nodes.h b/poincare/include/poincare_nodes.h index 41f1d0d9282..ed7316b8e86 100644 --- a/poincare/include/poincare_nodes.h +++ b/poincare/include/poincare_nodes.h @@ -89,5 +89,8 @@ #include #include #include +#include +#include +#include #endif diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index a9032a621f8..21daee9cbe3 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -187,7 +187,8 @@ bool Expression::IsMatrix(const Expression e, Context * context) { || e.type() == ExpressionNode::Type::MatrixIdentity || e.type() == ExpressionNode::Type::MatrixTranspose || e.type() == ExpressionNode::Type::MatrixRowEchelonForm - || e.type() == ExpressionNode::Type::MatrixReducedRowEchelonForm; + || e.type() == ExpressionNode::Type::MatrixReducedRowEchelonForm + || e.type() == ExpressionNode::Type::VectorCross; } bool Expression::IsInfinity(const Expression e, Context * context) { diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index cabbb2992a4..6c7df381521 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -483,6 +485,52 @@ Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext return result; } +Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const { + assert(numberOfColumns() == 1); + Addition sum = Addition::Builder(); + for (int j = 0; j < numberOfRows(); j++) { + Expression absValue = AbsoluteValue::Builder(const_cast(this)->matrixChild(0, 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 + assert(numberOfRows() == b->numberOfRows() && numberOfColumns() == 1 && b->numberOfColumns() == 1); + Addition sum = Addition::Builder(); + for (int j = 0; j < numberOfRows(); j++) { + Expression product = Multiplication::Builder(const_cast(this)->matrixChild(0, j).clone(), const_cast(b)->matrixChild(0, 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 + assert(numberOfRows() == 3 && numberOfColumns() == 1 && b->numberOfRows() == 3 && b->numberOfColumns() == 1); + 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)->matrixChild(0, j1).clone(), const_cast(b)->matrixChild(0, j2).clone()); + Expression a2b1 = Multiplication::Builder(const_cast(this)->matrixChild(0, j2).clone(), const_cast(b)->matrixChild(0, 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(3, 1); + return matrix; +} + Expression Matrix::shallowReduce(Context * context) { { Expression e = Expression::defaultShallowReduce(); diff --git a/poincare/src/matrix_complex.cpp b/poincare/src/matrix_complex.cpp index d0fa0b68c4b..eb94bab4012 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -135,6 +135,50 @@ MatrixComplex MatrixComplexNode::ref(bool reduced) const { return MatrixComplex::Builder(operandsCopy, m_numberOfRows, m_numberOfColumns); } +template +std::complex MatrixComplexNode::norm() const { + if (numberOfChildren() == 0 || numberOfColumns() > 1) { + 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 (numberOfChildren() == 0 || numberOfColumns() > 1 || b->numberOfChildren() == 0 || b->numberOfColumns() > 1 || numberOfRows() != b->numberOfRows()) { + 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 (numberOfChildren() == 0 || numberOfColumns() != 1 || numberOfRows() != 3 || b->numberOfChildren() == 0 || b->numberOfColumns() != 1 || b->numberOfRows() != 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, 3, 1); +} + // MATRIX COMPLEX REFERENCE template diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 5064d12f7eb..8770d6220a8 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -110,9 +110,11 @@ class Parser { &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 @@ class Parser { &NaperianLogarithm::s_functionHelper, &CommonLogarithm::s_functionHelper, &Logarithm::s_functionHelper, + &VectorNorm::s_functionHelper, &NormCDF::s_functionHelper, &NormCDF2::s_functionHelper, &NormPDF::s_functionHelper, diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 1b70f0b74f2..4e1d2c511ed 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -369,6 +369,9 @@ 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 MatrixLayout TreeHandle::NAryBuilder(const Tuple &); } diff --git a/poincare/src/vector_cross.cpp b/poincare/src/vector_cross.cpp new file mode 100644 index 00000000000..dc5f6dc196e --- /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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input0 = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation input1 = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); + 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 column matrices of size 3 + if (matrixChild0.numberOfColumns() != 1 || matrixChild1.numberOfColumns() != 1 || matrixChild0.numberOfRows() != 3 || matrixChild1.numberOfRows() != 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 00000000000..5dc5d23f1a2 --- /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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input0 = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + Evaluation input1 = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); + 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 column matrices of the same dimensions + if (matrixChild0.numberOfColumns() != 1 || matrixChild1.numberOfColumns() != 1 || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { + 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 00000000000..aeef1f8a337 --- /dev/null +++ b/poincare/src/vector_norm.cpp @@ -0,0 +1,58 @@ +#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 LayoutHelper::Prefix(VectorNorm(this), floatDisplayMode, numberOfSignificantDigits, VectorNorm::s_functionHelper.name()); +} + +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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); + 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); + if (matrixChild.numberOfColumns() != 1) { + // Norm is only defined on column matrices + return replaceWithUndefinedInPlace(); + } + Expression a = matrixChild.norm(reductionContext); + replaceWithInPlace(a); + return a.shallowReduce(reductionContext); + } + if (c.deepIsMatrix(reductionContext.context())) { + return *this; + } + return replaceWithUndefinedInPlace(); +} + +} From 8d2af7e77f29626eccae9829958bb55924ef712b Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 22 Jul 2020 12:07:46 +0200 Subject: [PATCH 113/560] [poincare/test] Add VectorOperation tests, tidy rref tests Change-Id: I4072bee6b2d3bf1ad17045149bff8dcdb6a2f238 --- poincare/test/approximation.cpp | 29 +++++++++---------- poincare/test/expression_properties.cpp | 1 + poincare/test/parsing.cpp | 7 +++-- poincare/test/simplification.cpp | 38 +++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index b1307d5953d..ec896ab7f37 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -420,26 +420,23 @@ 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]]"); - /* 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_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.2215568862275]]"); assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,3.3532934131737ᴇ-1][0,1,0,-0.1437125748503][0,0,1,1.2215568862275]]"); - assert_expression_approximates_to("ref([[1,0][5,6][0,10]])", "[[1,1.2][0,1][0,0]]"); - assert_expression_approximates_to("rref([[1,0][5,6][0,10]])", "[[1,0][0,1][0,0]]"); - assert_expression_approximates_to("ref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); - assert_expression_approximates_to("rref([[0,0][0,0][0,0]])", "[[0,0][0,0][0,0]]"); assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,9.1666666666667ᴇ-1,8.3333333333333ᴇ-1][0,1,-0.5][0,0,1]]"); assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); - assert_expression_approximates_to("ref([[3,9][2,5]])", "[[1,3][0,1]]"); - assert_expression_approximates_to("ref([[3,2][5,7]])", "[[1,1.4][0,1]]"); - assert_expression_approximates_to("ref([[3,11][5,7]])", "[[1,1.4][0,1]]"); - assert_expression_approximates_to("ref([[2,5][2,7]])", "[[1,2.5][0,1]]"); - assert_expression_approximates_to("ref([[3,12][-4,1]])", "[[1,-0.25][0,1]]"); - assert_expression_approximates_to("ref([[0,1][1ᴇ-100,1]])", "[[1,1ᴇ100][0,1]]"); - assert_expression_approximates_to("rref([[0,1][1ᴇ-100,1]])", "[[1,0][0,1]]"); + assert_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.221557]]"); + assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,0.3353293][0,1,0,-0.1437126][0,0,1,1.221557]]"); + assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0.9166667,0.8333333][0,1,-0.5][0,0,1]]"); + assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,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("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("round(2.3246,3)", "2.325"); assert_expression_approximates_to("round(2.3245,3)", "2.325"); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index b9570c996d4..cc3d11c4dbc 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -65,6 +65,7 @@ QUIZ_CASE(poincare_properties_is_matrix) { 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); } diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index b62ab6571eb..c40c05c9930 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -365,12 +365,14 @@ 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))); @@ -393,6 +395,7 @@ QUIZ_CASE(poincare_parsing_identifiers) { 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)"); @@ -401,9 +404,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))); @@ -414,8 +419,6 @@ QUIZ_CASE(poincare_parsing_identifiers) { assert_parsed_expression_is("tanh(1)", HyperbolicTangent::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("trace(1)", MatrixTrace::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("transpose(1)", MatrixTranspose::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("ref(1)", MatrixRowEchelonForm::Builder(BasedInteger::Builder(1))); - assert_parsed_expression_is("rref(1)", MatrixReducedRowEchelonForm::Builder(BasedInteger::Builder(1))); assert_parsed_expression_is("√(1)", SquareRoot::Builder(BasedInteger::Builder(1))); assert_text_not_parsable("cos(1,2)"); assert_text_not_parsable("log(1,2,3)"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 27b79630319..0a7f4e947db 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -381,10 +381,12 @@ 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"); @@ -411,6 +413,7 @@ QUIZ_CASE(poincare_simplification_units) { 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"); @@ -435,10 +438,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"); @@ -449,8 +454,6 @@ 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("ref(_s)", "undef"); - assert_parsed_expression_simplify_to("rref(_s)", "undef"); /* Valid expressions */ assert_parsed_expression_simplify_to("-2×_A", "-2×_A"); @@ -960,6 +963,37 @@ QUIZ_CASE(poincare_simplification_matrix) { 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("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]]"); + /* 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]])", "[[-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]])", "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"); From 3aa5e8bcd6afd69ad9dfbf99227f31a5c4705297 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 23 Jul 2020 14:29:36 +0200 Subject: [PATCH 114/560] [poincare] Add Vector Norm Layout Change-Id: Iae5bbeabaa0ca627ae6651f187d1a42b970df37d --- .../include/poincare/absolute_value_layout.h | 2 +- .../include/poincare/bracket_pair_layout.h | 1 + poincare/include/poincare/layout_node.h | 1 + poincare/include/poincare/vector_norm.h | 3 +- .../include/poincare/vector_norm_layout.h | 46 +++++++++++++++++++ poincare/include/poincare_layouts.h | 1 + poincare/src/bracket_pair_layout.cpp | 41 +++++++++++++---- poincare/src/horizontal_layout.cpp | 3 +- poincare/src/tree_handle.cpp | 1 + poincare/src/vector_norm.cpp | 5 +- 10 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 poincare/include/poincare/vector_norm_layout.h diff --git a/poincare/include/poincare/absolute_value_layout.h b/poincare/include/poincare/absolute_value_layout.h index d1cab825d6f..6e9c71ff39d 100644 --- a/poincare/include/poincare/absolute_value_layout.h +++ b/poincare/include/poincare/absolute_value_layout.h @@ -29,7 +29,7 @@ class AbsoluteValueLayoutNode final : public BracketPairLayoutNode { private: KDCoordinate widthMargin() const override { return 2; } - virtual KDCoordinate verticalExternMargin() const override { return 1; } + KDCoordinate verticalExternMargin() const override { return 1; } bool renderTopBar() const override { return false; } bool renderBottomBar() const override { return false; } }; diff --git a/poincare/include/poincare/bracket_pair_layout.h b/poincare/include/poincare/bracket_pair_layout.h index f1fcd0879d1..2196c7be901 100644 --- a/poincare/include/poincare/bracket_pair_layout.h +++ b/poincare/include/poincare/bracket_pair_layout.h @@ -49,6 +49,7 @@ class BracketPairLayoutNode : public LayoutNode { virtual KDCoordinate verticalExternMargin() const { return k_verticalExternMargin; } virtual bool renderTopBar() const { return true; } virtual bool renderBottomBar() const { return true; } + virtual bool renderDoubleBar() const { return false; } void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor, Layout * selectionStart = nullptr, Layout * selectionEnd = nullptr, KDColor selectionColor = KDColorRed) override; }; diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index a6a0f670fbb..8e7959cc02c 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -39,6 +39,7 @@ class LayoutNode : public TreeNode { RightParenthesisLayout, RightSquareBracketLayout, SumLayout, + VectorNormLayout, VerticalOffsetLayout }; diff --git a/poincare/include/poincare/vector_norm.h b/poincare/include/poincare/vector_norm.h index f0572ada4f3..c13a6a1d87c 100644 --- a/poincare/include/poincare/vector_norm.h +++ b/poincare/include/poincare/vector_norm.h @@ -25,8 +25,7 @@ class VectorNormNode final : public ExpressionNode { 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; } + 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); } diff --git a/poincare/include/poincare/vector_norm_layout.h b/poincare/include/poincare/vector_norm_layout.h new file mode 100644 index 00000000000..5c10dac87c4 --- /dev/null +++ b/poincare/include/poincare/vector_norm_layout.h @@ -0,0 +1,46 @@ +#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 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_layouts.h b/poincare/include/poincare_layouts.h index 07188050728..72cb49ac657 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/src/bracket_pair_layout.cpp b/poincare/src/bracket_pair_layout.cpp index b42a930b5eb..67ccbf53bf6 100644 --- a/poincare/src/bracket_pair_layout.cpp +++ b/poincare/src/bracket_pair_layout.cpp @@ -114,22 +114,41 @@ 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); + 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(); + } + 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(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); + ctx->fillRect(KDRect(horizontalPosition - k_bracketWidth, verticalPosition, k_bracketWidth, k_lineThickness), expressionColor); } 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 - 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 * k_verticalMargin + 2*verticalExternMargin()); } KDCoordinate BracketPairLayoutNode::computeBaseline() { @@ -138,7 +157,11 @@ KDCoordinate BracketPairLayoutNode::computeBaseline() { 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, k_verticalMargin + verticalExternMargin()); } } diff --git a/poincare/src/horizontal_layout.cpp b/poincare/src/horizontal_layout.cpp index 0f743904bf1..7ec6f4fe221 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/tree_handle.cpp b/poincare/src/tree_handle.cpp index 4e1d2c511ed..992e22cee67 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -372,6 +372,7 @@ 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/vector_norm.cpp b/poincare/src/vector_norm.cpp index aeef1f8a337..85a3ff59b28 100644 --- a/poincare/src/vector_norm.cpp +++ b/poincare/src/vector_norm.cpp @@ -1,9 +1,10 @@ -#include #include #include #include #include #include +#include +#include namespace Poincare { @@ -16,7 +17,7 @@ Expression VectorNormNode::shallowReduce(ReductionContext reductionContext) { } Layout VectorNormNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return LayoutHelper::Prefix(VectorNorm(this), floatDisplayMode, numberOfSignificantDigits, VectorNorm::s_functionHelper.name()); + return VectorNormLayout::Builder(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits)); } int VectorNormNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { From 25124258685461813108c532f72a6ffca6951aa3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 23 Jul 2020 16:18:52 +0200 Subject: [PATCH 115/560] [poincare] Factorize bracket pair rendering Change-Id: I80f2d04c8833352f4097bcfdcf90b5ec147dc19f --- .../include/poincare/bracket_pair_layout.h | 13 +++-- .../include/poincare/vector_norm_layout.h | 1 + poincare/src/bracket_pair_layout.cpp | 54 +++++++++---------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/poincare/include/poincare/bracket_pair_layout.h b/poincare/include/poincare/bracket_pair_layout.h index 2196c7be901..acde19d3fd7 100644 --- a/poincare/include/poincare/bracket_pair_layout.h +++ b/poincare/include/poincare/bracket_pair_layout.h @@ -44,13 +44,18 @@ class BracketPairLayoutNode : public LayoutNode { 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 bool renderDoubleBar() const { return false; } + 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/vector_norm_layout.h b/poincare/include/poincare/vector_norm_layout.h index 5c10dac87c4..e4249a3a0e1 100644 --- a/poincare/include/poincare/vector_norm_layout.h +++ b/poincare/include/poincare/vector_norm_layout.h @@ -29,6 +29,7 @@ class VectorNormLayoutNode final : public BracketPairLayoutNode { 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; } diff --git a/poincare/src/bracket_pair_layout.cpp b/poincare/src/bracket_pair_layout.cpp index 67ccbf53bf6..0163a404347 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,32 +102,42 @@ 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; - KDCoordinate horizontalPosition = p.x() + externWidthMargin(); - KDCoordinate verticalPosition = p.y() + verticalExternMargin(); - if (renderDoubleBar()) { + 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(); + horizontalPosition += k_lineThickness + widthMargin; } ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); - if (renderTopBar()) { + + if (renderTopBar) { ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_bracketWidth, k_lineThickness), expressionColor); } - if (renderBottomBar()) { + if (renderBottomBar) { ctx->fillRect(KDRect(horizontalPosition, verticalPosition + verticalBarHeight - k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); } - horizontalPosition += k_lineThickness + widthMargin() + childSize.width() + widthMargin(); + + horizontalPosition += k_lineThickness + widthMargin + childSize.width() + widthMargin; + ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); horizontalPosition += k_lineThickness; - if (renderTopBar()) { + + if (renderTopBar) { ctx->fillRect(KDRect(horizontalPosition - k_bracketWidth, verticalPosition, k_bracketWidth, k_lineThickness), expressionColor); } - if (renderBottomBar()) { + if (renderBottomBar) { ctx->fillRect(KDRect(horizontalPosition - k_bracketWidth, verticalPosition + verticalBarHeight - k_lineThickness, k_bracketWidth, k_lineThickness), expressionColor); } - if (renderDoubleBar()) { - horizontalPosition += widthMargin(); + + if (renderDoubleBar) { + horizontalPosition += widthMargin; ctx->fillRect(KDRect(horizontalPosition, verticalPosition, k_lineThickness, verticalBarHeight), expressionColor); } } @@ -148,11 +148,11 @@ KDSize BracketPairLayoutNode::computeSize() { if (renderDoubleBar()) { horizontalCoordinates += 2*k_lineThickness + 2*widthMargin(); } - return KDSize(horizontalCoordinates, childSize.height() + 2 * k_verticalMargin + 2*verticalExternMargin()); + 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) { @@ -161,7 +161,7 @@ KDPoint BracketPairLayoutNode::positionOfChild(LayoutNode * child) { if (renderDoubleBar()) { horizontalCoordinates += k_lineThickness + widthMargin(); } - return KDPoint(horizontalCoordinates, k_verticalMargin + verticalExternMargin()); + return KDPoint(horizontalCoordinates, verticalMargin() + verticalExternMargin()); } } From 685b2a8d6dcd5c6b5bc40045a3aed2f3edbea5b3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 24 Jul 2020 09:46:56 +0200 Subject: [PATCH 116/560] [poincare] Reduce Abs layout verticalMargin Change-Id: I3edacc192b0274d73fd787bb81b0c895e29ddda3 --- poincare/include/poincare/absolute_value_layout.h | 1 + 1 file changed, 1 insertion(+) diff --git a/poincare/include/poincare/absolute_value_layout.h b/poincare/include/poincare/absolute_value_layout.h index 6e9c71ff39d..e0a81610f49 100644 --- a/poincare/include/poincare/absolute_value_layout.h +++ b/poincare/include/poincare/absolute_value_layout.h @@ -29,6 +29,7 @@ class AbsoluteValueLayoutNode final : public BracketPairLayoutNode { 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; } From edcc1f6e8029569e0efa0215438b09bd7449e9e2 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Fri, 7 Aug 2020 10:27:43 +0200 Subject: [PATCH 117/560] [Poincare/integral_layout.cpp] Fixed crash due to infinite loop Changed previousNestedIntegral method to prevent integrals located in integral bounds to be considered as nested. Change-Id: Id8cc4369f53c278ac59039fde1c2818af2ccacab --- poincare/src/integral_layout.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index 253d429caec..6d6119560ea 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -251,8 +251,13 @@ IntegralLayoutNode * IntegralLayoutNode::previousNestedIntegral() { return nullptr; } if (p->type() == Type::IntegralLayout) { - // Parent can be an integral - return static_cast(p); + // 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(); From 5b82b94c207267a8b8e67bfc3d58340cf39c3c64 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 10 Aug 2020 11:55:26 +0200 Subject: [PATCH 118/560] [poincare] Prevent forbidden ChildAtIndex call in fraction_layout Change-Id: I00d282a6dd3618f669aaeb0202d6495ae8035f12 --- poincare/src/fraction_layout.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/poincare/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index ac035cf1b3d..2ecc7945385 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -175,8 +175,10 @@ bool FractionLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool going int indexOfAbsorbingSibling = indexInParent + (goingLeft ? 1 : -1); assert(indexOfAbsorbingSibling >= 0 && indexOfAbsorbingSibling < p.numberOfChildren()); Layout absorbingSibling = p.childAtIndex(indexOfAbsorbingSibling); - Layout absorbingChild = absorbingSibling.childAtIndex((goingLeft) ? absorbingSibling.leftCollapsingAbsorbingChildIndex() : absorbingSibling.rightCollapsingAbsorbingChildIndex()); - return absorbingChild.type() == LayoutNode::Type::HorizontalLayout && absorbingChild.isEmpty(); + 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) { From ec0e766c947c6b31501dc0dd445562e62e26304b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 11 Aug 2020 14:41:06 +0200 Subject: [PATCH 119/560] [apps/graph] text_field_function_title_cell: fix read of uninitialized buffer This fixes the following crash: when reloading the cell before initializing the m_textFieldBuffer, TextField::ContentView::text() will use the content of the uninitialized m_textFieldBuffer to compute the minimalSizeForOptimalDisplay. --- apps/graph/list/text_field_function_title_cell.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index a4ef2a48bdf..fd801cda2c8 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("") { } From 74a2211a6a5085bfcdbed899e323942956610f11 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 10 Aug 2020 16:09:59 +0200 Subject: [PATCH 120/560] [poincare] Use default methods for GCD and LCM instead of n_ary ones Change-Id: Id353e4367bcb6700bae2f9c121af93b9379a8051 --- poincare/Makefile | 1 + poincare/include/poincare/addition.h | 6 +- poincare/include/poincare/expression.h | 1 + poincare/include/poincare/expression_node.h | 1 + poincare/include/poincare/multiplication.h | 6 +- poincare/include/poincare/n_ary_expression.h | 8 --- .../include/poincare/n_ary_infix_expression.h | 23 ++++++++ poincare/src/multiplication.cpp | 2 +- poincare/src/n_ary_expression.cpp | 54 ------------------ poincare/src/n_ary_infix_expression.cpp | 57 +++++++++++++++++++ 10 files changed, 90 insertions(+), 69 deletions(-) create mode 100644 poincare/include/poincare/n_ary_infix_expression.h create mode 100644 poincare/src/n_ary_infix_expression.cpp diff --git a/poincare/Makefile b/poincare/Makefile index a6c0524b981..7ffd237cb59 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -106,6 +106,7 @@ poincare_src += $(addprefix poincare/src/,\ 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 \ diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 37ca11506b8..346c6c11a83 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); } diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 6e71cc117a7..4639195aaac 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -120,6 +120,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; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 8e56f312697..c08435fc33d 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -20,6 +20,7 @@ class ExpressionNode : public TreeNode { friend class AdditionNode; friend class DivisionNode; friend class NAryExpressionNode; + friend class NAryInfixExpressionNode; friend class PowerNode; friend class SymbolNode; public: diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 4c75085a714..fbd62e1438f 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); } diff --git a/poincare/include/poincare/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index b91095cbbd1..ed5a65846ee 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 @@ class NAryExpressionNode : public ExpressionNode { // TODO: VariableArityExpress } 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 @@ class NAryExpressionNode : public ExpressionNode { // TODO: VariableArityExpress /* 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 { 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 00000000000..545741474bf --- /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/src/multiplication.cpp b/poincare/src/multiplication.cpp index 3a3b5f92611..f647d6c4d34 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -57,7 +57,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}; diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index e0fdc1cd758..2303cc0d460 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -1,5 +1,4 @@ #include -#include #include extern "C" { #include @@ -9,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(); @@ -60,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 diff --git a/poincare/src/n_ary_infix_expression.cpp b/poincare/src/n_ary_infix_expression.cpp new file mode 100644 index 00000000000..0433c143af8 --- /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 From f562886a7ed0c5a6ff17f87efd5abbaa1f3fe6f1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 11 Aug 2020 11:26:48 +0200 Subject: [PATCH 121/560] [poincare] Fixed crash due to infinite loop with horizontal layout Change-Id: I00106525b29ce441e10a6d88766efdecd80d8291 --- poincare/src/integral_layout.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index 6d6119560ea..9729fb9e64a 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -266,7 +266,10 @@ IntegralLayoutNode * IntegralLayoutNode::previousNestedIntegral() { } 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 - return static_cast(prev); + // The horizontal layout must be the previous integral's integrand + if (prev->childAtIndex(0) == p) { + return static_cast(prev); + } } } return nullptr; From 58b69c277983dfcc63ae82a845039d1d6717d346 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Fri, 31 Jul 2020 17:41:12 +0200 Subject: [PATCH 122/560] [AbsoluteValue] Adding formal derivate to absolute value This will prevent diff(|x|, x, 0) to be evaluated to 0 instead of undef This fixes issue #1393 Change-Id: I73f40aa29f04c373cd3e9edbbd4fbe7be6d7a988 --- poincare/include/poincare/absolute_value.h | 3 +++ poincare/src/absolute_value.cpp | 20 ++++++++++++++++++++ poincare/test/derivative.cpp | 4 ++++ 3 files changed, 27 insertions(+) diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index 57be4dd32b1..bdbfc37d579 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -41,6 +41,8 @@ class AbsoluteValueNode final : public ExpressionNode { // 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 +54,7 @@ friend class AbsoluteValueNode; 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/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index 6945e1fc503..905f08d2223 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(); @@ -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/test/derivative.cpp b/poincare/test/derivative.cpp index f4da054b258..05d2a0a1a1f 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -159,6 +159,10 @@ QUIZ_CASE(poincare_derivative_functions) { assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,z)", "undef"); assert_parses_and_reduces_as("diff(sinh(x),x,z)", "undef"); assert_parses_and_reduces_as("diff(cosh(x),x,z)", "undef"); + + assert_parses_and_reduces_as("diff(abs(x),x,0)", "undef"); + assert_parses_and_reduces_as("diff(abs(x),x,1)", "1"); + assert_parses_and_reduces_as("diff(abs(-2x),x,1)", "2"); #endif emptyGlobalContext(); From aa14a6a82eff717ef80e7180e4383589a2c620f1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 22 Jul 2020 15:48:18 +0200 Subject: [PATCH 123/560] [apps/apps_conainer] Add Home and PowerOn/Off to interrupt computations Change-Id: I3951cda606760843fb0703864b9da078bf7fd887 --- apps/apps_container.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index d583909674b..a8cb20aa04a 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -52,7 +52,7 @@ AppsContainer::AppsContainer() : bool AppsContainer::poincareCircuitBreaker() { Ion::Keyboard::State state = Ion::Keyboard::scan(); - return state.keyDown(Ion::Keyboard::Key::Back); + return state.keyDown(Ion::Keyboard::Key::Back) || state.keyDown(Ion::Keyboard::Key::Home) || state.keyDown(Ion::Keyboard::Key::OnOff); } App::Snapshot * AppsContainer::hardwareTestAppSnapshot() { From 206013f2db06e95e985aad80b01b8b6af0fbf44d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 17 Aug 2020 12:11:25 +0200 Subject: [PATCH 124/560] [poincare/multiplication] Return Undef early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiplication::privateShallowReduce can create an Undef node by multiplying 0 and inf. In this case, we set the whole multiplication to undef, to avoid things such as undef*π. This fixes the following bug : - In Graph, enter i*inf*π as one of the bounds. An assertion fails in Multiplication::removeUnit. Change-Id: Ie9d0d561d6e310f52220b98541f22a4b5e56762c --- poincare/src/multiplication.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index f647d6c4d34..bf518c2ce9c 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -778,6 +778,9 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext SetInterruption(true); return *this; } + if (m.isUndefined()) { + return replaceWithUndefinedInPlace(); + } replaceChildAtIndexInPlace(0, m); removeChildAtIndexInPlace(i); } else { From 0a7833ec3d6e636fcdb52cc027c99ced53d4fd4a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 17 Aug 2020 12:12:25 +0200 Subject: [PATCH 125/560] =?UTF-8?q?[poincare/test]=20Added=200*inf*=CF=80?= =?UTF-8?q?=20=3D=20undef=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ie18e75c3cfd2a39f8436405f1061ba8fc5317b7f --- poincare/test/simplification.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 0a7f4e947db..301f6b8f95b 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"); From 87591369b830d8e5bf75addd3e88029453d71727 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 15:59:37 +0200 Subject: [PATCH 126/560] [apps/regression] Model stays selected in menu When opening the window for choosing the regression model, the row corresponding to the currently selected model will be highlighted. Change-Id: I65772186fc302c3706a5571f98ecff620e4a8ca9 --- apps/regression/regression_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/regression/regression_controller.cpp b/apps/regression/regression_controller.cpp index 188007d6b78..b9fd1d43447 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); } From d156c8c99ed8226e19fd320dc1e18772ac11dbd2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 18 Jun 2020 17:20:24 +0200 Subject: [PATCH 127/560] [apps/i18n] Added country support Added a list of supported countries to I18n, and created messages for each country's names in each language. Change-Id: I1b887d75da980d99e21f89cc151249e42c78de88 --- apps/Makefile | 2 +- apps/i18n.py | 24 ++++++++++++++++++++---- apps/shared.de.i18n | 11 +++++++++++ apps/shared.en.i18n | 11 +++++++++++ apps/shared.es.i18n | 11 +++++++++++ apps/shared.fr.i18n | 11 +++++++++++ apps/shared.it.i18n | 11 +++++++++++ apps/shared.nl.i18n | 11 +++++++++++ apps/shared.pt.i18n | 11 +++++++++++ build/config.mak | 1 + escher/include/escher/i18n.h | 2 +- 11 files changed, 100 insertions(+), 6 deletions(-) diff --git a/apps/Makefile b/apps/Makefile index bd71950417d..a76386d2120 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -71,7 +71,7 @@ $(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) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \ global \ )) diff --git a/apps/i18n.py b/apps/i18n.py index a6130e49a8d..6f9dddcc9c1 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -17,6 +17,7 @@ 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('--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)') @@ -114,7 +115,7 @@ def parse_codepoints(file): codepoints = parse_codepoints(args.codepoints) -def print_header(data, path, locales): +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") @@ -122,6 +123,7 @@ def print_header(data, path, locales): 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") @@ -135,10 +137,10 @@ def print_header(data, path, locales): f.write("};\n\n") # Languages enumeration - f.write("enum class Language : uint16_t {\n") + f.write("enum class Language : uint8_t {\n") index = 0 for locale in locales: - f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") +",\n") + f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") + ",\n") index = index + 1 f.write("};\n\n") @@ -155,6 +157,20 @@ def print_header(data, path, locales): f.write(" Message::LanguageISO6391" + locale.upper() + ",\n") f.write("};\n\n") + # Countries enumeration + f.write("enum class Country : uint8_t {\n") + index = 0 + for country in countries: + f.write(" " + country.upper() + (" = 0" if (index < 1) else "") + ",\n") + index += 1 + f.write("};\n\n") + + # Country names + f.write("constexpr const Message CountryNames[NumberOfCountries] = {\n"); + for country in countries: + f.write(" Message::Country" + country.upper() + ",\n") + f.write("};\n\n") + f.write("}\n\n") f.write("#endif\n") f.close() @@ -226,6 +242,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/shared.de.i18n b/apps/shared.de.i18n index b868b539563..df2df5f3dff 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -12,6 +12,17 @@ Cancel = "Abbrechen" ClearColumn = "Spalte löschen" ColumnOptions = "Optionen der Spalte" 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 " DataNotSuitable = "Daten nicht geeignet" DataTab = "Daten" DefaultSetting = "Grundeinstellung" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 2ddbf867b64..589744078a5 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -12,6 +12,17 @@ Cancel = "Cancel" ClearColumn = "Clear column" ColumnOptions = "Column options" 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 " DataNotSuitable = "Data not suitable" DataTab = "Data" DefaultSetting = "Basic settings" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 339be457f93..8b59ee580a2 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -12,6 +12,17 @@ Cancel = "Cancelar" ClearColumn = "Borrar la columna" ColumnOptions = "Opciones de la columna" 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 " DataNotSuitable = "Datos no adecuados" DataTab = "Datos" DefaultSetting = "Ajustes básicos" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 002e27297bd..c6e9ce7cf17 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -12,6 +12,17 @@ Cancel = "Annuler" ClearColumn = "Effacer la colonne" ColumnOptions = "Options de la colonne" 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 " DataNotSuitable = "Les données ne conviennent pas" DataTab = "Données" DefaultSetting = "Réglages de base" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index b18957f671e..579f7fcc346 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -12,6 +12,17 @@ Cancel = "Annullare" ClearColumn = "Cancella la colonna" ColumnOptions = "Opzioni colonna" 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 " DataNotSuitable = "I dati non sono adeguati" DataTab = "Dati" DefaultSetting = "Impostazioni di base" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index a2f65a93524..b12190ca1ab 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -12,6 +12,17 @@ Cancel = "Annuleer" ClearColumn = "Wis kolom" ColumnOptions = "Kolomopties" 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 " DataNotSuitable = "Gegevens niet geschikt" DataTab = "Gegevens" DefaultSetting = "Standaardinstelling" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index d3cfd3249a1..036d6e99eda 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -12,6 +12,17 @@ Cancel = "Cancelar" ClearColumn = "Excluir coluna" ColumnOptions = "Opções de coluna" 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 " DataNotSuitable = "Dados não adequados" DataTab = "Dados" DefaultSetting = "Configurações básicas" diff --git a/build/config.mak b/build/config.mak index e6b20d51537..e8e8074c600 100644 --- a/build/config.mak +++ b/build/config.mak @@ -6,6 +6,7 @@ DEBUG ?= 0 EPSILON_VERSION ?= 14.4.1 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es +EPSILON_COUNTRIES ?= CA DE ES FR GB IT NL PT US WW EPSILON_GETOPT ?= 0 EPSILON_TELEMETRY ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 diff --git a/escher/include/escher/i18n.h b/escher/include/escher/i18n.h index 301b7ef9b4c..11de7fddb47 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(); } From 3f9b8f03af1da01524eb8a4c14219dbd2b5c4a18 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 18 Jun 2020 17:22:46 +0200 Subject: [PATCH 128/560] [apps/global_preferences] Added country preference Change-Id: Ifbe09e2a24b4e1b9386f323bd5ebd9bed7fc8440 --- apps/global_preferences.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/global_preferences.h b/apps/global_preferences.h index fb82cbe0c97..0001787f82c 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -14,6 +14,8 @@ class GlobalPreferences { 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; } bool isInExamMode() const { return (int8_t)examMode() > 0; } ExamMode examMode() const; void setExamMode(ExamMode examMode); @@ -25,14 +27,17 @@ class GlobalPreferences { void setFont(const KDFont * font) { m_font = font; } constexpr static int NumberOfBrightnessStates = 12; 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_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; From 1642276678814ff474c4e7b3089bb067306bf558 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 18 Jun 2020 17:25:08 +0200 Subject: [PATCH 129/560] [apps/shared] CountryController Created a CountryController class to chose the country in the settings and at onboarding. Change-Id: I6787e8b45744a8a98e83f59e5d2c4760364635cb --- apps/shared/Makefile | 1 + apps/shared/country_controller.cpp | 51 ++++++++++++++++++++++++++++++ apps/shared/country_controller.h | 37 ++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 apps/shared/country_controller.cpp create mode 100644 apps/shared/country_controller.h diff --git a/apps/shared/Makefile b/apps/shared/Makefile index b525a9033ef..481c962e09d 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -26,6 +26,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + country_controller.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp new file mode 100644 index 00000000000..5efce8a6ffe --- /dev/null +++ b/apps/shared/country_controller.cpp @@ -0,0 +1,51 @@ +#include "country_controller.h" +#include "../global_preferences.h" +#include "../apps_container.h" + +namespace Shared { + +CountryController::CountryController(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::NumberOfCountries; i++) { + m_cells[i].setMessageFont(KDFont::LargeFont); + } +} + +void CountryController::resetSelection() { + m_selectableTableView.deselectTable(); + selectCellAtLocation(0, static_cast(GlobalPreferences::sharedGlobalPreferences()->country())); +} + + +void CountryController::viewWillAppear() { + ViewController::viewWillAppear(); + resetSelection(); + /* FIXME : When selecting a country, changing the language, then coming back + * to select a country, some countries' names will be cropped. We force the + * TableView to refresh to prevent that. */ + m_selectableTableView.reloadData(); +} + +bool CountryController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + /* TODO : Since the order in which the countries are displayed changes with + * the language, we need something more sophisticated + * than (I18n::Country)selectedRow() */ + GlobalPreferences::sharedGlobalPreferences()->setCountry(static_cast(selectedRow())); + return true; + } + return false; +} + +void CountryController::willDisplayCellForIndex(HighlightCell * cell, int index) { + /* TODO : We want the countries to always appear in alphabetical order. The + * index from which the country's name is fetched must take that into + * account.*/ + static_cast(cell)->setMessage(I18n::CountryNames[index]); +} + +} diff --git a/apps/shared/country_controller.h b/apps/shared/country_controller.h new file mode 100644 index 00000000000..8cf355e0751 --- /dev/null +++ b/apps/shared/country_controller.h @@ -0,0 +1,37 @@ +#ifndef SHARED_COUNTRY_CONTROLLER_H +#define SHARED_COUNTRY_CONTROLLER_H + +#include +#include + +namespace Shared { + +class CountryController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + CountryController(Responder * parentResponder, KDCoordinate verticalMargin); + void resetSelection(); + + View * view() override { return &m_selectableTableView; } + const char * title() override {return I18n::translate(I18n::Message::Country); } + void didBecomeFirstResponder() override {Container::activeApp()->setFirstResponder(&m_selectableTableView); } + void viewWillAppear() override; + bool handleEvent(Ion::Events::Event event) override; + + int numberOfRows() const override { return I18n::NumberOfCountries; } + KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } + HighlightCell * reusableCell(int index) override { return &m_cells[index]; } + int reusableCellCount() const override { return I18n::NumberOfCountries; } + + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + +protected: + SelectableTableView m_selectableTableView; + +private: + MessageTableCell m_cells[I18n::NumberOfCountries]; + // TODO : Add variables for static text +}; + +} + +#endif From 51747d3d36e75c02622803b4af01b5aa40dad7c2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 1 Jul 2020 10:21:22 +0200 Subject: [PATCH 130/560] [apps/settings] Added country to settings Refactored Settings::MainController::handleEvent Change-Id: Id8ad604183a64520dd785356514e87fddc50354c --- apps/settings/Makefile | 3 +- apps/settings/main_controller.cpp | 88 ++++++++++--------- apps/settings/main_controller.h | 7 +- apps/settings/main_controller_prompt_beta.cpp | 1 + apps/settings/main_controller_prompt_none.cpp | 1 + .../main_controller_prompt_update.cpp | 1 + apps/settings/sub_menu/country_controller.cpp | 13 +++ apps/settings/sub_menu/country_controller.h | 17 ++++ 8 files changed, 88 insertions(+), 43 deletions(-) create mode 100644 apps/settings/sub_menu/country_controller.cpp create mode 100644 apps/settings/sub_menu/country_controller.h diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 8f954d66813..17b679bb258 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -12,9 +12,10 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/about_controller.cpp \ sub_menu/about_controller_official.cpp:+official \ sub_menu/about_controller_non_official.cpp:-official \ + sub_menu/country_controller.cpp \ + sub_menu/display_mode_controller.cpp \ sub_menu/exam_mode_controller_official.cpp:+official \ sub_menu/exam_mode_controller_non_official.cpp:-official \ - sub_menu/display_mode_controller.cpp \ sub_menu/exam_mode_controller.cpp \ sub_menu/generic_sub_controller.cpp \ sub_menu/language_controller.cpp \ diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index c8b922a81ee..c15e67c15e1 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -23,6 +23,7 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_preferencesController(this), m_displayModeController(this, inputEventHandlerDelegate), m_languageController(this, Metric::CommonTopMargin), + m_countryController(this, Metric::CommonTopMargin), m_examModeController(this), m_aboutController(this) { @@ -44,52 +45,54 @@ void MainController::didBecomeFirstResponder() { bool MainController::handleEvent(Ion::Events::Event event) { GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); - if (model()->childAtIndex(selectedRow())->numberOfChildren() == 0) { - if (model()->childAtIndex(selectedRow())->label() == promptMessage()) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - globalPreferences->setShowPopUp(!globalPreferences->showPopUp()); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - return true; - } - return false; - } - if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Brightness) { - if (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus) { - int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; - globalPreferences->setBrightnessLevel(globalPreferences->brightnessLevel()+direction); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - return true; - } - return false; - } - if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language) { - if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - stackController()->push(&m_languageController); - return true; - } - return false; + int rowIndex = selectedRow(); + + if (hasPrompt() && rowIndex == k_indexOfPopUpCell) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + globalPreferences->setShowPopUp(!globalPreferences->showPopUp()); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + return true; } + return false; + } + + if (rowIndex == k_indexOfBrightnessCell + && (event == Ion::Events::Left || event == Ion::Events::Right || event == Ion::Events::Minus || event == Ion::Events::Plus)) { + int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; + int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; + globalPreferences->setBrightnessLevel(globalPreferences->brightnessLevel()+direction); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + return true; } + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - GenericSubController * subController = nullptr; - int rowIndex = selectedRow(); - if (rowIndex == k_indexOfDisplayModeCell) { - subController = &m_displayModeController; - } else if (rowIndex == k_indexOfBrightnessCell || rowIndex == k_indexOfLanguageCell) { - assert(false); - } else if (rowIndex == k_indexOfExamModeCell) { - subController = &m_examModeController; - } else if (rowIndex == k_indexOfAboutCell + hasPrompt()) { - subController = &m_aboutController; - } else { - subController = &m_preferencesController; + assert(rowIndex != k_indexOfBrightnessCell); + /* The About cell can either be found at index k_indexOfExamModeCell + 1 or + * k_indexOfExamModeCell + 2, depending on whether there is a Pop-Up cell. + * Since the Pop-Up cell has been handled above, we can use those two + * indices for the About cell. */ + ViewController * subControllers[k_indexOfAboutCell + 2] = { + &m_preferencesController, + &m_displayModeController, + &m_preferencesController, + &m_preferencesController, + nullptr, //&m_brightnessController + &m_preferencesController, + &m_languageController, + &m_countryController, + &m_examModeController, + &m_aboutController, + &m_aboutController + }; + ViewController * selectedSubController = subControllers[rowIndex]; + assert(selectedSubController); + if (model()->childAtIndex(rowIndex)->numberOfChildren() != 0) { + static_cast(selectedSubController)->setMessageTreeModel(model()->childAtIndex(rowIndex)); } - subController->setMessageTreeModel(model()->childAtIndex(selectedRow())); - StackViewController * stack = stackController(); - stack->push(subController); + stackController()->push(selectedSubController); return true; } + return false; } @@ -168,6 +171,11 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { static_cast(cell)->setSubtitle(I18n::LanguageNames[index]); return; } + if (index == k_indexOfCountryCell) { + int index = (int)(globalPreferences->country()); + static_cast(cell)->setSubtitle(I18n::CountryNames[index]); + return; + } if (hasPrompt() && index == k_indexOfPopUpCell) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index d8a42f97552..4e887aa0f9d 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -5,6 +5,7 @@ #include #include "message_table_cell_with_gauge_with_separator.h" #include "sub_menu/about_controller.h" +#include "sub_menu/country_controller.h" #include "sub_menu/display_mode_controller.h" #include "sub_menu/exam_mode_controller.h" #include "sub_menu/language_controller.h" @@ -45,7 +46,8 @@ class MainController : public ViewController, public ListViewDataSource, public constexpr static int k_indexOfBrightnessCell = k_indexOfComplexFormatCell + 1; constexpr static int k_indexOfFontCell = k_indexOfBrightnessCell + 1; constexpr static int k_indexOfLanguageCell = k_indexOfFontCell + 1; - constexpr static int k_indexOfExamModeCell = k_indexOfLanguageCell + 1; + constexpr static int k_indexOfCountryCell = k_indexOfLanguageCell + 1; + constexpr static int k_indexOfExamModeCell = k_indexOfCountryCell + 1; /* Pop-up cell and About cell are located at the same index because pop-up * cell is optional. We must always correct k_indexOfAboutCell with * hasPrompt() (TODO: make hasPrompt() constexpr and correct @@ -56,7 +58,7 @@ class MainController : public ViewController, public ListViewDataSource, public StackViewController * stackController() const; I18n::Message promptMessage() const; bool hasPrompt() const { return promptMessage() != I18n::Message::Default; } - constexpr static int k_numberOfSimpleChevronCells = 7; + constexpr static int k_numberOfSimpleChevronCells = (Ion::Display::Height - Metric::TitleBarHeight) / Metric::ParameterCellHeight + 1; MessageTableCellWithChevronAndMessage m_cells[k_numberOfSimpleChevronCells]; MessageTableCellWithGaugeWithSeparator m_brightnessCell; MessageTableCellWithSwitch m_popUpCell; @@ -64,6 +66,7 @@ class MainController : public ViewController, public ListViewDataSource, public PreferencesController m_preferencesController; DisplayModeController m_displayModeController; LanguageController m_languageController; + CountryController m_countryController; ExamModeController m_examModeController; AboutController m_aboutController; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index 900c6a22cf4..65e2a1d242f 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -14,6 +14,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Brightness), 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 153c7c49a55..f3c0e0ef80d 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -14,6 +14,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 38f19d3ebff..b9eeafb31be 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -14,6 +14,7 @@ constexpr SettingsMessageTree s_modelMenu[] = SettingsMessageTree(I18n::Message::Brightness), 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::About, s_modelAboutChildren)}; diff --git a/apps/settings/sub_menu/country_controller.cpp b/apps/settings/sub_menu/country_controller.cpp new file mode 100644 index 00000000000..f9b450be87a --- /dev/null +++ b/apps/settings/sub_menu/country_controller.cpp @@ -0,0 +1,13 @@ +#include "country_controller.h" + +namespace Settings { + +bool CountryController::handleEvent(Ion::Events::Event event) { + if (Shared::CountryController::handleEvent(event) || event == Ion::Events::Left) { + static_cast(parentResponder())->pop(); + return true; + } + return false; +} + +} diff --git a/apps/settings/sub_menu/country_controller.h b/apps/settings/sub_menu/country_controller.h new file mode 100644 index 00000000000..bfdac2d814a --- /dev/null +++ b/apps/settings/sub_menu/country_controller.h @@ -0,0 +1,17 @@ +#ifndef SETTING_COUNTRY_CONTROLLER_H +#define SETTING_COUNTRY_CONTROLLER_H + +#include +#include "../../shared/country_controller.h" + +namespace Settings { + +class CountryController : public Shared::CountryController { +public: + using Shared::CountryController::CountryController; + bool handleEvent(Ion::Events::Event event) override; +}; + +} + +#endif From fe25118d047a6f90753d1c78ce3909a1163460a6 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 1 Jul 2020 10:23:36 +0200 Subject: [PATCH 131/560] [apps/shared] Sorted countries in menu Country names now appear in alphabetical order in the menu, depending on the chosen language. Change-Id: I57b8ce0a7120a3c8a433ce1b05628842c4d0aeb5 --- apps/shared/country_controller.cpp | 42 +++++++++++++++++++++++------- apps/shared/country_controller.h | 3 +++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp index 5efce8a6ffe..c4a456bcbea 100644 --- a/apps/shared/country_controller.cpp +++ b/apps/shared/country_controller.cpp @@ -1,9 +1,39 @@ #include "country_controller.h" #include "../global_preferences.h" #include "../apps_container.h" +#include namespace Shared { +int CountryController::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 CountryController::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; +} + CountryController::CountryController(Responder * parentResponder, KDCoordinate verticalMargin) : ViewController(parentResponder), m_selectableTableView(this, this, this) @@ -17,7 +47,7 @@ CountryController::CountryController(Responder * parentResponder, KDCoordinate v void CountryController::resetSelection() { m_selectableTableView.deselectTable(); - selectCellAtLocation(0, static_cast(GlobalPreferences::sharedGlobalPreferences()->country())); + selectCellAtLocation(0, IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country())); } @@ -32,20 +62,14 @@ void CountryController::viewWillAppear() { bool CountryController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { - /* TODO : Since the order in which the countries are displayed changes with - * the language, we need something more sophisticated - * than (I18n::Country)selectedRow() */ - GlobalPreferences::sharedGlobalPreferences()->setCountry(static_cast(selectedRow())); + GlobalPreferences::sharedGlobalPreferences()->setCountry(CountryAtIndex(selectedRow())); return true; } return false; } void CountryController::willDisplayCellForIndex(HighlightCell * cell, int index) { - /* TODO : We want the countries to always appear in alphabetical order. The - * index from which the country's name is fetched must take that into - * account.*/ - static_cast(cell)->setMessage(I18n::CountryNames[index]); + static_cast(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); } } diff --git a/apps/shared/country_controller.h b/apps/shared/country_controller.h index 8cf355e0751..6b92dd71db2 100644 --- a/apps/shared/country_controller.h +++ b/apps/shared/country_controller.h @@ -8,6 +8,9 @@ namespace Shared { class CountryController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { public: + static int IndexOfCountry(I18n::Country country); + static I18n::Country CountryAtIndex(int i); + CountryController(Responder * parentResponder, KDCoordinate verticalMargin); void resetSelection(); From a96b5d1bc4c25d92a798888178bbc946d68286dc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 19 Jun 2020 15:17:11 +0200 Subject: [PATCH 132/560] [escher] Typo in StackViewController Change-Id: I13bdd05759fd2f0bc6911adafe8950e46c2bfe85 --- escher/include/escher/stack_view_controller.h | 2 +- escher/src/stack_view_controller.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index d13d4fb9d3a..a49a22ad288 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -46,7 +46,7 @@ class StackViewController : public ViewController { 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/src/stack_view_controller.cpp b/escher/src/stack_view_controller.cpp index 1b32e08797a..6cfbcc278ac 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; From 509a7e462812e18e1f4038a3ac039b58c7812d2a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 19 Jun 2020 15:22:53 +0200 Subject: [PATCH 133/560] [apps/on_boarding] Added country to onboarding Change-Id: I55c50330baf226826e44467756bcd9e5c89d4262 --- apps/on_boarding/Makefile | 1 + apps/on_boarding/app.cpp | 7 ++-- apps/on_boarding/app.h | 9 +++++ apps/on_boarding/country_controller.cpp | 43 ++++++++++++++++++++++++ apps/on_boarding/country_controller.h | 19 +++++++++++ apps/on_boarding/language_controller.cpp | 4 ++- apps/on_boarding/language_controller.h | 5 ++- apps/shared/country_controller.h | 2 +- 8 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 apps/on_boarding/country_controller.cpp create mode 100644 apps/on_boarding/country_controller.h diff --git a/apps/on_boarding/Makefile b/apps/on_boarding/Makefile index 42d22f50802..c65dbc6332d 100644 --- a/apps/on_boarding/Makefile +++ b/apps/on_boarding/Makefile @@ -1,5 +1,6 @@ app_on_boarding_src = $(addprefix apps/on_boarding/,\ app.cpp \ + country_controller.cpp \ language_controller.cpp \ logo_controller.cpp \ logo_view.cpp \ diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index f4c8697b49b..ae12d0792d8 100644 --- a/apps/on_boarding/app.cpp +++ b/apps/on_boarding/app.cpp @@ -13,8 +13,10 @@ App::Descriptor * App::Snapshot::descriptor() { } App::App(Snapshot * snapshot) : - ::App(snapshot, &m_languageController), - m_languageController(&m_modalViewController), + ::App(snapshot, &m_stackController), + m_stackController(&m_modalViewController, &m_languageController), + m_languageController(&m_stackController), + m_countryController(&m_languageController), m_logoController() { } @@ -45,6 +47,7 @@ void App::didBecomeActive(Window * window) { void App::reinitOnBoarding() { m_languageController.resetSelection(); + m_countryController.resetSelection(); displayModalViewController(&m_logoController, 0.5f, 0.5f); } diff --git a/apps/on_boarding/app.h b/apps/on_boarding/app.h index 24a600343ef..a965ce08783 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -2,6 +2,7 @@ #define ON_BOARDING_APP_H #include +#include "country_controller.h" #include "language_controller.h" #include "logo_controller.h" @@ -14,6 +15,12 @@ class App : public ::App { App * unpack(Container * container) override; Descriptor * descriptor() override; }; + + static App * app() { + return static_cast(Container::activeApp()); + } + + CountryController * countryController() { return &m_countryController; } int numberOfTimers() override; Timer * timerAtIndex(int i) override; bool processEvent(Ion::Events::Event) override; @@ -21,7 +28,9 @@ class App : public ::App { private: App(Snapshot * snapshot); void reinitOnBoarding(); + StackViewController m_stackController; LanguageController m_languageController; + CountryController m_countryController; LogoController m_logoController; }; diff --git a/apps/on_boarding/country_controller.cpp b/apps/on_boarding/country_controller.cpp new file mode 100644 index 00000000000..d8d5d2482ad --- /dev/null +++ b/apps/on_boarding/country_controller.cpp @@ -0,0 +1,43 @@ +#include "country_controller.h" +#include "../apps_container.h" +#include +#include + +namespace OnBoarding { + +CountryController::CountryController(Responder * parentResponder) : + Shared::CountryController( + parentResponder, + std::max( + static_cast(Metric::CommonLeftMargin), + (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)) +{ + static_cast(m_selectableTableView.decorator()->indicatorAtIndex(1))->setMargin( + std::max( + static_cast(Metric::CommonLeftMargin), + (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)); +} + +void CountryController::resetSelection() { + m_selectableTableView.deselectTable(); + /* The base ::CountryController behaviour is to highlight the previously + * chosen country. On boarding, we want the highlighted cell to be the first + * alphabetically, but with the default behaviour, it would be Canada, as it + * is the country of value 0. */ + selectCellAtLocation(0, 0); +} + +bool CountryController::handleEvent(Ion::Events::Event event) { + if (Shared::CountryController::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; + } + return false; +} + +} diff --git a/apps/on_boarding/country_controller.h b/apps/on_boarding/country_controller.h new file mode 100644 index 00000000000..21cdf80afe4 --- /dev/null +++ b/apps/on_boarding/country_controller.h @@ -0,0 +1,19 @@ +#ifndef ON_BOARDING_COUNTRY_CONTROLLER_H +#define ON_BOARDING_COUNTRY_CONTROLLER_H + +#include +#include "../shared/country_controller.h" + +namespace OnBoarding { + +class CountryController : public Shared::CountryController { +public: + CountryController(Responder * parentResponder); + void resetSelection() override; + bool handleEvent(Ion::Events::Event event) override; + ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::WantsMaximumSpace; } +}; + +} + +#endif diff --git a/apps/on_boarding/language_controller.cpp b/apps/on_boarding/language_controller.cpp index d4a3ae6794d..cbe21962fa6 100644 --- a/apps/on_boarding/language_controller.cpp +++ b/apps/on_boarding/language_controller.cpp @@ -1,6 +1,8 @@ #include "language_controller.h" #include "../global_preferences.h" #include "../apps_container.h" +#include "app.h" +#include "country_controller.h" #include #include @@ -23,7 +25,7 @@ bool LanguageController::handleEvent(Ion::Events::Event event) { if (appsContainer->promptController()) { Container::activeApp()->displayModalViewController(appsContainer->promptController(), 0.5f, 0.5f); } else { - appsContainer->switchTo(appsContainer->appSnapshotAtIndex(0)); + stackController()->push(App::app()->countryController()); } return true; } diff --git a/apps/on_boarding/language_controller.h b/apps/on_boarding/language_controller.h index 10ab2c99585..08e23b26d60 100644 --- a/apps/on_boarding/language_controller.h +++ b/apps/on_boarding/language_controller.h @@ -3,7 +3,6 @@ #include #include "../shared/language_controller.h" -#include "logo_controller.h" namespace OnBoarding { @@ -11,6 +10,10 @@ class LanguageController : public Shared::LanguageController { public: LanguageController(Responder * parentResponder); bool handleEvent(Ion::Events::Event event) override; + ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::DoNotShowOwnTitle; } + +private: + StackViewController * stackController() { return static_cast(parentResponder()); } }; } diff --git a/apps/shared/country_controller.h b/apps/shared/country_controller.h index 6b92dd71db2..4febf5f32fa 100644 --- a/apps/shared/country_controller.h +++ b/apps/shared/country_controller.h @@ -12,7 +12,7 @@ class CountryController : public ViewController, public SimpleListViewDataSource static I18n::Country CountryAtIndex(int i); CountryController(Responder * parentResponder, KDCoordinate verticalMargin); - void resetSelection(); + virtual void resetSelection(); View * view() override { return &m_selectableTableView; } const char * title() override {return I18n::translate(I18n::Message::Country); } From b35cdca9bb5b7b0d59464ea1364769d57905c6b5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 22 Jun 2020 10:23:10 +0200 Subject: [PATCH 134/560] [apps/exam_mode] Country-locked Dutch exam mode The Dutch exam mode is now only accessible when the selected country is Netherlands on an official firmware. Change-Id: I6d70f79b6e728482d8c7d86ef6a2e5c53ee18a39 --- apps/exam_mode_configuration_official.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp index 7640d21e6c2..d13b96bc30a 100644 --- a/apps/exam_mode_configuration_official.cpp +++ b/apps/exam_mode_configuration_official.cpp @@ -6,8 +6,7 @@ constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; int ExamModeConfiguration::numberOfAvailableExamMode() { - if ((GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::EN - && GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::NL) + if (GlobalPreferences::sharedGlobalPreferences()->country() != I18n::Country::NL || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { return 1; From 8d362ef4de12299022985ae01e13fcbf90b00434 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 22 Jun 2020 11:32:43 +0200 Subject: [PATCH 135/560] [apps/settings] Added message to country menu Change-Id: Iec064d0ac86f54a10cf63236b6f958d53cecb2e2 --- apps/on_boarding/country_controller.cpp | 6 +- apps/on_boarding/country_controller.h | 1 + apps/settings/sub_menu/country_controller.cpp | 7 ++ apps/settings/sub_menu/country_controller.h | 2 +- apps/shared.de.i18n | 2 + apps/shared.en.i18n | 2 + apps/shared.es.i18n | 2 + apps/shared.fr.i18n | 2 + apps/shared.it.i18n | 2 + apps/shared.nl.i18n | 2 + apps/shared.pt.i18n | 2 + apps/shared/country_controller.cpp | 81 +++++++++++++++++-- apps/shared/country_controller.h | 31 +++++-- 13 files changed, 128 insertions(+), 14 deletions(-) diff --git a/apps/on_boarding/country_controller.cpp b/apps/on_boarding/country_controller.cpp index d8d5d2482ad..29f04eec6e6 100644 --- a/apps/on_boarding/country_controller.cpp +++ b/apps/on_boarding/country_controller.cpp @@ -12,18 +12,18 @@ CountryController::CountryController(Responder * parentResponder) : static_cast(Metric::CommonLeftMargin), (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)) { - static_cast(m_selectableTableView.decorator()->indicatorAtIndex(1))->setMargin( + static_cast(selectableTableView()->decorator()->indicatorAtIndex(1))->setMargin( std::max( static_cast(Metric::CommonLeftMargin), (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)); } void CountryController::resetSelection() { - m_selectableTableView.deselectTable(); + selectableTableView()->deselectTable(); /* The base ::CountryController behaviour is to highlight the previously * chosen country. On boarding, we want the highlighted cell to be the first * alphabetically, but with the default behaviour, it would be Canada, as it - * is the country of value 0. */ + * is the country of value t 0. */ selectCellAtLocation(0, 0); } diff --git a/apps/on_boarding/country_controller.h b/apps/on_boarding/country_controller.h index 21cdf80afe4..737770414a9 100644 --- a/apps/on_boarding/country_controller.h +++ b/apps/on_boarding/country_controller.h @@ -12,6 +12,7 @@ class CountryController : public Shared::CountryController { void resetSelection() override; bool handleEvent(Ion::Events::Event event) override; ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::WantsMaximumSpace; } + }; } diff --git a/apps/settings/sub_menu/country_controller.cpp b/apps/settings/sub_menu/country_controller.cpp index f9b450be87a..8b709b36964 100644 --- a/apps/settings/sub_menu/country_controller.cpp +++ b/apps/settings/sub_menu/country_controller.cpp @@ -2,6 +2,13 @@ namespace Settings { +CountryController::CountryController(Responder * parentResponder, KDCoordinate verticalMargin) : + Shared::CountryController(parentResponder, verticalMargin) +{ + m_contentView.shouldDisplayTitle(false); +} + + bool CountryController::handleEvent(Ion::Events::Event event) { if (Shared::CountryController::handleEvent(event) || event == Ion::Events::Left) { static_cast(parentResponder())->pop(); diff --git a/apps/settings/sub_menu/country_controller.h b/apps/settings/sub_menu/country_controller.h index bfdac2d814a..a75b0f962f9 100644 --- a/apps/settings/sub_menu/country_controller.h +++ b/apps/settings/sub_menu/country_controller.h @@ -8,7 +8,7 @@ namespace Settings { class CountryController : public Shared::CountryController { public: - using Shared::CountryController::CountryController; + CountryController(Responder * parentResponder, KDCoordinate verticalMargin); bool handleEvent(Ion::Events::Event event) override; }; diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index df2df5f3dff..b3a070476a6 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 589744078a5..bac1737fc14 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 8b59ee580a2..310838d1732 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index c6e9ce7cf17..e96fa97fd9d 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 579f7fcc346..854a83b2ff7 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index b12190ca1ab..ed269f4ca22 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 036d6e99eda..d31171007c2 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -23,6 +23,8 @@ 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" diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp index c4a456bcbea..38a80b3842c 100644 --- a/apps/shared/country_controller.cpp +++ b/apps/shared/country_controller.cpp @@ -2,9 +2,80 @@ #include "../global_preferences.h" #include "../apps_container.h" #include +#include namespace Shared { +// CountryController::ContentView +constexpr int CountryController::ContentView::k_numberOfTextLines; + +CountryController::ContentView::ContentView(CountryController * controller, SelectableTableViewDataSource * dataSource) : + m_selectableTableView(controller, controller, dataSource), + m_titleMessage(KDFont::LargeFont, I18n::Message::Country), + m_displayTitle(true) +{ + m_titleMessage.setBackgroundColor(Palette::WallScreen); + m_titleMessage.setAlignment(0.5f, 0.5f); + I18n::Message textMessages[k_numberOfTextLines] = {I18n::Message::CountryWarning1, I18n::Message::CountryWarning2}; + for (int i = 0; i < k_numberOfTextLines; i++) { + m_messageLines[i].setBackgroundColor(Palette::WallScreen); + m_messageLines[i].setFont(KDFont::SmallFont); + m_messageLines[i].setAlignment(0.5f, 0.5f); + m_messageLines[i].setMessage(textMessages[i]); + } +} + +void CountryController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(bounds(), Palette::WallScreen); +} + +View * CountryController::ContentView::subviewAtIndex(int i) { + assert(i < numberOfSubviews()); + switch (i) { + case 0: + return &m_selectableTableView; + case 1: + return &m_titleMessage; + default: + return &m_messageLines[i - 2]; + } +} + +void CountryController::ContentView::layoutSubviews(bool force) { + KDCoordinate origin = Metric::CommonTopMargin; + if (m_displayTitle) { + origin = layoutTitleSubview(force, origin) + Metric::CommonSmallMargin; + } + origin = layoutSubtitleSubview(force, origin) + Metric::CommonTopMargin; + origin = layoutTableSubview(force, origin); + assert(origin <= bounds().height()); +} + +KDCoordinate CountryController::ContentView::layoutTitleSubview(bool force, KDCoordinate verticalOrigin) { + KDCoordinate titleHeight = m_titleMessage.font()->glyphSize().height(); + m_titleMessage.setFrame(KDRect(0, verticalOrigin, bounds().width(), titleHeight), force); + return verticalOrigin + titleHeight; +} + +KDCoordinate CountryController::ContentView::layoutSubtitleSubview(bool force, KDCoordinate verticalOrigin) { + assert(k_numberOfTextLines > 0); + KDCoordinate textHeight = m_messageLines[0].font()->glyphSize().height(); + for (int i = 0; i < k_numberOfTextLines; i++) { + m_messageLines[i].setFrame(KDRect(0, verticalOrigin, bounds().width(), textHeight), force); + verticalOrigin += textHeight; + } + return verticalOrigin; +} + +KDCoordinate CountryController::ContentView::layoutTableSubview(bool force, KDCoordinate verticalOrigin) { + KDCoordinate tableHeight = std::min( + bounds().height() - verticalOrigin, + m_selectableTableView.minimalSizeForOptimalDisplay().height()); + m_selectableTableView.setFrame(KDRect(0, verticalOrigin, bounds().width(), tableHeight), force); + return verticalOrigin + tableHeight; +} + +// CountryController int CountryController::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 @@ -36,17 +107,17 @@ I18n::Country CountryController::CountryAtIndex(int i) { CountryController::CountryController(Responder * parentResponder, KDCoordinate verticalMargin) : ViewController(parentResponder), - m_selectableTableView(this, this, this) + m_contentView(this, this) { - m_selectableTableView.setTopMargin(verticalMargin); - m_selectableTableView.setBottomMargin(verticalMargin); + selectableTableView()->setTopMargin(0); + selectableTableView()->setBottomMargin(verticalMargin); for (int i = 0; i < I18n::NumberOfCountries; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); } } void CountryController::resetSelection() { - m_selectableTableView.deselectTable(); + selectableTableView()->deselectTable(); selectCellAtLocation(0, IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country())); } @@ -57,7 +128,7 @@ void CountryController::viewWillAppear() { /* FIXME : When selecting a country, changing the language, then coming back * to select a country, some countries' names will be cropped. We force the * TableView to refresh to prevent that. */ - m_selectableTableView.reloadData(); + selectableTableView()->reloadData(); } bool CountryController::handleEvent(Ion::Events::Event event) { diff --git a/apps/shared/country_controller.h b/apps/shared/country_controller.h index 4febf5f32fa..624802f8618 100644 --- a/apps/shared/country_controller.h +++ b/apps/shared/country_controller.h @@ -14,9 +14,9 @@ class CountryController : public ViewController, public SimpleListViewDataSource CountryController(Responder * parentResponder, KDCoordinate verticalMargin); virtual void resetSelection(); - View * view() override { return &m_selectableTableView; } - const char * title() override {return I18n::translate(I18n::Message::Country); } - void didBecomeFirstResponder() override {Container::activeApp()->setFirstResponder(&m_selectableTableView); } + View * view() override { return &m_contentView; } + const char * title() override { return I18n::translate(I18n::Message::Country); } + void didBecomeFirstResponder() override { Container::activeApp()->setFirstResponder(selectableTableView()); } void viewWillAppear() override; bool handleEvent(Ion::Events::Event event) override; @@ -28,11 +28,32 @@ class CountryController : public ViewController, public SimpleListViewDataSource void willDisplayCellForIndex(HighlightCell * cell, int index) override; protected: - SelectableTableView m_selectableTableView; + class ContentView : public View { + public: + ContentView(CountryController * controller, SelectableTableViewDataSource * dataSource); + SelectableTableView * selectableTableView() { return &m_selectableTableView; } + void drawRect(KDContext * ctx, KDRect rect) const override; + void shouldDisplayTitle(bool flag) { m_displayTitle = flag; } + protected: + void layoutSubviews(bool force = false) override; + KDCoordinate layoutTitleSubview(bool force, KDCoordinate verticalOrigin); + KDCoordinate layoutSubtitleSubview(bool force, KDCoordinate verticalOrigin); + KDCoordinate layoutTableSubview(bool force, KDCoordinate verticalOrigin); + private: + constexpr static int k_numberOfTextLines = 2; + int numberOfSubviews() const override { return 1 + 1 + k_numberOfTextLines; } + View * subviewAtIndex(int i) override; + SelectableTableView m_selectableTableView; + MessageTextView m_titleMessage; + MessageTextView m_messageLines[k_numberOfTextLines]; + bool m_displayTitle; + }; + + SelectableTableView * selectableTableView() { return m_contentView.selectableTableView(); } + ContentView m_contentView; private: MessageTableCell m_cells[I18n::NumberOfCountries]; - // TODO : Add variables for static text }; } From 86f7a87d45c65042721bfa395c94dbf0a11b4583 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 26 Jun 2020 14:54:07 +0200 Subject: [PATCH 136/560] [apps/statistics] International quartiles Implemented a new method for computing quartiles, used for all countries except France and Italy. Quartiles are now computed as the medians of the lower half and upper half of the dataset. Change-Id: Idfb89a617799eca1d23326ebbcfbbea58e6236b8 --- apps/statistics/store.cpp | 57 +++++++++++++++++++++++++++++++++------ apps/statistics/store.h | 2 ++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 20733a6e903..5c660d36cef 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -1,4 +1,6 @@ #include "store.h" +#include +#include #include #include #include @@ -88,6 +90,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; } @@ -169,12 +180,40 @@ 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()->country() == I18n::Country::FR + || GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::IT + || !frequenciesAreInteger(series)) { + return sortedElementAtCumulatedFrequency(series, 1.0/4.0); + } + 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()->country() == I18n::Country::FR + || GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::IT + || !frequenciesAreInteger(series)) { + return sortedElementAtCumulatedFrequency(series, 3.0/4.0); + } + return sortedElementAtCumulatedPopulation(series, std::ceil(3./2. * sumOfOccurrences(series)) / 2., true); } double Store::quartileRange(int series) const { @@ -254,22 +293,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 3342021eb06..136791a237f 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -24,6 +24,7 @@ class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairSt 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 @@ -61,6 +62,7 @@ class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairSt 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; From ea127cdfa91a826f69703de03b67548069503a46 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 29 Jun 2020 10:19:49 +0200 Subject: [PATCH 137/560] [apps/statistics] Added tests for all countries As some definitions now depend on the selected country, tests for the statistics app now check that the right method is used for each country, and that each method yields the correct results. Change-Id: Ia5ad091136746f3284f7524376301fac0c582364 --- apps/statistics/test/store.cpp | 135 ++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index 24c43f4c653..19feeaeaf43 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -16,7 +18,33 @@ void assert_value_approximately_equal_to(double d1, double d2, double precision, || IsApproximatelyEqual(d1, d2, precision, reference)); } -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 trueFirstQuartile, double trueThirdQuartile, double trueQuartileRange, double trueMedian, double trueSum, double trueSquaredValueSum) { +/* 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; @@ -28,7 +56,6 @@ void assert_data_statictics_equal_to(double v[], double n[], int numberOfData, d double precision = 1e-3; - // Compare the statistics double sumOfOccurrences = store.sumOfOccurrences(seriesIndex); double maxValue = store.maxValue(seriesIndex); double minValue = store.minValue(seriesIndex); @@ -37,9 +64,6 @@ void assert_data_statictics_equal_to(double v[], double n[], int numberOfData, d double variance = store.variance(seriesIndex); double standardDeviation = store.standardDeviation(seriesIndex); double sampleStandardDeviation = store.sampleStandardDeviation(seriesIndex); - double firstQuartile = store.firstQuartile(seriesIndex); - double thirdQuartile = store.thirdQuartile(seriesIndex); - double quartileRange = store.quartileRange(seriesIndex); double median = store.median(seriesIndex); double sum = store.sum(seriesIndex); double squaredValueSum = store.squaredValueSum(seriesIndex); @@ -49,9 +73,9 @@ void assert_data_statictics_equal_to(double v[], double n[], int numberOfData, d quiz_assert(variance >= 0.0); quiz_assert(standardDeviation >= 0.0); quiz_assert(sampleStandardDeviation >= 0.0); - quiz_assert(quartileRange >= 0.0); quiz_assert(squaredValueSum >= 0.0); + // Compare the statistics double reference = trueSquaredValueSum; assert_value_approximately_equal_to(variance, trueVariance, precision, reference); assert_value_approximately_equal_to(squaredValueSum, trueSquaredValueSum, precision, reference); @@ -62,8 +86,6 @@ void assert_data_statictics_equal_to(double v[], double n[], int numberOfData, d 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(firstQuartile, trueFirstQuartile, precision, reference); - assert_value_approximately_equal_to(thirdQuartile, trueThirdQuartile, precision, reference); assert_value_approximately_equal_to(median, trueMedian, precision, reference); assert_value_approximately_equal_to(sum, trueSum, precision, reference); @@ -71,7 +93,21 @@ void assert_data_statictics_equal_to(double v[], double n[], int numberOfData, d 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); - assert_value_approximately_equal_to(quartileRange, trueQuartileRange, 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 = country == I18n::Country::FR || country == I18n::Country::IT; + 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) { @@ -94,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); @@ -120,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); @@ -145,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); @@ -170,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); @@ -195,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); @@ -220,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); @@ -245,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); @@ -270,9 +327,12 @@ 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); @@ -295,9 +355,12 @@ QUIZ_CASE(data_statistics) { /* variance */ 0.0, /* standardDeviation */ 0.0, /* sampleStandardDeviation */ 0.0, - /* firstQuartile */ -996.85840734641, - /* thirdQuartile */ -996.85840734641, - /* quartileRange */ 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); From 4d6682e21158a3df939d59387cfff6d119acd9e2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 6 Jul 2020 16:25:02 +0200 Subject: [PATCH 138/560] [poincare/unit] Added imperial units The new units are : distance mile, yard, foot, inch mass pound, ounce volume gallon, quart, pint, cup, fluid ounce table spoon, tea spoon Change-Id: I6864067a1822a077764ed3b61fc46004732e9447 --- apps/math_toolbox.cpp | 43 +++++++++++++++++++--- apps/shared.universal.i18n | 15 ++++++++ apps/toolbox.de.i18n | 16 +++++++++ apps/toolbox.en.i18n | 16 +++++++++ apps/toolbox.es.i18n | 16 +++++++++ apps/toolbox.fr.i18n | 16 +++++++++ apps/toolbox.it.i18n | 16 +++++++++ apps/toolbox.nl.i18n | 16 +++++++++ apps/toolbox.pt.i18n | 16 +++++++++ poincare/include/poincare/unit.h | 61 +++++++++++++++++++++++++++----- 10 files changed, 219 insertions(+), 12 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ec1d8a8cc86..8524596389a 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -117,14 +117,22 @@ const ToolboxMessageTree unitDistanceMeterChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterPicoSymbol, I18n::Message::UnitDistanceMeterPico), }; +const ToolboxMessageTree unitDistanceImperialChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMileSymbol, I18n::Message::UnitDistanceMile), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceYardSymbol, I18n::Message::UnitDistanceYard), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceFootSymbol, I18n::Message::UnitDistanceFoot), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceInchSymbol, I18n::Message::UnitDistanceInch), +}; + const ToolboxMessageTree unitDistanceChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitDistanceMeterMenu, unitDistanceMeterChildren), + ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, 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)}; + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceParsecSymbol, I18n::Message::UnitDistanceParsec), +}; -const ToolboxMessageTree unitMassChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne), +const ToolboxMessageTree unitMassGramChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramKiloSymbol, I18n::Message::UnitMassGramKilo), ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramSymbol, I18n::Message::UnitMassGram), ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMilliSymbol, I18n::Message::UnitMassGramMilli), @@ -132,6 +140,17 @@ const ToolboxMessageTree unitMassChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramNanoSymbol, I18n::Message::UnitMassGramNano), }; +const ToolboxMessageTree unitMassImperialChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitMassPoundSymbol, I18n::Message::UnitMassPound), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassOunceSymbol, I18n::Message::UnitMassOunce), +}; + +const ToolboxMessageTree unitMassChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitMassGram, unitMassGramChildren), + ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitMassImperialChildren), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne), +}; + const ToolboxMessageTree unitCurrentAmpereChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereSymbol, I18n::Message::UnitCurrentAmpere), ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMilliSymbol, I18n::Message::UnitCurrentAmpereMilli), @@ -228,6 +247,7 @@ const ToolboxMessageTree unitInductanceChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitInductanceHenrySymbol, I18n::Message::UnitInductanceHenry)}; const ToolboxMessageTree unitSurfaceChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceAcreSymbol, I18n::Message::UnitSurfaceAcre), ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar)}; const ToolboxMessageTree unitVolumeLiterChildren[] = { @@ -237,11 +257,26 @@ const ToolboxMessageTree unitVolumeLiterChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterMilliSymbol, I18n::Message::UnitVolumeLiterMilli), }; +const ToolboxMessageTree unitVolumeImperialChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeGallonSymbol, I18n::Message::UnitVolumeGallon), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeQuartSymbol, I18n::Message::UnitVolumeQuart), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumePintSymbol, I18n::Message::UnitVolumePint), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeCupSymbol, I18n::Message::UnitVolumeCup), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeFluidOunceSymbol, I18n::Message::UnitVolumeFluidOunce), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTablespoonSymbol, I18n::Message::UnitVolumeTablespoon), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTeaspoonSymbol, I18n::Message::UnitVolumeTeaspoon), +}; + +const ToolboxMessageTree unitVolumeChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitVolumeLiter, unitVolumeLiterChildren), + ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitVolumeImperialChildren), +}; + const ToolboxMessageTree unitChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitTimeMenu, unitTimeChildren), ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildren), ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildren), - ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeLiterChildren), + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeChildren), ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildren), ToolboxMessageTree::Node(I18n::Message::UnitCurrentMenu, unitCurrentAmpereChildren), ToolboxMessageTree::Node(I18n::Message::UnitTemperatureMenu, unitTemperatureChildren), diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 7542b995e44..68b7a3e3fb8 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -14,6 +14,10 @@ UnitDistanceMeterMilliSymbol = "_mm" UnitDistanceMeterMicroSymbol = "_μm" UnitDistanceMeterNanoSymbol = "_nm" UnitDistanceMeterPicoSymbol = "_pm" +UnitDistanceMileSymbol = "_mi" +UnitDistanceYardSymbol = "_yd" +UnitDistanceFootSymbol = "_ft" +UnitDistanceInchSymbol = "_in" UnitDistanceAstronomicalUnitSymbol = "_au" UnitDistanceLightYearSymbol = "_ly" UnitDistanceParsecSymbol = "_pc" @@ -23,6 +27,9 @@ UnitMassGramMilliSymbol = "_mg" UnitMassGramMicroSymbol = "_μg" UnitMassGramNanoSymbol = "_ng" UnitMassTonneSymbol = "_t" +UnitMassOunceSymbol = "_oz" +UnitMassPoundSymbol = "_lb" +UnitMassShortTonSymbol = "_ton" UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" @@ -69,11 +76,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 = "_cp" +UnitVolumePintSymbol = "_pt" +UnitVolumeQuartSymbol = "_qt" +UnitVolumeGallonSymbol = "_gal" A = "a" AbsCommandWithArg = "abs(x)" AcoshCommandWithArg = "acosh(x)" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 8e410a7f1fe..16359e3671c 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Pikometer" UnitDistanceAstronomicalUnit = "Astronomische Einheit" UnitDistanceLightYear = "Lichtjahr" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramm" UnitMassGram = "Gramm" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligramm" UnitMassGramMicro = "Mikrogramm" UnitMassGramNano = "Nanogramm" UnitMassTonne = "Tonne" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Elektrischer Strom" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Elektrische Induktivität" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Fläche" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hektar" UnitVolumeMenu = "Volumen" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deziliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Werkzeugkasten" AbsoluteValue = "Betragsfunktion" NthRoot = "n-te Wurzel" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index c8c295608d6..af73af4da39 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Mass" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" UnitMassTonne = "Tonne" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Electric current" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Electrical inductance" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Area" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deciliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Toolbox" AbsoluteValue = "Absolute value" NthRoot = "nth-root" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 73410e367ed..549aaef5a91 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Mass" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" UnitMassTonne = "Tonne" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Electric current" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Electrical inductance" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Area" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deciliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Caja de herramientas" AbsoluteValue = "Valor absoluto" NthRoot = "Raíz enesima" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 88b640b11ea..425159d4060 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picomètre" UnitDistanceAstronomicalUnit = "Unité astronomique" UnitDistanceLightYear = "Année-lumière" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramme" UnitMassGram = "Gramme" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligramme" UnitMassGramMicro = "Microgramme" UnitMassGramNano = "Nanogramme" UnitMassTonne = "Tonne" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Intensité du courant électrique" UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Inductance" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Superficie" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Litre" UnitVolumeLiterDeci = "Décilitre" UnitVolumeLiterCenti = "Centilitre" UnitVolumeLiterMilli = "Millilitre" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Boîte à outils" AbsoluteValue = "Valeur absolue" NthRoot = "Racine n-ième" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 1790bed866d..eabeccbc12a 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picometro" UnitDistanceAstronomicalUnit = "Unità astronomica" UnitDistanceLightYear = "Anno luce" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Massa" UnitMassGramKilo = "Kilogrammo" UnitMassGram = "Grammo" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligrammo" UnitMassGramMicro = "Microgrammo" UnitMassGramNano = "Nanogrammo" UnitMassTonne = "Tonnellata" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Intensità di corrente elettrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Induttanza" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Superficie" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Ettaro" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Litro" UnitVolumeLiterDeci = "Decilitro" UnitVolumeLiterCenti = "Centilitro" UnitVolumeLiterMilli = "Millilitro" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Toolbox" AbsoluteValue = "Valore assoluto" NthRoot = "Radice n-esima" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 02be8f21b77..ce8da3c1c1e 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomische eenheid" UnitDistanceLightYear = "Lichtjaar" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Massa" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" UnitMassTonne = "Ton" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Elektrische stroom" UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Zelfinductie" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Oppervlakte" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deciliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Toolbox" AbsoluteValue = "Absolute waarde" NthRoot = "n-de-machtswortel" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 54f95ee42d7..64df135f5a8 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -22,6 +22,10 @@ UnitDistanceMeterPico = "Picómetro" UnitDistanceAstronomicalUnit = "Unidade astronómica" UnitDistanceLightYear = "Ano-luz" UnitDistanceParsec = "Parsec" +UnitDistanceMile = "Mile" +UnitDistanceYard = "Yard" +UnitDistanceFoot = "Foot" +UnitDistanceInch = "Inch" UnitMassMenu = "Massa" UnitMassGramKilo = "Quilograma" UnitMassGram = "Grama" @@ -29,6 +33,9 @@ UnitMassGramMilli = "Miligrama" UnitMassGramMicro = "Micrograma" UnitMassGramNano = "Nanograma" UnitMassTonne = "Tonelada" +UnitMassOunce = "Ounce" +UnitMassPound = "Pound" +UnitMassShortTon = "Short Ton" UnitCurrentMenu = "Intensidade da corrente elétrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Miliampere" @@ -94,12 +101,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Indutância" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Área" +UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Litro" UnitVolumeLiterDeci = "Decilitro" UnitVolumeLiterCenti = "Centilitro" UnitVolumeLiterMilli = "Mililitro" +UnitVolumeTeaspoon = "Teaspoon" +UnitVolumeTablespoon= "Tablespoon" +UnitVolumeFluidOunce = "Fluid Ounce" +UnitVolumeCup = "Cup" +UnitVolumePint = "Pint" +UnitVolumeQuart = "Quart" +UnitVolumeGallon = "Gallon" +UnitImperialMenu = "Imperial" Toolbox = "Caixa de ferramentas" AbsoluteValue = "Valor absoluto" NthRoot = "Raiz de índice n" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 9109eff034d..f2b741674ac 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -285,32 +285,53 @@ class Unit final : public Expression { NoPrefix), }, DistanceRepresentatives[] = { - Representative("m", nullptr, + Representative("m", nullptr, Representative::Prefixable::Yes, LongScalePrefixes), Representative("au", "149597870700*_m", Representative::Prefixable::No, NoPrefix), - Representative("ly", "299792458*_m/_s*_year", + Representative("ly", "299792458*_m/_s*_year", Representative::Prefixable::No, NoPrefix), - Representative("pc", "180*60*60/π*_au", + Representative("pc", "180*60*60/π*_au", + Representative::Prefixable::No, + NoPrefix), + Representative("in", "0.0254*_m", + Representative::Prefixable::No, + NoPrefix), + Representative("ft", "12*_in", + Representative::Prefixable::No, + NoPrefix), + Representative("yd", "3*_ft", + Representative::Prefixable::No, + NoPrefix), + Representative("mi", "1760*_yd", Representative::Prefixable::No, NoPrefix), }, MassRepresentatives[] = { - Representative("kg", nullptr, + Representative("kg", nullptr, Representative::Prefixable::No, NoPrefix), - Representative("g", "0.001_kg", + Representative("g", "0.001_kg", Representative::Prefixable::Yes, NegativeLongScalePrefixes), - Representative("t", "1000_kg", + Representative("t", "1000_kg", Representative::Prefixable::Yes, NoPrefix), - Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", + Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", Representative::Prefixable::Yes, NoPrefix), + Representative("oz", "0.028349523125*_kg", + Representative::Prefixable::No, + NoPrefix), + Representative("lb", "16*_oz", + Representative::Prefixable::No, + NoPrefix), + Representative("ton", "2000*_lb", + Representative::Prefixable::No, + NoPrefix), }, CurrentRepresentatives[] = { Representative("A", nullptr, @@ -415,11 +436,35 @@ class Unit final : public Expression { Representative("ha", "10^4*_m^2", Representative::Prefixable::No, NoPrefix), + Representative("acre", "43560*_ft^2", + Representative::Prefixable::No, + NoPrefix), }, VolumeRepresentatives[] = { - Representative("L", "10^-3*_m^3", + Representative("L", "10^-3*_m^3", Representative::Prefixable::Yes, NegativePrefixes), + Representative("tsp", "4.92892159375*_mL", + Representative::Prefixable::No, + NoPrefix), + Representative("Tbsp", "3*_tsp", + Representative::Prefixable::No, + NoPrefix), + Representative("floz", "0.0295735295625*_L", + Representative::Prefixable::No, + NoPrefix), + Representative("cp", "8*_floz", + Representative::Prefixable::No, + NoPrefix), + Representative("pt", "2*_cp", + Representative::Prefixable::No, + NoPrefix), + Representative("qt", "4*_cp", + Representative::Prefixable::No, + NoPrefix), + Representative("gal", "4*_qt", + Representative::Prefixable::No, + NoPrefix), }; // 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."); From e2a6edd78f21bf3d0c4c123fd438399fbe683648 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 6 Jul 2020 17:39:56 +0200 Subject: [PATCH 139/560] [poincare/preferences] Added UnitFormat preference The UnitFormat can either be Imperial (for the US) or Metric (for the rest of the world. Change-Id: Ic698e6732f1fa225d88a14e61751b775fe70f1ab --- apps/shared/country_controller.cpp | 9 ++++++++- poincare/include/poincare/preferences.h | 7 +++++++ poincare/include/poincare/unit.h | 10 +++++----- poincare/src/unit.cpp | 7 ++++--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp index 38a80b3842c..911be04bc9f 100644 --- a/apps/shared/country_controller.cpp +++ b/apps/shared/country_controller.cpp @@ -133,7 +133,14 @@ void CountryController::viewWillAppear() { bool CountryController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { - GlobalPreferences::sharedGlobalPreferences()->setCountry(CountryAtIndex(selectedRow())); + /* FIXME : Changing the unit format should perhaps be done in setCountry.*/ + I18n::Country country = CountryAtIndex(selectedRow()); + GlobalPreferences::sharedGlobalPreferences()->setCountry(country); + if (country == I18n::Country::US) { + Poincare::Preferences::sharedPreferences()->setUnitFormat(Poincare::Preferences::UnitFormat::Imperial); + } else { + Poincare::Preferences::sharedPreferences()->setUnitFormat(Poincare::Preferences::UnitFormat::Metric); + } return true; } return false; diff --git a/poincare/include/poincare/preferences.h b/poincare/include/poincare/preferences.h index b01290ec36f..f8354186f5e 100644 --- a/poincare/include/poincare/preferences.h +++ b/poincare/include/poincare/preferences.h @@ -36,6 +36,10 @@ class Preferences final { Radian = 1, Gradian = 2 }; + enum class UnitFormat : uint8_t { + Metric = 0, + Imperial = 1 + }; Preferences(); static Preferences * sharedPreferences(); AngleUnit angleUnit() const { return m_angleUnit; } @@ -46,6 +50,8 @@ class Preferences final { void setEditionMode(EditionMode editionMode) { m_editionMode = editionMode; } ComplexFormat complexFormat() const { return m_complexFormat; } void setComplexFormat(Preferences::ComplexFormat complexFormat) { m_complexFormat = complexFormat; } + UnitFormat unitFormat() const { return m_unitFormat; } + void setUnitFormat(UnitFormat unitFormat) { m_unitFormat = unitFormat; } uint8_t numberOfSignificantDigits() const { return m_numberOfSignificantDigits; } void setNumberOfSignificantDigits(uint8_t numberOfSignificantDigits) { m_numberOfSignificantDigits = numberOfSignificantDigits; } private: @@ -53,6 +59,7 @@ class Preferences final { PrintFloatMode m_displayMode; EditionMode m_editionMode; ComplexFormat m_complexFormat; + UnitFormat m_unitFormat; uint8_t m_numberOfSignificantDigits; }; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index f2b741674ac..de74760992a 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -210,21 +210,21 @@ class Unit final : public Expression { static constexpr const Prefix * NoPrefix[] = { &EmptyPrefix }; - static constexpr const Prefix * NegativeLongScalePrefixes[] = { + static constexpr const Prefix * NegativeLongScalePrefixes[] = { &PicoPrefix, &NanoPrefix, &MicroPrefix, &MilliPrefix, &EmptyPrefix, }; - static constexpr const Prefix * PositiveLongScalePrefixes[] = { + static constexpr const Prefix * PositiveLongScalePrefixes[] = { &EmptyPrefix, &KiloPrefix, &MegaPrefix, &GigaPrefix, &TeraPrefix, }; - static constexpr const Prefix * LongScalePrefixes[] = { + static constexpr const Prefix * LongScalePrefixes[] = { &PicoPrefix, &NanoPrefix, &MicroPrefix, @@ -235,7 +235,7 @@ class Unit final : public Expression { &GigaPrefix, &TeraPrefix, }; - static constexpr const Prefix * NegativePrefixes[] = { + static constexpr const Prefix * NegativePrefixes[] = { &PicoPrefix, &NanoPrefix, &MicroPrefix, @@ -244,7 +244,7 @@ class Unit final : public Expression { &DeciPrefix, &EmptyPrefix, }; - static constexpr const Prefix * AllPrefixes[] = { + static constexpr const Prefix * AllPrefixes[] = { &PicoPrefix, &NanoPrefix, &MicroPrefix, diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 2080eed3b8f..30fc8cd0505 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -8,12 +8,13 @@ #include #include #include -#include -#include +#include #include +#include +#include +#include #include #include -#include namespace Poincare { From 2b59509fdde4539e28f1ff79cf7d19819bdc76e8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 8 Jul 2020 16:30:29 +0200 Subject: [PATCH 140/560] [apps/i18n] Derive preferences from country Each country comes with an set of preferences, built at compile time by apps/i18n.py, used to define : - the exam mode - the method for computing quartiles - the unit system in which to output the results with units Functions to access those preferences are available in via sharedGlobalPreferences. Change-Id: I220ebaa9b9e8954dfe33cd51936f47505b98978d --- apps/exam_mode_configuration_official.cpp | 3 +- apps/global_preferences.h | 3 ++ apps/i18n.py | 35 ++++++++++++++++++++++- apps/shared/country_controller.cpp | 6 +--- apps/statistics/store.cpp | 11 +++---- apps/statistics/test/store.cpp | 2 +- 6 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp index d13b96bc30a..20c89b4128c 100644 --- a/apps/exam_mode_configuration_official.cpp +++ b/apps/exam_mode_configuration_official.cpp @@ -6,11 +6,12 @@ constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; int ExamModeConfiguration::numberOfAvailableExamMode() { - if (GlobalPreferences::sharedGlobalPreferences()->country() != I18n::Country::NL + if (GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == I18n::AvailableExamModes::StandardOnly || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { return 1; } + assert(GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == I18n::AvailableExamModes::All); return 2; } diff --git a/apps/global_preferences.h b/apps/global_preferences.h index 0001787f82c..66996dc469e 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -16,6 +16,9 @@ class GlobalPreferences { void setLanguage(I18n::Language language) { m_language = language; } I18n::Country country() const { return m_country; } void setCountry(I18n::Country country) { m_country = country; } + I18n::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast(m_country)].availableExamModes; } + I18n::MethodForQuartiles methodForQuartiles() const { return I18n::CountryPreferencesArray[static_cast(m_country)].methodForQuartiles; } + Poincare::Preferences::UnitFormat unitFormat() const { return I18n::CountryPreferencesArray[static_cast(m_country)].unitFormat; } bool isInExamMode() const { return (int8_t)examMode() > 0; } ExamMode examMode() const; void setExamMode(ExamMode examMode); diff --git a/apps/i18n.py b/apps/i18n.py index 6f9dddcc9c1..a5c8437d9cc 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -120,7 +120,8 @@ def print_header(data, path, locales, countries): 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)) @@ -171,6 +172,38 @@ def print_header(data, path, locales, countries): f.write(" Message::Country" + country.upper() + ",\n") f.write("};\n\n") + # Country preferences + f.write("enum class AvailableExamModes : uint8_t {\n") + f.write(" None,\n") + f.write(" StandardOnly,\n") + f.write(" All\n") + f.write("};\n\n") + f.write("enum class MethodForQuartiles : uint8_t {\n") + f.write(" MedianOfSublist,\n") + f.write(" CumulatedFrequency\n") + f.write("};\n\n") + f.write("struct CountryPreferences {\n") + f.write(" AvailableExamModes availableExamModes;\n") + f.write(" MethodForQuartiles methodForQuartiles;\n") + f.write(" Poincare::Preferences::UnitFormat unitFormat;\n") + f.write("};\n\n") + countryPreferences = { + 'CA':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'DE':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'ES':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'FR':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::CumulatedFrequency, Poincare::Preferences::UnitFormat::Metric}", + 'GB':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'IT':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::CumulatedFrequency, Poincare::Preferences::UnitFormat::Metric}", + 'NL':"CountryPreferences{AvailableExamModes::All, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'PT':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", + 'US':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Imperial}", + 'WW':"CountryPreferences{AvailableExamModes::All, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}"} + f.write("constexpr static CountryPreferences CountryPreferencesArray[] = {\n") + for country in countries: + key = country if (country in countryPreferences) else 'WW' + f.write(" " + countryPreferences[key] + ",\n") + f.write("};\n\n") + f.write("}\n\n") f.write("#endif\n") f.close() diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp index 911be04bc9f..7678d23ec3b 100644 --- a/apps/shared/country_controller.cpp +++ b/apps/shared/country_controller.cpp @@ -136,11 +136,7 @@ bool CountryController::handleEvent(Ion::Events::Event event) { /* FIXME : Changing the unit format should perhaps be done in setCountry.*/ I18n::Country country = CountryAtIndex(selectedRow()); GlobalPreferences::sharedGlobalPreferences()->setCountry(country); - if (country == I18n::Country::US) { - Poincare::Preferences::sharedPreferences()->setUnitFormat(Poincare::Preferences::UnitFormat::Imperial); - } else { - Poincare::Preferences::sharedPreferences()->setUnitFormat(Poincare::Preferences::UnitFormat::Metric); - } + Poincare::Preferences::sharedPreferences()->setUnitFormat(GlobalPreferences::sharedGlobalPreferences()->unitFormat()); return true; } return false; diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 5c660d36cef..6d746599118 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -1,6 +1,5 @@ #include "store.h" #include -#include #include #include #include @@ -199,20 +198,18 @@ double Store::sampleStandardDeviation(int series) const { * the more general definition if non-integral frequencies are found. * */ double Store::firstQuartile(int series) const { - if (GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::FR - || GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::IT - || !frequenciesAreInteger(series)) { + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { return sortedElementAtCumulatedFrequency(series, 1.0/4.0); } + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::MedianOfSublist); return sortedElementAtCumulatedPopulation(series, std::floor(sumOfOccurrences(series) / 2.) / 2., true); } double Store::thirdQuartile(int series) const { - if (GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::FR - || GlobalPreferences::sharedGlobalPreferences()->country() == I18n::Country::IT - || !frequenciesAreInteger(series)) { + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { return sortedElementAtCumulatedFrequency(series, 3.0/4.0); } + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::MedianOfSublist); return sortedElementAtCumulatedPopulation(series, std::ceil(3./2. * sumOfOccurrences(series)) / 2., true); } diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index 19feeaeaf43..e56645160c3 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -103,7 +103,7 @@ void assert_data_statictics_equal_to( GlobalPreferences::sharedGlobalPreferences()->setCountry(country); quartileRange = store.quartileRange(seriesIndex); quiz_assert(quartileRange >= 0.0); - shouldUseFrequencyMethod = country == I18n::Country::FR || country == I18n::Country::IT; + shouldUseFrequencyMethod = GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::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); From 8121400cf2d33c57e208d92f8ac209bc4363b8fd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 17 Jul 2020 14:20:33 +0200 Subject: [PATCH 141/560] [apps/i18n.py] Method for printing lists New methods print_block_from_list factors code used to generate enum classes and arrays. Change-Id: I4ce7d22c3ffca4f12b9439f88ce288fcf9b85090 --- apps/i18n.py | 79 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/apps/i18n.py b/apps/i18n.py index a5c8437d9cc..2c7c08aa526 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -115,6 +115,12 @@ def parse_codepoints(file): codepoints = parse_codepoints(args.codepoints) +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") @@ -127,50 +133,46 @@ def print_header(data, path, locales, countries): 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 : uint8_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 - f.write("enum class Country : uint8_t {\n") - index = 0 - for country in countries: - f.write(" " + country.upper() + (" = 0" if (index < 1) else "") + ",\n") - index += 1 - f.write("};\n\n") + print_block_from_list(f, + "enum class Country : uint8_t {\n", + countries, + lambda arg: arg.upper()) # Country names - f.write("constexpr const Message CountryNames[NumberOfCountries] = {\n"); - for country in countries: - f.write(" Message::Country" + country.upper() + ",\n") - f.write("};\n\n") + print_block_from_list(f, + "constexpr const Message CountryNames[NumberOfCountries] = {\n", + countries, + lambda arg: arg.upper(), + " Message::Country") # Country preferences f.write("enum class AvailableExamModes : uint8_t {\n") @@ -227,11 +229,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"]: From 5749d871b05bc6441971d6b2c656b3c96a0a37bc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 17 Jul 2020 14:22:29 +0200 Subject: [PATCH 142/560] [apps/i18n] Moved country preferences Instead of being hardcoded in the python script, preferences specific to each country are defined in the country_preferences.h and .csv files. Change-Id: I71e276341e1586935b4d5814be5b1be80fa170a0 --- apps/Makefile | 4 +- apps/country_preferences.csv | 11 +++++ apps/country_preferences.h | 34 +++++++++++++++ apps/exam_mode_configuration_official.cpp | 4 +- apps/global_preferences.h | 6 +-- apps/i18n.py | 53 ++++++++++------------- apps/statistics/store.cpp | 8 ++-- apps/statistics/test/store.cpp | 2 +- 8 files changed, 80 insertions(+), 42 deletions(-) create mode 100644 apps/country_preferences.csv create mode 100644 apps/country_preferences.h diff --git a/apps/Makefile b/apps/Makefile index a76386d2120..e95a743699c 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -55,6 +55,8 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m # I18n file generation +country_preferences = apps/country_preferences.csv + # The header is refered to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) @@ -71,7 +73,7 @@ $(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) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \ + $$(PYTHON) apps/i18n.py --codepoints $(code_points) --countrypreferences $(country_preferences) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \ global \ )) diff --git a/apps/country_preferences.csv b/apps/country_preferences.csv new file mode 100644 index 00000000000..0e95a872b35 --- /dev/null +++ b/apps/country_preferences.csv @@ -0,0 +1,11 @@ +CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat +CA,StandardOnly,MedianOfSublist,Metric +DE,StandardOnly,MedianOfSublist,Metric +ES,StandardOnly,MedianOfSublist,Metric +FR,StandardOnly,CumulatedFrequency,Metric +GB,StandardOnly,MedianOfSublist,Metric +IT,StandardOnly,CumulatedFrequency,Metric +NL,All,MedianOfSublist,Metric +PT,StandardOnly,MedianOfSublist,Metric +US,StandardOnly,MedianOfSublist,Imperial +WW,StandardOnly,MedianOfSublist,Metric diff --git a/apps/country_preferences.h b/apps/country_preferences.h new file mode 100644 index 00000000000..703801c97b4 --- /dev/null +++ b/apps/country_preferences.h @@ -0,0 +1,34 @@ +#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 + }; + + constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat) : + m_availableExamModes(availableExamModes), + m_methodForQuartiles(methodForQuartiles), + m_unitFormat(unitFormat) + {} + + constexpr AvailableExamModes availableExamModes() const { return m_availableExamModes; } + constexpr MethodForQuartiles methodForQuartiles() const { return m_methodForQuartiles; } + constexpr Poincare::Preferences::UnitFormat unitFormat() const { return m_unitFormat; } + +private: + const AvailableExamModes m_availableExamModes; + const MethodForQuartiles m_methodForQuartiles; + const Poincare::Preferences::UnitFormat m_unitFormat; +}; + +#endif diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp index 20c89b4128c..5cdc1fb8f75 100644 --- a/apps/exam_mode_configuration_official.cpp +++ b/apps/exam_mode_configuration_official.cpp @@ -6,12 +6,12 @@ constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; int ExamModeConfiguration::numberOfAvailableExamMode() { - if (GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == I18n::AvailableExamModes::StandardOnly + if (GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::StandardOnly || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { return 1; } - assert(GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == I18n::AvailableExamModes::All); + assert(GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::All); return 2; } diff --git a/apps/global_preferences.h b/apps/global_preferences.h index 66996dc469e..c6c20ff8ba8 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -16,9 +16,9 @@ class GlobalPreferences { void setLanguage(I18n::Language language) { m_language = language; } I18n::Country country() const { return m_country; } void setCountry(I18n::Country country) { m_country = country; } - I18n::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast(m_country)].availableExamModes; } - I18n::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::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(); } bool isInExamMode() const { return (int8_t)examMode() > 0; } ExamMode examMode() const; void setExamMode(ExamMode examMode); diff --git a/apps/i18n.py b/apps/i18n.py index 2c7c08aa526..83bcdfb8a3c 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -6,11 +6,12 @@ # 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.") @@ -19,6 +20,7 @@ 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('--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)') @@ -115,6 +117,17 @@ def parse_codepoints(file): codepoints = parse_codepoints(args.codepoints) +def parse_country_preferences(file): + countryPreferences = {} + with io.open(file, "r", encoding="utf-8") as csvfile: + csvreader = csv.reader(csvfile, delimiter=',') + headers = next(csvreader, None) + for row in csvreader: + countryPreferences[row[0]] = [headers[i] + "::" + row[i] for i in range(1, len(row))] + return countryPreferences + +countryPreferences = parse_country_preferences(args.countrypreferences) + 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)): @@ -127,7 +140,7 @@ def print_header(data, path, locales, countries): f.write("#define APPS_I18N_H\n\n") f.write("// This file is auto-generated by i18n.py\n\n") f.write("#include \n") - f.write("#include \n\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)) @@ -175,35 +188,13 @@ def print_header(data, path, locales, countries): " Message::Country") # Country preferences - f.write("enum class AvailableExamModes : uint8_t {\n") - f.write(" None,\n") - f.write(" StandardOnly,\n") - f.write(" All\n") - f.write("};\n\n") - f.write("enum class MethodForQuartiles : uint8_t {\n") - f.write(" MedianOfSublist,\n") - f.write(" CumulatedFrequency\n") - f.write("};\n\n") - f.write("struct CountryPreferences {\n") - f.write(" AvailableExamModes availableExamModes;\n") - f.write(" MethodForQuartiles methodForQuartiles;\n") - f.write(" Poincare::Preferences::UnitFormat unitFormat;\n") - f.write("};\n\n") - countryPreferences = { - 'CA':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'DE':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'ES':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'FR':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::CumulatedFrequency, Poincare::Preferences::UnitFormat::Metric}", - 'GB':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'IT':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::CumulatedFrequency, Poincare::Preferences::UnitFormat::Metric}", - 'NL':"CountryPreferences{AvailableExamModes::All, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'PT':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}", - 'US':"CountryPreferences{AvailableExamModes::StandardOnly, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Imperial}", - 'WW':"CountryPreferences{AvailableExamModes::All, MethodForQuartiles::MedianOfSublist, Poincare::Preferences::UnitFormat::Metric}"} f.write("constexpr static CountryPreferences CountryPreferencesArray[] = {\n") for country in countries: - key = country if (country in countryPreferences) else 'WW' - f.write(" " + countryPreferences[key] + ",\n") + key = country if (country in countryPreferences) else 'inl' + line = " CountryPreferences(" + for param in countryPreferences[key]: + line += param + ", " + f.write(line[:-2] + "),\n") f.write("};\n\n") f.write("}\n\n") diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 6d746599118..219f4c8dc1c 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -198,18 +198,18 @@ double Store::sampleStandardDeviation(int series) const { * the more general definition if non-integral frequencies are found. * */ double Store::firstQuartile(int series) const { - if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { return sortedElementAtCumulatedFrequency(series, 1.0/4.0); } - assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::MedianOfSublist); + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::MedianOfSublist); return sortedElementAtCumulatedPopulation(series, std::floor(sumOfOccurrences(series) / 2.) / 2., true); } double Store::thirdQuartile(int series) const { - if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { + if (GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::CumulatedFrequency || !frequenciesAreInteger(series)) { return sortedElementAtCumulatedFrequency(series, 3.0/4.0); } - assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::MedianOfSublist); + assert(GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == CountryPreferences::MethodForQuartiles::MedianOfSublist); return sortedElementAtCumulatedPopulation(series, std::ceil(3./2. * sumOfOccurrences(series)) / 2., true); } diff --git a/apps/statistics/test/store.cpp b/apps/statistics/test/store.cpp index e56645160c3..18d0aac24ee 100644 --- a/apps/statistics/test/store.cpp +++ b/apps/statistics/test/store.cpp @@ -103,7 +103,7 @@ void assert_data_statictics_equal_to( GlobalPreferences::sharedGlobalPreferences()->setCountry(country); quartileRange = store.quartileRange(seriesIndex); quiz_assert(quartileRange >= 0.0); - shouldUseFrequencyMethod = GlobalPreferences::sharedGlobalPreferences()->methodForQuartiles() == I18n::MethodForQuartiles::CumulatedFrequency; + 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); From 62f598110e1b24d32055fdab6149518bf74658b8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 17 Jul 2020 15:10:38 +0200 Subject: [PATCH 143/560] [apps/shared] Created LocalizationController Classes LanguageController and CountryController have been fused into one class LocalizationController, as they were very similar. This allows the Settings and OnBoarding apps to only keep one controller for both functions. Change-Id: Ic23f300c37122249d34caaf18a633b5815240a78 --- apps/global_preferences.cpp | 5 + apps/global_preferences.h | 2 +- apps/on_boarding/Makefile | 3 +- apps/on_boarding/app.cpp | 10 +- apps/on_boarding/app.h | 12 +- apps/on_boarding/country_controller.cpp | 43 ----- apps/on_boarding/country_controller.h | 20 -- apps/on_boarding/language_controller.cpp | 38 ---- apps/on_boarding/language_controller.h | 21 --- apps/on_boarding/localization_controller.cpp | 33 ++++ apps/on_boarding/localization_controller.h | 21 +++ apps/settings/Makefile | 3 +- apps/settings/main_controller.cpp | 13 +- apps/settings/main_controller.h | 6 +- apps/settings/sub_menu/country_controller.cpp | 20 -- apps/settings/sub_menu/country_controller.h | 17 -- .../settings/sub_menu/language_controller.cpp | 13 -- apps/settings/sub_menu/language_controller.h | 18 -- .../sub_menu/localization_controller.cpp | 13 ++ .../sub_menu/localization_controller.h | 21 +++ apps/shared/Makefile | 3 +- apps/shared/country_controller.cpp | 149 --------------- apps/shared/country_controller.h | 61 ------ apps/shared/language_controller.cpp | 72 ------- apps/shared/language_controller.h | 36 ---- apps/shared/localization_controller.cpp | 176 ++++++++++++++++++ apps/shared/localization_controller.h | 79 ++++++++ 27 files changed, 369 insertions(+), 539 deletions(-) delete mode 100644 apps/on_boarding/country_controller.cpp delete mode 100644 apps/on_boarding/country_controller.h delete mode 100644 apps/on_boarding/language_controller.cpp delete mode 100644 apps/on_boarding/language_controller.h create mode 100644 apps/on_boarding/localization_controller.cpp create mode 100644 apps/on_boarding/localization_controller.h delete mode 100644 apps/settings/sub_menu/country_controller.cpp delete mode 100644 apps/settings/sub_menu/country_controller.h delete mode 100644 apps/settings/sub_menu/language_controller.cpp delete mode 100644 apps/settings/sub_menu/language_controller.h create mode 100644 apps/settings/sub_menu/localization_controller.cpp create mode 100644 apps/settings/sub_menu/localization_controller.h delete mode 100644 apps/shared/country_controller.cpp delete mode 100644 apps/shared/country_controller.h delete mode 100644 apps/shared/language_controller.cpp delete mode 100644 apps/shared/language_controller.h create mode 100644 apps/shared/localization_controller.cpp create mode 100644 apps/shared/localization_controller.h diff --git a/apps/global_preferences.cpp b/apps/global_preferences.cpp index c6644f5fdcb..5c6c4cd7700 100644 --- a/apps/global_preferences.cpp +++ b/apps/global_preferences.cpp @@ -5,6 +5,11 @@ GlobalPreferences * GlobalPreferences::sharedGlobalPreferences() { return &globalPreferences; } +void GlobalPreferences::setCountry(I18n::Country country) { + m_country = country; + Poincare::Preferences::sharedPreferences()->setUnitFormat(unitFormat()); +} + GlobalPreferences::ExamMode GlobalPreferences::examMode() const { if (m_examMode == ExamMode::Unknown) { uint8_t mode = Ion::ExamMode::FetchExamMode(); diff --git a/apps/global_preferences.h b/apps/global_preferences.h index c6c20ff8ba8..b4471d008fa 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -15,7 +15,7 @@ class GlobalPreferences { 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; } + void setCountry(I18n::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(); } diff --git a/apps/on_boarding/Makefile b/apps/on_boarding/Makefile index c65dbc6332d..b85d410e7d2 100644 --- a/apps/on_boarding/Makefile +++ b/apps/on_boarding/Makefile @@ -1,9 +1,8 @@ app_on_boarding_src = $(addprefix apps/on_boarding/,\ app.cpp \ - country_controller.cpp \ - language_controller.cpp \ logo_controller.cpp \ logo_view.cpp \ + localization_controller.cpp \ pop_up_controller.cpp \ power_on_self_test.cpp \ ) diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index ae12d0792d8..3e914f7d9d4 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,10 +14,8 @@ App::Descriptor * App::Snapshot::descriptor() { } App::App(Snapshot * snapshot) : - ::App(snapshot, &m_stackController), - m_stackController(&m_modalViewController, &m_languageController), - m_languageController(&m_stackController), - m_countryController(&m_languageController), + ::App(snapshot, &m_localizationController), + m_localizationController(&m_modalViewController, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_logoController() { } @@ -46,8 +45,7 @@ void App::didBecomeActive(Window * window) { } void App::reinitOnBoarding() { - m_languageController.resetSelection(); - m_countryController.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 a965ce08783..d58a0b1adee 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -2,9 +2,8 @@ #define ON_BOARDING_APP_H #include -#include "country_controller.h" -#include "language_controller.h" #include "logo_controller.h" +#include "localization_controller.h" namespace OnBoarding { @@ -16,11 +15,6 @@ class App : public ::App { Descriptor * descriptor() override; }; - static App * app() { - return static_cast(Container::activeApp()); - } - - CountryController * countryController() { return &m_countryController; } int numberOfTimers() override; Timer * timerAtIndex(int i) override; bool processEvent(Ion::Events::Event) override; @@ -28,9 +22,7 @@ class App : public ::App { private: App(Snapshot * snapshot); void reinitOnBoarding(); - StackViewController m_stackController; - LanguageController m_languageController; - CountryController m_countryController; + LocalizationController m_localizationController; LogoController m_logoController; }; diff --git a/apps/on_boarding/country_controller.cpp b/apps/on_boarding/country_controller.cpp deleted file mode 100644 index 29f04eec6e6..00000000000 --- a/apps/on_boarding/country_controller.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "country_controller.h" -#include "../apps_container.h" -#include -#include - -namespace OnBoarding { - -CountryController::CountryController(Responder * parentResponder) : - Shared::CountryController( - parentResponder, - std::max( - static_cast(Metric::CommonLeftMargin), - (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)) -{ - static_cast(selectableTableView()->decorator()->indicatorAtIndex(1))->setMargin( - std::max( - static_cast(Metric::CommonLeftMargin), - (Ion::Display::Height - I18n::NumberOfCountries*Metric::ParameterCellHeight)/2)); -} - -void CountryController::resetSelection() { - selectableTableView()->deselectTable(); - /* The base ::CountryController behaviour is to highlight the previously - * chosen country. On boarding, we want the highlighted cell to be the first - * alphabetically, but with the default behaviour, it would be Canada, as it - * is the country of value t 0. */ - selectCellAtLocation(0, 0); -} - -bool CountryController::handleEvent(Ion::Events::Event event) { - if (Shared::CountryController::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; - } - return false; -} - -} diff --git a/apps/on_boarding/country_controller.h b/apps/on_boarding/country_controller.h deleted file mode 100644 index 737770414a9..00000000000 --- a/apps/on_boarding/country_controller.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef ON_BOARDING_COUNTRY_CONTROLLER_H -#define ON_BOARDING_COUNTRY_CONTROLLER_H - -#include -#include "../shared/country_controller.h" - -namespace OnBoarding { - -class CountryController : public Shared::CountryController { -public: - CountryController(Responder * parentResponder); - void resetSelection() override; - bool handleEvent(Ion::Events::Event event) override; - ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::WantsMaximumSpace; } - -}; - -} - -#endif diff --git a/apps/on_boarding/language_controller.cpp b/apps/on_boarding/language_controller.cpp deleted file mode 100644 index cbe21962fa6..00000000000 --- a/apps/on_boarding/language_controller.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "language_controller.h" -#include "../global_preferences.h" -#include "../apps_container.h" -#include "app.h" -#include "country_controller.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 { - stackController()->push(App::app()->countryController()); - } - 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 08e23b26d60..00000000000 --- a/apps/on_boarding/language_controller.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef ON_BOARDING_LANGUAGE_CONTROLLER_H -#define ON_BOARDING_LANGUAGE_CONTROLLER_H - -#include -#include "../shared/language_controller.h" - -namespace OnBoarding { - -class LanguageController : public Shared::LanguageController { -public: - LanguageController(Responder * parentResponder); - bool handleEvent(Ion::Events::Event event) override; - ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::DoNotShowOwnTitle; } - -private: - StackViewController * stackController() { return static_cast(parentResponder()); } -}; - -} - -#endif diff --git a/apps/on_boarding/localization_controller.cpp b/apps/on_boarding/localization_controller.cpp new file mode 100644 index 00000000000..bed17051f14 --- /dev/null +++ b/apps/on_boarding/localization_controller.cpp @@ -0,0 +1,33 @@ +#include "localization_controller.h" +#include +#include + +namespace OnBoarding { + +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 00000000000..3f47a6fb0d9 --- /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; + + bool shouldDisplayTitle() override { return mode() == Mode::Country; } + bool shouldResetSelectionToTopCell() override { return true; } + + bool handleEvent(Ion::Events::Event event) override; +}; + +} + +#endif diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 17b679bb258..0d38a7d188d 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -12,13 +12,12 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/about_controller.cpp \ sub_menu/about_controller_official.cpp:+official \ sub_menu/about_controller_non_official.cpp:-official \ - sub_menu/country_controller.cpp \ sub_menu/display_mode_controller.cpp \ sub_menu/exam_mode_controller_official.cpp:+official \ sub_menu/exam_mode_controller_non_official.cpp:-official \ 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/selectable_view_with_messages.cpp \ ) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index c15e67c15e1..d12cb186bd9 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -22,8 +22,7 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_selectableTableView(this), m_preferencesController(this), m_displayModeController(this, inputEventHandlerDelegate), - m_languageController(this, Metric::CommonTopMargin), - m_countryController(this, Metric::CommonTopMargin), + m_localizationController(this, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_examModeController(this), m_aboutController(this) { @@ -67,6 +66,12 @@ bool MainController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { assert(rowIndex != k_indexOfBrightnessCell); + + if (rowIndex == k_indexOfLanguageCell) { + m_localizationController.setMode(LocalizationController::Mode::Language); + } else if (rowIndex == k_indexOfCountryCell) { + m_localizationController.setMode(LocalizationController::Mode::Country); + } /* The About cell can either be found at index k_indexOfExamModeCell + 1 or * k_indexOfExamModeCell + 2, depending on whether there is a Pop-Up cell. * Since the Pop-Up cell has been handled above, we can use those two @@ -78,8 +83,8 @@ bool MainController::handleEvent(Ion::Events::Event event) { &m_preferencesController, nullptr, //&m_brightnessController &m_preferencesController, - &m_languageController, - &m_countryController, + &m_localizationController, + &m_localizationController, &m_examModeController, &m_aboutController, &m_aboutController diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index 4e887aa0f9d..f9675455fd1 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -5,10 +5,9 @@ #include #include "message_table_cell_with_gauge_with_separator.h" #include "sub_menu/about_controller.h" -#include "sub_menu/country_controller.h" #include "sub_menu/display_mode_controller.h" #include "sub_menu/exam_mode_controller.h" -#include "sub_menu/language_controller.h" +#include "sub_menu/localization_controller.h" #include "sub_menu/preferences_controller.h" namespace Settings { @@ -65,8 +64,7 @@ class MainController : public ViewController, public ListViewDataSource, public SelectableTableView m_selectableTableView; PreferencesController m_preferencesController; DisplayModeController m_displayModeController; - LanguageController m_languageController; - CountryController m_countryController; + LocalizationController m_localizationController; ExamModeController m_examModeController; AboutController m_aboutController; diff --git a/apps/settings/sub_menu/country_controller.cpp b/apps/settings/sub_menu/country_controller.cpp deleted file mode 100644 index 8b709b36964..00000000000 --- a/apps/settings/sub_menu/country_controller.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "country_controller.h" - -namespace Settings { - -CountryController::CountryController(Responder * parentResponder, KDCoordinate verticalMargin) : - Shared::CountryController(parentResponder, verticalMargin) -{ - m_contentView.shouldDisplayTitle(false); -} - - -bool CountryController::handleEvent(Ion::Events::Event event) { - if (Shared::CountryController::handleEvent(event) || event == Ion::Events::Left) { - static_cast(parentResponder())->pop(); - return true; - } - return false; -} - -} diff --git a/apps/settings/sub_menu/country_controller.h b/apps/settings/sub_menu/country_controller.h deleted file mode 100644 index a75b0f962f9..00000000000 --- a/apps/settings/sub_menu/country_controller.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SETTING_COUNTRY_CONTROLLER_H -#define SETTING_COUNTRY_CONTROLLER_H - -#include -#include "../../shared/country_controller.h" - -namespace Settings { - -class CountryController : public Shared::CountryController { -public: - CountryController(Responder * parentResponder, KDCoordinate verticalMargin); - bool handleEvent(Ion::Events::Event event) override; -}; - -} - -#endif diff --git a/apps/settings/sub_menu/language_controller.cpp b/apps/settings/sub_menu/language_controller.cpp deleted file mode 100644 index 9edb1857ad8..00000000000 --- 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 e0e358acaed..00000000000 --- 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 00000000000..b77981c8ef0 --- /dev/null +++ b/apps/settings/sub_menu/localization_controller.cpp @@ -0,0 +1,13 @@ +#include "localization_controller.h" + +namespace Settings { + +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 00000000000..7fee93862d9 --- /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; + + bool shouldDisplayTitle() override { return false; } + bool shouldResetSelectionToTopCell() override { return false; } + + bool handleEvent(Ion::Events::Event event) override; + TELEMETRY_ID("Localization"); +}; +} + +#endif diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 481c962e09d..c90ce297362 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -26,7 +26,6 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ - country_controller.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ @@ -51,9 +50,9 @@ app_shared_src = $(addprefix apps/shared/,\ 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 \ diff --git a/apps/shared/country_controller.cpp b/apps/shared/country_controller.cpp deleted file mode 100644 index 7678d23ec3b..00000000000 --- a/apps/shared/country_controller.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "country_controller.h" -#include "../global_preferences.h" -#include "../apps_container.h" -#include -#include - -namespace Shared { - -// CountryController::ContentView -constexpr int CountryController::ContentView::k_numberOfTextLines; - -CountryController::ContentView::ContentView(CountryController * controller, SelectableTableViewDataSource * dataSource) : - m_selectableTableView(controller, controller, dataSource), - m_titleMessage(KDFont::LargeFont, I18n::Message::Country), - m_displayTitle(true) -{ - m_titleMessage.setBackgroundColor(Palette::WallScreen); - m_titleMessage.setAlignment(0.5f, 0.5f); - I18n::Message textMessages[k_numberOfTextLines] = {I18n::Message::CountryWarning1, I18n::Message::CountryWarning2}; - for (int i = 0; i < k_numberOfTextLines; i++) { - m_messageLines[i].setBackgroundColor(Palette::WallScreen); - m_messageLines[i].setFont(KDFont::SmallFont); - m_messageLines[i].setAlignment(0.5f, 0.5f); - m_messageLines[i].setMessage(textMessages[i]); - } -} - -void CountryController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(bounds(), Palette::WallScreen); -} - -View * CountryController::ContentView::subviewAtIndex(int i) { - assert(i < numberOfSubviews()); - switch (i) { - case 0: - return &m_selectableTableView; - case 1: - return &m_titleMessage; - default: - return &m_messageLines[i - 2]; - } -} - -void CountryController::ContentView::layoutSubviews(bool force) { - KDCoordinate origin = Metric::CommonTopMargin; - if (m_displayTitle) { - origin = layoutTitleSubview(force, origin) + Metric::CommonSmallMargin; - } - origin = layoutSubtitleSubview(force, origin) + Metric::CommonTopMargin; - origin = layoutTableSubview(force, origin); - assert(origin <= bounds().height()); -} - -KDCoordinate CountryController::ContentView::layoutTitleSubview(bool force, KDCoordinate verticalOrigin) { - KDCoordinate titleHeight = m_titleMessage.font()->glyphSize().height(); - m_titleMessage.setFrame(KDRect(0, verticalOrigin, bounds().width(), titleHeight), force); - return verticalOrigin + titleHeight; -} - -KDCoordinate CountryController::ContentView::layoutSubtitleSubview(bool force, KDCoordinate verticalOrigin) { - assert(k_numberOfTextLines > 0); - KDCoordinate textHeight = m_messageLines[0].font()->glyphSize().height(); - for (int i = 0; i < k_numberOfTextLines; i++) { - m_messageLines[i].setFrame(KDRect(0, verticalOrigin, bounds().width(), textHeight), force); - verticalOrigin += textHeight; - } - return verticalOrigin; -} - -KDCoordinate CountryController::ContentView::layoutTableSubview(bool force, KDCoordinate verticalOrigin) { - KDCoordinate tableHeight = std::min( - bounds().height() - verticalOrigin, - m_selectableTableView.minimalSizeForOptimalDisplay().height()); - m_selectableTableView.setFrame(KDRect(0, verticalOrigin, bounds().width(), tableHeight), force); - return verticalOrigin + tableHeight; -} - -// CountryController -int CountryController::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 CountryController::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; -} - -CountryController::CountryController(Responder * parentResponder, KDCoordinate verticalMargin) : - ViewController(parentResponder), - m_contentView(this, this) -{ - selectableTableView()->setTopMargin(0); - selectableTableView()->setBottomMargin(verticalMargin); - for (int i = 0; i < I18n::NumberOfCountries; i++) { - m_cells[i].setMessageFont(KDFont::LargeFont); - } -} - -void CountryController::resetSelection() { - selectableTableView()->deselectTable(); - selectCellAtLocation(0, IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country())); -} - - -void CountryController::viewWillAppear() { - ViewController::viewWillAppear(); - resetSelection(); - /* FIXME : When selecting a country, changing the language, then coming back - * to select a country, some countries' names will be cropped. We force the - * TableView to refresh to prevent that. */ - selectableTableView()->reloadData(); -} - -bool CountryController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - /* FIXME : Changing the unit format should perhaps be done in setCountry.*/ - I18n::Country country = CountryAtIndex(selectedRow()); - GlobalPreferences::sharedGlobalPreferences()->setCountry(country); - Poincare::Preferences::sharedPreferences()->setUnitFormat(GlobalPreferences::sharedGlobalPreferences()->unitFormat()); - return true; - } - return false; -} - -void CountryController::willDisplayCellForIndex(HighlightCell * cell, int index) { - static_cast(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); -} - -} diff --git a/apps/shared/country_controller.h b/apps/shared/country_controller.h deleted file mode 100644 index 624802f8618..00000000000 --- a/apps/shared/country_controller.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef SHARED_COUNTRY_CONTROLLER_H -#define SHARED_COUNTRY_CONTROLLER_H - -#include -#include - -namespace Shared { - -class CountryController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { -public: - static int IndexOfCountry(I18n::Country country); - static I18n::Country CountryAtIndex(int i); - - CountryController(Responder * parentResponder, KDCoordinate verticalMargin); - virtual void resetSelection(); - - View * view() override { return &m_contentView; } - const char * title() override { return I18n::translate(I18n::Message::Country); } - void didBecomeFirstResponder() override { Container::activeApp()->setFirstResponder(selectableTableView()); } - void viewWillAppear() override; - bool handleEvent(Ion::Events::Event event) override; - - int numberOfRows() const override { return I18n::NumberOfCountries; } - KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } - HighlightCell * reusableCell(int index) override { return &m_cells[index]; } - int reusableCellCount() const override { return I18n::NumberOfCountries; } - - void willDisplayCellForIndex(HighlightCell * cell, int index) override; - -protected: - class ContentView : public View { - public: - ContentView(CountryController * controller, SelectableTableViewDataSource * dataSource); - SelectableTableView * selectableTableView() { return &m_selectableTableView; } - void drawRect(KDContext * ctx, KDRect rect) const override; - void shouldDisplayTitle(bool flag) { m_displayTitle = flag; } - protected: - void layoutSubviews(bool force = false) override; - KDCoordinate layoutTitleSubview(bool force, KDCoordinate verticalOrigin); - KDCoordinate layoutSubtitleSubview(bool force, KDCoordinate verticalOrigin); - KDCoordinate layoutTableSubview(bool force, KDCoordinate verticalOrigin); - private: - constexpr static int k_numberOfTextLines = 2; - int numberOfSubviews() const override { return 1 + 1 + k_numberOfTextLines; } - View * subviewAtIndex(int i) override; - SelectableTableView m_selectableTableView; - MessageTextView m_titleMessage; - MessageTextView m_messageLines[k_numberOfTextLines]; - bool m_displayTitle; - }; - - SelectableTableView * selectableTableView() { return m_contentView.selectableTableView(); } - ContentView m_contentView; - -private: - MessageTableCell m_cells[I18n::NumberOfCountries]; -}; - -} - -#endif diff --git a/apps/shared/language_controller.cpp b/apps/shared/language_controller.cpp deleted file mode 100644 index 5c0897f53d1..00000000000 --- 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 a3ab284e2ab..00000000000 --- 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 00000000000..ba433b95fa3 --- /dev/null +++ b/apps/shared/localization_controller.cpp @@ -0,0 +1,176 @@ +#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_countryTitleMessage.setBackgroundColor(Palette::WallScreen); + m_countryTitleMessage.setAlignment(0.5f, 0.5f); + 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 * m_controller->shouldDisplayWarning(); +} + +View * LocalizationController::ContentView::subviewAtIndex(int i) { + assert(i < numberOfSubviews()); + /* FIXME : This relies on the fact that the title is never displayed without the warning. */ + switch (i) { + case 0: + return &m_selectableTableView; + case 3: + 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()); + 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, (shouldResetSelectionToTopCell()) ? 0 : (mode() == Mode::Language) ? static_cast(GlobalPreferences::sharedGlobalPreferences()->language()) : IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country())); +} + +void LocalizationController::setMode(LocalizationController::Mode mode) { + selectableTableView()->deselectTable(); + m_mode = mode; + selectableTableView()->setTopMargin((shouldDisplayWarning()) ? 0 : selectableTableView()->bottomMargin()); + m_contentView.modeHasChanged(); +} + +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 00000000000..224ca7c37c1 --- /dev/null +++ b/apps/shared/localization_controller.h @@ -0,0 +1,79 @@ +#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 bool shouldDisplayTitle() = 0; + virtual bool shouldResetSelectionToTopCell() = 0; + bool shouldDisplayWarning() { 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]; + }; + + 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 From 5a31a6c1e2c82d0fb451f653c819955a206040dc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 20 Jul 2020 15:36:13 +0200 Subject: [PATCH 144/560] [poincare/unit] Output units based on region When the country is USA, the units will be simplified to common imperial units rather than metric. Change-Id: Ia533527a429ac26526380e324b9543b359f3b400 --- poincare/include/poincare/unit.h | 76 ++++++++++++++++++++++---------- poincare/src/unit.cpp | 17 ++++++- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index de74760992a..7e7f61cad16 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -55,13 +55,20 @@ class UnitNode final : public ExpressionNode { No, Yes }; + enum class OutputSystem { + None, + Imperial, + Metric, + All + }; template - constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix * const (&outputPrefixes)[N]) : + constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix * const (&outputPrefixes)[N], const OutputSystem outputSystem = OutputSystem::All) : m_rootSymbol(rootSymbol), m_definition(definition), m_prefixable(prefixable), m_outputPrefixes(outputPrefixes), - m_outputPrefixesLength(N) + m_outputPrefixesLength(N), + m_outputSystem(outputSystem) { } const char * rootSymbol() const { return m_rootSymbol; } @@ -73,12 +80,14 @@ class UnitNode final : public ExpressionNode { const Prefix * * prefix) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; const Prefix * bestPrefixForValue(double & value, const float exponent) const; + bool canOutputInSystem(Preferences::UnitFormat system) const; private: const char * m_rootSymbol; const char * m_definition; const Prefixable m_prefixable; const Prefix * const * m_outputPrefixes; const size_t m_outputPrefixesLength; + const OutputSystem m_outputSystem; }; class Dimension { @@ -287,7 +296,8 @@ class Unit final : public Expression { DistanceRepresentatives[] = { Representative("m", nullptr, Representative::Prefixable::Yes, - LongScalePrefixes), + LongScalePrefixes, + Representative::OutputSystem::Metric), Representative("au", "149597870700*_m", Representative::Prefixable::No, NoPrefix), @@ -299,39 +309,49 @@ class Unit final : public Expression { NoPrefix), Representative("in", "0.0254*_m", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("ft", "12*_in", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("yd", "3*_ft", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::None), Representative("mi", "1760*_yd", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), }, MassRepresentatives[] = { Representative("kg", nullptr, Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Metric), Representative("g", "0.001_kg", Representative::Prefixable::Yes, - NegativeLongScalePrefixes), + NegativeLongScalePrefixes, + Representative::OutputSystem::Metric), Representative("t", "1000_kg", Representative::Prefixable::Yes, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Metric), Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", Representative::Prefixable::Yes, NoPrefix), Representative("oz", "0.028349523125*_kg", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("lb", "16*_oz", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("ton", "2000*_lb", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), }, CurrentRepresentatives[] = { Representative("A", nullptr, @@ -435,36 +455,46 @@ class Unit final : public Expression { SurfaceRepresentatives[] = { Representative("ha", "10^4*_m^2", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Metric), Representative("acre", "43560*_ft^2", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), }, VolumeRepresentatives[] = { Representative("L", "10^-3*_m^3", Representative::Prefixable::Yes, - NegativePrefixes), + NegativePrefixes, + Representative::OutputSystem::Metric), Representative("tsp", "4.92892159375*_mL", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::None), Representative("Tbsp", "3*_tsp", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::None), Representative("floz", "0.0295735295625*_L", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("cp", "8*_floz", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), Representative("pt", "2*_cp", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::None), Representative("qt", "4*_cp", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::None), Representative("gal", "4*_qt", Representative::Prefixable::No, - NoPrefix), + NoPrefix, + Representative::OutputSystem::Imperial), }; // 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."); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 30fc8cd0505..261aaec9eeb 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -97,6 +97,16 @@ const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & v return bestPre; } +bool UnitNode::Representative::canOutputInSystem(Preferences::UnitFormat system) const { + if (m_outputSystem == OutputSystem::None) { + return false; + } + if (m_outputSystem == OutputSystem::All) { + return true; + } + return (system == Preferences::UnitFormat::Metric) == (m_outputSystem == OutputSystem::Metric); +} + template<> size_t UnitNode::Dimension::Vector::supportSize() const { size_t supportSize = 0; @@ -383,7 +393,7 @@ void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool t void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { assert(!std::isnan(*value) && exponent != 0.0f); - if (*value == 0.0 || *value == 1.0 || std::isinf(*value)) { + if (*value == 0.0 || std::isinf(*value)) { return; } UnitNode * unitNode = node(); @@ -393,12 +403,15 @@ void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool */ const Representative * bestRep = unitNode->representative(); const Prefix * bestPre = unitNode->prefix(); - double bestVal = *value; + double bestVal = (tuneRepresentative) ? DBL_MAX : *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++) { + if (!rep->canOutputInSystem(Preferences::sharedPreferences()->unitFormat())) { + continue; + } // 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 From 4d9f3aae97308ce2f081cf269d891c8fe05984c8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 20 Jul 2020 17:04:43 +0200 Subject: [PATCH 145/560] [poincare/unit] Generalized BuildTimeSplit method BuildTimesplit (used to create expressions of the form h+min+s) is now based on the more general BuildSplit. Change-Id: I3e55359cc6b9735269140942b29bd1d364fc35e7 --- poincare/include/poincare/unit.h | 1 + poincare/src/unit.cpp | 48 ++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 7e7f61cad16..82a45fb39cf 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -840,6 +840,7 @@ class Unit final : public Expression { 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 BuildSplit(double baseValue, const Unit * units, const double * conversionFactors, int numberOfUnits, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); static Expression BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); static bool IsSI(Expression & e); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 261aaec9eeb..f8e89761623 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -514,43 +514,49 @@ 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()); +Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + assert(!std::isnan(baseValue)); + if (std::isinf(baseValue) || std::fabs(baseValue) < Expression::Epsilon()) { + return Multiplication::Builder(Number::FloatNumber(baseValue), units[numberOfUnits-1]); } - /* 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]; + /* Round the base value 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 value was then computed from this approximation. + * We thus round it to avoid displaying small numbers that are + * artifacts of the previous approximations. */ + double err = std::pow(10.0, Poincare::PrintFloat::k_numberOfStoredSignificantDigits - 1 - std::ceil(log10(std::fabs(baseValue)))); + double remain = std::round(baseValue*err)/err; + + double valuesPerUnit[numberOfUnits]; Addition a = Addition::Builder(); - for (size_t i = 0; i < numberOfTimeUnits; i++) { - valuesPerUnit[i] = remain/timeFactors[i]; + for (int i = 0; i < numberOfUnits; i++) { + valuesPerUnit[i] = remain/conversionFactors[i]; // Keep only the floor of the values except for the last unit (seconds) - if (i < numberOfTimeUnits - 1) { + if (i < numberOfUnits - 1) { valuesPerUnit[i] = valuesPerUnit[i] >= 0.0 ? std::floor(valuesPerUnit[i]) : std::ceil(valuesPerUnit[i]); } - remain -= valuesPerUnit[i]*timeFactors[i]; + remain -= valuesPerUnit[i] * conversionFactors[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()); } } + 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); } +Expression Unit::BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + constexpr static int numberOfTimeUnits = 6; + Unit units[numberOfTimeUnits] = {Unit::Year(), Unit::Month(), Unit::Day(), Unit::Hour(), Unit::Minute(), Unit::Second()}; + constexpr static double timeFactors[numberOfTimeUnits] = {MonthPerYear*DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, HoursPerDay*MinutesPerHour*SecondsPerMinute, MinutesPerHour*SecondsPerMinute, SecondsPerMinute, 1.0}; + return BuildSplit(seconds, units, timeFactors, numberOfTimeUnits, context, complexFormat, angleUnit); +} + 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; From 9f40e45b20eee35a2b85ae50a8aa79ca5c1abc83 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 21 Jul 2020 11:45:50 +0200 Subject: [PATCH 146/560] [poincare/unit] Split for several dimensions A split (such as _h+_min+_s) can now be generated for distances, volumes and masses using imperial units. Change-Id: Ib3ad63614979eddd02fbe0e99f16cf09dcf7c1fc --- poincare/include/poincare/unit.h | 44 ++++++++++++++++++++++++++++++-- poincare/src/unit.cpp | 32 +++++++++++++++++++---- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 82a45fb39cf..14cfe83777e 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -505,11 +505,28 @@ class Unit final : public Expression { static const Representative constexpr * MonthRepresentative = &TimeRepresentatives[5]; static const Representative constexpr * YearRepresentative = &TimeRepresentatives[6]; static const Representative constexpr * MeterRepresentative = &DistanceRepresentatives[0]; + static_assert(sizeof(DistanceRepresentatives)/sizeof(Representative) == 8, "The Unit::MileRepresentative et al. might require to be fixed if the DistanceRepresentatives table was changed."); + static const Representative constexpr * InchRepresentative = &DistanceRepresentatives[4]; + static const Representative constexpr * FootRepresentative = &DistanceRepresentatives[5]; + static const Representative constexpr * YardRepresentative = &DistanceRepresentatives[6]; + static const Representative constexpr * MileRepresentative = &DistanceRepresentatives[7]; static const Representative constexpr * KilogramRepresentative = &MassRepresentatives[0]; + static const Representative constexpr * GramRepresentative = &MassRepresentatives[1]; + static_assert(sizeof(MassRepresentatives)/sizeof(Representative) == 7, "The Unit::OunceRepresentative et al. might require to be fixed if the MassRepresentatives table was changed."); + static const Representative constexpr * OunceRepresentative = &MassRepresentatives[4]; + static const Representative constexpr * PoundRepresentative = &MassRepresentatives[5]; static const Representative constexpr * LiterRepresentative = &VolumeRepresentatives[0]; + static_assert(sizeof(VolumeRepresentatives)/sizeof(Representative) == 8, "The Unit::FluidOunceRepresentative et al. might require to be fixed if the VolumeRepresentatives table was changed."); + static const Representative constexpr * FluidOunceRepresentative = &VolumeRepresentatives[3]; + static const Representative constexpr * CupRepresentative = &VolumeRepresentatives[4]; + static const Representative constexpr * GallonRepresentative = &VolumeRepresentatives[7]; 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_assert(sizeof(SurfaceRepresentatives)/sizeof(Representative) == 2, "The Unit::HectareRepresentative et al. might require to be fixed if the VolumeRepresentatives table was changed."); + static const Representative constexpr * HectareRepresentative = &SurfaceRepresentatives[0]; + static const Representative constexpr * AcreRepresentative = &SurfaceRepresentatives[1]; + static constexpr const Dimension DimensionTable[] = { /* The current table is sorted from most to least simple units. * The order determines the behavior of simplification. @@ -821,6 +838,7 @@ class Unit final : public Expression { static const Dimension constexpr * MassDimension = &DimensionTable[2]; static const Dimension constexpr * EnergyDimension = &DimensionTable[10]; static const Dimension constexpr * PowerDimension = &DimensionTable[11]; + static const Dimension constexpr * SurfaceDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-2]; static const Dimension constexpr * VolumeDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-1]; static constexpr const Unit::Dimension * DimensionTableUpperBound = @@ -830,7 +848,12 @@ class Unit final : public Expression { Unit(const UnitNode * node) : Expression(node) {} static Unit Builder(const Dimension * dimension, const Representative * representative, const Prefix * prefix); + static Unit Meter() { return Builder(DistanceDimension, MeterRepresentative, &EmptyPrefix); } static Unit Kilometer() { return Builder(DistanceDimension, MeterRepresentative, &KiloPrefix); } + static Unit Inch() { return Builder(DistanceDimension, InchRepresentative, &EmptyPrefix); } + static Unit Foot() { return Builder(DistanceDimension, FootRepresentative, &EmptyPrefix); } + static Unit Yard() { return Builder(DistanceDimension, YardRepresentative, &EmptyPrefix); } + static Unit Mile() { return Builder(DistanceDimension, MileRepresentative, &EmptyPrefix); } static Unit Second() { return Builder(TimeDimension, SecondRepresentative, &EmptyPrefix); } static Unit Minute() { return Builder(TimeDimension, MinuteRepresentative, &EmptyPrefix); } static Unit Hour() { return Builder(TimeDimension, HourRepresentative, &EmptyPrefix); } @@ -840,8 +863,19 @@ class Unit final : public Expression { 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 BuildSplit(double baseValue, const Unit * units, const double * conversionFactors, int numberOfUnits, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - static Expression BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + static Unit Gram() { return Builder(MassDimension, GramRepresentative, &EmptyPrefix); } + static Unit Ounce() { return Builder(MassDimension, OunceRepresentative, &EmptyPrefix); } + static Unit Pound() { return Builder(MassDimension, PoundRepresentative, &EmptyPrefix); } + static Unit FluidOunce() { return Builder(VolumeDimension, FluidOunceRepresentative, &EmptyPrefix); } + static Unit Cup() { return Builder(VolumeDimension, CupRepresentative, &EmptyPrefix); } + static Unit Gallon() { return Builder(VolumeDimension, GallonRepresentative, &EmptyPrefix); } + static Unit Hectare() { return Builder(SurfaceDimension, HectareRepresentative, &EmptyPrefix); } + static Unit Acre() { return Builder(SurfaceDimension, AcreRepresentative, &EmptyPrefix); } + static Expression BuildSplit(double baseValue, const Unit * units, const double * conversionFactors, int numberOfUnits, Context * context); + static Expression BuildTimeSplit(double seconds, Context * context); + static Expression BuildImperialDistanceSplit(double inches, Context * context); + static Expression BuildImperialMassSplit(double ounces, Context * context); + static Expression BuildImperialVolumeSplit(double fluidOunces, Context * context); static bool IsSI(Expression & e); static bool IsSISpeed(Expression & e); @@ -866,6 +900,12 @@ class Unit final : public Expression { static constexpr double DaysPerYear = 365.25; static constexpr double MonthPerYear = 12.0; static constexpr double DaysPerMonth = DaysPerYear/MonthPerYear; + static constexpr double InchesPerFoot = 12.; + static constexpr double FeetPerYard = 3.; + static constexpr double YardsPerMile = 1760.; + static constexpr double OuncesPerPound = 16.; + static constexpr double FluidOuncesPerCup = 8.; + static constexpr double CupsPerGallon = 16.; UnitNode * node() const { return static_cast(Expression::node()); } bool isSI() const; static void ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index f8e89761623..ddb3c48cbc4 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -4,10 +4,11 @@ #include #include #include +#include #include #include #include -#include +#include #include #include #include @@ -514,7 +515,7 @@ bool Unit::IsSITime(Expression & e) { return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); } -Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context) { assert(!std::isnan(baseValue)); if (std::isinf(baseValue) || std::fabs(baseValue) < Expression::Epsilon()) { return Multiplication::Builder(Number::FloatNumber(baseValue), units[numberOfUnits-1]); @@ -545,16 +546,37 @@ Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * } } - ExpressionNode::ReductionContext reductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); + ExpressionNode::ReductionContext reductionContext(context, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); // Beautify the addition into an subtraction if necessary return a.squashUnaryHierarchyInPlace().shallowBeautify(reductionContext); } -Expression Unit::BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Expression Unit::BuildTimeSplit(double seconds, Context * context) { constexpr static int numberOfTimeUnits = 6; Unit units[numberOfTimeUnits] = {Unit::Year(), Unit::Month(), Unit::Day(), Unit::Hour(), Unit::Minute(), Unit::Second()}; constexpr static double timeFactors[numberOfTimeUnits] = {MonthPerYear*DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, HoursPerDay*MinutesPerHour*SecondsPerMinute, MinutesPerHour*SecondsPerMinute, SecondsPerMinute, 1.0}; - return BuildSplit(seconds, units, timeFactors, numberOfTimeUnits, context, complexFormat, angleUnit); + return BuildSplit(seconds, units, timeFactors, numberOfTimeUnits, context); +} + +Expression Unit::BuildImperialDistanceSplit(double inches, Context * context) { + constexpr static int numberOfUnits = 4; + Unit units[numberOfUnits] = {Unit::Mile(), Unit::Yard(), Unit::Foot(), Unit::Inch()}; + constexpr static double factors[numberOfUnits] = {InchesPerFoot*FeetPerYard*YardsPerMile, InchesPerFoot*FeetPerYard, InchesPerFoot, 1.}; + return BuildSplit(inches, units, factors, numberOfUnits, context); +} + +Expression Unit::BuildImperialMassSplit(double ounces, Context * context) { + constexpr static int numberOfUnits = 2; + Unit units[numberOfUnits] = {Unit::Pound(), Unit::Ounce()}; + constexpr static double factors[numberOfUnits] = {OuncesPerPound, 1.}; + return BuildSplit(ounces, units, factors, numberOfUnits, context); +} + +Expression Unit::BuildImperialVolumeSplit(double fluidOunces, Context * context) { + constexpr static int numberOfUnits = 3; + Unit units[numberOfUnits] = {Unit::Gallon(), Unit::Cup(), Unit::FluidOunce()}; + constexpr static double factors[numberOfUnits] = {FluidOuncesPerCup*CupsPerGallon, FluidOuncesPerCup, 1.}; + return BuildSplit(fluidOunces, units, factors, numberOfUnits, context); } template Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; From 3ff25fb5c1df1153bc0972cae2b6061cf0a9e09d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 21 Jul 2020 11:56:50 +0200 Subject: [PATCH 147/560] [poincare/unit] Standard unit methods Added methods to return the standard format for each dimension, depending on the chosen unit system. Change-Id: I3591a806beca315674cc09093b57e8753db5db6a --- poincare/include/poincare/unit.h | 7 ++++ poincare/src/unit.cpp | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 14cfe83777e..c7a59123a72 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -876,6 +876,7 @@ class Unit final : public Expression { static Expression BuildImperialDistanceSplit(double inches, Context * context); static Expression BuildImperialMassSplit(double ounces, Context * context); static Expression BuildImperialVolumeSplit(double fluidOunces, Context * context); + static double ConvertedValueInUnit(Expression e, Unit unit, Context * context); static bool IsSI(Expression & e); static bool IsSISpeed(Expression & e); @@ -886,6 +887,12 @@ class Unit final : public Expression { bool isSecond() const; bool isKilogram() const; + static Expression StandardSpeedConversion(Expression e, Preferences::UnitFormat format, Context * context); + static Expression StandardDistanceConversion(Expression e, Preferences::UnitFormat format, Context * context); + static Expression StandardVolumeConversion(Expression e, Preferences::UnitFormat format, Context * context); + static Expression StandardMassConversion(Expression e, Preferences::UnitFormat format, Context * context); + static Expression StandardSurfaceConversion(Expression e, Preferences::UnitFormat format, Context * context); + // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index ddb3c48cbc4..5f0e8d67461 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -515,6 +515,15 @@ bool Unit::IsSITime(Expression & e) { return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); } + +double Unit::ConvertedValueInUnit(Expression e, Unit unit, Context * context) { + Expression conversion = UnitConvert::Builder(e.clone(), unit); + Expression newUnit; + conversion = conversion.simplify(ExpressionNode::ReductionContext(context, Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::Default)); + conversion = conversion.removeUnit(&newUnit); + return conversion.approximateToScalar(context, Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit()); +} + Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context) { assert(!std::isnan(baseValue)); if (std::isinf(baseValue) || std::fabs(baseValue) < Expression::Epsilon()) { @@ -579,6 +588,52 @@ Expression Unit::BuildImperialVolumeSplit(double fluidOunces, Context * context) return BuildSplit(fluidOunces, units, factors, numberOfUnits, context); } +Expression Unit::StandardSpeedConversion(Expression e, Preferences::UnitFormat format, Context * context) { + return UnitConvert::Builder(e.clone(), Multiplication::Builder( + format == Preferences::UnitFormat::Metric ? Unit::Kilometer() : Unit::Mile(), + Power::Builder( + Unit::Hour(), + Rational::Builder(-1) + ) + ) + ); +} + +Expression Unit::StandardDistanceConversion(Expression e, Preferences::UnitFormat format, Context * context) { + if (format == Preferences::UnitFormat::Metric) { + return UnitConvert::Builder(e.clone(), Unit::Meter()); + } + assert(format == Preferences::UnitFormat::Imperial); + double rawValue = ConvertedValueInUnit(e, Unit::Inch(), context); + return BuildImperialDistanceSplit(rawValue, context); +} + +Expression Unit::StandardVolumeConversion(Expression e, Preferences::UnitFormat format, Context * context) { + if (format == Preferences::UnitFormat::Metric) { + return UnitConvert::Builder(e.clone(), Unit::Liter()); + } + assert(format == Preferences::UnitFormat::Imperial); + double rawValue = ConvertedValueInUnit(e, Unit::FluidOunce(), context); + return BuildImperialVolumeSplit(rawValue, context); +} + +Expression Unit::StandardMassConversion(Expression e, Preferences::UnitFormat format, Context * context) { + if (format == Preferences::UnitFormat::Metric) { + return UnitConvert::Builder(e.clone(), Unit::Gram()); + } + assert(format == Preferences::UnitFormat::Imperial); + double rawValue = ConvertedValueInUnit(e, Unit::Ounce(), context); + return BuildImperialMassSplit(rawValue, context); +} + +Expression Unit::StandardSurfaceConversion(Expression e, Preferences::UnitFormat format, Context * context) { + if (format == Preferences::UnitFormat::Metric) { + return UnitConvert::Builder(e.clone(), Unit::Hectare()); + } + assert(format == Preferences::UnitFormat::Imperial); + return UnitConvert::Builder(e.clone(), Unit::Acre()); +} + 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; From 6c676782aad50412a3afb0782f4354855ebd5909 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 21 Jul 2020 13:14:20 +0200 Subject: [PATCH 148/560] [apps/calculation] New additional results on units The additional results on units now include conversions into both unit systems (metric and imperial). Change-Id: Ie0f12eb3735e775560b66c2cbd78bc9a659145bb --- .../unit_list_controller.cpp | 119 +++++++++--------- poincare/include/poincare/unit.h | 3 + poincare/src/unit.cpp | 13 ++ 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index eed7e7bb558..111fab65624 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -12,6 +12,34 @@ using namespace Shared; namespace Calculation { +void storeAndSimplify(Expression e, Expression * dest, bool requireSimplification, bool canChangeUnitPrefix) { + assert(!e.isUninitialized()); + *dest = e; + if (requireSimplification) { + Shared::PoincareHelpers::Simplify(dest, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + } + if (canChangeUnitPrefix) { + Expression newUnits; + /* If the expression has already been simplified, we do not want to reduce + * it further, as this would units introduced by a UnitConvert node back to + * SI units. */ + if (!requireSimplification) { + // Reduce to be able to removeUnit + PoincareHelpers::Reduce(dest, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + } + *dest = dest->removeUnit(&newUnits); + double value = Shared::PoincareHelpers::ApproximateToScalar(*dest, 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); + *dest = Multiplication::Builder(Number::FloatNumber(value), newUnits); + } +} + void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); @@ -24,82 +52,47 @@ void UnitListController::setExpression(Poincare::Expression e) { } size_t numberOfExpressions = 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; + Preferences::UnitFormat chosenFormat = Preferences::sharedPreferences()->unitFormat(); + Preferences::UnitFormat otherFormat = (chosenFormat == Preferences::UnitFormat::Metric) ? Preferences::UnitFormat::Imperial : Preferences::UnitFormat::Metric; if (Unit::IsSISpeed(units)) { - // 1.a. Turn speed into km/h - expressions[numberOfExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Multiplication::Builder( - Unit::Kilometer(), - Power::Builder( - Unit::Hour(), - Rational::Builder(-1) - ) - ) - ); - requireSimplification = true; // Simplify the conversion + // 1.a. Turn speed into km/h or mi/h + storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); + storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); } else if (Unit::IsSIVolume(units)) { - // 1.b. Turn volume into L - expressions[numberOfExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Unit::Liter() - ); - requireSimplification = true; // Simplify the conversion - canChangeUnitPrefix = true; // Pick best prefix (mL) + // 1.b. Turn volume into L or _gal + _cp + _floz + storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); } else if (Unit::IsSIEnergy(units)) { // 1.c. Turn energy into Wh - expressions[numberOfExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Multiplication::Builder( - Unit::Watt(), - Unit::Hour() - ) - ); - expressions[numberOfExpressions++] = UnitConvert::Builder( - m_expression.clone(), - Unit::ElectronVolt() - ); - requireSimplification = true; // Simplify the conversion - canChangeUnitPrefix = true; // Pick best prefix (kWh) + storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Multiplication::Builder(Unit::Watt(), Unit::Hour())), &expressions[numberOfExpressions++], true, true); + storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Unit::ElectronVolt()), &expressions[numberOfExpressions++], true, true); } else if (Unit::IsSITime(units)) { - // Turn time into ? year + ? month + ? day + ? h + ? min + ? s + // 1.d. Turn time into ? year + ? month + ? day + ? h + ? min + ? s double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); - expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit()); - } - // 1.d. Simplify and tune prefix of all computed expressions - size_t currentExpressionIndex = 0; - while (currentExpressionIndex < numberOfExpressions) { - assert(!expressions[currentExpressionIndex].isUninitialized()); - if (requireSimplification) { - Shared::PoincareHelpers::Simplify(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); - } - if (canChangeUnitPrefix) { - Expression newUnits; - // Reduce to be able to removeUnit - PoincareHelpers::Reduce(&expressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User); - expressions[currentExpressionIndex] = expressions[currentExpressionIndex].removeUnit(&newUnits); - double value = Shared::PoincareHelpers::ApproximateToScalar(expressions[currentExpressionIndex], App::app()->localContext()); - ExpressionNode::ReductionContext reductionContext( - App::app()->localContext(), - Preferences::sharedPreferences()->complexFormat(), - Preferences::sharedPreferences()->angleUnit(), - ExpressionNode::ReductionTarget::User, - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext); - expressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); - } - currentExpressionIndex++; + expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext()); + } else if (Unit::IsSIDistance(units)) { + // 1.e. Turn distance into _?m or _mi + _yd + _ft + _in + storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); + } else if (Unit::IsSISurface(units)) { + // 1.f. Turn surface into hectares or acres + storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); + storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); + } else if (Unit::IsSIMass(units)) { + // 1.g. Turn mass into _?g and _lb + _oz + storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); } - // 2. IS units only + // 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); @@ -109,7 +102,7 @@ void UnitListController::setExpression(Poincare::Expression e) { Expression reduceExpression = m_expression.clone(); // 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; + int currentExpressionIndex = 1; while (currentExpressionIndex < numberOfExpressions) { bool duplicateFound = false; for (size_t i = 0; i < currentExpressionIndex + 1; i++) { diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index c7a59123a72..fda797f13ec 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -883,6 +883,9 @@ class Unit final : public Expression { static bool IsSIVolume(Expression & e); static bool IsSIEnergy(Expression & e); static bool IsSITime(Expression & e); + static bool IsSIDistance(Expression & e); + static bool IsSIMass(Expression & e); + static bool IsSISurface(Expression & e); bool isMeter() const; bool isSecond() const; bool isKilogram() const; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 5f0e8d67461..de1a31bbda8 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -515,6 +515,19 @@ bool Unit::IsSITime(Expression & e) { return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); } +bool Unit::IsSIDistance(Expression & e) { + return e.type() == ExpressionNode::Type::Unit && static_cast(e).isMeter(); +} + +bool Unit::IsSIMass(Expression & e) { + return e.type() == ExpressionNode::Type::Unit && static_cast(e).isKilogram(); +} + +bool Unit::IsSISurface(Expression & e) { + 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().isTwo(); +} double Unit::ConvertedValueInUnit(Expression e, Unit unit, Context * context) { Expression conversion = UnitConvert::Builder(e.clone(), unit); From fad375c11c557d59e28036275b2a9347e4a245b9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 22 Jul 2020 10:01:53 +0200 Subject: [PATCH 149/560] [poincare] Add unitFormat to ReductionContext Change-Id: I1d3fcd2f47c973c041e1be84e9a902dd58de3562 --- .../matrix_list_controller.cpp | 6 +- .../unit_list_controller.cpp | 53 +-- apps/calculation/calculation.cpp | 2 +- apps/global_preferences.cpp | 5 - apps/global_preferences.h | 2 +- apps/shared/poincare_helpers.h | 11 +- apps/solver/equation.cpp | 3 +- apps/solver/equation_store.cpp | 14 +- apps/solver/test/helpers.cpp | 4 +- poincare/include/poincare/equal.h | 2 +- poincare/include/poincare/expression.h | 14 +- poincare/include/poincare/expression_node.h | 5 +- poincare/include/poincare/matrix.h | 2 +- poincare/include/poincare/number.h | 2 +- poincare/include/poincare/preferences.h | 3 - poincare/include/poincare/unit.h | 12 +- poincare/src/addition.cpp | 1 + poincare/src/equal.cpp | 4 +- poincare/src/expression.cpp | 38 +- poincare/src/integer.cpp | 2 +- poincare/src/matrix.cpp | 4 +- poincare/src/unit.cpp | 37 +- poincare/src/unit_convert.cpp | 3 + poincare/test/approximation.cpp | 352 +++++++++--------- poincare/test/derivative.cpp | 4 +- poincare/test/expression_properties.cpp | 38 +- poincare/test/helper.cpp | 40 +- poincare/test/helper.h | 17 +- poincare/test/simplification.cpp | 323 ++++++++-------- 29 files changed, 513 insertions(+), 490 deletions(-) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 6ab5e6a6e71..eed72ad189f 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -1,6 +1,7 @@ #include "matrix_list_controller.h" #include "../app.h" #include "../../shared/poincare_helpers.h" +#include #include #include #include @@ -30,6 +31,7 @@ void MatrixListController::setExpression(Poincare::Expression e) { context, preferences->complexFormat(), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::SystemForApproximation, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); @@ -78,7 +80,7 @@ Poincare::Layout MatrixListController::getLayoutFromExpression(Expression e, Con Expression approximateExpression; Expression simplifiedExpression; e.simplifyAndApproximate(&simplifiedExpression, &approximateExpression, context, - preferences->complexFormat(), preferences->angleUnit(), + preferences->complexFormat(), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); // simplify might have been interrupted, in which case we use approximate if (simplifiedExpression.isUninitialized()) { @@ -100,4 +102,4 @@ I18n::Message MatrixListController::messageAtIndex(int index) { return messages[m_indexMessageMap[index]]; } -} \ No newline at end of file +} diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 111fab65624..2ae84d91721 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -12,11 +12,11 @@ using namespace Shared; namespace Calculation { -void storeAndSimplify(Expression e, Expression * dest, bool requireSimplification, bool canChangeUnitPrefix) { +void storeAndSimplify(Expression e, Expression * dest, bool requireSimplification, bool canChangeUnitPrefix, ExpressionNode::ReductionContext reductionContext) { assert(!e.isUninitialized()); *dest = e; if (requireSimplification) { - Shared::PoincareHelpers::Simplify(dest, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + *dest = dest->simplify(reductionContext); } if (canChangeUnitPrefix) { Expression newUnits; @@ -25,17 +25,10 @@ void storeAndSimplify(Expression e, Expression * dest, bool requireSimplificatio * SI units. */ if (!requireSimplification) { // Reduce to be able to removeUnit - PoincareHelpers::Reduce(dest, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + *dest = dest->reduce(reductionContext); } *dest = dest->removeUnit(&newUnits); double value = Shared::PoincareHelpers::ApproximateToScalar(*dest, 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); *dest = Multiplication::Builder(Number::FloatNumber(value), newUnits); } } @@ -59,37 +52,51 @@ void UnitListController::setExpression(Poincare::Expression e) { // Reduce to be able to recognize units PoincareHelpers::Reduce(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User); copy = copy.removeUnit(&units); - Preferences::UnitFormat chosenFormat = Preferences::sharedPreferences()->unitFormat(); + Preferences::UnitFormat chosenFormat = GlobalPreferences::sharedGlobalPreferences()->unitFormat(); Preferences::UnitFormat otherFormat = (chosenFormat == Preferences::UnitFormat::Metric) ? Preferences::UnitFormat::Imperial : Preferences::UnitFormat::Metric; + ExpressionNode::ReductionContext chosenFormatContext( + App::app()->localContext(), + Preferences::sharedPreferences()->complexFormat(), + Preferences::sharedPreferences()->angleUnit(), + chosenFormat, + ExpressionNode::ReductionTarget::User, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + ExpressionNode::ReductionContext otherFormatContext( + App::app()->localContext(), + Preferences::sharedPreferences()->complexFormat(), + Preferences::sharedPreferences()->angleUnit(), + otherFormat, + ExpressionNode::ReductionTarget::User, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); if (Unit::IsSISpeed(units)) { // 1.a. Turn speed into km/h or mi/h - storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); - storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); + storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext); + storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext); } else if (Unit::IsSIVolume(units)) { // 1.b. Turn volume into L or _gal + _cp + _floz - storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); - storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); + storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); } else if (Unit::IsSIEnergy(units)) { // 1.c. Turn energy into Wh - storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Multiplication::Builder(Unit::Watt(), Unit::Hour())), &expressions[numberOfExpressions++], true, true); - storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Unit::ElectronVolt()), &expressions[numberOfExpressions++], true, true); + storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Multiplication::Builder(Unit::Watt(), Unit::Hour())), &expressions[numberOfExpressions++], true, true, chosenFormatContext); + storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Unit::ElectronVolt()), &expressions[numberOfExpressions++], true, true, chosenFormatContext); } else if (Unit::IsSITime(units)) { // 1.d. Turn time into ? year + ? month + ? day + ? h + ? min + ? s double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext()); } else if (Unit::IsSIDistance(units)) { // 1.e. Turn distance into _?m or _mi + _yd + _ft + _in - storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); - storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); + storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); } else if (Unit::IsSISurface(units)) { // 1.f. Turn surface into hectares or acres - storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); - storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], true, false); + storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext); + storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext); } else if (Unit::IsSIMass(units)) { // 1.g. Turn mass into _?g and _lb + _oz - storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), chosenFormat, App::app()->localContext()), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric); - storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), otherFormat, App::app()->localContext()), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric); + storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); + storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); } // 2. SI units only diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index c51091768c1..f0c62dddb61 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -218,7 +218,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 diff --git a/apps/global_preferences.cpp b/apps/global_preferences.cpp index 5c6c4cd7700..c6644f5fdcb 100644 --- a/apps/global_preferences.cpp +++ b/apps/global_preferences.cpp @@ -5,11 +5,6 @@ GlobalPreferences * GlobalPreferences::sharedGlobalPreferences() { return &globalPreferences; } -void GlobalPreferences::setCountry(I18n::Country country) { - m_country = country; - Poincare::Preferences::sharedPreferences()->setUnitFormat(unitFormat()); -} - GlobalPreferences::ExamMode GlobalPreferences::examMode() const { if (m_examMode == ExamMode::Unknown) { uint8_t mode = Ion::ExamMode::FetchExamMode(); diff --git a/apps/global_preferences.h b/apps/global_preferences.h index b4471d008fa..c6c20ff8ba8 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -15,7 +15,7 @@ class GlobalPreferences { 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); + 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(); } diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 774547e174c..372c66add09 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,33 @@ 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 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) { diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index 80d2a4cab89..a9b14f1941a 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -1,4 +1,5 @@ #include "equation.h" +#include #include #include #include @@ -49,7 +50,7 @@ Expression Equation::Model::standardForm(const Storage::Record * record, Context *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()); + *returnedExpression = static_cast(expressionRed).standardEquation(contextToUse, Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), expressionInputWithoutFunctions, contextToUse), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); } else { assert(expressionRed.type() == ExpressionNode::Type::Rational && static_cast(expressionRed).isOne()); // The equality was reduced which means the equality was always true. diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 867a19d7c59..8517bef6c46 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -180,7 +180,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 && modelForRecord(definedRecordAtIndex(i))->standardForm(context, replaceFunctionsButNotSymbols).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 @@ -218,6 +218,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex context, updatedComplexFormat(context), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); @@ -259,7 +260,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 +270,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 +288,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; @@ -311,7 +313,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,7 +325,7 @@ 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(); } @@ -340,7 +342,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 diff --git a/apps/solver/test/helpers.cpp b/apps/solver/test/helpers.cpp index 119e085f650..a5bba40e372 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/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index 6826f35ab28..5f39dc7720a 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -39,7 +39,7 @@ class Equal final : public Expression { 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) const; // Expression Expression shallowReduce(); }; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 4639195aaac..e658a926d71 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -196,14 +196,14 @@ class Expression : public TreeHandle { * 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 */ @@ -226,7 +226,7 @@ class Expression : public TreeHandle { /* 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; @@ -247,11 +247,11 @@ class Expression : public TreeHandle { * 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 mapOnMatrixFirstChild(ExpressionNode::ReductionContext reductionContext); @@ -267,7 +267,7 @@ class Expression : public TreeHandle { 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 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; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index c08435fc33d..e30d627ce13 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -158,10 +158,11 @@ class ExpressionNode : public TreeNode { class ReductionContext { public: - ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, UnitConversion unitConversion = UnitConversion::Default) : + ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, UnitConversion unitConversion = UnitConversion::Default) : m_context(context), m_complexFormat(complexFormat), m_angleUnit(angleUnit), + m_unitFormat(unitFormat), m_target(target), m_symbolicComputation(symbolicComputation), m_unitConversion(unitConversion) @@ -169,6 +170,7 @@ class ExpressionNode : public TreeNode { Context * context() { return m_context; } Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } Preferences::AngleUnit angleUnit() const { return m_angleUnit; } + 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; } @@ -176,6 +178,7 @@ class ExpressionNode : public TreeNode { Context * m_context; Preferences::ComplexFormat m_complexFormat; Preferences::AngleUnit m_angleUnit; + Preferences::UnitFormat m_unitFormat; ReductionTarget m_target; SymbolicComputation m_symbolicComputation; UnitConversion m_unitConversion; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 30ea4f5d467..06013fffe05 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -74,7 +74,7 @@ class Matrix final : public Expression { 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); diff --git a/poincare/include/poincare/number.h b/poincare/include/poincare/number.h index ea2b48f2add..7c4f955c216 100644 --- a/poincare/include/poincare/number.h +++ b/poincare/include/poincare/number.h @@ -53,7 +53,7 @@ class Number : public Expression { 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); diff --git a/poincare/include/poincare/preferences.h b/poincare/include/poincare/preferences.h index f8354186f5e..e98a7ac39da 100644 --- a/poincare/include/poincare/preferences.h +++ b/poincare/include/poincare/preferences.h @@ -50,8 +50,6 @@ class Preferences final { void setEditionMode(EditionMode editionMode) { m_editionMode = editionMode; } ComplexFormat complexFormat() const { return m_complexFormat; } void setComplexFormat(Preferences::ComplexFormat complexFormat) { m_complexFormat = complexFormat; } - UnitFormat unitFormat() const { return m_unitFormat; } - void setUnitFormat(UnitFormat unitFormat) { m_unitFormat = unitFormat; } uint8_t numberOfSignificantDigits() const { return m_numberOfSignificantDigits; } void setNumberOfSignificantDigits(uint8_t numberOfSignificantDigits) { m_numberOfSignificantDigits = numberOfSignificantDigits; } private: @@ -59,7 +57,6 @@ class Preferences final { PrintFloatMode m_displayMode; EditionMode m_editionMode; ComplexFormat m_complexFormat; - UnitFormat m_unitFormat; uint8_t m_numberOfSignificantDigits; }; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index fda797f13ec..9c6f655e4fc 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -876,7 +876,7 @@ class Unit final : public Expression { static Expression BuildImperialDistanceSplit(double inches, Context * context); static Expression BuildImperialMassSplit(double ounces, Context * context); static Expression BuildImperialVolumeSplit(double fluidOunces, Context * context); - static double ConvertedValueInUnit(Expression e, Unit unit, Context * context); + static double ConvertedValueInUnit(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); static bool IsSI(Expression & e); static bool IsSISpeed(Expression & e); @@ -890,11 +890,11 @@ class Unit final : public Expression { bool isSecond() const; bool isKilogram() const; - static Expression StandardSpeedConversion(Expression e, Preferences::UnitFormat format, Context * context); - static Expression StandardDistanceConversion(Expression e, Preferences::UnitFormat format, Context * context); - static Expression StandardVolumeConversion(Expression e, Preferences::UnitFormat format, Context * context); - static Expression StandardMassConversion(Expression e, Preferences::UnitFormat format, Context * context); - static Expression StandardSurfaceConversion(Expression e, Preferences::UnitFormat format, Context * context); + static Expression StandardSpeedConversion(Expression e, ExpressionNode::ReductionContext reductionContext); + static Expression StandardDistanceConversion(Expression e, ExpressionNode::ReductionContext reductionContext); + static Expression StandardVolumeConversion(Expression e, ExpressionNode::ReductionContext reductionContext); + static Expression StandardMassConversion(Expression e, ExpressionNode::ReductionContext reductionContext); + static Expression StandardSurfaceConversion(Expression e, ExpressionNode::ReductionContext reductionContext); // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index 7f868ab488a..070168b28b3 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -441,6 +441,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index f5f16e68648..6ae1f00cc63 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -44,13 +44,13 @@ Evaluation EqualNode::templatedApproximate(Context * context, Preferences::Co } -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) 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, ExpressionNode::ReductionTarget::SystemForAnalysis)); } Expression Equal::shallowReduce() { diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 21daee9cbe3..a26abadea75 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -214,7 +214,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; @@ -229,7 +229,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); @@ -250,7 +250,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. */ @@ -479,11 +479,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; } @@ -570,9 +570,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; } @@ -589,12 +589,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()) { @@ -603,7 +603,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()) { @@ -611,7 +611,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()) { @@ -693,15 +693,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(); @@ -868,8 +868,8 @@ U Expression::approximateToScalar(Context * context, Preferences::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(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); } @@ -1164,8 +1164,8 @@ template Expression Expression::approximate(Context * context, Preferenc 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(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; diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index 6cbe5b91cb9..b85150974d1 100644 --- a/poincare/src/integer.cpp +++ b/poincare/src/integer.cpp @@ -695,7 +695,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/matrix.cpp b/poincare/src/matrix.cpp index 6c7df381521..97417781c15 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -128,9 +128,9 @@ 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; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index de1a31bbda8..01c6e103f6a 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -410,7 +410,7 @@ void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool const Representative * startRep = tuneRepresentative ? dim->stdRepresentative() : bestRep; const Representative * endRep = tuneRepresentative ? dim->representativesUpperBound() : bestRep + 1; for (const Representative * rep = startRep; rep < endRep; rep++) { - if (!rep->canOutputInSystem(Preferences::sharedPreferences()->unitFormat())) { + if (!rep->canOutputInSystem(reductionContext.unitFormat())) { continue; } // evaluate quotient @@ -529,12 +529,12 @@ bool Unit::IsSISurface(Expression & e) { e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isTwo(); } -double Unit::ConvertedValueInUnit(Expression e, Unit unit, Context * context) { +double Unit::ConvertedValueInUnit(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext) { Expression conversion = UnitConvert::Builder(e.clone(), unit); Expression newUnit; - conversion = conversion.simplify(ExpressionNode::ReductionContext(context, Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::Default)); + conversion = conversion.simplify(reductionContext); conversion = conversion.removeUnit(&newUnit); - return conversion.approximateToScalar(context, Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit()); + return conversion.approximateToScalar(reductionContext.context(), Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit()); } Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context) { @@ -568,7 +568,7 @@ Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * } } - ExpressionNode::ReductionContext reductionContext(context, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); + ExpressionNode::ReductionContext reductionContext(context, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, Preferences::UnitFormat::Imperial, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); // Beautify the addition into an subtraction if necessary return a.squashUnaryHierarchyInPlace().shallowBeautify(reductionContext); } @@ -601,7 +601,8 @@ Expression Unit::BuildImperialVolumeSplit(double fluidOunces, Context * context) return BuildSplit(fluidOunces, units, factors, numberOfUnits, context); } -Expression Unit::StandardSpeedConversion(Expression e, Preferences::UnitFormat format, Context * context) { +Expression Unit::StandardSpeedConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { + Preferences::UnitFormat format = reductionContext.unitFormat(); return UnitConvert::Builder(e.clone(), Multiplication::Builder( format == Preferences::UnitFormat::Metric ? Unit::Kilometer() : Unit::Mile(), Power::Builder( @@ -612,34 +613,38 @@ Expression Unit::StandardSpeedConversion(Expression e, Preferences::UnitFormat f ); } -Expression Unit::StandardDistanceConversion(Expression e, Preferences::UnitFormat format, Context * context) { +Expression Unit::StandardDistanceConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { + Preferences::UnitFormat format = reductionContext.unitFormat(); if (format == Preferences::UnitFormat::Metric) { return UnitConvert::Builder(e.clone(), Unit::Meter()); } assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::Inch(), context); - return BuildImperialDistanceSplit(rawValue, context); + double rawValue = ConvertedValueInUnit(e, Unit::Inch(), reductionContext); + return BuildImperialDistanceSplit(rawValue, reductionContext.context()); } -Expression Unit::StandardVolumeConversion(Expression e, Preferences::UnitFormat format, Context * context) { +Expression Unit::StandardVolumeConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { + Preferences::UnitFormat format = reductionContext.unitFormat(); if (format == Preferences::UnitFormat::Metric) { return UnitConvert::Builder(e.clone(), Unit::Liter()); } assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::FluidOunce(), context); - return BuildImperialVolumeSplit(rawValue, context); + double rawValue = ConvertedValueInUnit(e, Unit::FluidOunce(), reductionContext); + return BuildImperialVolumeSplit(rawValue, reductionContext.context()); } -Expression Unit::StandardMassConversion(Expression e, Preferences::UnitFormat format, Context * context) { +Expression Unit::StandardMassConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { + Preferences::UnitFormat format = reductionContext.unitFormat(); if (format == Preferences::UnitFormat::Metric) { return UnitConvert::Builder(e.clone(), Unit::Gram()); } assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::Ounce(), context); - return BuildImperialMassSplit(rawValue, context); + double rawValue = ConvertedValueInUnit(e, Unit::Ounce(), reductionContext); + return BuildImperialMassSplit(rawValue, reductionContext.context()); } -Expression Unit::StandardSurfaceConversion(Expression e, Preferences::UnitFormat format, Context * context) { +Expression Unit::StandardSurfaceConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { + Preferences::UnitFormat format = reductionContext.unitFormat(); if (format == Preferences::UnitFormat::Metric) { return UnitConvert::Builder(e.clone(), Unit::Hectare()); } diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index c8c2acf7c36..995798561fc 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -42,6 +42,7 @@ void UnitConvert::deepReduceChildren(ExpressionNode::ReductionContext reductionC reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, ExpressionNode::UnitConversion::None); @@ -56,6 +57,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined); Expression unit; @@ -70,6 +72,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), + reductionContext.unitFormat(), reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, ExpressionNode::UnitConversion::None); diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index ec896ab7f37..30b2d57d57a 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"); } @@ -232,7 +232,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"); @@ -244,22 +244,22 @@ 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("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"); @@ -319,7 +319,7 @@ 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(5, 7, 0.3162)", "1.265256ᴇ-10", Radian, Cartesian, 7); + 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"); @@ -375,10 +375,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]]"); @@ -395,8 +395,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×𝐢"); @@ -498,8 +498,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) @@ -525,10 +525,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) @@ -550,9 +550,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) @@ -567,27 +567,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); @@ -605,36 +605,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) @@ -645,31 +645,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); @@ -695,9 +695,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) @@ -717,8 +717,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) @@ -738,8 +738,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) @@ -753,19 +753,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); @@ -793,13 +793,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) @@ -826,15 +826,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); @@ -876,100 +876,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("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("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("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, 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, 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×𝐢"); @@ -981,8 +981,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"); @@ -995,13 +995,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/derivative.cpp b/poincare/test/derivative.cpp index 05d2a0a1a1f..b9a9100142e 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -34,7 +34,7 @@ void assert_parses_and_reduces_as(const char * expression, const char * simplifi ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllSymbolsWithDefinitionsOrUndefined; #endif - assert_parsed_expression_simplify_to(expression, simplifiedDerivative, User, Radian, Cartesian, symbolicComputation); + assert_parsed_expression_simplify_to(expression, simplifiedDerivative, User, Radian, Metric, Cartesian, symbolicComputation); } QUIZ_CASE(poincare_derivative_literals) { @@ -166,4 +166,4 @@ QUIZ_CASE(poincare_derivative_functions) { #endif emptyGlobalContext(); -} \ No newline at end of file +} diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index cc3d11c4dbc..97af3fde373 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -105,10 +105,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); } @@ -166,14 +166,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); } @@ -207,10 +207,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); } @@ -240,9 +240,9 @@ 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) { +void assert_reduced_expression_has_characteristic_range(Expression e, float range, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Degree, Preferences::UnitFormat unitFormat = Metric) { Shared::GlobalContext globalContext; - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, Preferences::ComplexFormat::Cartesian, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation)); + e = e.reduce(ExpressionNode::ReductionContext(&globalContext, Preferences::ComplexFormat::Cartesian, angleUnit, unitFormat, ExpressionNode::ReductionTarget::SystemForApproximation)); if (std::isnan(range)) { quiz_assert(std::isnan(e.characteristicXRange(&globalContext, angleUnit))); } else { @@ -323,16 +323,16 @@ QUIZ_CASE(poincare_properties_get_variables) { assert_expression_has_variables("a+b+c+d+e+f", variableBuffer9, 6); } -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); @@ -363,9 +363,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(); @@ -374,7 +374,7 @@ 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; @@ -395,7 +395,7 @@ QUIZ_CASE(poincare_properties_remove_unit) { } 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); + Expression time = Unit::BuildTimeSplit(totalSeconds, context); constexpr static int bufferSize = 100; char buffer[bufferSize]; time.serialize(buffer, bufferSize, DecimalMode); @@ -404,7 +404,7 @@ void assert_seconds_split_to(double totalSeconds, const char * splittedTime, Con Expression extract_unit(const char * expression) { Shared::GlobalContext globalContext; - ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion); + ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion); Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); Expression unit; e.removeUnit(&unit); diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index d940dd1086a..ba8298bc849 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); } @@ -119,29 +119,29 @@ bool IsApproximatelyEqual(double observedValue, double expectedValue, double pre } 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); @@ -166,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 841a5bc1126..13ba89accf3 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,11 +41,12 @@ 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 @@ -51,10 +54,10 @@ void assert_parsed_expression_simplify_to(const char * expression, const char * * 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/simplification.cpp b/poincare/test/simplification.cpp index 301f6b8f95b..5a7bfd3d5e0 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -106,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"); @@ -528,11 +528,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"); @@ -672,8 +672,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"); @@ -1154,8 +1154,8 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("4→_km/_m", Undefined::Name()); assert_parsed_expression_simplify_to("3×_min→_s+1-1", Undefined::Name()); - assert_reduce("_m→a", Radian, Real); - assert_reduce("_m→b", Radian, Real); + 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"); @@ -1171,170 +1171,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("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("(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, 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)", "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("gcd(x,gcd(y,z))", "gcd(x,y,z)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("gcd(3, 1, 2, x, x^2)", "gcd(x^2,x,3,2,1)", 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("lcm(x,lcm(y,z))", "lcm(x,y,z)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("lcm(3, 1, 2, x, x^2)", "lcm(x^2,x,3,2,1)", 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(); } @@ -1391,19 +1392,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(); } @@ -1483,7 +1484,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) { @@ -1499,49 +1500,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) { From b2f81db4a9f0b08d8033f0b882fbcc7d911a8ed5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 22 Jul 2020 16:43:25 +0200 Subject: [PATCH 150/560] [apps/home] Apps placement defined by country Instead of relying on the order in which the apps are passed at compile time, the look of the Home app is defined in a config file, and depends on the country chosen. Change-Id: If7f56a284e0001d6d75ece1e7efdf5fd1d0a9978 --- apps/country_preferences.csv | 22 ++++----- apps/country_preferences.h | 12 ++++- apps/global_preferences.h | 1 + apps/home/Makefile | 19 ++++++++ apps/home/apps_layout.csv | 2 + apps/home/apps_layout.py | 90 ++++++++++++++++++++++++++++++++++++ apps/home/controller.cpp | 5 +- 7 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 apps/home/apps_layout.csv create mode 100644 apps/home/apps_layout.py diff --git a/apps/country_preferences.csv b/apps/country_preferences.csv index 0e95a872b35..55b1965051e 100644 --- a/apps/country_preferences.csv +++ b/apps/country_preferences.csv @@ -1,11 +1,11 @@ -CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat -CA,StandardOnly,MedianOfSublist,Metric -DE,StandardOnly,MedianOfSublist,Metric -ES,StandardOnly,MedianOfSublist,Metric -FR,StandardOnly,CumulatedFrequency,Metric -GB,StandardOnly,MedianOfSublist,Metric -IT,StandardOnly,CumulatedFrequency,Metric -NL,All,MedianOfSublist,Metric -PT,StandardOnly,MedianOfSublist,Metric -US,StandardOnly,MedianOfSublist,Imperial -WW,StandardOnly,MedianOfSublist,Metric +CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat,CountryPreferences::HomeAppsLayout +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 +WW,StandardOnly,MedianOfSublist,Metric,Default diff --git a/apps/country_preferences.h b/apps/country_preferences.h index 703801c97b4..2ec6bb7ee9a 100644 --- a/apps/country_preferences.h +++ b/apps/country_preferences.h @@ -15,20 +15,28 @@ class CountryPreferences { CumulatedFrequency }; - constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat) : + 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_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/global_preferences.h b/apps/global_preferences.h index c6c20ff8ba8..215824b8080 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -19,6 +19,7 @@ class GlobalPreferences { 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; } ExamMode examMode() const; void setExamMode(ExamMode examMode); diff --git a/apps/home/Makefile b/apps/home/Makefile index fc45cb479ba..c76c4580862 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/apps_layout.csv b/apps/home/apps_layout.csv new file mode 100644 index 00000000000..3e2f967d331 --- /dev/null +++ b/apps/home/apps_layout.csv @@ -0,0 +1,2 @@ +Default,calculation,graph,code,statistics,probability,solver,sequence,regression,settings +HidePython,calculation,graph,solver,statistics,probability,regression,sequence,code,settings diff --git a/apps/home/apps_layout.py b/apps/home/apps_layout.py new file mode 100644 index 00000000000..cff1f712b87 --- /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/controller.cpp b/apps/home/controller.cpp index 754a367dd2a..387cc5358ed 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" @@ -60,7 +61,7 @@ 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(); - ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1); + ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1)); if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); } else { @@ -128,7 +129,7 @@ void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { appCell->setVisible(false); } else { appCell->setVisible(true); - ::App::Descriptor * descriptor = container->appSnapshotAtIndex(appIndex)->descriptor(); + ::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor(); appCell->setAppDescriptor(descriptor); } } From 284c5015325315853b65d4fd869c9382ed7bdc85 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 24 Jul 2020 15:09:17 +0200 Subject: [PATCH 151/560] [apps/unit] Add restriction to some input prefixes The symbol for pint (_pt) conflicts with the symbol for pico-tonne. To solve that, prefixes for tonnes are now restricted to the positive prefixes : k, M, G, T. Change-Id: Ie968374bbb5e0ebd2c0f08e4b1bdc1708eb6a041 --- poincare/include/poincare/unit.h | 7 ++++--- poincare/src/unit.cpp | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 9c6f655e4fc..2360fcf2ba1 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -53,7 +53,8 @@ class UnitNode final : public ExpressionNode { * input, whereas the allowed output prefixes are prescribed manually. */ enum class Prefixable { No, - Yes + Yes, + PositiveOnly }; enum class OutputSystem { None, @@ -73,7 +74,7 @@ class UnitNode final : public ExpressionNode { } const char * rootSymbol() const { return m_rootSymbol; } const char * definition() const { return m_definition; } - bool isPrefixable() const { return m_prefixable == Prefixable::Yes; } + bool isPrefixable() const { return m_prefixable != Prefixable::No; } const Prefix * const * outputPrefixes() const { return m_outputPrefixes; } size_t outputPrefixesLength() const { return m_outputPrefixesLength; } bool canParse(const char * symbol, size_t length, @@ -334,7 +335,7 @@ class Unit final : public Expression { NegativeLongScalePrefixes, Representative::OutputSystem::Metric), Representative("t", "1000_kg", - Representative::Prefixable::Yes, + Representative::Prefixable::PositiveOnly, NoPrefix, Representative::OutputSystem::Metric), Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 01c6e103f6a..069cde29b10 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -31,9 +31,10 @@ bool UnitNode::Representative::canParse(const char * symbol, size_t length, *prefix = &Unit::EmptyPrefix; return length == 0; } - size_t numberOfPrefixes = sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix *); + const Prefix * const * prefixesList = (m_prefixable == Prefixable::PositiveOnly) ? Unit::PositiveLongScalePrefixes : Unit::AllPrefixes; + size_t numberOfPrefixes = ((m_prefixable == Prefixable::PositiveOnly) ? sizeof(Unit::PositiveLongScalePrefixes) : sizeof(Unit::AllPrefixes))/sizeof(Unit::Prefix *); for (size_t i = 0; i < numberOfPrefixes; i++) { - const Prefix * pre = Unit::AllPrefixes[i]; + const Prefix * pre = prefixesList[i]; const char * prefixSymbol = pre->symbol(); if (strncmp(symbol, prefixSymbol, length) == 0 && prefixSymbol[length] == 0) From f288573db0bad927dff64b9552331f0806c4ed9b Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 24 Jul 2020 15:30:48 +0200 Subject: [PATCH 152/560] [apps/unit] Add short and long tons Change-Id: Ib48c6a7c773e9e1d18c9f55e6dda5330fe0a28f9 --- apps/math_toolbox.cpp | 2 ++ apps/shared.universal.i18n | 3 ++- apps/toolbox.de.i18n | 1 + apps/toolbox.en.i18n | 1 + apps/toolbox.es.i18n | 1 + apps/toolbox.fr.i18n | 1 + apps/toolbox.it.i18n | 1 + apps/toolbox.nl.i18n | 1 + apps/toolbox.pt.i18n | 1 + poincare/include/poincare/unit.h | 11 +++++++++-- poincare/src/unit.cpp | 6 +++--- 11 files changed, 23 insertions(+), 6 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 8524596389a..ae33aa01cce 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -143,6 +143,8 @@ const ToolboxMessageTree unitMassGramChildren[] = { const ToolboxMessageTree unitMassImperialChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitMassPoundSymbol, I18n::Message::UnitMassPound), ToolboxMessageTree::Leaf(I18n::Message::UnitMassOunceSymbol, I18n::Message::UnitMassOunce), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassShortTonSymbol, I18n::Message::UnitMassShortTon), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassLongTonSymbol, I18n::Message::UnitMassLongTon) }; const ToolboxMessageTree unitMassChildren[] = { diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 68b7a3e3fb8..0ffd90a8439 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -29,7 +29,8 @@ UnitMassGramNanoSymbol = "_ng" UnitMassTonneSymbol = "_t" UnitMassOunceSymbol = "_oz" UnitMassPoundSymbol = "_lb" -UnitMassShortTonSymbol = "_ton" +UnitMassShortTonSymbol = "_shtn" +UnitMassLongTonSymbol = "_lgtn" UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 16359e3671c..bba947d96da 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonne" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Elektrischer Strom" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index af73af4da39..2b55b9276b5 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonne" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Electric current" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 549aaef5a91..8e7b57a3ec1 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonne" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Electric current" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 425159d4060..e1c891a60fd 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonne" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Intensité du courant électrique" UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index eabeccbc12a..1de9c59909d 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonnellata" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Intensità di corrente elettrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index ce8da3c1c1e..a31ece72212 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Ton" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Elektrische stroom" UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 64df135f5a8..f99af1d5a54 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -36,6 +36,7 @@ UnitMassTonne = "Tonelada" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" +UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Intensidade da corrente elétrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Miliampere" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 2360fcf2ba1..853e269a0f6 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -349,10 +349,14 @@ class Unit final : public Expression { Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("ton", "2000*_lb", + Representative("shtn", "2000*_lb", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), + Representative("lgtn", "2240*_lb", + Representative::Prefixable::No, + NoPrefix, + Representative::OutputSystem::None), }, CurrentRepresentatives[] = { Representative("A", nullptr, @@ -513,9 +517,10 @@ class Unit final : public Expression { static const Representative constexpr * MileRepresentative = &DistanceRepresentatives[7]; static const Representative constexpr * KilogramRepresentative = &MassRepresentatives[0]; static const Representative constexpr * GramRepresentative = &MassRepresentatives[1]; - static_assert(sizeof(MassRepresentatives)/sizeof(Representative) == 7, "The Unit::OunceRepresentative et al. might require to be fixed if the MassRepresentatives table was changed."); + static_assert(sizeof(MassRepresentatives)/sizeof(Representative) == 8, "The Unit::OunceRepresentative et al. might require to be fixed if the MassRepresentatives table was changed."); static const Representative constexpr * OunceRepresentative = &MassRepresentatives[4]; static const Representative constexpr * PoundRepresentative = &MassRepresentatives[5]; + static const Representative constexpr * ShortTonRepresentative = &MassRepresentatives[6]; static const Representative constexpr * LiterRepresentative = &VolumeRepresentatives[0]; static_assert(sizeof(VolumeRepresentatives)/sizeof(Representative) == 8, "The Unit::FluidOunceRepresentative et al. might require to be fixed if the VolumeRepresentatives table was changed."); static const Representative constexpr * FluidOunceRepresentative = &VolumeRepresentatives[3]; @@ -867,6 +872,7 @@ class Unit final : public Expression { static Unit Gram() { return Builder(MassDimension, GramRepresentative, &EmptyPrefix); } static Unit Ounce() { return Builder(MassDimension, OunceRepresentative, &EmptyPrefix); } static Unit Pound() { return Builder(MassDimension, PoundRepresentative, &EmptyPrefix); } + static Unit ShortTon() { return Builder(MassDimension, ShortTonRepresentative, &EmptyPrefix); } static Unit FluidOunce() { return Builder(VolumeDimension, FluidOunceRepresentative, &EmptyPrefix); } static Unit Cup() { return Builder(VolumeDimension, CupRepresentative, &EmptyPrefix); } static Unit Gallon() { return Builder(VolumeDimension, GallonRepresentative, &EmptyPrefix); } @@ -915,6 +921,7 @@ class Unit final : public Expression { static constexpr double FeetPerYard = 3.; static constexpr double YardsPerMile = 1760.; static constexpr double OuncesPerPound = 16.; + static constexpr double PoundsPerShortTon = 2000.; static constexpr double FluidOuncesPerCup = 8.; static constexpr double CupsPerGallon = 16.; UnitNode * node() const { return static_cast(Expression::node()); } diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 069cde29b10..12fc5b77fd2 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -589,9 +589,9 @@ Expression Unit::BuildImperialDistanceSplit(double inches, Context * context) { } Expression Unit::BuildImperialMassSplit(double ounces, Context * context) { - constexpr static int numberOfUnits = 2; - Unit units[numberOfUnits] = {Unit::Pound(), Unit::Ounce()}; - constexpr static double factors[numberOfUnits] = {OuncesPerPound, 1.}; + constexpr static int numberOfUnits = 3; + Unit units[numberOfUnits] = {Unit::ShortTon(), Unit::Pound(), Unit::Ounce()}; + constexpr static double factors[numberOfUnits] = {OuncesPerPound*PoundsPerShortTon, OuncesPerPound, 1.}; return BuildSplit(ounces, units, factors, numberOfUnits, context); } From 87dd1b63108c196039a229388aed1a949ebd7a3e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 24 Jul 2020 16:52:45 +0200 Subject: [PATCH 153/560] [apps/i18n] Translate imperial units Change-Id: I8f586b752c0c2114dfb656e34c9b9d928c5b6c59 --- apps/toolbox.de.i18n | 26 +++++++++++++------------- apps/toolbox.es.i18n | 30 +++++++++++++++--------------- apps/toolbox.fr.i18n | 24 ++++++++++++------------ apps/toolbox.it.i18n | 32 ++++++++++++++++---------------- apps/toolbox.nl.i18n | 20 ++++++++++---------- apps/toolbox.pt.i18n | 30 +++++++++++++++--------------- 6 files changed, 81 insertions(+), 81 deletions(-) diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index bba947d96da..5be05ac9c74 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -22,9 +22,9 @@ UnitDistanceMeterPico = "Pikometer" UnitDistanceAstronomicalUnit = "Astronomische Einheit" UnitDistanceLightYear = "Lichtjahr" UnitDistanceParsec = "Parsec" -UnitDistanceMile = "Mile" +UnitDistanceMile = "Meile" UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" +UnitDistanceFoot = "Fuß" UnitDistanceInch = "Inch" UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramm" @@ -33,10 +33,10 @@ UnitMassGramMilli = "Milligramm" UnitMassGramMicro = "Mikrogramm" UnitMassGramNano = "Nanogramm" UnitMassTonne = "Tonne" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" -UnitMassShortTon = "Short Ton" -UnitMassLongTon = "Long Ton" +UnitMassOunce = "Unze" +UnitMassPound = "Pfund" +UnitMassShortTon = "Amerikanische Tonne" +UnitMassLongTon = "Britische Tonne" UnitCurrentMenu = "Elektrischer Strom" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -102,21 +102,21 @@ UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Elektrische Induktivität" UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Fläche" -UnitSurfaceAcre = "Acre" +UnitSurfaceAcre = "Morgen" UnitSurfaceHectar = "Hektar" UnitVolumeMenu = "Volumen" UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deziliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" +UnitVolumeTeaspoon = "Teelöffel" +UnitVolumeTablespoon= "Esslöffel" +UnitVolumeFluidOunce = "Flüssigunze" +UnitVolumeCup = "Tasse" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" -UnitImperialMenu = "Imperial" +UnitVolumeGallon = "Gallone" +UnitImperialMenu = "Empire" Toolbox = "Werkzeugkasten" AbsoluteValue = "Betragsfunktion" NthRoot = "n-te Wurzel" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 8e7b57a3ec1..f48b851364d 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -22,10 +22,10 @@ UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" -UnitDistanceMile = "Mile" -UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" -UnitDistanceInch = "Inch" +UnitDistanceMile = "Milla" +UnitDistanceYard = "Yardas" +UnitDistanceFoot = "Pie" +UnitDistanceInch = "Pulgada" UnitMassMenu = "Mass" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" @@ -33,10 +33,10 @@ UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" UnitMassTonne = "Tonne" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" -UnitMassShortTon = "Short Ton" -UnitMassLongTon = "Long Ton" +UnitMassOunce = "Onza" +UnitMassPound = "Libra" +UnitMassShortTon = "Tonelada corta" +UnitMassLongTon = "Tonelada larga" UnitCurrentMenu = "Electric current" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -109,13 +109,13 @@ UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deciliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" -UnitVolumePint = "Pint" -UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" +UnitVolumeTeaspoon = "Cucharadita" +UnitVolumeTablespoon= "Cucharada" +UnitVolumeFluidOunce = "Onza líquida" +UnitVolumeCup = "Taza" +UnitVolumePint = "Pinta" +UnitVolumeQuart = "Cuarto" +UnitVolumeGallon = "Galón" UnitImperialMenu = "Imperial" Toolbox = "Caja de herramientas" AbsoluteValue = "Valor absoluto" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index e1c891a60fd..df9c16da5bb 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -24,8 +24,8 @@ UnitDistanceLightYear = "Année-lumière" UnitDistanceParsec = "Parsec" UnitDistanceMile = "Mile" UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" -UnitDistanceInch = "Inch" +UnitDistanceFoot = "Pied" +UnitDistanceInch = "Pouce" UnitMassMenu = "Masse" UnitMassGramKilo = "Kilogramme" UnitMassGram = "Gramme" @@ -33,10 +33,10 @@ UnitMassGramMilli = "Milligramme" UnitMassGramMicro = "Microgramme" UnitMassGramNano = "Nanogramme" UnitMassTonne = "Tonne" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" -UnitMassShortTon = "Short Ton" -UnitMassLongTon = "Long Ton" +UnitMassOunce = "Once" +UnitMassPound = "Livre" +UnitMassShortTon = "Tonne courte" +UnitMassLongTon = "Tonne longue" UnitCurrentMenu = "Intensité du courant électrique" UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" @@ -109,14 +109,14 @@ UnitVolumeLiter = "Litre" UnitVolumeLiterDeci = "Décilitre" UnitVolumeLiterCenti = "Centilitre" UnitVolumeLiterMilli = "Millilitre" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" -UnitVolumePint = "Pint" +UnitVolumeTeaspoon = "Cuillère à café" +UnitVolumeTablespoon= "Cuillère à soupe" +UnitVolumeFluidOunce = "Once fluide" +UnitVolumeCup = "Tasse" +UnitVolumePint = "Pinte" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallon" -UnitImperialMenu = "Imperial" +UnitImperialMenu = "Impérial" Toolbox = "Boîte à outils" AbsoluteValue = "Valeur absolue" NthRoot = "Racine n-ième" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 1de9c59909d..2b6faead37b 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -22,10 +22,10 @@ UnitDistanceMeterPico = "Picometro" UnitDistanceAstronomicalUnit = "Unità astronomica" UnitDistanceLightYear = "Anno luce" UnitDistanceParsec = "Parsec" -UnitDistanceMile = "Mile" -UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" -UnitDistanceInch = "Inch" +UnitDistanceMile = "Milla" +UnitDistanceYard = "Iarda" +UnitDistanceFoot = "Piede" +UnitDistanceInch = "Pollice" UnitMassMenu = "Massa" UnitMassGramKilo = "Kilogrammo" UnitMassGram = "Grammo" @@ -33,10 +33,10 @@ UnitMassGramMilli = "Milligrammo" UnitMassGramMicro = "Microgrammo" UnitMassGramNano = "Nanogrammo" UnitMassTonne = "Tonnellata" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" -UnitMassShortTon = "Short Ton" -UnitMassLongTon = "Long Ton" +UnitMassOunce = "Oncia" +UnitMassPound = "Libbra" +UnitMassShortTon = "Tonnellata americana" +UnitMassLongTon = "Tonnellata inglese" UnitCurrentMenu = "Intensità di corrente elettrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" @@ -109,14 +109,14 @@ UnitVolumeLiter = "Litro" UnitVolumeLiterDeci = "Decilitro" UnitVolumeLiterCenti = "Centilitro" UnitVolumeLiterMilli = "Millilitro" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" -UnitVolumePint = "Pint" -UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" -UnitImperialMenu = "Imperial" +UnitVolumeTeaspoon = "Cucchiaino" +UnitVolumeTablespoon= "Cucchiaio" +UnitVolumeFluidOunce = "Oncia fluida" +UnitVolumeCup = "Tazza" +UnitVolumePint = "Pinta" +UnitVolumeQuart = "Quarto" +UnitVolumeGallon = "Gallone" +UnitImperialMenu = "Imperiale" Toolbox = "Toolbox" AbsoluteValue = "Valore assoluto" NthRoot = "Radice n-esima" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index a31ece72212..8ec226df12f 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -22,10 +22,10 @@ UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomische eenheid" UnitDistanceLightYear = "Lichtjaar" UnitDistanceParsec = "Parsec" -UnitDistanceMile = "Mile" +UnitDistanceMile = "Mijl" UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" -UnitDistanceInch = "Inch" +UnitDistanceFoot = "Voet" +UnitDistanceInch = "Duim" UnitMassMenu = "Massa" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" @@ -33,8 +33,8 @@ UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" UnitMassTonne = "Ton" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" +UnitMassOunce = "Ons" +UnitMassPound = "Pond" UnitMassShortTon = "Short Ton" UnitMassLongTon = "Long Ton" UnitCurrentMenu = "Elektrische stroom" @@ -109,14 +109,14 @@ UnitVolumeLiter = "Liter" UnitVolumeLiterDeci = "Deciliter" UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" +UnitVolumeTeaspoon = "Theelepel" +UnitVolumeTablespoon= "Eetlepel" +UnitVolumeFluidOunce = "Ounce" +UnitVolumeCup = "Kop" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallon" -UnitImperialMenu = "Imperial" +UnitImperialMenu = "Imperiaal" Toolbox = "Toolbox" AbsoluteValue = "Absolute waarde" NthRoot = "n-de-machtswortel" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index f99af1d5a54..304330c0335 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -22,10 +22,10 @@ UnitDistanceMeterPico = "Picómetro" UnitDistanceAstronomicalUnit = "Unidade astronómica" UnitDistanceLightYear = "Ano-luz" UnitDistanceParsec = "Parsec" -UnitDistanceMile = "Mile" -UnitDistanceYard = "Yard" -UnitDistanceFoot = "Foot" -UnitDistanceInch = "Inch" +UnitDistanceMile = "Milha" +UnitDistanceYard = "Jarda" +UnitDistanceFoot = "Pé" +UnitDistanceInch = "Polegada" UnitMassMenu = "Massa" UnitMassGramKilo = "Quilograma" UnitMassGram = "Grama" @@ -33,10 +33,10 @@ UnitMassGramMilli = "Miligrama" UnitMassGramMicro = "Micrograma" UnitMassGramNano = "Nanograma" UnitMassTonne = "Tonelada" -UnitMassOunce = "Ounce" -UnitMassPound = "Pound" -UnitMassShortTon = "Short Ton" -UnitMassLongTon = "Long Ton" +UnitMassOunce = "Onça" +UnitMassPound = "Libra" +UnitMassShortTon = "Tonelada americana" +UnitMassLongTon = "Tonelada inglesa" UnitCurrentMenu = "Intensidade da corrente elétrica" UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Miliampere" @@ -109,13 +109,13 @@ UnitVolumeLiter = "Litro" UnitVolumeLiterDeci = "Decilitro" UnitVolumeLiterCenti = "Centilitro" UnitVolumeLiterMilli = "Mililitro" -UnitVolumeTeaspoon = "Teaspoon" -UnitVolumeTablespoon= "Tablespoon" -UnitVolumeFluidOunce = "Fluid Ounce" -UnitVolumeCup = "Cup" -UnitVolumePint = "Pint" -UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" +UnitVolumeTeaspoon = "Colher de chá" +UnitVolumeTablespoon= "Colher de sopa" +UnitVolumeFluidOunce = "Onça fluída" +UnitVolumeCup = "Xícara de chá" +UnitVolumePint = "Quartilho" +UnitVolumeQuart = "Quarto" +UnitVolumeGallon = "Galão" UnitImperialMenu = "Imperial" Toolbox = "Caixa de ferramentas" AbsoluteValue = "Valor absoluto" From 1f3c4b66a06bfd83c97772c7f8a5a60c40897f78 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 27 Jul 2020 13:38:53 +0200 Subject: [PATCH 154/560] [poincare/unit] Changed imperial units definitions Imperial units are now defined directly from the base unit, instead of building on one another. Change-Id: Ieb020643983ef49092d2d3033a7cc2135c646015 --- poincare/include/poincare/unit.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 853e269a0f6..d16ad23c275 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -312,15 +312,15 @@ class Unit final : public Expression { Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("ft", "12*_in", + Representative("ft", "12*0.0254*_m", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("yd", "3*_ft", + Representative("yd", "3*12*0.0254*_m", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), - Representative("mi", "1760*_yd", + Representative("mi", "1760*3*12*0.0254*_m", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), @@ -345,15 +345,15 @@ class Unit final : public Expression { Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("lb", "16*_oz", + Representative("lb", "16*0.028349523125*_kg", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("shtn", "2000*_lb", + Representative("shtn", "2000*16*0.028349523125*_kg", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("lgtn", "2240*_lb", + Representative("lgtn", "2240*16*0.028349523125*_kg", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), @@ -462,7 +462,7 @@ class Unit final : public Expression { Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Metric), - Representative("acre", "43560*_ft^2", + Representative("acre", "4046.8564224*_m^2", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), @@ -472,11 +472,11 @@ class Unit final : public Expression { Representative::Prefixable::Yes, NegativePrefixes, Representative::OutputSystem::Metric), - Representative("tsp", "4.92892159375*_mL", + Representative("tsp", "0.00492892159375*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), - Representative("Tbsp", "3*_tsp", + Representative("Tbsp", "3*0.00492892159375*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), @@ -484,19 +484,19 @@ class Unit final : public Expression { Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("cp", "8*_floz", + Representative("cp", "8*0.0295735295625*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), - Representative("pt", "2*_cp", + Representative("pt", "16*0.0295735295625*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), - Representative("qt", "4*_cp", + Representative("qt", "32*0.0295735295625*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::None), - Representative("gal", "4*_qt", + Representative("gal", "128*0.0295735295625*_L", Representative::Prefixable::No, NoPrefix, Representative::OutputSystem::Imperial), From e48e82653615ee6c7a6954353d86934d0ac96cb8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 30 Jul 2020 16:14:10 +0200 Subject: [PATCH 155/560] [poincare/test] Updated tests on units Change-Id: I7c146174bcedcccc3ed31a298c9720aa2a5ba3a9 --- poincare/test/parsing.cpp | 6 ++++-- poincare/test/simplification.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index c40c05c9930..a0437eaf000 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -294,9 +294,11 @@ QUIZ_CASE(poincare_parsing_units) { 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 *); + /* ton is only prefixable by positive prefixes */ + bool isTon = strcmp("t", rep->rootSymbol()) == 0; + size_t numberOfPrefixes = ((isTon) ? sizeof(Unit::PositiveLongScalePrefixes) : sizeof(Unit::AllPrefixes))/sizeof(Unit::Prefix *); for (size_t i = 0; i < numberOfPrefixes; i++) { - const Unit::Prefix * pre = Unit::AllPrefixes[i]; + const Unit::Prefix * pre = (isTon) ? Unit::PositiveLongScalePrefixes[i] : Unit::AllPrefixes[i]; Unit::Builder(dim, 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"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 5a7bfd3d5e0..702fc6665e0 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -271,13 +271,13 @@ QUIZ_CASE(poincare_simplification_units) { * 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(), "Hz") == 0 || strcmp(rep->rootSymbol(), "S") == 0 || strcmp(rep->rootSymbol(), "ha") == 0 || strcmp(rep->rootSymbol(), "L") == 0) { + if (strcmp(rep->rootSymbol(), "Hz") == 0 || strcmp(rep->rootSymbol(), "S") == 0 || strcmp(rep->rootSymbol(), "ha") == 0 || strcmp(rep->rootSymbol(), "L") == 0 || strcmp(rep->rootSymbol(), "yd") || strcmp(rep->rootSymbol(), "tsp") || strcmp(rep->rootSymbol(), "Tbsp") || strcmp(rep->rootSymbol(), "pt") || strcmp(rep->rootSymbol(), "qt") || strcmp(rep->rootSymbol(), "lgtn")) { 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); + assert_parsed_expression_simplify_to(buffer, buffer, User, Radian, (rep->canOutputInSystem(Metric) ? Metric : Imperial)); if (rep->isPrefixable()) { for (size_t i = 0; i < rep->outputPrefixesLength(); i++) { const Unit::Prefix * pre = rep->outputPrefixes()[i]; From 52dcd8e749f8fd22aa424b9e9153d9a0561372a4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 31 Jul 2020 16:00:07 +0200 Subject: [PATCH 156/560] [poincare/unit] Restructure Unit 1. Information about a unit's dimension now uses inheritance. _m is an instance of DistanceAlias, which is derived from Alias. A UnitNode now keeps a pointer to an Alias and one to a Prefix. All aliases are still defined as constexpr. This cleans up a lot of the code used namely for computing the additional outputs in Calculation. 2. Instead of being defined with a string, each unit is described by its ratio with the base SI unit (ex: _L is 0.001 instead of "0.001_m^3"). This greatly speeds up the calculations using units, as the algorithm to find the best unit used to parse the definition. Change-Id: I4d6ed6ad4cb967026a3f01a335aec270066e2b9f --- .../unit_list_controller.cpp | 71 +- apps/calculation/calculation.cpp | 31 +- poincare/include/poincare/unit.h | 1375 +++++++---------- poincare/src/multiplication.cpp | 29 +- poincare/src/parsing/parser.cpp | 10 +- poincare/src/unit.cpp | 1093 +++++++------ poincare/test/expression.cpp | 10 +- poincare/test/expression_properties.cpp | 97 +- poincare/test/parsing.cpp | 19 +- poincare/test/simplification.cpp | 101 +- 10 files changed, 1341 insertions(+), 1495 deletions(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 2ae84d91721..af0cc851f06 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -12,27 +12,6 @@ using namespace Shared; namespace Calculation { -void storeAndSimplify(Expression e, Expression * dest, bool requireSimplification, bool canChangeUnitPrefix, ExpressionNode::ReductionContext reductionContext) { - assert(!e.isUninitialized()); - *dest = e; - if (requireSimplification) { - *dest = dest->simplify(reductionContext); - } - if (canChangeUnitPrefix) { - Expression newUnits; - /* If the expression has already been simplified, we do not want to reduce - * it further, as this would units introduced by a UnitConvert node back to - * SI units. */ - if (!requireSimplification) { - // Reduce to be able to removeUnit - *dest = dest->reduce(reductionContext); - } - *dest = dest->removeUnit(&newUnits); - double value = Shared::PoincareHelpers::ApproximateToScalar(*dest, App::app()->localContext()); - *dest = Multiplication::Builder(Number::FloatNumber(value), newUnits); - } -} - void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); @@ -44,7 +23,6 @@ void UnitListController::setExpression(Poincare::Expression e) { expressions[i] = Expression(); } - size_t numberOfExpressions = 0; /* 1. First rows: miscellaneous classic units for some dimensions, in both * metric and imperial units. */ Expression copy = m_expression.clone(); @@ -52,52 +30,15 @@ void UnitListController::setExpression(Poincare::Expression e) { // Reduce to be able to recognize units PoincareHelpers::Reduce(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User); copy = copy.removeUnit(&units); - Preferences::UnitFormat chosenFormat = GlobalPreferences::sharedGlobalPreferences()->unitFormat(); - Preferences::UnitFormat otherFormat = (chosenFormat == Preferences::UnitFormat::Metric) ? Preferences::UnitFormat::Imperial : Preferences::UnitFormat::Metric; - ExpressionNode::ReductionContext chosenFormatContext( - App::app()->localContext(), - Preferences::sharedPreferences()->complexFormat(), - Preferences::sharedPreferences()->angleUnit(), - chosenFormat, - ExpressionNode::ReductionTarget::User, - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - ExpressionNode::ReductionContext otherFormatContext( + double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); + ExpressionNode::ReductionContext reductionContext( App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit(), - otherFormat, + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - - if (Unit::IsSISpeed(units)) { - // 1.a. Turn speed into km/h or mi/h - storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext); - storeAndSimplify(Unit::StandardSpeedConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext); - } else if (Unit::IsSIVolume(units)) { - // 1.b. Turn volume into L or _gal + _cp + _floz - storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); - storeAndSimplify(Unit::StandardVolumeConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); - } else if (Unit::IsSIEnergy(units)) { - // 1.c. Turn energy into Wh - storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Multiplication::Builder(Unit::Watt(), Unit::Hour())), &expressions[numberOfExpressions++], true, true, chosenFormatContext); - storeAndSimplify(UnitConvert::Builder(m_expression.clone(), Unit::ElectronVolt()), &expressions[numberOfExpressions++], true, true, chosenFormatContext); - } else if (Unit::IsSITime(units)) { - // 1.d. Turn time into ? year + ? month + ? day + ? h + ? min + ? s - double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); - expressions[numberOfExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext()); - } else if (Unit::IsSIDistance(units)) { - // 1.e. Turn distance into _?m or _mi + _yd + _ft + _in - storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); - storeAndSimplify(Unit::StandardDistanceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); - } else if (Unit::IsSISurface(units)) { - // 1.f. Turn surface into hectares or acres - storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], true, false, chosenFormatContext); - storeAndSimplify(Unit::StandardSurfaceConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], true, false, otherFormatContext); - } else if (Unit::IsSIMass(units)) { - // 1.g. Turn mass into _?g and _lb + _oz - storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), chosenFormatContext), &expressions[numberOfExpressions++], chosenFormat == Preferences::UnitFormat::Metric, chosenFormat == Preferences::UnitFormat::Metric, chosenFormatContext); - storeAndSimplify(Unit::StandardMassConversion(m_expression.clone(), otherFormatContext), &expressions[numberOfExpressions++], otherFormat == Preferences::UnitFormat::Metric, otherFormat == Preferences::UnitFormat::Metric, otherFormatContext); - } + int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext); // 2. SI units only assert(numberOfExpressions < k_maxNumberOfRows - 1); @@ -112,14 +53,14 @@ void UnitListController::setExpression(Poincare::Expression e) { int currentExpressionIndex = 1; while (currentExpressionIndex < numberOfExpressions) { bool duplicateFound = false; - for (size_t i = 0; i < currentExpressionIndex + 1; i++) { + 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(expressions[currentExpressionIndex])) { numberOfExpressions--; // Shift next expressions - for (size_t j = currentExpressionIndex; j < numberOfExpressions; j++) { + for (int j = currentExpressionIndex; j < numberOfExpressions; j++) { expressions[j] = expressions[j+1]; } // Remove last expression diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index f0c62dddb61..682a27d668d 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -254,32 +254,13 @@ 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); + /* FIXME : When this method is accessed via leaving the additional outputs, + * ie via a press on BACK, the reduction is interrupted, and removeUnit + * goes badly.*/ + PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); 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; - } + double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); + return (Unit::ShouldDisplayAdditionalOutputs(value, unit)) ? AdditionalInformationType::Unit : AdditionalInformationType::None; } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index d16ad23c275..9802de816c1 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,115 +28,401 @@ class UnitNode final : public ExpressionNode { * and scopes. */ - // There are 7 base units from which all other units are derived. - static constexpr size_t NumberOfBaseUnits = 7; +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; } + 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, - PositiveOnly - }; - enum class OutputSystem { None, - Imperial, - Metric, - All + PositiveLongScale, + NegativeLongScale, + Positive, + Negative, + NegativeAndKilo, + LongScale, + All, }; - template - constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix * const (&outputPrefixes)[N], const OutputSystem outputSystem = OutputSystem::All) : + 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_outputSystem(outputSystem) - { - } + 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); } + virtual bool hasAdditionalExpressions(double value) const { return true; } + 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::No; } - 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; } + bool canPrefix(const Prefix * prefix, bool input) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; - const Prefix * bestPrefixForValue(double & value, const float exponent) const; - bool canOutputInSystem(Preferences::UnitFormat system) 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; + 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; - const OutputSystem m_outputSystem; + /* 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 { - // 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 < 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; - }; - 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 hasAdditionalExpressions(double value) const override { return m_ratio * value >= representativesOfSameDimension()[1].ratio(); } + int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const override; + private: + using Representative::Representative; + }; + + 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; + 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; + 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(); } + bool hasAdditionalExpressions(double value) const override { return false; } + private: + using Representative::Representative; + }; + + class TemperatureRepresentative : public Representative { + friend class Unit; + public: + 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 1; } + const Representative * representativesOfSameDimension() const override; + bool isBaseUnit() const override { return this == representativesOfSameDimension(); } + bool hasAdditionalExpressions(double value) const override { return false; } + private: + 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(); } + bool hasAdditionalExpressions(double value) const override { return false; } + 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(); } + bool hasAdditionalExpressions(double value) const override { return false; } + 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; + bool hasAdditionalExpressions(double value) const override { return false; } + 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; + 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: - 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 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; + 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; + 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; } + 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) {} @@ -176,758 +461,208 @@ class UnitNode final : public ExpressionNode { 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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) 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; + 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), + }; 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::OutputSystem::Metric), - Representative("au", "149597870700*_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("in", "0.0254*_m", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("ft", "12*0.0254*_m", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("yd", "3*12*0.0254*_m", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - Representative("mi", "1760*3*12*0.0254*_m", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - }, - MassRepresentatives[] = { - Representative("kg", nullptr, - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Metric), - Representative("g", "0.001_kg", - Representative::Prefixable::Yes, - NegativeLongScalePrefixes, - Representative::OutputSystem::Metric), - Representative("t", "1000_kg", - Representative::Prefixable::PositiveOnly, - NoPrefix, - Representative::OutputSystem::Metric), - Representative("Da", "(6.02214076*10^23*1000)^-1*_kg", - Representative::Prefixable::Yes, - NoPrefix), - Representative("oz", "0.028349523125*_kg", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("lb", "16*0.028349523125*_kg", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("shtn", "2000*16*0.028349523125*_kg", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("lgtn", "2240*16*0.028349523125*_kg", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - }, - 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), - }, - 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, - LongScalePrefixes), - }, - CatalyticActivityRepresentatives[] = { - Representative("kat", "_mol*_s^-1", - Representative::Prefixable::Yes, - NoPrefix), - }, - SurfaceRepresentatives[] = { - Representative("ha", "10^4*_m^2", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Metric), - Representative("acre", "4046.8564224*_m^2", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - }, - VolumeRepresentatives[] = { - Representative("L", "10^-3*_m^3", - Representative::Prefixable::Yes, - NegativePrefixes, - Representative::OutputSystem::Metric), - Representative("tsp", "0.00492892159375*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - Representative("Tbsp", "3*0.00492892159375*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - Representative("floz", "0.0295735295625*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("cp", "8*0.0295735295625*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - Representative("pt", "16*0.0295735295625*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - Representative("qt", "32*0.0295735295625*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::None), - Representative("gal", "128*0.0295735295625*_L", - Representative::Prefixable::No, - NoPrefix, - Representative::OutputSystem::Imperial), - }; - // 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_assert(sizeof(DistanceRepresentatives)/sizeof(Representative) == 8, "The Unit::MileRepresentative et al. might require to be fixed if the DistanceRepresentatives table was changed."); - static const Representative constexpr * InchRepresentative = &DistanceRepresentatives[4]; - static const Representative constexpr * FootRepresentative = &DistanceRepresentatives[5]; - static const Representative constexpr * YardRepresentative = &DistanceRepresentatives[6]; - static const Representative constexpr * MileRepresentative = &DistanceRepresentatives[7]; - static const Representative constexpr * KilogramRepresentative = &MassRepresentatives[0]; - static const Representative constexpr * GramRepresentative = &MassRepresentatives[1]; - static_assert(sizeof(MassRepresentatives)/sizeof(Representative) == 8, "The Unit::OunceRepresentative et al. might require to be fixed if the MassRepresentatives table was changed."); - static const Representative constexpr * OunceRepresentative = &MassRepresentatives[4]; - static const Representative constexpr * PoundRepresentative = &MassRepresentatives[5]; - static const Representative constexpr * ShortTonRepresentative = &MassRepresentatives[6]; - static const Representative constexpr * LiterRepresentative = &VolumeRepresentatives[0]; - static_assert(sizeof(VolumeRepresentatives)/sizeof(Representative) == 8, "The Unit::FluidOunceRepresentative et al. might require to be fixed if the VolumeRepresentatives table was changed."); - static const Representative constexpr * FluidOunceRepresentative = &VolumeRepresentatives[3]; - static const Representative constexpr * CupRepresentative = &VolumeRepresentatives[4]; - static const Representative constexpr * GallonRepresentative = &VolumeRepresentatives[7]; - 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_assert(sizeof(SurfaceRepresentatives)/sizeof(Representative) == 2, "The Unit::HectareRepresentative et al. might require to be fixed if the VolumeRepresentatives table was changed."); - static const Representative constexpr * HectareRepresentative = &SurfaceRepresentatives[0]; - static const Representative constexpr * AcreRepresentative = &SurfaceRepresentatives[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, - }, - TimeRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 1, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - DistanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - MassRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - CurrentRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 1, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - TemperatureRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 1, - .luminuousIntensity = 0, - }, - AmountOfSubstanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 1, - }, - LuminousIntensityRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-1, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - FrequencyRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 1, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ForceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance =-1, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - PressureRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - EnergyRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - PowerRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 1, - .distance = 0, - .mass = 0, - .current = 1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ElectricChargeRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ElectricPotentialRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 4, - .distance =-2, - .mass =-1, - .current = 2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ElectricCapacitanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-3, - .distance = 2, - .mass = 1, - .current =-2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ElectricResistanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 3, - .distance =-2, - .mass =-1, - .current = 2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - ElectricConductanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - MagneticFluxRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 0, - .mass = 1, - .current =-1, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - MagneticFieldRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-2, - .distance = 2, - .mass = 1, - .current =-2, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - InductanceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time =-1, - .distance = 0, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 1, - .luminuousIntensity = 0, - }, - CatalyticActivityRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 2, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - SurfaceRepresentatives, - &EmptyPrefix - ), - Dimension( - Dimension::Vector { - .time = 0, - .distance = 3, - .mass = 0, - .current = 0, - .temperature = 0, - .amountOfSubstance = 0, - .luminuousIntensity = 0, - }, - VolumeRepresentatives, - &EmptyPrefix - ), - }; - // TODO: find a better way to find defines these pointers - static_assert(sizeof(DimensionTable)/sizeof(Dimension) == 23, "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[10]; - static const Dimension constexpr * PowerDimension = &DimensionTable[11]; - static const Dimension constexpr * SurfaceDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-2]; - static const Dimension constexpr * VolumeDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-1]; - - 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); + 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::LongScale) }; + 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("cp", 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. */ + + /* 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_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_fluidOunceRepresentativeIndex = 3; + static_assert(strings_equal(k_volumeRepresentatives[k_fluidOunceRepresentativeIndex].m_rootSymbol, "floz"), "Index for the Fluid Ounce Representative is incorrect."); + static constexpr int k_cupRepresentativeIndex = 4; + static_assert(strings_equal(k_volumeRepresentatives[k_cupRepresentativeIndex].m_rootSymbol, "cp"), "Index for the Cup 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 Meter() { return Builder(DistanceDimension, MeterRepresentative, &EmptyPrefix); } - static Unit Kilometer() { return Builder(DistanceDimension, MeterRepresentative, &KiloPrefix); } - static Unit Inch() { return Builder(DistanceDimension, InchRepresentative, &EmptyPrefix); } - static Unit Foot() { return Builder(DistanceDimension, FootRepresentative, &EmptyPrefix); } - static Unit Yard() { return Builder(DistanceDimension, YardRepresentative, &EmptyPrefix); } - static Unit Mile() { return Builder(DistanceDimension, MileRepresentative, &EmptyPrefix); } - 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 Unit Gram() { return Builder(MassDimension, GramRepresentative, &EmptyPrefix); } - static Unit Ounce() { return Builder(MassDimension, OunceRepresentative, &EmptyPrefix); } - static Unit Pound() { return Builder(MassDimension, PoundRepresentative, &EmptyPrefix); } - static Unit ShortTon() { return Builder(MassDimension, ShortTonRepresentative, &EmptyPrefix); } - static Unit FluidOunce() { return Builder(VolumeDimension, FluidOunceRepresentative, &EmptyPrefix); } - static Unit Cup() { return Builder(VolumeDimension, CupRepresentative, &EmptyPrefix); } - static Unit Gallon() { return Builder(VolumeDimension, GallonRepresentative, &EmptyPrefix); } - static Unit Hectare() { return Builder(SurfaceDimension, HectareRepresentative, &EmptyPrefix); } - static Unit Acre() { return Builder(SurfaceDimension, AcreRepresentative, &EmptyPrefix); } - static Expression BuildSplit(double baseValue, const Unit * units, const double * conversionFactors, int numberOfUnits, Context * context); - static Expression BuildTimeSplit(double seconds, Context * context); - static Expression BuildImperialDistanceSplit(double inches, Context * context); - static Expression BuildImperialMassSplit(double ounces, Context * context); - static Expression BuildImperialVolumeSplit(double fluidOunces, Context * context); - static double ConvertedValueInUnit(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext); - - 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); - static bool IsSIDistance(Expression & e); - static bool IsSIMass(Expression & e); - static bool IsSISurface(Expression & e); - bool isMeter() const; - bool isSecond() const; - bool isKilogram() const; - - static Expression StandardSpeedConversion(Expression e, ExpressionNode::ReductionContext reductionContext); - static Expression StandardDistanceConversion(Expression e, ExpressionNode::ReductionContext reductionContext); - static Expression StandardVolumeConversion(Expression e, ExpressionNode::ReductionContext reductionContext); - static Expression StandardMassConversion(Expression e, ExpressionNode::ReductionContext reductionContext); - static Expression StandardSurfaceConversion(Expression e, ExpressionNode::ReductionContext reductionContext); + 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); + 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); // 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); } - // This could be computed from the time representatives but we save time by using constexpr double - static constexpr double SecondsPerMinute = 60.0; + bool isBaseUnit() const { return node()->representative()->isBaseUnit() && node()->prefix() == node()->representative()->basePrefix(); } + void chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix); + 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; - static constexpr double InchesPerFoot = 12.; - static constexpr double FeetPerYard = 3.; - static constexpr double YardsPerMile = 1760.; - static constexpr double OuncesPerPound = 16.; - static constexpr double PoundsPerShortTon = 2000.; - static constexpr double FluidOuncesPerCup = 8.; - static constexpr double CupsPerGallon = 16.; 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 float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); Expression removeUnit(Expression * unit); }; diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index bf518c2ce9c..aadd50c0c86 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -322,14 +322,14 @@ Expression Multiplication::shallowReduce(ExpressionNode::ReductionContext reduct } static bool CanSimplifyUnitProduct( - const Unit::Dimension::Vector &unitsExponents, size_t &unitsSupportSize, - const Unit::Dimension::Vector *entryUnitExponents, int8_t entryUnitExponent, - int8_t &bestUnitExponent, Unit::Dimension::Vector &bestRemainderExponents, size_t &bestRemainderSupportSize) { + 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' 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; + UnitNode::Vector simplifiedExponents; #if 0 /* In the current algorithm, simplification is attempted using derived units @@ -369,7 +369,7 @@ static bool CanSimplifyUnitProduct( n -= step; #endif - for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { + 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)); @@ -444,26 +444,27 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * representation of units with base units and integer exponents. * It cause no problem because once the best derived units are found, * units is divided then multiplied by them. */ - Unit::Dimension::Vector unitsExponents = Unit::Dimension::Vector::FromBaseUnits(units); + UnitNode::Vector unitsExponents = UnitNode::Vector::FromBaseUnits(units); size_t unitsSupportSize = unitsExponents.supportSize(); - Unit::Dimension::Vector bestRemainderExponents; + UnitNode::Vector bestRemainderExponents; size_t bestRemainderSupportSize; while (unitsSupportSize > 1) { - const Unit::Dimension * bestDim = nullptr; + const UnitNode::Representative * bestDim = nullptr; int8_t bestUnitExponent = 0; // Look up in the table of derived units. - for (const Unit::Dimension * dim = Unit::DimensionTable + Unit::NumberOfBaseUnits; dim < Unit::DimensionTableUpperBound; dim++) { - const Unit::Dimension::Vector * entryUnitExponents = dim->vector(); + 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, unitsSupportSize, - entryUnitExponents, 1, + &entryUnitExponents, 1, bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize ) || CanSimplifyUnitProduct( unitsExponents, unitsSupportSize, - entryUnitExponents, -1, + &entryUnitExponents, -1, bestUnitExponent, bestRemainderExponents, bestRemainderSupportSize )) { @@ -477,7 +478,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu break; } // Build and add the best derived unit - Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix()); + Expression derivedUnit = Unit::Builder(bestDim->representativesOfSameDimension(), bestDim->basePrefix()); #if 0 if (bestUnitExponent != 1) { @@ -533,7 +534,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu } else { if (unitConversionMode == ExpressionNode::UnitConversion::Default) { // Find the right unit prefix - Unit::ChooseBestRepresentativeAndPrefixForValue(&units, &value, reductionContext); + Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, reductionContext); } // Build final Expression result = Multiplication::Builder(Number::FloatNumber(value), units); diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 782e41757a5..d20b3960b72 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -347,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; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 12fc5b77fd2..4312288e8ce 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -1,120 +1,83 @@ #include #include -#include #include -#include -#include #include #include #include #include -#include #include #include -#include -#include #include -#include #include namespace Poincare { -int UnitNode::Prefix::serialize(char * buffer, int bufferSize) const { - assert(bufferSize >= 0); - return std::min(strlcpy(buffer, m_symbol, bufferSize), bufferSize - 1); -} - -bool UnitNode::Representative::canParse(const char * symbol, size_t length, - const Prefix * * prefix) const -{ - if (!isPrefixable()) { - *prefix = &Unit::EmptyPrefix; - return length == 0; - } - const Prefix * const * prefixesList = (m_prefixable == Prefixable::PositiveOnly) ? Unit::PositiveLongScalePrefixes : Unit::AllPrefixes; - size_t numberOfPrefixes = ((m_prefixable == Prefixable::PositiveOnly) ? sizeof(Unit::PositiveLongScalePrefixes) : sizeof(Unit::AllPrefixes))/sizeof(Unit::Prefix *); - for (size_t i = 0; i < numberOfPrefixes; i++) { - const Prefix * pre = prefixesList[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; -} - -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 && 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)); -} - -const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const float exponent) const { - if (!isPrefixable()) { - return &Unit::EmptyPrefix; - } - float bestOrder; - const Prefix * bestPre = nullptr; - /* Find the 'Prefix' with the most adequate 'exponent' for the order of - * magnitude of 'value'. - */ - const float orderOfMagnitude = std::log10(std::fabs(value)); - for (size_t i = 0; i < m_outputPrefixesLength; i++) { - const Prefix * pre = m_outputPrefixes[i]; - float order = orderOfMagnitude - pre->exponent() * exponent; - if (bestPre == nullptr || compareMagnitudeOrders(order, bestOrder)) { - bestOrder = order; - bestPre = pre; - } - } - value *= std::pow(10.0, -bestPre->exponent() * exponent); - return bestPre; -} - -bool UnitNode::Representative::canOutputInSystem(Preferences::UnitFormat system) const { - if (m_outputSystem == OutputSystem::None) { - return false; - } - if (m_outputSystem == OutputSystem::All) { - return true; - } - return (system == Preferences::UnitFormat::Metric) == (m_outputSystem == OutputSystem::Metric); -} - +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[]; + +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_electronVoltRepresentativeIndex, + Unit::k_wattRepresentativeIndex, + Unit::k_hectareRepresentativeIndex, + Unit::k_acreRepresentativeIndex, + Unit::k_literRepresentativeIndex, + Unit::k_fluidOunceRepresentativeIndex, + Unit::k_cupRepresentativeIndex, + Unit::k_gallonRepresentativeIndex; + +// UnitNode::Prefix +const UnitNode::Prefix * UnitNode::Prefix::Prefixes() { + return Unit::k_prefixes; +} + +const UnitNode::Prefix * UnitNode::Prefix::EmptyPrefix() { + return Prefixes() + Unit::k_emptyPrefixIndex; +} + +// UnitNode::Vector template<> -size_t UnitNode::Dimension::Vector::supportSize() const { +size_t UnitNode::Vector::supportSize() const { size_t supportSize = 0; - for (const int * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { - int coefficient = *i; - if (coefficient == 0) { + for (int i = 0; i < k_numberOfBaseUnits; i++) { + if (coefficientAtIndex(i) == 0) { continue; } supportSize++; @@ -123,7 +86,14 @@ size_t UnitNode::Dimension::Vector::supportSize() const { } template<> -Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { +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); + } +} + +template<> +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 = { @@ -169,9 +139,7 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(con } // 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; } @@ -180,477 +148,640 @@ Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(con 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(); - 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; - return true; +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; } - rep++; + 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++; } - return false; + assert(numberOfChildren > 0); + result = static_cast(result).squashUnaryHierarchyInPlace(); + return result; } -ExpressionNode::Sign UnitNode::sign(Context * context) const { - return Sign::Positive; +// 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; } -Expression UnitNode::removeUnit(Expression * unit) { - return Unit(this).removeUnit(unit); +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; } -int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { - if (!ascending) { - return e->simplificationOrderSameType(this, true, canBeInterrupted, ignoreParentheses); +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; } - 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 representatives 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; + if (std::fabs(otherOrder) < Expression::Epsilon()) { + otherOrder = 0.0f; } - 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 µ - * which would change the required size?*/ - static constexpr size_t bufferSize = 10; - char buffer[bufferSize]; - int length = serialize(buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits); - assert(length < bufferSize); - return LayoutHelper::String(buffer, length); + 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::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { +int UnitNode::Prefix::serialize(char * buffer, int bufferSize) const { assert(bufferSize >= 0); - int underscoreLength = std::min(strlcpy(buffer, "_", bufferSize), bufferSize - 1); - buffer += underscoreLength; - bufferSize -= underscoreLength; - return underscoreLength + m_representative->serialize(buffer, bufferSize, m_prefix); + return std::min(strlcpy(buffer, m_symbol, bufferSize), bufferSize - 1); } -template -Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Complex::Undefined(); +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; } -Expression UnitNode::shallowReduce(ReductionContext reductionContext) { - return Unit(this).shallowReduce(reductionContext); +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; } -Expression UnitNode::shallowBeautify(ReductionContext reductionContext) { - return Unit(this).shallowBeautify(reductionContext); +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 + && candidate[i].canParse(symbol, potentialPrefixLength, prefix)) { + *representative = (candidate + i); + return true; + } + } + return false; } -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::MassRepresentatives[], - Unit::CurrentRepresentatives[], - Unit::TemperatureRepresentatives[], - Unit::AmountOfSubstanceRepresentatives[], - Unit::LuminousIntensityRepresentatives[], - Unit::FrequencyRepresentatives[], - 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; - -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; +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 (prefixSymbol[length] == 0 + && canPrefix(pre, true) + && strncmp(symbol, prefixSymbol, length) == 0) + { + *prefix = pre; 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); +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); } -Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.unitConversion() == ExpressionNode::UnitConversion::None) { - return *this; +bool UnitNode::Representative::canPrefix(const UnitNode::Prefix * prefix, bool input) const { + Prefixable prefixable = (input) ? m_inputPrefixable : m_outputPrefixable; + if (prefix->exponent() == 0) { + return true; } - 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(); + if (prefixable == Prefixable::None) { + return false; } - Expression result; - if (rep->definition() == nullptr) { - result = clone(); - } else { - result = Expression::Parse(rep->definition(), nullptr, false).deepReduce(reductionContext); + if (prefixable == Prefixable::All) { + return true; } - if (prefixMultiplier != 0) { - Expression multiplier = Power::Builder(Rational::Builder(10), Rational::Builder(prefixMultiplier)).shallowReduce(reductionContext); - result = Multiplication::Builder(multiplier, result).shallowReduce(reductionContext); + if (prefixable == Prefixable::LongScale) { + return prefix->exponent() % 3 == 0; } - replaceWithInPlace(result); - return result; -} - -Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { - // 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)); - replaceWithInPlace(m); - m.addChildAtIndexInPlace(*this, 1, 1); - return std::move(m); + if (prefixable == Prefixable::NegativeAndKilo) { + return prefix->exponent() < 0 || prefix->exponent() == 3; } - 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; - float exponent = 1.0f; - if (firstFactor.type() == ExpressionNode::Type::Multiplication) { - firstFactor = firstFactor.childAtIndex(0); + 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 (firstFactor.type() == ExpressionNode::Type::Power) { - Expression exp = firstFactor.childAtIndex(1); - firstFactor = firstFactor.childAtIndex(0); - assert(exp.type() == ExpressionNode::Type::Rational); - exponent = static_cast(exp).node()->templatedApproximate(); + if (prefixable == Prefixable::Negative) { + return prefix->exponent() < 0; } - assert(firstFactor.type() == ExpressionNode::Type::Unit); - // Choose its multiple and update value accordingly - if (exponent != 0.0f) { - static_cast(firstFactor).chooseBestMultipleForValue(value, exponent, tuneRepresentative, reductionContext); + if (prefixable == Prefixable::Positive) { + return prefix->exponent() > 0; } + assert(false); + return false; } -void Unit::chooseBestMultipleForValue(double * value, const float exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { - assert(!std::isnan(*value) && exponent != 0.0f); - if (*value == 0.0 || std::isinf(*value)) { - return; +const UnitNode::Prefix * UnitNode::Representative::findBestPrefix(double value, double exponent) const { + if (!isOutputPrefixable()) { + return Prefix::EmptyPrefix(); } - 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 = (tuneRepresentative) ? DBL_MAX : *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++) { - if (!rep->canOutputInSystem(reductionContext.unitFormat())) { + 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; } - // 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 (compareMagnitudeOrders(std::log10(std::fabs(val)), std::log10(std::fabs(bestVal)))) { - /* At this point, val is closer to one than bestVal is.*/ - bestRep = rep; - bestPre = pre; - bestVal = val; + float order = magnitude - (Prefix::Prefixes()[i].exponent() - basePrefix()->exponent()) * exponent; + if (compareMagnitudeOrders(order, bestOrder)) { + bestOrder = order; + res = Prefix::Prefixes() + i; } } - unitNode->setRepresentative(bestRep); - unitNode->setPrefix(bestPre); - *value = bestVal; + 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; } -Expression Unit::removeUnit(Expression * unit) { - *unit = *this; - Expression one = Rational::Builder(1); - replaceWithInPlace(one); - return one; +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); } -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; +int UnitNode::DistanceRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 1); + 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, 4, reductionContext); + return 1; } -bool Unit::isMeter() const { - // See comment on isSecond - return node()->dimension() == DistanceDimension && node()->representative() == MeterRepresentative && node()->prefix() == &EmptyPrefix; +const UnitNode::Prefix * UnitNode::MassRepresentative::basePrefix() const { + return isBaseUnit() ? Prefix::Prefixes() + Unit::k_kiloPrefixIndex : Prefix::EmptyPrefix(); } - -bool Unit::isKilogram() const { - // See comment on isSecond - return node()->dimension() == MassDimension && node()->representative() == KilogramRepresentative && node()->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); } -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 (e.type() == ExpressionNode::Type::Power) { - // Rational exponents are accepted in IS system - assert(e.childAtIndex(1).type() == ExpressionNode::Type::Rational); - Expression child = e.childAtIndex(0); - assert(child.type() == ExpressionNode::Type::Unit); - return IsSI(child); - } - assert(e.type() == ExpressionNode::Type::Unit); - return static_cast(e).isSI(); +int UnitNode::MassRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 1); + 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, 3, reductionContext); + return 1; +} + +int UnitNode::EnergyRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + /* 1. 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[0] = Multiplication::Builder( + Float::Builder(adjustedValue * std::pow(10., -wattPrefix->exponent())), + Multiplication::Builder( + Unit::Builder(watt, wattPrefix), + Unit::Builder(hour, Prefix::EmptyPrefix()))); + /* 2. Convert into eV */ + const Representative * eV = representativesOfSameDimension() + Unit::k_electronVoltRepresentativeIndex; + adjustedValue = value / eV->ratio(); + const Prefix * eVPrefix = eV->findBestPrefix(adjustedValue, 1.); + dest[1] = Multiplication::Builder( + Float::Builder(adjustedValue * std::pow(10., -eVPrefix->exponent())), + Unit::Builder(eV, eVPrefix)); + return 2; +} + +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); + int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; + Expression * destMetric = dest + k; + Expression * destImperial = dest + (1 - k); + // 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 + 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); + int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; + Expression * destMetric = dest + k; + Expression * destImperial = dest + (1 - k); + // 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 + const Unit splitUnits[] = { + Unit::Builder(representativesOfSameDimension() + Unit::k_fluidOunceRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_cupRepresentativeIndex, Prefix::EmptyPrefix()), + Unit::Builder(representativesOfSameDimension() + Unit::k_gallonRepresentativeIndex, Prefix::EmptyPrefix()), + }; + *destImperial = Unit::BuildSplit(value, splitUnits, numberOfRepresentatives() - 5, reductionContext); + return 2; +} + +int UnitNode::SpeedRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { + assert(availableLength >= 2); + int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; + Expression * destMetric = dest + k; + Expression * destImperial = dest + (1 - k); + // 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 + 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 +ExpressionNode::Sign UnitNode::sign(Context * context) const { + return Sign::Positive; } -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(); +Expression UnitNode::removeUnit(Expression * unit) { + return Unit(this).removeUnit(unit); } -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(); +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 µ + * which would change the required size?*/ + static constexpr size_t bufferSize = 10; + char buffer[bufferSize]; + int length = serialize(buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits); + assert(length < bufferSize); + return LayoutHelper::String(buffer, length); } -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(); +int UnitNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + assert(bufferSize >= 0); + int underscoreLength = std::min(strlcpy(buffer, "_", bufferSize), bufferSize - 1); + buffer += underscoreLength; + bufferSize -= underscoreLength; + return underscoreLength + m_representative->serialize(buffer, bufferSize, m_prefix); } -bool Unit::IsSITime(Expression & e) { - return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); +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; } -bool Unit::IsSIDistance(Expression & e) { - return e.type() == ExpressionNode::Type::Unit && static_cast(e).isMeter(); +Expression UnitNode::shallowReduce(ReductionContext reductionContext) { + return Unit(this).shallowReduce(reductionContext); } -bool Unit::IsSIMass(Expression & e) { - return e.type() == ExpressionNode::Type::Unit && static_cast(e).isKilogram(); +Expression UnitNode::shallowBeautify(ReductionContext reductionContext) { + return Unit(this).shallowBeautify(reductionContext); } -bool Unit::IsSISurface(Expression & e) { - 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().isTwo(); +template +Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + return Complex::Undefined(); } -double Unit::ConvertedValueInUnit(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext) { - Expression conversion = UnitConvert::Builder(e.clone(), unit); - Expression newUnit; - conversion = conversion.simplify(reductionContext); - conversion = conversion.removeUnit(&newUnit); - return conversion.approximateToScalar(reductionContext.context(), Preferences::ComplexFormat::Real, Preferences::sharedPreferences()->angleUnit()); +// 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); } -Expression Unit::BuildSplit(double baseValue, Unit const * units, double const * conversionFactors, const int numberOfUnits, Context * context) { - assert(!std::isnan(baseValue)); - if (std::isinf(baseValue) || std::fabs(baseValue) < Expression::Epsilon()) { - return Multiplication::Builder(Number::FloatNumber(baseValue), units[numberOfUnits-1]); - } - - /* Round the base value 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 value was then computed from this approximation. - * We thus round it to avoid displaying small numbers that are - * artifacts of the previous approximations. */ - double err = std::pow(10.0, Poincare::PrintFloat::k_numberOfStoredSignificantDigits - 1 - std::ceil(log10(std::fabs(baseValue)))); - double remain = std::round(baseValue*err)/err; - - double valuesPerUnit[numberOfUnits]; - Addition a = Addition::Builder(); - for (int i = 0; i < numberOfUnits; i++) { - valuesPerUnit[i] = remain/conversionFactors[i]; - // Keep only the floor of the values except for the last unit (seconds) - if (i < numberOfUnits - 1) { - valuesPerUnit[i] = valuesPerUnit[i] >= 0.0 ? std::floor(valuesPerUnit[i]) : std::ceil(valuesPerUnit[i]); - } - remain -= valuesPerUnit[i] * conversionFactors[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()); +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; } } - - ExpressionNode::ReductionContext reductionContext(context, Preferences::ComplexFormat::Real, Preferences::AngleUnit::Degree, Preferences::UnitFormat::Imperial, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); - // Beautify the addition into an subtraction if necessary - return a.squashUnaryHierarchyInPlace().shallowBeautify(reductionContext); + return false; } -Expression Unit::BuildTimeSplit(double seconds, Context * context) { - constexpr static int numberOfTimeUnits = 6; - Unit units[numberOfTimeUnits] = {Unit::Year(), Unit::Month(), Unit::Day(), Unit::Hour(), Unit::Minute(), Unit::Second()}; - constexpr static double timeFactors[numberOfTimeUnits] = {MonthPerYear*DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, HoursPerDay*MinutesPerHour*SecondsPerMinute, MinutesPerHour*SecondsPerMinute, SecondsPerMinute, 1.0}; - return BuildSplit(seconds, units, timeFactors, numberOfTimeUnits, context); +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); + static_cast(factor).chooseBestRepresentativeAndPrefix(value, exponent, reductionContext, optimizePrefix); } -Expression Unit::BuildImperialDistanceSplit(double inches, Context * context) { - constexpr static int numberOfUnits = 4; - Unit units[numberOfUnits] = {Unit::Mile(), Unit::Yard(), Unit::Foot(), Unit::Inch()}; - constexpr static double factors[numberOfUnits] = {InchesPerFoot*FeetPerYard*YardsPerMile, InchesPerFoot*FeetPerYard, InchesPerFoot, 1.}; - return BuildSplit(inches, units, factors, numberOfUnits, context); +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); + } } -Expression Unit::BuildImperialMassSplit(double ounces, Context * context) { - constexpr static int numberOfUnits = 3; - Unit units[numberOfUnits] = {Unit::ShortTon(), Unit::Pound(), Unit::Ounce()}; - constexpr static double factors[numberOfUnits] = {OuncesPerPound*PoundsPerShortTon, OuncesPerPound, 1.}; - return BuildSplit(ounces, units, factors, numberOfUnits, context); +bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit) { + UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); + const Representative * representative = Representative::RepresentativeForDimension(vector); + return representative != nullptr && representative->hasAdditionalExpressions(value); } -Expression Unit::BuildImperialVolumeSplit(double fluidOunces, Context * context) { - constexpr static int numberOfUnits = 3; - Unit units[numberOfUnits] = {Unit::Gallon(), Unit::Cup(), Unit::FluidOunce()}; - constexpr static double factors[numberOfUnits] = {FluidOuncesPerCup*CupsPerGallon, FluidOuncesPerCup, 1.}; - return BuildSplit(fluidOunces, units, factors, numberOfUnits, context); +int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { + const Representative * representative = UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); + assert(representative); + return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); } -Expression Unit::StandardSpeedConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { - Preferences::UnitFormat format = reductionContext.unitFormat(); - return UnitConvert::Builder(e.clone(), Multiplication::Builder( - format == Preferences::UnitFormat::Metric ? Unit::Kilometer() : Unit::Mile(), - Power::Builder( - Unit::Hour(), - Rational::Builder(-1) - ) - ) - ); -} +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; -Expression Unit::StandardDistanceConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { - Preferences::UnitFormat format = reductionContext.unitFormat(); - if (format == Preferences::UnitFormat::Metric) { - return UnitConvert::Builder(e.clone(), Unit::Meter()); + 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()); + } } - assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::Inch(), reductionContext); - return BuildImperialDistanceSplit(rawValue, reductionContext.context()); + 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::StandardVolumeConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { - Preferences::UnitFormat format = reductionContext.unitFormat(); - if (format == Preferences::UnitFormat::Metric) { - return UnitConvert::Builder(e.clone(), Unit::Liter()); +Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + 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; } - assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::FluidOunce(), reductionContext); - return BuildImperialVolumeSplit(rawValue, reductionContext.context()); + UnitNode * unitNode = node(); + 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::StandardMassConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { - Preferences::UnitFormat format = reductionContext.unitFormat(); - if (format == Preferences::UnitFormat::Metric) { - return UnitConvert::Builder(e.clone(), Unit::Gram()); +Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { + // 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)); + replaceWithInPlace(m); + m.addChildAtIndexInPlace(*this, 1, 1); + return std::move(m); } - assert(format == Preferences::UnitFormat::Imperial); - double rawValue = ConvertedValueInUnit(e, Unit::Ounce(), reductionContext); - return BuildImperialMassSplit(rawValue, reductionContext.context()); + return *this; } -Expression Unit::StandardSurfaceConversion(Expression e, ExpressionNode::ReductionContext reductionContext) { - Preferences::UnitFormat format = reductionContext.unitFormat(); - if (format == Preferences::UnitFormat::Metric) { - return UnitConvert::Builder(e.clone(), Unit::Hectare()); +Expression Unit::removeUnit(Expression * unit) { + *unit = *this; + Expression one = Rational::Builder(1); + replaceWithInPlace(one); + return one; +} + +void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix) { + assert(exponent != 0.f); + if (std::isinf(*value) || *value == 0.0) { + node()->setRepresentative(node()->representative()->representativesOfSameDimension()); + node()->setPrefix(node()->representative()->basePrefix()); + return; + } + // 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 (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); + } + if (optimizePrefix && bestPrefix != node()->prefix()) { + *value = *value * std::pow(10., exponent * (node()->prefix()->exponent() - bestPrefix->exponent())); + node()->setPrefix(bestPrefix); } - assert(format == Preferences::UnitFormat::Imperial); - return UnitConvert::Builder(e.clone(), Unit::Acre()); } template Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/test/expression.cpp b/poincare/test/expression.cpp index bd1ae80fccb..c6508847d27 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 97af3fde373..adbd01e0d80 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -394,51 +394,62 @@ 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); - 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) { Shared::GlobalContext globalContext; - ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion); + constexpr int maxNumberOfResults = 5; + assert(length <= maxNumberOfResults); + Expression additional[maxNumberOfResults]; + ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion); Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); - Expression unit; - e.removeUnit(&unit); - return unit; -} + Expression units; + e = e.removeUnit(&units); + double value = e.approximateToScalar(&globalContext, Cartesian, Degree); -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()); + if (!Unit::ShouldDisplayAdditionalOutputs(value, units)) { + quiz_assert(length == 0); + return; + } + const int numberOfResults = Unit::SetAdditionalExpressions(units, value, additional, maxNumberOfResults, reductionContext); - 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); - - // 2. Speed - Expression meterPerSecond = extract_unit("_m×_s^-1"); - quiz_assert(Unit::IsSISpeed(meterPerSecond)); - - // 3. Volume - Expression meter3 = extract_unit("_m^3"); - quiz_assert(Unit::IsSIVolume(meter3)); - - // 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)); - - // 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)); + 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_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); + + // Distance + const char * array4[1] = {"19×_mi+853×_yd+1×_ft+7×_in"}; + assert_additional_results_compute_to("1234567×_in", array4, 1); + const char * array5[1] = {"1×_yd+7.700787×_in"}; + assert_additional_results_compute_to("1.11×_m", array5, 1); + + // Masses + const char * array6[1] = {"1×_shtn+240×_lb"}; + assert_additional_results_compute_to("1×_lgtn", array6, 1); + const char * array7[1] = {"2×_lb+3.273962×_oz"}; + assert_additional_results_compute_to("1×_kg", array7, 1); + + // Energy + const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; + assert_additional_results_compute_to("3.6×_MN_m", array8, 2); + + // Volume + const char * array9[2] = {"1000×_L", "264×_gal+2×_cp+6.022702×_floz"}; + assert_additional_results_compute_to("1×_m^3", array9, 2); + const char * array10[2] = {"182.5426×_L", "48×_gal+3×_cp+4.5×_floz"}; + assert_additional_results_compute_to("12345×_Tbsp", array10, 2); + + // Speed + const char * array11[2] = {"3.6×_km×_h^\x12-1\x13", "2.236936×_mi×_h^\x12-1\x13"}; + assert_additional_results_compute_to("1×_m/_s", array11, 2); } diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index a0437eaf000..49bd40c2f95 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -286,20 +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()) { - /* ton is only prefixable by positive prefixes */ - bool isTon = strcmp("t", rep->rootSymbol()) == 0; - size_t numberOfPrefixes = ((isTon) ? sizeof(Unit::PositiveLongScalePrefixes) : sizeof(Unit::AllPrefixes))/sizeof(Unit::Prefix *); - for (size_t i = 0; i < numberOfPrefixes; i++) { - const Unit::Prefix * pre = (isTon) ? Unit::PositiveLongScalePrefixes[i] : 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"); } diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 702fc6665e0..f4cf7a7d071 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -196,6 +196,26 @@ QUIZ_CASE(poincare_simplification_multiplication) { assert_parsed_expression_simplify_to("0*[[1,0][0,1]]^500", "0×[[1,0][0,1]]^500"); } +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) { /* SI base units */ assert_parsed_expression_simplify_to("_s", "1×_s"); @@ -258,10 +278,9 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_mol×_s^-1", "1×_kat"); /* Displayed order of magnitude */ - assert_parsed_expression_simplify_to("1_t", "1×_t"); 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", "100×_mm"); + 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"); @@ -269,31 +288,60 @@ QUIZ_CASE(poincare_simplification_units) { * 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(), "Hz") == 0 || strcmp(rep->rootSymbol(), "S") == 0 || strcmp(rep->rootSymbol(), "ha") == 0 || strcmp(rep->rootSymbol(), "L") == 0 || strcmp(rep->rootSymbol(), "yd") || strcmp(rep->rootSymbol(), "tsp") || strcmp(rep->rootSymbol(), "Tbsp") || strcmp(rep->rootSymbol(), "pt") || strcmp(rep->rootSymbol(), "qt") || strcmp(rep->rootSymbol(), "lgtn")) { - 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, User, Radian, (rep->canOutputInSystem(Metric) ? Metric : Imperial)); - 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); - } - } - } - } - - /* Units that do not appear as output yet */ + 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_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 */ + 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("_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); + /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); assert_parsed_expression_simplify_to("_m-_m", "0×_m"); @@ -339,11 +387,12 @@ 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", "13.083978345207×_ps"); assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); @@ -1426,7 +1475,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'))); From 5e06f62ff6310e897e08e8b5ab7a772cbb546fbc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 16:40:34 +0200 Subject: [PATCH 157/560] [poincare/unit] Symbols for cup and tablespoon _cp -> _cup _Tbsp -> _tbsp Change-Id: Ied3e1624edc980f76c2f775cbb9e6028db8a83c5 --- apps/shared.universal.i18n | 4 ++-- poincare/include/poincare/unit.h | 6 +++--- poincare/test/expression_properties.cpp | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 0ffd90a8439..2c5ad9ab8c5 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -84,9 +84,9 @@ UnitVolumeLiterDeciSymbol = "_dL" UnitVolumeLiterCentiSymbol = "_cL" UnitVolumeLiterMilliSymbol = "_mL" UnitVolumeTeaspoonSymbol = "_tsp" -UnitVolumeTablespoonSymbol = "_Tbsp" +UnitVolumeTablespoonSymbol = "_tbsp" UnitVolumeFluidOunceSymbol = "_floz" -UnitVolumeCupSymbol = "_cp" +UnitVolumeCupSymbol = "_cup" UnitVolumePintSymbol = "_pt" UnitVolumeQuartSymbol = "_qt" UnitVolumeGallonSymbol = "_gal" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 9802de816c1..99af96d1864 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -586,9 +586,9 @@ class Unit : public Expression { 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("tbsp", 3*0.00000492892159375, Prefixable::None, Prefixable::None), VolumeRepresentative("floz", 0.0000295735295625, Prefixable::None, Prefixable::None), - VolumeRepresentative("cp", 8*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), @@ -642,7 +642,7 @@ class Unit : public Expression { static constexpr int k_fluidOunceRepresentativeIndex = 3; static_assert(strings_equal(k_volumeRepresentatives[k_fluidOunceRepresentativeIndex].m_rootSymbol, "floz"), "Index for the Fluid Ounce Representative is incorrect."); static constexpr int k_cupRepresentativeIndex = 4; - static_assert(strings_equal(k_volumeRepresentatives[k_cupRepresentativeIndex].m_rootSymbol, "cp"), "Index for the Cup Representative is incorrect."); + static_assert(strings_equal(k_volumeRepresentatives[k_cupRepresentativeIndex].m_rootSymbol, "cup"), "Index for the Cup 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."); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index adbd01e0d80..5e609c378d2 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -444,10 +444,10 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("3.6×_MN_m", array8, 2); // Volume - const char * array9[2] = {"1000×_L", "264×_gal+2×_cp+6.022702×_floz"}; + const char * array9[2] = {"1000×_L", "264×_gal+2×_cup+6.022702×_floz"}; assert_additional_results_compute_to("1×_m^3", array9, 2); - const char * array10[2] = {"182.5426×_L", "48×_gal+3×_cp+4.5×_floz"}; - assert_additional_results_compute_to("12345×_Tbsp", array10, 2); + const char * array10[2] = {"182.5426×_L", "48×_gal+3×_cup+4.5×_floz"}; + assert_additional_results_compute_to("12345×_tbsp", array10, 2); // Speed const char * array11[2] = {"3.6×_km×_h^\x12-1\x13", "2.236936×_mi×_h^\x12-1\x13"}; From 801c61549c0a227e3a9387d5d529b23ec1e012cd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 16:48:28 +0200 Subject: [PATCH 158/560] [poincare/unit] Change units for volume split In additional outputs, a volume is now splitted as : _gal+_qt+_pt+_cup instead of : _gal+_cup+_floz Change-Id: I5020afbab23be6331d8a8742fd6295db178f0b37 --- poincare/include/poincare/unit.h | 6 ++++-- poincare/src/unit.cpp | 12 +++++++----- poincare/test/expression_properties.cpp | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 99af96d1864..bef0ddabba0 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -639,10 +639,12 @@ class Unit : public Expression { 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_fluidOunceRepresentativeIndex = 3; - static_assert(strings_equal(k_volumeRepresentatives[k_fluidOunceRepresentativeIndex].m_rootSymbol, "floz"), "Index for the Fluid Ounce 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."); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 4312288e8ce..ff341199a6e 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -59,8 +59,9 @@ constexpr const int Unit::k_hectareRepresentativeIndex, Unit::k_acreRepresentativeIndex, Unit::k_literRepresentativeIndex, - Unit::k_fluidOunceRepresentativeIndex, Unit::k_cupRepresentativeIndex, + Unit::k_pintRepresentativeIndex, + Unit::k_quartRepresentativeIndex, Unit::k_gallonRepresentativeIndex; // UnitNode::Prefix @@ -442,7 +443,7 @@ int UnitNode::DistanceRepresentative::setAdditionalExpressions(double value, Exp Unit::Builder(representativesOfSameDimension() + Unit::k_yardRepresentativeIndex, Prefix::EmptyPrefix()), Unit::Builder(representativesOfSameDimension() + Unit::k_mileRepresentativeIndex, Prefix::EmptyPrefix()), }; - dest[0] = Unit::BuildSplit(value, splitUnits, 4, reductionContext); + dest[0] = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); return 1; } @@ -465,7 +466,7 @@ int UnitNode::MassRepresentative::setAdditionalExpressions(double value, Express Unit::Builder(representativesOfSameDimension() + Unit::k_poundRepresentativeIndex, Prefix::EmptyPrefix()), Unit::Builder(representativesOfSameDimension() + Unit::k_shortTonRepresentativeIndex, Prefix::EmptyPrefix()), }; - dest[0] = Unit::BuildSplit(value, splitUnits, 3, reductionContext); + dest[0] = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); return 1; } @@ -534,11 +535,12 @@ int UnitNode::VolumeRepresentative::setAdditionalExpressions(double value, Expre Unit::Builder(liter, literPrefix)); // 2. Convert to imperial volumes const Unit splitUnits[] = { - Unit::Builder(representativesOfSameDimension() + Unit::k_fluidOunceRepresentativeIndex, Prefix::EmptyPrefix()), 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, numberOfRepresentatives() - 5, reductionContext); + *destImperial = Unit::BuildSplit(value, splitUnits, sizeof(splitUnits)/sizeof(Unit), reductionContext); return 2; } diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 5e609c378d2..26d07d74907 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -444,9 +444,9 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("3.6×_MN_m", array8, 2); // Volume - const char * array9[2] = {"1000×_L", "264×_gal+2×_cup+6.022702×_floz"}; + const char * array9[2] = {"1000×_L", "264×_gal+1×_pt+0.7528377×_cup"}; assert_additional_results_compute_to("1×_m^3", array9, 2); - const char * array10[2] = {"182.5426×_L", "48×_gal+3×_cup+4.5×_floz"}; + const char * array10[2] = {"182.5426×_L", "48×_gal+1×_pt+1.5625×_cup"}; assert_additional_results_compute_to("12345×_tbsp", array10, 2); // Speed From 860ce558c227b4a76893217df2f8a32a83b4f795 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 17:15:14 +0200 Subject: [PATCH 159/560] [poincare/unit] Remove some additional results Imperial additional results now only appear when the selected unit system is Imperial. Change-Id: Icc314d0148810bea67e4d729179393f1fceaf214 --- apps/calculation/calculation.cpp | 4 +- poincare/include/poincare/unit.h | 18 +++++---- poincare/src/unit.cpp | 54 ++++++++++++++++++++----- poincare/test/expression_properties.cpp | 32 +++++++++------ 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 682a27d668d..3c0372c8c85 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -257,10 +257,10 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co /* FIXME : When this method is accessed via leaving the additional outputs, * ie via a press on BACK, the reduction is interrupted, and removeUnit * goes badly.*/ - PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); o = o.removeUnit(&unit); double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); - return (Unit::ShouldDisplayAdditionalOutputs(value, unit)) ? AdditionalInformationType::Unit : AdditionalInformationType::None; + return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None; } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index bef0ddabba0..456ff470066 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -110,7 +110,7 @@ class UnitNode final : public ExpressionNode { 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); } - virtual bool hasAdditionalExpressions(double value) const { return true; } + virtual bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return true; } virtual int setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { return 0; } const char * rootSymbol() const { return m_rootSymbol; } double ratio() const { return m_ratio; } @@ -142,7 +142,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value) const override { return m_ratio * value >= representativesOfSameDimension()[1].ratio(); } + bool hasAdditionalExpressions(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: using Representative::Representative; @@ -157,6 +157,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -172,6 +173,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -185,7 +187,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value) const override { return false; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -198,7 +200,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value) const override { return false; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -211,7 +213,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value) const override { return false; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -224,7 +226,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value) const override { return false; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -236,7 +238,7 @@ class UnitNode final : public ExpressionNode { 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; - bool hasAdditionalExpressions(double value) const override { return false; } + bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -652,7 +654,7 @@ class Unit : public Expression { 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); + 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); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index ff341199a6e..8ea2607c3b8 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -437,6 +437,9 @@ const UnitNode::Representative * UnitNode::DistanceRepresentative::standardRepre 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()), @@ -461,6 +464,9 @@ const UnitNode::Representative * UnitNode::MassRepresentative::standardRepresent 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()), @@ -501,13 +507,21 @@ const UnitNode::Representative * UnitNode::SurfaceRepresentative::standardRepres int UnitNode::SurfaceRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { assert(availableLength >= 2); - int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; - Expression * destMetric = dest + k; - Expression * destImperial = dest + (1 - k); + 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; @@ -523,9 +537,14 @@ const UnitNode::Representative * UnitNode::VolumeRepresentative::standardReprese int UnitNode::VolumeRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { assert(availableLength >= 2); - int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; - Expression * destMetric = dest + k; - Expression * destImperial = dest + (1 - k); + 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(); @@ -534,6 +553,9 @@ int UnitNode::VolumeRepresentative::setAdditionalExpressions(double value, Expre 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()), @@ -546,9 +568,14 @@ int UnitNode::VolumeRepresentative::setAdditionalExpressions(double value, Expre int UnitNode::SpeedRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { assert(availableLength >= 2); - int k = (reductionContext.unitFormat() == Preferences::UnitFormat::Metric) ? 0 : 1; - Expression * destMetric = dest + k; - Expression * destImperial = dest + (1 - k); + 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; @@ -558,6 +585,9 @@ int UnitNode::SpeedRepresentative::setAdditionalExpressions(double value, Expres 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()), @@ -676,10 +706,12 @@ void Unit::ChooseBestRepresentativeAndPrefixForValue(Expression units, double * } } -bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit) { +bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat) { UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); const Representative * representative = Representative::RepresentativeForDimension(vector); - return representative != nullptr && representative->hasAdditionalExpressions(value); + return representative != nullptr + && ((unit.type() == ExpressionNode::Type::Unit && !unit.convert().isBaseUnit()) + || representative->hasAdditionalExpressions(value, unitFormat)); } int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 26d07d74907..79f10e8e419 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -394,18 +394,18 @@ QUIZ_CASE(poincare_properties_remove_unit) { assert_reduced_expression_unit_is("_L^2×3×_s", "_m^6×_s"); } -void assert_additional_results_compute_to(const char * expression, const char * * results, int length) { +void assert_additional_results_compute_to(const char * expression, const char * * results, int length, Preferences::UnitFormat unitFormat = Metric) { Shared::GlobalContext globalContext; constexpr int maxNumberOfResults = 5; assert(length <= maxNumberOfResults); Expression additional[maxNumberOfResults]; - ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, Metric, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion); + ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, unitFormat, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion); Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); Expression units; e = e.removeUnit(&units); double value = e.approximateToScalar(&globalContext, Cartesian, Degree); - if (!Unit::ShouldDisplayAdditionalOutputs(value, units)) { + if (!Unit::ShouldDisplayAdditionalOutputs(value, units, unitFormat)) { quiz_assert(length == 0); return; } @@ -429,27 +429,33 @@ QUIZ_CASE(poincare_expression_additional_results) { // Distance const char * array4[1] = {"19×_mi+853×_yd+1×_ft+7×_in"}; - assert_additional_results_compute_to("1234567×_in", array4, 1); + 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); + assert_additional_results_compute_to("1.11×_m", array5, 1, Imperial); + assert_additional_results_compute_to("1.11×_m", nullptr, 0, Metric); // Masses const char * array6[1] = {"1×_shtn+240×_lb"}; - assert_additional_results_compute_to("1×_lgtn", array6, 1); + 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); + assert_additional_results_compute_to("1×_kg", array7, 1, Imperial); + assert_additional_results_compute_to("1×_kg", nullptr, 0, Metric); // Energy const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; assert_additional_results_compute_to("3.6×_MN_m", array8, 2); // Volume - const char * array9[2] = {"1000×_L", "264×_gal+1×_pt+0.7528377×_cup"}; - assert_additional_results_compute_to("1×_m^3", array9, 2); - const char * array10[2] = {"182.5426×_L", "48×_gal+1×_pt+1.5625×_cup"}; - assert_additional_results_compute_to("12345×_tbsp", array10, 2); + 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 * array11[2] = {"3.6×_km×_h^\x12-1\x13", "2.236936×_mi×_h^\x12-1\x13"}; - assert_additional_results_compute_to("1×_m/_s", array11, 2); + 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); } From ec6ee82b818b981eebc2846c828079146b2cbea8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 18:34:42 +0200 Subject: [PATCH 160/560] [poincare/multiplication] Imperial volume display With the imperial system selected, volumes are expressed as volume in Calculation's results, instead of cubic lengths. Change-Id: Ib6c0a1a595dce8ae8db6371b41af818b3fdc6236 --- poincare/src/multiplication.cpp | 16 +++++++++++++++- poincare/test/simplification.cpp | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index aadd50c0c86..3a6f8e9e189 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -534,7 +534,21 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu } 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); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index f4cf7a7d071..8fe5e021bea 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -341,6 +341,8 @@ QUIZ_CASE(poincare_simplification_units) { 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"); /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); From df6383d2d8866a657c3c7bdd913b277f9a5c3a15 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 10:49:27 +0200 Subject: [PATCH 161/560] [apps/i18n] Added default country for languages After choosing a language at onboarding, the country menu now has a specific country selected by default (Spain for Spanish, Italy for Italian...) Default countries are specified in apps/language_preferences.csv Change-Id: Ia6392aceb9bebf7e62a692c5a79eb8c4d7b71a9d --- apps/Makefile | 3 +- apps/i18n.py | 38 ++++++++++++++++--- apps/language_preferences.csv | 9 +++++ apps/on_boarding/localization_controller.cpp | 7 ++++ apps/on_boarding/localization_controller.h | 4 +- .../sub_menu/localization_controller.cpp | 8 ++++ .../sub_menu/localization_controller.h | 4 +- apps/shared/localization_controller.cpp | 2 +- apps/shared/localization_controller.h | 6 +-- 9 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 apps/language_preferences.csv diff --git a/apps/Makefile b/apps/Makefile index e95a743699c..eb07e5ceb0f 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -56,6 +56,7 @@ $(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) @@ -73,7 +74,7 @@ $(eval $(call rule_for, \ I18N, \ apps/i18n.cpp, \ $(i18n_files), \ - $$(PYTHON) apps/i18n.py --codepoints $(code_points) --countrypreferences $(country_preferences) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --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 \ )) diff --git a/apps/i18n.py b/apps/i18n.py index 83bcdfb8a3c..3c688d64e72 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -21,6 +21,7 @@ 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)') @@ -117,17 +118,32 @@ def parse_codepoints(file): codepoints = parse_codepoints(args.codepoints) -def parse_country_preferences(file): - countryPreferences = {} - with io.open(file, "r", encoding="utf-8") as csvfile: +def parse_csv_with_header(file): + res = [] + with io.open(file, 'r', encoding='utf-8') as csvfile: csvreader = csv.reader(csvfile, delimiter=',') - headers = next(csvreader, None) for row in csvreader: - countryPreferences[row[0]] = [headers[i] + "::" + row[i] for i in range(1, len(row))] + 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)): @@ -179,6 +195,7 @@ def print_header(data, path, locales, countries): "enum class Country : uint8_t {\n", countries, lambda arg: arg.upper()) + defaultCountry = countries[-1] # Country names print_block_from_list(f, @@ -187,10 +204,19 @@ def print_header(data, path, locales, 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 'inl' + key = country if (country in countryPreferences) else defaultCountry line = " CountryPreferences(" for param in countryPreferences[key]: line += param + ", " diff --git a/apps/language_preferences.csv b/apps/language_preferences.csv new file mode 100644 index 00000000000..867820f7154 --- /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/on_boarding/localization_controller.cpp b/apps/on_boarding/localization_controller.cpp index bed17051f14..059d03a28a3 100644 --- a/apps/on_boarding/localization_controller.cpp +++ b/apps/on_boarding/localization_controller.cpp @@ -1,9 +1,16 @@ #include "localization_controller.h" #include #include +#include namespace OnBoarding { +int LocalizationController::indexOfCellToSelectOnReset() const { + return mode() == Mode::Language ? + 0 : + IndexOfCountry(I18n::DefaultCountryForLanguage[static_cast(GlobalPreferences::sharedGlobalPreferences()->language())]); +} + bool LocalizationController::handleEvent(Ion::Events::Event event) { if (Shared::LocalizationController::handleEvent(event)) { if (mode() == Mode::Language) { diff --git a/apps/on_boarding/localization_controller.h b/apps/on_boarding/localization_controller.h index 3f47a6fb0d9..03e0c131f92 100644 --- a/apps/on_boarding/localization_controller.h +++ b/apps/on_boarding/localization_controller.h @@ -10,8 +10,8 @@ class LocalizationController : public Shared::LocalizationController { public: using Shared::LocalizationController::LocalizationController; - bool shouldDisplayTitle() override { return mode() == Mode::Country; } - bool shouldResetSelectionToTopCell() override { return true; } + int indexOfCellToSelectOnReset() const override; + bool shouldDisplayTitle() const override { return mode() == Mode::Country; } bool handleEvent(Ion::Events::Event event) override; }; diff --git a/apps/settings/sub_menu/localization_controller.cpp b/apps/settings/sub_menu/localization_controller.cpp index b77981c8ef0..5088289db07 100644 --- a/apps/settings/sub_menu/localization_controller.cpp +++ b/apps/settings/sub_menu/localization_controller.cpp @@ -1,7 +1,15 @@ #include "localization_controller.h" +#include + namespace Settings { +int LocalizationController::indexOfCellToSelectOnReset() const { + return mode() == Mode::Language ? + static_cast(GlobalPreferences::sharedGlobalPreferences()->language()) : + IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country()); +} + bool LocalizationController::handleEvent(Ion::Events::Event event) { if (Shared::LocalizationController::handleEvent(event) || event == Ion::Events::Left) { static_cast(parentResponder())->pop(); diff --git a/apps/settings/sub_menu/localization_controller.h b/apps/settings/sub_menu/localization_controller.h index 7fee93862d9..531014cf4a3 100644 --- a/apps/settings/sub_menu/localization_controller.h +++ b/apps/settings/sub_menu/localization_controller.h @@ -10,8 +10,8 @@ class LocalizationController : public Shared::LocalizationController { public: using Shared::LocalizationController::LocalizationController; - bool shouldDisplayTitle() override { return false; } - bool shouldResetSelectionToTopCell() override { return false; } + int indexOfCellToSelectOnReset() const override; + bool shouldDisplayTitle() const override { return false; } bool handleEvent(Ion::Events::Event event) override; TELEMETRY_ID("Localization"); diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index ba433b95fa3..f08c86b7a87 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -127,7 +127,7 @@ LocalizationController::LocalizationController(Responder * parentResponder, KDCo void LocalizationController::resetSelection() { selectableTableView()->deselectTable(); - selectCellAtLocation(0, (shouldResetSelectionToTopCell()) ? 0 : (mode() == Mode::Language) ? static_cast(GlobalPreferences::sharedGlobalPreferences()->language()) : IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country())); + selectCellAtLocation(0, indexOfCellToSelectOnReset()); } void LocalizationController::setMode(LocalizationController::Mode mode) { diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h index 224ca7c37c1..82cee36894c 100644 --- a/apps/shared/localization_controller.h +++ b/apps/shared/localization_controller.h @@ -22,9 +22,9 @@ class LocalizationController : public ViewController, public SimpleListViewDataS Mode mode() const { return m_mode; } void setMode(Mode mode); - virtual bool shouldDisplayTitle() = 0; - virtual bool shouldResetSelectionToTopCell() = 0; - bool shouldDisplayWarning() { return mode() == Mode::Country; } + virtual int indexOfCellToSelectOnReset() const = 0; + virtual bool shouldDisplayTitle() const = 0; + bool shouldDisplayWarning() const { return mode() == Mode::Country; } View * view() override { return &m_contentView; } const char * title() override; From 504223612dd7a1c6bd58c4241b8177fab32df2ee Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 13:22:39 +0200 Subject: [PATCH 162/560] [apps/apps_container] Add timer to circuit breaker poincareCircuitBreaker requires the Back key to be pressed for at least 50 ms before interrupting a computation. This is effectively invisible for the user, but fixes a bug in Calculation : - When leaving the additional results for a result using units by pressing the Back key, a reduction preceding a call to removeUnits would be interrupted, causing an undefined behaviour. Change-Id: Iec667ae964f190de2171850cc22e1726959e6cb5 --- apps/apps_container.cpp | 17 ++++++++++++++++- apps/calculation/calculation.cpp | 3 --- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index a8cb20aa04a..ca3c1be1cb7 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -51,8 +51,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) || state.keyDown(Ion::Keyboard::Key::Home) || state.keyDown(Ion::Keyboard::Key::OnOff); + 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() { diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 3c0372c8c85..2ed67a817ef 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -254,9 +254,6 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co } if (o.hasUnit()) { Expression unit; - /* FIXME : When this method is accessed via leaving the additional outputs, - * ie via a press on BACK, the reduction is interrupted, and removeUnit - * goes badly.*/ PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); o = o.removeUnit(&unit); double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); From be8e452285c460cd162838c6dfdf11c77f6ada93 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 18 Aug 2020 17:48:01 +0200 Subject: [PATCH 163/560] [poincare/layout_cursor] Fix cursor after Square When typing Square in 2D Edition mode, the cursor would not be repositionned correctly, leading to some issues : - Typing 2 POW 3, pressing RIGHT to return to the baseline, then typing POW again adds parentheses around 2^3, as is mathematically correct. Typing 2 SQUARE, then POW, would show 2^2^_, while the user really typed (2^2)^_. Change-Id: I29c01d964924907a1866490189ea56e40771521d --- poincare/src/layout_cursor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index d77e152c397..17fc57fc56d 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -177,6 +177,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() { From 7c274409163045040d6a1fa6a56995c10a2f43fb Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 10:21:56 +0200 Subject: [PATCH 164/560] [poincare/layout] Tracking issue in addSibling The addSibling method sometimes did not take into account the offset introduced by adding parentheses, leading to some issues : - Type 2 SQUARE RIGHTPARENTHESIS LEFT SQUARE. Parentheses are added but the new square should be outside, between the two right parentheses. Change-Id: Ifbc49cee2cd03c4511fc5a678d6d5d42243f4a22 --- poincare/src/layout.cpp | 8 +++++++- poincare/src/vertical_offset_layout.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 1caa20ce667..bc81e3ec19a 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -176,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/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 40d6571ff80..1ab101e27a3 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); From db4a1b02654f53b4309b8dd9c4a80598e74d2d56 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 10:43:53 +0200 Subject: [PATCH 165/560] [poincare/tests] Add tests on power layout Change-Id: Ic7b0fbf87eec528221a4f836d34e1736e219ab96 --- poincare/test/layout.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/poincare/test/layout.cpp b/poincare/test/layout.cpp index 534c2ff1f52..573ebf8cc79 100644 --- a/poincare/test/layout.cpp +++ b/poincare/test/layout.cpp @@ -104,6 +104,44 @@ QUIZ_CASE(poincare_layout_fraction_create) { 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) { /* 3 * (2+(---)6)1 From 52f83eb68209fc35b59d449eaa8dd2ec365fdc28 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 30 Jul 2020 17:59:19 +0200 Subject: [PATCH 166/560] [apps/shared] Halve k_parametricStepFactor to maximise cache hits Change-Id: Ibe3c441e4af88277d1e4594801b805336a1f3e0b --- apps/graph/graph/graph_view.cpp | 4 ++-- apps/shared/continuous_function_cache.cpp | 2 +- apps/shared/continuous_function_cache.h | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index d455cdb9d5e..907d4b1003a 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -41,7 +41,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float tmin = f->tMin(); float tmax = f->tMax(); - float tstep = (tmax-tmin)/k_graphStepDenominator; + float tstep = (tmax-tmin) / k_graphStepDenominator; float tCacheMin, tCacheStep; if (type == ContinuousFunction::PlotType::Cartesian) { @@ -50,7 +50,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { tCacheStep = pixelWidth(); } else { tCacheMin = tmin; - tCacheStep = tstep / ContinuousFunctionCache::k_parametricStepFactor; + tCacheStep = int(k_graphStepDenominator) * tstep / ContinuousFunctionCache::k_numberOfParametricCacheablePoints; } ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 24435e92683..ce43fd31282 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -7,7 +7,7 @@ namespace Shared { constexpr int ContinuousFunctionCache::k_sizeOfCache; constexpr float ContinuousFunctionCache::k_cacheHitTolerance; constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; -constexpr int ContinuousFunctionCache::k_parametricStepFactor; +constexpr int ContinuousFunctionCache::k_numberOfParametricCacheablePoints; // public void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index 60faca9b840..2d035c34c39 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -17,7 +17,10 @@ class ContinuousFunctionCache { static constexpr int k_sizeOfCache = Ion::Display::Width; public: static constexpr int k_numberOfAvailableCaches = 2; - static constexpr int k_parametricStepFactor = k_sizeOfCache / int(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, + * k_numberOfParametricCacheablePoints is halved. */ + static constexpr int k_numberOfParametricCacheablePoints = k_sizeOfCache / 2; static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); @@ -35,7 +38,7 @@ class ContinuousFunctionCache { * * 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 * FLT_EPSILON; + static constexpr float k_cacheHitTolerance = 128.0f * FLT_EPSILON; void invalidateBetween(int iInf, int iSup); void setRange(ContinuousFunction * function, float tMin, float tStep); From aba135bc8b34a328065d5613fb85321234820679 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 11 Aug 2020 16:24:49 +0200 Subject: [PATCH 167/560] [escher] Add textArea line number limit Change-Id: Ia0d9a9136282efc87c0996c141c0852ed75892f9 --- apps/code/editor_view.cpp | 14 +++++++++++--- apps/code/editor_view.h | 1 + escher/include/escher/text_area.h | 5 +++++ escher/src/text_area.cpp | 5 +++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 86a29ba660e..9ec48d7eb74 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 ab3038c9494..547f7340b53 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -42,6 +42,7 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { 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/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 51881daebb5..ffc65b4bcce 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -94,6 +94,9 @@ class TextArea : public TextInput, public InputEventHandler { 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; @@ -133,6 +136,8 @@ class TextArea : public TextInput, public InputEventHandler { private: 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; }; #endif diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 76e7c0386c6..bbddcb70046 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -63,6 +63,11 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for } } + // Check the text will not overflow the max number of lines + if (contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') >= k_maxLines) { + return false; + } + // Insert the text if (!insertTextAtLocation(text, insertionPosition)) { return true; From d937bc692b22103f8ef2c932f94590b4bb9cb2e6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 13 Aug 2020 15:03:06 +0200 Subject: [PATCH 168/560] [apps/code] Fix crashes on full buffer Change-Id: If9297f1ca29015cad0cc1cdda3ad610fd1493392 --- apps/code/editor_controller.cpp | 6 +++--- apps/code/python_text_area.cpp | 8 ++++---- apps/code/script.h | 1 + ion/src/shared/storage.cpp | 3 ++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index bee8bb5d955..39348a1d412 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/python_text_area.cpp b/apps/code/python_text_area.cpp index e89e6005ba1..d0242debb12 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -441,9 +441,7 @@ void PythonTextArea::addAutocompletion() { const int scriptIndex = m_contentView.pythonDelegate()->menuController()->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) { @@ -468,6 +466,7 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn return false; } autocompletionLocation += textToInsertLength; + m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation); } @@ -480,8 +479,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/script.h b/apps/code/script.h index 9d2df78c68c..6f7df9cdae7 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -49,6 +49,7 @@ class Script : public Ion::Storage::Record { 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/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 56fc333ea6f..2bcb3471e9d 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); } @@ -479,7 +480,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; } From 0e11760f08390252288eebe73d94f5c757497d39 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 15 Jul 2020 14:22:57 +0200 Subject: [PATCH 169/560] [apps/shared/curve_view] Improving performances for non cartesian curves Change-Id: Ie1a9a6450e8f92ceaf93c5fe78711640be175e9e --- apps/shared/curve_view.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index f4b8683fa5a..115142165f1 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -713,7 +713,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 @@ -750,8 +750,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); } } From 59d5adace318a4fb57c54147bd0d6e13d8255ee2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 30 Jul 2020 10:26:20 +0200 Subject: [PATCH 170/560] [apps/shared] Optimize polar curve range display Change-Id: Ic1b044212711d1f73e147cb0857084ff9d61fbd9 --- apps/graph/graph/graph_view.cpp | 31 +++++++----- apps/shared/curve_view.cpp | 88 +++++++++++++++++++++++++++++++++ apps/shared/curve_view.h | 1 + 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 907d4b1003a..ad19d706742 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -46,6 +46,9 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float tCacheMin, tCacheStep; 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 { @@ -54,8 +57,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { } ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); - // Cartesian 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; @@ -75,18 +78,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::GrayVeryDark, false); } - continue; + } else if (type == Shared::ContinuousFunction::PlotType::Polar) { + // Polar + drawPolarCurve(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()); + } else { + // Parametric + assert(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()); } - - // 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/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 115142165f1..51980252384 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include using namespace Poincare; @@ -650,6 +652,92 @@ 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); + + if (std::isnan(rectLeft) || std::isnan(rectRight) || std::isnan(rectUp) || std::isnan(rectDown)) { + return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + + bool rectOverlapsNegativeAbscissaAxis = false; + if (rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f) { + if (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; + } + + Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + + float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); + /* Compute angular coordinate of each corners of rect. + * t4 --- t3 + * | | + * t1 --- t2 */ + float t1 = PolarThetaFromCoordinates(rectLeft, rectDown, angleUnit); + float t2 = PolarThetaFromCoordinates(rectRight, rectDown, angleUnit); + float t3 = PolarThetaFromCoordinates(rectRight, rectUp, angleUnit); + float t4 = PolarThetaFromCoordinates(rectLeft, rectUp, angleUnit); + + /* The area between tMin and tMax (modulo π) is the area where something might + * be plotted. */ + float tMin = std::min(std::min(t1,t2),std::min(t3,t4)); + float tMax = std::max(std::max(t1,t2),std::max(t3,t4)); + + if (rectOverlapsNegativeAbscissaAxis) { + /* PolarThetaFromCoordinates yields coordinates between -π and π. When rect + * is overlapping the negative abscissa (at this point, the origin cannot be + * inside rect), t1 and t2 have a negative angle whereas t3 and t4 have a + * positive angle. We ensure here that tMin is t3 (modulo 2π), tMax is t2, + * and that tMax-tMin is minimal and positive. */ + tMin = t3 - 2 * piInAngleUnit; + tMax = t2; + } + + /* Draw 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 the intervals : + * - [ π/4, π/3 ], [ 2π + π/4, 2π + π/3 ] + * - [ -π + π/4, -π + π/3 ], [ π + π/4, π + π/3 ] in case f(θ) is negative*/ + + // 1 - Translate tMin and tMax to the left so that no intersection is missed + while (tMax - piInAngleUnit > tStart) { + tMin -= piInAngleUnit; + tMax -= piInAngleUnit; + } + + // 2 - Translate tMin and tMax to the right until tMin is greater than tEnd + while (tMin < tEnd) { + float t1 = std::max(tMin, tStart); + float t2 = std::min(tMax, tEnd); + // Draw curve if there is an intersection + if (t1 <= t2) { + /* To maximize cache hits, we floor (and ceil) t1 (and t2) to the closest + * cached value. More of the curve is drawn. */ + int i = std::floor((t1 - tStart) / tStep); + float tCache1 = tStart + tStep * i; + + int j = std::ceil((t2 - tStart) / tStep); + float tCache2 = std::min(tStart + tStep * j, tEnd); + + drawCurve(ctx, rect, tCache1, tCache2, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); + } + tMin += piInAngleUnit; + tMax += 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()); diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 00a3119a04d..481429489fd 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -109,6 +109,7 @@ class CurveView : public View { 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); From d6c7b2b1ac3a5dc4cc3c15f90e240f3bf3a5c6ea Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 31 Aug 2020 11:43:42 +0200 Subject: [PATCH 171/560] [poincare/multiplication] Factoring undef power When factoring 1^inf * 1^-inf with Multiplication::factorizeBase, an Undef factor would appear an be carried into the reduced multiplication. We escape early in this situation by returning Undef. Change-Id: Id826ad3cc131349e5bb460f7a8c82fe606294b97 --- poincare/src/multiplication.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 3a6f8e9e189..9db2b6c43c1 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -731,6 +731,11 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::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)) { From 9be5e7671c9da1dc45fa52df277607297bea0ffd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 27 Aug 2020 11:20:14 +0200 Subject: [PATCH 172/560] [apps/settings] Fix broken assert Pressing OK or EXE with the brightness setting selected would cause a crash because of a mishandling of the events for this cell. Change-Id: I4978b4f749b5f19bc1e49ec05b60a32044d86638 --- apps/settings/main_controller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index d12cb186bd9..2c6b8069803 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -65,7 +65,11 @@ bool MainController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - assert(rowIndex != k_indexOfBrightnessCell); + if (rowIndex == k_indexOfBrightnessCell) { + /* Nothing is supposed to happen when OK or EXE are pressed on the + * brightness cell. The case of pressing Right has been handled above. */ + return true; + } if (rowIndex == k_indexOfLanguageCell) { m_localizationController.setMode(LocalizationController::Mode::Language); From 1917999f6d23ffa2598e847d24e5c15d1caf756e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 28 Aug 2020 12:29:04 +0200 Subject: [PATCH 173/560] [poincare/unit] Fix crash with Units in Graph In Graph, the weak ReductionTarget would cause _X^0 to not be simplified to 1, and would break an assertion in Unit::chooseBestRepresentativeAndPrefix. Change-Id: I8167a472802baf0a73bf48513f492e78517107ef --- poincare/src/unit.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 8ea2607c3b8..a62dea5a8dd 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -687,6 +687,13 @@ static void chooseBestRepresentativeAndPrefixForValueOnSingleUnit(Expression uni 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); } From 8104caea50f4301c0eb82de5211609bcf1bf15df Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 3 Aug 2020 17:10:17 +0200 Subject: [PATCH 174/560] [apps/shared] New automatic zoom on curves Initial zoom for displaying curves is computed according to the following rules : - For polar and parametric curves, the algorithm has not changed. Vertical and horizontal ranges are chosen in order to display the full curve with orthonormal graduations. - For cartesian curves, the horizontal range is chosen in order to display all points of interest (roots, extrema, asymptotes...). The vertical range is fitted to be able to display those points, but also cuts out points outside of the function's order of magnitude. Change-Id: Idf8233fc2e6586b85d34c4da152c83e75513d85c --- apps/graph/graph/graph_controller.cpp | 124 ++------ apps/graph/graph/graph_controller.h | 5 +- apps/sequence/graph/graph_controller.cpp | 48 ++++ apps/sequence/graph/graph_controller.h | 1 + apps/shared/continuous_function.cpp | 272 ++++++++++++++++++ apps/shared/continuous_function.h | 8 + apps/shared/function_graph_controller.cpp | 47 --- apps/shared/function_graph_controller.h | 3 - apps/shared/interactive_curve_view_range.cpp | 82 +++--- .../interactive_curve_view_range_delegate.h | 2 +- 10 files changed, 389 insertions(+), 203 deletions(-) diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 3ef644d219e..f2552c39191 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -35,116 +35,40 @@ 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 { + privateComputeRanges(true, xm, xM, ym, yM); } -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; +Shared::InteractiveCurveViewRangeDelegate::Range GraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { + float xm = interactiveCurveViewRange->xMin(), + xM = interactiveCurveViewRange->xMax(), + ym = FLT_MAX, + yM = -FLT_MAX; + privateComputeRanges(false, &xm, &xM, &ym, &yM); + return Shared::InteractiveCurveViewRangeDelegate::Range{.min = ym, .max = yM}; +} + +void GraphController::privateComputeRanges(bool tuneXRange, float * xm, float * xM, float * ym, float * yM) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + float resultXMin = tuneXRange ? FLT_MAX : *xm; + float resultXMax = tuneXRange ? -FLT_MAX : *xM; + 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. */ + int functionsCount = functionStore()->numberOfActiveFunctions(); 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); + f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context, tuneXRange); } - 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; + *xm = resultXMin; + *xM = resultXMax; + *ym = resultYMin; + *yM = resultYMax; } void GraphController::selectFunctionWithCursor(int functionIndex) { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index abc05b8c86a..f7dc798c0c6 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -20,7 +20,6 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont 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; } @@ -34,10 +33,12 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont 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; + Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; + void privateComputeRanges(bool tuneXRange, float * xm, float * xM, float * ym, float * yM) const; Shared::RoundCursorView m_cursorView; BannerView m_bannerView; diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index e2e4fe52fb6..303d4b001ee 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -101,4 +101,52 @@ double GraphController::defaultCursorT(Ion::Storage::Record record) { return std::fmax(0.0, std::round(Shared::FunctionGraphController::defaultCursorT(record))); } +InteractiveCurveViewRangeDelegate::Range GraphController::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; +} + } diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index b08a2b3ce2f..79440af9174 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -27,6 +27,7 @@ class GraphController final : public Shared::FunctionGraphController { bool handleEnter() override; bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override; double defaultCursorT(Ion::Storage::Record record) override; + InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; CurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } SequenceStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } GraphView * functionGraphView() override { return &m_view; } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 265c9dd974e..bcc0ef62f2e 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -261,6 +261,278 @@ void ContinuousFunction::setTMax(float tMax) { setCache(nullptr); } +void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange) const { + if (plotType() == PlotType::Cartesian) { + interestingXAndYRangesForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange); + } else { + fullXYRange(xMin, xMax, yMin, yMax, context); + } +} + +void ContinuousFunction::fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Context * context) const { + assert(yMin && yMax); + assert(!(std::isinf(tMin()) || std::isinf(tMax()) || std::isnan(rangeStep()))); + + float resultXMin = FLT_MAX, resultXMax = - FLT_MAX, resultYMin = FLT_MAX, resultYMax = - FLT_MAX; + for (float t = tMin(); t <= tMax(); t += rangeStep()) { + Coordinate2D xy = privateEvaluateXYAtParameter(t, context); + if (!std::isfinite(xy.x1()) || !std::isfinite(xy.x2())) { + continue; + } + resultXMin = std::min(xy.x1(), resultXMin); + resultXMax = std::max(xy.x1(), resultXMax); + resultYMin = std::min(xy.x2(), resultYMin); + resultYMax = std::max(xy.x2(), resultYMax); + } + if (xMin) { + *xMin = resultXMin; + } + if (xMax) { + *xMax = resultXMax; + } + *yMin = resultYMin; + *yMax = resultYMax; +} + +static float evaluateAndRound(const ContinuousFunction * f, float x, Context * context, float precision = 1e-5) { + /* When evaluating sin(x)/x close to zero using the standard sine function, + * one can detect small varitions, 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. */ + return precision * std::round(f->evaluateXYAtParameter(x, context).x2() / precision); +} + + +/* TODO : These three 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); +} + +/* This function 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 isExtremum(const ContinuousFunction * f, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, int iterations = 3) { + 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 = evaluateAndRound(f, xm, context); + bool res = ((y[i] < ym) != (ym < y2)) ? isExtremum(f, x[i], xm, x2, y[i], ym, y2, context, iterations - 1) : std::fabs(ym - y[i]) >= std::fabs(y2 - ym); + if (!res) { + return false; + } + } + return true; +} + +enum class PointOfInterest : uint8_t { + None, + Bound, + Extremum, + Root +}; + +void ContinuousFunction::interestingXAndYRangesForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Context * context, bool tuneXRange) const { + assert(xMin && xMax && yMin && yMax); + assert(plotType() == PlotType::Cartesian); + + /* Constants of the algorithm. */ + constexpr float defaultMaxInterval = 2e5f; + constexpr float minDistance = 1e-2f; + constexpr float asymptoteThreshold = 2e-1f; + constexpr float stepFactor = 1.1f; + constexpr int maxNumberOfPoints = 3; + constexpr float breathingRoom = 0.3f; + constexpr float maxRatioBetweenPoints = 100.f; + + const bool hasIntervalOfDefinition = std::isfinite(tMin()) && std::isfinite(tMax()); + float center, maxDistance; + if (!tuneXRange) { + center = (*xMax + *xMin) / 2.f; + maxDistance = (*xMax - *xMin) / 2.f; + } else if (hasIntervalOfDefinition) { + center = (tMax() + tMin()) / 2.f; + maxDistance = (tMax() - tMin()) / 2.f; + } else { + center = 0.f; + maxDistance = defaultMaxInterval / 2.f; + } + + float resultX[2] = {FLT_MAX, - FLT_MAX}; + float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; + float asymptote[2] = {FLT_MAX, - FLT_MAX}; + int numberOfPoints; + float xFallback, yFallback[2] = {NAN, NAN}; + float firstResult; + float dXOld, dXPrev, dXNext, yOld, yPrev, yNext; + + /* Look for an point of interest at the center. */ + const float a = center - minDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + minDistance + FLT_EPSILON; + const float ya = evaluateAndRound(this, a, context), yb = evaluateAndRound(this, b, context), yc = evaluateAndRound(this, c, context); + if (boundOfIntervalOfDefinitionIsReached(ya, yc) || + boundOfIntervalOfDefinitionIsReached(yc, ya) || + rootExistsOnInterval(ya, yc) || + extremumExistsOnInterval(ya, yb, yc) || ya == yc) + { + resultX[0] = resultX[1] = center; + if (extremumExistsOnInterval(ya, yb, yc) && isExtremum(this, a, b, c, ya, yb, yc, context)) { + 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 ? - minDistance : minDistance; + dXNext = dXPrev * stepFactor; + yPrev = evaluateAndRound(this, center + dXPrev, context); + yNext = evaluateAndRound(this, center + dXNext, context); + + while(std::fabs(dXPrev) < maxDistance) { + /* Update the slider. */ + dXOld = dXPrev; + dXPrev = dXNext; + dXNext *= stepFactor; + yOld = yPrev; + yPrev = yNext; + yNext = evaluateAndRound(this, center + dXNext, context); + 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) ? PointOfInterest::Extremum : + PointOfInterest::None; + switch (static_cast(variation)) { + /* The fall through 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 (isExtremum(this, center + dXOld, center + dXPrev, center + dXNext, yOld, yPrev, yNext, context)) { + 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 == maxNumberOfPoints) { + /* 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; + 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; + } + break; + default: + const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); + if ((std::fabs(slopeNext) < asymptoteThreshold) && (std::fabs(slopePrev) > 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) < asymptoteThreshold) && (std::fabs(slopePrev) > 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(resultX[i]) > std::fabs(firstResult) * maxRatioBetweenPoints && !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 (tuneXRange) { + /* Cut after horizontal asymptotes. */ + resultX[0] = std::min(resultX[0], asymptote[0]); + resultX[1] = std::max(resultX[1], asymptote[1]); + if (resultX[0] >= resultX[1]) { + /* Fallback to default range. */ + resultX[0] = - Range1D::k_default; + resultX[1] = Range1D::k_default; + } else { + /* Add breathing room around points of interest. */ + float xRange = resultX[1] - resultX[0]; + resultX[0] -= breathingRoom * xRange; + resultX[1] += breathingRoom * xRange; + /* Round to the next integer. */ + resultX[0] = std::floor(resultX[0]); + resultX[1] = std::ceil(resultX[1]); + } + *xMin = std::min(resultX[0], *xMin); + *xMax = std::max(resultX[1], *xMax); + } + *yMin = std::min(resultYMin, *yMin); + *yMax = std::max(resultYMax, *yMax); + + refinedYRangeForDisplay(*xMin, *xMax, yMin, yMax, context); +} + +void ContinuousFunction::refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Context * context) const { + /* This methods computes the Y range that will be displayed for the cartesian + * function, given an X range (xMin, xMax) and bounds yMin and yMax that must + * be inside the Y range.*/ + assert(plotType() == PlotType::Cartesian); + assert(yMin && yMax); + + constexpr int sampleSize = 80; + + float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; + const float step = (xMax - xMin) / (sampleSize - 1); + float x, y; + float sum = 0.f; + int pop = 0; + + for (int i = 1; i < sampleSize; i++) { + x = xMin + i * step; + y = privateEvaluateXYAtParameter(x, context).x2(); + sampleYMin = std::min(sampleYMin, y); + sampleYMax = std::max(sampleYMax, y); + if (std::isfinite(y) && std::fabs(y) > FLT_EPSILON) { + sum += std::log(std::fabs(y)); + pop++; + } + } + /* sum/n 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. */ + float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; + *yMin = std::min(*yMin, std::max(sampleYMin, -bound)); + *yMax = std::max(*yMax, std::min(sampleYMax, bound)); + if (*yMin == *yMax) { + float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; + *yMin -= d; + *yMax += d; + } +} + void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { return (char *)record->value().buffer+sizeof(RecordDataBuffer); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index b6ea846bb67..61674b83499 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -70,6 +70,8 @@ class ContinuousFunction : public Function { 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, Poincare::Context * context, bool tuneXRange = true) const; + // 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; @@ -88,6 +90,12 @@ class ContinuousFunction : public Function { 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; + + // Ranges + void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; + void interestingXAndYRangesForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange = true) const; + void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, 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. */ diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 9f791969ce7..949c925052c 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -73,53 +73,6 @@ 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; diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 9e7297efebc..245e1876ef6 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -44,9 +44,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu 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; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 4b09d3b97be..7882b3316db 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -90,25 +90,29 @@ void InteractiveCurveViewRange::roundAbscissa() { void InteractiveCurveViewRange::normalize() { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ + m_yAuto = false; - const float unit = std::max(xGridUnit(), yGridUnit()); + float xRange = xMax() - xMin(); + float yRange = yMax() - yMin(); + float xyRatio = xRange/yRange; - // 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); + 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); } } @@ -128,21 +132,15 @@ 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; - } + // Compute the interesting range 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); + float xm, xM, ym, yM; + m_delegate->interestingRanges(&xm, &xM, &ym, &yM); + m_xRange.setMin(xm, k_lowerMaxFloat, k_upperMaxFloat); + m_xRange.setMax(xM, k_lowerMaxFloat, k_upperMaxFloat); + m_yRange.setMin(ym, k_lowerMaxFloat, k_upperMaxFloat); + m_yRange.setMax(yM, k_lowerMaxFloat, k_upperMaxFloat); // Add margins float xRange = xMax() - xMin(); @@ -153,28 +151,12 @@ void InteractiveCurveViewRange::setDefault() { 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); - // 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()) { + return; } + + // Normalize the axes, so that a polar circle is displayed as a circle + normalize(); } void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index b9a98b0a394..5055972caaf 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -12,7 +12,7 @@ class InteractiveCurveViewRangeDelegate { bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange); virtual float interestingXMin() const { return -interestingXHalfRange(); } virtual float interestingXHalfRange() const { return 10.0f; } - virtual bool defautRangeIsNormalized() const { return false; } + virtual bool defaultRangeIsNormalized() const { return false; } virtual void interestingRanges(float * xm, float * xM, float * ym, float * yM) const { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; protected: From 0ed0cc56e90ecdf1a06a8a1dc426b7c95084f310 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 5 Aug 2020 11:08:57 +0200 Subject: [PATCH 175/560] [poincare] Remove characteristicXHalfRange Method characteristicXHalfRange was used to compute the range on which to display cartesian function in Graph. With the new zoom algorithm, this method is deprecated. Change-Id: Ic681fab8d58d0f5628a94302a7b49dacaaa1a6a3 --- poincare/include/poincare/cosine.h | 1 - poincare/include/poincare/expression.h | 9 ----- poincare/include/poincare/expression_node.h | 1 - poincare/include/poincare/function.h | 1 - poincare/include/poincare/sine.h | 1 - poincare/include/poincare/symbol.h | 1 - poincare/include/poincare/tangent.h | 1 - poincare/include/poincare/trigonometry.h | 1 - poincare/src/cosine.cpp | 4 --- poincare/src/expression_node.cpp | 16 --------- poincare/src/function.cpp | 9 ----- poincare/src/sine.cpp | 4 --- poincare/src/symbol.cpp | 4 --- poincare/src/tangent.cpp | 4 --- poincare/src/trigonometry.cpp | 26 -------------- poincare/test/expression_properties.cpp | 39 --------------------- 16 files changed, 122 deletions(-) diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index b5c81aa4357..bca04a2ed6e 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -21,7 +21,6 @@ class CosineNode final : public ExpressionNode { // 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); diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index e658a926d71..0b665ff86cf 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -170,15 +170,6 @@ class Expression : public TreeHandle { 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 */ diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index e30d627ce13..2e7e45c10f7 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -200,7 +200,6 @@ class ExpressionNode : public TreeNode { /*!*/ 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 diff --git a/poincare/include/poincare/function.h b/poincare/include/poincare/function.h index cc64a531b23..ddd11f81b87 100644 --- a/poincare/include/poincare/function.h +++ b/poincare/include/poincare/function.h @@ -27,7 +27,6 @@ class FunctionNode : public SymbolAbstractNode { 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 diff --git a/poincare/include/poincare/sine.h b/poincare/include/poincare/sine.h index eb3938b13cc..169db870bd2 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -21,7 +21,6 @@ class SineNode final : public ExpressionNode { // 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); diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 8db3794c48d..8b800a5fce1 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -25,7 +25,6 @@ class SymbolNode final : public SymbolAbstractNode { 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.*/ diff --git a/poincare/include/poincare/tangent.h b/poincare/include/poincare/tangent.h index 7ec86acb0f9..0576191b992 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -20,7 +20,6 @@ class TangentNode final : public ExpressionNode { // Properties Type type() const override { return Type::Tangent; } - float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; private: // Layout diff --git a/poincare/include/poincare/trigonometry.h b/poincare/include/poincare/trigonometry.h index 0eac9d5c2e9..86e49da2a69 100644 --- a/poincare/include/poincare/trigonometry.h +++ b/poincare/include/poincare/trigonometry.h @@ -13,7 +13,6 @@ class Trigonometry final { 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); diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index dcf87c6e461..16a47ef1f8b 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -15,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); diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index e74456cc946..4bd4b9823c1 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(); diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index ae5764af27f..1d966416db9 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -44,15 +44,6 @@ int FunctionNode::getVariables(Context * context, isVariableTest isVariable, cha 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()); } diff --git a/poincare/src/sine.cpp b/poincare/src/sine.cpp index 7d90778b890..17b77a1335f 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -13,10 +13,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); diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 3cedd2daaa7..e04e35656a9 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -64,10 +64,6 @@ 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() ? diff --git a/poincare/src/tangent.cpp b/poincare/src/tangent.cpp index 9708ebd9856..d1d1578053c 100644 --- a/poincare/src/tangent.cpp +++ b/poincare/src/tangent.cpp @@ -17,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()); } diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index a466f11a62f..209aebcc787 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -55,32 +55,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 diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 79f10e8e419..4cf2a3b855e 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -240,45 +240,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, Preferences::UnitFormat unitFormat = Metric) { - Shared::GlobalContext globalContext; - e = e.reduce(ExpressionNode::ReductionContext(&globalContext, Preferences::ComplexFormat::Cartesian, angleUnit, unitFormat, 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); From 17f39e5e398999397ad196e758b8e57213c88779 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 5 Aug 2020 14:07:02 +0200 Subject: [PATCH 176/560] [apps/graph] Take cursor into account for Y range Additional checks have been added to ensure that the first move of the cursor from the center of the graph would not cause the window to pan up or down. Change-Id: I44d7e86223941076cbf03db7a221e9c0427a64e4 --- apps/graph/graph/graph_controller.cpp | 50 +++++++++++++------ apps/graph/graph/graph_controller.h | 5 +- apps/shared/interactive_curve_view_range.cpp | 7 +-- .../interactive_curve_view_range_delegate.h | 2 +- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index f2552c39191..998c2a20d01 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -39,23 +39,21 @@ bool GraphController::defaultRangeIsNormalized() const { return functionStore()->displaysNonCartesianFunctions(); } -void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const { - privateComputeRanges(true, xm, xM, ym, yM); +void GraphController::interestingRanges(InteractiveCurveViewRange * range) const { + privateComputeRanges(true, range); } Shared::InteractiveCurveViewRangeDelegate::Range GraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { - float xm = interactiveCurveViewRange->xMin(), - xM = interactiveCurveViewRange->xMax(), - ym = FLT_MAX, - yM = -FLT_MAX; - privateComputeRanges(false, &xm, &xM, &ym, &yM); - return Shared::InteractiveCurveViewRangeDelegate::Range{.min = ym, .max = yM}; + InteractiveCurveViewRange tempRange = *interactiveCurveViewRange; + tempRange.setYAuto(false); + privateComputeRanges(false, &tempRange); + return Shared::InteractiveCurveViewRangeDelegate::Range{.min = tempRange.yMin(), .max = tempRange.yMax()}; } -void GraphController::privateComputeRanges(bool tuneXRange, float * xm, float * xM, float * ym, float * yM) const { +void GraphController::privateComputeRanges(bool tuneXRange, InteractiveCurveViewRange * range) const { Poincare::Context * context = textFieldDelegateApp()->localContext(); - float resultXMin = tuneXRange ? FLT_MAX : *xm; - float resultXMax = tuneXRange ? -FLT_MAX : *xM; + float resultXMin = tuneXRange ? FLT_MAX : range->xMin(); + float resultXMax = tuneXRange ? -FLT_MAX : range->xMax(); float resultYMin = FLT_MAX; float resultYMax = -FLT_MAX; assert(functionStore()->numberOfActiveFunctions() > 0); @@ -65,10 +63,32 @@ void GraphController::privateComputeRanges(bool tuneXRange, float * xm, float * f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context, tuneXRange); } - *xm = resultXMin; - *xM = resultXMax; - *ym = resultYMin; - *yM = resultYMax; + range->setXMin(resultXMin); + range->setXMax(resultXMax); + range->setYMin(resultYMin); + range->setYMax(resultYMax); + /* We can only call this method once the X range has been fully computed. */ + yRangeForCursorFirstMove(range); +} + +void GraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + assert(functionStore()->numberOfActiveFunctions() > 0); + int functionsCount = functionStore()->numberOfActiveFunctions(); + + float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; + float yN, yP; + + for (int i = 0; i < functionsCount; i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + if (f->plotType() != ContinuousFunction::PlotType::Cartesian) { + continue; + } + yN = f->evaluateXYAtParameter(range->xCenter() - cursorStep, context).x2(); + yP = f->evaluateXYAtParameter(range->xCenter() + cursorStep, context).x2(); + range->setYMin(std::min(range->yMin(), std::min(yN, yP))); + range->setYMax(std::max(range->yMax(), std::max(yN, yP))); + } } void GraphController::selectFunctionWithCursor(int functionIndex) { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index f7dc798c0c6..89df7333a8c 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -20,7 +20,7 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont void viewWillAppear() override; bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } - void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override; + void interestingRanges(Shared::InteractiveCurveViewRange * range) const override; private: int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; @@ -38,7 +38,8 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont bool shouldSetDefaultOnModelChange() const override; void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override; Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; - void privateComputeRanges(bool tuneXRange, float * xm, float * xM, float * ym, float * yM) const; + void privateComputeRanges(bool tuneXRange, Shared::InteractiveCurveViewRange * range) const; + void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; Shared::RoundCursorView m_cursorView; BannerView m_bannerView; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 7882b3316db..7011f408a1e 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -135,12 +135,7 @@ void InteractiveCurveViewRange::setDefault() { // Compute the interesting range m_yAuto = false; - float xm, xM, ym, yM; - m_delegate->interestingRanges(&xm, &xM, &ym, &yM); - m_xRange.setMin(xm, k_lowerMaxFloat, k_upperMaxFloat); - m_xRange.setMax(xM, k_lowerMaxFloat, k_upperMaxFloat); - m_yRange.setMin(ym, k_lowerMaxFloat, k_upperMaxFloat); - m_yRange.setMax(yM, k_lowerMaxFloat, k_upperMaxFloat); + m_delegate->interestingRanges(this); // Add margins float xRange = xMax() - xMin(); diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index 5055972caaf..6da8921a2d6 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -13,7 +13,7 @@ class InteractiveCurveViewRangeDelegate { virtual float interestingXMin() const { return -interestingXHalfRange(); } virtual float interestingXHalfRange() const { return 10.0f; } virtual bool defaultRangeIsNormalized() const { return false; } - virtual void interestingRanges(float * xm, float * xM, float * ym, float * yM) const { assert(false); } + virtual void interestingRanges(InteractiveCurveViewRange * range) const { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; protected: struct Range { From a1aefb0cb25dd7d579975a4d8ff23029d4920512 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 5 Aug 2020 17:50:57 +0200 Subject: [PATCH 177/560] [apps/graph] Added tests on graph ranges Change-Id: I6c080f40c934cd31083be3d19aa0a4d0bb33c5cc --- apps/graph/Makefile | 4 +- apps/graph/test/caching.cpp | 47 ++++------ apps/graph/test/helper.cpp | 16 ++++ apps/graph/test/helper.h | 19 ++++ apps/graph/test/range.cpp | 182 ++++++++++++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 30 deletions(-) create mode 100644 apps/graph/test/helper.cpp create mode 100644 apps/graph/test/helper.h create mode 100644 apps/graph/test/range.cpp diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 1a3fbddccbc..de03db3d67d 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -40,7 +40,9 @@ apps_src += $(app_graph_src) i18n_files += $(call i18n_without_universal_for,graph/base) tests_src += $(addprefix apps/graph/test/,\ - caching.cpp\ + caching.cpp \ + helper.cpp \ + range.cpp \ ) $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png)) diff --git a/apps/graph/test/caching.cpp b/apps/graph/test/caching.cpp index 70ee6863122..059c7cef344 100644 --- a/apps/graph/test/caching.cpp +++ b/apps/graph/test/caching.cpp @@ -1,5 +1,5 @@ #include -#include "../app.h" +#include "helper.h" #include using namespace Poincare; @@ -13,17 +13,6 @@ bool floatEquals(float a, float b, float tolerance = 1.f/static_cast(Ion: return a == b || std::abs(a - b) <= tolerance * std::abs(a + b) / 2.f || (std::isnan(a) && std::isnan(b)); } -ContinuousFunction * addFunction(ContinuousFunctionStore * store, ContinuousFunction::PlotType type, const char * definition, 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; -} - 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). */ @@ -97,11 +86,11 @@ void assert_cache_stays_valid(ContinuousFunction::PlotType type, const char * de graphRange.setYMax(3.f); CurveViewCursor cursor; - ContinuousFunction * function = addFunction(&functionStore, type, definition, &globalContext); + ContinuousFunction * function = addFunction(definition, type, &functionStore, &globalContext); Coordinate2D origin = function->evaluateXYAtParameter(0.f, &globalContext); cursor.moveTo(0.f, origin.x1(), origin.x2()); - if (type == ContinuousFunction::PlotType::Cartesian) { + 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 { @@ -112,21 +101,21 @@ void assert_cache_stays_valid(ContinuousFunction::PlotType type, const char * de } QUIZ_CASE(graph_caching) { - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "x"); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "x^2"); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x)"); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x)", -1e6f, 2e8f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "sin(x^2)"); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "1/x"); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "1/x", -5e-5f, 5e-5f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Cartesian, "-ℯ^x"); - - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "1", 0.f, 360.f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "θ", 0.f, 360.f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "sin(θ)", 0.f, 360.f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "sin(θ)", 2e-4f, 1e-3f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "cos(5θ)", 0.f, 360.f); - assert_cache_stays_valid(ContinuousFunction::PlotType::Polar, "cos(5θ)", -1e8f, 1e8f); + 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 00000000000..6c4f4ac3cf0 --- /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 00000000000..2f5e9fb5861 --- /dev/null +++ b/apps/graph/test/helper.h @@ -0,0 +1,19 @@ +#ifndef APPS_GRAPH_TEST_HELPER_H +#define APPS_GRAPH_TEST_HELPER_H + +#include "../app.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/range.cpp b/apps/graph/test/range.cpp new file mode 100644 index 00000000000..defda15e721 --- /dev/null +++ b/apps/graph/test/range.cpp @@ -0,0 +1,182 @@ +#include +#include "helper.h" +#include + +using namespace Poincare; +using namespace Shared; + +namespace Graph { + +void assert_range_inclusion_predicate(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length, bool testInclusion) { + /* Test that all points (x, f(x)) for x in xList are either included in the + * range if testIncluded is true, or exculded from the range if testInclusion + * is false. + * If f(x) is not finite, only the presence of x in the horizontal range is + * tested. */ + GlobalContext globalContext; + ContinuousFunctionStore functionStore; + ContinuousFunction * f = addFunction(definition, type, &functionStore, &globalContext); + + float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; + f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); + assert(xMin <= xMax && yMin <= yMax); + + for (size_t i = 0; i < length; i++) { + float x = xList[i]; + float y = f->evaluateXYAtParameter(x, &globalContext).x2(); + assert(std::isfinite(x)); + if (!testInclusion) { + quiz_assert(xMin > x || x > xMax || (std::isfinite(y) && (yMin > y || y > yMax))); + } else { + /* The program can miss the exact abscissa of an extremum, resulting in + * bounds that are close from its value but that do not encompass it. We + * thus test the inclusion of (x, f(x)) along with two neighbouring + * points. */ + float dx = (xMax - xMin) / (Ion::Display::Width / 2); + float y1 = f->evaluateXYAtParameter(x - dx, &globalContext).x2(), y2 = f->evaluateXYAtParameter(x + dx, &globalContext).x2(); + quiz_assert(xMin <= x + && x <= xMax + && (!std::isfinite(y) + || (yMin <= y && y <= yMax) + || (yMin <= y1 && y1 <= yMax) + || (yMin <= y2 && y2 <= yMax))); + } + } +} + +void assert_range_includes_points(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length) { assert_range_inclusion_predicate(definition, type, xList, length, true); } +void assert_range_excludes_points(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length) { assert_range_inclusion_predicate(definition, type, xList, length, false); } + +void assert_range_includes_single_point(const char * definition, ContinuousFunction::PlotType type, float x) { assert_range_includes_points(definition, type, &x, 1); } +void assert_range_excludes_single_point(const char * definition, ContinuousFunction::PlotType type, float x) { assert_range_excludes_points(definition, type, &x, 1); } + +void assert_range_includes_and_bounds_asymptotes(const char * definition, const float * asymptotesXList, size_t length) { + /* The value for delta is the step the old algorithm used to sample a + * cartesian function, causing functions such as 1/x to be evaluated too + * close to 0. */ + constexpr float delta = 1.f / 32.f; + for (size_t i = 0; i < length; i++) { + float x = asymptotesXList[i]; + assert_range_includes_single_point(definition, Cartesian, x); + assert_range_excludes_single_point(definition, Cartesian, x - delta); + assert_range_excludes_single_point(definition, Cartesian, x + delta); + } +} + +void assert_range_shows_enough_periods(const char * definition, float period, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Degree) { + /* The graph should display at least 3 periods, but no more than 7. */ + constexpr int lowNumberOfPeriods = 3, highNumberOfPeriods = 8; + + GlobalContext globalContext; + ContinuousFunctionStore functionStore; + ContinuousFunction * f = addFunction(definition, Cartesian, &functionStore, &globalContext); + + Preferences::sharedPreferences()->setAngleUnit(angleUnit); + if (angleUnit != Preferences::AngleUnit::Degree) { + f->setPlotType(Cartesian, angleUnit, &globalContext); + } + + float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; + f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); + assert(xMin <= xMax && yMin <= yMax); + float numberOfPeriods = (xMax - xMin) / period; + + quiz_assert(lowNumberOfPeriods <= numberOfPeriods && numberOfPeriods <= highNumberOfPeriods); +} + +void assert_range_displays_entire_polar_function(const char * definition) { + GlobalContext globalContext; + ContinuousFunctionStore functionStore; + ContinuousFunction * f = addFunction(definition, Polar, &functionStore, &globalContext); + + float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; + f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); + assert(xMin <= xMax && yMin <= yMax); + + for (float t = f->tMin(); t < f->tMax(); t += f->rangeStep()) { + const Coordinate2D xy = f->evaluateXYAtParameter(t, &globalContext); + if (!std::isfinite(xy.x1()) || !std::isfinite(xy.x2())) { + continue; + } + quiz_assert(xMin <= xy.x1() && xy.x1() <= xMax && yMin <= xy.x2() && xy.x2() <= yMax); + } +} + +QUIZ_CASE(graph_range_polynomes) { + assert_range_includes_single_point("37", Cartesian, 0.f); + assert_range_includes_single_point("x-1", Cartesian, 1.f); + assert_range_includes_single_point("100+x", Cartesian, -100.f); + assert_range_includes_single_point("x^2-20*x+101", Cartesian, 10.f); + + constexpr float array1[2] = {-2.f, 3.f}; + assert_range_includes_points("(x+2)*(x-3)", Cartesian, array1, 2); + + constexpr float array2[2] = {-1.f, 0.5f}; + assert_range_includes_points("2*x^2+x-1", Cartesian, array2, 2); + + constexpr float array3[6] = {-3.f, 3.f, -2.f, 2.f, -1.f, 1.f}; + assert_range_includes_points("(x+3)(x+2)(x+1)(x-1)(x-2)(x-3)", Cartesian, array3, 6); + + constexpr float array4[3] = {0.f, 4.f, 5.f}; + assert_range_includes_points("x^5-5*x^4", Cartesian, array4, 3); +} + +QUIZ_CASE(graph_range_exponential) { + assert_range_includes_single_point("ℯ^x", Cartesian, 0.f); + assert_range_excludes_single_point("ℯ^x", Cartesian, 4.f); + + assert_range_includes_single_point("ℯ^(-x)", Cartesian, 0.f); + assert_range_excludes_single_point("ℯ^(-x)", Cartesian, -4.f); + + constexpr float array1[3] = {1.f, 5.f, 1e-1f}; + assert_range_includes_points("ln(x)", Cartesian, array1, 3); + assert_range_excludes_single_point("ln(x)", Cartesian, 1e-3f); + + constexpr float array2[2] = {-1.f, 3.f}; + assert_range_includes_points("2-ℯ^(-x)", Cartesian, array2, 2); + assert_range_excludes_single_point("2-ℯ^(-x)", Cartesian, 50.f); + assert_range_excludes_single_point("2-ℯ^(-x)", Cartesian, -10.f); + + assert_range_includes_single_point("x^x", Cartesian, 0.5f); + assert_range_excludes_single_point("x^x", Cartesian, -5.f); + assert_range_includes_single_point("x^(1/x)", Cartesian, 1e-3f); +} + +QUIZ_CASE(graph_range_fractions) { + constexpr float array1[1] = {0.f}; + assert_range_includes_and_bounds_asymptotes("1/x", array1, 1); + assert_range_includes_and_bounds_asymptotes("1/x^2", array1, 1); + + constexpr float array2[2] = {-2.f, 2.f}; + assert_range_includes_and_bounds_asymptotes("1/(x^2-4)", array2, 2); + + constexpr float array3[1] = {-100.f}; + assert_range_includes_and_bounds_asymptotes("1/(x+100)", array3, 1); +} + +QUIZ_CASE(graph_range_periodic) { + assert_range_shows_enough_periods("sin(x)", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("sin(x)", 360.f); + assert_range_shows_enough_periods("sin(x)+10", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("sin(x)+10", 360.f); + assert_range_shows_enough_periods("cos(x)", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("cos(x)", 360.f); + assert_range_shows_enough_periods("sin(x)/x", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("sin(x)/x", 360.f); + assert_range_shows_enough_periods("x*sin(x)", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("x*sin(x)", 360.f); + assert_range_shows_enough_periods("ln(sin(x))", 2*M_PI, Preferences::AngleUnit::Radian); + assert_range_shows_enough_periods("ln(sin(x))", 360.f); + constexpr float array1[2] = {-1.f, 1.f}; + assert_range_includes_points("x*sin(1/x)", Cartesian, array1, 2); +} + +QUIZ_CASE(graph_range_polar) { + assert_range_displays_entire_polar_function("1"); + assert_range_displays_entire_polar_function("θ"); + assert_range_displays_entire_polar_function("sin(θ)"); + assert_range_displays_entire_polar_function("sin(10θ)"); + assert_range_displays_entire_polar_function("ln(θ)"); +} + +} From 33f9bb50a3deda166e18fbb08d4ee917e7f97766 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 11:23:49 +0200 Subject: [PATCH 178/560] [apps/continuous_function] Forced X axis display The auto zoom did not display the X axis when drawing positive curves with roots, such as sqrt(x). Change-Id: Ic80fd3207691dc917f7b64c07d7745ab5df1daa3 --- apps/shared/continuous_function.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index bcc0ef62f2e..043e7634394 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -378,7 +378,7 @@ void ContinuousFunction::interestingXAndYRangesForDisplay(float * xMin, float * float firstResult; float dXOld, dXPrev, dXNext, yOld, yPrev, yNext; - /* Look for an point of interest at the center. */ + /* Look for a point of interest at the center. */ const float a = center - minDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + minDistance + FLT_EPSILON; const float ya = evaluateAndRound(this, a, context), yb = evaluateAndRound(this, b, context), yc = evaluateAndRound(this, c, context); if (boundOfIntervalOfDefinitionIsReached(ya, yc) || @@ -502,7 +502,8 @@ void ContinuousFunction::refinedYRangeForDisplay(float xMin, float xMax, float * assert(plotType() == PlotType::Cartesian); assert(yMin && yMax); - constexpr int sampleSize = 80; + constexpr int sampleSize = Ion::Display::Width / 4; + constexpr float boundNegligigbleThreshold = 0.2f; float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; const float step = (xMax - xMin) / (sampleSize - 1); @@ -520,7 +521,7 @@ void ContinuousFunction::refinedYRangeForDisplay(float xMin, float xMax, float * pop++; } } - /* sum/n is the log mean value of the function, which can be interpreted as + /* 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. */ float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; @@ -531,6 +532,14 @@ void ContinuousFunction::refinedYRangeForDisplay(float xMin, float xMax, float * *yMin -= d; *yMax += d; } + /* 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 < boundNegligigbleThreshold) { + *yMin = 0.f; + } else if (*yMax < 0.f && *yMax / *yMin < boundNegligigbleThreshold) { + *yMax = 0.f; + } } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { From a6db9688cd18ae0893105e0b766e6fc531f7dc6c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Sep 2020 12:09:21 +0200 Subject: [PATCH 179/560] [apps/function] Factorize zoom in Function class The new zoom implemented for ContinuousFunction is now factorized inside Function to benefit the Sequence class. The same things is done to code added to Graph::GraphController, which is moved into FunctionGraphController. This removes the reimplementation of several methods, most notably computeYRange, as the implementation for function is general enough to work on sequences. Change-Id: I9b8211354064f46c3fa3dde3191dcb39d627a1d2 --- apps/graph/graph/graph_controller.cpp | 52 ---- apps/graph/graph/graph_controller.h | 4 - apps/sequence/graph/curve_view_range.cpp | 11 - apps/sequence/graph/curve_view_range.h | 1 - apps/sequence/graph/graph_controller.cpp | 62 +---- apps/sequence/graph/graph_controller.h | 3 +- apps/sequence/sequence.h | 1 + apps/shared/continuous_function.cpp | 250 +---------------- apps/shared/continuous_function.h | 6 +- apps/shared/function.cpp | 261 +++++++++++++++++- apps/shared/function.h | 9 + apps/shared/function_graph_controller.cpp | 49 ++++ apps/shared/function_graph_controller.h | 6 + .../interactive_curve_view_range_delegate.h | 4 +- 14 files changed, 339 insertions(+), 380 deletions(-) diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 998c2a20d01..14232d7171c 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -39,58 +39,6 @@ bool GraphController::defaultRangeIsNormalized() const { return functionStore()->displaysNonCartesianFunctions(); } -void GraphController::interestingRanges(InteractiveCurveViewRange * range) const { - privateComputeRanges(true, range); -} - -Shared::InteractiveCurveViewRangeDelegate::Range GraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { - InteractiveCurveViewRange tempRange = *interactiveCurveViewRange; - tempRange.setYAuto(false); - privateComputeRanges(false, &tempRange); - return Shared::InteractiveCurveViewRangeDelegate::Range{.min = tempRange.yMin(), .max = tempRange.yMax()}; -} - -void GraphController::privateComputeRanges(bool tuneXRange, InteractiveCurveViewRange * range) const { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - float resultXMin = tuneXRange ? FLT_MAX : range->xMin(); - float resultXMax = tuneXRange ? -FLT_MAX : range->xMax(); - float resultYMin = FLT_MAX; - float resultYMax = -FLT_MAX; - assert(functionStore()->numberOfActiveFunctions() > 0); - int functionsCount = functionStore()->numberOfActiveFunctions(); - for (int i = 0; i < functionsCount; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context, tuneXRange); - } - - range->setXMin(resultXMin); - range->setXMax(resultXMax); - range->setYMin(resultYMin); - range->setYMax(resultYMax); - /* We can only call this method once the X range has been fully computed. */ - yRangeForCursorFirstMove(range); -} - -void GraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - assert(functionStore()->numberOfActiveFunctions() > 0); - int functionsCount = functionStore()->numberOfActiveFunctions(); - - float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; - float yN, yP; - - for (int i = 0; i < functionsCount; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - if (f->plotType() != ContinuousFunction::PlotType::Cartesian) { - continue; - } - yN = f->evaluateXYAtParameter(range->xCenter() - cursorStep, context).x2(); - yP = f->evaluateXYAtParameter(range->xCenter() + cursorStep, context).x2(); - range->setYMin(std::min(range->yMin(), std::min(yN, yP))); - range->setYMax(std::max(range->yMax(), std::max(yN, yP))); - } -} - void GraphController::selectFunctionWithCursor(int functionIndex) { FunctionGraphController::selectFunctionWithCursor(functionIndex); ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex)); diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 89df7333a8c..347ceabc6f3 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -20,7 +20,6 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont void viewWillAppear() override; bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } - void interestingRanges(Shared::InteractiveCurveViewRange * range) const override; private: int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; @@ -37,9 +36,6 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont 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; - Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; - void privateComputeRanges(bool tuneXRange, Shared::InteractiveCurveViewRange * range) const; - void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; Shared::RoundCursorView m_cursorView; BannerView m_bannerView; diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index a4374164dfe..977c58dcc83 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -75,15 +75,4 @@ void CurveViewRange::setTrigonometric() { 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); -} - } diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index cb50fb50285..83c4da170ac 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -11,7 +11,6 @@ class CurveViewRange : public Shared::InteractiveCurveViewRange { void roundAbscissa() override; void normalize() override; void setTrigonometric() override; - void setDefault() override; private: constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 303d4b001ee..6f98d3ef039 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -2,6 +2,8 @@ #include #include #include "../app.h" +#include +#include #include using namespace Shared; @@ -43,8 +45,7 @@ float GraphController::interestingXMin() const { return nmin; } -float GraphController::interestingXHalfRange() const { - float standardRange = Shared::FunctionGraphController::interestingXHalfRange(); +void GraphController::interestingRanges(InteractiveCurveViewRange * range) const { int nmin = INT_MAX; int nmax = 0; int nbOfActiveModels = functionStore()->numberOfActiveFunctions(); @@ -52,10 +53,13 @@ float GraphController::interestingXHalfRange() const { Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); int firstInterestingIndex = s->initialRank(); nmin = std::min(nmin, firstInterestingIndex); - nmax = std::max(nmax, firstInterestingIndex + static_cast(standardRange)); + nmax = std::max(nmax, firstInterestingIndex + static_cast(k_defaultXHalfRange)); } - assert(nmax - nmin >= standardRange); - return nmax - nmin; + assert(nmax - nmin >= k_defaultXHalfRange); + + range->setXMin(nmin); + range->setYAuto(true); + range->setXMax(nmax); } bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { @@ -101,52 +105,4 @@ double GraphController::defaultCursorT(Ion::Storage::Record record) { return std::fmax(0.0, std::round(Shared::FunctionGraphController::defaultCursorT(record))); } -InteractiveCurveViewRangeDelegate::Range GraphController::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; -} - } diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index 79440af9174..de95bb6478e 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -20,14 +20,13 @@ class GraphController final : public Shared::FunctionGraphController { TermSumController * termSumController() { return &m_termSumController; } // InteractiveCurveViewRangeDelegate float interestingXMin() const override; - float interestingXHalfRange() const override; + void interestingRanges(Shared::InteractiveCurveViewRange * range) 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, int scrollSpeed = 1) override; double defaultCursorT(Ion::Storage::Record record) override; - InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; CurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } SequenceStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } GraphView * functionGraphView() override { return &m_view; } diff --git a/apps/sequence/sequence.h b/apps/sequence/sequence.h index d7f10389077..850cffdb06b 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -148,6 +148,7 @@ friend class SequenceStore; }; template T templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const; + void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRefinedYRangeForDisplay(xMin, xMax, yMin, yMax, context, false); } size_t metaDataSize() const override { return sizeof(RecordDataBuffer); } const Shared::ExpressionModel * model() const override { return &m_definition; } RecordDataBuffer * recordData() const; diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 043e7634394..0d316a5e1a0 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -263,7 +263,7 @@ void ContinuousFunction::setTMax(float tMax) { void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange) const { if (plotType() == PlotType::Cartesian) { - interestingXAndYRangesForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange); + Function::rangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange); } else { fullXYRange(xMin, xMax, yMin, yMax, context); } @@ -294,254 +294,6 @@ void ContinuousFunction::fullXYRange(float * xMin, float * xMax, float * yMin, f *yMax = resultYMax; } -static float evaluateAndRound(const ContinuousFunction * f, float x, Context * context, float precision = 1e-5) { - /* When evaluating sin(x)/x close to zero using the standard sine function, - * one can detect small varitions, 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. */ - return precision * std::round(f->evaluateXYAtParameter(x, context).x2() / precision); -} - - -/* TODO : These three 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); -} - -/* This function 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 isExtremum(const ContinuousFunction * f, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, int iterations = 3) { - 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 = evaluateAndRound(f, xm, context); - bool res = ((y[i] < ym) != (ym < y2)) ? isExtremum(f, x[i], xm, x2, y[i], ym, y2, context, iterations - 1) : std::fabs(ym - y[i]) >= std::fabs(y2 - ym); - if (!res) { - return false; - } - } - return true; -} - -enum class PointOfInterest : uint8_t { - None, - Bound, - Extremum, - Root -}; - -void ContinuousFunction::interestingXAndYRangesForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Context * context, bool tuneXRange) const { - assert(xMin && xMax && yMin && yMax); - assert(plotType() == PlotType::Cartesian); - - /* Constants of the algorithm. */ - constexpr float defaultMaxInterval = 2e5f; - constexpr float minDistance = 1e-2f; - constexpr float asymptoteThreshold = 2e-1f; - constexpr float stepFactor = 1.1f; - constexpr int maxNumberOfPoints = 3; - constexpr float breathingRoom = 0.3f; - constexpr float maxRatioBetweenPoints = 100.f; - - const bool hasIntervalOfDefinition = std::isfinite(tMin()) && std::isfinite(tMax()); - float center, maxDistance; - if (!tuneXRange) { - center = (*xMax + *xMin) / 2.f; - maxDistance = (*xMax - *xMin) / 2.f; - } else if (hasIntervalOfDefinition) { - center = (tMax() + tMin()) / 2.f; - maxDistance = (tMax() - tMin()) / 2.f; - } else { - center = 0.f; - maxDistance = defaultMaxInterval / 2.f; - } - - float resultX[2] = {FLT_MAX, - FLT_MAX}; - float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; - float asymptote[2] = {FLT_MAX, - FLT_MAX}; - int numberOfPoints; - 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 - minDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + minDistance + FLT_EPSILON; - const float ya = evaluateAndRound(this, a, context), yb = evaluateAndRound(this, b, context), yc = evaluateAndRound(this, c, context); - if (boundOfIntervalOfDefinitionIsReached(ya, yc) || - boundOfIntervalOfDefinitionIsReached(yc, ya) || - rootExistsOnInterval(ya, yc) || - extremumExistsOnInterval(ya, yb, yc) || ya == yc) - { - resultX[0] = resultX[1] = center; - if (extremumExistsOnInterval(ya, yb, yc) && isExtremum(this, a, b, c, ya, yb, yc, context)) { - 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 ? - minDistance : minDistance; - dXNext = dXPrev * stepFactor; - yPrev = evaluateAndRound(this, center + dXPrev, context); - yNext = evaluateAndRound(this, center + dXNext, context); - - while(std::fabs(dXPrev) < maxDistance) { - /* Update the slider. */ - dXOld = dXPrev; - dXPrev = dXNext; - dXNext *= stepFactor; - yOld = yPrev; - yPrev = yNext; - yNext = evaluateAndRound(this, center + dXNext, context); - 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) ? PointOfInterest::Extremum : - PointOfInterest::None; - switch (static_cast(variation)) { - /* The fall through 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 (isExtremum(this, center + dXOld, center + dXPrev, center + dXNext, yOld, yPrev, yNext, context)) { - 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 == maxNumberOfPoints) { - /* 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; - 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; - } - break; - default: - const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); - if ((std::fabs(slopeNext) < asymptoteThreshold) && (std::fabs(slopePrev) > 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) < asymptoteThreshold) && (std::fabs(slopePrev) > 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(resultX[i]) > std::fabs(firstResult) * maxRatioBetweenPoints && !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 (tuneXRange) { - /* Cut after horizontal asymptotes. */ - resultX[0] = std::min(resultX[0], asymptote[0]); - resultX[1] = std::max(resultX[1], asymptote[1]); - if (resultX[0] >= resultX[1]) { - /* Fallback to default range. */ - resultX[0] = - Range1D::k_default; - resultX[1] = Range1D::k_default; - } else { - /* Add breathing room around points of interest. */ - float xRange = resultX[1] - resultX[0]; - resultX[0] -= breathingRoom * xRange; - resultX[1] += breathingRoom * xRange; - /* Round to the next integer. */ - resultX[0] = std::floor(resultX[0]); - resultX[1] = std::ceil(resultX[1]); - } - *xMin = std::min(resultX[0], *xMin); - *xMax = std::max(resultX[1], *xMax); - } - *yMin = std::min(resultYMin, *yMin); - *yMax = std::max(resultYMax, *yMax); - - refinedYRangeForDisplay(*xMin, *xMax, yMin, yMax, context); -} - -void ContinuousFunction::refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Context * context) const { - /* This methods computes the Y range that will be displayed for the cartesian - * function, given an X range (xMin, xMax) and bounds yMin and yMax that must - * be inside the Y range.*/ - assert(plotType() == PlotType::Cartesian); - assert(yMin && yMax); - - constexpr int sampleSize = Ion::Display::Width / 4; - constexpr float boundNegligigbleThreshold = 0.2f; - - float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; - const float step = (xMax - xMin) / (sampleSize - 1); - float x, y; - float sum = 0.f; - int pop = 0; - - for (int i = 1; i < sampleSize; i++) { - x = xMin + i * step; - y = privateEvaluateXYAtParameter(x, context).x2(); - sampleYMin = std::min(sampleYMin, y); - sampleYMax = std::max(sampleYMax, y); - if (std::isfinite(y) && std::fabs(y) > FLT_EPSILON) { - 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. */ - float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; - *yMin = std::min(*yMin, std::max(sampleYMin, -bound)); - *yMax = std::max(*yMax, std::min(sampleYMax, bound)); - if (*yMin == *yMax) { - float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; - *yMin -= d; - *yMax += d; - } - /* 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 < boundNegligigbleThreshold) { - *yMin = 0.f; - } else if (*yMax < 0.f && *yMax / *yMin < boundNegligigbleThreshold) { - *yMax = 0.f; - } -} - void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { return (char *)record->value().buffer+sizeof(RecordDataBuffer); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 61674b83499..7d1580c0128 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -70,7 +70,7 @@ class ContinuousFunction : public Function { 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, Poincare::Context * context, bool tuneXRange = true) const; + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange = true) const override; // Extremum Poincare::Coordinate2D nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const; @@ -91,10 +91,8 @@ class ContinuousFunction : public Function { 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; - // Ranges void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; - void interestingXAndYRangesForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange = true) const; - void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const; + void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRefinedYRangeForDisplay(xMin, xMax, yMin, yMax, context, true); } /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 31b1e01e05f..9491c395631 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -1,10 +1,14 @@ #include "function.h" +#include "range_1D.h" #include "poincare_helpers.h" #include "poincare/src/parsing/parser.h" +#include #include -#include -#include +#include #include +#include +#include +#include using namespace Poincare; @@ -74,4 +78,257 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(d.buffer)); } +// Ranges +static float evaluateAndRound(const Function * f, float x, Context * context, float precision = 1e-5) { + /* When evaluating sin(x)/x close to zero using the standard sine function, + * one can detect small varitions, 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. */ + return precision * std::round(f->evaluateXYAtParameter(x, context).x2() / precision); +} + +/* TODO : These three 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); +} + +/* This function 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 isExtremum(const Function * f, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, int iterations = 3) { + 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 = evaluateAndRound(f, xm, context); + bool res = ((y[i] < ym) != (ym < y2)) ? isExtremum(f, x[i], xm, x2, y[i], ym, y2, context, iterations - 1) : std::fabs(ym - y[i]) >= std::fabs(y2 - ym); + if (!res) { + return false; + } + } + return true; +} + +enum class PointOfInterest : uint8_t { + None, + Bound, + Extremum, + Root +}; + +void Function::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Context * context, bool tuneXRange) const { + assert(xMin && xMax && yMin && yMax); + + /* Constants of the algorithm. */ + constexpr float defaultMaxInterval = 2e5f; + constexpr float minDistance = 1e-2f; + constexpr float asymptoteThreshold = 2e-1f; + constexpr float stepFactor = 1.1f; + constexpr int maxNumberOfPoints = 3; + constexpr float breathingRoom = 0.3f; + constexpr float maxRatioBetweenPoints = 100.f; + + const bool hasIntervalOfDefinition = std::isfinite(tMin()) && std::isfinite(tMax()); + float center, maxDistance; + if (!tuneXRange) { + center = (*xMax + *xMin) / 2.f; + maxDistance = (*xMax - *xMin) / 2.f; + } else if (hasIntervalOfDefinition) { + center = (tMax() + tMin()) / 2.f; + maxDistance = (tMax() - tMin()) / 2.f; + } else { + center = 0.f; + maxDistance = defaultMaxInterval / 2.f; + } + + float resultX[2] = {FLT_MAX, - FLT_MAX}; + float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; + float asymptote[2] = {FLT_MAX, - FLT_MAX}; + int numberOfPoints; + 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 - minDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + minDistance + FLT_EPSILON; + const float ya = evaluateAndRound(this, a, context), yb = evaluateAndRound(this, b, context), yc = evaluateAndRound(this, c, context); + if (boundOfIntervalOfDefinitionIsReached(ya, yc) || + boundOfIntervalOfDefinitionIsReached(yc, ya) || + rootExistsOnInterval(ya, yc) || + extremumExistsOnInterval(ya, yb, yc) || ya == yc) + { + resultX[0] = resultX[1] = center; + if (extremumExistsOnInterval(ya, yb, yc) && isExtremum(this, a, b, c, ya, yb, yc, context)) { + 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 ? - minDistance : minDistance; + dXNext = dXPrev * stepFactor; + yPrev = evaluateAndRound(this, center + dXPrev, context); + yNext = evaluateAndRound(this, center + dXNext, context); + + while(std::fabs(dXPrev) < maxDistance) { + /* Update the slider. */ + dXOld = dXPrev; + dXPrev = dXNext; + dXNext *= stepFactor; + yOld = yPrev; + yPrev = yNext; + yNext = evaluateAndRound(this, center + dXNext, context); + 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) ? 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 (isExtremum(this, center + dXOld, center + dXPrev, center + dXNext, yOld, yPrev, yNext, context)) { + 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 == maxNumberOfPoints) { + /* 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; + 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; + } + break; + default: + const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); + if ((std::fabs(slopeNext) < asymptoteThreshold) && (std::fabs(slopePrev) > 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) < asymptoteThreshold) && (std::fabs(slopePrev) > 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(resultX[i]) > std::fabs(firstResult) * maxRatioBetweenPoints && !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 (tuneXRange) { + /* Cut after horizontal asymptotes. */ + resultX[0] = std::min(resultX[0], asymptote[0]); + resultX[1] = std::max(resultX[1], asymptote[1]); + if (resultX[0] >= resultX[1]) { + /* Fallback to default range. */ + resultX[0] = - Range1D::k_default; + resultX[1] = Range1D::k_default; + } else { + /* Add breathing room around points of interest. */ + float xRange = resultX[1] - resultX[0]; + resultX[0] -= breathingRoom * xRange; + resultX[1] += breathingRoom * xRange; + /* Round to the next integer. */ + resultX[0] = std::floor(resultX[0]); + resultX[1] = std::ceil(resultX[1]); + } + *xMin = std::min(resultX[0], *xMin); + *xMax = std::max(resultX[1], *xMax); + } + *yMin = std::min(resultYMin, *yMin); + *yMax = std::max(resultYMax, *yMax); + + refinedYRangeForDisplay(*xMin, *xMax, yMin, yMax, context); +} + +void Function::protectedRefinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Context * context, bool boundByMagnitude) const { + /* 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); + + constexpr int sampleSize = Ion::Display::Width / 4; + constexpr float boundNegligigbleThreshold = 0.2f; + + float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; + const float step = (xMax - xMin) / (sampleSize - 1); + float x, y; + float sum = 0.f; + int pop = 0; + + for (int i = 1; i < sampleSize; i++) { + x = xMin + i * step; + y = evaluateXYAtParameter(x, context).x2(); + if (!std::isfinite(y)) { + continue; + } + sampleYMin = std::min(sampleYMin, y); + sampleYMax = std::max(sampleYMax, y); + if (std::fabs(y) > FLT_EPSILON) { + 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 (boundByMagnitude) { + float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; + sampleYMin = std::max(sampleYMin, - bound); + sampleYMax = std::min(sampleYMax, bound); + } + *yMin = std::min(*yMin, sampleYMin); + *yMax = std::max(*yMax, sampleYMax); + if (*yMin == *yMax) { + float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; + *yMin -= d; + *yMax += d; + } + /* 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 < boundNegligigbleThreshold) { + *yMin = 0.f; + } else if (*yMax < 0.f && *yMax / *yMin < boundNegligigbleThreshold) { + *yMax = 0.f; + } +} + } diff --git a/apps/shared/function.h b/apps/shared/function.h index 7cfad9a11c9..c755a0675f5 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -53,6 +53,10 @@ class Function : public ExpressionModelHandle { 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, Poincare::Context * context, bool tuneXRange = true) const; + protected: /* RecordDataBuffer is the layout of the data buffer of Record * representing a Function. We want to avoid padding which would: @@ -88,7 +92,12 @@ class Function : public ExpressionModelHandle { #endif bool m_active; }; + + void protectedRefinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const; + private: + virtual void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const = 0; + RecordDataBuffer * recordData() const; }; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 949c925052c..65a6653c0a5 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -152,4 +152,53 @@ int FunctionGraphController::numberOfCurves() const { return functionStore()->numberOfActiveFunctions(); } +void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const { + privateComputeRanges(true, range); +} + +Shared::InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { + InteractiveCurveViewRange tempRange = *interactiveCurveViewRange; + tempRange.setYAuto(false); + privateComputeRanges(false, &tempRange); + return Shared::InteractiveCurveViewRangeDelegate::Range{.min = tempRange.yMin(), .max = tempRange.yMax()}; +} + +void FunctionGraphController::privateComputeRanges(bool tuneXRange, InteractiveCurveViewRange * range) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + float resultXMin = tuneXRange ? FLT_MAX : range->xMin(); + float resultXMax = tuneXRange ? -FLT_MAX : range->xMax(); + float resultYMin = FLT_MAX; + float resultYMax = -FLT_MAX; + assert(functionStore()->numberOfActiveFunctions() > 0); + int functionsCount = functionStore()->numberOfActiveFunctions(); + for (int i = 0; i < functionsCount; i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context, tuneXRange); + } + + range->setXMin(resultXMin); + range->setXMax(resultXMax); + range->setYMin(resultYMin); + range->setYMax(resultYMax); + /* We can only call this method once the X range has been fully computed. */ + yRangeForCursorFirstMove(range); +} + +void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + assert(functionStore()->numberOfActiveFunctions() > 0); + int functionsCount = functionStore()->numberOfActiveFunctions(); + + float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; + float yN, yP; + + for (int i = 0; i < functionsCount; i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + yN = f->evaluateXYAtParameter(range->xCenter() - cursorStep, context).x2(); + yP = f->evaluateXYAtParameter(range->xCenter() + cursorStep, context).x2(); + range->setYMin(std::min(range->yMin(), std::min(yN, yP))); + range->setYMax(std::max(range->yMax(), std::max(yN, yP))); + } +} + } diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 245e1876ef6..72717664bd9 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -20,6 +20,8 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu void didBecomeFirstResponder() override; void viewWillAppear() override; + void interestingRanges(Shared::InteractiveCurveViewRange * range) const override; + protected: float cursorTopMarginRatio() override { return 0.068f; } void reloadBannerView() override; @@ -40,6 +42,10 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu void initCursorParameters() override; CurveView * curveView() override; + Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; + void privateComputeRanges(bool tuneXRange, Shared::InteractiveCurveViewRange * range) const; + void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; + private: virtual FunctionGraphView * functionGraphView() = 0; virtual FunctionCurveParameterController * curveParameterController() = 0; diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index 6da8921a2d6..e5c05a9213f 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -9,9 +9,9 @@ class InteractiveCurveViewRange; class InteractiveCurveViewRangeDelegate { public: + static constexpr float k_defaultXHalfRange = 10.0f; bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange); - virtual float interestingXMin() const { return -interestingXHalfRange(); } - virtual float interestingXHalfRange() const { return 10.0f; } + virtual float interestingXMin() const { return -k_defaultXHalfRange; } virtual bool defaultRangeIsNormalized() const { return false; } virtual void interestingRanges(InteractiveCurveViewRange * range) const { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; From ae43ec0cc98c87ed4df7c7bb1f6746a0bcd3cb28 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 24 Aug 2020 15:37:10 +0200 Subject: [PATCH 180/560] [Poincare/layouts] Fixed a crash due to infinite loop Change-Id: Ieb6563dd978c5c363326163b983b40ed13dab913 --- poincare/src/bracket_layout.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/poincare/src/bracket_layout.cpp b/poincare/src/bracket_layout.cpp index fa9c088545e..cd81c0bc5f1 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; From 338968a4938c53b47ef89f523b16d7eefd7ab6a8 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Fri, 4 Sep 2020 10:19:15 +0200 Subject: [PATCH 181/560] improved translations in toolbox and the calculation, python and functions app --- apps/calculation/base.nl.i18n | 8 ++++---- apps/code/catalog.nl.i18n | 24 ++++++++++++------------ apps/graph/base.nl.i18n | 6 +++--- apps/settings/base.nl.i18n | 2 +- apps/toolbox.nl.i18n | 14 +++++++------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index 742f8bdabc3..478e4a3ec56 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -1,9 +1,9 @@ -CalculApp = "Calculatie" -CalculAppCapital = "CALCULATIE" -AdditionalResults = "Bijkomende resultaten" +CalculApp = "Rekenen" +CalculAppCapital = "REKENEN" +AdditionalResults = "Aanvullende resultaten" DecimalBase = "Decimaal" HexadecimalBase = "Hexadecimaal" -BinaryBase = "Binaire" +BinaryBase = "Binair" PrimeFactors = "Priemfactoren" MixedFraction = "Gemengde breuk" EuclideanDivision = "Geheeltallige deling" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 4efbbee5037..8fbcb094a13 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" @@ -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" PythonImportTurtle = "Importeer turtle module" PythonIndex = "Index van de eerste x aanwezigheden" @@ -141,14 +141,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" @@ -157,10 +157,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" @@ -169,7 +169,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" @@ -194,4 +194,4 @@ 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]" diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n index a809429e581..ebc756b54f7 100644 --- a/apps/graph/base.nl.i18n +++ b/apps/graph/base.nl.i18n @@ -17,16 +17,16 @@ NoActivatedFunction = "Geen functie is ingeschakelt" PlotOptions = "Plot opties" 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, _" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 9e44db18873..3c6e2752406 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -1,7 +1,7 @@ SettingsApp = "Instellingen" SettingsAppCapital = "INSTELLINGEN" AngleUnit = "Hoekmaat" -DisplayMode = "Resultaat formaat" +DisplayMode = "Resultaatformaat" EditionMode = "Schrijfformaat" EditionLinear = "Lineair " Edition2D = "Natuurlijk " diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 8ec226df12f..ddfee739fa9 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -121,7 +121,7 @@ Toolbox = "Toolbox" AbsoluteValue = "Absolute waarde" NthRoot = "n-de-machtswortel" BasedLogarithm = "Logaritme met grondtal a" -Calculation = "Calculatie" +Calculation = "Differentiëren en integreren" ComplexNumber = "Complexe getallen" Combinatorics = "Combinatoriek" Arithmetic = "Rekenkunde" @@ -129,8 +129,8 @@ Matrices = "Matrix" NewMatrix = "Nieuwe matrix" Identity = "Eenheidsmatrix van formaat n" Lists = "Lijst" -HyperbolicTrigonometry = "Hyperbolische meetkunde" -Fluctuation = "Voorspellingsinterval" +HyperbolicTrigonometry = "Hyperbolische functies" +Fluctuation = "Statistische intervallen" DerivateNumber = "Afgeleide" Integral = "Integraal" Sum = "Som" @@ -174,9 +174,9 @@ InverseHyperbolicTangent = "Inverse hyperbolische tangens" Prediction95 = "Voorspellingsinterval 95%" Prediction = "Eenvoudig voorspellingsinterval" Confidence = "Betrouwbaarheidsinterval" -RandomAndApproximation = "Willekeurig en benadering" -RandomFloat = "Zwevendekommagetal in [0,1[" -RandomInteger = "Willekeurig geheel getal in [a,b]" +RandomAndApproximation = "Random en afronding" +RandomFloat = "Decimaal getal in [0,1)" +RandomInteger = "Random geheel getal in [a,b]" PrimeFactorDecomposition = "Ontbinden in factoren" NormCDF = "P(X Date: Tue, 4 Aug 2020 10:22:35 +0200 Subject: [PATCH 182/560] [Poincare/IEEE754] Changed methods to use std function Implemented std::nextafter to replace hand made one. Methods next and previous are no longer making the difference between -0 and +0 Change-Id: I42e1a073623b70656d9df954694803840cf3088c --- liba/Makefile | 2 + liba/include/math.h | 4 ++ liba/src/external/openbsd/s_nextafter.c | 71 ++++++++++++++++++++++++ liba/src/external/openbsd/s_nextafterf.c | 62 +++++++++++++++++++++ libaxx/include/cmath | 4 ++ poincare/Makefile | 1 - poincare/include/poincare/ieee754.h | 28 ---------- poincare/src/solver.cpp | 8 ++- poincare/test/ieee754.cpp | 40 ------------- 9 files changed, 149 insertions(+), 71 deletions(-) create mode 100644 liba/src/external/openbsd/s_nextafter.c create mode 100644 liba/src/external/openbsd/s_nextafterf.c delete mode 100644 poincare/test/ieee754.cpp diff --git a/liba/Makefile b/liba/Makefile index 3639ceb91dc..639bf196db7 100644 --- a/liba/Makefile +++ b/liba/Makefile @@ -70,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/math.h b/liba/include/math.h index 3cb95bcb57f..4c05d5bcb35 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/src/external/openbsd/s_nextafter.c b/liba/src/external/openbsd/s_nextafter.c new file mode 100644 index 00000000000..199926cd81b --- /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 00000000000..e5c3d9f9fa5 --- /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 9fdad6b7afd..cc42319e772 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 7ffd237cb59..c289935ccd9 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -178,7 +178,6 @@ tests_src += $(addprefix poincare/test/,\ function_solver.cpp\ helper.cpp\ helpers.cpp\ - ieee754.cpp\ integer.cpp\ layout.cpp\ layout_cursor.cpp\ diff --git a/poincare/include/poincare/ieee754.h b/poincare/include/poincare/ieee754.h index c1b3c0bcc51..accda9d33d4 100644 --- a/poincare/include/poincare/ieee754.h +++ b/poincare/include/poincare/ieee754.h @@ -73,40 +73,12 @@ class IEEE754 final { } 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/src/solver.cpp b/poincare/src/solver.cpp index a5ea72bc3df..37d8ec9760f 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, INFINITY); + } } if (currentAbscissa == max) { - currentAbscissa = IEEE754::previous(max); + if (currentAbscissa != INFINITY) { + currentAbscissa = std::nextafter(currentAbscissa, -INFINITY); + } } if (currentAbscissa == min || currentAbscissa == max) { break; diff --git a/poincare/test/ieee754.cpp b/poincare/test/ieee754.cpp deleted file mode 100644 index 85bdee9d470..00000000000 --- 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); - -} From d917b2e1c91db476c5464f24c8b199969ad65b1c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Sep 2020 11:13:06 +0200 Subject: [PATCH 183/560] [poincare/unit_convert] Fix improper reduction In UnitConvert::shallowBeautify, an orphan expression would be reduced without reaffecting the reduction's result. The reduction not being taken into account would cause problem when handling undef values. ex : Try to calculate i->_min Change-Id: I4e53afa40ba838fed8c2fba9944c9c9ed1dc95b4 --- poincare/src/unit_convert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index 995798561fc..e38bef1fec5 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -94,7 +94,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti } Expression result = Multiplication::Builder(division, unit); replaceWithInPlace(result); - result.shallowReduce(reductionContextWithoutUnits); + result = result.shallowReduce(reductionContextWithoutUnits); return result.shallowBeautify(reductionContextWithoutUnits); } From 70a628f2c8457200b386603c2eb53ad66507849d Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 27 Aug 2020 16:29:43 +0200 Subject: [PATCH 184/560] [apps] Fix Python assert crash on malloc free Change-Id: I48f86422f7d6af5227e2556e6ef531dfad696da4 --- apps/main.cpp | 23 +++++++++++++++++++++++ ion/include/ion.h | 2 ++ python/port/port.cpp | 9 +++++---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/main.cpp b/apps/main.cpp index d0dcf00b7c1..51a95e2b9ab 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,6 +2,19 @@ #include "global_preferences.h" #include +#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 * Ion::stackStart() { + assert(s_stackStart != nullptr); + return s_stackStart; +} + #define DUMMY_MAIN 0 #if DUMMY_MAIN @@ -60,6 +73,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; + s_stackStart = (void *)(&stackTop); +#endif + AppsContainer::sharedAppsContainer()->run(); } diff --git a/ion/include/ion.h b/ion/include/ion.h index 45a8e555912..47c01061449 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -46,6 +46,8 @@ uint32_t random(); // Decompress data void decompress(const uint8_t * src, uint8_t * dst, int srcSize, int dstSize); +// Returns address to the first object that can be allocated on stack +void * stackStart(); // Tells whether the stack pointer is within acceptable bounds bool stackSafe(); diff --git a/python/port/port.cpp b/python/port/port.cpp index 49ecf055c07..c9880aed103 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -122,15 +122,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; From 8fdec305c4651daf12e9909b624a12e91ebad206 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 31 Aug 2020 11:21:55 +0200 Subject: [PATCH 185/560] [apps/graph/test] Fix caching test Change-Id: I536d1422ca6c9ecded66f7876ae019aaf5742005 --- apps/graph/graph/graph_view.cpp | 11 +++++------ apps/graph/test/caching.cpp | 7 +++++-- apps/shared/continuous_function_cache.cpp | 21 ++++++++++++++++++++- apps/shared/continuous_function_cache.h | 13 +++++-------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index ad19d706742..c0d36a18633 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -41,9 +41,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { float tmin = f->tMin(); float tmax = f->tMax(); - float tstep = (tmax-tmin) / k_graphStepDenominator; - - float tCacheMin, tCacheStep; + 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) @@ -53,7 +51,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { tCacheStep = pixelWidth(); } else { tCacheMin = tmin; - tCacheStep = int(k_graphStepDenominator) * tstep / ContinuousFunctionCache::k_numberOfParametricCacheablePoints; + // Compute tCacheStep and tStepNonCartesian + ContinuousFunctionCache::ComputeNonCartesianSteps(&tStepNonCartesian, &tCacheStep, tmax, tmin); } ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep); @@ -80,7 +79,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { } } else if (type == Shared::ContinuousFunction::PlotType::Polar) { // Polar - drawPolarCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { + 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); @@ -88,7 +87,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { } else { // Parametric assert(type == Shared::ContinuousFunction::PlotType::Parametric); - drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { + 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); diff --git a/apps/graph/test/caching.cpp b/apps/graph/test/caching.cpp index 059c7cef344..bef20e35dc3 100644 --- a/apps/graph/test/caching.cpp +++ b/apps/graph/test/caching.cpp @@ -55,8 +55,11 @@ void assert_check_polar_cache_against_function(ContinuousFunction * function, Co float tMin = range->xMin(); float tMax = range->xMax(); - float tStep = ((tMax - tMin) / Graph::GraphView::k_graphStepDenominator) / ContinuousFunctionCache::k_parametricStepFactor; - ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tStep); + + float tStep, tCacheStep; + ContinuousFunctionCache::ComputeNonCartesianSteps(&tStep, &tCacheStep, tMax, tMin); + + ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tCacheStep); // Fill the cache float t; diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index ce43fd31282..ad1e0e98c7c 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -7,7 +7,6 @@ namespace Shared { constexpr int ContinuousFunctionCache::k_sizeOfCache; constexpr float ContinuousFunctionCache::k_cacheHitTolerance; constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches; -constexpr int ContinuousFunctionCache::k_numberOfParametricCacheablePoints; // public void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) { @@ -52,6 +51,26 @@ Poincare::Coordinate2D ContinuousFunctionCache::valueForParameter(const C 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"); + /* 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 * numberOfWholeSteps / numberOfCacheablePoints; +} + // private void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) { for (int i = iInf; i < iSup; i++) { diff --git a/apps/shared/continuous_function_cache.h b/apps/shared/continuous_function_cache.h index 2d035c34c39..4ccf8b015e6 100644 --- a/apps/shared/continuous_function_cache.h +++ b/apps/shared/continuous_function_cache.h @@ -11,16 +11,8 @@ namespace Shared { class ContinuousFunction; class ContinuousFunctionCache { -private: - /* The size of the cache is chosen to optimize the display of cartesian - * functions */ - static constexpr int k_sizeOfCache = Ion::Display::Width; public: static constexpr int k_numberOfAvailableCaches = 2; - /* Parametric and polar functions require caching both x and y values, - * with the same k_sizeOfCache. To cover the entire range, - * k_numberOfParametricCacheablePoints is halved. */ - static constexpr int k_numberOfParametricCacheablePoints = k_sizeOfCache / 2; static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep); @@ -29,7 +21,12 @@ class ContinuousFunctionCache { 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 From e199143412512a1caf2472a7342ecdf240664b8c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Sep 2020 09:40:29 +0200 Subject: [PATCH 186/560] [apps/shared/curve_view] Small binary code size optimization Change-Id: I4b61506edb434cb901a6fb92ef5c145f43c51035 --- apps/shared/curve_view.cpp | 76 +++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 51980252384..fd27981d116 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -664,13 +664,9 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float float rectUp = pixelToFloat(Axis::Vertical, rect.top() + k_externRectMargin); float rectDown = pixelToFloat(Axis::Vertical, rect.bottom() - k_externRectMargin); - if (std::isnan(rectLeft) || std::isnan(rectRight) || std::isnan(rectUp) || std::isnan(rectDown)) { - return drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); - } - - bool rectOverlapsNegativeAbscissaAxis = false; - if (rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f) { - if (rectRight > 0.0f) { + bool rectOverlapsNegativeAbscissaAxis = std::isnan(rectLeft + rectRight + rectUp + rectDown); + if ((rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f) || rectOverlapsNegativeAbscissaAxis) { + if (rectRight > 0.0f || rectOverlapsNegativeAbscissaAxis) { // 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); } @@ -678,63 +674,59 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float rectOverlapsNegativeAbscissaAxis = true; } - Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + const Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + const float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); - float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); + float tMin, tMax; /* Compute angular coordinate of each corners of rect. - * t4 --- t3 + * t3 --- t2 * | | - * t1 --- t2 */ - float t1 = PolarThetaFromCoordinates(rectLeft, rectDown, angleUnit); - float t2 = PolarThetaFromCoordinates(rectRight, rectDown, angleUnit); - float t3 = PolarThetaFromCoordinates(rectRight, rectUp, angleUnit); - float t4 = PolarThetaFromCoordinates(rectLeft, rectUp, angleUnit); - - /* The area between tMin and tMax (modulo π) is the area where something might - * be plotted. */ - float tMin = std::min(std::min(t1,t2),std::min(t3,t4)); - float tMax = std::max(std::max(t1,t2),std::max(t3,t4)); - - if (rectOverlapsNegativeAbscissaAxis) { + * 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 area where something can + * 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 t2 have a negative angle whereas t3 and t4 have a - * positive angle. We ensure here that tMin is t3 (modulo 2π), tMax is t2, + * 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 = t3 - 2 * piInAngleUnit; - tMax = t2; + tMin = t2 - 2 * piInAngleUnit; + tMax = t1; } - /* Draw curve on intervals where (tMin%π,tMax%π) intersects (tStart,tEnd). + /* Draw 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 the intervals : * - [ π/4, π/3 ], [ 2π + π/4, 2π + π/3 ] * - [ -π + π/4, -π + π/3 ], [ π + π/4, π + π/3 ] in case f(θ) is negative*/ - // 1 - Translate tMin and tMax to the left so that no intersection is missed - while (tMax - piInAngleUnit > tStart) { - tMin -= piInAngleUnit; - tMax -= piInAngleUnit; - } + // 1 - Set offset so that tStart <= tMax+thetaOffset < piInAngleUnit+tStart + float thetaOffset = std::ceil((tStart - tMax)/piInAngleUnit) * piInAngleUnit; - // 2 - Translate tMin and tMax to the right until tMin is greater than tEnd - while (tMin < tEnd) { - float t1 = std::max(tMin, tStart); - float t2 = std::min(tMax, tEnd); + // 2 - Increase offset until tMin + thetaOffset > tEnd + 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 (t1 <= t2) { - /* To maximize cache hits, we floor (and ceil) t1 (and t2) to the closest + if (tS <= tE) { + /* To maximize cache hits, we floor (and ceil) tS (and tE) to the closest * cached value. More of the curve is drawn. */ - int i = std::floor((t1 - tStart) / tStep); + int i = std::floor((tS - tStart) / tStep); float tCache1 = tStart + tStep * i; - int j = std::ceil((t2 - tStart) / tStep); + int j = std::ceil((tE - tStart) / tStep); float tCache2 = std::min(tStart + tStep * j, tEnd); drawCurve(ctx, rect, tCache1, tCache2, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } - tMin += piInAngleUnit; - tMax += piInAngleUnit; + thetaOffset += piInAngleUnit; } } From 688394abce1cb4fd1bd288428e18c619a98d4255 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 10 Sep 2020 18:12:40 +0200 Subject: [PATCH 187/560] [ion] Set stack pointer when testing as well Change-Id: Ibeae7961535208c224f9ac51f4cf33e3978665b3 --- apps/main.cpp | 15 +-------------- ion/Makefile | 1 + ion/include/ion.h | 3 ++- ion/src/shared/stack_position.cpp | 22 ++++++++++++++++++++++ quiz/src/runner.cpp | 12 +++++++++--- 5 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 ion/src/shared/stack_position.cpp diff --git a/apps/main.cpp b/apps/main.cpp index 51a95e2b9ab..0ea75e8885e 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,19 +2,6 @@ #include "global_preferences.h" #include -#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 * Ion::stackStart() { - assert(s_stackStart != nullptr); - return s_stackStart; -} - #define DUMMY_MAIN 0 #if DUMMY_MAIN @@ -80,7 +67,7 @@ void ion_main(int argc, const char * const argv[]) { * example, stack pointer could go backward after initialization and allocated * memory pointers could be overlooked during mark procedure. */ volatile int stackTop; - s_stackStart = (void *)(&stackTop); + Ion::setStackStart((void *)(&stackTop)); #endif AppsContainer::sharedAppsContainer()->run(); diff --git a/ion/Makefile b/ion/Makefile index 1c325ebc654..6efea82b872 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -31,6 +31,7 @@ ion_src += $(addprefix ion/src/shared/, \ events_keyboard.cpp \ events_modifier.cpp \ platform_info.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 47c01061449..e01bc03c1fb 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -46,8 +46,9 @@ uint32_t random(); // Decompress data void decompress(const uint8_t * src, uint8_t * dst, int srcSize, int dstSize); -// Returns address to the first object that can be allocated on stack +// 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/src/shared/stack_position.cpp b/ion/src/shared/stack_position.cpp new file mode 100644 index 00000000000..9cb17b2ee76 --- /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/quiz/src/runner.cpp b/quiz/src/runner.cpp index ef4b883c3d9..996a7ec9673 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)) { From 8434da60efff25cd5a0bb5744992b0927d977d8c Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Fri, 3 Jul 2020 17:15:38 +0200 Subject: [PATCH 188/560] [sequence] Changed sequences behavior Sequences can now be defined using specific terms form other sequences : Un = n Vn = u(3) Initial values can also depend on other sequences. Should there be a circular dependency, the sequences concerned will display "undef" as value Change-Id: I6fe1f3ff7b500f35d480ddefb42de729c327432e --- apps/sequence/cache_context.cpp | 36 +++++-- apps/sequence/cache_context.h | 2 + apps/sequence/sequence.cpp | 59 +++++++++-- apps/sequence/sequence.h | 3 +- apps/sequence/sequence_context.cpp | 97 ++++++++++++++----- apps/sequence/sequence_context.h | 72 ++++++++++---- apps/sequence/sequence_store.h | 1 + apps/sequence/test/sequence.cpp | 64 ++++++++++++ .../include/poincare/context_with_parent.h | 1 + poincare/src/parsing/parser.cpp | 10 ++ poincare/src/symbol.cpp | 16 +++ 11 files changed, 301 insertions(+), 60 deletions(-) diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp index be5a834b070..f871eba5aea 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/sequence/cache_context.cpp @@ -1,5 +1,8 @@ #include "cache_context.h" +#include "sequence.h" #include "sequence_store.h" +#include "../shared/poincare_helpers.h" +#include #include using namespace Poincare; @@ -16,13 +19,30 @@ CacheContext::CacheContext(Context * parentContext) : template const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { // [u|v|w](n(+1)?) + // u,v,w are reserved names. They can only be set through the sequence app 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.name()[0] <= SequenceStore::k_sequenceNames[MaxNumberOfSequences-1][0]) { + assert((symbol.name()+1)[0] == '('); Symbol s = const_cast(static_cast(symbol)); - return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); + if (strcmp(symbol.name()+1, "(n)") == 0 || strcmp(symbol.name()+1, "(n+1)") == 0) { + return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); + } else { + Sequence seq = m_sequenceContext->sequenceStore()->sequenceAtIndex(nameIndexForSymbol(s)); + // In case the sequence referenced is not defined, return NAN + if (seq.fullName() == nullptr) { + return Float::Builder(NAN); + } + int numberOfDigits = 1; + constexpr int offset = 2; // 2 = 1 for ('u') + 1 for ('(') + while (symbol.name()[offset+numberOfDigits] != ')') { + numberOfDigits++; + } + // Get the value of k in u(k) and store it in x + Integer integer(symbol.name()+2, numberOfDigits, false); + T x = integer.approximate(); + return Float::Builder(seq.valueAtRank(x, m_sequenceContext)); + } } return ContextWithParent::expressionForSymbolAbstract(symbol, clone); } @@ -34,7 +54,7 @@ void CacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol template int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { - assert(strlen(symbol.name()) == 4 || strlen(symbol.name()) == 6); // [u|v|w](n(+1)?) + assert(strlen(symbol.name()) >= 4); // [u|v|w](n(+1) or k ?) 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'; @@ -42,11 +62,11 @@ int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { 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) + 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) + // (n+1) return 1; } diff --git a/apps/sequence/cache_context.h b/apps/sequence/cache_context.h index 37c6200e526..4bb4f049304 100644 --- a/apps/sequence/cache_context.h +++ b/apps/sequence/cache_context.h @@ -14,10 +14,12 @@ class CacheContext : public Poincare::ContextWithParent { CacheContext(Poincare::Context * parentContext); const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); + void setSequenceContext(SequenceContext * sequenceContext) { m_sequenceContext = sequenceContext;} 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/sequence/sequence.cpp b/apps/sequence/sequence.cpp index 64643ace14e..667afc7d24d 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -118,7 +118,30 @@ T Sequence::templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const { } template -T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { +T Sequence::valueAtRank(int n, SequenceContext *sqctx) { + if (n < 0) { + return NAN; + } + int sequenceIndex = SequenceStore::sequenceIndexForName(fullName()[0]); + if (sqctx->independantSequenceRank(sequenceIndex) > n || sqctx->independantSequenceRank(sequenceIndex) < 0) { + // Reset cache indexes and cache values + sqctx->setIndependantSequenceRank(-1, sequenceIndex); + for (int i = 0 ; i < MaxRecurrenceDepth+1; i++) { + sqctx->setIndependantSequenceValue(NAN, sequenceIndex, i); + } + } + + while(sqctx->independantSequenceRank(sequenceIndex) < n) { + sqctx->stepSequenceAtIndex(sequenceIndex); + } + /* In case we have sqctx->independantSequenceRank(sequenceIndex) = n, we can return the + * value */ + T value = sqctx->independantSequenceValue(sequenceIndex, 0); + return value; +} + +template +T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIndex) const { if (n < initialRank() || n < 0) { return NAN; } @@ -128,11 +151,29 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); CacheContext ctx = CacheContext(sqctx); + ctx.setSequenceContext(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 + * independant, 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 independantRank = sqctx->independantSequenceRank(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->independantSequenceRank(i) != independantRank) { + int offset = independantRank - sqctx->independantSequenceRank(i); + if (offset != 0) { + for (int j = MaxRecurrenceDepth; j >= 0; j--) { + values[i][j] = j-offset < 0 ? NAN : sqctx->independantSequenceValue(i, j-offset); + } + } + } else { + for (int j = 0; j < MaxRecurrenceDepth+1; j++) { + values[i][j] = sequenceIndex != -1 ? sqctx->independantSequenceValue(i, j) : sqctx->valueOfSequenceAtPreviousRank(i, j); + } } } // Hold symbols u(n), u(n+1), v(n), v(n+1), w(n), w(n+1) @@ -157,7 +198,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 +210,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 @@ -293,7 +334,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/sequence/sequence.h index 850cffdb06b..1843acb6f92 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -65,7 +65,8 @@ friend class SequenceStore; 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 diff --git a/apps/sequence/sequence_context.cpp b/apps/sequence/sequence_context.cpp index fbb485f14e7..264738b098f 100644 --- a/apps/sequence/sequence_context.cpp +++ b/apps/sequence/sequence_context.cpp @@ -1,5 +1,7 @@ #include "sequence_context.h" #include "sequence_store.h" +#include "cache_context.h" +#include "../shared/poincare_helpers.h" #include using namespace Poincare; @@ -9,63 +11,104 @@ namespace Sequence { template TemplatedSequenceContext::TemplatedSequenceContext() : - m_rank(-1), - m_values{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}} + m_commonRank(-1), + m_commonRankValues{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}}, + m_independantRanks{-1, -1, -1}, + m_independantRankValues{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}} { } template T TemplatedSequenceContext::valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const { - return m_values[sequenceIndex][rank]; + return m_commonRankValues[sequenceIndex][rank]; } template void TemplatedSequenceContext::resetCache() { - m_rank = -1; + /* 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_independantRankValues are dirty + * and do not use them. */ + m_commonRank = -1; + for (int i = 0; i < MaxNumberOfSequences; i ++) { + m_independantRanks[i] = -1; + } } template bool TemplatedSequenceContext::iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx) { - if (m_rank > n) { - m_rank = -1; + if (m_commonRank > n) { + m_commonRank = -1; } - if (n < 0 || n-m_rank > k_maxRecurrentRank) { + if (n < 0 || n-m_commonRank > k_maxRecurrentRank) { return false; } - while (m_rank++ < n) { - step(sequenceStore, sqctx); + while (m_commonRank < n) { + step(sqctx); } - m_rank--; return true; } template -void TemplatedSequenceContext::step(SequenceStore * sequenceStore, SequenceContext * sqctx) { - /* Shift values */ - for (int i = 0; i < MaxNumberOfSequences; i++) { +void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceIndex) { + // First we increment the rank + bool stepMultipleSequences = sequenceIndex == -1; + if (stepMultipleSequences) { + m_commonRank++; + } else { + setIndependantSequenceRank(independantSequenceRank(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_independantRankValues); + rank = independantSequenceRank(sequenceIndex); + } + + for (int i = start; i < stop; i++) { + T * rowPointer = sequenceArray + i * (MaxRecurrenceDepth + 1); for (int j = MaxRecurrenceDepth; j > 0; j--) { - m_values[i][j] = m_values[i][j-1]; + *(rowPointer + j) = *(rowPointer + j - 1); } - m_values[i][0] = NAN; + *rowPointer= NAN; } - /* Evaluate new u(n) and v(n) */ - // sequences hold u, v, w in this order + // We create an array containing the sequences we want to update Sequence * sequences[MaxNumberOfSequences] = {nullptr, nullptr, nullptr}; - for (int i = 0; i < sequenceStore->numberOfModels(); i++) { + int usedSize = stepMultipleSequences ? MaxNumberOfSequences : 1; + SequenceStore * sequenceStore = sqctx->sequenceStore(); + stop = stepMultipleSequences ? sequenceStore->numberOfModels() : start + 1; + for (int i = start; i < stop; i++) { Sequence * u = sequenceStore->modelForRecord(sequenceStore->recordAtIndex(i)); - sequences[SequenceStore::sequenceIndexForName(u->fullName()[0])] = u->isDefined() ? u : nullptr; + int index = stepMultipleSequences ? SequenceStore::sequenceIndexForName(u->fullName()[0]) : 0; + sequences[index] = 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. + // 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 . */ - 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; + * 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; } } } @@ -73,5 +116,7 @@ void TemplatedSequenceContext::step(SequenceStore * sequenceStore, SequenceCo template class TemplatedSequenceContext; template class TemplatedSequenceContext; +template void * SequenceContext::helper(); +template void * SequenceContext::helper(); } diff --git a/apps/sequence/sequence_context.h b/apps/sequence/sequence_context.h index e4ca2cb4251..702fa8b9f8f 100644 --- a/apps/sequence/sequence_context.h +++ b/apps/sequence/sequence_context.h @@ -20,17 +20,37 @@ class TemplatedSequenceContext { T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const; void resetCache(); bool iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx); + + int independantSequenceRank(int sequenceIndex) { return m_independantRanks[sequenceIndex]; } + void setIndependantSequenceRank(int rank, int sequenceIndex) { m_independantRanks[sequenceIndex] = rank; } + T independantSequenceValue(int sequenceIndex, int depth) { return m_independantRankValues[sequenceIndex][depth]; } + void setIndependantSequenceValue(T value, int sequenceIndex, int depth) { m_independantRankValues[sequenceIndex][depth] = value; } + void step(SequenceContext * sqctx, int sequenceIndex = -1); 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]; + * 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 independant rank. This means that + * (u(3), v(5), w(10)) can be computed at the same time. + * This cache is therefore used for independant steps of sequences + */ + int m_commonRank; + T m_commonRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; + + // Used for fixed computations + int m_independantRanks[MaxNumberOfSequences]; + T m_independantRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; }; class SequenceContext : public Poincare::ContextWithParent { @@ -44,26 +64,44 @@ class SequenceContext : public Poincare::ContextWithParent { * 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); + template T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) { + return static_cast*>(helper())->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); + return static_cast*>(helper())->iterateUntilRank(n, m_sequenceStore, this); + } + + template int independantSequenceRank(int sequenceIndex) { + return static_cast*>(helper())->independantSequenceRank(sequenceIndex); + } + + template void setIndependantSequenceRank(int rank, int sequenceIndex) { + static_cast*>(helper())->setIndependantSequenceRank(rank, sequenceIndex); + } + + template T independantSequenceValue(int sequenceIndex, int depth) { + return static_cast*>(helper())->independantSequenceValue(sequenceIndex, depth); + } + + template void setIndependantSequenceValue(T value, int sequenceIndex, int depth) { + static_cast*>(helper())->setIndependantSequenceValue(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; } }; } diff --git a/apps/sequence/sequence_store.h b/apps/sequence/sequence_store.h index dd6346ee977..1d1013ca5f1 100644 --- a/apps/sequence/sequence_store.h +++ b/apps/sequence/sequence_store.h @@ -27,6 +27,7 @@ class SequenceStore : public Shared::FunctionStore { 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/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 8d423b01048..224e4ef260b 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -336,6 +336,70 @@ 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); } QUIZ_CASE(sequence_sum_evaluation) { diff --git a/poincare/include/poincare/context_with_parent.h b/poincare/include/poincare/context_with_parent.h index 7f628abad5b..4e52ce82838 100644 --- a/poincare/include/poincare/context_with_parent.h +++ b/poincare/include/poincare/context_with_parent.h @@ -15,6 +15,7 @@ class ContextWithParent : 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); } + private: Context * m_parentContext; }; diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index d20b3960b72..36c90252fa1 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -421,6 +421,16 @@ void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Ty constexpr int symbolNameSize = 5; char sym[symbolNameSize] = {name, '(', 'n', ')', 0}; leftHandSide = Symbol::Builder(sym, symbolNameSize); + } else if (rank.type() == ExpressionNode::Type::BasedInteger) { + Integer integer = static_cast(rank).integer(); + int symbolNameSize = 4 + Integer::NumberOfBase10DigitsWithoutSign(integer); + char sym[symbolNameSize]; + sym[0] = name; + sym[1] = '('; + integer.serialize(&sym[2], Integer::NumberOfBase10DigitsWithoutSign(integer)+1); + sym[symbolNameSize-2] = ')'; + sym[symbolNameSize-1] = '\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}; diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index e04e35656a9..369ed5832bf 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -81,6 +81,22 @@ Layout SymbolNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, in VerticalOffsetLayoutNode::Position::Subscript)); } } + /* Checking for u(k) forms with k positive integer. Since the sequence + * parser only handles sequenceIndexes like above and BasedIntegers, there + * is no need to be carefull with how we process m_name. */ + if (m_name[1] == '(') { + int numberOfDigits = 0; + while (m_name[numberOfDigits + 2] != ')') { + if (m_name[numberOfDigits + 2] <= '9' && m_name[numberOfDigits + 2] >= '0') { + numberOfDigits++; + } + } + return HorizontalLayout::Builder( + CodePointLayout::Builder(sequenceName), + VerticalOffsetLayout::Builder( + LayoutHelper::String(m_name+2, numberOfDigits), + VerticalOffsetLayoutNode::Position::Subscript)); + } } } return LayoutHelper::String(m_name, strlen(m_name)); From ed358590cec1d1e480e09aed61fb381c5ff0697d Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 22 Jul 2020 17:22:37 +0200 Subject: [PATCH 189/560] [Sequence] Changed the way sequences are parsed Change-Id: If19b46a317e4f336ac857690827ab08e147ac75a --- apps/sequence/cache_context.cpp | 27 ++--- poincare/Makefile | 1 + poincare/include/poincare/context.h | 1 + poincare/include/poincare/expression.h | 2 + poincare/include/poincare/expression_node.h | 1 + poincare/include/poincare/product.h | 14 +-- poincare/include/poincare/sequence.h | 59 +++++---- poincare/include/poincare/sum.h | 12 +- poincare/include/poincare/sum_and_product.h | 36 ++++++ poincare/include/poincare/symbol_abstract.h | 2 + poincare/include/poincare_nodes.h | 1 + poincare/src/parsing/parser.cpp | 22 +--- poincare/src/product.cpp | 2 +- poincare/src/sequence.cpp | 127 +++++++++++++------- poincare/src/sum.cpp | 2 +- poincare/src/sum_and_product.cpp | 68 +++++++++++ poincare/src/symbol.cpp | 33 ----- poincare/src/symbol_abstract.cpp | 8 ++ 18 files changed, 271 insertions(+), 147 deletions(-) create mode 100644 poincare/include/poincare/sum_and_product.h create mode 100644 poincare/src/sum_and_product.cpp diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp index f871eba5aea..771bda1cb2b 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/sequence/cache_context.cpp @@ -19,29 +19,24 @@ CacheContext::CacheContext(Context * parentContext) : template const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { // [u|v|w](n(+1)?) - // u,v,w are reserved names. They can only be set through the sequence app - if (symbol.type() == ExpressionNode::Type::Symbol - && symbol.name()[0] >= SequenceStore::k_sequenceNames[0][0] - && symbol.name()[0] <= SequenceStore::k_sequenceNames[MaxNumberOfSequences-1][0]) { - assert((symbol.name()+1)[0] == '('); + if (symbol.type() == ExpressionNode::Type::Sequence) { Symbol s = const_cast(static_cast(symbol)); - if (strcmp(symbol.name()+1, "(n)") == 0 || strcmp(symbol.name()+1, "(n+1)") == 0) { - return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); + if (s.childAtIndex(0).type() == ExpressionNode::Type::Symbol) { + return Float::Builder(m_values[nameIndexForSymbol(s)][0]); + } else if (s.childAtIndex(0).type() == ExpressionNode::Type::Addition) { + return Float::Builder(m_values[nameIndexForSymbol(s)][1]); } else { Sequence seq = m_sequenceContext->sequenceStore()->sequenceAtIndex(nameIndexForSymbol(s)); // In case the sequence referenced is not defined, return NAN if (seq.fullName() == nullptr) { return Float::Builder(NAN); } - int numberOfDigits = 1; - constexpr int offset = 2; // 2 = 1 for ('u') + 1 for ('(') - while (symbol.name()[offset+numberOfDigits] != ')') { - numberOfDigits++; + Expression rank = symbol.childAtIndex(0); + if (rank.isNumber()) { + return Float::Builder(seq.valueAtRank(rank.approximateToScalar(this, Poincare::Preferences::ComplexFormat::Cartesian, Poincare::Preferences::AngleUnit::Radian), m_sequenceContext)); + } else { + return Float::Builder(NAN); } - // Get the value of k in u(k) and store it in x - Integer integer(symbol.name()+2, numberOfDigits, false); - T x = integer.approximate(); - return Float::Builder(seq.valueAtRank(x, m_sequenceContext)); } } return ContextWithParent::expressionForSymbolAbstract(symbol, clone); @@ -54,7 +49,7 @@ void CacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol template int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { - assert(strlen(symbol.name()) >= 4); // [u|v|w](n(+1) or k ?) + 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'; diff --git a/poincare/Makefile b/poincare/Makefile index c289935ccd9..790c7fa6ce4 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -137,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 \ diff --git a/poincare/include/poincare/context.h b/poincare/include/poincare/context.h index a36cc1eeaa3..c695d471111 100644 --- a/poincare/include/poincare/context.h +++ b/poincare/include/poincare/context.h @@ -14,6 +14,7 @@ class Context { enum class SymbolAbstractType : uint8_t { None, Function, + Sequence, Symbol }; virtual SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) = 0; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 0b665ff86cf..20a5dee45f9 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -88,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; @@ -96,6 +97,7 @@ class Expression : public TreeHandle { friend class Subtraction; friend class SubtractionNode; friend class Sum; + friend class SumAndProduct; friend class Symbol; friend class SymbolAbstractNode; friend class Tangent; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 2e7e45c10f7..3393d8d267c 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -95,6 +95,7 @@ class ExpressionNode : public TreeNode { Randint, RealPart, Round, + Sequence, SignFunction, SquareRoot, Subtraction, diff --git a/poincare/include/poincare/product.h b/poincare/include/poincare/product.h index 140fe5c4f89..04c5605e8cd 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 @@ class ProductNode final : public SequenceNode { 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 @@ class ProductNode final : public SequenceNode { 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 @@ friend class ProductNode; } -#endif +#endif \ No newline at end of file diff --git a/poincare/include/poincare/sequence.h b/poincare/include/poincare/sequence.h index 0067f3790ab..7d15d26627e 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -1,38 +1,55 @@ #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; + 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, 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; }; -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/sum.h b/poincare/include/poincare/sum.h index af15422031b..84b062a8889 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 @@ class SumNode final : public SequenceNode { 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 @@ class SumNode final : public SequenceNode { 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 00000000000..a74ecc0d20c --- /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, 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 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_abstract.h b/poincare/include/poincare/symbol_abstract.h index 02b2e79a8f8..633ecad08ae 100644 --- a/poincare/include/poincare/symbol_abstract.h +++ b/poincare/include/poincare/symbol_abstract.h @@ -61,6 +61,8 @@ 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; diff --git a/poincare/include/poincare_nodes.h b/poincare/include/poincare_nodes.h index ed7316b8e86..c0d3a853e15 100644 --- a/poincare/include/poincare_nodes.h +++ b/poincare/include/poincare_nodes.h @@ -76,6 +76,7 @@ #include #include #include +#include #include #include #include diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 36c90252fa1..dfd85c16b39 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -417,24 +417,10 @@ void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Ty 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.type() == ExpressionNode::Type::BasedInteger) { - Integer integer = static_cast(rank).integer(); - int symbolNameSize = 4 + Integer::NumberOfBase10DigitsWithoutSign(integer); - char sym[symbolNameSize]; - sym[0] = name; - sym[1] = '('; - integer.serialize(&sym[2], Integer::NumberOfBase10DigitsWithoutSign(integer)+1); - sym[symbolNameSize-2] = ')'; - sym[symbolNameSize-1] = '\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); + } else if (rank.type() == ExpressionNode::Type::BasedInteger + || rank.isIdenticalTo(Symbol::Builder('n')) + || rank.isIdenticalTo(Addition::Builder(Symbol::Builder('n'), BasedInteger::Builder("1")))) { + leftHandSide = Sequence::Builder(&name, 1, rank); } else { m_status = Status::Error; // Unexpected parameter. } diff --git a/poincare/src/product.cpp b/poincare/src/product.cpp index 51759f28148..d168785d9d3 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/sequence.cpp b/poincare/src/sequence.cpp index 9955cacfa4f..28d9f0a96bc 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -1,68 +1,107 @@ #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); +} + 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; + for (char sequenceName = 'u'; sequenceName <= 'w'; sequenceName++) { + if (name()[0] == sequenceName) { + // Checking for the sequence children + if (childAtIndex(0)->type() == Type::Symbol) { + // u(n) + rank = LayoutHelper::String("n", strlen("n")); + } else if (childAtIndex(0)->type() == Type::Addition) { + rank = LayoutHelper::String("n+1", strlen("n+1")); + } else { + assert(childAtIndex(0)->type() == Type::BasedInteger); + rank = static_cast(*childAtIndex(0)).integer().createLayout(); + } + return HorizontalLayout::Builder( + CodePointLayout::Builder(sequenceName), + VerticalOffsetLayout::Builder(rank, VerticalOffsetLayoutNode::Position::Subscript)); + } + } + assert(false); + return LayoutHelper::String(name(), strlen(name())); +} + +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); // This uses Symbol::shallowReduce +} + +Evaluation SequenceNode::approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + return templatedApproximate(context, complexFormat, angleUnit); +} + +Evaluation SequenceNode::approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { + return templatedApproximate(context, complexFormat, angleUnit); } 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) { + if (childAtIndex(0)->approximate((T)1, context, complexFormat, angleUnit).isUndefined()) { 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 = context->expressionForSymbolAbstract(this, false); + if (e.isUninitialized()) { + return Complex::Undefined(); } - return result; + return e.node()->approximate(T(), context, complexFormat, angleUnit); } -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 replaceWithUndefinedInPlace(); + 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; } -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; +// Those two functions will be updated in a comming commit +Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + return *this; +} + +Expression Sequence::deepReplaceReplaceableSymbols(Context * context, bool * didReplace, bool replaceFunctionsOnly, int parameteredAncestorsCount) { + return *this; +} } diff --git a/poincare/src/sum.cpp b/poincare/src/sum.cpp index ceee70feb31..84757093c0c 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 00000000000..8816dc958f0 --- /dev/null +++ b/poincare/src/sum_and_product.cpp @@ -0,0 +1,68 @@ +#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(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) { + return Complex::Undefined(); + } + VariableContext nContext = VariableContext(static_cast(childAtIndex(1))->name(), 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); + result = evaluateWithNextTerm(T(), result, childAtIndex(0)->approximate(T(), &nContext, complexFormat, angleUnit), 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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation SumAndProductNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + +} \ No newline at end of file diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 369ed5832bf..29e0c28afeb 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -66,39 +66,6 @@ int SymbolNode::getVariables(Context * context, isVariableTest isVariable, char 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)); - } - } - /* Checking for u(k) forms with k positive integer. Since the sequence - * parser only handles sequenceIndexes like above and BasedIntegers, there - * is no need to be carefull with how we process m_name. */ - if (m_name[1] == '(') { - int numberOfDigits = 0; - while (m_name[numberOfDigits + 2] != ')') { - if (m_name[numberOfDigits + 2] <= '9' && m_name[numberOfDigits + 2] >= '0') { - numberOfDigits++; - } - } - return HorizontalLayout::Builder( - CodePointLayout::Builder(sequenceName), - VerticalOffsetLayout::Builder( - LayoutHelper::String(m_name+2, numberOfDigits), - VerticalOffsetLayoutNode::Position::Subscript)); - } - } - } return LayoutHelper::String(m_name, strlen(m_name)); } diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index 61c679767ff..541b1ee3f25 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,11 @@ Expression SymbolAbstractNode::setSign(ExpressionNode::Sign s, ReductionContext int SymbolAbstractNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { assert(type() == e->type()); + /* We do not want the sequences to be factorized. Otherwise, u(n) will be + * factorized with u(n+1). */ + if (type() == Type::Sequence) { + return -1; + } return strcmp(name(), static_cast(e)->name()); } @@ -81,6 +87,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); + } From c006ed7b105af413338d25315a66dfa85640afa5 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 23 Jul 2020 17:41:55 +0200 Subject: [PATCH 190/560] [Sequence & Shared] Changed the location of various files Moved sequences files to shared in order to allow the system to compile without the app sequence. Change-Id: Ia8349814a72776244acc41af964059f24e58cff0 --- apps/sequence/Makefile | 6 +----- apps/sequence/app.cpp | 2 +- apps/sequence/app.h | 14 ++++++------- .../graph/curve_parameter_controller.cpp | 1 + apps/sequence/graph/graph_controller.cpp | 7 ++++--- apps/sequence/graph/graph_controller.h | 6 +++--- apps/sequence/graph/graph_view.cpp | 2 +- apps/sequence/graph/graph_view.h | 6 +++--- apps/sequence/graph/term_sum_controller.cpp | 2 +- apps/sequence/list/list_controller.cpp | 21 ++++++++++--------- apps/sequence/list/list_controller.h | 10 ++++----- .../list/list_parameter_controller.cpp | 9 ++++---- .../sequence/list/list_parameter_controller.h | 6 +++--- apps/sequence/list/sequence_toolbox.cpp | 8 +++---- .../list/type_parameter_controller.cpp | 9 ++++---- .../sequence/list/type_parameter_controller.h | 6 +++--- apps/sequence/test/sequence.cpp | 7 +++---- apps/sequence/values/values_controller.cpp | 7 ++++--- apps/sequence/values/values_controller.h | 10 ++++----- apps/shared/Makefile | 16 +++++++++++--- apps/{sequence => shared}/cache_context.cpp | 2 +- apps/{sequence => shared}/cache_context.h | 2 +- apps/shared/global_context.h | 2 ++ apps/{sequence => shared}/sequence.cpp | 3 +-- apps/{sequence => shared}/sequence.h | 2 +- .../{sequence => shared}/sequence_context.cpp | 3 +-- apps/{sequence => shared}/sequence_context.h | 2 +- apps/{sequence => shared}/sequence_store.cpp | 2 +- apps/{sequence => shared}/sequence_store.h | 2 +- .../sequence_title_cell.cpp | 3 +-- .../sequence_title_cell.h | 2 +- apps/shared/test/function_alignement.cpp | 14 ++++++------- poincare/src/sequence.cpp | 2 +- 33 files changed, 103 insertions(+), 93 deletions(-) rename apps/{sequence => shared}/cache_context.cpp (99%) rename apps/{sequence => shared}/cache_context.h (97%) rename apps/{sequence => shared}/sequence.cpp (99%) rename apps/{sequence => shared}/sequence.h (99%) rename apps/{sequence => shared}/sequence_context.cpp (98%) rename apps/{sequence => shared}/sequence_context.h (99%) rename apps/{sequence => shared}/sequence_store.cpp (98%) rename apps/{sequence => shared}/sequence_store.h (99%) rename apps/{sequence => shared}/sequence_title_cell.cpp (98%) rename apps/{sequence => shared}/sequence_title_cell.h (98%) diff --git a/apps/sequence/Makefile b/apps/sequence/Makefile index f36277bca2d..9331234ba5e 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 9ee1e34f3b0..6023049ac03 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -64,7 +64,7 @@ App::App(Snapshot * snapshot) : { } -SequenceContext * App::localContext() { +Shared::SequenceContext * App::localContext() { return &m_sequenceContext; } diff --git a/apps/sequence/app.h b/apps/sequence/app.h index a2e610a2588..349f3afe5bd 100644 --- a/apps/sequence/app.h +++ b/apps/sequence/app.h @@ -2,8 +2,8 @@ #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" @@ -27,12 +27,12 @@ class App : public Shared::FunctionApp { App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; - SequenceStore * functionStore() override { return &m_sequenceStore; } + Shared::SequenceStore * functionStore() override { return &m_sequenceStore; } CurveViewRange * graphRange() { return &m_graphRange; } Shared::Interval * interval() { return &m_interval; } private: void tidy() override; - SequenceStore m_sequenceStore; + Shared::SequenceStore m_sequenceStore; CurveViewRange m_graphRange; Shared::Interval m_interval; }; @@ -46,8 +46,8 @@ class App : public Shared::FunctionApp { // 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(); } + Shared::SequenceContext * localContext() override; + Shared::SequenceStore * functionStore() override { return snapshot()->functionStore(); } Shared::Interval * interval() { return snapshot()->interval(); } ValuesController * valuesController() override { return &m_valuesController; @@ -57,7 +57,7 @@ class App : public Shared::FunctionApp { } 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/graph/curve_parameter_controller.cpp b/apps/sequence/graph/curve_parameter_controller.cpp index cab2f43302b..546672d18d4 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/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 6f98d3ef039..1178d20e7d7 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace Shared; using namespace Poincare; @@ -38,7 +39,7 @@ 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); @@ -50,7 +51,7 @@ void GraphController::interestingRanges(InteractiveCurveViewRange * range) const int nmax = 0; 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)); int firstInterestingIndex = s->initialRank(); nmin = std::min(nmin, firstInterestingIndex); nmax = std::max(nmax, firstInterestingIndex + static_cast(k_defaultXHalfRange)); @@ -95,7 +96,7 @@ bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) { 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 de95bb6478e..76accdee820 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -8,13 +8,13 @@ #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 * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; TermSumController * termSumController() { return &m_termSumController; } @@ -28,7 +28,7 @@ class GraphController final : public Shared::FunctionGraphController { 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 285be74829d..73d18fe5ed1 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 62550ca8683..e7f0fa60cc4 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 71cad17bb19..cd2a47d883d 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 a0eedda1da1..b19d9954770 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 70387a3bbaf..d16af18d9cd 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 @@ class ListController : public Shared::FunctionListController, public Shared::Inp 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 ebdd85905d5..9058083e3c9 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 cc5ddae78b8..911630a0c8e 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 @@ class ListParameterController : public Shared::ListParameterController, public S 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 a25ce35cf8e..d268fcc6489 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 @@ -75,8 +75,8 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu * 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 +84,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) ); } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index cec1c3e04f3..939cefe16fa 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; @@ -55,14 +56,14 @@ void TypeParameterController::didBecomeFirstResponder() { 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 711e0f0731a..f6bf68862fc 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 @@ class TypeParameterController : public ViewController, public SimpleListViewData 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/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 224e4ef260b..c09412eb4a3 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(); diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index 36ae9979097..9afe28fc805 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 e24558e4a1b..80cd98b8f67 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 @@ class ValuesController : public Shared::ValuesController { 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 @@ class ValuesController : public Shared::ValuesController { 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 @@ class ValuesController : public Shared::ValuesController { } 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/shared/Makefile b/apps/shared/Makefile index c90ce297362..9a4ed1975a3 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -16,6 +16,9 @@ app_shared_test_src = $(addprefix apps/shared/,\ 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 \ @@ -26,6 +29,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + cache_context.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ @@ -60,6 +64,10 @@ app_shared_src = $(addprefix apps/shared/,\ range_parameter_controller.cpp \ regular_table_view_data_source.cpp \ round_cursor_view.cpp \ + sequence.cpp\ + sequence_context.cpp\ + sequence_store.cpp\ + sequence_title_cell.cpp \ scrollable_multiple_expressions_view.cpp \ scrollable_two_expressions_cell.cpp \ separable.cpp \ @@ -93,9 +101,11 @@ app_shared_test_src += $(addprefix apps/graph/,\ continuous_function_store.cpp\ ) -app_shared_test_src += $(addprefix apps/sequence/,\ - sequence.cpp\ - sequence_store.cpp\ +app_shared_test_src += $(addprefix apps/shared/,\ + cache_context.cpp \ + sequence.cpp \ + sequence_context.cpp \ + sequence_store.cpp \ ) tests_src += $(addprefix apps/shared/test/,\ diff --git a/apps/sequence/cache_context.cpp b/apps/shared/cache_context.cpp similarity index 99% rename from apps/sequence/cache_context.cpp rename to apps/shared/cache_context.cpp index 771bda1cb2b..760c1531aec 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/shared/cache_context.cpp @@ -7,7 +7,7 @@ using namespace Poincare; -namespace Sequence { +namespace Shared { template CacheContext::CacheContext(Context * parentContext) : diff --git a/apps/sequence/cache_context.h b/apps/shared/cache_context.h similarity index 97% rename from apps/sequence/cache_context.h rename to apps/shared/cache_context.h index 4bb4f049304..7c54c1f029d 100644 --- a/apps/sequence/cache_context.h +++ b/apps/shared/cache_context.h @@ -6,7 +6,7 @@ #include #include "sequence_context.h" -namespace Sequence { +namespace Shared { template class CacheContext : public Poincare::ContextWithParent { diff --git a/apps/shared/global_context.h b/apps/shared/global_context.h index de6b53fc9d6..eff54b0f96d 100644 --- a/apps/shared/global_context.h +++ b/apps/shared/global_context.h @@ -41,6 +41,8 @@ class GlobalContext final : public Poincare::Context { 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/sequence/sequence.cpp b/apps/shared/sequence.cpp similarity index 99% rename from apps/sequence/sequence.cpp rename to apps/shared/sequence.cpp index 667afc7d24d..93042ea3299 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -12,10 +12,9 @@ #include #include -using namespace Shared; using namespace Poincare; -namespace Sequence { +namespace Shared { I18n::Message Sequence::parameterMessageName() const { return I18n::Message::N; diff --git a/apps/sequence/sequence.h b/apps/shared/sequence.h similarity index 99% rename from apps/sequence/sequence.h rename to apps/shared/sequence.h index 1843acb6f92..d1ded7d87b1 100644 --- a/apps/sequence/sequence.h +++ b/apps/shared/sequence.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 diff --git a/apps/sequence/sequence_context.cpp b/apps/shared/sequence_context.cpp similarity index 98% rename from apps/sequence/sequence_context.cpp rename to apps/shared/sequence_context.cpp index 264738b098f..05600441b3b 100644 --- a/apps/sequence/sequence_context.cpp +++ b/apps/shared/sequence_context.cpp @@ -5,9 +5,8 @@ #include using namespace Poincare; -using namespace Shared; -namespace Sequence { +namespace Shared { template TemplatedSequenceContext::TemplatedSequenceContext() : diff --git a/apps/sequence/sequence_context.h b/apps/shared/sequence_context.h similarity index 99% rename from apps/sequence/sequence_context.h rename to apps/shared/sequence_context.h index 702fa8b9f8f..b0adbed67ad 100644 --- a/apps/sequence/sequence_context.h +++ b/apps/shared/sequence_context.h @@ -5,7 +5,7 @@ #include #include -namespace Sequence { +namespace Shared { constexpr static int MaxRecurrenceDepth = 2; static constexpr int MaxNumberOfSequences = 3; 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 18012cfa644..a9e54d71774 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 99% rename from apps/sequence/sequence_store.h rename to apps/shared/sequence_store.h index 1d1013ca5f1..f30cc25e377 100644 --- a/apps/sequence/sequence_store.h +++ b/apps/shared/sequence_store.h @@ -7,7 +7,7 @@ #include #include -namespace Sequence { +namespace Shared { class SequenceStore : public Shared::FunctionStore { public: diff --git a/apps/sequence/sequence_title_cell.cpp b/apps/shared/sequence_title_cell.cpp similarity index 98% rename from apps/sequence/sequence_title_cell.cpp rename to apps/shared/sequence_title_cell.cpp index aeabfec6abb..7bc349c0ddf 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), diff --git a/apps/sequence/sequence_title_cell.h b/apps/shared/sequence_title_cell.h similarity index 98% rename from apps/sequence/sequence_title_cell.h rename to apps/shared/sequence_title_cell.h index db5c7c20913..353a2060a75 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/shared/sequence_title_cell.h @@ -4,7 +4,7 @@ #include "../shared/function_title_cell.h" #include -namespace Sequence { +namespace Shared { class SequenceTitleCell : public Shared::FunctionTitleCell { public: diff --git a/apps/shared/test/function_alignement.cpp b/apps/shared/test/function_alignement.cpp index df9136b26e2..6654ae9eb68 100644 --- a/apps/shared/test/function_alignement.cpp +++ b/apps/shared/test/function_alignement.cpp @@ -1,8 +1,8 @@ #include #include "../continuous_function.h" #include "../../graph/continuous_function_store.h" -#include "../../sequence/sequence.h" -#include "../../sequence/sequence_store.h" +#include "../sequence.h" +#include "../sequence_store.h" namespace Shared { @@ -14,12 +14,12 @@ void interactWithBaseRecordMember(F * fct) { (void) color; // Silence compilation warning about unused variable. } -void interactWithRecordMember(Sequence::SequenceStore * store, Ion::Storage::Record rec) { - Sequence::Sequence * seq = store->modelForRecord(rec); +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::Sequence::Type::SingleRecurrence); - interactWithBaseRecordMember(seq); + seq->setType(Sequence::Type::SingleRecurrence); + interactWithBaseRecordMember(seq); } void interactWithRecordMember(Graph::ContinuousFunctionStore * store, Ion::Storage::Record rec) { @@ -73,7 +73,7 @@ QUIZ_CASE(alignment_handling) { * 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(); testAlignmentHandlingFor(); } diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 28d9f0a96bc..869afa9eb25 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include namespace Poincare { From 3dca515441de001b2ded6f2e8f3ab71eae5d4349 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Fri, 4 Sep 2020 16:20:33 +0200 Subject: [PATCH 191/560] [VariableBox] Added sequences to the variable box It is now possible to call the value of a defined sequence anywhere. Change-Id: I1990e93c50f9add175b7ea274e07004ba63289e5 --- apps/Makefile | 5 +- apps/calculation/app.h | 3 +- apps/calculation/calculation.cpp | 3 +- apps/code/app.h | 3 +- apps/hardware_test/app.h | 3 +- apps/home/app.h | 3 +- apps/math_variable_box_controller.cpp | 26 ++++++++-- apps/math_variable_box_controller.h | 5 +- apps/math_variable_box_empty_controller.cpp | 6 +++ apps/math_variable_box_empty_controller.h | 3 +- apps/on_boarding/app.h | 3 +- apps/probability/app.h | 3 +- apps/regression/app.h | 3 +- apps/sequence/app.cpp | 5 +- apps/sequence/app.h | 7 +-- apps/settings/app.h | 3 +- apps/shared/Makefile | 5 +- apps/shared/cache_context.cpp | 35 ++++++------- apps/shared/cache_context.h | 2 + apps/shared/function.h | 2 +- apps/shared/function_app.h | 3 +- apps/shared/global_context.cpp | 52 ++++++++++++++++---- apps/shared/global_context.h | 11 +++-- apps/shared/sequence.cpp | 54 +++++++++++++++------ apps/shared/sequence.h | 5 +- apps/shared/sequence_context.cpp | 18 +++---- apps/shared/sequence_context.h | 42 ++++++++-------- apps/shared/sequence_store.h | 5 +- apps/shared/sequence_title_cell.h | 4 +- apps/shared/shared_app.cpp | 10 ++++ apps/shared/shared_app.h | 14 ++++++ apps/solver/app.h | 3 +- apps/statistics/app.h | 3 +- apps/usb/app.h | 3 +- apps/variables.de.i18n | 2 + apps/variables.en.i18n | 2 + apps/variables.es.i18n | 2 + apps/variables.fr.i18n | 2 + apps/variables.it.i18n | 2 + apps/variables.nl.i18n | 2 + apps/variables.pt.i18n | 2 + escher/include/escher/app.h | 2 +- poincare/include/poincare/expression.h | 1 + poincare/include/poincare/sequence.h | 1 + poincare/include/poincare/symbol_abstract.h | 1 + poincare/src/parsing/parser.cpp | 8 +-- poincare/src/sequence.cpp | 49 ++++++++++--------- poincare/src/sum_and_product.cpp | 12 ++++- poincare/src/symbol_abstract.cpp | 5 -- poincare/src/variable_context.cpp | 1 - 50 files changed, 297 insertions(+), 152 deletions(-) create mode 100644 apps/shared/shared_app.cpp create mode 100644 apps/shared/shared_app.h diff --git a/apps/Makefile b/apps/Makefile index eb07e5ceb0f..0fc8247938f 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -41,7 +41,10 @@ apps_src += $(addprefix apps/,\ title_bar_view.cpp \ ) -tests_src += apps/exam_mode_configuration_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));) diff --git a/apps/calculation/app.h b/apps/calculation/app.h index 0c188181e64..56cb218541e 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 { @@ -17,7 +18,7 @@ class App : public Shared::ExpressionFieldDelegateApp { I18n::Message upperName() 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/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 2ed67a817ef..b4c898b916a 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) diff --git a/apps/code/app.h b/apps/code/app.h index 57ee6181a37..a9babedce13 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 { @@ -20,7 +21,7 @@ class App : public Shared::InputEventHandlerDelegateApp { I18n::Message upperName() 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/hardware_test/app.h b/apps/hardware_test/app.h index 98b6827a699..5579381ca44 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/home/app.h b/apps/home/app.h index 59325a44014..4d92c556426 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 @@ class App : public ::App { 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/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index 1d6c968ce77..060d44f2ee3 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -9,6 +9,7 @@ #include #include #include +#include using namespace Poincare; using namespace Shared; @@ -76,6 +77,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; } @@ -104,13 +107,19 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in 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.nameWithArgument( + symbolName, + Shared::Sequence::k_maxNameWithArgumentSize + ); } Layout symbolLayout = LayoutHelper::String(symbolName, symbolLength); myCell->setLayout(symbolLayout); @@ -147,7 +156,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]; } @@ -212,7 +221,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 +257,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 8e6052a1149..86f968303a5 100644 --- a/apps/math_variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -27,13 +27,14 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { 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 f5ad187f7fb..d025851ecd1 100644 --- a/apps/math_variable_box_empty_controller.cpp +++ b/apps/math_variable_box_empty_controller.cpp @@ -45,6 +45,12 @@ 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[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 120ff42b6e7..c5066530336 100644 --- a/apps/math_variable_box_empty_controller.h +++ b/apps/math_variable_box_empty_controller.h @@ -13,7 +13,8 @@ class MathVariableBoxEmptyController : public ModalViewEmptyController { 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/app.h b/apps/on_boarding/app.h index d58a0b1adee..d9dbe43a487 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -4,12 +4,13 @@ #include #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; diff --git a/apps/probability/app.h b/apps/probability/app.h index afb9075a711..323087042c4 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)); @@ -29,7 +30,7 @@ class App : public Shared::TextFieldDelegateApp { I18n::Message upperName() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: enum class Page { Distribution, diff --git a/apps/regression/app.h b/apps/regression/app.h index 2c529af77d3..5ebb06f1b52 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 { @@ -19,7 +20,7 @@ class App : public Shared::TextFieldDelegateApp { I18n::Message upperName() 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/sequence/app.cpp b/apps/sequence/app.cpp index 6023049ac03..b5ef4253e6a 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; @@ -20,7 +21,6 @@ const Image * App::Descriptor::icon() { App::Snapshot::Snapshot() : Shared::FunctionApp::Snapshot::Snapshot(), - m_sequenceStore(), m_graphRange() { } @@ -40,13 +40,12 @@ 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::EmbossedGray), m_listHeader(nullptr, &m_listFooter, &m_listController), diff --git a/apps/sequence/app.h b/apps/sequence/app.h index 349f3afe5bd..834c8d3cea0 100644 --- a/apps/sequence/app.h +++ b/apps/sequence/app.h @@ -10,6 +10,8 @@ #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 { @@ -27,12 +29,11 @@ class App : public Shared::FunctionApp { App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; - Shared::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; - Shared::SequenceStore m_sequenceStore; CurveViewRange m_graphRange; Shared::Interval m_interval; }; @@ -47,7 +48,7 @@ class App : public Shared::FunctionApp { // NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; CodePoint XNT() override { return 'n'; } Shared::SequenceContext * localContext() override; - Shared::SequenceStore * functionStore() override { return snapshot()->functionStore(); } + Shared::SequenceStore * functionStore() override { return static_cast(AppsContainer::sharedAppsContainer()->globalContext())->sequenceStore(); } Shared::Interval * interval() { return snapshot()->interval(); } ValuesController * valuesController() override { return &m_valuesController; diff --git a/apps/settings/app.h b/apps/settings/app.h index f395958b572..72967b40b7c 100644 --- a/apps/settings/app.h +++ b/apps/settings/app.h @@ -3,6 +3,7 @@ #include "main_controller.h" #include "../shared/text_field_delegate_app.h" +#include "../shared/shared_app.h" namespace Settings { @@ -14,7 +15,7 @@ class App : public Shared::TextFieldDelegateApp { I18n::Message upperName() override; const Image * icon() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: App * unpack(Container * container) override; Descriptor * descriptor() override; diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 9a4ed1975a3..e12384f37a7 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -64,14 +64,15 @@ app_shared_src = $(addprefix apps/shared/,\ 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_context.cpp\ sequence_store.cpp\ sequence_title_cell.cpp \ - scrollable_multiple_expressions_view.cpp \ - scrollable_two_expressions_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 \ diff --git a/apps/shared/cache_context.cpp b/apps/shared/cache_context.cpp index 760c1531aec..41425558f8f 100644 --- a/apps/shared/cache_context.cpp +++ b/apps/shared/cache_context.cpp @@ -1,8 +1,10 @@ #include "cache_context.h" #include "sequence.h" #include "sequence_store.h" -#include "../shared/poincare_helpers.h" +#include "poincare_helpers.h" #include +#include +#include #include using namespace Poincare; @@ -20,23 +22,22 @@ template const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { // [u|v|w](n(+1)?) if (symbol.type() == ExpressionNode::Type::Sequence) { - Symbol s = const_cast(static_cast(symbol)); - if (s.childAtIndex(0).type() == ExpressionNode::Type::Symbol) { - return Float::Builder(m_values[nameIndexForSymbol(s)][0]); - } else if (s.childAtIndex(0).type() == ExpressionNode::Type::Addition) { - return Float::Builder(m_values[nameIndexForSymbol(s)][1]); + 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); + Sequence * seq = m_sequenceContext->sequenceStore()->modelForRecord(record); + rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(m_nValue)); + T n = PoincareHelpers::ApproximateToScalar(rank, this); + // In case the rank is not int or sequence referenced is not defined, return NAN + if (std::floor(n) == n && seq->fullName() != nullptr) { + return Float::Builder(seq->valueAtRank(n, m_sequenceContext)); } else { - Sequence seq = m_sequenceContext->sequenceStore()->sequenceAtIndex(nameIndexForSymbol(s)); - // In case the sequence referenced is not defined, return NAN - if (seq.fullName() == nullptr) { - return Float::Builder(NAN); - } - Expression rank = symbol.childAtIndex(0); - if (rank.isNumber()) { - return Float::Builder(seq.valueAtRank(rank.approximateToScalar(this, Poincare::Preferences::ComplexFormat::Cartesian, Poincare::Preferences::AngleUnit::Radian), m_sequenceContext)); - } else { - return Float::Builder(NAN); - } + return Float::Builder(NAN); } } return ContextWithParent::expressionForSymbolAbstract(symbol, clone); diff --git a/apps/shared/cache_context.h b/apps/shared/cache_context.h index 7c54c1f029d..7f10a99f940 100644 --- a/apps/shared/cache_context.h +++ b/apps/shared/cache_context.h @@ -14,11 +14,13 @@ class CacheContext : public Poincare::ContextWithParent { CacheContext(Poincare::Context * parentContext); const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); + void setNValue(int n) { m_nValue = n; } void setSequenceContext(SequenceContext * sequenceContext) { m_sequenceContext = sequenceContext;} private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); T m_values[MaxNumberOfSequences][MaxRecurrenceDepth]; + int m_nValue; SequenceContext * m_sequenceContext; }; diff --git a/apps/shared/function.h b/apps/shared/function.h index c755a0675f5..45394002573 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -45,7 +45,7 @@ class Function : public ExpressionModelHandle { // 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; diff --git a/apps/shared/function_app.h b/apps/shared/function_app.h index d15e4c5e831..4763906b5db 100644 --- a/apps/shared/function_app.h +++ b/apps/shared/function_app.h @@ -6,12 +6,13 @@ #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; } diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 8109f94688d..43817af5ba8 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -1,5 +1,6 @@ #include "global_context.h" #include "continuous_function.h" +#include "sequence.h" #include "poincare_helpers.h" #include #include @@ -10,6 +11,13 @@ 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) { Ion::Storage::Record r = SymbolAbstractRecordWithBaseName(symbol.name()); - return ExpressionForSymbolAndRecord(symbol, r); + return ExpressionForSymbolAndRecord(symbol, r, this); } 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) { 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); } const Expression GlobalContext::ExpressionForActualSymbol(Ion::Storage::Record r) { @@ -99,6 +115,22 @@ const Expression GlobalContext::ExpressionForFunction(const SymbolAbstract & sym return e; } +const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx) { + 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); + double rank = PoincareHelpers::ApproximateToScalar(symbol.childAtIndex(0), ctx); + if (std::floor(rank) == rank) { + SequenceContext sqctx(ctx, sequenceStore()); + return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); + } else { + 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 eff54b0f96d..f17aa44fd33 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); @@ -30,19 +31,19 @@ class GlobalContext final : public Poincare::Context { SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override; const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) 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); 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); // 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/sequence.cpp b/apps/shared/sequence.cpp index 93042ea3299..e6302274f15 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -20,6 +20,31 @@ I18n::Message Sequence::parameterMessageName() const { return I18n::Message::N; } +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); + switch (type()) + { + case Type::Explicit: + result += strlcpy(buffer+result, ")", bufferSize-result); + break; + case Type::SingleRecurrence: + result += strlcpy(buffer+result, "+1)", bufferSize-result); + break; + default: + result += strlcpy(buffer+result, "+2)", bufferSize-result); + break; + } + return result; +} + void Sequence::tidy() { m_definition.tidyName(); Function::tidy(); // m_definitionName.tidy() @@ -111,7 +136,7 @@ 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; } @@ -122,20 +147,19 @@ T Sequence::valueAtRank(int n, SequenceContext *sqctx) { return NAN; } int sequenceIndex = SequenceStore::sequenceIndexForName(fullName()[0]); - if (sqctx->independantSequenceRank(sequenceIndex) > n || sqctx->independantSequenceRank(sequenceIndex) < 0) { + if (sqctx->independentSequenceRank(sequenceIndex) > n || sqctx->independentSequenceRank(sequenceIndex) < 0) { // Reset cache indexes and cache values - sqctx->setIndependantSequenceRank(-1, sequenceIndex); + sqctx->setIndependentSequenceRank(-1, sequenceIndex); for (int i = 0 ; i < MaxRecurrenceDepth+1; i++) { - sqctx->setIndependantSequenceValue(NAN, sequenceIndex, i); + sqctx->setIndependentSequenceValue(NAN, sequenceIndex, i); } } - - while(sqctx->independantSequenceRank(sequenceIndex) < n) { + while(sqctx->independentSequenceRank(sequenceIndex) < n) { sqctx->stepSequenceAtIndex(sequenceIndex); } - /* In case we have sqctx->independantSequenceRank(sequenceIndex) = n, we can return the + /* In case we have sqctx->independentSequenceRank(sequenceIndex) = n, we can return the * value */ - T value = sqctx->independantSequenceValue(sequenceIndex, 0); + T value = sqctx->independentSequenceValue(sequenceIndex, 0); return value; } @@ -156,22 +180,22 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn /* 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 - * independant, if the index for u is 3 but the one for v is 5, value will + * 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 independantRank = sqctx->independantSequenceRank(sequenceIndex); + int independentRank = sqctx->independentSequenceRank(sequenceIndex); for (int i = 0; i < MaxNumberOfSequences; i++) { - if (sequenceIndex != -1 && sqctx->independantSequenceRank(i) != independantRank) { - int offset = independantRank - sqctx->independantSequenceRank(i); + 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 ? NAN : sqctx->independantSequenceValue(i, j-offset); + values[i][j] = j-offset < 0 ? NAN : sqctx->independentSequenceValue(i, j-offset); } } } else { for (int j = 0; j < MaxRecurrenceDepth+1; j++) { - values[i][j] = sequenceIndex != -1 ? sqctx->independantSequenceValue(i, j) : sqctx->valueOfSequenceAtPreviousRank(i, j); + values[i][j] = sequenceIndex != -1 ? sqctx->independentSequenceValue(i, j) : sqctx->valueOfCommonRankSequenceAtPreviousRank(i, j); } } } @@ -184,7 +208,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn symbols[i][j] = Symbol::Builder(name[j], strlen(name[j])); } } - + ctx.setNValue(n); switch (type()) { case Type::Explicit: { diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index d1ded7d87b1..016d12e2343 100644 --- a/apps/shared/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" @@ -29,6 +29,7 @@ friend class SequenceStore; } I18n::Message parameterMessageName() const override; CodePoint symbol() const override { return 'n'; } + int nameWithArgument(char * buffer, size_t bufferSize) override; void tidy() override; // MetaData getters Type type() const; diff --git a/apps/shared/sequence_context.cpp b/apps/shared/sequence_context.cpp index 05600441b3b..fc1f48738f3 100644 --- a/apps/shared/sequence_context.cpp +++ b/apps/shared/sequence_context.cpp @@ -12,13 +12,13 @@ template TemplatedSequenceContext::TemplatedSequenceContext() : m_commonRank(-1), m_commonRankValues{{NAN, NAN, NAN}, {NAN, NAN, NAN}, {NAN, NAN, NAN}}, - m_independantRanks{-1, -1, -1}, - m_independantRankValues{{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::valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const { +T TemplatedSequenceContext::valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) const { return m_commonRankValues[sequenceIndex][rank]; } @@ -26,11 +26,11 @@ 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_independantRankValues are dirty + * 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_independantRanks[i] = -1; + m_independentRanks[i] = -1; } } @@ -55,7 +55,7 @@ void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceInde if (stepMultipleSequences) { m_commonRank++; } else { - setIndependantSequenceRank(independantSequenceRank(sequenceIndex) + 1, sequenceIndex); + setIndependentSequenceRank(independentSequenceRank(sequenceIndex) + 1, sequenceIndex); } // Then we shift the values stored in the arrays @@ -69,8 +69,8 @@ void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceInde } else { start = sequenceIndex; stop = sequenceIndex + 1; - sequenceArray = reinterpret_cast(&m_independantRankValues); - rank = independantSequenceRank(sequenceIndex); + sequenceArray = reinterpret_cast(&m_independentRankValues); + rank = independentSequenceRank(sequenceIndex); } for (int i = start; i < stop; i++) { @@ -107,7 +107,7 @@ void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceInde 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; + *pointerToUpdate = sequences[j] ? sequences[j]->template approximateToNextRank(rank, sqctx, sequenceIndex) : NAN; } } } diff --git a/apps/shared/sequence_context.h b/apps/shared/sequence_context.h index b0adbed67ad..83c8d8e9f04 100644 --- a/apps/shared/sequence_context.h +++ b/apps/shared/sequence_context.h @@ -1,5 +1,5 @@ -#ifndef SEQUENCE_SEQUENCE_CONTEXT_H -#define SEQUENCE_SEQUENCE_CONTEXT_H +#ifndef APPS_SHARED_SEQUENCE_CONTEXT_H +#define APPS_SHARED_SEQUENCE_CONTEXT_H #include #include @@ -17,14 +17,14 @@ template class TemplatedSequenceContext { public: TemplatedSequenceContext(); - T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const; + T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) const; void resetCache(); bool iterateUntilRank(int n, SequenceStore * sequenceStore, SequenceContext * sqctx); - int independantSequenceRank(int sequenceIndex) { return m_independantRanks[sequenceIndex]; } - void setIndependantSequenceRank(int rank, int sequenceIndex) { m_independantRanks[sequenceIndex] = rank; } - T independantSequenceValue(int sequenceIndex, int depth) { return m_independantRankValues[sequenceIndex][depth]; } - void setIndependantSequenceValue(T value, int sequenceIndex, int depth) { m_independantRankValues[sequenceIndex][depth] = value; } + 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; @@ -41,16 +41,16 @@ class TemplatedSequenceContext { * 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 independant rank. This means that + * 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 independant steps of sequences + * 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_independantRanks[MaxNumberOfSequences]; - T m_independantRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; + int m_independentRanks[MaxNumberOfSequences]; + T m_independentRankValues[MaxNumberOfSequences][MaxRecurrenceDepth+1]; }; class SequenceContext : public Poincare::ContextWithParent { @@ -64,8 +64,8 @@ class SequenceContext : public Poincare::ContextWithParent { * 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) { - return static_cast*>(helper())->valueOfSequenceAtPreviousRank(sequenceIndex, rank); + template T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) { + return static_cast*>(helper())->valueOfCommonRankSequenceAtPreviousRank(sequenceIndex, rank); } void resetCache() { @@ -77,20 +77,20 @@ class SequenceContext : public Poincare::ContextWithParent { return static_cast*>(helper())->iterateUntilRank(n, m_sequenceStore, this); } - template int independantSequenceRank(int sequenceIndex) { - return static_cast*>(helper())->independantSequenceRank(sequenceIndex); + template int independentSequenceRank(int sequenceIndex) { + return static_cast*>(helper())->independentSequenceRank(sequenceIndex); } - template void setIndependantSequenceRank(int rank, int sequenceIndex) { - static_cast*>(helper())->setIndependantSequenceRank(rank, sequenceIndex); + template void setIndependentSequenceRank(int rank, int sequenceIndex) { + static_cast*>(helper())->setIndependentSequenceRank(rank, sequenceIndex); } - template T independantSequenceValue(int sequenceIndex, int depth) { - return static_cast*>(helper())->independantSequenceValue(sequenceIndex, depth); + template T independentSequenceValue(int sequenceIndex, int depth) { + return static_cast*>(helper())->independentSequenceValue(sequenceIndex, depth); } - template void setIndependantSequenceValue(T value, int sequenceIndex, int depth) { - static_cast*>(helper())->setIndependantSequenceValue(value, sequenceIndex, depth); + template void setIndependentSequenceValue(T value, int sequenceIndex, int depth) { + static_cast*>(helper())->setIndependentSequenceValue(value, sequenceIndex, depth); } template void stepSequenceAtIndex(int sequenceIndex) { diff --git a/apps/shared/sequence_store.h b/apps/shared/sequence_store.h index f30cc25e377..6e0a4818483 100644 --- a/apps/shared/sequence_store.h +++ b/apps/shared/sequence_store.h @@ -1,8 +1,7 @@ -#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 diff --git a/apps/shared/sequence_title_cell.h b/apps/shared/sequence_title_cell.h index 353a2060a75..fe7473db74e 100644 --- a/apps/shared/sequence_title_cell.h +++ b/apps/shared/sequence_title_cell.h @@ -1,5 +1,5 @@ -#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 diff --git a/apps/shared/shared_app.cpp b/apps/shared/shared_app.cpp new file mode 100644 index 00000000000..24c341392af --- /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 00000000000..ebe0a9003c7 --- /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/solver/app.h b/apps/solver/app.h index 54c2aa71e54..c0deb34ff03 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 { @@ -18,7 +19,7 @@ class App : public Shared::ExpressionFieldDelegateApp { I18n::Message upperName() 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/statistics/app.h b/apps/statistics/app.h index 587eb2b9c0a..ef5b4cfa8fb 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 { @@ -19,7 +20,7 @@ class App : public Shared::TextFieldDelegateApp { I18n::Message upperName() 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/usb/app.h b/apps/usb/app.h index 356cf36f1c0..f928e08cce3 100644 --- a/apps/usb/app.h +++ b/apps/usb/app.h @@ -3,6 +3,7 @@ #include "escher/include/escher/app.h" #include "usb_connected_controller.h" +#include "../shared/shared_app.h" namespace USB { @@ -13,7 +14,7 @@ class App : public ::App { I18n::Message name() override; I18n::Message upperName() override; }; - class Snapshot : public ::App::Snapshot { + class Snapshot : public ::SharedApp::Snapshot { public: App * unpack(Container * container) override; Descriptor * descriptor() override; diff --git a/apps/variables.de.i18n b/apps/variables.de.i18n index 220ab368020..777ba78b733 100644 --- a/apps/variables.de.i18n +++ b/apps/variables.de.i18n @@ -1,8 +1,10 @@ Variables = "Variablen" Expressions = "Ausdrücke" Functions = "Funktionen" +Sequences = "Folgen" EmptyExpressionBox0 = "Sie haben keine Variable definiert." EmptyFunctionBox0 = "Sie haben keine Funktion definiert." +EmptySequenceBox0 = "Sie haben keine Folge definiert." EmptyExpressionBox1 = "Um eine Variable zu definieren:" EmptyFunctionBox1 = "Um eine Funktion zu definieren:" EmptyExpressionBox2 = "Erlaubte Zeichen im Namen:" diff --git a/apps/variables.en.i18n b/apps/variables.en.i18n index 5c8aa8ef65d..c4dbff4998b 100644 --- a/apps/variables.en.i18n +++ b/apps/variables.en.i18n @@ -1,8 +1,10 @@ Variables = "Variables" Expressions = "Expressions" Functions = "Functions" +Sequences = "Sequences" EmptyExpressionBox0 = "You have not defined any variables." EmptyFunctionBox0 = "You have not defined any functions." +EmptySequenceBox0 = "You have not defined any sequences." EmptyExpressionBox1 = "To define a variable, type:" EmptyFunctionBox1 = "To define a function, type:" EmptyExpressionBox2 = "The variable name can contain:" diff --git a/apps/variables.es.i18n b/apps/variables.es.i18n index 86124ebe377..7d1f1ec4d86 100644 --- a/apps/variables.es.i18n +++ b/apps/variables.es.i18n @@ -1,8 +1,10 @@ Variables = "Variables" Expressions = "Expresiones" Functions = "Funciones" +Sequences = "Sucesiones" EmptyExpressionBox0 = "Ninguna variable definida." EmptyFunctionBox0 = "Ninguna función definida." +EmptySequenceBox0 = "Ninguna sucesión definida." EmptyExpressionBox1 = "Para definir una, teclear :" EmptyFunctionBox1 = "Para definir una, teclear :" EmptyExpressionBox2 = "El nombre de variable debe" diff --git a/apps/variables.fr.i18n b/apps/variables.fr.i18n index da626f514ad..243c1d205c0 100644 --- a/apps/variables.fr.i18n +++ b/apps/variables.fr.i18n @@ -1,8 +1,10 @@ Variables = "Variables" Expressions = "Expressions" Functions = "Fonctions" +Sequences = "Suites" EmptyExpressionBox0 = "Vous n'avez défini aucune variable." EmptyFunctionBox0 = "Vous n'avez défini aucune fonction." +EmptySequenceBox0 = "Vous n'avez défini aucune suite." EmptyExpressionBox1 = "Pour définir une variable, tapez :" EmptyFunctionBox1 = "Pour définir une fonction, tapez :" EmptyExpressionBox2 = "Le nom de la variable peut" diff --git a/apps/variables.it.i18n b/apps/variables.it.i18n index 8871d3e5168..e32ba5af017 100644 --- a/apps/variables.it.i18n +++ b/apps/variables.it.i18n @@ -1,8 +1,10 @@ Variables = "Variabili" Expressions = "Espressioni" Functions = "Funzioni" +Sequences = "Successioni" EmptyExpressionBox0 = "Non avete definito nessuna variabile." EmptyFunctionBox0 = "Non avete definito nessuna funzione." +EmptySequenceBox0 = "Non avete definito nessuna successione." EmptyExpressionBox1 = "Per definire una variabile, digitare :" EmptyFunctionBox1 = "Per definire una funzione, digitare :" EmptyExpressionBox2 = "Il nome della variabile può" diff --git a/apps/variables.nl.i18n b/apps/variables.nl.i18n index 06305f23987..3f80a107b3c 100644 --- a/apps/variables.nl.i18n +++ b/apps/variables.nl.i18n @@ -1,8 +1,10 @@ Variables = "Variabelen" Expressions = "Uitdrukkingen" Functions = "Functies" +Sequences = "Rijen" EmptyExpressionBox0 = "Je hebt geen variabelen gedefinieerd." EmptyFunctionBox0 = "Je hebt geen functies gedefinieerd." +EmptySequenceBox0 = "Je hebt geen rij gedefinieerd." EmptyExpressionBox1 = "Om een variabele to definiëren, typ:" EmptyFunctionBox1 = "Om een functie to definiëren, typ:" EmptyExpressionBox2 = "De naam van de variabele kan bevatten:" diff --git a/apps/variables.pt.i18n b/apps/variables.pt.i18n index 493e43f018b..5f080d0f989 100644 --- a/apps/variables.pt.i18n +++ b/apps/variables.pt.i18n @@ -1,8 +1,10 @@ Variables = "Variáveis" Expressions = "Expressões" Functions = "Funções" +Sequences = "Sequências" EmptyExpressionBox0 = "Nenhuma variável definida." EmptyFunctionBox0 = "Nenhuma função definida." +EmptySequenceBox0 = "Nenhuma sequência definida." EmptyExpressionBox1 = "Para definir uma, digite :" EmptyFunctionBox1 = "Para definir uma, digite :" EmptyExpressionBox2 = "O nome da variável pode conter:" diff --git a/escher/include/escher/app.h b/escher/include/escher/app.h index 7a5a79d5ebc..b3986884561 100644 --- a/escher/include/escher/app.h +++ b/escher/include/escher/app.h @@ -34,7 +34,7 @@ class App : public Responder { class Snapshot { public: virtual App * unpack(Container * container) = 0; - void pack(App * app); + virtual void pack(App * app); /* reset all instances to their initial values */ virtual void reset() {} virtual void storageDidChangeForRecord(Ion::Storage::Record) {} diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 20a5dee45f9..f94cf79a68c 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -98,6 +98,7 @@ class Expression : public TreeHandle { friend class SubtractionNode; friend class Sum; friend class SumAndProduct; + friend class SumAndProductNode; friend class Symbol; friend class SymbolAbstractNode; friend class Tangent; diff --git a/poincare/include/poincare/sequence.h b/poincare/include/poincare/sequence.h index 7d15d26627e..043d71f4cc6 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -19,6 +19,7 @@ class SequenceNode : public SymbolAbstractNode { 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]; diff --git a/poincare/include/poincare/symbol_abstract.h b/poincare/include/poincare/symbol_abstract.h index 633ecad08ae..e31ba27b4dd 100644 --- a/poincare/include/poincare/symbol_abstract.h +++ b/poincare/include/poincare/symbol_abstract.h @@ -66,6 +66,7 @@ class SymbolAbstract : public Expression { 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/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index dfd85c16b39..eb206b1f6fe 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -416,13 +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.type() == ExpressionNode::Type::BasedInteger - || rank.isIdenticalTo(Symbol::Builder('n')) - || rank.isIdenticalTo(Addition::Builder(Symbol::Builder('n'), BasedInteger::Builder("1")))) { - leftHandSide = Sequence::Builder(&name, 1, rank); + m_status = Status::Error; // Right delimiter missing } else { - m_status = Status::Error; // Unexpected parameter. + leftHandSide = Sequence::Builder(&name, 1, rank); } } } diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 869afa9eb25..83957a0505e 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -20,28 +20,29 @@ Expression SequenceNode::replaceSymbolWithExpression(const SymbolAbstract & symb 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 { assert(name()[0] >= 'u' && name()[0] <= 'w'); - Layout rank; - for (char sequenceName = 'u'; sequenceName <= 'w'; sequenceName++) { - if (name()[0] == sequenceName) { - // Checking for the sequence children - if (childAtIndex(0)->type() == Type::Symbol) { - // u(n) - rank = LayoutHelper::String("n", strlen("n")); - } else if (childAtIndex(0)->type() == Type::Addition) { - rank = LayoutHelper::String("n+1", strlen("n+1")); - } else { - assert(childAtIndex(0)->type() == Type::BasedInteger); - rank = static_cast(*childAtIndex(0)).integer().createLayout(); - } - return HorizontalLayout::Builder( - CodePointLayout::Builder(sequenceName), - VerticalOffsetLayout::Builder(rank, VerticalOffsetLayoutNode::Position::Subscript)); - } - } - assert(false); - return LayoutHelper::String(name(), strlen(name())); + 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 { @@ -49,7 +50,7 @@ int SequenceNode::serialize(char * buffer, int bufferSize, Preferences::PrintFlo } Expression SequenceNode::shallowReduce(ReductionContext reductionContext) { - return Sequence(this).shallowReduce(reductionContext); // This uses Symbol::shallowReduce + return Sequence(this).shallowReduce(reductionContext); } Evaluation SequenceNode::approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { @@ -95,8 +96,12 @@ Expression Sequence::replaceSymbolWithExpression(const SymbolAbstract & symbol, return *this; } -// Those two functions will be updated in a comming commit Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionContext) { + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined + || childAtIndex(0).isUndefined()) + { + return replaceWithUndefinedInPlace(); + } return *this; } diff --git a/poincare/src/sum_and_product.cpp b/poincare/src/sum_and_product.cpp index 8816dc958f0..c5f6cd5b359 100644 --- a/poincare/src/sum_and_product.cpp +++ b/poincare/src/sum_and_product.cpp @@ -32,14 +32,22 @@ Evaluation SumAndProductNode::templatedApproximate(Context * context, Prefere if (std::isnan(start) || std::isnan(end) || start != (int)start || end != (int)end || end - start > k_maxNumberOfSteps) { return Complex::Undefined(); } - VariableContext nContext = VariableContext(static_cast(childAtIndex(1))->name(), context); + SymbolNode * symbol = static_cast(childAtIndex(1)); + VariableContext nContext = VariableContext(symbol->name(), 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); - result = evaluateWithNextTerm(T(), result, childAtIndex(0)->approximate(T(), &nContext, complexFormat, angleUnit), complexFormat); + 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)); + } + result = evaluateWithNextTerm(T(), result, child.node()->approximate(T(), &nContext, complexFormat, angleUnit), complexFormat); if (result.isUndefined()) { return Complex::Undefined(); } diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index 541b1ee3f25..09b87aff048 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -37,11 +37,6 @@ Expression SymbolAbstractNode::setSign(ExpressionNode::Sign s, ReductionContext int SymbolAbstractNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { assert(type() == e->type()); - /* We do not want the sequences to be factorized. Otherwise, u(n) will be - * factorized with u(n+1). */ - if (type() == Type::Sequence) { - return -1; - } return strcmp(name(), static_cast(e)->name()); } diff --git a/poincare/src/variable_context.cpp b/poincare/src/variable_context.cpp index b7899cefee7..75812fab7af 100644 --- a/poincare/src/variable_context.cpp +++ b/poincare/src/variable_context.cpp @@ -2,7 +2,6 @@ #include #include #include - #include namespace Poincare { From 1d71a14d2ce06f7f37e13deb5e2d5e22fea1efb2 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 10 Sep 2020 16:19:14 +0200 Subject: [PATCH 192/560] [Context] Modifying context method signature for sequences This allows sequences to be used in functions by calling u(x) Change-Id: I336e84a19bf9b3dd0f2e435d1aaebda3c9e71ec8 --- apps/regression/regression_context.cpp | 2 +- apps/regression/regression_context.h | 2 +- apps/shared/cache_context.cpp | 2 +- apps/shared/cache_context.h | 2 +- apps/shared/global_context.cpp | 16 ++++++++++------ apps/shared/global_context.h | 6 +++--- apps/statistics/statistics_context.cpp | 2 +- apps/statistics/statistics_context.h | 2 +- poincare/include/poincare/context.h | 3 ++- poincare/include/poincare/context_with_parent.h | 2 +- poincare/include/poincare/empty_context.h | 2 +- poincare/include/poincare/variable_context.h | 2 +- poincare/src/variable_context.cpp | 8 ++++++-- 13 files changed, 30 insertions(+), 21 deletions(-) diff --git a/apps/regression/regression_context.cpp b/apps/regression/regression_context.cpp index 956da75d8b0..ef84c9aadae 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 c9e5fe8ddd3..0ab483acc5d 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/shared/cache_context.cpp b/apps/shared/cache_context.cpp index 41425558f8f..0758ba9b469 100644 --- a/apps/shared/cache_context.cpp +++ b/apps/shared/cache_context.cpp @@ -19,7 +19,7 @@ CacheContext::CacheContext(Context * parentContext) : } template -const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { +const Expression CacheContext::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))); diff --git a/apps/shared/cache_context.h b/apps/shared/cache_context.h index 7f10a99f940..3dabf0cfea7 100644 --- a/apps/shared/cache_context.h +++ b/apps/shared/cache_context.h @@ -12,7 +12,7 @@ template class CacheContext : public Poincare::ContextWithParent { public: CacheContext(Poincare::Context * parentContext); - 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 setValueForSymbol(T value, const Poincare::Symbol & symbol); void setNValue(int n) { m_nValue = n; } void setSequenceContext(SequenceContext * sequenceContext) { m_sequenceContext = sequenceContext;} diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 43817af5ba8..b5b6d6fe258 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -2,6 +2,7 @@ #include "continuous_function.h" #include "sequence.h" #include "poincare_helpers.h" +#include #include #include @@ -56,9 +57,9 @@ Context::SymbolAbstractType GlobalContext::expressionTypeForIdentifier(const cha } } -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, this); + return ExpressionForSymbolAndRecord(symbol, r, this, unknownSymbolValue); } void GlobalContext::setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) { @@ -83,14 +84,14 @@ void GlobalContext::setExpressionForSymbolAbstract(const Expression & expression } } -const Expression GlobalContext::ExpressionForSymbolAndRecord(const SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx) { +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::Sequence); - return ExpressionForSequence(symbol, r, ctx); + return ExpressionForSequence(symbol, r, ctx, unknownSymbolValue); } const Expression GlobalContext::ExpressionForActualSymbol(Ion::Storage::Record r) { @@ -115,14 +116,17 @@ const Expression GlobalContext::ExpressionForFunction(const SymbolAbstract & sym return e; } -const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & symbol, Ion::Storage::Record r, Context * ctx) { +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); - double rank = PoincareHelpers::ApproximateToScalar(symbol.childAtIndex(0), ctx); + constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; + char unknownN[bufferSize]; + Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); + float rank = symbol.childAtIndex(0).approximateWithValueForSymbol(unknownN, unknownSymbolValue, ctx, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); if (std::floor(rank) == rank) { SequenceContext sqctx(ctx, sequenceStore()); return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); diff --git a/apps/shared/global_context.h b/apps/shared/global_context.h index f17aa44fd33..bf71a2c99bc 100644 --- a/apps/shared/global_context.h +++ b/apps/shared/global_context.h @@ -29,15 +29,15 @@ class GlobalContext final : public Poincare::Context { * 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, Context * ctx); + 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); + 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); diff --git a/apps/statistics/statistics_context.cpp b/apps/statistics/statistics_context.cpp index 96a891d4311..f781635a752 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 2ca69c08974..6a37d663d96 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/poincare/include/poincare/context.h b/poincare/include/poincare/context.h index c695d471111..5b23367c7fb 100644 --- a/poincare/include/poincare/context.h +++ b/poincare/include/poincare/context.h @@ -3,6 +3,7 @@ #include #include +#include namespace Poincare { @@ -18,7 +19,7 @@ class Context { 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 4e52ce82838..6b80cd907f1 100644 --- a/poincare/include/poincare/context_with_parent.h +++ b/poincare/include/poincare/context_with_parent.h @@ -14,7 +14,7 @@ class ContextWithParent : public Context { // 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/empty_context.h b/poincare/include/poincare/empty_context.h index f8e7e65c513..a3fe24fa3ab 100644 --- a/poincare/include/poincare/empty_context.h +++ b/poincare/include/poincare/empty_context.h @@ -12,7 +12,7 @@ class EmptyContext : public Context { // 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/variable_context.h b/poincare/include/poincare/variable_context.h index 06d9f7992f5..19b41360754 100644 --- a/poincare/include/poincare/variable_context.h +++ b/poincare/include/poincare/variable_context.h @@ -18,7 +18,7 @@ class VariableContext : public ContextWithParent { // 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/src/variable_context.cpp b/poincare/src/variable_context.cpp index 75812fab7af..f7191a1a258 100644 --- a/poincare/src/variable_context.cpp +++ b/poincare/src/variable_context.cpp @@ -23,14 +23,18 @@ 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) { + unknownSymbolValue = m_value.approximateToScalar(this, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); + } + return ContextWithParent::expressionForSymbolAbstract(symbol, clone, unknownSymbolValue); } } From fa0e8de0a9c015b87a54813b7968ac82d73681ba Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 9 Sep 2020 16:57:52 +0200 Subject: [PATCH 193/560] [VariableBoxController] Changed the way sequences appear Change-Id: I45a1cb01ab9c5aefd298c40050e69850a4cc5c71 --- apps/math_variable_box_controller.cpp | 12 ++++++++++-- poincare/src/variable_context.cpp | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index 060d44f2ee3..f1032a5b5f5 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -4,12 +4,15 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include "global_preferences.h" using namespace Poincare; using namespace Shared; @@ -104,6 +107,7 @@ 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); @@ -120,8 +124,12 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in 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(); @@ -201,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); diff --git a/poincare/src/variable_context.cpp b/poincare/src/variable_context.cpp index f7191a1a258..92d54c3644a 100644 --- a/poincare/src/variable_context.cpp +++ b/poincare/src/variable_context.cpp @@ -32,6 +32,7 @@ const Expression VariableContext::expressionForSymbolAbstract(const SymbolAbstra } else { 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()); } return ContextWithParent::expressionForSymbolAbstract(symbol, clone, unknownSymbolValue); From b8544e37083955184a3b0af1e0f402aab3324056 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 12 Aug 2020 12:27:42 +0200 Subject: [PATCH 194/560] [apps/shared/text_field_delegate_app] Refuse Equal The Equal sign now triggers a syntax error in all apps but Solver. This make its behaviour symmetrical with that of < and >. Change-Id: Ia886c547a315d5627a69f3f2acac2cbce0e202c2 --- apps/calculation/app.cpp | 2 +- apps/shared/function_app.cpp | 10 +++------- apps/shared/text_field_delegate_app.cpp | 8 +------- apps/solver/app.cpp | 6 ++++++ apps/solver/app.h | 3 +++ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index cb8990b2350..8cc0e3c9275 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -68,7 +68,7 @@ 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) { diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 0b7a95454b5..b9de91e5a91 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -37,13 +37,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/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index ece157f9bd9..e66a874224d 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/solver/app.cpp b/apps/solver/app.cpp index bce24a51816..7718fcad111 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -64,4 +64,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 c0deb34ff03..f5a002ce8e1 100644 --- a/apps/solver/app.h +++ b/apps/solver/app.h @@ -46,6 +46,9 @@ class App : public Shared::ExpressionFieldDelegateApp { void willBecomeInactive() override; TELEMETRY_ID("Solver"); private: + // TextFieldDelegateApp + bool isAcceptableExpression(const Poincare::Expression expression) override; + App(Snapshot * snapshot); SolutionsController m_solutionsController; IntervalController m_intervalController; From 51002066e9c8ff79c85a56bbfb41463cd69b1926 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 14 Sep 2020 12:41:29 +0200 Subject: [PATCH 195/560] [sequence] Fixed crash due to undefined sequence Change-Id: Ie67ff4aa9a53eb8b04535e3b61737e11c9049316 --- apps/sequence/app.cpp | 7 +++++++ apps/sequence/app.h | 1 + apps/shared/cache_context.cpp | 14 ++++++++------ apps/shared/expression_model.h | 1 + apps/shared/global_context.cpp | 2 +- apps/shared/sequence.h | 1 + apps/shared/sequence_context.cpp | 11 ++++++++--- 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index b5ef4253e6a..3f01d17bc98 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -67,4 +67,11 @@ 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 834c8d3cea0..be61dd8cbf0 100644 --- a/apps/sequence/app.h +++ b/apps/sequence/app.h @@ -47,6 +47,7 @@ class App : public Shared::FunctionApp { // TODO: override variableBoxForInputEventHandler to lock sequence in the variable box once they appear there // NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; CodePoint XNT() override { return 'n'; } + 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(); } diff --git a/apps/shared/cache_context.cpp b/apps/shared/cache_context.cpp index 0758ba9b469..3fcf76f4393 100644 --- a/apps/shared/cache_context.cpp +++ b/apps/shared/cache_context.cpp @@ -30,12 +30,14 @@ const Expression CacheContext::expressionForSymbolAbstract(const Poincare::Sy return Float::Builder(m_values[index][1]); } Ion::Storage::Record record = m_sequenceContext->sequenceStore()->recordAtIndex(index); - Sequence * seq = m_sequenceContext->sequenceStore()->modelForRecord(record); - rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(m_nValue)); - T n = PoincareHelpers::ApproximateToScalar(rank, this); - // In case the rank is not int or sequence referenced is not defined, return NAN - if (std::floor(n) == n && seq->fullName() != nullptr) { - return Float::Builder(seq->valueAtRank(n, m_sequenceContext)); + if (!record.isNull()) { + Sequence * seq = m_sequenceContext->sequenceStore()->modelForRecord(record); + rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(m_nValue)); + T n = PoincareHelpers::ApproximateToScalar(rank, this); + // In case the rank is not int or sequence referenced is not defined, return NAN + if (std::floor(n) == n && seq->fullName() != nullptr) { + return Float::Builder(seq->valueAtRank(n, m_sequenceContext)); + } } else { return Float::Builder(NAN); } diff --git a/apps/shared/expression_model.h b/apps/shared/expression_model.h index b57ae5ac287..6fe6501a3b6 100644 --- a/apps/shared/expression_model.h +++ b/apps/shared/expression_model.h @@ -22,6 +22,7 @@ class ExpressionModel { 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/global_context.cpp b/apps/shared/global_context.cpp index b5b6d6fe258..654c7737bfe 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -127,7 +127,7 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym char unknownN[bufferSize]; Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); float rank = symbol.childAtIndex(0).approximateWithValueForSymbol(unknownN, unknownSymbolValue, ctx, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); - if (std::floor(rank) == rank) { + if (std::floor(rank) == rank && seq.hasValidExpression()) { SequenceContext sqctx(ctx, sequenceStore()); return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); } else { diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index 016d12e2343..dffbc45b822 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -59,6 +59,7 @@ friend class SequenceStore; Poincare::Layout nameLayout(); bool isDefined() override; bool isEmpty() override; + bool hasValidExpression() { return m_definition.hasValidExpression(); } // Approximation Poincare::Coordinate2D evaluateXYAtParameter(float x, Poincare::Context * context) const override { return Poincare::Coordinate2D(x, templatedApproximateAtAbscissa(x, static_cast(context))); diff --git a/apps/shared/sequence_context.cpp b/apps/shared/sequence_context.cpp index fc1f48738f3..528a2c3595f 100644 --- a/apps/shared/sequence_context.cpp +++ b/apps/shared/sequence_context.cpp @@ -87,9 +87,14 @@ void TemplatedSequenceContext::step(SequenceContext * sqctx, int sequenceInde SequenceStore * sequenceStore = sqctx->sequenceStore(); stop = stepMultipleSequences ? sequenceStore->numberOfModels() : start + 1; for (int i = start; i < stop; i++) { - Sequence * u = sequenceStore->modelForRecord(sequenceStore->recordAtIndex(i)); - int index = stepMultipleSequences ? SequenceStore::sequenceIndexForName(u->fullName()[0]) : 0; - sequences[index] = u->isDefined() ? u : nullptr; + 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 From b92c819ea2565f1d6e1a62e12fbe2c1644f5e3e3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 3 Sep 2020 17:19:59 +0200 Subject: [PATCH 196/560] [escher/src/text_area] Add char limit in text_area line Change-Id: I9284936f0202d788edc785aa3f7c82b45ab34cf5 --- escher/include/escher/text_area.h | 1 + escher/src/text_area.cpp | 103 ++++++++++++++++++++++--- escher/src/text_input.cpp | 8 +- ion/include/ion/unicode/utf8_helper.h | 3 + ion/src/shared/unicode/utf8_helper.cpp | 14 ++++ kandinsky/src/context_text.cpp | 1 + kandinsky/src/font.cpp | 1 + kandinsky/src/point.cpp | 4 + 8 files changed, 122 insertions(+), 13 deletions(-) diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index ffc65b4bcce..e8aa34aa514 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -138,6 +138,7 @@ class TextArea : public TextInput, public InputEventHandler { 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/src/text_area.cpp b/escher/src/text_area.cpp index bbddcb70046..72a209a4610 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -47,24 +47,79 @@ 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; } } - // Check the text will not overflow the max number of lines - if (contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') >= k_maxLines) { + /* 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; } @@ -89,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 @@ -268,9 +323,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; @@ -288,6 +350,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) { @@ -551,6 +631,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(), diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index ccf943ddd75..da741f62c9d 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() { @@ -201,7 +203,7 @@ bool TextInput::moveCursorLeft(int step) { } i++; } - // true is returned if there was at least one successful cursor mouvement + // true is returned if there was at least one successful cursor movement return (i > 1 || canMove); } @@ -218,7 +220,7 @@ bool TextInput::moveCursorRight(int step) { } i++; } - // true is returned if there was at least one successful cursor mouvement + // true is returned if there was at least one successful cursor movement return (i > 1 || canMove); } diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 17ff9385f6b..96404b863e1 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -89,6 +89,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/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index d15c897c60a..e3ae93fba63 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -407,4 +407,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/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index 8f36b6247e3..c1fd73a6e8d 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) { diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index ce4fb81fd55..cae40c78983 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -30,6 +30,7 @@ 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; } diff --git a/kandinsky/src/point.cpp b/kandinsky/src/point.cpp index 796232558e9..f6a096de43a 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()); } From bad21f0ab552f85823b9f7b02fc09e842d84a953 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 13 Aug 2020 15:16:49 +0200 Subject: [PATCH 197/560] [poincare/integer] Fix negative mixed fraction Mixed fractions for negative rational numbers are now computed as the opposite of their opposite's mixed fraction, as is conventionnal. ex : -9/5 is now decomposed as -1-4/5 instead of -2+1/5 Change-Id: I6df3dce585ccadd1bcd7cc562576995face98f9c --- poincare/src/integer.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index b85150974d1..fc67dda6609 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))); } From 6e9a5a010f8df4b5b15f80f759b75085dd5d5e63 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 25 Aug 2020 10:30:29 +0200 Subject: [PATCH 198/560] [poincare/integer] Add tests on euclidean division This commit also adds tests on mixed fractions. Change-Id: I0fee88eb00febccaa9445230f8f2bbb92cd1ba98 --- poincare/test/integer.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/poincare/test/integer.cpp b/poincare/test/integer.cpp index 0a093c132b9..f653b887c49 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"); +} From 913c81a0d337fc0448b5d5ef5cb27de75133dc07 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 25 Jun 2020 15:15:26 +0200 Subject: [PATCH 199/560] [solver] Modified the way to solve some equations The solutions of equations that need numerical approximations to be solved are now computed base on the undeveloped equation (instead of fully the expended one used to identify polynomials) This allow (x-10)^7=0 to yield x=10 as result (9.95 before) Change-Id: Ia8acbe57a9cfebf0b5016e9c896d21c8ddac7a64 --- apps/solver/equation.cpp | 82 +++++++++++----------------- apps/solver/equation.h | 7 +-- apps/solver/equation_store.cpp | 59 +++++++++++++------- apps/solver/equation_store.h | 8 ++- apps/solver/interval_controller.cpp | 5 +- apps/solver/interval_controller.h | 2 + apps/solver/list_controller.cpp | 4 +- apps/solver/solutions_controller.cpp | 5 +- apps/solver/solutions_controller.h | 3 - apps/solver/test/equation_store.cpp | 4 ++ poincare/include/poincare/equal.h | 2 +- poincare/src/equal.cpp | 8 +-- 12 files changed, 96 insertions(+), 93 deletions(-) diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index a9b14f1941a..e0a4aada541 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -19,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; +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(); + } + 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; - } + // Reduce the expression + Expression expressionRed = expressionInputWithoutFunctions.clone(); + PoincareHelpers::Simplify(&expressionRed, contextToUse, reductionTarget); - 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()); - } 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; - } - } + // simplify might return an uninitialized Expression if interrupted + if (expressionRed.isUninitialized()) { + expressionRed = expressionInputWithoutFunctions; } - return *returnedExpression; -} - -void Equation::Model::tidy() const { - ExpressionModel::tidy(); - // Free the pool of the m_standardForm - m_standardFormWithReplacedFunctionsAndSymbols = Expression(); - m_standardFormWithReplacedFunctionsButNotSymbols = Expression(); + 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 549ab6998d0..a43db56c822 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -11,19 +11,16 @@ class Equation : public Shared::ExpressionModelHandle { 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 8517bef6c46..1523ff4f0b9 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 = !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(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(), 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,7 +230,7 @@ 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, diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h index 5cc28c6b180..362392091dc 100644 --- a/apps/solver/equation_store.h +++ b/apps/solver/equation_store.h @@ -74,14 +74,18 @@ class EquationStore : public Shared::ExpressionModelStore { /* 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 01655199e52..9086e4686cc 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(), KDColorWhite, Palette::SubTab, Palette::SubTab); } diff --git a/apps/solver/interval_controller.h b/apps/solver/interval_controller.h index 5ece5e3617e..6e273284d32 100644 --- a/apps/solver/interval_controller.h +++ b/apps/solver/interval_controller.h @@ -15,6 +15,7 @@ class IntervalController : public Shared::FloatParameterController { 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 @@ class IntervalController : public Shared::FloatParameterController { 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 774ac498ce1..0c84e55553b 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(), KDColorWhite, Palette::PurpleBright, Palette::PurpleBright); 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(), KDColorWhite, Palette::PurpleBright, Palette::PurpleBright); } } diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 22443438b1a..8e5e985dcc1 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 f63465d94c4..b78d8b960a3 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 @@ class SolutionsController : public ViewController, public AlternateEmptyViewDefa 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 5a5534a6f41..2b74241338e 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -89,8 +89,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/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index 5f39dc7720a..b81e0b0f3d9 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -39,7 +39,7 @@ class Equal final : public Expression { 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, Preferences::UnitFormat unitFormat) 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/src/equal.cpp b/poincare/src/equal.cpp index 6ae1f00cc63..1d5091d88ae 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -44,13 +44,9 @@ Evaluation EqualNode::templatedApproximate(Context * context, Preferences::Co } -Expression Equal::standardEquation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat) 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, unitFormat, ExpressionNode::ReductionTarget::SystemForAnalysis)); + return sub.reduce(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, unitFormat, reductionTarget)); } Expression Equal::shallowReduce() { From 4b965a0ff6e1776cfae53f061ae148d74b317c61 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 11 Sep 2020 14:05:00 +0200 Subject: [PATCH 200/560] [shared/localization_controller] Add top border When selecting a country, the table would be cropped by the text, with no separator between the white of the cropped cell and the background color. Change-Id: Ia6be40bce40b7cdb3efa064989d45ef5c046e364 --- apps/shared/localization_controller.cpp | 11 ++++++++--- apps/shared/localization_controller.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index f08c86b7a87..781bd1070a2 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -10,7 +10,8 @@ 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_countryTitleMessage(KDFont::LargeFont, I18n::Message::Country), + m_borderView(Palette::GrayBright) { m_countryTitleMessage.setBackgroundColor(Palette::WallScreen); m_countryTitleMessage.setAlignment(0.5f, 0.5f); @@ -24,16 +25,19 @@ LocalizationController::ContentView::ContentView(LocalizationController * contro } int LocalizationController::ContentView::numberOfSubviews() const { - return 1 + m_controller->shouldDisplayTitle() + k_numberOfCountryWarningLines * m_controller->shouldDisplayWarning(); + return 1 + m_controller->shouldDisplayTitle() + (k_numberOfCountryWarningLines + 1) * m_controller->shouldDisplayWarning(); } View * LocalizationController::ContentView::subviewAtIndex(int i) { assert(i < numberOfSubviews()); - /* FIXME : This relies on the fact that the title is never displayed without the warning. */ + /* 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]; @@ -52,6 +56,7 @@ void LocalizationController::ContentView::layoutSubviews(bool force) { } if (m_controller->shouldDisplayWarning()) { origin = layoutWarningSubview(force, Metric::CommonTopMargin + origin) + Metric::CommonTopMargin; + m_borderView.setFrame(KDRect(Metric::CommonLeftMargin, origin, bounds().width() - Metric::CommonLeftMargin - Metric::CommonRightMargin, Metric::CellSeparatorThickness), force); } origin = layoutTableSubview(force, origin); assert(origin <= bounds().height()); diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h index 82cee36894c..e0dce228637 100644 --- a/apps/shared/localization_controller.h +++ b/apps/shared/localization_controller.h @@ -62,6 +62,7 @@ class LocalizationController : public ViewController, public SimpleListViewDataS SelectableTableView m_selectableTableView; MessageTextView m_countryTitleMessage; MessageTextView m_countryWarningLines[k_numberOfCountryWarningLines]; + SolidColorView m_borderView; }; SelectableTableView * selectableTableView() { return m_contentView.selectableTableView(); } From 8f97a332f610a2e8d7666665274580a7f8fd5fc0 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 29 Jun 2020 15:33:10 +0200 Subject: [PATCH 201/560] [Python] Modified the paste effect in script and shell area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a formula is pasted in a script or in the shell, some symbols are replaced by their equivalent in python : x turns into * ^ turns into ** √ turns into sqrt etc Change-Id: If6f2a22d4f3c148c2655e0892023b0e28058a9a6 --- apps/code/app.cpp | 2 + apps/code/helpers.cpp | 40 ++------ escher/Makefile | 1 + escher/include/escher/clipboard.h | 32 ++++++- escher/src/clipboard.cpp | 5 + escher/src/text_field.cpp | 1 + escher/test/clipboard.cpp | 21 +++++ ion/include/ion/unicode/utf8_helper.h | 32 +++++++ ion/src/shared/unicode/utf8_helper.cpp | 121 +++++++++++++++++++------ ion/test/utf8_helper.cpp | 71 ++++++++++++++- 10 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 escher/test/clipboard.cpp diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 5ed4e204b38..63537618c24 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -85,11 +85,13 @@ App::App(Snapshot * snapshot) : 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/helpers.cpp b/apps/code/helpers.cpp index 5634dbf670e..18250e77f47 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; i #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() { return m_textBuffer; } 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/src/clipboard.cpp b/escher/src/clipboard.cpp index a8551bc2bb9..5fa30079964 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -1,4 +1,5 @@ #include +#include #include static Clipboard s_clipboard; @@ -14,3 +15,7 @@ void Clipboard::store(const char * storedText, int length) { void Clipboard::reset() { strlcpy(m_textBuffer, "", 1); } + +void Clipboard::replaceCharForPython(bool entersPythonApp) { + UTF8Helper::tryAndReplacePatternsInStringByPatterns((char *)m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *)&PythonTextPairs, NumberOfPythonTextPairs, entersPythonApp); +} \ No newline at end of file diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index b16e5c70fdf..a7101b9b5a7 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/test/clipboard.cpp b/escher/test/clipboard.cpp new file mode 100644 index 00000000000..7c895c9b45d --- /dev/null +++ b/escher/test/clipboard.cpp @@ -0,0 +1,21 @@ +#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)"); +} diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 96404b863e1..53608642228 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, int 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); diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index e3ae93fba63..71b2f9a091c 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -132,37 +132,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; +bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength) { + int lenText = strlen(text); + if (lenText + slidingSize > textMaxLength || lenText + slidingSize < 0) { + return false; + } + if (slidingSize > 0) { + memmove(text+slidingSize, text, strlen(text)+1); + } else if (slidingSize < 0) { + memmove(text, text-slidingSize, strlen(text)+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, int lengthOfPatternToRemove, const char * replacementPattern, int textMaxLength) { + int lengthOfReplacementPattern = strlen(replacementPattern); + if (lengthOfPatternToRemove <= strlen(text) && slideStringByNumberOfChar(text, lengthOfReplacementPattern-lengthOfPatternToRemove, textMaxLength)) { + for (int i = 0; i < lengthOfReplacementPattern; i++) { + text[i] = replacementPattern[i]; } - currentPointer = nextPointer; - codePoint = decoder.nextCodePoint(); - nextPointer = decoder.stringPosition(); + return true; } - 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); + 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); + 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; + } } } diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index b96f361d9c1..4434bf27e4b 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'; From ea36c6e5d7e0f3cbc3742dfb136d6aae5b42877e Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 7 Sep 2020 16:18:49 +0200 Subject: [PATCH 202/560] [Clipboard] Changed the general copy/paste Fixed issues due to copy/paste of empty formulas. When pasted, empty formulas are now recognized by the parser and apear with the correct layout Without this process, copying an empty integral then pasting it gives : int((), x, (), ()) instead of drawing an empty integral Change-Id: I680aaf4eea953149e2d57efa8153ab4d3f93e2a7 --- escher/include/escher/clipboard.h | 2 +- escher/src/clipboard.cpp | 25 +++++++++++++++++++++++++ escher/test/clipboard.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/escher/include/escher/clipboard.h b/escher/include/escher/clipboard.h index 808cbd9d371..63431faec25 100644 --- a/escher/include/escher/clipboard.h +++ b/escher/include/escher/clipboard.h @@ -9,7 +9,7 @@ class Clipboard { public: static Clipboard * sharedClipboard(); void store(const char * storedText, int length = -1); - const char * storedText() { return m_textBuffer; } + const char * storedText(); void reset(); void enterPython() { replaceCharForPython(true); } void exitPython() { replaceCharForPython(false); } diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index 5fa30079964..863ea183aae 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -12,6 +12,31 @@ void Clipboard::store(const char * storedText, int length) { strlcpy(m_textBuffer, storedText, length == -1 ? TextField::maxBufferSize() : std::min(TextField::maxBufferSize(), length + 1)); } +const char * Clipboard::storedText() { + /* 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); } diff --git a/escher/test/clipboard.cpp b/escher/test/clipboard.cpp index 7c895c9b45d..510c683ef9b 100644 --- a/escher/test/clipboard.cpp +++ b/escher/test/clipboard.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include void assert_clipboard_enters_and_exits_python(const char * string, const char * stringResult) { @@ -19,3 +21,29 @@ QUIZ_CASE(escher_clipboard_enters_and_exits_python) { 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 From c130f8e881b69789574c88e3eb0e913dfe670962 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 17 Sep 2020 14:47:34 +0200 Subject: [PATCH 203/560] [apps/shared] Prevent KDPoint overlfowed translation on cursorFrame Change-Id: Ifc32eefaf4241d59d7567cadb9b2962ba5efb4d9 --- apps/shared/curve_view.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index fd27981d116..ea477f8788e 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -963,7 +963,7 @@ 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())); From 26bbdead7d9b9f8fb22c7f26abb3b8c7c84e5f2c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 17 Sep 2020 17:43:30 +0200 Subject: [PATCH 204/560] [apps/shared] Allow movement from undef curve Fix a bug preventing the cursor from moving to other curves using UP and DOWN when the y value was undef. To reproduce : - In Graph, define f(x) = 1 and g(x) = ln(x), then draw the curves - Press DOWN to select g - Press LEFT until g is not defined anymore --> Pressing UP or DOWN won't allow you to select f Change-Id: I79ed4a57b78ac0b8dac3f66e722e358bd4be18d9 --- apps/shared/interactive_curve_view_controller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 9e4c2ebed9e..20bac53116f 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -261,6 +261,9 @@ bool InteractiveCurveViewController::isCursorVisible() { 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(); From 960335c330129edac9eaebc00f6285e725b2acc4 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 17 Sep 2020 11:43:27 +0200 Subject: [PATCH 205/560] [Sequences] Fixed a few crashes Change-Id: Ib929bbae0f9ca06409706336ff799075e1288694 --- apps/shared/cache_context.cpp | 18 ++++++++++------ apps/shared/cache_context.h | 3 +-- apps/shared/global_context.cpp | 2 +- apps/shared/sequence.cpp | 39 ++++++++++++++++++++++++++++++++-- apps/shared/sequence.h | 3 ++- apps/solver/equation_store.cpp | 2 +- poincare/src/sequence.cpp | 9 +++++--- 7 files changed, 60 insertions(+), 16 deletions(-) diff --git a/apps/shared/cache_context.cpp b/apps/shared/cache_context.cpp index 3fcf76f4393..e2cd46d48f0 100644 --- a/apps/shared/cache_context.cpp +++ b/apps/shared/cache_context.cpp @@ -12,9 +12,10 @@ using namespace Poincare; namespace Shared { template -CacheContext::CacheContext(Context * parentContext) : - ContextWithParent(parentContext), - m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}} +CacheContext::CacheContext(SequenceContext * sequenceContext) : + ContextWithParent(sequenceContext), + m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}}, + m_sequenceContext(sequenceContext) { } @@ -34,9 +35,14 @@ const Expression CacheContext::expressionForSymbolAbstract(const Poincare::Sy Sequence * seq = m_sequenceContext->sequenceStore()->modelForRecord(record); rank.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Float::Builder(m_nValue)); T n = PoincareHelpers::ApproximateToScalar(rank, this); - // In case the rank is not int or sequence referenced is not defined, return NAN - if (std::floor(n) == n && seq->fullName() != nullptr) { - return Float::Builder(seq->valueAtRank(n, m_sequenceContext)); + // 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); diff --git a/apps/shared/cache_context.h b/apps/shared/cache_context.h index 3dabf0cfea7..56f6f96b677 100644 --- a/apps/shared/cache_context.h +++ b/apps/shared/cache_context.h @@ -11,11 +11,10 @@ namespace Shared { template class CacheContext : public Poincare::ContextWithParent { public: - CacheContext(Poincare::Context * parentContext); + CacheContext(SequenceContext * sequenceContext); const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue = NAN) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); void setNValue(int n) { m_nValue = n; } - void setSequenceContext(SequenceContext * sequenceContext) { m_sequenceContext = sequenceContext;} private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 654c7737bfe..b5b6d6fe258 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -127,7 +127,7 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym char unknownN[bufferSize]; Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); float rank = symbol.childAtIndex(0).approximateWithValueForSymbol(unknownN, unknownSymbolValue, ctx, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); - if (std::floor(rank) == rank && seq.hasValidExpression()) { + if (std::floor(rank) == rank) { SequenceContext sqctx(ctx, sequenceStore()); return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); } else { diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index e6302274f15..df64d31a1e5 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "../shared/poincare_helpers.h" #include #include @@ -131,6 +133,40 @@ 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); @@ -143,7 +179,7 @@ T Sequence::templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const { template T Sequence::valueAtRank(int n, SequenceContext *sqctx) { - if (n < 0) { + if (n < 0 || badlyReferencesItself(sqctx)) { return NAN; } int sequenceIndex = SequenceStore::sequenceIndexForName(fullName()[0]); @@ -174,7 +210,6 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); CacheContext ctx = CacheContext(sqctx); - ctx.setSequenceContext(sqctx); // Hold values u(n), u(n-1), u(n-2), v(n), v(n-1), v(n-2)... T values[MaxNumberOfSequences][MaxRecurrenceDepth+1]; diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index dffbc45b822..d53422c5206 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -59,7 +59,8 @@ friend class SequenceStore; Poincare::Layout nameLayout(); bool isDefined() override; bool isEmpty() override; - bool hasValidExpression() { return m_definition.hasValidExpression(); } + 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))); diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 1523ff4f0b9..2ad2a7d531f 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -102,7 +102,7 @@ void EquationStore::approximateSolve(Poincare::Context * context, bool shouldRep 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 = !isnan(root); + m_hasMoreThanMaxNumberOfApproximateSolution = !std::isnan(root); break; } m_approximateSolutions[i] = root; diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 83957a0505e..e6177bc1978 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -97,9 +97,12 @@ Expression Sequence::replaceSymbolWithExpression(const SymbolAbstract & symbol, } Expression Sequence::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined - || childAtIndex(0).isUndefined()) - { + Expression e = Expression::defaultShallowReduce(); + e = e.defaultHandleUnitsInChildren(); + if (e.isUndefined()) { + return e; + } + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined) { return replaceWithUndefinedInPlace(); } return *this; From 4274b558b6e0d9aee19f0c8eac298ea92edbd7a7 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Fri, 18 Sep 2020 17:41:12 +0200 Subject: [PATCH 206/560] [Sequences] Changing the name of CacheContext to SequenceCacheContext Change-Id: If2d542748f6f7b2363d6c1443f88c058475945eb --- apps/shared/Makefile | 4 ++-- apps/shared/sequence.cpp | 4 ++-- ...he_context.cpp => sequence_cache_context.cpp} | 16 ++++++++-------- ...{cache_context.h => sequence_cache_context.h} | 8 ++++---- apps/shared/sequence_context.cpp | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) rename apps/shared/{cache_context.cpp => sequence_cache_context.cpp} (79%) rename apps/shared/{cache_context.h => sequence_cache_context.h} (77%) diff --git a/apps/shared/Makefile b/apps/shared/Makefile index e12384f37a7..0c6ead3e763 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -29,7 +29,6 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ - cache_context.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ @@ -67,6 +66,7 @@ app_shared_src = $(addprefix apps/shared/,\ 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 \ @@ -103,8 +103,8 @@ app_shared_test_src += $(addprefix apps/graph/,\ ) app_shared_test_src += $(addprefix apps/shared/,\ - cache_context.cpp \ sequence.cpp \ + sequence_cache_context.cpp \ sequence_context.cpp \ sequence_store.cpp \ ) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index df64d31a1e5..4125879940b 100644 --- a/apps/shared/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 @@ -209,7 +209,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn 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]; diff --git a/apps/shared/cache_context.cpp b/apps/shared/sequence_cache_context.cpp similarity index 79% rename from apps/shared/cache_context.cpp rename to apps/shared/sequence_cache_context.cpp index e2cd46d48f0..518029f0453 100644 --- a/apps/shared/cache_context.cpp +++ b/apps/shared/sequence_cache_context.cpp @@ -1,4 +1,4 @@ -#include "cache_context.h" +#include "sequence_cache_context.h" #include "sequence.h" #include "sequence_store.h" #include "poincare_helpers.h" @@ -12,7 +12,7 @@ using namespace Poincare; namespace Shared { template -CacheContext::CacheContext(SequenceContext * sequenceContext) : +SequenceCacheContext::SequenceCacheContext(SequenceContext * sequenceContext) : ContextWithParent(sequenceContext), m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}}, m_sequenceContext(sequenceContext) @@ -20,7 +20,7 @@ CacheContext::CacheContext(SequenceContext * sequenceContext) : } template -const Expression CacheContext::expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone, float unknownSymbolValue ) { +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))); @@ -52,12 +52,12 @@ const Expression CacheContext::expressionForSymbolAbstract(const Poincare::Sy } template -void CacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol) { +void SequenceCacheContext::setValueForSymbol(T value, const Poincare::Symbol & symbol) { m_values[nameIndexForSymbol(symbol)][rankIndexForSymbol(symbol)] = value; } template -int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { +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 @@ -65,7 +65,7 @@ int CacheContext::nameIndexForSymbol(const Poincare::Symbol & symbol) { } template -int CacheContext::rankIndexForSymbol(const Poincare::Symbol & symbol) { +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; @@ -74,7 +74,7 @@ int CacheContext::rankIndexForSymbol(const Poincare::Symbol & symbol) { return 1; } -template class CacheContext; -template class CacheContext; +template class SequenceCacheContext; +template class SequenceCacheContext; } diff --git a/apps/shared/cache_context.h b/apps/shared/sequence_cache_context.h similarity index 77% rename from apps/shared/cache_context.h rename to apps/shared/sequence_cache_context.h index 56f6f96b677..9fa79b22ea2 100644 --- a/apps/shared/cache_context.h +++ b/apps/shared/sequence_cache_context.h @@ -1,5 +1,5 @@ -#ifndef SEQUENCE_CACHE_CONTEXT_H -#define SEQUENCE_CACHE_CONTEXT_H +#ifndef SHARED_SEQUENCE_CACHE_CONTEXT_H +#define SHARED_SEQUENCE_CACHE_CONTEXT_H #include #include @@ -9,9 +9,9 @@ namespace Shared { template -class CacheContext : public Poincare::ContextWithParent { +class SequenceCacheContext : public Poincare::ContextWithParent { public: - CacheContext(SequenceContext * sequenceContext); + 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); void setNValue(int n) { m_nValue = n; } diff --git a/apps/shared/sequence_context.cpp b/apps/shared/sequence_context.cpp index 528a2c3595f..1accf43ad4e 100644 --- a/apps/shared/sequence_context.cpp +++ b/apps/shared/sequence_context.cpp @@ -1,6 +1,6 @@ #include "sequence_context.h" #include "sequence_store.h" -#include "cache_context.h" +#include "sequence_cache_context.h" #include "../shared/poincare_helpers.h" #include From d37540f032c5115a06bcc7a28edd370979e3f5f9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 19 Aug 2020 15:25:06 +0200 Subject: [PATCH 207/560] [poincare/unit] Add Celsius and Fahrenheit Temperatures can be converted to and from degree Celsius and Fahrenheit. When used in non-trivial calculations, they are always reduced to undef, as the rules for manipulating relative scales are not well defined. Change-Id: If59e224a0e7f940b421bc894bbe2279c90f38d04 --- apps/math_toolbox.cpp | 5 +- apps/shared.universal.i18n | 2 + apps/toolbox.de.i18n | 2 + apps/toolbox.en.i18n | 2 + apps/toolbox.es.i18n | 2 + apps/toolbox.fr.i18n | 2 + apps/toolbox.it.i18n | 2 + apps/toolbox.nl.i18n | 2 + apps/toolbox.pt.i18n | 2 + poincare/include/poincare/unit.h | 40 +++++++-- poincare/src/unit.cpp | 109 +++++++++++++++++++++++- poincare/src/unit_convert.cpp | 11 +++ poincare/test/expression_properties.cpp | 10 +++ poincare/test/simplification.cpp | 24 ++++++ 14 files changed, 201 insertions(+), 14 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ae33aa01cce..00a32610aaf 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -160,7 +160,10 @@ const ToolboxMessageTree unitCurrentAmpereChildren[] = { }; 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), diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 2c5ad9ab8c5..e6d00f02e7c 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -35,6 +35,8 @@ UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" UnitTemperatureKelvinSymbol = "_K" +UnitTemperatureCelsiusSymbol = "_Cel" +UnitTemperatureFahrenheitSymbol = "_Fah" UnitAmountMoleSymbol = "_mol" UnitAmountMoleMilliSymbol = "_mmol" UnitAmountMoleMicroSymbol = "_μmol" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 5be05ac9c74..9e91443fdf6 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Mikroampere" UnitTemperatureMenu = "Temperatur" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Stoffmenge" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 2b55b9276b5..153b68a6dca 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index f48b851364d..0bf1900e038 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index df9c16da5bb..b5920b07d41 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Température" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantité de matière" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index 2b6faead37b..efd60309bc8 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantità de materia" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index ddfee739fa9..f1aecba0c3f 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Temperatuur" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Hoeveelheid stof" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 304330c0335..7fb81d46e40 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -43,6 +43,8 @@ UnitCurrentAmpereMilli = "Miliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantidade da substância" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Milimole" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 456ff470066..1ea8f7fe428 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -69,6 +69,7 @@ class UnitNode final : public ExpressionNode { *(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; @@ -102,6 +103,7 @@ class UnitNode final : public ExpressionNode { 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 @@ -110,18 +112,20 @@ class UnitNode final : public ExpressionNode { 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); } - virtual bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return true; } + virtual bool hasAdditionalExpressions(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; } double ratio() const { return m_ratio; } bool isInputPrefixable() const { return m_inputPrefixable != Prefixable::None; } bool isOutputPrefixable() const { return m_outputPrefixable != Prefixable::None; } - bool canPrefix(const Prefix * prefix, bool input) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; 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; @@ -187,7 +191,6 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -195,13 +198,18 @@ class UnitNode final : public ExpressionNode { 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 1; } + int numberOfRepresentatives() const override { return 3; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } + const Representative * standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const override { return this; } + bool hasAdditionalExpressions(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; }; @@ -213,7 +221,6 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -226,7 +233,6 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 1; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -238,7 +244,6 @@ class UnitNode final : public ExpressionNode { 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; - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return false; } private: using Representative::Representative; }; @@ -272,6 +277,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -394,6 +400,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -407,6 +414,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -418,6 +426,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(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; @@ -539,7 +548,11 @@ class Unit : public Expression { 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::LongScale) }; + static constexpr const TemperatureRepresentative k_temperatureRepresentatives[] = { + TemperatureRepresentative("K", 1., Prefixable::All, Prefixable::None), + TemperatureRepresentative("Cel", 1., Prefixable::None, Prefixable::None), + TemperatureRepresentative("Fah", 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; @@ -631,6 +644,12 @@ class Unit : public Expression { 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, "Cel"), "Index for the Celsius Representative is incorrect."); + static constexpr int k_fahrenheitRepresentativeIndex = 2; + static_assert(strings_equal(k_temperatureRepresentatives[k_fahrenheitRepresentativeIndex].m_rootSymbol, "Fah"), "Index for the Fahrenheit 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; @@ -657,6 +676,7 @@ class Unit : public Expression { 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); @@ -665,6 +685,8 @@ class Unit : public Expression { 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(); } + private: UnitNode * node() const { return static_cast(Expression::node()); } Expression removeUnit(Expression * unit); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index a62dea5a8dd..976f8ff1d1d 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,9 @@ constexpr const int Unit::k_ounceRepresentativeIndex, Unit::k_poundRepresentativeIndex, Unit::k_shortTonRepresentativeIndex, + Unit::k_kelvinRepresentativeIndex, + Unit::k_celsiusRepresentativeIndex, + Unit::k_fahrenheitRepresentativeIndex, Unit::k_electronVoltRepresentativeIndex, Unit::k_wattRepresentativeIndex, Unit::k_hectareRepresentativeIndex, @@ -476,6 +480,44 @@ int UnitNode::MassRepresentative::setAdditionalExpressions(double value, Express 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); /* 1. Convert into Wh @@ -717,12 +759,12 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); const Representative * representative = Representative::RepresentativeForDimension(vector); return representative != nullptr - && ((unit.type() == ExpressionNode::Type::Unit && !unit.convert().isBaseUnit()) + && ((unit.type() == ExpressionNode::Type::Unit && !static_cast(unit).isBaseUnit()) || representative->hasAdditionalExpressions(value, unitFormat)); } int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { - const Representative * representative = UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); + const Representative * representative = units.type() == ExpressionNode::Type::Unit ? static_cast(units).node()->representative() : UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); assert(representative); return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); } @@ -764,6 +806,28 @@ Expression Unit::BuildSplit(double value, const Unit * units, int length, Expres 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.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 || isBaseUnit()) { @@ -772,6 +836,39 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext * 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()) { + 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 Representative * representative = unitNode->representative(); const Prefix * prefix = unitNode->prefix(); @@ -789,7 +886,7 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { // 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); @@ -806,7 +903,11 @@ Expression Unit::removeUnit(Expression * unit) { void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, ExpressionNode::ReductionContext reductionContext, bool optimizePrefix) { assert(exponent != 0.f); - if (std::isinf(*value) || *value == 0.0) { + + 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; diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index e38bef1fec5..c2984c386a6 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -83,6 +83,17 @@ 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); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 4cf2a3b855e..81eb6574993 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -402,6 +402,16 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("1×_kg", array7, 1, Imperial); assert_additional_results_compute_to("1×_kg", nullptr, 0, Metric); + // Temperatures + const char * array14[2] = {"-273.15×_Cel", "-459.67×_Fah"}; + assert_additional_results_compute_to("0×_K", array14, 2, Metric); + const char * array15[2] = {"-279.67×_Fah", "-173.15×_Cel"}; + assert_additional_results_compute_to("100×_K", array15, 2, Imperial); + const char * array16[2] = {"12.02×_Fah", "262.05×_K"}; + assert_additional_results_compute_to("-11.1×_Cel", array16, 2); + const char * array17[2] = {"-20×_Cel", "253.15×_K"}; + assert_additional_results_compute_to("-4×_Fah", array17, 2); + // Energy const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; assert_additional_results_compute_to("3.6×_MN_m", array8, 2); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 8fe5e021bea..ee7dc196aea 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -309,6 +309,8 @@ QUIZ_CASE(poincare_simplification_units) { 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("_Cel", "1×_Cel"); + assert_parsed_expression_simplify_to("_Fah", "1×_Fah"); 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); @@ -344,6 +346,21 @@ QUIZ_CASE(poincare_simplification_units) { 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→_Cel", "0×_Cel"); + assert_parsed_expression_simplify_to("0×_Cel", "0×_Cel"); + assert_parsed_expression_simplify_to("-32×_Fah", "-32×_Fah"); + assert_parsed_expression_simplify_to("273.16×_K", "273.16×_K"); + assert_parsed_expression_simplify_to("100×_Cel→_K", "373.15×_K"); + assert_parsed_expression_simplify_to("-100×_Cel→_K", "173.15×_K"); + assert_parsed_expression_simplify_to("_Cel+_Cel", Undefined::Name()); + assert_parsed_expression_simplify_to("_Cel+_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("_K+_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("2*20_Fah", Undefined::Name()); + assert_parsed_expression_simplify_to("_Cel^2", Undefined::Name()); + assert_parsed_expression_simplify_to("1/(-3_Cel)", Undefined::Name()); + assert_parsed_expression_simplify_to("-1×100×_Cel→_K", Undefined::Name()); + /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); assert_parsed_expression_simplify_to("_m-_m", "0×_m"); @@ -1205,6 +1222,13 @@ QUIZ_CASE(poincare_simplification_unit_convert) { 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→_Cel", "-273.15×_Cel"); + assert_parsed_expression_simplify_to("0_Cel→_K", "273.15×_K"); + assert_parsed_expression_simplify_to("_Cel→_K", "274.15×_K"); + assert_parsed_expression_simplify_to("0_K→_Fah", "-459.67×_Fah"); + assert_parsed_expression_simplify_to("0_Fah→_K", "255.37222222222×_K"); + assert_parsed_expression_simplify_to("_Fah→_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()); From 67e6e388252c96f9568c983fe19c228e6c8913a7 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 18 Sep 2020 16:15:25 +0200 Subject: [PATCH 208/560] =?UTF-8?q?[poincare/parsing]=20Parse=20degree=20s?= =?UTF-8?q?ign=20=C2=B0=20in=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I518aba17214b049dfa8fd3b89f4ce124dbd6b41a --- ion/include/ion/unicode/code_point.h | 1 + poincare/src/parsing/tokenizer.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ion/include/ion/unicode/code_point.h b/ion/include/ion/unicode/code_point.h index d05688d5508..2000737374f 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 UCodePointGreekSmallLetterTheta = 0x3b8; // θ diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 4cf8dc99c7a..13ae60956b6 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() || From 3f19d66c78d8c0f6479a6c30fed85e4ba09742bb Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 18 Sep 2020 16:04:34 +0200 Subject: [PATCH 209/560] [poincare/unit] Change temperature symbols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Celsius and Fahrenheit now use the degree symbol °. _Cel -> _°C _Fah -> _°F Change-Id: Ic5935d3a6263a556692bebb6254b6b6d4de48431 --- apps/shared.universal.i18n | 4 +-- poincare/include/poincare/unit.h | 8 ++--- poincare/test/expression_properties.cpp | 12 ++++---- poincare/test/simplification.cpp | 40 ++++++++++++------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index e6d00f02e7c..798c5e2be63 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -35,8 +35,8 @@ UnitCurrentAmpereSymbol = "_A" UnitCurrentAmpereMilliSymbol = "_mA" UnitCurrentAmpereMicroSymbol = "_μA" UnitTemperatureKelvinSymbol = "_K" -UnitTemperatureCelsiusSymbol = "_Cel" -UnitTemperatureFahrenheitSymbol = "_Fah" +UnitTemperatureCelsiusSymbol = "_°C" +UnitTemperatureFahrenheitSymbol = "_°F" UnitAmountMoleSymbol = "_mol" UnitAmountMoleMilliSymbol = "_mmol" UnitAmountMoleMicroSymbol = "_μmol" diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 1ea8f7fe428..8c759494c51 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -550,8 +550,8 @@ class Unit : public Expression { typedef UnitNode::TemperatureRepresentative TemperatureRepresentative; static constexpr const TemperatureRepresentative k_temperatureRepresentatives[] = { TemperatureRepresentative("K", 1., Prefixable::All, Prefixable::None), - TemperatureRepresentative("Cel", 1., Prefixable::None, Prefixable::None), - TemperatureRepresentative("Fah", 5./9., Prefixable::None, 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) }; @@ -647,9 +647,9 @@ class Unit : public Expression { 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, "Cel"), "Index for the Celsius Representative is incorrect."); + 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, "Fah"), "Index for the Fahrenheit Representative is incorrect."); + static_assert(strings_equal(k_temperatureRepresentatives[k_fahrenheitRepresentativeIndex].m_rootSymbol, "°F"), "Index for the Fahrenheit 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; diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 81eb6574993..b3d6aa5b58e 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -403,14 +403,14 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("1×_kg", nullptr, 0, Metric); // Temperatures - const char * array14[2] = {"-273.15×_Cel", "-459.67×_Fah"}; + 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×_Fah", "-173.15×_Cel"}; + 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×_Fah", "262.05×_K"}; - assert_additional_results_compute_to("-11.1×_Cel", array16, 2); - const char * array17[2] = {"-20×_Cel", "253.15×_K"}; - assert_additional_results_compute_to("-4×_Fah", array17, 2); + 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); // Energy const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index ee7dc196aea..dcac131d55a 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -309,8 +309,8 @@ QUIZ_CASE(poincare_simplification_units) { 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("_Cel", "1×_Cel"); - assert_parsed_expression_simplify_to("_Fah", "1×_Fah"); + 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); @@ -347,19 +347,19 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("1_qt", "946.352946×_cm^3"); /* Tests for non-absolute units */ - assert_parsed_expression_simplify_to("273.15×_K→_Cel", "0×_Cel"); - assert_parsed_expression_simplify_to("0×_Cel", "0×_Cel"); - assert_parsed_expression_simplify_to("-32×_Fah", "-32×_Fah"); + 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("100×_Cel→_K", "373.15×_K"); - assert_parsed_expression_simplify_to("-100×_Cel→_K", "173.15×_K"); - assert_parsed_expression_simplify_to("_Cel+_Cel", Undefined::Name()); - assert_parsed_expression_simplify_to("_Cel+_Fah", Undefined::Name()); - assert_parsed_expression_simplify_to("_K+_Fah", Undefined::Name()); - assert_parsed_expression_simplify_to("2*20_Fah", Undefined::Name()); - assert_parsed_expression_simplify_to("_Cel^2", Undefined::Name()); - assert_parsed_expression_simplify_to("1/(-3_Cel)", Undefined::Name()); - assert_parsed_expression_simplify_to("-1×100×_Cel→_K", Undefined::Name()); + 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()); /* Unit sum/subtract */ assert_parsed_expression_simplify_to("_m+_m", "2×_m"); @@ -1222,12 +1222,12 @@ QUIZ_CASE(poincare_simplification_unit_convert) { 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→_Cel", "-273.15×_Cel"); - assert_parsed_expression_simplify_to("0_Cel→_K", "273.15×_K"); - assert_parsed_expression_simplify_to("_Cel→_K", "274.15×_K"); - assert_parsed_expression_simplify_to("0_K→_Fah", "-459.67×_Fah"); - assert_parsed_expression_simplify_to("0_Fah→_K", "255.37222222222×_K"); - assert_parsed_expression_simplify_to("_Fah→_K", "255.92777777778×_K"); + 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); From bd068312d90b95bf924ecaec8241f5a2fe1cc3a2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Sep 2020 17:49:06 +0200 Subject: [PATCH 210/560] [apps/continuous_function_cache] Clear on bad step Cache did not check that the step was the same before panning, causing old values to remains cached. e.g. : - Type f(x) = x, and plot it. - Go to Axes, and set Xmax = 0. - Confirm Change-Id: Ie8ed10c336cf517a65f19b05bd14866daf8128c2 --- apps/shared/continuous_function_cache.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index ad1e0e98c7c..62064532d3e 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -31,6 +31,10 @@ void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCa function->setCache(cache); } + if (tStep != 0. && tStep != cache->step()) { + cache->clear(); + } + if (function->plotType() == ContinuousFunction::PlotType::Cartesian && tStep != 0) { function->cache()->pan(function, tMin); } From 28d18dd6a5f4f98b2556d7ad6a71de07d628bcf9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Sep 2020 12:37:22 +0200 Subject: [PATCH 211/560] [ion/utf8_helper] Signedness and capitalization Change-Id: I3451a206fbd45a490eea0115621c07abb868344a --- escher/src/clipboard.cpp | 6 +++--- ion/include/ion/unicode/utf8_helper.h | 10 +++++----- ion/src/shared/unicode/utf8_helper.cpp | 16 ++++++++-------- ion/test/utf8_helper.cpp | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index 863ea183aae..c69c072105a 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -33,7 +33,7 @@ const char * Clipboard::storedText() { UTF8Helper::TextPair("\x12\x13", "\x12\x11\x13"), }; - UTF8Helper::tryAndReplacePatternsInStringByPatterns(m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *) &textPairs, numberOfPairs, true); + UTF8Helper::TryAndReplacePatternsInStringByPatterns(m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *) &textPairs, numberOfPairs, true); return m_textBuffer; } @@ -42,5 +42,5 @@ void Clipboard::reset() { } void Clipboard::replaceCharForPython(bool entersPythonApp) { - UTF8Helper::tryAndReplacePatternsInStringByPatterns((char *)m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *)&PythonTextPairs, NumberOfPythonTextPairs, entersPythonApp); -} \ No newline at end of file + UTF8Helper::TryAndReplacePatternsInStringByPatterns((char *)m_textBuffer, TextField::maxBufferSize(), (UTF8Helper::TextPair *)&PythonTextPairs, NumberOfPythonTextPairs, entersPythonApp); +} diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 53608642228..edc3793259c 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -44,10 +44,10 @@ void RemoveCodePoint(char * buffer, CodePoint c, const char * * indexToUpdate = /* 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, int textMaxLength); + * 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. @@ -58,7 +58,7 @@ bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength); * - 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); +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); diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 71b2f9a091c..44f034d4160 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -138,11 +138,11 @@ void RemoveCodePoint(char * buffer, CodePoint c, const char * * pointerToUpdate, UTF8Decoder::CodePointToChars(c, pattern, codePointCharSize); pattern[codePointCharSize] = '\0'; TextPair pair(pattern, ""); - tryAndReplacePatternsInStringByPatterns(buffer, strlen(buffer), &pair, 1, true, pointerToUpdate, stoppingPosition); + TryAndReplacePatternsInStringByPatterns(buffer, strlen(buffer), &pair, 1, true, pointerToUpdate, stoppingPosition); } -bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength) { - int lenText = strlen(text); +bool SlideStringByNumberOfChar(char * text, int slidingSize, size_t textMaxLength) { + size_t lenText = strlen(text); if (lenText + slidingSize > textMaxLength || lenText + slidingSize < 0) { return false; } @@ -158,10 +158,10 @@ bool slideStringByNumberOfChar(char * text, int slidingSize, int textMaxLength) /* 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, int lengthOfPatternToRemove, const char * replacementPattern, int textMaxLength) { - int lengthOfReplacementPattern = strlen(replacementPattern); - if (lengthOfPatternToRemove <= strlen(text) && slideStringByNumberOfChar(text, lengthOfReplacementPattern-lengthOfPatternToRemove, textMaxLength)) { - for (int i = 0; i < lengthOfReplacementPattern; i++) { +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; @@ -169,7 +169,7 @@ static bool replaceFirstCharsByPattern(char * text, int lengthOfPatternToRemove, return false; } -void tryAndReplacePatternsInStringByPatterns(char * text, int textMaxLength, TextPair * textPairs, int numberOfPairs, bool firstToSecond, const char * * pointerToUpdate, const char * stoppingPosition) { +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); diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index 4434bf27e4b..4e182142be3 100644 --- a/ion/test/utf8_helper.cpp +++ b/ion/test/utf8_helper.cpp @@ -166,7 +166,7 @@ QUIZ_CASE(ion_utf8_remove_code_point) { 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); + bool success = UTF8Helper::SlideStringByNumberOfChar((char *)buffer, slidingSize, bufferSize); quiz_assert(success == successResult); if (successResult) { quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0); @@ -187,7 +187,7 @@ QUIZ_CASE(ion_utf8_move_string_from_index_by_number_of_char) { } 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); + UTF8Helper::TryAndReplacePatternsInStringByPatterns(buffer, bufferSize, textPairs, numberOfPairs, firstToSecond, indexToUpdate, stoppingPosition); quiz_assert(strncmp(buffer, stringResult, bufferSize) == 0); if (indexToUpdateResult != nullptr) { quiz_assert(*indexToUpdate == indexToUpdateResult); From 4bde3ed4b0f3af0a6fe162fa59822f46061a89f2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Sep 2020 12:43:13 +0200 Subject: [PATCH 212/560] [poincare/solver] Fix type ambiguity warning Change-Id: I9248080520d6fa6430dddb8811dbd0120fc08d79 --- poincare/src/solver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/solver.cpp b/poincare/src/solver.cpp index 37d8ec9760f..d0c0d638d0f 100644 --- a/poincare/src/solver.cpp +++ b/poincare/src/solver.cpp @@ -197,12 +197,12 @@ Coordinate2D Solver::IncreasingFunctionRoot(double ax, double bx, double * it instead, otherwise, we reached the most precise result possible. */ if (currentAbscissa == min) { if (currentAbscissa != -INFINITY) { - currentAbscissa = std::nextafter(currentAbscissa, INFINITY); + currentAbscissa = std::nextafter(currentAbscissa, static_cast(INFINITY)); } } if (currentAbscissa == max) { if (currentAbscissa != INFINITY) { - currentAbscissa = std::nextafter(currentAbscissa, -INFINITY); + currentAbscissa = std::nextafter(currentAbscissa, -static_cast(INFINITY)); } } if (currentAbscissa == min || currentAbscissa == max) { From d5ce234d4d54118e1b745d75fd403955725eb758 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 16 Sep 2020 10:20:06 +0200 Subject: [PATCH 213/560] [probability] Align snapshot buffers char buffers m_distribution and m_calculation had too weak an alignment, causing a crash when starting the web simulator. Change-Id: Ibfd3c2582bc4de97beb4d6cc51b9fb86300edb5e --- apps/probability/app.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/probability/app.h b/apps/probability/app.h index 323087042c4..23071b707dd 100644 --- a/apps/probability/app.h +++ b/apps/probability/app.h @@ -50,14 +50,23 @@ class App : public Shared::TextFieldDelegateApp { 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() { From 6a84ba2523ff813ac558e7afadd8f4e33d8f0099 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 24 Sep 2020 15:40:37 +0200 Subject: [PATCH 214/560] [global_context.cpp] Fixing sequence bug u(n+2) = u(n+1) + u(n) + u(2) u(0) = 2 u(1) = 2 Crashed because the global context was not checking if the sequence was badly referencing itself Change-Id: I97f694955e15518bb088db50deb761d7ee09d75f --- apps/shared/global_context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index b5b6d6fe258..a153c9817d2 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -127,7 +127,7 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym char unknownN[bufferSize]; Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); float rank = symbol.childAtIndex(0).approximateWithValueForSymbol(unknownN, unknownSymbolValue, ctx, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); - if (std::floor(rank) == rank) { + if (std::floor(rank) == rank && !seq.badlyReferencesItself(ctx)) { SequenceContext sqctx(ctx, sequenceStore()); return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); } else { From 77ac333e1116aa1a0dd8d3776b5101e9badb2012 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 13:14:17 +0200 Subject: [PATCH 215/560] [on_boarding/i18n] Localize contact email address Change-Id: I19ae4299e09b63c373734ddff835fedcabb6749c --- apps/on_boarding/base.fr.i18n | 2 +- apps/on_boarding/base.it.i18n | 2 +- apps/on_boarding/base.pt.i18n | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/on_boarding/base.fr.i18n b/apps/on_boarding/base.fr.i18n index fdd2dca845e..6f25f930da0 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 33ff2bd3c1e..90da9192faf 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 bd16512a01d..d0f0d8a0702 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" From 54c28fc87f22abf8a734952c9af25ce7092c9a58 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 11:13:12 +0200 Subject: [PATCH 216/560] [escher/modal_view_controller] Hide regular view Do not display the regular view when the main view takes up all the frame. This fixes a bug causing the language menu to be briefly visible before the logo on start-up. Change-Id: Ia2de44de52ac6f852e0eca05101587042f02913b --- escher/src/modal_view_controller.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/escher/src/modal_view_controller.cpp b/escher/src/modal_view_controller.cpp index 6fbed78b1b3..548f29a05ad 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); } From 64e7057d5055969729c845d8e814b0deeb9c9acf Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 11:52:04 +0200 Subject: [PATCH 217/560] [apps/on_boarding] Remember language choice When choosing a language and then pressing back from the country menu to get back to the language menu, the previously selected language remains selected. Change-Id: I018c51cce09d47b15bb4864c135d381a94b2a72f --- apps/on_boarding/localization_controller.cpp | 2 +- apps/settings/sub_menu/localization_controller.cpp | 2 +- apps/shared/localization_controller.cpp | 5 +++++ apps/shared/localization_controller.h | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/on_boarding/localization_controller.cpp b/apps/on_boarding/localization_controller.cpp index 059d03a28a3..91f8b81cdb9 100644 --- a/apps/on_boarding/localization_controller.cpp +++ b/apps/on_boarding/localization_controller.cpp @@ -7,7 +7,7 @@ namespace OnBoarding { int LocalizationController::indexOfCellToSelectOnReset() const { return mode() == Mode::Language ? - 0 : + Shared::LocalizationController::indexOfCellToSelectOnReset() : IndexOfCountry(I18n::DefaultCountryForLanguage[static_cast(GlobalPreferences::sharedGlobalPreferences()->language())]); } diff --git a/apps/settings/sub_menu/localization_controller.cpp b/apps/settings/sub_menu/localization_controller.cpp index 5088289db07..047fee4a79a 100644 --- a/apps/settings/sub_menu/localization_controller.cpp +++ b/apps/settings/sub_menu/localization_controller.cpp @@ -6,7 +6,7 @@ namespace Settings { int LocalizationController::indexOfCellToSelectOnReset() const { return mode() == Mode::Language ? - static_cast(GlobalPreferences::sharedGlobalPreferences()->language()) : + Shared::LocalizationController::indexOfCellToSelectOnReset() : IndexOfCountry(GlobalPreferences::sharedGlobalPreferences()->country()); } diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index 781bd1070a2..fc655badfea 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -142,6 +142,11 @@ void LocalizationController::setMode(LocalizationController::Mode mode) { 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); diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h index e0dce228637..c011df59e3b 100644 --- a/apps/shared/localization_controller.h +++ b/apps/shared/localization_controller.h @@ -22,7 +22,7 @@ class LocalizationController : public ViewController, public SimpleListViewDataS Mode mode() const { return m_mode; } void setMode(Mode mode); - virtual int indexOfCellToSelectOnReset() const = 0; + virtual int indexOfCellToSelectOnReset() const; virtual bool shouldDisplayTitle() const = 0; bool shouldDisplayWarning() const { return mode() == Mode::Country; } From 30196099467063f7f7deb493ef703b9c52d4860f Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Tue, 29 Sep 2020 11:20:50 +0200 Subject: [PATCH 218/560] [Sequence] Fixing display issue in sequence graphs Change-Id: I1b914c31cf2e7a84329e86486e30f3a7244f235d --- apps/shared/sequence.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index 4125879940b..20f0f47d960 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -187,7 +187,7 @@ T Sequence::valueAtRank(int n, SequenceContext *sqctx) { // Reset cache indexes and cache values sqctx->setIndependentSequenceRank(-1, sequenceIndex); for (int i = 0 ; i < MaxRecurrenceDepth+1; i++) { - sqctx->setIndependentSequenceValue(NAN, sequenceIndex, i); + sqctx->setIndependentSequenceValue(NAN, sequenceIndex, i); } } while(sqctx->independentSequenceRank(sequenceIndex) < n) { From 63076758a3830631f40914c685efd003a22b69fa Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 14:05:47 +0200 Subject: [PATCH 219/560] [i18n] Check for redundancy in localized messages After parsing the .i18n files, compare the messages and raise an error if some of them could be factored into a universal file. Change-Id: I0dcfe309dc4c310555a0ea16b6d1680e51a8e87d --- apps/i18n.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/i18n.py b/apps/i18n.py index 3c688d64e72..d03ec1ede0c 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -71,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() @@ -95,6 +107,7 @@ def parse_files(files): else: messages.add(name) data[locale][name] = definition + check_redundancy(messages, data, args.locales) return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data} def parse_codepoints(file): From 6a9a5dbe27869a31df37f6ddd6ba8c4abe1766a4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 14:33:14 +0200 Subject: [PATCH 220/560] [i18n] Factor duplicate messages in universal Units with the same name in all available languages have been moved to the new file toolbox.universal.i18n. Change-Id: I82ef362ca335dc61da5f3bca67714a9ae409793d --- apps/Makefile | 2 +- apps/regression/base.de.i18n | 1 - apps/regression/base.en.i18n | 1 - apps/regression/base.es.i18n | 1 - apps/regression/base.fr.i18n | 1 - apps/regression/base.it.i18n | 1 - apps/regression/base.nl.i18n | 1 - apps/regression/base.pt.i18n | 1 - apps/regression/base.universal.i18n | 1 + apps/shared.de.i18n | 2 -- apps/shared.en.i18n | 2 -- apps/shared.es.i18n | 2 -- apps/shared.fr.i18n | 2 -- apps/shared.it.i18n | 2 -- apps/shared.nl.i18n | 2 -- apps/shared.pt.i18n | 2 -- apps/shared.universal.i18n | 6 ++++-- apps/toolbox.de.i18n | 27 --------------------------- apps/toolbox.en.i18n | 27 --------------------------- apps/toolbox.es.i18n | 27 --------------------------- apps/toolbox.fr.i18n | 27 --------------------------- apps/toolbox.it.i18n | 27 --------------------------- apps/toolbox.nl.i18n | 27 --------------------------- apps/toolbox.pt.i18n | 27 --------------------------- apps/toolbox.universal.i18n | 27 +++++++++++++++++++++++++++ 25 files changed, 33 insertions(+), 213 deletions(-) create mode 100644 apps/toolbox.universal.i18n diff --git a/apps/Makefile b/apps/Makefile index 0fc8247938f..da4dc6bbad3 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -70,7 +70,7 @@ 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, \ diff --git a/apps/regression/base.de.i18n b/apps/regression/base.de.i18n index b49e91e7c6f..619e8a0bae3 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 7b257c653e4..8ee05f87a6e 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 147304a40dc..30fa05f5168 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 c8f80d87597..21a47ed37ad 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 06976e19610..a7976dea5ad 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 f5411a0a0ba..b3176cd5aeb 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" diff --git a/apps/regression/base.pt.i18n b/apps/regression/base.pt.i18n index e5db50f79c3..561ea59ed39 100644 --- a/apps/regression/base.pt.i18n +++ b/apps/regression/base.pt.i18n @@ -1,7 +1,6 @@ RegressionApp = "Regressão" RegressionAppCapital = "REGRESSÃO" Regression = "Regressão" -Reg = "reg" MeanDot = "média" RegressionCurve = "Curva de regressão" XPrediction = "Predição dado X" diff --git a/apps/regression/base.universal.i18n b/apps/regression/base.universal.i18n index a26602af1e9..e974d8a9fc3 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/shared.de.i18n b/apps/shared.de.i18n index b3a070476a6..6e42dfbaa76 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -67,7 +67,6 @@ Orthonormal = "Orthonormal" Plot = "Graphen zeichnen" PoolMemoryFull1 = "Der Arbeitsspeicher ist voll." PoolMemoryFull2 = "Versuchen Sie es erneut." -Rad = "rad" Rename = "Umbenennen" RoundAbscissa = "Ganzzahl" Sci = "wiss" @@ -92,4 +91,3 @@ ValuesTab = "Tabelle" Warning = "Achtung" XEnd = "X Endwert" XStart = "X Startwert" -Zoom = "Zoom" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index bac1737fc14..5c040516810 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -67,7 +67,6 @@ Orthonormal = "Orthonormal" Plot = "Plot graph" PoolMemoryFull1 = "The working memory is full." PoolMemoryFull2 = "Try again." -Rad = "rad" Rename = "Rename" RoundAbscissa = "Integer" Sci = "sci" @@ -92,4 +91,3 @@ ValuesTab = "Table" Warning = "Warning" XEnd = "X end" XStart = "X start" -Zoom = "Zoom" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 310838d1732..8458e6a54f8 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -67,7 +67,6 @@ 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" @@ -92,4 +91,3 @@ ValuesTab = "Tabla" Warning = "Cuidado" XEnd = "X fin" XStart = "X inicio" -Zoom = "Zoom" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index e96fa97fd9d..17c188870d6 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -67,7 +67,6 @@ 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" @@ -92,4 +91,3 @@ ValuesTab = "Tableau" Warning = "Attention" XEnd = "X fin" XStart = "X début" -Zoom = "Zoom" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 854a83b2ff7..4622a48882f 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -67,7 +67,6 @@ Orthonormal = "Ortogonale" Plot = "Traccia grafico" PoolMemoryFull1 = "La memoria di lavoro è piena." PoolMemoryFull2 = "Riprovare." -Rad = "rad" Rename = "Rinominare" RoundAbscissa = "Ascisse intere" Sci = "sci" @@ -92,4 +91,3 @@ ValuesTab = "Tabella" Warning = "Attenzione" XEnd = "X finale" XStart = "X iniziale" -Zoom = "Zoom" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index ed269f4ca22..7722c9cad17 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -67,7 +67,6 @@ Orthonormal = "Orthonormaal" Plot = "Grafiek plotten" PoolMemoryFull1 = "Het werkgeheugen is vol." PoolMemoryFull2 = "Opnieuw proberen." -Rad = "rad" Rename = "Hernoem" RoundAbscissa = "Geheel getal" Sci = "sci" @@ -92,4 +91,3 @@ ValuesTab = "Tabel" Warning = "Waarschuwing" XEnd = "X einde" XStart = "X begin" -Zoom = "Zoom" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index d31171007c2..f78ae8ee5a3 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -67,7 +67,6 @@ 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" @@ -92,4 +91,3 @@ ValuesTab = "Tabela" Warning = "Atenção" XEnd = "X fim" XStart = "X início" -Zoom = "Zoom" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 798c5e2be63..4e0072a5e52 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -167,6 +167,7 @@ RootCommandWithArg = "root(x,n)" RoundCommandWithArg = "round(x,n)" RowEchelonFormCommandWithArg = "ref(M)" R = "r" +Rad = "rad" Shift = "shift" Sigma = "σ" SinhCommandWithArg = "sinh(x)" @@ -177,6 +178,8 @@ Sxy = "∑xy" T = "t" TanhCommandWithArg = "tanh(x)" Theta = "θ" +ThetaMax = "θmax" +ThetaMin = "θmin" TMax = "Tmax" TMin = "Tmin" TraceCommandWithArg = "trace(M)" @@ -188,5 +191,4 @@ YAuto = "Y auto" YMax = "Ymax" YMin = "Ymin" Y = "y" -ThetaMax = "θmax" -ThetaMin = "θmin" +Zoom = "Zoom" diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 9e91443fdf6..f5d5d8634f8 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Pikometer" UnitDistanceAstronomicalUnit = "Astronomische Einheit" UnitDistanceLightYear = "Lichtjahr" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Meile" UnitDistanceYard = "Yard" UnitDistanceFoot = "Fuß" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Mikroampere" UnitTemperatureMenu = "Temperatur" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" 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" @@ -76,33 +61,21 @@ 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 = "Electrical conductance" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Magnetfeld" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Elektrische Induktivität" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Fläche" UnitSurfaceAcre = "Morgen" UnitSurfaceHectar = "Hektar" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 153b68a6dca..9d0eef2b1fd 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Mile" UnitDistanceYard = "Yard" UnitDistanceFoot = "Foot" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" UnitAmountMoleMicro = "Micromole" UnitLuminousIntensityMenu = "Luminous intensity" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequency" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Force" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Pressure" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" UnitEnergyElectronVoltMilli = "Millielectronvolt" UnitPowerMenu = "Power" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Electric charge" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Electric potential" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Electrical capacitance" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Electrical resistance" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Electrical conductance" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Magnetic field" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Electrical inductance" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Area" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 0bf1900e038..77166234b16 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Milla" UnitDistanceYard = "Yardas" UnitDistanceFoot = "Pie" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperature" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Amount of substance" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" UnitAmountMoleMicro = "Micromole" UnitLuminousIntensityMenu = "Luminous intensity" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequency" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Force" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Pressure" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" UnitEnergyElectronVoltMilli = "Millielectronvolt" UnitPowerMenu = "Power" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Electric charge" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Electric potential" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Electrical capacitance" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Electrical resistance" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Electrical conductance" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Magnetic field" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Electrical inductance" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Area" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index b5920b07d41..b2ecff04f97 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanomètre" UnitDistanceMeterPico = "Picomètre" UnitDistanceAstronomicalUnit = "Unité astronomique" UnitDistanceLightYear = "Année-lumière" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Mile" UnitDistanceYard = "Yard" UnitDistanceFoot = "Pied" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Température" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantité de matière" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" UnitAmountMoleMicro = "Micromole" UnitLuminousIntensityMenu = "Intensité lumineuse" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Fréquence" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Mégahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Force" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Pression" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosphère" UnitEnergyMenu = "Énergie" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Électronvolt" UnitEnergyElectronVoltMega = "Mégaélectronvolt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloélectronvolt" UnitEnergyElectronVolt = "Électronvolt" UnitEnergyElectronVoltMilli = "Milliélectronvolt" UnitPowerMenu = "Puissance" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Mégawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Charge électrique" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Tension électrique" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Capacité électrique" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Résistance électrique" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Conductance électrique" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Induction électromagnétique" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Inductance" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Superficie" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index efd60309bc8..b9a149866f1 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanometro" UnitDistanceMeterPico = "Picometro" UnitDistanceAstronomicalUnit = "Unità astronomica" UnitDistanceLightYear = "Anno luce" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Milla" UnitDistanceYard = "Iarda" UnitDistanceFoot = "Piede" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Milliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantità de materia" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Millimole" UnitAmountMoleMicro = "Micromole" UnitLuminousIntensityMenu = "Intensità luminosa" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequenza" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Forza" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Pressione" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosfera" UnitEnergyMenu = "Energia" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" UnitEnergyElectronVoltMilli = "Millielectronvolt" UnitPowerMenu = "Potenza" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Carica elettrica" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Tensione elettrica" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Capacità elettrica" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Resistenza elettrica" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Conduttanza elettrica" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Induzione elettromagnetica" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Induttanza" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Superficie" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Ettaro" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index f1aecba0c3f..bf80e5c8fe6 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" UnitDistanceAstronomicalUnit = "Astronomische eenheid" UnitDistanceLightYear = "Lichtjaar" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Mijl" UnitDistanceYard = "Yard" UnitDistanceFoot = "Voet" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampère" UnitCurrentAmpereMilli = "Milliampère" UnitCurrentAmpereMicro = "Microampère" UnitTemperatureMenu = "Temperatuur" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Hoeveelheid stof" UnitAmountMole = "Mol" UnitAmountMoleMilli = "Millimol" UnitAmountMoleMicro = "Micromol" UnitLuminousIntensityMenu = "Lichtsterkte" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequentie" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Kracht" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Millinewton" UnitPressureMenu = "Druk" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosfeer" UnitEnergyMenu = "Energie" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Millijoule" UnitEnergyEletronVoltMenu = "Elektronvolt" UnitEnergyElectronVoltMega = "Megaelektronvolt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloelektronvolt" UnitEnergyElectronVolt = "Elektronvolt" UnitEnergyElectronVoltMilli = "Millielektronvolt" UnitPowerMenu = "Vermogen" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Milliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Elektrische lading" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Elektrisch potentiaal" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Millivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Elektrische capaciteit" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Millifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Elektrische weerstand" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Elektrische geleidbaarheid" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Magnetisch veld" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Zelfinductie" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Oppervlakte" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 7fb81d46e40..90d7fffa4d7 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -21,7 +21,6 @@ UnitDistanceMeterNano = "Nanómetro" UnitDistanceMeterPico = "Picómetro" UnitDistanceAstronomicalUnit = "Unidade astronómica" UnitDistanceLightYear = "Ano-luz" -UnitDistanceParsec = "Parsec" UnitDistanceMile = "Milha" UnitDistanceYard = "Jarda" UnitDistanceFoot = "Pé" @@ -42,33 +41,19 @@ UnitCurrentAmpere = "Ampere" UnitCurrentAmpereMilli = "Miliampere" UnitCurrentAmpereMicro = "Microampere" UnitTemperatureMenu = "Temperatura" -UnitTemperatureKelvin = "Kelvin" -UnitTemperatureCelsius = "Celsius" -UnitTemperatureFahrenheit = "Fahrenheit" UnitAmountMenu = "Quantidade da substância" UnitAmountMole = "Mole" UnitAmountMoleMilli = "Milimole" UnitAmountMoleMicro = "Micromole" UnitLuminousIntensityMenu = "Intensidade luminosa" -UnitLuminousIntensityCandela = "Candela" UnitFrequencyMenu = "Frequência" -UnitFrequencyHertzGiga = "Gigahertz" UnitFrequencyHertzMega = "Megahertz" -UnitFrequencyHertzKilo = "Kilohertz" -UnitFrequencyHertz = "Hertz" UnitForceMenu = "Força" -UnitForceNewtonKilo = "Kilonewton" -UnitForceNewton = "Newton" UnitForceNewtonMilli = "Milinewton" UnitPressureMenu = "Pressão" -UnitPressurePascal = "Pascal" UnitPressurePascalHecto = "Hectopascal" -UnitPressureBar = "Bar" UnitPressureAtm = "Atmosfera" UnitEnergyMenu = "Energia" -UnitEnergyJouleMenu = "Joule" -UnitEnergyJouleKilo = "Kilojoule" -UnitEnergyJoule = "Joule" UnitEnergyJouleMilli = "Milijoule" UnitEnergyEletronVoltMenu = "Eletrão-volt" UnitEnergyElectronVoltMega = "Megaeletrão-volt" @@ -76,33 +61,21 @@ UnitEnergyElectronVoltKilo = "Kiloeletrão-volt" UnitEnergyElectronVolt = "Eletrão-volt" UnitEnergyElectronVoltMilli = "Milieletrão-volt" UnitPowerMenu = "Potência" -UnitPowerWattGiga = "Gigawatt" UnitPowerWattMega = "Megawatt" -UnitPowerWattKilo = "Kilowatt" -UnitPowerWatt = "Watt" UnitPowerWattMilli = "Miliwatt" UnitPowerWattMicro = "Microwatt" UnitElectricChargeMenu = "Carga elétrica" -UnitChargeCoulomb = "Coulomb" UnitPotentialMenu = "Potência elétrica" -UnitPotentialVoltKilo = "Kilovolt" -UnitPotentialVolt = "Volt" UnitPotentialVoltMilli = "Milivolt" UnitPotentialVoltMicro = "Microvolt" UnitCapacitanceMenu = "Capacidade elétrica" -UnitCapacitanceFarad = "Farad" UnitCapacitanceFaradMilli = "Milifarad" UnitCapacitanceFaradMicro = "Microfarad" UnitResistanceMenu = "Resistência elétrica" -UnitResistanceOhmKilo = "Kiloohm" -UnitResistanceOhm = "Ohm" UnitConductanceMenu = "Condutância elétrica" -UnitConductanceSiemens = "Siemens" UnitConductanceSiemensMilli = "Milisiemens" UnitMagneticFieldMenu = "Campo magnético" -UnitMagneticFieldTesla = "Tesla" InductanceMenu = "Indutância" -UnitInductanceHenry = "Henry" UnitSurfaceMenu = "Área" UnitSurfaceAcre = "Acre" UnitSurfaceHectar = "Hectare" diff --git a/apps/toolbox.universal.i18n b/apps/toolbox.universal.i18n new file mode 100644 index 00000000000..a9e9fb7bff6 --- /dev/null +++ b/apps/toolbox.universal.i18n @@ -0,0 +1,27 @@ +UnitDistanceParsec= "Parsec" +UnitTemperatureKelvin = "Kelvin" +UnitTemperatureCelsius = "Celsius" +UnitTemperatureFahrenheit = "Fahrenheit" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitPressurePascal = "Pascal" +UnitPressureBar = "Bar" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitChargeCoulomb = "Coulomb" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitCapacitanceFarad = "Farad" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceSiemens = "Siemens" +UnitMagneticFieldTesla = "Tesla" +UnitInductanceHenry = "Henry" From 7e5ddcd36b6d851c3b8b77717dd4abcf656875a2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 23 Sep 2020 15:27:14 +0200 Subject: [PATCH 221/560] [python/turtle] Prevent position overflow from turtle Change-Id: Ieb8a7b64bab1ce6fac23ca4c8dcb43467b8d4d5c --- python/port/mod/turtle/turtle.cpp | 15 ++++++++++++--- python/port/mod/turtle/turtle.h | 8 ++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/python/port/mod/turtle/turtle.cpp b/python/port/mod/turtle/turtle.cpp index 0b8f85c90c5..abba04f4605 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 bf0045ee912..e6480839607 100644 --- a/python/port/mod/turtle/turtle.h +++ b/python/port/mod/turtle/turtle.h @@ -82,6 +82,13 @@ class Turtle { 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 @@ class Turtle { 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, From 91d943842136032face8968b6b1acd23cf2c209c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 28 Sep 2020 11:19:58 +0200 Subject: [PATCH 222/560] [poincare] Fix forbidden array access Change-Id: I3de76c114f6712ee08007601766577d87ed96877 --- poincare/src/unit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 976f8ff1d1d..9bb2370d03b 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -314,7 +314,7 @@ bool UnitNode::Representative::canParse(const char * symbol, size_t length, cons for (size_t i = 0; i < Prefix::k_numberOfPrefixes; i++) { const Prefix * pre = Prefix::Prefixes() + i; const char * prefixSymbol = pre->symbol(); - if (prefixSymbol[length] == 0 + if (strlen(prefixSymbol) == length && canPrefix(pre, true) && strncmp(symbol, prefixSymbol, length) == 0) { From 857ca4eecd158bb1f3adb2509dd393907a688bc2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 29 Sep 2020 11:21:11 +0200 Subject: [PATCH 223/560] [poincare/trigonometry] Fix wrong use of pi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Trigonometry::shallowReduceInverseFunction, the reduction of atan(1/x) used the hard value of pi instead of its equivalent in the selected angle unit. This fixes a bug causing atan(1/tan(40°)) to be equal to π/2-40 instead of 50. Change-Id: Ifd7df5cd168fadf3146eee168c0bc2136ea15a7b --- poincare/src/trigonometry.cpp | 4 +++- poincare/test/simplification.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index 209aebcc787..a3eb841776d 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -331,14 +331,16 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expression * 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/test/simplification.cpp b/poincare/test/simplification.cpp index dcac131d55a..51debe5b5e4 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -948,7 +948,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); From a89878a24d6093ca842f57ad05936739acdb4606 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 25 Sep 2020 11:52:23 +0200 Subject: [PATCH 224/560] [poincare] Allow more BinomialCoefficient exact results Change-Id: I9dbbf9471ee6d9e12fe4861a5b11990858382562 --- poincare/src/binomial_coefficient.cpp | 28 +++++++++++++++++---------- poincare/test/approximation.cpp | 19 +++++++++--------- poincare/test/simplification.cpp | 5 +++++ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/poincare/src/binomial_coefficient.cpp b/poincare/src/binomial_coefficient.cpp index 7beacb5fb08..a14ef202913 100644 --- a/poincare/src/binomial_coefficient.cpp +++ b/poincare/src/binomial_coefficient.cpp @@ -87,32 +87,40 @@ Expression BinomialCoefficient::shallowReduce(Context * context) { } if (!r0.isInteger()) { - // Generalized binomial coefficient + // 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 - return *this; - } - /* 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)) { + // 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/test/approximation.cpp b/poincare/test/approximation.cpp index 30b2d57d57a..9852fb2bd30 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -249,6 +249,16 @@ QUIZ_CASE(poincare_approximation_function) { 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, Metric, Cartesian, 6); // FIXME: precision problem assert_expression_approximates_to("binompdf(4.4, 9, 0.7)", "0.073513818"); @@ -461,15 +471,6 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximation_is_bounded("randint(4,45)", 4.0f, 45.0f, true); assert_expression_approximation_is_bounded("randint(4,45)", 4.0, 45.0, true); - - 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"); } QUIZ_CASE(poincare_approximation_trigonometry_functions) { diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 51debe5b5e4..b8bc0627615 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -687,6 +687,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"); From 073893ae481279133915906e55743573b21bd1a5 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 28 Sep 2020 16:30:37 +0200 Subject: [PATCH 225/560] [poincare] Exact undef results with GCD and LCM Change-Id: Iba39cd6140d8ef5b3fd24e0f53c9e28fbd57d438 --- .../include/poincare/great_common_divisor.h | 2 +- .../include/poincare/least_common_multiple.h | 2 +- poincare/include/poincare/n_ary_expression.h | 2 +- poincare/src/expression.cpp | 5 +++-- poincare/src/great_common_divisor.cpp | 6 +++--- poincare/src/least_common_multiple.cpp | 6 +++--- poincare/src/n_ary_expression.cpp | 17 +++++++++++++++-- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index f01fc5dcfea..d5a6c4ae3cc 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -42,7 +42,7 @@ class GreatCommonDivisor final : public NAryExpression { 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/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index c65550cebe6..1feda8cb7bd 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -42,7 +42,7 @@ class LeastCommonMultiple final : public NAryExpression { 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/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index ed5a65846ee..64dedd156df 100644 --- a/poincare/include/poincare/n_ary_expression.h +++ b/poincare/include/poincare/n_ary_expression.h @@ -55,7 +55,7 @@ class NAryExpression : public Expression { node()->sortChildrenInPlace(order, context, canSwapMatrices, canBeInterrupted); } NAryExpressionNode * node() const { return static_cast(Expression::node()); } - Expression checkChildrenAreRationalIntegers(Context * context); + Expression checkChildrenAreRationalIntegersAndUpdate(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index a26abadea75..cc407c89238 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -289,12 +289,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; diff --git a/poincare/src/great_common_divisor.cpp b/poincare/src/great_common_divisor.cpp index 95aa3205e5d..51d6a6fcc61 100644 --- a/poincare/src/great_common_divisor.cpp +++ b/poincare/src/great_common_divisor.cpp @@ -16,7 +16,7 @@ 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) { @@ -36,7 +36,7 @@ Expression GreatCommonDivisor::shallowBeautify(Context * context) { return *this; } -Expression GreatCommonDivisor::shallowReduce(Context * context) { +Expression GreatCommonDivisor::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -51,7 +51,7 @@ Expression GreatCommonDivisor::shallowReduce(Context * context) { // Step 1: check that all children are compatible { - Expression checkChildren = checkChildrenAreRationalIntegers(context); + Expression checkChildren = checkChildrenAreRationalIntegersAndUpdate(reductionContext); if (!checkChildren.isUninitialized()) { return checkChildren; } diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 9a1e5005620..6d49b02a493 100644 --- a/poincare/src/least_common_multiple.cpp +++ b/poincare/src/least_common_multiple.cpp @@ -16,7 +16,7 @@ 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) { @@ -36,7 +36,7 @@ Expression LeastCommonMultiple::shallowBeautify(Context * context) { return *this; } -Expression LeastCommonMultiple::shallowReduce(Context * context) { +Expression LeastCommonMultiple::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -51,7 +51,7 @@ Expression LeastCommonMultiple::shallowReduce(Context * context) { // Step 1: check that all children are compatible { - Expression checkChildren = checkChildrenAreRationalIntegers(context); + Expression checkChildren = checkChildrenAreRationalIntegersAndUpdate(reductionContext); if (!checkChildren.isUninitialized()) { return checkChildren; } diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index 2303cc0d460..a64be96c9be 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -74,13 +74,26 @@ int NAryExpression::allChildrenAreReal(Context * context) const { return result; } -Expression NAryExpression::checkChildrenAreRationalIntegers(Context * context) { +Expression NAryExpression::checkChildrenAreRationalIntegersAndUpdate(ExpressionNode::ReductionContext reductionContext) { for (int i = 0; i < numberOfChildren(); ++i) { Expression c = childAtIndex(i); - if (c.deepIsMatrix(context)) { + 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()); + 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()) { From 586e53bb42c16ceea312323aa34de7b319f6ad45 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 18 Sep 2020 15:23:53 +0200 Subject: [PATCH 226/560] [i18n] Set International as default country When launching a firmware without onboarding, starts with International selected by default instead of Canada. Change-Id: Ice5cde4210c4c2934c909d97296460707343773f --- apps/country_preferences.csv | 2 +- build/config.mak | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/country_preferences.csv b/apps/country_preferences.csv index 55b1965051e..e900d55d7a0 100644 --- a/apps/country_preferences.csv +++ b/apps/country_preferences.csv @@ -1,4 +1,5 @@ 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 @@ -8,4 +9,3 @@ IT,StandardOnly,CumulatedFrequency,Metric,Default NL,All,MedianOfSublist,Metric,Default PT,StandardOnly,MedianOfSublist,Metric,Default US,StandardOnly,MedianOfSublist,Imperial,HidePython -WW,StandardOnly,MedianOfSublist,Metric,Default diff --git a/build/config.mak b/build/config.mak index e8e8074c600..e4b5c10d438 100644 --- a/build/config.mak +++ b/build/config.mak @@ -6,7 +6,7 @@ DEBUG ?= 0 EPSILON_VERSION ?= 14.4.1 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es -EPSILON_COUNTRIES ?= CA DE ES FR GB IT NL PT US WW +EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US EPSILON_GETOPT ?= 0 EPSILON_TELEMETRY ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 From ac2dd9cdf03aa439824dad481cc978467403f068 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 18 Sep 2020 15:29:26 +0200 Subject: [PATCH 227/560] [i18n] Translated "Tonne" to "Metric Ton" Change-Id: I515d78445065a94cc387781ee87a5fa23b3c2df2 --- apps/toolbox.en.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 9d0eef2b1fd..becb8628eb2 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -31,7 +31,7 @@ UnitMassGram = "Gram" UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" UnitMassGramNano = "Nanogram" -UnitMassTonne = "Tonne" +UnitMassTonne = "Metric Ton" UnitMassOunce = "Ounce" UnitMassPound = "Pound" UnitMassShortTon = "Short Ton" From 4d0950fa1eb58f9b9fd8a0c37329aba45b229bd2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 18 Sep 2020 16:50:15 +0200 Subject: [PATCH 228/560] [ion/simulator] Handle typing % on simulator Change-Id: Ib47786659e710e473f75d1c6887a9794fad0a75f --- ion/src/simulator/shared/events_keyboard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 26d1ba2089d..79cfb89302a 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -133,7 +133,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, From 4ba0603e0c9e250d57e268aaacd9c6db9f483c8c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 23 Sep 2020 12:22:29 +0200 Subject: [PATCH 229/560] [apps/calculation] Duplicate additional result Fix duplicate additional results on units by comparing serialization. Change-Id: Ia360ae7846716a801253b60fcf000240834528bc --- .../additional_outputs/unit_list_controller.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index af0cc851f06..f3463b2488f 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -46,18 +46,25 @@ void UnitListController::setExpression(Poincare::Expression e) { Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem); numberOfExpressions++; - // 3. Get rid of duplicates + /* 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 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); - int currentExpressionIndex = 1; + int currentExpressionIndex = 0; while (currentExpressionIndex < numberOfExpressions) { bool duplicateFound = false; + 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(expressions[currentExpressionIndex])) { + char buffer2[buffersSize]; + int size2 = PoincareHelpers::Serialize(comparedExpression, buffer2, buffersSize); + if (size1 == size2 && strcmp(buffer1, buffer2) == 0) { numberOfExpressions--; // Shift next expressions for (int j = currentExpressionIndex; j < numberOfExpressions; j++) { From d862d5503c36926b242e9431e9e4653d6f9de63a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 10:45:39 +0200 Subject: [PATCH 230/560] [poincare/unit] Fix rational exponent rounding In metric, sqrt(N) would be simplified to _N_kg^-0.5_m^-0.5_s instead of _kg^0.5_m^0.5_s^-1. We solve this issue by preventing simplification using derived units in presence of rational exponents. Change-Id: I97118bb32c963809c8d176b7b297d1682965e9af --- poincare/src/multiplication.cpp | 7 +++---- poincare/src/unit.cpp | 6 ++++++ poincare/test/simplification.cpp | 8 ++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 9db2b6c43c1..e3dfcf3f71c 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -440,10 +440,9 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu * - Repeat those steps until no more simplification is possible. */ Multiplication unitsAccu = Multiplication::Builder(); - /* If exponents are not integers, FromBaseUnits will return the closest - * representation of units with base units and integer exponents. - * It cause no problem because once the best derived units are found, - * units is divided then multiplied by them. */ + /* 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; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 9bb2370d03b..90108569583 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -128,6 +128,12 @@ UnitNode::Vector UnitNode::Vector::FromBaseUnits(const Expression base 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. */ diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index b8bc0627615..8ccc7718bdb 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -361,6 +361,13 @@ QUIZ_CASE(poincare_simplification_units) { 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"); assert_parsed_expression_simplify_to("_m-_m", "0×_m"); @@ -528,6 +535,7 @@ QUIZ_CASE(poincare_simplification_units) { 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"); } QUIZ_CASE(poincare_simplification_power) { From 4e771fdb544a0fa82b73c122221ffd48cfec629b Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 11:06:28 +0200 Subject: [PATCH 231/560] [poincare/unit] Missing prefix reset When tuning the representative without tuning the prefix, the prefix would not be reset to its default value, causing a problem for masses, whose base prefix depend on the representative (k for grams, none for the rest). Fixes a crash when typing sqrt(_N) with country set to US. Change-Id: Iade900de5fb31e06fdfafef34ae27da3f36983fa --- poincare/src/unit.cpp | 6 +++++- poincare/test/simplification.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 90108569583..f92b0bc7a4c 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -922,11 +922,15 @@ void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, Ex 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(); + } + 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); } - if (optimizePrefix && bestPrefix != node()->prefix()) { + if (bestPrefix != node()->prefix()) { *value = *value * std::pow(10., exponent * (node()->prefix()->exponent() - bestPrefix->exponent())); node()->setPrefix(bestPrefix); } diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 8ccc7718bdb..32b5bf580e9 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -536,6 +536,7 @@ QUIZ_CASE(poincare_simplification_units) { 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); } QUIZ_CASE(poincare_simplification_power) { From ddbe76fec5c47e22bcf4426c32bb166ccac2c0a5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 11:36:08 +0200 Subject: [PATCH 232/560] [poincare/unit] Add missing additonal results Additional results are proposed for all units combinations that include a non-base unit, in addition to units with special results. Change-Id: I91f2a1a43c72bad0327132396534a223e87b06a2 --- poincare/src/unit.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index f92b0bc7a4c..d7af01d2a27 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -764,14 +764,20 @@ void Unit::ChooseBestRepresentativeAndPrefixForValue(Expression units, double * bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Preferences::UnitFormat unitFormat) { UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); const Representative * representative = Representative::RepresentativeForDimension(vector); - return representative != nullptr - && ((unit.type() == ExpressionNode::Type::Unit && !static_cast(unit).isBaseUnit()) - || representative->hasAdditionalExpressions(value, unitFormat)); + + ExpressionTypeTest isNonBase = [](const Expression e, const void * context) { + return e.type() == ExpressionNode::Type::Unit && !e.convert().isBaseUnit(); + }; + + return (representative != nullptr && representative->hasAdditionalExpressions(value, unitFormat)) + || unit.hasExpression(isNonBase, nullptr); } int Unit::SetAdditionalExpressions(Expression units, double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) { const Representative * representative = units.type() == ExpressionNode::Type::Unit ? static_cast(units).node()->representative() : UnitNode::Representative::RepresentativeForDimension(UnitNode::Vector::FromBaseUnits(units)); - assert(representative); + if (!representative) { + return 0; + } return representative->setAdditionalExpressions(value, dest, availableLength, reductionContext); } From 10f8e2f507819b965fdb250fece51a7fa71a7dc4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 24 Sep 2020 12:00:59 +0200 Subject: [PATCH 233/560] [poicare/unit] Rename method hasAdditionalExpressions -> hasSpecialAdditionalExpressions Change-Id: Ia5200402b23ab5adf0f94dabe027955a86d1174d --- poincare/include/poincare/unit.h | 21 ++++++++++++--------- poincare/src/unit.cpp | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 8c759494c51..d6c22f381fc 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -112,7 +112,10 @@ class UnitNode final : public ExpressionNode { 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); } - virtual bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const { return false; } + /* 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; } @@ -146,7 +149,7 @@ class UnitNode final : public ExpressionNode { int numberOfRepresentatives() const override { return 7; } const Representative * representativesOfSameDimension() const override; bool isBaseUnit() const override { return this == representativesOfSameDimension(); } - bool hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return m_ratio * value >= representativesOfSameDimension()[1].ratio(); } + 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: using Representative::Representative; @@ -161,7 +164,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return unitFormat == Preferences::UnitFormat::Imperial; } + 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; @@ -177,7 +180,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return unitFormat == Preferences::UnitFormat::Imperial; } + 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; @@ -205,7 +208,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + 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; @@ -277,7 +280,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + 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; @@ -400,7 +403,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + 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; @@ -414,7 +417,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + 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; @@ -426,7 +429,7 @@ class UnitNode final : public ExpressionNode { 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 hasAdditionalExpressions(double value, Preferences::UnitFormat unitFormat) const override { return true; } + 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; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index d7af01d2a27..52ba22f4d80 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -769,7 +769,7 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere return e.type() == ExpressionNode::Type::Unit && !e.convert().isBaseUnit(); }; - return (representative != nullptr && representative->hasAdditionalExpressions(value, unitFormat)) + return (representative != nullptr && representative->hasSpecialAdditionalExpressions(value, unitFormat)) || unit.hasExpression(isNonBase, nullptr); } From bc7ca057a084bcd8f9fa8d93d9769fb2ecb24c2d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 12:37:05 +0200 Subject: [PATCH 234/560] [apps/calculation] Add Joules to results on energy Change-Id: Ib775b1bd2bac1fd3bf755a9a4d3d5b3739a67cfa --- poincare/include/poincare/unit.h | 2 ++ poincare/src/unit.cpp | 17 ++++++++++++----- poincare/test/expression_properties.cpp | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index d6c22f381fc..c44138a4153 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -653,6 +653,8 @@ class Unit : public Expression { 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; diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 52ba22f4d80..237f45200b9 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -58,6 +58,7 @@ constexpr const int Unit::k_kelvinRepresentativeIndex, Unit::k_celsiusRepresentativeIndex, Unit::k_fahrenheitRepresentativeIndex, + Unit::k_jouleRepresentativeIndex, Unit::k_electronVoltRepresentativeIndex, Unit::k_wattRepresentativeIndex, Unit::k_hectareRepresentativeIndex, @@ -526,26 +527,32 @@ int UnitNode::TemperatureRepresentative::setAdditionalExpressions(double value, int UnitNode::EnergyRepresentative::setAdditionalExpressions(double value, Expression * dest, int availableLength, ExpressionNode::ReductionContext reductionContext) const { assert(availableLength >= 2); - /* 1. Convert into Wh + 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[0] = Multiplication::Builder( + dest[index++] = Multiplication::Builder( Float::Builder(adjustedValue * std::pow(10., -wattPrefix->exponent())), Multiplication::Builder( Unit::Builder(watt, wattPrefix), Unit::Builder(hour, Prefix::EmptyPrefix()))); - /* 2. Convert into eV */ + /* 3. Convert into eV */ const Representative * eV = representativesOfSameDimension() + Unit::k_electronVoltRepresentativeIndex; adjustedValue = value / eV->ratio(); const Prefix * eVPrefix = eV->findBestPrefix(adjustedValue, 1.); - dest[1] = Multiplication::Builder( + dest[index++] = Multiplication::Builder( Float::Builder(adjustedValue * std::pow(10., -eVPrefix->exponent())), Unit::Builder(eV, eVPrefix)); - return 2; + return index; } const UnitNode::Representative * UnitNode::SurfaceRepresentative::standardRepresentative(double value, double exponent, ExpressionNode::ReductionContext reductionContext, const Prefix * * prefix) const { diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index b3d6aa5b58e..d6a80ce21bc 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -413,8 +413,8 @@ QUIZ_CASE(poincare_expression_additional_results) { assert_additional_results_compute_to("-4×_°F", array17, 2); // Energy - const char * array8[2] = {"1×_kW×_h", "2.246943ᴇ13×_TeV"}; - assert_additional_results_compute_to("3.6×_MN_m", array8, 2); + 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); // Volume const char * array9[2] = {"264×_gal+1×_pt+0.7528377×_cup", "1000×_L"}; From 0c1d0195cdcc92ba0929c7b84935304be0c6acbf Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 14:26:24 +0200 Subject: [PATCH 235/560] [poincare/unit] Check uninitialized expressions In Calculation, keeping the BACK key pressed can lead to iffy simplifications and uninitialized expressions beign passed to ShouldDisplayAdditionalOutputs and SetAdditionalExpressions methods. Change-Id: If248ab193c471ff433828fc841e01a391e0c052d --- poincare/src/unit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 237f45200b9..44649c45041 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -769,6 +769,9 @@ void Unit::ChooseBestRepresentativeAndPrefixForValue(Expression units, double * } 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); @@ -781,6 +784,9 @@ bool Unit::ShouldDisplayAdditionalOutputs(double value, Expression unit, Prefere } 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; From f777cad3f78c36d29ae337735f46fb8cfe9e7676 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 25 Sep 2020 16:02:09 +0200 Subject: [PATCH 236/560] [kandinsky/font] Degree symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligned the top of the degree symbol ° with the top of capital C and F, and reduced its right margin. Change-Id: I63f50654afd965186400e451d71be41add9556c8 --- kandinsky/fonts/LargeFont.ttf | Bin 224264 -> 226352 bytes kandinsky/fonts/SmallFont.ttf | Bin 218308 -> 220448 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/kandinsky/fonts/LargeFont.ttf b/kandinsky/fonts/LargeFont.ttf index 0d7966eaef4066a2c4c3ebac6df9eba729ead520..ddf0ade0a6356db845e30df56a300c204921b84e 100644 GIT binary patch delta 57476 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 U4=3MrE05=sa2_uC*p03IHxR@5I{*Lx delta 55202 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 delta 56394 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 Date: Mon, 21 Sep 2020 15:35:44 +0200 Subject: [PATCH 237/560] [escher/toolbox_message_tree] Add indirection The children of a ToolboxMessageTree object can be stored as a simple pointer, representing an array of all the children stored consecutively, or as a double pointer, representing an array storing the addresses of all children. Change-Id: I10134684963aaafc635aaf9a2374d0f3c32d3d0c --- escher/include/escher/toolbox_message_tree.h | 38 +++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index 835bccde57a..1e17b83b0a2 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -10,7 +10,7 @@ class ToolboxMessageTree : public MessageTree { label, text, (insertedText == (I18n::Message)0) ? label : insertedText, - nullptr, + static_cast(0), 0, stripInsertedText); }; @@ -24,7 +24,19 @@ class ToolboxMessageTree : public MessageTree { 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]) { + return ToolboxMessageTree( + label, + (I18n::Message)0, + (I18n::Message)0, + children, + 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; } @@ -34,12 +46,30 @@ class ToolboxMessageTree : public MessageTree { m_children(children), m_text(text), m_insertedText(insertedText), - m_stripInsertedText(stripInsertedText) + m_stripInsertedText(stripInsertedText), + m_childrenConsecutive(true) + {} + 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) {} - const ToolboxMessageTree * m_children; + + 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 From 2e2845370aba9e761b29ddb8efeff8691ea86b3e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 21 Sep 2020 18:02:09 +0200 Subject: [PATCH 238/560] [apps/math_toolbox] Add fork nodes in tree Some nodes of the ToolboxMessageTree can be marked as "forks", dispatching one branch or the other depending on preferences. They are used in the Unit menu to provide a different toolbox depending on the unit system. Change-Id: I591f7abc3d24e682e827a540a9defac1877871b5 --- apps/math_toolbox.cpp | 11 +++++++++++ apps/math_toolbox.h | 2 ++ escher/include/escher/toolbox.h | 5 ++++- escher/include/escher/toolbox_message_tree.h | 9 +++++---- escher/src/toolbox.cpp | 15 +++++++++------ 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 00a32610aaf..f5dd35a862a 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 @@ -384,3 +385,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 77bc6bd56c8..41eb996fa85 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -15,6 +15,8 @@ class MathToolbox : public Toolbox { 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/escher/include/escher/toolbox.h b/escher/include/escher/toolbox.h index e38df51ed3f..f128f54ebb9 100644 --- a/escher/include/escher/toolbox.h +++ b/escher/include/escher/toolbox.h @@ -25,9 +25,12 @@ class Toolbox : public NestedMenuController { 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 1e17b83b0a2..ba8ac5005a3 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -15,23 +15,23 @@ class ToolboxMessageTree : public MessageTree { 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); } 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 { @@ -40,6 +40,7 @@ class ToolboxMessageTree : public MessageTree { 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), diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index e4848e8bd28..9fe23d8b57a 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(); From bad0c81bf01b8e72fbfe6bf099fa209e86de9b1c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Sep 2020 11:02:30 +0200 Subject: [PATCH 239/560] [apps/math_toolbox] Rearrange unit toolbox The layout of the unit toolbox changes depending on the unit system in the selected country. All units have been rearranged by increasing order of magnitude. Change-Id: Ib51759cca88fd6b6122dd5a3a25b8ed04024aaf3 --- apps/math_toolbox.cpp | 271 +++++++++++++++++++++++++----------------- 1 file changed, 163 insertions(+), 108 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index f5dd35a862a..eb828a244bc 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -93,71 +93,102 @@ 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), -}; - -const ToolboxMessageTree unitDistanceImperialChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMileSymbol, I18n::Message::UnitDistanceMile), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceYardSymbol, I18n::Message::UnitDistanceYard), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceFootSymbol, I18n::Message::UnitDistanceFoot), - ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceInchSymbol, I18n::Message::UnitDistanceInch), -}; - -const ToolboxMessageTree unitDistanceChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitDistanceMeterMenu, unitDistanceMeterChildren), - ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, 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), -}; - -const ToolboxMessageTree unitMassGramChildren[] = { - 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[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitMassPoundSymbol, I18n::Message::UnitMassPound), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassOunceSymbol, I18n::Message::UnitMassOunce), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassShortTonSymbol, I18n::Message::UnitMassShortTon), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassLongTonSymbol, I18n::Message::UnitMassLongTon) -}; - -const ToolboxMessageTree unitMassChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitMassGram, unitMassGramChildren), - ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitMassImperialChildren), - ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeYearSymbol, I18n::Message::UnitTimeYear) +}; + +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::UnitDistanceMeter, 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) +}; + +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 * unitMassGramChildren[] = {&unitMassGramMicro, &unitMassGramMilli, &unitMassGram, &unitMassGramKilo, &unitMassTonne}; +const ToolboxMessageTree unitMassGramNode = ToolboxMessageTree::Node(I18n::Message::UnitMassGram, unitMassGramChildren); +const ToolboxMessageTree * unitMassChildrenForImperialToolbox[] = { + &unitMassOunce, + &unitMassPound, + &unitMassShortTon, + &unitMassLongTon, + &unitMassGramNode +}; +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[] = { @@ -167,24 +198,25 @@ const ToolboxMessageTree unitTemperatureChildren[] = { }; 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[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitLuminousIntensityCandelaSymbol, I18n::Message::UnitLuminousIntensityCandela)}; 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[] = { @@ -194,29 +226,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[] = { @@ -224,26 +254,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[] = { @@ -252,38 +282,63 @@ const ToolboxMessageTree unitMagneticFieldChildren[] = { const ToolboxMessageTree unitInductanceChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitInductanceHenrySymbol, I18n::Message::UnitInductanceHenry)}; -const ToolboxMessageTree unitSurfaceChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceAcreSymbol, I18n::Message::UnitSurfaceAcre), - ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar)}; - -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), +const ToolboxMessageTree unitSurfaceChildrenForMetricToolbox[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar), + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceAcreSymbol, I18n::Message::UnitSurfaceAcre) }; - -const ToolboxMessageTree unitVolumeImperialChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeGallonSymbol, I18n::Message::UnitVolumeGallon), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeQuartSymbol, I18n::Message::UnitVolumeQuart), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumePintSymbol, I18n::Message::UnitVolumePint), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeCupSymbol, I18n::Message::UnitVolumeCup), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeFluidOunceSymbol, I18n::Message::UnitVolumeFluidOunce), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTablespoonSymbol, I18n::Message::UnitVolumeTablespoon), - ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeTeaspoonSymbol, I18n::Message::UnitVolumeTeaspoon), -}; - -const ToolboxMessageTree unitVolumeChildren[] = { - ToolboxMessageTree::Node(I18n::Message::UnitVolumeLiter, unitVolumeLiterChildren), - ToolboxMessageTree::Node(I18n::Message::UnitImperialMenu, unitVolumeImperialChildren), +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) +}; + +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::UnitVolumeLiter, 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::UnitSurfaceMenu, unitSurfaceChildren), - ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeChildren), - 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), From c1b0465ca45ef59b8a53a16d280b60ce421d5ce1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Sep 2020 11:15:43 +0200 Subject: [PATCH 240/560] [apps/i18n] Add 'Metric' message 'Metric' is used to label the toolbox sub-menus containing metric units when the selected country uses the imperial system. Change-Id: I6f40bcd3151be614a5a02cdd17c70ea4a91bfc00 --- apps/math_toolbox.cpp | 6 +++--- apps/toolbox.de.i18n | 1 + apps/toolbox.en.i18n | 1 + apps/toolbox.es.i18n | 1 + apps/toolbox.fr.i18n | 1 + apps/toolbox.it.i18n | 1 + apps/toolbox.nl.i18n | 1 + apps/toolbox.pt.i18n | 1 + 8 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index eb828a244bc..49c4f584caa 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -121,7 +121,7 @@ constexpr ToolboxMessageTree unitDistanceYard = ToolboxMessageTree::Leaf(I18n::M 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::UnitDistanceMeter, unitDistanceMeterChildren); +const ToolboxMessageTree unitDistanceMeterNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitDistanceMeterChildren); const ToolboxMessageTree * unitDistanceChildrenForImperialToolbox[] = { &unitDistanceInch, &unitDistanceFoot, @@ -162,7 +162,7 @@ constexpr ToolboxMessageTree unitMassShortTon = ToolboxMessageTree::Leaf(I18n::M constexpr ToolboxMessageTree unitMassLongTon = ToolboxMessageTree::Leaf(I18n::Message::UnitMassLongTonSymbol, I18n::Message::UnitMassLongTon); const ToolboxMessageTree * unitMassGramChildren[] = {&unitMassGramMicro, &unitMassGramMilli, &unitMassGram, &unitMassGramKilo, &unitMassTonne}; -const ToolboxMessageTree unitMassGramNode = ToolboxMessageTree::Node(I18n::Message::UnitMassGram, unitMassGramChildren); +const ToolboxMessageTree unitMassGramNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitMassGramChildren); const ToolboxMessageTree * unitMassChildrenForImperialToolbox[] = { &unitMassOunce, &unitMassPound, @@ -308,7 +308,7 @@ constexpr ToolboxMessageTree unitVolumeQuart = ToolboxMessageTree::Leaf(I18n::Me 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::UnitVolumeLiter, unitVolumeLiterChildren); +const ToolboxMessageTree unitVolumeLiterNode = ToolboxMessageTree::Node(I18n::Message::UnitMetricMenu, unitVolumeLiterChildren); const ToolboxMessageTree * unitVolumeChildrenForImperialToolbox[] = { &unitVolumeTeaspoon, &unitVolumeTablespoon, diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index f5d5d8634f8..5cbe2768e02 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Tasse" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallone" +UnitMetricMenu = "Metrisch" UnitImperialMenu = "Empire" Toolbox = "Werkzeugkasten" AbsoluteValue = "Betragsfunktion" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index becb8628eb2..32a26a0c70b 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Cup" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallon" +UnitMetricMenu = "Metric" UnitImperialMenu = "Imperial" Toolbox = "Toolbox" AbsoluteValue = "Absolute value" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 77166234b16..7b665f55aa0 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Taza" UnitVolumePint = "Pinta" UnitVolumeQuart = "Cuarto" UnitVolumeGallon = "Galón" +UnitMetricMenu = "Métrico" UnitImperialMenu = "Imperial" Toolbox = "Caja de herramientas" AbsoluteValue = "Valor absoluto" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index b2ecff04f97..1b74fe81817 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Tasse" UnitVolumePint = "Pinte" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallon" +UnitMetricMenu = "Métrique" UnitImperialMenu = "Impérial" Toolbox = "Boîte à outils" AbsoluteValue = "Valeur absolue" diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index b9a149866f1..aa96c0474df 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Tazza" UnitVolumePint = "Pinta" UnitVolumeQuart = "Quarto" UnitVolumeGallon = "Gallone" +UnitMetricMenu = "Metrico" UnitImperialMenu = "Imperiale" Toolbox = "Toolbox" AbsoluteValue = "Valore assoluto" diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index bf80e5c8fe6..bdf5a50ff01 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Kop" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" UnitVolumeGallon = "Gallon" +UnitMetricMenu = "Metriek" UnitImperialMenu = "Imperiaal" Toolbox = "Toolbox" AbsoluteValue = "Absolute waarde" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index 90d7fffa4d7..f0779e3d586 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -91,6 +91,7 @@ UnitVolumeCup = "Xícara de chá" UnitVolumePint = "Quartilho" UnitVolumeQuart = "Quarto" UnitVolumeGallon = "Galão" +UnitMetricMenu = "Métrico" UnitImperialMenu = "Imperial" Toolbox = "Caixa de ferramentas" AbsoluteValue = "Valor absoluto" From 5d3069ff34ad718ba8752acdf163546ac847d00e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 12:11:03 +0200 Subject: [PATCH 241/560] [code/catalog] Capitalize 'line feed' in EN Change-Id: I91b8c4b613b9e9dece13fbb0e9635b4aa8dbfd4a --- apps/code/catalog.en.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 6926b3ed215..eaffc09a7b9 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" From d6751e6e039909ed6575ad0c61e9ab97d5c43ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 1 Oct 2020 12:04:07 +0200 Subject: [PATCH 242/560] [apps/shared] CurveView: improve floatLengthToPixelLength to avoid useless computations (and potential overflowing) This fixes the drawing of arrow(100,100,0.1,3) in matplotlib module of python --- apps/shared/curve_view.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index ea477f8788e..6e4c0f272a2 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -118,26 +118,31 @@ float CurveView::pixelToFloat(Axis axis, KDCoordinate p) const { m_curveViewRange->yMax() - p * pixelHeight(); } -float CurveView::floatToPixel(Axis axis, float f) const { - float result = (axis == Axis::Horizontal) ? - (f - m_curveViewRange->xMin()) / pixelWidth() : - (m_curveViewRange->yMax() - f) / pixelHeight(); +static float clippedFloat(float f) { /* Make sure that the returned value is between the maximum and minimum * possible values of KDCoordinate. */ - if (result == NAN) { + if (f == NAN) { return NAN; - } else if (result < KDCOORDINATE_MIN) { + } else if (f < KDCOORDINATE_MIN) { return KDCOORDINATE_MIN; - } else if (result > KDCOORDINATE_MAX) { + } else if (f > KDCOORDINATE_MAX) { return KDCOORDINATE_MAX; } else { - return result; + 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(); + 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 pixelLength = axis == Axis::Horizontal ? pixelWidth() : pixelHeight(); + float dist = f / pixelLength; + return clippedFloat(dist); } float CurveView::floatLengthToPixelLength(float dx, float dy) const { From 5b4fc6ff84e640e769b8e1fc66432ba2d3c72484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 1 Oct 2020 13:38:33 +0200 Subject: [PATCH 243/560] [apps/shared] CurveView: implement CurveView::pixelLength(Axis) --- apps/shared/curve_view.cpp | 7 +++++-- apps/shared/curve_view.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 6e4c0f272a2..1890d2455e7 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -112,6 +112,10 @@ 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() : @@ -140,8 +144,7 @@ float CurveView::floatToPixel(Axis axis, float f) const { } float CurveView::floatLengthToPixelLength(Axis axis, float f) const { - float pixelLength = axis == Axis::Horizontal ? pixelWidth() : pixelHeight(); - float dist = f / pixelLength; + float dist = f / pixelLength(axis); return clippedFloat(dist); } diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 481429489fd..cc882b8f792 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -41,6 +41,7 @@ class CurveView : public View { 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); From 64ff5b4c705b6f9ffa287f231fd2be63d932fb0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 1 Oct 2020 13:39:37 +0200 Subject: [PATCH 244/560] [app/shared] CurveView: simplify implementation of pixelLengthToFloatLength (avoid avoidable overflows) --- apps/shared/curve_view.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 1890d2455e7..c655e54e24b 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -155,8 +155,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 { From 388d1a785e63f4c1f19d01398ab5af675e7699ed Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Fri, 2 Oct 2020 11:25:15 +0200 Subject: [PATCH 245/560] [apps/calculation] Update NL translation Change NL definition in additional results --- apps/calculation/base.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index 478e4a3ec56..51e412cb40c 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -4,7 +4,7 @@ AdditionalResults = "Aanvullende resultaten" DecimalBase = "Decimaal" HexadecimalBase = "Hexadecimaal" BinaryBase = "Binair" -PrimeFactors = "Priemfactoren" +PrimeFactors = "Ontbinding" MixedFraction = "Gemengde breuk" EuclideanDivision = "Geheeltallige deling" AdditionalDeterminant = "Determinant" From 7168188a4405863be3e4e47487dde97b8e4879a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 2 Oct 2020 14:16:10 +0200 Subject: [PATCH 246/560] [ion] Simulator apple: fix rule_label for assets --- ion/src/simulator/shared/apple/helpers.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index 85e4b4f8656..ed1e01d5a16 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 From d8e6b63a2ec87b67a771a638bb18cb1af9217679 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 16:57:35 +0200 Subject: [PATCH 247/560] [sequence] Type stays selected in menu - Create a first-order recursive sequence - Select u_n -> Sequence type --> The second line will be selected, as expected. Change-Id: I2025ebaa9a7b49ee33ff5e63a4c767a858b46850 --- apps/sequence/list/type_parameter_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index 939cefe16fa..e7ad0c56e9d 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -49,7 +49,7 @@ void TypeParameterController::viewDidDisappear() { } void TypeParameterController::didBecomeFirstResponder() { - selectCellAtLocation(0, 0); + selectCellAtLocation(0, m_record == nullptr ? 0 : static_cast(sequence()->type())); Container::activeApp()->setFirstResponder(&m_selectableTableView); } From ab864d10d886150799861fec4a2063cf288ce8fc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 30 Sep 2020 10:12:12 +0200 Subject: [PATCH 248/560] [ion/events] Compare event for repetition This fixes the following bug : (Anywhere with a movable cursor, like a table, a text field or a curve) - press Alpha (once, or twice to activate the lock) - keep pressing a direction --> The cursor would only move once. Change-Id: I46115d1a31ced244615bfdfe08f37dfe7e918d6e --- ion/src/shared/events_keyboard.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 009939ba3b9..2c1cba7e3d1 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -50,6 +50,10 @@ void resetLongRepetition() { ComputeAndSetRepetionFactor(sEventRepetitionCount); } +static Keyboard::Key keyFromState(Keyboard::State state) { + return static_cast(63 - __builtin_clzll(state)); +} + Event getEvent(int * timeout) { assert(*timeout > delayBeforeRepeat); assert(*timeout > delayBetweenRepeat); @@ -66,6 +70,11 @@ Event getEvent(int * timeout) { keysSeenUp |= ~state; keysSeenTransitionningFromUpToDown = keysSeenUp & state; + Keyboard::Key key; + bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); + bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); + bool lock = isLockActive(); + if (keysSeenTransitionningFromUpToDown != 0) { sEventIsRepeating = false; resetLongRepetition(); @@ -75,10 +84,7 @@ Event getEvent(int * timeout) { * processors have an instruction (ARM thumb uses CLZ). * Unfortunately there's no way to express this in standard C, so we have * to resort to using a builtin function. */ - Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); - bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); - bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); - bool lock = isLockActive(); + key = keyFromState(keysSeenTransitionningFromUpToDown); Event event(key, shift, alpha, lock); sLastEventShift = shift; sLastEventAlpha = alpha; @@ -97,10 +103,11 @@ Event getEvent(int * timeout) { // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero // In other words, no new key has been pressed + key = keyFromState(state); + Event event(key, shift, alpha, lock); if (canRepeatEvent(sLastEvent) && state == sLastKeyboardState - && sLastEventShift == state.keyDown(Keyboard::Key::Shift) - && sLastEventAlpha == state.keyDown(Keyboard::Key::Alpha)) + && sLastEvent == event) { int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); if (time >= delay) { From 3f43504398b76656c7eca7ec8dfa1c4e1cad5570 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 30 Sep 2020 16:15:21 +0200 Subject: [PATCH 249/560] [poincare] Check for nan values when inverting matrix Change-Id: Idb542682003348da151975e635958cf909010d16 --- poincare/src/matrix.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 97417781c15..a212b35a6df 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -172,7 +172,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; @@ -181,7 +186,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; } } From e517128a9e4b1c7c3f98a7e0d09051a9f5dae5f9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 1 Oct 2020 10:29:03 +0200 Subject: [PATCH 250/560] [apps/regression] Compute R2 better when dealing with constant regression Change-Id: Ic724d8d96cb723718a1ce57e72132972a782fc5e --- apps/regression/store.cpp | 20 +++++++++++++++----- apps/regression/test/model.cpp | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index d49665b14d6..9e85c8a7498 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -309,10 +309,12 @@ double Store::correlationCoefficient(int series) const { double Store::computeDeterminationCoefficient(int series, Poincare::Context * globalContext) { /* Computes and returns the determination coefficient (R2) of the regression. - * For regressions, it is equal to the square of the correlation coefficient between - * the series Y and the evaluated values from the series X and the selected model - * Computing the coefficient using the latter equality would require more calls to the evaluated - * values and would be less precise. */ + * 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 @@ -327,7 +329,15 @@ double Store::computeDeterminationCoefficient(int series, Poincare::Context * gl double difference = m_data[series][1][k] - mean; sst += difference * difference; } - return sst == 0.0 ? 1.0 : 1.0 - ssr / sst; + 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. + assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional); + return r2; } Model * Store::regressionModel(int index) { diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 4148288c35d..0fadb74fc1f 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -34,7 +34,7 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T double precision = 1e-2; // When trueCoefficients = 0, a DBL_EPSILON reference ensures that the only accepted errors are due to double approximations - double reference = 100.0 * DBL_EPSILON; + double reference = 1e6 * DBL_EPSILON; // Compute and compare the coefficients double * coefficients = store.coefficientsForSeries(series, &context); @@ -45,7 +45,7 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T // Compute and check r2 value and sign double r2 = store.determinationCoefficientForSeries(series, &globalContext); - quiz_assert(r2 >= 0.0); + quiz_assert(r2 <= 1.0 && (r2 >= 0.0 || modelType == Model::Type::Proportional)); quiz_assert(IsApproximatelyEqual(r2, trueR2, precision, reference)); } @@ -82,6 +82,36 @@ QUIZ_CASE(proportional_regression2) { 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}; From fa9c9c15675b22d1a7a2594ff607d5ef45e3da74 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 1 Oct 2020 14:19:13 +0200 Subject: [PATCH 251/560] [apps/regression] Add todos for logistic regressions Change-Id: If2ddc0527d8be89970803de240a6760d037570e2 --- apps/regression/model/logistic_model.cpp | 8 ++++++++ apps/regression/store.cpp | 3 ++- apps/regression/test/model.cpp | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index ec9c50a4844..5587fea8c81 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/store.cpp b/apps/regression/store.cpp index 9e85c8a7498..4de71661712 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -336,7 +336,8 @@ double Store::computeDeterminationCoefficient(int series, Poincare::Context * gl } double r2 = 1.0 - ssr / sst; // Check if regression fit was optimal. - assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional); + // TODO : Optimize Logistic regression. + assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional || seriesRegressionType(series) == Model::Type::Logistic); return r2; } diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 0fadb74fc1f..b914ec1f70f 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -194,6 +194,13 @@ QUIZ_CASE(logistic_regression) { double coefficients2[] = {64.9, 1.0, 297.4}; 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[] = {4.0, 3.0, 21.0, 1.0, 6.0}; + // double y3[] = {0.0, 4.0, 5.0, 4.0, 58.0}; + // double coefficients3[] = {370162529.359743, 4.266439, 31.445238}; + // double r23 = 0.401040; + // assert_regression_is(x3, y3, 5, Model::Type::Logistic, coefficients3, r23); } // Testing column and regression calculation From d2247783309c088ff24cea635edb72c4f0128216 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 5 Oct 2020 15:12:13 +0200 Subject: [PATCH 252/560] [ion/simulator] Temporarly increase stack size on simulator Change-Id: I0f27277ea704f81e316c7ff78fb7bed8c70ff4ae --- ion/src/simulator/shared/main_headless.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ion/src/simulator/shared/main_headless.cpp b/ion/src/simulator/shared/main_headless.cpp index d4e96e18199..8b8fec39c2c 100644 --- a/ion/src/simulator/shared/main_headless.cpp +++ b/ion/src/simulator/shared/main_headless.cpp @@ -15,7 +15,9 @@ constexpr int kHeapSize = 131072; #ifdef NDEBUG -constexpr int kStackSize = 32768; +/* TODO : Reduce stack memory cost in prime factorization to allow running + * tests with the actual stack size */ +constexpr int kStackSize = 32768*10; #else constexpr int kStackSize = 32768*10; // In DEBUG mode, we increase the stack to be able to pass the tests #endif From a326a1f9ff500232ef0ecf93c9cf7e38739f8545 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 6 Oct 2020 10:43:19 +0200 Subject: [PATCH 253/560] [poincare] Fix approximation test Change-Id: I0951e01acf7266e33d05f40de3119b73c656b9a2 --- poincare/test/approximation.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index 9852fb2bd30..fadb677b1d9 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -191,9 +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×𝐢,𝐢]]"); - // TODO: get rid of the neglectable real or imaginary parts - assert_expression_approximates_to("(3+4𝐢)/[[3,4][1,𝐢]]", "[[1+5.5511151231258ᴇ-17×𝐢,-2.2204460492503ᴇ-16+4×𝐢][𝐢,-3×𝐢]]"); - // [[1,4×𝐢][𝐢,-3×𝐢]] is expected + // 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×𝐢"); From b051242f2bbb6301ab053b68539be759a4591f41 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 6 Oct 2020 13:15:56 +0200 Subject: [PATCH 254/560] [test] Avoid high precision number approximation Change-Id: I88537653cea2a399581e9686bd027c9d20953667 --- poincare/test/approximation.cpp | 12 ++++-------- poincare/test/simplification.cpp | 3 +++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index fadb677b1d9..e15204106c0 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -433,14 +433,10 @@ 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([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.2215568862275]]"); - assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,3.3532934131737ᴇ-1][0,1,0,-0.1437125748503][0,0,1,1.2215568862275]]"); - assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,9.1666666666667ᴇ-1,8.3333333333333ᴇ-1][0,1,-0.5][0,0,1]]"); - assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0,0][0,1,0][0,0,1]]"); - assert_expression_approximates_to("ref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,1.4,1.2,1.6][0,1,1.1,1.2][0,0,1,1.221557]]"); - assert_expression_approximates_to("rref([[1,0,3,4][5,7,6,8][0,10,11,12]])", "[[1,0,0,0.3353293][0,1,0,-0.1437126][0,0,1,1.221557]]"); - assert_expression_approximates_to("ref([[0,2,-1][5,6,7][12,11,10]])", "[[1,0.9166667,0.8333333][0,1,-0.5][0,0,1]]"); - assert_expression_approximates_to("rref([[0,2,-1][5,6,7][12,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("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]]"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 32b5bf580e9..fda691e2750 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1048,11 +1048,14 @@ QUIZ_CASE(poincare_simplification_matrix) { 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 From 25ab4b2a52e458875ea6792327152e28526e9fab Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 12:03:22 +0200 Subject: [PATCH 255/560] [shared/localization_controller] Table cropping Forced the top cell of the country table to be cropped in the middle, cutting the text, to avoid the cell looking squashed. Change-Id: I4d126ed19249f47652bd7f2001384b2491cfe6f8 --- apps/shared/localization_controller.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index fc655badfea..92ff1bd2c71 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -56,7 +56,6 @@ void LocalizationController::ContentView::layoutSubviews(bool force) { } if (m_controller->shouldDisplayWarning()) { origin = layoutWarningSubview(force, Metric::CommonTopMargin + origin) + Metric::CommonTopMargin; - m_borderView.setFrame(KDRect(Metric::CommonLeftMargin, origin, bounds().width() - Metric::CommonLeftMargin - Metric::CommonRightMargin, Metric::CellSeparatorThickness), force); } origin = layoutTableSubview(force, origin); assert(origin <= bounds().height()); @@ -82,6 +81,20 @@ KDCoordinate LocalizationController::ContentView::layoutTableSubview(bool force, 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; } From 59b2a64830c9b6738ee1285967ba964e3a1aafc5 Mon Sep 17 00:00:00 2001 From: Serenela Moreira Date: Thu, 8 Oct 2020 14:41:12 +0200 Subject: [PATCH 256/560] [pt] corrected prime factors And improved translation of another detail --- apps/calculation/base.pt.i18n | 2 +- apps/regression/base.pt.i18n | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index 2f5720e5e61..941363a04b7 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -4,7 +4,7 @@ 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" diff --git a/apps/regression/base.pt.i18n b/apps/regression/base.pt.i18n index 561ea59ed39..9bdf4806533 100644 --- a/apps/regression/base.pt.i18n +++ b/apps/regression/base.pt.i18n @@ -3,8 +3,8 @@ RegressionAppCapital = "REGRESSÃO" Regression = "Regressão" 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" From 8a5ed92a5673276d535835156394af1c432986e4 Mon Sep 17 00:00:00 2001 From: Roberta Rabotti Date: Fri, 2 Oct 2020 16:55:03 +0200 Subject: [PATCH 257/560] [IT] Translation changes: Prime factors --- apps/calculation/base.it.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index 6e60f096491..ca41028937e 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -4,7 +4,7 @@ AdditionalResults = "Risultati complementari" DecimalBase = "Decimale" HexadecimalBase = "Esadecimale" BinaryBase = "Binario" -PrimeFactors = "Fattori primi" +PrimeFactors = "Fattorizzazione" MixedFraction = "Frazione mista" EuclideanDivision = "Divisione euclidea" AdditionalDeterminant = "Determinante" From 8f117e9d4eb3ed5d76c686b568fabcfa3927a536 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Fri, 9 Oct 2020 10:16:12 +0200 Subject: [PATCH 258/560] [NL] correct spelling mistake --- apps/shared.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 7722c9cad17..da20649c4fd 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -6,7 +6,7 @@ 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" From f64a650ef601b4b8448fa073fea5a4b38ceac96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 9 Oct 2020 16:27:29 +0200 Subject: [PATCH 259/560] [apps] I18n: localize Dutch exam mode messages --- apps/home/base.es.i18n | 4 ++-- apps/shared.de.i18n | 8 ++++---- apps/shared.es.i18n | 8 ++++---- apps/shared.fr.i18n | 8 ++++---- apps/shared.it.i18n | 14 +++++++------- apps/shared.pt.i18n | 8 ++++---- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/home/base.es.i18n b/apps/home/base.es.i18n index 4f04ddc56ac..9477043e258 100644 --- a/apps/home/base.es.i18n +++ b/apps/home/base.es.i18n @@ -1,4 +1,4 @@ Apps = "Aplicaciones" AppsCapital = "APLICACIONES" -ForbidenAppInExamMode1 = "This application is" -ForbidenAppInExamMode2 = "forbidden in exam mode" +ForbidenAppInExamMode1 = "Esta aplicación está prohibida" +ForbidenAppInExamMode2 = "en el modo de examen" diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 6e42dfbaa76..a8e653419a9 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -1,12 +1,12 @@ 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" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 8458e6a54f8..547919edd8b 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -1,12 +1,12 @@ 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" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 17c188870d6..5207340abef 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -1,12 +1,12 @@ 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" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 4622a48882f..bb1c7850a62 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -1,12 +1,12 @@ 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" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index f78ae8ee5a3..35cb54cbb79 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -1,12 +1,12 @@ 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" From bf0f6c7a33988b508175beeff11ee1083de79c4d Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 30 Sep 2020 16:02:48 +0200 Subject: [PATCH 260/560] [Global Context] Fixed rank approximation error for sequences Change-Id: I29c0a55956aee8764e50650b229a49aa8a0f9336 --- apps/shared/global_context.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index a153c9817d2..18bacf7b749 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -2,6 +2,7 @@ #include "continuous_function.h" #include "sequence.h" #include "poincare_helpers.h" +#include #include #include #include @@ -123,16 +124,29 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym /* An function record value has metadata before the expression. To get the * expression, use the function record handle. */ Sequence seq(r); - constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; - char unknownN[bufferSize]; - Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); - float rank = symbol.childAtIndex(0).approximateWithValueForSymbol(unknownN, unknownSymbolValue, ctx, Preferences::sharedPreferences()->complexFormat(),Preferences::sharedPreferences()->angleUnit()); - if (std::floor(rank) == rank && !seq.badlyReferencesItself(ctx)) { - SequenceContext sqctx(ctx, sequenceStore()); - return Float::Builder(seq.evaluateXYAtParameter(rank, &sqctx).x2()); - } else { - return Float::Builder(NAN); + 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) { From 86d0c192931c2419b184559dc9c4776c169798d5 Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Wed, 30 Sep 2020 17:39:06 +0200 Subject: [PATCH 261/560] [ok view] Fixing the grey tone of the ok symbol Change-Id: Id65a4e37c41e1b675178d3220c15ebfe26b1e71e --- apps/shared/ok_view.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/shared/ok_view.cpp b/apps/shared/ok_view.cpp index e97d9a96e92..0f531cd04ea 100644 --- a/apps/shared/ok_view.cpp +++ b/apps/shared/ok_view.cpp @@ -5,22 +5,22 @@ namespace Shared { const uint8_t okMask[OkView::k_okSize][OkView::k_okSize] = { {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, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xBC, 0x27, 0x76, 0xFC, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x76, 0x3D, 0xED, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEE, 0x3E, 0x76, 0xFF, 0xFF}, - {0xFF, 0xAB, 0x26, 0xEC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEC, 0x27, 0xAB, 0xFF}, - {0xF9, 0x1D, 0xC1, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xC2, 0x1C, 0xF9}, - {0xA4, 0x43, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0x43, 0xA3}, - {0x54, 0x98, 0xFB, 0xFB, 0xFB, 0xEC, 0x4C, 0x06, 0x3E, 0xE1, 0xFB, 0x00, 0xFB, 0xE6, 0x00, 0xFB, 0xFB, 0xFB, 0x98, 0x53}, - {0x1F, 0xD8, 0xFB, 0xFB, 0xFB, 0x5D, 0x64, 0xEF, 0x73, 0x53, 0xFB, 0x00, 0xE6, 0x2A, 0x7D, 0xFB, 0xFB, 0xFB, 0xD9, 0x1F}, - {0x07, 0xF5, 0xFB, 0xFB, 0xFB, 0x0F, 0xE9, 0xFB, 0xE8, 0x0C, 0xFB, 0x00, 0x2A, 0x69, 0xFA, 0xFB, 0xFB, 0xFB, 0xF5, 0x06}, - {0x0D, 0xEE, 0xFB, 0xFB, 0xFB, 0x0F, 0xE9, 0xFB, 0xE8, 0x0C, 0xFB, 0x00, 0x7D, 0x29, 0xC4, 0xFB, 0xFB, 0xFB, 0xF5, 0x06}, - {0x26, 0xD4, 0xFB, 0xFB, 0xFB, 0x5E, 0x62, 0xEE, 0x73, 0x53, 0xFB, 0x00, 0xFB, 0x7E, 0x6F, 0xFB, 0xFB, 0xFB, 0xD9, 0x1F}, - {0x54, 0x98, 0xFB, 0xFB, 0xFB, 0xEC, 0x4D, 0x07, 0x3F, 0xE2, 0xFB, 0x00, 0xFB, 0xD2, 0x00, 0xFB, 0xFB, 0xFB, 0x98, 0x53}, - {0xA4, 0x43, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0x43, 0xA3}, - {0xF9, 0x1D, 0xC2, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xC3, 0x1C, 0xF8}, - {0xFF, 0xAA, 0x28, 0xEC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEC, 0x27, 0xAA, 0xFF}, - {0xFF, 0xFF, 0x75, 0x3E, 0xEE, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xEE, 0x3E, 0x75, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFC, 0x6C, 0x27, 0xBC, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xBC, 0x27, 0x6B, 0xFC, 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}, }; From bf95b460c38ebc8a3ffe4cfc1f3a12e5d152a26b Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 1 Oct 2020 16:18:39 +0200 Subject: [PATCH 262/560] [Sequence] Fixed computation error and asan fails Change-Id: Ib3a619d29bee5cc6f10b939ee066459a5b135c5d --- apps/sequence/test/sequence.cpp | 13 +++++++++++++ apps/shared/sequence.cpp | 1 - apps/shared/sequence_cache_context.cpp | 2 +- apps/shared/sequence_cache_context.h | 2 -- poincare/src/parsing/parser.cpp | 6 +++--- poincare/src/parsing/parser.h | 2 +- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index c09412eb4a3..337f060ad54 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -399,6 +399,19 @@ QUIZ_CASE(sequence_evaluation) { 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); } QUIZ_CASE(sequence_sum_evaluation) { diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index 20f0f47d960..c72c131a671 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -243,7 +243,6 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn symbols[i][j] = Symbol::Builder(name[j], strlen(name[j])); } } - ctx.setNValue(n); switch (type()) { case Type::Explicit: { diff --git a/apps/shared/sequence_cache_context.cpp b/apps/shared/sequence_cache_context.cpp index 518029f0453..92842203a8d 100644 --- a/apps/shared/sequence_cache_context.cpp +++ b/apps/shared/sequence_cache_context.cpp @@ -33,7 +33,7 @@ const Expression SequenceCacheContext::expressionForSymbolAbstract(const Poin 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(m_nValue)); + 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) { diff --git a/apps/shared/sequence_cache_context.h b/apps/shared/sequence_cache_context.h index 9fa79b22ea2..a3495f5124e 100644 --- a/apps/shared/sequence_cache_context.h +++ b/apps/shared/sequence_cache_context.h @@ -14,12 +14,10 @@ class SequenceCacheContext : public Poincare::ContextWithParent { 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); - void setNValue(int n) { m_nValue = n; } private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); T m_values[MaxNumberOfSequences][MaxRecurrenceDepth]; - int m_nValue; SequenceContext * m_sequenceContext; }; diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index eb206b1f6fe..32a72fbccea 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -407,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. @@ -418,7 +418,7 @@ void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Ty } else if (!popTokenIfType(rightDelimiter)) { m_status = Status::Error; // Right delimiter missing } else { - leftHandSide = Sequence::Builder(&name, 1, rank); + leftHandSide = Sequence::Builder(name, 1, rank); } } } @@ -439,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); } } diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 8770d6220a8..e6fe72f5883 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -75,7 +75,7 @@ class Parser { 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); From 91dc5eb5ec2646020146cf7cadf3003abd3727da Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Mon, 12 Oct 2020 16:44:53 +0200 Subject: [PATCH 263/560] [Sequence Graph] Fixing the banner to display u(n) The banner used to display u(n+1)/u(n+2) if the sequences were not explicit. This was incorrect and fixed Change-Id: I59100942b51f4170d9662a6fd7b82d0f7e50ae6f --- apps/shared/sequence.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index c72c131a671..7b4acf86d5f 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -32,18 +32,7 @@ int Sequence::nameWithArgument(char * buffer, size_t bufferSize) { assert(UTF8Decoder::CharSizeOfCodePoint(symbol()) <= 2); result += UTF8Decoder::CodePointToChars(symbol(), buffer+result, bufferSize-result); assert(result <= bufferSize); - switch (type()) - { - case Type::Explicit: - result += strlcpy(buffer+result, ")", bufferSize-result); - break; - case Type::SingleRecurrence: - result += strlcpy(buffer+result, "+1)", bufferSize-result); - break; - default: - result += strlcpy(buffer+result, "+2)", bufferSize-result); - break; - } + result += strlcpy(buffer+result, ")", bufferSize-result); return result; } From bd23135198bc816a15c6536a3299428c9414f6d3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 9 Oct 2020 15:57:36 +0200 Subject: [PATCH 264/560] [apps/shared] Add Discard confirmation pop-up Change-Id: Ida3878894090ecfa99145618b8e1ff0bbcb4743a --- apps/apps_container.cpp | 2 +- apps/apps_container.h | 6 +- apps/exam_pop_up_controller.cpp | 132 +++--------------- apps/exam_pop_up_controller.h | 39 +----- apps/hardware_test/pop_up_controller.cpp | 121 ++-------------- apps/hardware_test/pop_up_controller.h | 36 +---- apps/on_boarding/Makefile | 2 +- ...p_controller.cpp => prompt_controller.cpp} | 14 +- ...op_up_controller.h => prompt_controller.h} | 9 +- apps/shared.de.i18n | 2 + apps/shared.en.i18n | 2 + apps/shared.es.i18n | 2 + apps/shared.fr.i18n | 2 + apps/shared.it.i18n | 2 + apps/shared.nl.i18n | 2 + apps/shared.pt.i18n | 2 + apps/shared/Makefile | 1 + apps/shared/discard_pop_up_controller.cpp | 12 ++ apps/shared/discard_pop_up_controller.h | 15 ++ escher/Makefile | 1 + escher/include/escher/pop_up_controller.h | 46 ++++++ escher/src/pop_up_controller.cpp | 107 ++++++++++++++ 22 files changed, 257 insertions(+), 300 deletions(-) rename apps/on_boarding/{pop_up_controller.cpp => prompt_controller.cpp} (76%) rename apps/on_boarding/{pop_up_controller.h => prompt_controller.h} (80%) create mode 100644 apps/shared/discard_pop_up_controller.cpp create mode 100644 apps/shared/discard_pop_up_controller.h create mode 100644 escher/include/escher/pop_up_controller.h create mode 100644 escher/src/pop_up_controller.cpp diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index ca3c1be1cb7..d8631f3ac53 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -329,7 +329,7 @@ bool AppsContainer::updateAlphaLock() { return m_window.updateAlphaLock(); } -OnBoarding::PopUpController * AppsContainer::promptController() { +OnBoarding::PromptController * AppsContainer::promptController() { if (k_promptNumberOfMessages == 0) { return nullptr; } diff --git a/apps/apps_container.h b/apps/apps_container.h index 48299910327..e217f2495c4 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -16,7 +16,7 @@ #include "global_preferences.h" #include "backlight_dimming_timer.h" #include "shared/global_context.h" -#include "on_boarding/pop_up_controller.h" +#include "on_boarding/prompt_controller.h" #include @@ -45,7 +45,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); - OnBoarding::PopUpController * promptController(); + OnBoarding::PromptController * promptController(); void redrawWindow(); void activateExamMode(GlobalPreferences::ExamMode examMode); // Exam pop-up controller delegate @@ -72,7 +72,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag 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/exam_pop_up_controller.cpp b/apps/exam_pop_up_controller.cpp index 2f9dc5a3b19..0bc5982ac68 100644 --- a/apps/exam_pop_up_controller.cpp +++ b/apps/exam_pop_up_controller.cpp @@ -1,13 +1,29 @@ #include "exam_pop_up_controller.h" #include "apps_container.h" #include "exam_mode_configuration.h" -#include -#include "global_preferences.h" #include 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) { @@ -15,11 +31,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() { @@ -27,103 +41,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 694bf5ca2fc..192f38f4755 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::YellowDark; } -}; - -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/hardware_test/pop_up_controller.cpp b/apps/hardware_test/pop_up_controller.cpp index 76afaa19bfc..ed785d24a3b 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 b04724e952a..35b74e26567 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/on_boarding/Makefile b/apps/on_boarding/Makefile index b85d410e7d2..ffefeaf114d 100644 --- a/apps/on_boarding/Makefile +++ b/apps/on_boarding/Makefile @@ -3,7 +3,7 @@ app_on_boarding_src = $(addprefix apps/on_boarding/,\ logo_controller.cpp \ logo_view.cpp \ localization_controller.cpp \ - pop_up_controller.cpp \ + prompt_controller.cpp \ power_on_self_test.cpp \ ) 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 c63328b9e49..e938a43a0c8 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 f092e885394..b0ce7b61554 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 @@ class PopUpController : public ViewController { } #endif - diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index a8e653419a9..75948221087 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -11,6 +11,8 @@ 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 " diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 5c040516810..b793d7dc705 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -11,6 +11,8 @@ 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 " diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 547919edd8b..0a60eb2fedc 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -11,6 +11,8 @@ 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á " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 5207340abef..d75eed312a9 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -11,6 +11,8 @@ 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 " diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index bb1c7850a62..339a4db650f 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -11,6 +11,8 @@ 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 " diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index da20649c4fd..b3d5ab0a19f 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -11,6 +11,8 @@ 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 " diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 35cb54cbb79..b0fd4ac0e42 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -11,6 +11,8 @@ 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á " diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 0c6ead3e763..2d68eee9146 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -59,6 +59,7 @@ app_shared_src = $(addprefix apps/shared/,\ 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 \ diff --git a/apps/shared/discard_pop_up_controller.cpp b/apps/shared/discard_pop_up_controller.cpp new file mode 100644 index 00000000000..19a5aa5503f --- /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 00000000000..6c54d494522 --- /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/escher/Makefile b/escher/Makefile index d0f7b49b543..421fbed1e12 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -51,6 +51,7 @@ escher_src += $(addprefix escher/src/,\ nested_menu_controller.cpp \ palette.cpp \ pointer_text_view.cpp \ + pop_up_controller.cpp \ responder.cpp \ run_loop.cpp \ scroll_view.cpp \ diff --git a/escher/include/escher/pop_up_controller.h b/escher/include/escher/pop_up_controller.h new file mode 100644 index 00000000000..fd601c87e45 --- /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::YellowDark; } +}; + +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/src/pop_up_controller.cpp b/escher/src/pop_up_controller.cpp new file mode 100644 index 00000000000..d1afbae2959 --- /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); +} From 150a5bfa5ace73b158c3a02f2d773cf38d451cf0 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 9 Oct 2020 15:58:46 +0200 Subject: [PATCH 265/560] [apps/shared] Add discard confirmation on table intervals Change-Id: Id152577c570febf06b4d829fd2723c2a30c3ccd9 --- apps/shared/interval.cpp | 4 ++++ apps/shared/interval.h | 1 + apps/shared/interval_parameter_controller.cpp | 12 +++++++++++- apps/shared/interval_parameter_controller.h | 2 ++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/shared/interval.cpp b/apps/shared/interval.cpp index 1fccb9e817c..6b5151308c2 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 13e3e2cf4c6..bf7c9792535 100644 --- a/apps/shared/interval.h +++ b/apps/shared/interval.h @@ -23,6 +23,7 @@ class Interval { 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; } diff --git a/apps/shared/interval_parameter_controller.cpp b/apps/shared/interval_parameter_controller.cpp index 2404f46042d..11256bfd438 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 21c689a3aff..0821f033faa 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 @@ class IntervalParameterController : public Shared::FloatParameterController Date: Fri, 9 Oct 2020 16:00:26 +0200 Subject: [PATCH 266/560] [apps/graph] Add discard confirmation on domain parameters Change-Id: I61043aa195f10038bbcfb6c091f08e684db20849 --- .../list/domain_parameter_controller.cpp | 22 ++++++++++++++----- apps/graph/list/domain_parameter_controller.h | 4 ++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/graph/list/domain_parameter_controller.cpp b/apps/graph/list/domain_parameter_controller.cpp index b4c70627c9e..25a927704b9 100644 --- a/apps/graph/list/domain_parameter_controller.cpp +++ b/apps/graph/list/domain_parameter_controller.cpp @@ -11,7 +11,12 @@ DomainParameterController::DomainParameterController(Responder * parentResponder FloatParameterController(parentResponder), m_domainCells{}, m_record(), - m_tempDomain() + 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); @@ -65,6 +70,11 @@ 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; } @@ -80,8 +90,7 @@ void DomainParameterController::extractParameters() { * 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(function()->tMin() == m_tempDomain.min()); - assert(function()->tMax() == m_tempDomain.max()); + assert(equalTempParameters()); } bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) { @@ -95,8 +104,11 @@ void DomainParameterController::confirmParameters() { function()->setTMin(parameterAtIndex(0)); function()->setTMax(parameterAtIndex(1)); // See comment on Range1D initialization in extractParameters - assert(function()->tMin() == m_tempDomain.min()); - assert(function()->tMax() == m_tempDomain.max()); + assert(equalTempParameters()); +} + +bool DomainParameterController::equalTempParameters() { + return function()->tMin() == m_tempDomain.min() && function()->tMax() == m_tempDomain.max(); } void DomainParameterController::buttonAction() { diff --git a/apps/graph/list/domain_parameter_controller.h b/apps/graph/list/domain_parameter_controller.h index 5efe1c19318..00f876c0ea4 100644 --- a/apps/graph/list/domain_parameter_controller.h +++ b/apps/graph/list/domain_parameter_controller.h @@ -7,6 +7,7 @@ #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 { @@ -38,9 +39,12 @@ class DomainParameterController : public Shared::FloatParameterController 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; }; } From 697f72742976dae1d4e5301bb4a06c935a4151a9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 9 Oct 2020 16:02:43 +0200 Subject: [PATCH 267/560] [apps/shared] Add discard confirmation on range parameters Change-Id: I609acbd136cb0bc84899777810f4c1497832952f --- apps/shared/range_parameter_controller.cpp | 12 +++++++++++- apps/shared/range_parameter_controller.h | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index 04225346673..6256ce6aaa8 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -11,7 +11,12 @@ RangeParameterController::RangeParameterController(Responder * parentResponder, m_tempInteractiveRange(*interactiveRange), m_xRangeCells{}, m_yRangeCells{}, - m_yAutoCell(I18n::Message::YAuto) + m_yAutoCell(I18n::Message::YAuto), + 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); @@ -82,6 +87,11 @@ bool RangeParameterController::handleEvent(Ion::Events::Event event) { m_selectableTableView.reloadData(); return true; } + 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); } diff --git a/apps/shared/range_parameter_controller.h b/apps/shared/range_parameter_controller.h index a2006f284cc..6de99e91206 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 { @@ -45,6 +46,7 @@ class RangeParameterController : public FloatParameterController { MessageTableCellWithEditableText m_xRangeCells[k_numberOfEditableTextCell]; MessageTableCellWithConvertibleEditableText m_yRangeCells[k_numberOfConvertibleTextCell]; MessageTableCellWithSwitch m_yAutoCell; + DiscardPopUpController m_confirmPopUpController; }; } From ad80c2c4d75687c3b3bb1a6cd2bb22e1e5a614d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 13 Oct 2020 09:54:41 +0200 Subject: [PATCH 268/560] [ion/keyboard] Do not define constexpr arrays in headers They are duplicated in each compilation unit where they are called --- ion/Makefile | 2 ++ ion/include/ion/keyboard/layout_events.h | 24 +++++++++++++++++++ .../keyboard/layout_B2/layout_events.cpp} | 16 +++---------- .../keyboard/layout_B3/layout_events.cpp} | 16 +++---------- 4 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 ion/include/ion/keyboard/layout_events.h rename ion/{include/ion/keyboard/layout_B2/layout_events.h => src/shared/keyboard/layout_B2/layout_events.cpp} (91%) rename ion/{include/ion/keyboard/layout_B3/layout_events.h => src/shared/keyboard/layout_B3/layout_events.cpp} (91%) diff --git a/ion/Makefile b/ion/Makefile index 6efea82b872..c49f1327733 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 diff --git a/ion/include/ion/keyboard/layout_events.h b/ion/include/ion/keyboard/layout_events.h new file mode 100644 index 00000000000..21cdaeed038 --- /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 const char * s_nameForEvent[255]; + +inline const char * Event::name() const { + return s_nameForEvent[m_id]; +} +#endif + +} +} + +#endif diff --git a/ion/include/ion/keyboard/layout_B2/layout_events.h b/ion/src/shared/keyboard/layout_B2/layout_events.cpp similarity index 91% rename from ion/include/ion/keyboard/layout_B2/layout_events.h rename to ion/src/shared/keyboard/layout_B2/layout_events.cpp index d0621d3d648..24518533e43 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 * 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 91% rename from ion/include/ion/keyboard/layout_B3/layout_events.h rename to ion/src/shared/keyboard/layout_B3/layout_events.cpp index 0bdd59e56e8..6133c57874b 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 * 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 From b567a09103c4029883a7a0b04b6702aaa97cf55a Mon Sep 17 00:00:00 2001 From: Arthur Camouseigt Date: Thu, 15 Oct 2020 11:29:21 +0200 Subject: [PATCH 269/560] [VariableBox] Allowing sequences to display their type Change-Id: I2929f7d9f3a266fbff81100a39f793fe6200da1d --- apps/math_variable_box_controller.cpp | 2 +- apps/shared/sequence.cpp | 18 ++++++++++++++++++ apps/shared/sequence.h | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index f1032a5b5f5..f8e01ea9f33 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -120,7 +120,7 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in } else { assert(m_currentPage == Page::Sequence); Shared::Sequence u(record); - symbolLength = u.nameWithArgument( + symbolLength = u.nameWithArgumentAndType( symbolName, Shared::Sequence::k_maxNameWithArgumentSize ); diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index 7b4acf86d5f..d9ffbb59122 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -36,6 +36,24 @@ int Sequence::nameWithArgument(char * buffer, size_t bufferSize) { 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; +} + void Sequence::tidy() { m_definition.tidyName(); Function::tidy(); // m_definitionName.tidy() diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index d53422c5206..e3c6b92ad6d 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -30,6 +30,7 @@ friend class SequenceStore; I18n::Message parameterMessageName() const override; CodePoint symbol() const override { return 'n'; } int nameWithArgument(char * buffer, size_t bufferSize) override; + int nameWithArgumentAndType(char * buffer, size_t bufferSize); void tidy() override; // MetaData getters Type type() const; From ebe207d67d5cb1fb09730742bebf2e9b34b4262c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 14 Oct 2020 16:08:15 +0200 Subject: [PATCH 270/560] [apps/shared] Fix cache overlap when plotting polar curves Change-Id: I4b1d6dccdedc5b3cea455e2ad2038336b4b48064 --- apps/shared/curve_view.cpp | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index c655e54e24b..ad96f8e006b 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -671,9 +671,16 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float float rectUp = pixelToFloat(Axis::Vertical, rect.top() + k_externRectMargin); float rectDown = pixelToFloat(Axis::Vertical, rect.bottom() - k_externRectMargin); - bool rectOverlapsNegativeAbscissaAxis = std::isnan(rectLeft + rectRight + rectUp + rectDown); - if ((rectUp > 0.0f && rectDown < 0.0f && rectLeft < 0.0f) || rectOverlapsNegativeAbscissaAxis) { - if (rectRight > 0.0f || rectOverlapsNegativeAbscissaAxis) { + 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, any optimization would be counter productive. */ + bool cancelOptimization = std::isnan(rectLeft + rectRight + rectUp + rectDown) || 2 * 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); } @@ -681,9 +688,6 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float rectOverlapsNegativeAbscissaAxis = true; } - const Preferences::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); - const float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); - float tMin, tMax; /* Compute angular coordinate of each corners of rect. * t3 --- t2 @@ -694,8 +698,8 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float if (!rectOverlapsNegativeAbscissaAxis) { float t3 = PolarThetaFromCoordinates(rectLeft, rectUp, angleUnit); float t4 = PolarThetaFromCoordinates(rectLeft, rectDown, angleUnit); - /* The area between tMin and tMax (modulo π) is the area where something can - * be plotted. */ + /* 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 { @@ -708,11 +712,18 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float tMax = t1; } - /* Draw 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 the intervals : + /* To optimize cache hits, the area actually drawn will be extended to nearest + * cached θ by at most tStep on both sides. If the extended area is too large + * the optimization will be ineffective. */ + if (2 * tStep + tMax - tMin >= piInAngleUnit) { + 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*/ + * - [ -π + π/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; @@ -724,7 +735,8 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float // 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. More of the curve is drawn. */ + * cached value. Step is small enough so that the extra drawn curve cannot + * overlap */ int i = std::floor((tS - tStart) / tStep); float tCache1 = tStart + tStep * i; From 4acbf3346be6bf9030218b36fb5060774a507531 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 15 Oct 2020 15:30:10 +0200 Subject: [PATCH 271/560] [apps/shared] Add comments and minimize threshold Change-Id: Ieb56513ea0a7fab35acf15531277aec5cd6071ca --- apps/shared/curve_view.cpp | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index ad96f8e006b..676c179fb28 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -675,8 +675,9 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float const float piInAngleUnit = Trigonometry::PiInAngleUnit(angleUnit); /* Cancel optimization if : * - One of rect limits is nan. - * - Step is too large, any optimization would be counter productive. */ - bool cancelOptimization = std::isnan(rectLeft + rectRight + rectUp + rectDown) || 2 * tStep >= piInAngleUnit; + * - 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)) { @@ -713,9 +714,27 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float } /* To optimize cache hits, the area actually drawn will be extended to nearest - * cached θ by at most tStep on both sides. If the extended area is too large - * the optimization will be ineffective. */ - if (2 * tStep + tMax - tMin >= piInAngleUnit) { + * 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); } @@ -729,6 +748,7 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float 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); @@ -736,13 +756,15 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float 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 */ + * 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); - float tCache2 = std::min(tStart + tStep * j, tEnd); + 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; From 5339d9e7b47e3e7a889c926990ce2de56ef48ed9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 15 Oct 2020 16:25:11 +0200 Subject: [PATCH 272/560] [apps/shared] Add static assert for cache steps Change-Id: I77498775b4caf3d97522219d2a7c979601b985e8 --- apps/shared/continuous_function_cache.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 62064532d3e..9b3657960a7 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -64,6 +64,8 @@ void ContinuousFunctionCache::ComputeNonCartesianSteps(float * tStep, float * tC 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 @@ -72,7 +74,7 @@ void ContinuousFunctionCache::ComputeNonCartesianSteps(float * tStep, float * tC * 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 * numberOfWholeSteps / numberOfCacheablePoints; + *tCacheStep = *tStep / multiple; } // private From 7499a9e0a4f1390ab3177985e727a690c7713db9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 15 Oct 2020 17:19:48 +0200 Subject: [PATCH 273/560] [apps] Set Country from language in main.cpp Change-Id: I09f6733fa36e8f0c7870bc26eb8662cda79ae715 --- apps/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/main.cpp b/apps/main.cpp index 0ea75e8885e..3a22fda5c77 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; } } From d2b36be84680cc3470f62a768e155d0ec94bcfb5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 28 Sep 2020 15:18:17 +0200 Subject: [PATCH 274/560] [poincare/zoom] Move automatic zoom to Poincare Change-Id: I7a70ab54b33b9150682cc4f999ff4999a8288b7f --- apps/shared/continuous_function.cpp | 2 +- apps/shared/continuous_function.h | 1 - apps/shared/function.cpp | 259 ++-------------------------- apps/shared/function.h | 6 +- apps/shared/range_1D.h | 3 +- apps/shared/sequence.h | 5 +- poincare/Makefile | 1 + poincare/include/poincare/zoom.h | 50 ++++++ poincare/src/zoom.cpp | 226 ++++++++++++++++++++++++ 9 files changed, 298 insertions(+), 255 deletions(-) create mode 100644 poincare/include/poincare/zoom.h create mode 100644 poincare/src/zoom.cpp diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 0d316a5e1a0..36d6b4390e2 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -263,7 +263,7 @@ void ContinuousFunction::setTMax(float tMax) { void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange) const { if (plotType() == PlotType::Cartesian) { - Function::rangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange); + protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange, true); } else { fullXYRange(xMin, xMax, yMin, yMax, context); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 7d1580c0128..c66be00f740 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -92,7 +92,6 @@ class ContinuousFunction : public Function { template Poincare::Coordinate2D privateEvaluateXYAtParameter(T t, Poincare::Context * context) const; void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; - void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRefinedYRangeForDisplay(xMin, xMax, yMin, yMax, context, true); } /* RecordDataBuffer is the layout of the data buffer of Record * representing a ContinuousFunction. See comment on diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 9491c395631..6cfc6996386 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -2,6 +2,7 @@ #include "range_1D.h" #include "poincare_helpers.h" #include "poincare/src/parsing/parser.h" +#include #include #include #include @@ -78,257 +79,21 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(d.buffer)); } -// Ranges -static float evaluateAndRound(const Function * f, float x, Context * context, float precision = 1e-5) { +void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange, bool boundByMagnitude) const { + 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 varitions, while the cardinal sine is supposed to be + * 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. */ - return precision * std::round(f->evaluateXYAtParameter(x, context).x2() / precision); -} - -/* TODO : These three 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); -} + constexpr float precision = 1e-5; + return precision * std::round(static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision); + }; + Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this, tuneXRange); -/* This function 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 isExtremum(const Function * f, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, int iterations = 3) { - 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 = evaluateAndRound(f, xm, context); - bool res = ((y[i] < ym) != (ym < y2)) ? isExtremum(f, x[i], xm, x2, y[i], ym, y2, context, iterations - 1) : std::fabs(ym - y[i]) >= std::fabs(y2 - ym); - if (!res) { - return false; - } - } - return true; + evaluation = [](float x, Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; + Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this, boundByMagnitude); } - -enum class PointOfInterest : uint8_t { - None, - Bound, - Extremum, - Root -}; - -void Function::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Context * context, bool tuneXRange) const { - assert(xMin && xMax && yMin && yMax); - - /* Constants of the algorithm. */ - constexpr float defaultMaxInterval = 2e5f; - constexpr float minDistance = 1e-2f; - constexpr float asymptoteThreshold = 2e-1f; - constexpr float stepFactor = 1.1f; - constexpr int maxNumberOfPoints = 3; - constexpr float breathingRoom = 0.3f; - constexpr float maxRatioBetweenPoints = 100.f; - - const bool hasIntervalOfDefinition = std::isfinite(tMin()) && std::isfinite(tMax()); - float center, maxDistance; - if (!tuneXRange) { - center = (*xMax + *xMin) / 2.f; - maxDistance = (*xMax - *xMin) / 2.f; - } else if (hasIntervalOfDefinition) { - center = (tMax() + tMin()) / 2.f; - maxDistance = (tMax() - tMin()) / 2.f; - } else { - center = 0.f; - maxDistance = defaultMaxInterval / 2.f; - } - - float resultX[2] = {FLT_MAX, - FLT_MAX}; - float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; - float asymptote[2] = {FLT_MAX, - FLT_MAX}; - int numberOfPoints; - 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 - minDistance - FLT_EPSILON, b = center + FLT_EPSILON, c = center + minDistance + FLT_EPSILON; - const float ya = evaluateAndRound(this, a, context), yb = evaluateAndRound(this, b, context), yc = evaluateAndRound(this, c, context); - if (boundOfIntervalOfDefinitionIsReached(ya, yc) || - boundOfIntervalOfDefinitionIsReached(yc, ya) || - rootExistsOnInterval(ya, yc) || - extremumExistsOnInterval(ya, yb, yc) || ya == yc) - { - resultX[0] = resultX[1] = center; - if (extremumExistsOnInterval(ya, yb, yc) && isExtremum(this, a, b, c, ya, yb, yc, context)) { - 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 ? - minDistance : minDistance; - dXNext = dXPrev * stepFactor; - yPrev = evaluateAndRound(this, center + dXPrev, context); - yNext = evaluateAndRound(this, center + dXNext, context); - - while(std::fabs(dXPrev) < maxDistance) { - /* Update the slider. */ - dXOld = dXPrev; - dXPrev = dXNext; - dXNext *= stepFactor; - yOld = yPrev; - yPrev = yNext; - yNext = evaluateAndRound(this, center + dXNext, context); - 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) ? 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 (isExtremum(this, center + dXOld, center + dXPrev, center + dXNext, yOld, yPrev, yNext, context)) { - 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 == maxNumberOfPoints) { - /* 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; - 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; - } - break; - default: - const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); - if ((std::fabs(slopeNext) < asymptoteThreshold) && (std::fabs(slopePrev) > 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) < asymptoteThreshold) && (std::fabs(slopePrev) > 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(resultX[i]) > std::fabs(firstResult) * maxRatioBetweenPoints && !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 (tuneXRange) { - /* Cut after horizontal asymptotes. */ - resultX[0] = std::min(resultX[0], asymptote[0]); - resultX[1] = std::max(resultX[1], asymptote[1]); - if (resultX[0] >= resultX[1]) { - /* Fallback to default range. */ - resultX[0] = - Range1D::k_default; - resultX[1] = Range1D::k_default; - } else { - /* Add breathing room around points of interest. */ - float xRange = resultX[1] - resultX[0]; - resultX[0] -= breathingRoom * xRange; - resultX[1] += breathingRoom * xRange; - /* Round to the next integer. */ - resultX[0] = std::floor(resultX[0]); - resultX[1] = std::ceil(resultX[1]); - } - *xMin = std::min(resultX[0], *xMin); - *xMax = std::max(resultX[1], *xMax); - } - *yMin = std::min(resultYMin, *yMin); - *yMax = std::max(resultYMax, *yMax); - - refinedYRangeForDisplay(*xMin, *xMax, yMin, yMax, context); -} - -void Function::protectedRefinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Context * context, bool boundByMagnitude) const { - /* 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); - - constexpr int sampleSize = Ion::Display::Width / 4; - constexpr float boundNegligigbleThreshold = 0.2f; - - float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; - const float step = (xMax - xMin) / (sampleSize - 1); - float x, y; - float sum = 0.f; - int pop = 0; - - for (int i = 1; i < sampleSize; i++) { - x = xMin + i * step; - y = evaluateXYAtParameter(x, context).x2(); - if (!std::isfinite(y)) { - continue; - } - sampleYMin = std::min(sampleYMin, y); - sampleYMax = std::max(sampleYMax, y); - if (std::fabs(y) > FLT_EPSILON) { - 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 (boundByMagnitude) { - float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; - sampleYMin = std::max(sampleYMin, - bound); - sampleYMax = std::min(sampleYMax, bound); - } - *yMin = std::min(*yMin, sampleYMin); - *yMax = std::max(*yMax, sampleYMax); - if (*yMin == *yMax) { - float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; - *yMin -= d; - *yMax += d; - } - /* 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 < boundNegligigbleThreshold) { - *yMin = 0.f; - } else if (*yMax < 0.f && *yMax / *yMin < boundNegligigbleThreshold) { - *yMax = 0.f; - } -} - } diff --git a/apps/shared/function.h b/apps/shared/function.h index 45394002573..49442d1b381 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -55,7 +55,7 @@ class Function : public ExpressionModelHandle { 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, Poincare::Context * context, bool tuneXRange = true) const; + virtual void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange = true) const = 0; protected: /* RecordDataBuffer is the layout of the data buffer of Record @@ -93,11 +93,9 @@ class Function : public ExpressionModelHandle { bool m_active; }; - void protectedRefinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const; + void protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange, bool boundByMagnitude) const; private: - virtual void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const = 0; - RecordDataBuffer * recordData() const; }; diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index 58ef2537fcc..b89b316d8d3 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -3,6 +3,7 @@ #include #include +#include #if __EMSCRIPTEN__ #include @@ -19,7 +20,7 @@ class __attribute__((packed)) Range1D final { /* 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_default = Poincare::Zoom::k_defaultHalfRange; Range1D(float min = -k_default, float max = k_default) : m_min(min), m_max(max) diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index e3c6b92ad6d..58ac6e7adb7 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -74,6 +74,10 @@ friend class SequenceStore; 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, Poincare::Context * context, bool tuneXRange = true) const override { protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange, false); }; + private: constexpr static const KDFont * k_layoutFont = KDFont::LargeFont; @@ -153,7 +157,6 @@ friend class SequenceStore; }; template T templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const; - void refinedYRangeForDisplay(float xMin, float xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRefinedYRangeForDisplay(xMin, xMax, yMin, yMax, context, false); } size_t metaDataSize() const override { return sizeof(RecordDataBuffer); } const Shared::ExpressionModel * model() const override { return &m_definition; } RecordDataBuffer * recordData() const; diff --git a/poincare/Makefile b/poincare/Makefile index 790c7fa6ce4..61ee32fe3bb 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -156,6 +156,7 @@ poincare_src += $(addprefix poincare/src/,\ vector_cross.cpp \ vector_dot.cpp \ vector_norm.cpp \ + zoom.cpp \ ) poincare_src += $(addprefix poincare/src/parsing/,\ diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h new file mode 100644 index 00000000000..69a33d5f7f4 --- /dev/null +++ b/poincare/include/poincare/zoom.h @@ -0,0 +1,50 @@ +#ifndef POINCARE_ZOOM_H +#define POINCARE_ZOOM_H + +#include +#include + +namespace Poincare { + +class Zoom { +public: + static constexpr float k_defaultHalfRange = 10.f; + + typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); + + static void InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary, bool tuneXRange = false); + static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); + +private: + static constexpr int k_peakNumberOfPointsOfInterest = 3; + static constexpr int k_sampleSize = Ion::Display::Width / 4; + static constexpr float k_defaultMaxInterval = 2e5f; + static constexpr float k_minimalDistance = 1e-2f; + static constexpr float k_asymptoteThreshold = 2e-1f; + static constexpr float k_stepFactor = 1.1f; + 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); +}; + +} + +#endif diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp new file mode 100644 index 00000000000..315f255bf80 --- /dev/null +++ b/poincare/src/zoom.cpp @@ -0,0 +1,226 @@ +#include +#include +#include + +namespace Poincare { + +constexpr int + Zoom::k_peakNumberOfPointsOfInterest, + Zoom::k_sampleSize; +constexpr float + Zoom::k_defaultMaxInterval, + Zoom::k_minimalDistance, + Zoom::k_asymptoteThreshold, + Zoom::k_stepFactor, + Zoom::k_breathingRoom, + Zoom::k_forceXAxisThreshold, + Zoom::k_defaultHalfRange, + Zoom::k_maxRatioBetweenPointsOfInterest; + +void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary, bool tuneXRange) { + assert(xMin && xMax && yMin && yMax); + + const bool hasIntervalOfDefinition = std::isfinite(tMin) && std::isfinite(tMax); + float center, maxDistance; + if (!tuneXRange) { + center = (*xMax + *xMin) / 2.f; + maxDistance = (*xMax - *xMin) / 2.f; + } else if (hasIntervalOfDefinition) { + center = (tMax + tMin) / 2.f; + maxDistance = (tMax - tMin) / 2.f; + } else { + center = 0.f; + maxDistance = k_defaultMaxInterval / 2.f; + } + + float resultX[2] = {FLT_MAX, - FLT_MAX}; + float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; + float asymptote[2] = {FLT_MAX, - FLT_MAX}; + int numberOfPoints; + 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; + 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) ? 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; + 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; + } + 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(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 (tuneXRange) { + /* Cut after horizontal asymptotes. */ + resultX[0] = std::min(resultX[0], asymptote[0]); + resultX[1] = std::max(resultX[1], asymptote[1]); + if (resultX[0] >= resultX[1]) { + /* Fallback to default range. */ + resultX[0] = - k_defaultHalfRange; + resultX[1] = k_defaultHalfRange; + } else { + /* Add breathing room around points of interest. */ + float xRange = resultX[1] - resultX[0]; + resultX[0] -= k_breathingRoom * xRange; + resultX[1] += k_breathingRoom * xRange; + /* Round to the next integer. */ + resultX[0] = std::floor(resultX[0]); + resultX[1] = std::ceil(resultX[1]); + } + *xMin = std::min(resultX[0], *xMin); + *xMax = std::max(resultX[1], *xMax); + } + *yMin = std::min(resultYMin, *yMin); + *yMax = std::max(resultYMax, *yMax); +} + +void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude) { + /* 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 sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; + const float step = (xMax - xMin) / (k_sampleSize - 1); + float x, y; + float sum = 0.f; + int pop = 0; + + for (int i = 1; i < k_sampleSize; i++) { + x = xMin + i * step; + y = evaluation(x, context, auxiliary); + if (!std::isfinite(y)) { + continue; + } + sampleYMin = std::min(sampleYMin, y); + sampleYMax = std::max(sampleYMax, y); + if (std::fabs(y) > FLT_EPSILON) { + 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 (boundByMagnitude) { + float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; + sampleYMin = std::max(sampleYMin, - bound); + sampleYMax = std::min(sampleYMax, bound); + } + *yMin = std::min(*yMin, sampleYMin); + *yMax = std::max(*yMax, sampleYMax); + if (*yMin == *yMax) { + float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; + *yMin -= d; + *yMax += d; + } + /* 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; + } +} + +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; +} + +} From 5a07db345265f13d6fe1ca6fb3de4ace0b3662d4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 10:05:43 +0200 Subject: [PATCH 275/560] [apps] Remove "Preadjustments" menu for curves Change-Id: I1a30efd502c5db36325d87fa0f1dffacc37a4f1c --- apps/regression/Makefile | 1 - apps/regression/graph_controller.cpp | 5 -- apps/regression/graph_controller.h | 3 - .../initialisation_parameter_controller.cpp | 65 ------------------- .../initialisation_parameter_controller.h | 31 --------- apps/sequence/graph/curve_view_range.cpp | 29 --------- apps/sequence/graph/curve_view_range.h | 2 - apps/shared.de.i18n | 3 - apps/shared.en.i18n | 3 - apps/shared.es.i18n | 3 - apps/shared.fr.i18n | 3 - apps/shared.it.i18n | 3 - apps/shared.nl.i18n | 3 - apps/shared.pt.i18n | 3 - apps/shared/Makefile | 1 - apps/shared/function_graph_controller.cpp | 5 -- apps/shared/function_graph_controller.h | 3 - .../initialisation_parameter_controller.cpp | 62 ------------------ .../initialisation_parameter_controller.h | 36 ---------- .../interactive_curve_view_controller.cpp | 10 +-- .../interactive_curve_view_controller.h | 2 - apps/shared/interactive_curve_view_range.cpp | 23 ------- apps/shared/interactive_curve_view_range.h | 2 - 23 files changed, 2 insertions(+), 299 deletions(-) delete mode 100644 apps/regression/initialisation_parameter_controller.cpp delete mode 100644 apps/regression/initialisation_parameter_controller.h delete mode 100644 apps/shared/initialisation_parameter_controller.cpp delete mode 100644 apps/shared/initialisation_parameter_controller.h diff --git a/apps/regression/Makefile b/apps/regression/Makefile index 2211e319238..56daa0ef212 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/graph_controller.cpp b/apps/regression/graph_controller.cpp index 919bd0396fa..2231f2a927f 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -18,7 +18,6 @@ GraphController::GraphController(Responder * parentResponder, InputEventHandlerD 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 +28,6 @@ GraphController::GraphController(Responder * parentResponder, InputEventHandlerD m_store->setDelegate(this); } -ViewController * GraphController::initialisationParameterController() { - return &m_initialisationParameterController; -} - bool GraphController::isEmpty() const { return m_store->isEmpty(); } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 73c1d99fb3c..852027573b0 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" @@ -17,7 +16,6 @@ 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; bool isEmpty() const override; I18n::Message emptyMessage() override; void viewWillAppear() override; @@ -63,7 +61,6 @@ class GraphController : public Shared::InteractiveCurveViewController { 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/initialisation_parameter_controller.cpp b/apps/regression/initialisation_parameter_controller.cpp deleted file mode 100644 index 421d3f7c377..00000000000 --- 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 d3c97b59e8e..00000000000 --- 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/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index 977c58dcc83..77d2aa20ee9 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -15,23 +15,6 @@ 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; - 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(); @@ -63,16 +46,4 @@ void CurveViewRange::normalize() { } } -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); -} - } diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index 83c4da170ac..5611b6056b1 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -8,9 +8,7 @@ namespace Sequence { class CurveViewRange : public Shared::InteractiveCurveViewRange { public: CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr); - void roundAbscissa() override; void normalize() override; - void setTrigonometric() override; private: constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 75948221087..d300411ef70 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -48,7 +48,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" @@ -70,7 +69,6 @@ Plot = "Graphen zeichnen" PoolMemoryFull1 = "Der Arbeitsspeicher ist voll." PoolMemoryFull2 = "Versuchen Sie es erneut." Rename = "Umbenennen" -RoundAbscissa = "Ganzzahl" Sci = "wiss" SortValues = "Nach steigenden Werten sortieren" SortSizes = "Nach steigenden Frequenzen sortieren" @@ -87,7 +85,6 @@ ThetaEnd = "θ Endwert" ThetaStart = "θ Startwert" TStart = "T Startwert" ToZoom = "Zoom: " -Trigonometric = "Trigonometrisch" UndefinedValue = "Nicht definierter Wert" ValuesTab = "Tabelle" Warning = "Achtung" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index b793d7dc705..653ce19ccad 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -48,7 +48,6 @@ 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" @@ -70,7 +69,6 @@ Plot = "Plot graph" PoolMemoryFull1 = "The working memory is full." PoolMemoryFull2 = "Try again." Rename = "Rename" -RoundAbscissa = "Integer" Sci = "sci" SortValues = "Sort by increasing values" SortSizes = "Sort by increasing sizes" @@ -87,7 +85,6 @@ ThetaEnd = "θ end" ThetaStart = "θ start" TStart = "T start" ToZoom = "Zoom: " -Trigonometric = "Trigonometrical" UndefinedValue = "Undefined value" ValuesTab = "Table" Warning = "Warning" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 0a60eb2fedc..4d9db157405 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -48,7 +48,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" @@ -70,7 +69,6 @@ Plot = "Dibujar el gráfico" PoolMemoryFull1 = "La memoria de trabajo está llena." PoolMemoryFull2 = "Intente de nuevo." Rename = "Renombrar" -RoundAbscissa = "Abscisas enteras" Sci = "cie" SortValues = "Ordenar por valores crecientes" SortSizes = "Ordenar por frecuencias crecientes" @@ -87,7 +85,6 @@ ThetaEnd = "θ fin" ThetaStart = "θ inicio" TStart = "T inicio" ToZoom = "Zoom : " -Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" ValuesTab = "Tabla" Warning = "Cuidado" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index d75eed312a9..9a04b810ca3 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -48,7 +48,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" @@ -70,7 +69,6 @@ Plot = "Tracer le graphique" PoolMemoryFull1 = "La mémoire de travail est pleine." PoolMemoryFull2 = "Réessayez." Rename = "Renommer" -RoundAbscissa = "Abscisses entières" Sci = "sci" SortValues = "Trier par valeurs croissantes" SortSizes = "Trier par effectifs croissants" @@ -87,7 +85,6 @@ ThetaEnd = "θ fin" ThetaStart = "θ début" TStart = "T début" ToZoom = "Zoomer : " -Trigonometric = "Trigonométrique" UndefinedValue = "Valeur non définie" ValuesTab = "Tableau" Warning = "Attention" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 339a4db650f..0297dbbc7d0 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -48,7 +48,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" @@ -70,7 +69,6 @@ Plot = "Traccia grafico" PoolMemoryFull1 = "La memoria di lavoro è piena." PoolMemoryFull2 = "Riprovare." Rename = "Rinominare" -RoundAbscissa = "Ascisse intere" Sci = "sci" SortValues = "Ordinare per valori crescenti" SortSizes = "Ordinare per frequenze crescenti" @@ -87,7 +85,6 @@ ThetaEnd = "θ finale" ThetaStart = "θ iniziale" TStart = "T iniziale" ToZoom = "Ingrandire : " -Trigonometric = "Trigonometrica" UndefinedValue = "Valore non definito" ValuesTab = "Tabella" Warning = "Attenzione" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index b3d5ab0a19f..30d3cfd7819 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -48,7 +48,6 @@ 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" @@ -70,7 +69,6 @@ Plot = "Grafiek plotten" PoolMemoryFull1 = "Het werkgeheugen is vol." PoolMemoryFull2 = "Opnieuw proberen." Rename = "Hernoem" -RoundAbscissa = "Geheel getal" Sci = "sci" SortValues = "Sorteer waarden oplopend" SortSizes = "Sorteer frequenties oplopend" @@ -87,7 +85,6 @@ ThetaEnd = "θ einde" ThetaStart = "θ begin" TStart = "T begin" ToZoom = "Zoom: " -Trigonometric = "Goniometrisch" UndefinedValue = "Ongedefinieerde waarde" ValuesTab = "Tabel" Warning = "Waarschuwing" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index b0fd4ac0e42..a4fc7603cc4 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -48,7 +48,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" @@ -70,7 +69,6 @@ Plot = "Traçar o gráfico" PoolMemoryFull1 = "A memória de trabalho está cheia." PoolMemoryFull2 = "Tente novamente." Rename = "Renomear" -RoundAbscissa = "Inteiro" Sci = "cie" SortValues = "Ordenar por ordem crescente" SortSizes = "Ordenar por ordem crescente" @@ -87,7 +85,6 @@ ThetaEnd = "θ fim" ThetaStart = "θ início" TStart = "T início" ToZoom = "Zoom : " -Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" ValuesTab = "Tabela" Warning = "Atenção" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 2d68eee9146..45ee4c71a51 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -48,7 +48,6 @@ app_shared_src = $(addprefix apps/shared/,\ 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 \ diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 65a6653c0a5..8944d922159 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -13,7 +13,6 @@ 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), 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); diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 72717664bd9..2604bd078b9 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" @@ -16,7 +15,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu 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); bool isEmpty() const override; - ViewController * initialisationParameterController() override; void didBecomeFirstResponder() override; void viewWillAppear() override; @@ -57,7 +55,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu 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/initialisation_parameter_controller.cpp b/apps/shared/initialisation_parameter_controller.cpp deleted file mode 100644 index 531fed09148..00000000000 --- 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 e1f1c925c90..00000000000 --- 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 20bac53116f..fb5fe865a3f 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -29,12 +29,6 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren 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) { } @@ -129,11 +123,11 @@ int InteractiveCurveViewController::numberOfButtons(ButtonRowController::Positio if (isEmpty()) { return 0; } - return 3; + return 2; } Button * InteractiveCurveViewController::buttonAtIndex(int index, ButtonRowController::Position position) const { - const Button * buttons[3] = {&m_rangeButton, &m_zoomButton, &m_defaultInitialisationButton}; + const Button * buttons[] = {&m_rangeButton, &m_zoomButton}; return (Button *)buttons[index]; } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 99927b65a46..aec24ea02c1 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -21,7 +21,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll RangeParameterController * rangeParameterController(); ViewController * zoomParameterController(); - virtual ViewController * initialisationParameterController() = 0; int numberOfButtons(ButtonRowController::Position position) const override; Button * buttonAtIndex(int index, ButtonRowController::Position position) const override; @@ -81,7 +80,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll InteractiveCurveViewRange * m_interactiveRange; 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 7011f408a1e..1963cfde528 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -76,17 +76,6 @@ void InteractiveCurveViewRange::panWithVector(float x, float y) { 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() { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ @@ -116,18 +105,6 @@ void InteractiveCurveViewRange::normalize() { } } -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); -} - void InteractiveCurveViewRange::setDefault() { if (m_delegate == nullptr) { return; diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 1c6d308a21e..3c5262d516d 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -32,9 +32,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { // 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 setDefault(); void centerAxisAround(Axis axis, float position); void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); From 1a5661fd4cac84f4897ba6f89cf32c10f966698c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 10:53:37 +0200 Subject: [PATCH 276/560] [apps/range_parameter_controller] Remove YAuto This allows for a great deal of simplifications in the class RangeParamterController. Change-Id: I5de55d4e8e1f598eb13b08fb8f042907f55b2fa8 --- apps/regression/graph_controller.cpp | 3 - apps/regression/store.cpp | 1 - apps/sequence/graph/curve_view_range.cpp | 1 - apps/sequence/graph/graph_controller.cpp | 1 - apps/shared.universal.i18n | 1 - apps/shared/function_graph_controller.cpp | 4 - apps/shared/interactive_curve_view_range.cpp | 19 +---- apps/shared/interactive_curve_view_range.h | 5 -- .../interactive_curve_view_range_delegate.cpp | 39 +-------- apps/shared/range_parameter_controller.cpp | 84 ++++--------------- apps/shared/range_parameter_controller.h | 25 +----- 11 files changed, 22 insertions(+), 161 deletions(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 2231f2a927f..2d936eecdee 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -256,9 +256,6 @@ 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(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); - } *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 4de71661712..45e0f2e840e 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -139,7 +139,6 @@ int Store::nextDot(int series, int direction, int dot) { /* Window */ void Store::setDefault() { - m_yAuto = true; float minX = FLT_MAX; float maxX = -FLT_MAX; for (int series = 0; series < k_numberOfSeries; series++) { diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index 77d2aa20ee9..c08a82e1265 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -36,7 +36,6 @@ void CurveViewRange::normalize() { } // Compute the Y - m_yAuto = false; const float newYHalfRange = NormalizedYHalfRange(unit); float newYMin = yMean - newYHalfRange; float newYMax = clipped(yMean + newYHalfRange, true); diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 1178d20e7d7..458218acab5 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -59,7 +59,6 @@ void GraphController::interestingRanges(InteractiveCurveViewRange * range) const assert(nmax - nmin >= k_defaultXHalfRange); range->setXMin(nmin); - range->setYAuto(true); range->setXMax(nmax); } diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 4e0072a5e52..a328de9fe95 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -187,7 +187,6 @@ TransposeCommandWithArg = "transpose(M)" XMax = "Xmax" XMin = "Xmin" X = "x" -YAuto = "Y auto" YMax = "Ymax" YMin = "Ymin" Y = "y" diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 8944d922159..2ca8ae180a7 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -93,9 +93,6 @@ void FunctionGraphController::initCursorParameters() { functionIndex = 0; } m_cursor->moveTo(t, xy.x1(), xy.x2()); - if (interactiveCurveViewRange()->yAuto()) { - interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth()); - } selectFunctionWithCursor(functionIndex); } @@ -153,7 +150,6 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang Shared::InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { InteractiveCurveViewRange tempRange = *interactiveCurveViewRange; - tempRange.setYAuto(false); privateComputeRanges(false, &tempRange); return Shared::InteractiveCurveViewRangeDelegate::Range{.min = tempRange.yMin(), .max = tempRange.yMax()}; } diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 1963cfde528..1f39cf56bbb 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -11,17 +11,12 @@ 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); + float data[] = {xMin(), xMax(), yMin(), yMax()}; + 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)); } -void InteractiveCurveViewRange::setYAuto(bool yAuto) { - m_yAuto = yAuto; - notifyRangeChange(); -} - void InteractiveCurveViewRange::setXMin(float xMin) { MemoizedCurveViewRange::protectedSetXMin(xMin, k_lowerMaxFloat, k_upperMaxFloat); notifyRangeChange(); @@ -52,7 +47,6 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { 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); @@ -66,7 +60,6 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { } 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; } @@ -80,8 +73,6 @@ void InteractiveCurveViewRange::normalize() { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ - m_yAuto = false; - float xRange = xMax() - xMin(); float yRange = yMax() - yMin(); float xyRatio = xRange/yRange; @@ -111,7 +102,6 @@ void InteractiveCurveViewRange::setDefault() { } // Compute the interesting range - m_yAuto = false; m_delegate->interestingRanges(this); // Add margins @@ -143,7 +133,6 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { m_xRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); } else { - m_yAuto = false; float range = yMax() - yMin(); if (std::fabs(position/range) > k_maxRatioPositionRange) { range = Range1D::defaultRangeLengthFor(position); @@ -158,7 +147,6 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; if (x < xMin() + leftMargin) { - m_yAuto = 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(); @@ -167,7 +155,6 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } const float rightMargin = rightMarginRatio * xRange; if (x > xMax() - rightMargin) { - m_yAuto = 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); @@ -177,14 +164,12 @@ 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; 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; m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 3c5262d516d..17d91220dcd 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -13,16 +13,12 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { public: InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) : MemoizedCurveViewRange(), - m_yAuto(true), m_delegate(delegate) {} void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } uint32_t rangeChecksum() override; - bool yAuto() const { return m_yAuto; } - void setYAuto(bool yAuto); - // CurveViewWindow void setXMin(float f) override; void setXMax(float f) override; @@ -55,7 +51,6 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { * 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; InteractiveCurveViewRangeDelegate * m_delegate; private: void notifyRangeChange(); diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp index 1232676ffe8..4c137f7edc0 100644 --- a/apps/shared/interactive_curve_view_range_delegate.cpp +++ b/apps/shared/interactive_curve_view_range_delegate.cpp @@ -6,42 +6,9 @@ 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; - } - 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; + /*TODO : De we want to keep this method ? + * We might want to put some computations in here, like the new Auto and Normailzed statuses. */ + return false; } } diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index 6256ce6aaa8..62a0f77e717 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -9,23 +9,16 @@ 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); } } @@ -34,59 +27,26 @@ 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::GrayDark : KDColorBlack; - KDColor colors[k_numberOfTextCell+1] = {KDColorBlack, KDColorBlack, KDColorBlack, yColor, yColor}; - myCell->setTextColor(colors[index]); + myCell->setTextColor(KDColorBlack); 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(); - return true; - } 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); @@ -98,41 +58,25 @@ 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() { diff --git a/apps/shared/range_parameter_controller.h b/apps/shared/range_parameter_controller.h index 6de99e91206..52e75a6c546 100644 --- a/apps/shared/range_parameter_controller.h +++ b/apps/shared/range_parameter_controller.h @@ -13,39 +13,20 @@ class RangeParameterController : public FloatParameterController { 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; }; From 8970e294aa32d07ee4ab8af96e7d02c8014b4924 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 11:45:28 +0200 Subject: [PATCH 277/560] [apps] Remove modelVersion from curves apps The graph range used to be reset to default whenever all functions were modified. As we no longer want to reset the range without the user's input, we do not need to track whether the functions changed at all. /!\ As of this commit, there is no longer a way to restore the default zoom, until a new automatic zoom button is added. Change-Id: Ie74e8fd61e13055fa6ce2b2d1e883182d4ecffce --- apps/graph/app.cpp | 2 +- apps/graph/graph/graph_controller.cpp | 8 +-- apps/graph/graph/graph_controller.h | 3 +- apps/regression/app.cpp | 4 +- apps/regression/app.h | 3 - apps/regression/graph_controller.cpp | 13 +--- apps/regression/graph_controller.h | 5 +- apps/sequence/app.cpp | 2 +- apps/sequence/graph/graph_controller.cpp | 4 +- apps/sequence/graph/graph_controller.h | 2 +- apps/shared/function_app.cpp | 5 -- apps/shared/function_app.h | 4 -- apps/shared/function_graph_controller.cpp | 12 +--- apps/shared/function_graph_controller.h | 6 +- apps/shared/function_store.cpp | 7 --- apps/shared/function_store.h | 1 - .../interactive_curve_view_controller.cpp | 59 ++----------------- .../interactive_curve_view_controller.h | 10 +--- 18 files changed, 22 insertions(+), 128 deletions(-) diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 9845cecc1a8..e64c100ac5b 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -54,7 +54,7 @@ App::App(Snapshot * snapshot) : 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(), snapshot->angleUnitVersion(), &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/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 14232d7171c..36b5a9e7c5e 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, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : + FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion, angleUnitVersion), m_bannerView(this, inputEventHandlerDelegate, this), m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(curveViewRange), @@ -79,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 347ceabc6f3..0d558f211e4 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -15,7 +15,7 @@ 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, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } @@ -34,7 +34,6 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont ContinuousFunctionStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } 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/regression/app.cpp b/apps/regression/app.cpp index 9331fdefc36..c81fc75969c 100644 --- a/apps/regression/app.cpp +++ b/apps/regression/app.cpp @@ -23,7 +23,6 @@ App::Snapshot::Snapshot() : m_store(), m_cursor(), m_graphSelectedDotIndex(-1), - m_modelVersion(0), m_rangeVersion(0), m_selectedSeriesIndex(-1) { @@ -35,7 +34,6 @@ App * App::Snapshot::unpack(Container * container) { void App::Snapshot::reset() { m_store.reset(); - m_modelVersion = 0; m_rangeVersion = 0; setActiveTab(0); } @@ -55,7 +53,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 5ebb06f1b52..140b83114ec 100644 --- a/apps/regression/app.h +++ b/apps/regression/app.h @@ -30,15 +30,12 @@ class App : public Shared::TextFieldDelegateApp { 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/graph_controller.cpp b/apps/regression/graph_controller.cpp index 2d936eecdee..9e56ce98d9b 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -11,8 +11,8 @@ 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), @@ -338,15 +338,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(); } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 852027573b0..fbe9b121cac 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -15,7 +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); + 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; @@ -40,10 +40,7 @@ class GraphController : public Shared::InteractiveCurveViewController { // InteractiveCurveViewController void initCursorParameters() override; - uint32_t modelVersion() override; - uint32_t modelVersionAtIndex(int i) 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; diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index 3f01d17bc98..3a4ee7191eb 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -50,7 +50,7 @@ App::App(Snapshot * snapshot) : 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(), snapshot->angleUnitVersion(), &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/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 458218acab5..b4643df10e1 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -12,8 +12,8 @@ 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, Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : + FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, graphRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion, angleUnitVersion), m_bannerView(this, inputEventHandlerDelegate, this), m_view(sequenceStore, graphRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(graphRange), diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index 76accdee820..cbb17d9484c 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -14,7 +14,7 @@ namespace Sequence { class GraphController final : public Shared::FunctionGraphController { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::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, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; TermSumController * termSumController() { return &m_termSumController; } diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index b9de91e5a91..b66b01d1a0f 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -7,18 +7,13 @@ namespace Shared { FunctionApp::Snapshot::Snapshot() : m_cursor(), m_indexFunctionSelectedByCursor(0), - m_modelVersion(0), m_rangeVersion(0), m_angleUnitVersion(Preferences::AngleUnit::Radian) { - 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); } diff --git a/apps/shared/function_app.h b/apps/shared/function_app.h index 4763906b5db..df932d2af74 100644 --- a/apps/shared/function_app.h +++ b/apps/shared/function_app.h @@ -16,8 +16,6 @@ class FunctionApp : public ExpressionFieldDelegateApp { 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; @@ -28,8 +26,6 @@ class FunctionApp : public ExpressionFieldDelegateApp { 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; }; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 2ca8ae180a7..869de86b8b3 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -11,8 +11,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), +FunctionGraphController::FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion) : + InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, interactiveRange, curveView, cursor, rangeVersion), m_angleUnitVersion(angleUnitVersion), m_indexFunctionSelectedByCursor(indexFunctionSelectedByCursor) { @@ -120,14 +120,6 @@ 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(); } diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 2604bd078b9..fca17d75300 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -12,8 +12,7 @@ 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, Poincare::Preferences::AngleUnit * angleUnitVersion); bool isEmpty() const override; void didBecomeFirstResponder() override; void viewWillAppear() override; @@ -50,10 +49,7 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu // 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; } Poincare::Preferences::AngleUnit * m_angleUnitVersion; int * m_indexFunctionSelectedByCursor; diff --git a/apps/shared/function_store.cpp b/apps/shared/function_store.cpp index 4ef45d9565f..77ed25132a2 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 0233a37dd14..2790f670355 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/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index fb5fe865a3f..56ab2d9932a 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -8,11 +8,9 @@ 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), @@ -135,58 +133,13 @@ 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. */ - initCursorParameters(); - } - uint32_t newRangeVersion = rangeVersion(); - if (*m_rangeVersion != newRangeVersion) { - *m_rangeVersion = newRangeVersion; - if (!isCursorVisible()) { - initCursorParameters(); - } - } + + /* Warning: init cursor parameter before reloading banner view. Indeed, + * reloading banner view needs an updated cursor to load the right data. */ + initCursorParameters(); + curveView()->setOkView(&m_okView); if (!curveView()->isMainViewSelected()) { curveView()->selectMainView(true); diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index aec24ea02c1..4e8523c9173 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -12,7 +12,7 @@ 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; @@ -27,8 +27,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll Responder * defaultController() override; - bool previousModelsWereAllDeleted(); - void viewWillAppear() override; void viewDidDisappear() override; void willExitResponderChain(Responder * nextFirstResponder) override; @@ -40,8 +38,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll 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(); @@ -70,10 +66,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll // InteractiveCurveViewRangeDelegate float addMargin(float x, float range, bool isVertical, bool isMin) override; - 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; From 13d1b7077cbce42f83eecf123cac4be7d394df81 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 13:47:55 +0200 Subject: [PATCH 278/560] [apps/shared] Removed method computeYRange This method of InteractiveCurveViewRangeDelegate was only used to compute the Y range when Yauto was on. Change-Id: I9b688a87e16fc58c7d2b00eb071076b98f945df6 --- apps/regression/graph_controller.cpp | 17 ----------------- apps/regression/graph_controller.h | 3 --- apps/shared/function_graph_controller.cpp | 6 ------ apps/shared/function_graph_controller.h | 1 - .../interactive_curve_view_range_delegate.h | 2 -- 5 files changed, 29 deletions(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 9e56ce98d9b..aad2a78e532 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -366,23 +366,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 fbe9b121cac..4adf50f9c4b 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -49,9 +49,6 @@ class GraphController : public Shared::InteractiveCurveViewController { 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; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 869de86b8b3..7f6bf66d793 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -140,12 +140,6 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang privateComputeRanges(true, range); } -Shared::InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) { - InteractiveCurveViewRange tempRange = *interactiveCurveViewRange; - privateComputeRanges(false, &tempRange); - return Shared::InteractiveCurveViewRangeDelegate::Range{.min = tempRange.yMin(), .max = tempRange.yMax()}; -} - void FunctionGraphController::privateComputeRanges(bool tuneXRange, InteractiveCurveViewRange * range) const { Poincare::Context * context = textFieldDelegateApp()->localContext(); float resultXMin = tuneXRange ? FLT_MAX : range->xMin(); diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index fca17d75300..9605564d851 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -39,7 +39,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu void initCursorParameters() override; CurveView * curveView() override; - Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; void privateComputeRanges(bool tuneXRange, Shared::InteractiveCurveViewRange * range) const; void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index e5c05a9213f..bd9f07dcca1 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -20,8 +20,6 @@ class InteractiveCurveViewRangeDelegate { float min; float max; }; -private: - virtual Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) = 0; }; } From e2725c8e61bd10e9807c5dec64e2d609024915b5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 14:18:05 +0200 Subject: [PATCH 279/560] [apps/interactive_curve_view_range] Status markers Add member variable m_zoomAuto and m_zoomNormalized to remember the interactive range's status. Change-Id: I77c43ddd683d3c6011742801139d23f5e20c99fe --- apps/shared/interactive_curve_view_range.cpp | 10 ++++------ apps/shared/interactive_curve_view_range.h | 14 ++++++++++++-- apps/shared/range_parameter_controller.cpp | 3 +++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 1f39cf56bbb..3f9f1e6ab9f 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -19,12 +19,10 @@ uint32_t InteractiveCurveViewRange::rangeChecksum() { 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) { @@ -176,10 +174,10 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } } -void InteractiveCurveViewRange::notifyRangeChange() { - if (m_delegate) { - m_delegate->didChangeRange(this); - } +void InteractiveCurveViewRange::checkForNormalizedRange() { + float pixelHeight = std::round(Ion::Display::Height * (NormalizedYHalfRange(100.f) / Ion::Display::HeightInTenthOfMillimeter)); + float pixelWidth = std::round(Ion::Display::Width * (NormalizedXHalfRange(100.f) / Ion::Display::WidthInTenthOfMillimeter)); + m_zoomNormalize = std::round(pixelHeight * (xMax() - xMin())) == std::round(pixelWidth * (yMax() - yMin())); } } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 17d91220dcd..4b1c389bdd6 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -13,12 +13,19 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { public: InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) : MemoizedCurveViewRange(), - m_delegate(delegate) + m_delegate(delegate), + m_zoomAuto(true), + m_zoomNormalize(false) {} void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } uint32_t rangeChecksum() override; + bool zoomAuto() const { return m_zoomAuto; } + void setZoomAuto(bool v) { m_zoomAuto = v; } + bool zoomNormalize() const { return m_zoomNormalize; } + void setZoomNormalize(bool v) { m_zoomNormalize = v; } + // CurveViewWindow void setXMin(float f) override; void setXMax(float f) override; @@ -32,6 +39,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { virtual void setDefault(); void centerAxisAround(Axis axis, float position); void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); + void checkForNormalizedRange(); + protected: constexpr static float k_upperMaxFloat = 1E+8f; constexpr static float k_lowerMaxFloat = 9E+7f; @@ -53,7 +62,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } InteractiveCurveViewRangeDelegate * m_delegate; private: - void notifyRangeChange(); + 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/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index 62a0f77e717..d841783d016 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -81,6 +81,9 @@ int RangeParameterController::reusableParameterCellCount(int type) { void RangeParameterController::buttonAction() { *m_interactiveRange = m_tempInteractiveRange; + m_interactiveRange->setZoomAuto(false); + m_interactiveRange->checkForNormalizedRange(); + StackViewController * stack = stackController(); stack->pop(); } From cc22b0d298dee71e26a29d6f33bfd682f4263ad5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 14:21:09 +0200 Subject: [PATCH 280/560] [apps/shared] Reset curve range to default in Auto When the range is set to ZoomAuto, the range is recomputed before displaying the curve. --- apps/shared/interactive_curve_view_controller.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 56ab2d9932a..29cb6068dd1 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -136,6 +136,10 @@ Responder * InteractiveCurveViewController::defaultController() { void InteractiveCurveViewController::viewWillAppear() { SimpleInteractiveCurveViewController::viewWillAppear(); + 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. */ initCursorParameters(); From 5c67f92d18942bd298f8d274998d2e0723070b78 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 14:55:26 +0200 Subject: [PATCH 281/560] [apps/interactive_curve_view_controller] Buttons Add two new buttons to toggle Auto and Normalized modes for curves. Change-Id: Ib583d087ce5ae779d24f6a8bbc72ab7140752dd5 --- apps/shared.de.i18n | 1 - apps/shared.en.i18n | 1 - apps/shared.es.i18n | 1 - apps/shared.fr.i18n | 1 - apps/shared.it.i18n | 1 - apps/shared.nl.i18n | 1 - apps/shared.pt.i18n | 1 - apps/shared.universal.i18n | 1 + .../interactive_curve_view_controller.cpp | 53 ++++++++++++++++--- .../interactive_curve_view_controller.h | 9 ++++ apps/shared/interactive_curve_view_range.cpp | 4 +- 11 files changed, 58 insertions(+), 16 deletions(-) diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index d300411ef70..46029c6e0b2 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -29,7 +29,6 @@ CountryWarning1 = "Diese Einstellung definiert die verwendeten" CountryWarning2 = "mathematischen Konventionen." DataNotSuitable = "Daten nicht geeignet" DataTab = "Daten" -DefaultSetting = "Grundeinstellung" Deg = "gra" Deviation = "Varianz" DisplayValues = "Werte anzeigen" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 653ce19ccad..d1a10ea3e2c 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -29,7 +29,6 @@ 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" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 4d9db157405..985c8be44e0 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -29,7 +29,6 @@ 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" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 9a04b810ca3..fc14d256534 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -29,7 +29,6 @@ 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" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 0297dbbc7d0..1a91cae8fda 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -29,7 +29,6 @@ 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" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 30d3cfd7819..272ae980720 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -29,7 +29,6 @@ CountryWarning1 = "Deze instelling definieert de" CountryWarning2 = "gebruikte wiskundige conventies." DataNotSuitable = "Gegevens niet geschikt" DataTab = "Gegevens" -DefaultSetting = "Standaardinstelling" Deg = "deg" Deviation = "Variantie" DisplayValues = "Waarden weergeven" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index a4fc7603cc4..fc7470c24c3 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -29,7 +29,6 @@ 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" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index a328de9fe95..9c77fe6b40e 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -114,6 +114,7 @@ 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)" diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 29cb6068dd1..bb9abd1fe1f 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -15,6 +15,16 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren 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_rangeButton(this, I18n::Message::Axis, Invocation([](void * context, void * sender) { InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; graphController->rangeParameterController()->setRange(graphController->interactiveRange()); @@ -64,14 +74,18 @@ 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) { @@ -121,11 +135,11 @@ int InteractiveCurveViewController::numberOfButtons(ButtonRowController::Positio if (isEmpty()) { return 0; } - return 2; + return 4; } Button * InteractiveCurveViewController::buttonAtIndex(int index, ButtonRowController::Position position) const { - const Button * buttons[] = {&m_rangeButton, &m_zoomButton}; + const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_rangeButton, &m_zoomButton}; return (Button *)buttons[index]; } @@ -275,4 +289,29 @@ float InteractiveCurveViewController::estimatedBannerHeight() const { return BannerView::HeightGivenNumberOfLines(estimatedBannerNumberOfLines()); } +bool InteractiveCurveViewController::autoButtonAction() { + if (m_interactiveRange->zoomAuto()) { + m_interactiveRange->setZoomAuto(false); + } else { + m_interactiveRange->setDefault(); + m_interactiveRange->setZoomAuto(true); + m_interactiveRange->checkForNormalizedRange(); + setCurveViewAsMainView(); + } + return m_interactiveRange->zoomAuto(); +} + +bool InteractiveCurveViewController::normalizeButtonAction() { + if (m_interactiveRange->zoomNormalize()) { + m_interactiveRange->setZoomNormalize(false); + } else { + m_interactiveRange->normalize(); + m_interactiveRange->setZoomAuto(false); + m_interactiveRange->setZoomNormalize(true); + setCurveViewAsMainView(); + } + return m_interactiveRange->zoomNormalize(); +} + + } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 4e8523c9173..fff685f3029 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -66,10 +66,19 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll // InteractiveCurveViewRangeDelegate float addMargin(float x, float range, bool isVertical, bool isMin) override; + void setCurveViewAsMainView(); + + /* Those methods return the new status for the button, ie either + * m_interactiveRange->m_zoomAuto or m_zoomNormalize respectively. */ + bool autoButtonAction(); + bool normalizeButtonAction(); + uint32_t * m_rangeVersion; RangeParameterController m_rangeParameterController; ZoomParameterController m_zoomParameterController; InteractiveCurveViewRange * m_interactiveRange; + Button m_autoButton; + Button m_normalizeButton; Button m_rangeButton; Button m_zoomButton; }; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 3f9f1e6ab9f..f9ee20cc788 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -81,13 +81,13 @@ void InteractiveCurveViewRange::normalize() { float normalizedXYRatio = newXHalfRange/newYHalfRange; if (xyRatio < normalizedXYRatio) { float newXRange = normalizedXYRatio * yRange; - assert(newXRange > xRange); + 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); + 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); From 43d21180bc480908e6e370f889f5256427ac7b48 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 15:51:24 +0200 Subject: [PATCH 282/560] [apps/shared] Deactivate zoomAuto on pan and zoom The Auto mode for curve ranges must be deactivated when moving the cursor outside the screen and when activating navigation (formerly Zoom). Change-Id: I281a1d069c2ebcc30e7ead469c4f48438054d7c2 --- apps/shared/interactive_curve_view_controller.cpp | 8 ++++++-- apps/shared/interactive_curve_view_controller.h | 3 ++- apps/shared/interactive_curve_view_range.cpp | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index bb9abd1fe1f..06f219938a2 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -34,8 +34,7 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren }, 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()); + graphController->navigationButtonAction(); return true; }, this), KDFont::SmallFont) { @@ -313,5 +312,10 @@ bool InteractiveCurveViewController::normalizeButtonAction() { return m_interactiveRange->zoomNormalize(); } +void InteractiveCurveViewController::navigationButtonAction() { + m_interactiveRange->setZoomAuto(false); + stackController()->push(zoomParameterController()); +} + } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index fff685f3029..53c33963e42 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -68,7 +68,8 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll void setCurveViewAsMainView(); - /* Those methods return the new status for the button, ie either + 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(); diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index f9ee20cc788..46d3471274f 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -38,6 +38,7 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { float xMa = xMax(); float yMi = yMin(); float yMa = yMax(); + m_zoomAuto = false; if (ratio*std::fabs(xMa-xMi) < Range1D::k_minFloat || ratio*std::fabs(yMa-yMi) < Range1D::k_minFloat) { return; } @@ -145,6 +146,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; if (x < xMin() + leftMargin) { + m_zoomAuto = 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(); @@ -153,6 +155,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } const float rightMargin = rightMarginRatio * xRange; if (x > xMax() - rightMargin) { + m_zoomAuto = 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); @@ -162,12 +165,14 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float yRange = yMax() - yMin(); const float bottomMargin = bottomMarginRatio * yRange; if (y < yMin() + bottomMargin) { + m_zoomAuto = 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_zoomAuto = false; m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); } From bf76b56f9096decd77515d5bfec1b72b8fe33e55 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 16:10:14 +0200 Subject: [PATCH 283/560] [apps/shared] Change Zoom button in curves Zoom is now called navigate, and its button is placed before the Axes button. Change-Id: Ied194c20438e573a237ce36f221b9be87cfd4bab --- apps/shared.de.i18n | 1 + apps/shared.en.i18n | 1 + apps/shared.es.i18n | 1 + apps/shared.fr.i18n | 1 + apps/shared.it.i18n | 1 + apps/shared.nl.i18n | 1 + apps/shared.pt.i18n | 1 + apps/shared.universal.i18n | 1 - apps/shared/interactive_curve_view_controller.cpp | 12 ++++++------ apps/shared/interactive_curve_view_controller.h | 2 +- apps/shared/zoom_parameter_controller.cpp | 2 +- 11 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 46029c6e0b2..80bb683d824 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -55,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" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index d1a10ea3e2c..bc9344b50ad 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -55,6 +55,7 @@ Move = " Move: " 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" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 985c8be44e0..8b9f366cf0c 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -55,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" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index fc14d256534..df8873c706c 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -55,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" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 1a91cae8fda..439622e84e9 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -55,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" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 272ae980720..d5b526ffa59 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -55,6 +55,7 @@ Move = " Verplaats: " NameCannotStartWithNumber = "Een naam kan niet beginnen met een nummer" NameTaken = "Deze naam is al in gebruik" NameTooLong = "Deze naam is te lang" +Navigate = "Bladeren" Next = "Volgende" NoDataToPlot = "Geen gegevens om te plotten" NoFunctionToDelete = "Geen functie om te verwijderen" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index fc7470c24c3..80009b7d942 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -55,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" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 9c77fe6b40e..03f8b3f03c8 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -191,4 +191,3 @@ X = "x" YMax = "Ymax" YMin = "Ymin" Y = "y" -Zoom = "Zoom" diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 06f219938a2..402c0888bb5 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -25,17 +25,17 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren graphController->normalizeButtonAction(); return true; }, this), KDFont::SmallFont), + m_zoomButton(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; - graphController->navigationButtonAction(); - return true; }, this), KDFont::SmallFont) { } @@ -138,7 +138,7 @@ int InteractiveCurveViewController::numberOfButtons(ButtonRowController::Positio } Button * InteractiveCurveViewController::buttonAtIndex(int index, ButtonRowController::Position position) const { - const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_rangeButton, &m_zoomButton}; + const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_zoomButton, &m_rangeButton}; return (Button *)buttons[index]; } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 53c33963e42..62a6b24381c 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -80,8 +80,8 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll InteractiveCurveViewRange * m_interactiveRange; Button m_autoButton; Button m_normalizeButton; - Button m_rangeButton; Button m_zoomButton; + Button m_rangeButton; }; } diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index f468aff72d4..90ed8ddd045 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -12,7 +12,7 @@ ZoomParameterController::ZoomParameterController(Responder * parentResponder, In } const char * ZoomParameterController::title() { - return I18n::translate(I18n::Message::Zoom); + return I18n::translate(I18n::Message::Navigate); } void ZoomParameterController::viewWillAppear() { From f0cd0694a6ea6e2a732313da359a062f97b72489 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 16:31:21 +0200 Subject: [PATCH 284/560] [apps/shared] Activating Auto zoom resets cursor Change-Id: Ib815286e1316e20f72b31d3d9206aa032b06eff4 --- apps/shared/interactive_curve_view_controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 402c0888bb5..ce4902d8467 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -295,6 +295,7 @@ bool InteractiveCurveViewController::autoButtonAction() { m_interactiveRange->setDefault(); m_interactiveRange->setZoomAuto(true); m_interactiveRange->checkForNormalizedRange(); + initCursorParameters(); setCurveViewAsMainView(); } return m_interactiveRange->zoomAuto(); From 1d4d56a7dd9034a7e99fcdbdc0139a7e41ea97a6 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 17:40:51 +0200 Subject: [PATCH 285/560] [apps/range_parameter_controller] Change margins Reduce the top and bottom margin of the table view to better see the Confirm button. Change-Id: I71df479400c44a10fe75b8beaae1c81be305cdfd --- apps/shared/range_parameter_controller.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index d841783d016..3d8b6a45810 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -20,6 +20,10 @@ RangeParameterController::RangeParameterController(Responder * parentResponder, 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() { From 0f1e27b2b64e98efcf93d47e972b05864a90a905 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 2 Oct 2020 17:56:12 +0200 Subject: [PATCH 286/560] [apps/sequence] Add Y range to interestingRanges The computation of Sequence's graph default Y range used to rely on Yauto. As this feature does not exist anymore, this work is now done int Sequence::GraphController::interestingRanges. Change-Id: Idba8560d5f25d0bf34dd0e1dd98c2af67f427709 --- apps/sequence/graph/graph_controller.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index b4643df10e1..dda21f17b73 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -60,6 +60,19 @@ void GraphController::interestingRanges(InteractiveCurveViewRange * range) const range->setXMin(nmin); range->setXMax(nmax); + + Context * context = textFieldDelegateApp()->localContext(); + float yMin = FLT_MAX, yMax = -FLT_MAX; + for (int i = 0; i < nbOfActiveModels; i++) { + Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; + Zoom::RefinedYRangeForDisplay(evaluation, nmin, nmax, &yMin, &yMax, context, s); + } + + range->setYMin(yMin); + range->setYMax(yMax); } bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { From 7322751453e2b1b908cd0f4777b64dbdeb62cf59 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 7 Oct 2020 11:19:55 +0200 Subject: [PATCH 287/560] [regression] Compute Y range for curve Regression's automatic Y range used to rely on the Yauto feature. Since it has been removed, it is now done in Store::setDefault. Change-Id: I1b6653bb8ebe0ba99decff05bae29bd5a69a9f6c --- apps/regression/store.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 45e0f2e840e..06f486095fa 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -150,6 +150,20 @@ void Store::setDefault() { float range = maxX - minX; setXMin(minX - k_displayHorizontalMarginRatio * range); setXMax(maxX + k_displayHorizontalMarginRatio * range); + + float minY = FLT_MAX; + float maxY = -FLT_MAX; + for (int series = 0; series < k_numberOfSeries; series++) { + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (xMin() <= get(series, 0, k) && get(series, 0, k) <= xMax()) { + minY = std::min(minY, get(series, 1, k)); + maxY = std::max(maxY, get(series, 1, k)); + } + } + } + range = maxY - minY; + setYMin(m_delegate->addMargin(minY, range, true, true)); + setYMax(m_delegate->addMargin(maxY, range, true, false)); } /* Series */ From 26754a3230f5c47ce07dc1f99f1e7c5ecaf81234 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 6 Oct 2020 11:57:45 +0200 Subject: [PATCH 288/560] [graph] Force same grid unit on orthonormal graphs Change-Id: I2abca9eb1c4a14057323ee97e1044715f89620ff --- apps/shared/curve_view_range.h | 3 ++- apps/shared/interactive_curve_view_range.cpp | 16 ++++++++++++++++ apps/shared/interactive_curve_view_range.h | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/shared/curve_view_range.h b/apps/shared/curve_view_range.h index caa1d9ab041..ae31a94f2a6 100644 --- a/apps/shared/curve_view_range.h +++ b/apps/shared/curve_view_range.h @@ -27,9 +27,10 @@ class CurveViewRange { } 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: /* 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.*/ diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 46d3471274f..86953bc049d 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -33,6 +33,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(); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 4b1c389bdd6..5ba3ba3a613 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -26,6 +26,10 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { bool zoomNormalize() const { return m_zoomNormalize; } void setZoomNormalize(bool v) { m_zoomNormalize = v; } + // MemoizedCurveViewRange + float xGridUnit() const override { return m_zoomNormalize ? yGridUnit() : MemoizedCurveViewRange::xGridUnit(); } + float yGridUnit() const override; + // CurveViewWindow void setXMin(float f) override; void setXMax(float f) override; From 07c52139b94b3bb8480b65ba53bbd66faf9d06f9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 7 Oct 2020 11:46:14 +0200 Subject: [PATCH 289/560] [apps/shared] InteractiveCurveViewRangeDelegate Clean up the class by removing some unused methods and structures. Change-Id: Ife38a9a0ed4bfb84986227d1f57fe3f5d2c4ff2a --- apps/shared/Makefile | 1 - .../interactive_curve_view_range_delegate.cpp | 14 -------------- .../shared/interactive_curve_view_range_delegate.h | 6 ------ 3 files changed, 21 deletions(-) delete mode 100644 apps/shared/interactive_curve_view_range_delegate.cpp diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 45ee4c71a51..8fb5e859c56 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -11,7 +11,6 @@ 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 \ labeled_curve_view.cpp \ memoized_curve_view_range.cpp \ diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp deleted file mode 100644 index 4c137f7edc0..00000000000 --- a/apps/shared/interactive_curve_view_range_delegate.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "interactive_curve_view_range_delegate.h" -#include "interactive_curve_view_range.h" -#include -#include - -namespace Shared { - -bool InteractiveCurveViewRangeDelegate::didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) { - /*TODO : De we want to keep this method ? - * We might want to put some computations in here, like the new Auto and Normailzed statuses. */ - return false; -} - -} diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index bd9f07dcca1..0fcbea73566 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -10,16 +10,10 @@ class InteractiveCurveViewRange; class InteractiveCurveViewRangeDelegate { public: static constexpr float k_defaultXHalfRange = 10.0f; - bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange); virtual float interestingXMin() const { return -k_defaultXHalfRange; } virtual bool defaultRangeIsNormalized() const { return false; } virtual void interestingRanges(InteractiveCurveViewRange * range) const { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; -protected: - struct Range { - float min; - float max; - }; }; } From ad6edffc071ec73bc659867ee05f1083a734dc43 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 11:56:21 +0200 Subject: [PATCH 290/560] [poincare/zoom] Remove tuneXRange argument The ability to perform a range search without changing the X range was only there to accomodate the Yauto feature. Change-Id: I8c2b61b447fbd3dc1f4e303dff06d1a8d6e7a4f2 --- apps/shared/continuous_function.cpp | 4 +-- apps/shared/continuous_function.h | 2 +- apps/shared/function.cpp | 4 +-- apps/shared/function.h | 4 +-- apps/shared/function_graph_controller.cpp | 10 ++---- apps/shared/function_graph_controller.h | 1 - apps/shared/sequence.h | 2 +- poincare/include/poincare/zoom.h | 2 +- poincare/src/zoom.cpp | 44 +++++++++++------------ 9 files changed, 32 insertions(+), 41 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 36d6b4390e2..7fd9b47c398 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -261,9 +261,9 @@ void ContinuousFunction::setTMax(float tMax) { setCache(nullptr); } -void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange) const { +void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const { if (plotType() == PlotType::Cartesian) { - protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange, true); + protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, true); } else { fullXYRange(xMin, xMax, yMin, yMax, context); } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index c66be00f740..34642fb8ab2 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -70,7 +70,7 @@ class ContinuousFunction : public Function { 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, Poincare::Context * context, bool tuneXRange = true) const override; + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override; // Extremum Poincare::Coordinate2D nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const; diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 6cfc6996386..253e51f77e2 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -79,7 +79,7 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(d.buffer)); } -void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange, bool boundByMagnitude) const { +void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const { 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 @@ -89,7 +89,7 @@ void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin constexpr float precision = 1e-5; return precision * std::round(static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision); }; - Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this, tuneXRange); + 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(); diff --git a/apps/shared/function.h b/apps/shared/function.h index 49442d1b381..83e65f273db 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -55,7 +55,7 @@ class Function : public ExpressionModelHandle { 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, Poincare::Context * context, bool tuneXRange = true) const = 0; + virtual void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const = 0; protected: /* RecordDataBuffer is the layout of the data buffer of Record @@ -93,7 +93,7 @@ class Function : public ExpressionModelHandle { bool m_active; }; - void protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange, bool boundByMagnitude) const; + void protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const; private: RecordDataBuffer * recordData() const; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 7f6bf66d793..eb554782783 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -137,20 +137,16 @@ int FunctionGraphController::numberOfCurves() const { } void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const { - privateComputeRanges(true, range); -} - -void FunctionGraphController::privateComputeRanges(bool tuneXRange, InteractiveCurveViewRange * range) const { Poincare::Context * context = textFieldDelegateApp()->localContext(); - float resultXMin = tuneXRange ? FLT_MAX : range->xMin(); - float resultXMax = tuneXRange ? -FLT_MAX : range->xMax(); + float resultXMin = FLT_MAX; + float resultXMax = -FLT_MAX; float resultYMin = FLT_MAX; float resultYMax = -FLT_MAX; assert(functionStore()->numberOfActiveFunctions() > 0); int functionsCount = functionStore()->numberOfActiveFunctions(); for (int i = 0; i < functionsCount; i++) { ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context, tuneXRange); + f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context); } range->setXMin(resultXMin); diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 9605564d851..6c3469a25c0 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -39,7 +39,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu void initCursorParameters() override; CurveView * curveView() override; - void privateComputeRanges(bool tuneXRange, Shared::InteractiveCurveViewRange * range) const; void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; private: diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index 58ac6e7adb7..2918f704122 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -76,7 +76,7 @@ friend class SequenceStore; constexpr static int k_initialRankNumberOfDigits = 3; // m_initialRank is capped by 999 //Range - void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool tuneXRange = true) const override { protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, tuneXRange, false); }; + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, false); }; private: constexpr static const KDFont * k_layoutFont = KDFont::LargeFont; diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 69a33d5f7f4..33a08e932c4 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -12,7 +12,7 @@ class Zoom { typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); - static void InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary, bool tuneXRange = false); + static void InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary); static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); private: diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 315f255bf80..b5aedb0a73a 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -17,15 +17,12 @@ constexpr float Zoom::k_defaultHalfRange, Zoom::k_maxRatioBetweenPointsOfInterest; -void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary, bool tuneXRange) { +void 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 (!tuneXRange) { - center = (*xMax + *xMin) / 2.f; - maxDistance = (*xMax - *xMin) / 2.f; - } else if (hasIntervalOfDefinition) { + if (hasIntervalOfDefinition) { center = (tMax + tMin) / 2.f; maxDistance = (tMax - tMin) / 2.f; } else { @@ -132,26 +129,25 @@ void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, } } - if (tuneXRange) { - /* Cut after horizontal asymptotes. */ - resultX[0] = std::min(resultX[0], asymptote[0]); - resultX[1] = std::max(resultX[1], asymptote[1]); - if (resultX[0] >= resultX[1]) { - /* Fallback to default range. */ - resultX[0] = - k_defaultHalfRange; - resultX[1] = k_defaultHalfRange; - } else { - /* Add breathing room around points of interest. */ - float xRange = resultX[1] - resultX[0]; - resultX[0] -= k_breathingRoom * xRange; - resultX[1] += k_breathingRoom * xRange; - /* Round to the next integer. */ - resultX[0] = std::floor(resultX[0]); - resultX[1] = std::ceil(resultX[1]); - } - *xMin = std::min(resultX[0], *xMin); - *xMax = std::max(resultX[1], *xMax); + /* Cut after horizontal asymptotes. */ + resultX[0] = std::min(resultX[0], asymptote[0]); + resultX[1] = std::max(resultX[1], asymptote[1]); + if (resultX[0] >= resultX[1]) { + /* Fallback to default range. */ + resultX[0] = - k_defaultHalfRange; + resultX[1] = k_defaultHalfRange; + } else { + /* Add breathing room around points of interest. */ + float xRange = resultX[1] - resultX[0]; + resultX[0] -= k_breathingRoom * xRange; + resultX[1] += k_breathingRoom * xRange; + /* Round to the next integer. */ + resultX[0] = std::floor(resultX[0]); + resultX[1] = std::ceil(resultX[1]); } + *xMin = std::min(resultX[0], *xMin); + *xMax = std::max(resultX[1], *xMax); + *yMin = std::min(resultYMin, *yMin); *yMax = std::max(resultYMax, *yMax); } From 0465cec9241f0a4cdf90fd612293fe0b722a25bd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 14:10:14 +0200 Subject: [PATCH 291/560] [poincare/zoom] Remove rounding of abscissa As margins are added later in InteractiveCurveViewRange::setDefault, rounding abscissa at this point is premature. Change-Id: I411dd014b712649efd55cb995ed7a9c5c14a7f43 --- poincare/src/zoom.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b5aedb0a73a..5edab809a80 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -141,9 +141,6 @@ void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float xRange = resultX[1] - resultX[0]; resultX[0] -= k_breathingRoom * xRange; resultX[1] += k_breathingRoom * xRange; - /* Round to the next integer. */ - resultX[0] = std::floor(resultX[0]); - resultX[1] = std::ceil(resultX[1]); } *xMin = std::min(resultX[0], *xMin); *xMax = std::max(resultX[1], *xMax); From c4aad1641eaa794d684aa264f43fb187e6ee0dbf Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 15:34:43 +0200 Subject: [PATCH 292/560] [poincare/zoom] Rename k_defaultMaxInterval Bring it in line with k_minimalDistance which as a similar role. Change-Id: Ifc3ad793e8b5ff1e7f2598d0665abdd5c954e61f --- poincare/include/poincare/zoom.h | 2 +- poincare/src/zoom.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 33a08e932c4..4b3dfd42c63 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -18,7 +18,7 @@ class Zoom { private: static constexpr int k_peakNumberOfPointsOfInterest = 3; static constexpr int k_sampleSize = Ion::Display::Width / 4; - static constexpr float k_defaultMaxInterval = 2e5f; + static constexpr float k_maximalDistance = 1e5f; static constexpr float k_minimalDistance = 1e-2f; static constexpr float k_asymptoteThreshold = 2e-1f; static constexpr float k_stepFactor = 1.1f; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 5edab809a80..ac4ed1956ba 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -8,7 +8,7 @@ constexpr int Zoom::k_peakNumberOfPointsOfInterest, Zoom::k_sampleSize; constexpr float - Zoom::k_defaultMaxInterval, + Zoom::k_maximalDistance, Zoom::k_minimalDistance, Zoom::k_asymptoteThreshold, Zoom::k_stepFactor, @@ -27,7 +27,7 @@ void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, maxDistance = (tMax - tMin) / 2.f; } else { center = 0.f; - maxDistance = k_defaultMaxInterval / 2.f; + maxDistance = k_maximalDistance; } float resultX[2] = {FLT_MAX, - FLT_MAX}; From 08c96c51078ebef48b31f2e70c13d488194d531c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 16:36:37 +0200 Subject: [PATCH 293/560] [poincare/zoom] Method SetToRatio Added a method to Zoom to set a range to a specific ratio. This method is used by InteractiveCurveViewRange::normalize. Change-Id: Id3029439294ece02ea19fb6c6de90b9edc85d771 --- apps/shared/interactive_curve_view_range.cpp | 27 ++++++++------------ poincare/include/poincare/zoom.h | 4 +++ poincare/src/zoom.cpp | 23 +++++++++++++++++ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 86953bc049d..d7181f66d3b 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include using namespace Poincare; @@ -88,27 +89,19 @@ void InteractiveCurveViewRange::normalize() { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ - float xRange = xMax() - xMin(); - float yRange = yMax() - yMin(); - float xyRatio = xRange/yRange; + 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 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); - } + float normalizedYXRatio = newYHalfRange/newXHalfRange; + + Zoom::SetToRatio(normalizedYXRatio, &newXMin, &newXMax, &newYMin, &newYMax); + + 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); } void InteractiveCurveViewRange::setDefault() { diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 4b3dfd42c63..1adb1d425ae 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -15,6 +15,10 @@ class Zoom { static void InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary); static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); + /* 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); + private: static constexpr int k_peakNumberOfPointsOfInterest = 3; static constexpr int k_sampleSize = Ion::Display::Width / 4; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index ac4ed1956ba..2c93223fa9a 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -199,6 +199,29 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } } +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; +} + 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; From 403315f6aca5eac31aed31ac7e52d51beabc7ead Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 17:02:01 +0200 Subject: [PATCH 294/560] [poincare/zoom] Find range with specific ratio Change-Id: Id0c816c42e3fa93519241bf53f920c333e204af1 --- poincare/include/poincare/zoom.h | 5 ++++- poincare/src/zoom.cpp | 38 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 1adb1d425ae..5b0d445d92c 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -12,8 +12,11 @@ class Zoom { typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); - static void InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary); + /* 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); static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); + static void RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary); /* 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.*/ diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 2c93223fa9a..c556db42295 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -17,7 +17,7 @@ constexpr float Zoom::k_defaultHalfRange, Zoom::k_maxRatioBetweenPointsOfInterest; -void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary) { +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); @@ -136,6 +136,7 @@ void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, /* Fallback to default range. */ resultX[0] = - k_defaultHalfRange; resultX[1] = k_defaultHalfRange; + return false; } else { /* Add breathing room around points of interest. */ float xRange = resultX[1] - resultX[0]; @@ -147,6 +148,8 @@ void Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, *yMin = std::min(resultYMin, *yMin); *yMax = std::max(resultYMax, *yMax); + + return true; } void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude) { @@ -222,6 +225,39 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, f *tMin = center - newRange / 2.f; } +void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { + constexpr int searchSpeedUp = 13; + constexpr float rangeMagnitudeWeight = 0.2f; + constexpr float maxMagnitudeDifference = 1.2f; + + const float step = std::pow(k_stepFactor, searchSpeedUp); + float bestGrade = FLT_MAX; + float xRange = k_minimalDistance; + float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; + while (xRange < k_maximalDistance) { + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, true); + float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); + float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight; + if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { + bestGrade = grade; + *xMin = -xRange; + *xMax = xRange; + *yMin = yMinRange; + *yMax = yMaxRange; + } + + xRange *= step; + } + if (bestGrade == FLT_MAX) { + *xMin = -k_defaultHalfRange; + *xMax = k_defaultHalfRange; + RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary, true); + return; + } + + SetToRatio(yxRatio, xMin, xMax, yMin, yMax, true); +} + 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; From d642339963be48f32d397995e2ebfee4d8d07a6f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 17:09:27 +0200 Subject: [PATCH 295/560] [interactive_curve_view_range] Normal ratio The method NormaRatio returns the screen ratio for an orthonormal range. Change-Id: Ib834f9934232866646da16c78447782cdbdc0af2 --- apps/shared/interactive_curve_view_range.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 5ba3ba3a613..79106f1ddae 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -18,6 +18,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { m_zoomNormalize(false) {} + static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } + void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } uint32_t rangeChecksum() override; From 29714b401efecef33782d46c40b16a18219160cf Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 17:13:12 +0200 Subject: [PATCH 296/560] [shared/function] Create an orthonormal range When the function does not have any points of interest, fall back to an orthonormal range. Change-Id: I90e4bcfae80ebd37f95d404a4a63121c93cf7cff --- apps/shared/function.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 253e51f77e2..0ff2ec9b2c7 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -1,4 +1,5 @@ #include "function.h" +#include "interactive_curve_view_range.h" #include "range_1D.h" #include "poincare_helpers.h" #include "poincare/src/parsing/parser.h" @@ -89,11 +90,16 @@ void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin constexpr float precision = 1e-5; return precision * std::round(static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision); }; - Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this); + 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(); }; - Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this, boundByMagnitude); + + if (fullyComputed) { + Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this, boundByMagnitude); + } else { + Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this); + } } } From 5d9c7b0c1f2bdb1feb78ed001eab4cec660a86c4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 17:17:16 +0200 Subject: [PATCH 297/560] [interactive_curve_view_range] isOrthonormal Change-Id: I154487908d1a2a11e01b2a4e51ef867e235babab --- apps/shared/interactive_curve_view_range.cpp | 7 +++++-- apps/shared/interactive_curve_view_range.h | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index d7181f66d3b..31e87abb666 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -189,9 +189,12 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } void InteractiveCurveViewRange::checkForNormalizedRange() { + m_zoomNormalize = isOrthonormal(); +} + +bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { float pixelHeight = std::round(Ion::Display::Height * (NormalizedYHalfRange(100.f) / Ion::Display::HeightInTenthOfMillimeter)); float pixelWidth = std::round(Ion::Display::Width * (NormalizedXHalfRange(100.f) / Ion::Display::WidthInTenthOfMillimeter)); - m_zoomNormalize = std::round(pixelHeight * (xMax() - xMin())) == std::round(pixelWidth * (yMax() - yMin())); + return std::fabs(std::round(pixelHeight * (xMax() - xMin())) - std::round(pixelWidth * (yMax() - yMin()))) <= tolerance; } - } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 79106f1ddae..447f746ad59 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -68,6 +68,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } InteractiveCurveViewRangeDelegate * m_delegate; private: + bool isOrthonormal(float tolerance = 0.f) const; + bool m_zoomAuto; bool m_zoomNormalize; }; From f54b15b9c8db293719c4b97d933578005bcf238a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 8 Oct 2020 17:31:27 +0200 Subject: [PATCH 298/560] [interactive_curve_view_range] Preserve ratio Preserve orthonormality when adding margins to a default range. Change-Id: Iaa1da8d36812e2dad1be3894198ee0fabcd3020f --- apps/shared/interactive_curve_view_range.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 31e87abb666..8a4d1103330 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -111,6 +111,7 @@ void InteractiveCurveViewRange::setDefault() { // Compute the interesting range m_delegate->interestingRanges(this); + bool revertToNormalized = isOrthonormal(); // Add margins float xRange = xMax() - xMin(); @@ -121,7 +122,7 @@ void InteractiveCurveViewRange::setDefault() { 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); - if (!m_delegate->defaultRangeIsNormalized()) { + if (!(m_delegate->defaultRangeIsNormalized() || revertToNormalized)) { return; } From bcb4b3095bd3b97ef0d5acd2169eb9f1d6841856 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 10:11:27 +0200 Subject: [PATCH 299/560] [function_graph_controller] Fix Y range expansion The method FunctionGraphController::yRangeForCursorFirstMove expands the Y range to include the values of the first cursor moves on the left and the right of the center. It has been modified to preserve orthonormality. Change-Id: I898ab9721e45e2acde261f8b94b80cab81b39a92 --- apps/shared/function_graph_controller.cpp | 6 ++++++ apps/shared/interactive_curve_view_range.cpp | 4 ++++ apps/shared/interactive_curve_view_range.h | 3 +-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index eb554782783..df603d7775b 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -165,6 +165,8 @@ void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; float yN, yP; + bool normalized = range->isOrthonormal(); + for (int i = 0; i < functionsCount; i++) { ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); yN = f->evaluateXYAtParameter(range->xCenter() - cursorStep, context).x2(); @@ -172,6 +174,10 @@ void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange range->setYMin(std::min(range->yMin(), std::min(yN, yP))); range->setYMax(std::max(range->yMax(), std::max(yN, yP))); } + + if (normalized) { + range->normalize(); + } } } diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 8a4d1103330..0c3a7fdabc4 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -109,6 +109,10 @@ void InteractiveCurveViewRange::setDefault() { return; } + /* If m_zoomNormalize was left active, xGridUnit() would return the value of + * yGridUnit, even if the ranger were not truly normalized. */ + m_zoomNormalize = false; + // Compute the interesting range m_delegate->interestingRanges(this); bool revertToNormalized = isOrthonormal(); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 447f746ad59..b0c8d38c94c 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -19,6 +19,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { {} static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } + bool isOrthonormal(float tolerance = 0.f) const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } uint32_t rangeChecksum() override; @@ -68,8 +69,6 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } InteractiveCurveViewRangeDelegate * m_delegate; private: - bool isOrthonormal(float tolerance = 0.f) const; - bool m_zoomAuto; bool m_zoomNormalize; }; From 43f50b7b2d86989ef926c451ea62ca4b0f3166bc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 11:22:02 +0200 Subject: [PATCH 300/560] [interactive_curve_view_range] Missing refresh The normalized status would not be refreshed after a call to setDefault triggered by InteractiveCurveViewController::viewWillAppear. Change-Id: I923ef3ca0585dbcc1f32b992d7dd1f4a3a206532 --- apps/shared/interactive_curve_view_controller.cpp | 1 + apps/shared/interactive_curve_view_range.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index ce4902d8467..d091aea8dae 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -151,6 +151,7 @@ void InteractiveCurveViewController::viewWillAppear() { if (m_interactiveRange->zoomAuto()) { m_interactiveRange->setDefault(); + m_interactiveRange->checkForNormalizedRange(); } /* Warning: init cursor parameter before reloading banner view. Indeed, diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 0c3a7fdabc4..a0c91610da5 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -194,7 +194,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } void InteractiveCurveViewRange::checkForNormalizedRange() { - m_zoomNormalize = isOrthonormal(); + setZoomNormalize(isOrthonormal()); } bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { From 88ea32bd0d83a95e1385c8bc896112535ef37d6f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 11:41:37 +0200 Subject: [PATCH 301/560] [poincare/zoom] Change range forms for fixed ratio When looking for a range with a certain ratio using RangeWithRatioForDisplay, instead of looking for ranges of the form 0.01*1.1^n, search for ranges of the form a*10^k, with a being either 1, 2, or 5. Change-Id: I2eaa229574a9a6aa4afa6eb1a3d57365d3d52801 --- poincare/src/zoom.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index c556db42295..3a09992331b 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -226,27 +226,29 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, f } void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { - constexpr int searchSpeedUp = 13; + constexpr float units[] = {1.f, 2.f, 5.f}; constexpr float rangeMagnitudeWeight = 0.2f; constexpr float maxMagnitudeDifference = 1.2f; - const float step = std::pow(k_stepFactor, searchSpeedUp); float bestGrade = FLT_MAX; - float xRange = k_minimalDistance; + float xMagnitude = k_minimalDistance; float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; - while (xRange < k_maximalDistance) { - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, true); - float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); - float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight; - if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { - bestGrade = grade; - *xMin = -xRange; - *xMax = xRange; - *yMin = yMinRange; - *yMax = yMaxRange; - } + while (xMagnitude < k_maximalDistance) { + for(const float unit : units) { + const float xRange = unit * xMagnitude; + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, true); + float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); + float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight; + if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { + bestGrade = grade; + *xMin = -xRange; + *xMax = xRange; + *yMin = yMinRange; + *yMax = yMaxRange; + } - xRange *= step; + } + xMagnitude *= 10.f; } if (bestGrade == FLT_MAX) { *xMin = -k_defaultHalfRange; From a525c35ebbb4bea764b9acc19aaf0274a07c9293 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 11:59:58 +0200 Subject: [PATCH 302/560] [poincare/zoom] Factor constants The mantissas {1, 2, 5} used in CurveViewRange to compute the grid units can be factored in Zoom. Change-Id: I2bcc9b9df1ff6b6df82092a5d78ed3db132bf63a --- apps/shared/curve_view_range.h | 7 ++++--- poincare/include/poincare/zoom.h | 3 +++ poincare/src/zoom.cpp | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/shared/curve_view_range.h b/apps/shared/curve_view_range.h index ae31a94f2a6..40c0709bb69 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 { @@ -34,9 +35,9 @@ class CurveViewRange { /* 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/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 5b0d445d92c..a12c9f1d8e8 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -9,6 +9,9 @@ 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; typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 3a09992331b..b9019840548 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -15,7 +15,10 @@ constexpr float Zoom::k_breathingRoom, Zoom::k_forceXAxisThreshold, Zoom::k_defaultHalfRange, - Zoom::k_maxRatioBetweenPointsOfInterest; + Zoom::k_maxRatioBetweenPointsOfInterest, + Zoom::k_smallUnitMantissa, + Zoom::k_mediumUnitMantissa, + Zoom::k_largeUnitMantissa; 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); @@ -226,7 +229,7 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, f } void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { - constexpr float units[] = {1.f, 2.f, 5.f}; + constexpr float units[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; constexpr float rangeMagnitudeWeight = 0.2f; constexpr float maxMagnitudeDifference = 1.2f; From 09c061e871299394a7ec3333aea96ec5049e5ee3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 12:44:30 +0200 Subject: [PATCH 303/560] [poincare/zoom] Method RangeFromSingleValue This method adds margins to a range whose bounds are equal. Change-Id: I2b2fe26fe431deda112389060d401f738a75b1ae --- apps/shared/range_1D.h | 2 +- poincare/include/poincare/zoom.h | 4 ++++ poincare/src/zoom.cpp | 17 +++++++++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index b89b316d8d3..bd1b7a3b9e2 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -19,7 +19,7 @@ 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_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), diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index a12c9f1d8e8..958c8875642 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -12,6 +12,7 @@ class Zoom { 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); @@ -25,6 +26,9 @@ class Zoom { * 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); + /* Compute a default range so that boundMin < value < boundMax */ + static void RangeFromSingleValue(float value, float * boundMin, float * boundMax); + private: static constexpr int k_peakNumberOfPointsOfInterest = 3; static constexpr int k_sampleSize = Ion::Display::Width / 4; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b9019840548..75e23640177 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -18,7 +18,8 @@ constexpr float Zoom::k_maxRatioBetweenPointsOfInterest, Zoom::k_smallUnitMantissa, Zoom::k_mediumUnitMantissa, - Zoom::k_largeUnitMantissa; + Zoom::k_largeUnitMantissa, + Zoom::k_minimalRangeLength; 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); @@ -191,9 +192,7 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float *yMin = std::min(*yMin, sampleYMin); *yMax = std::max(*yMax, sampleYMax); if (*yMin == *yMax) { - float d = (*yMin == 0.f) ? 1.f : *yMin * 0.2f; - *yMin -= d; - *yMax += d; + RangeFromSingleValue(*yMin, yMin, yMax); } /* 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 @@ -263,6 +262,16 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f SetToRatio(yxRatio, xMin, xMax, yMin, yMax, true); } +void Zoom::RangeFromSingleValue(float value, float * boundMin, float * boundMax) { + constexpr float margin = 0.2f; + float delta = margin * std::fabs(value); + if (delta < k_minimalRangeLength) { + delta = 1.f; + } + *boundMin = value - delta; + *boundMax = value + delta; +} + 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; From 81e425eb04412d8246a2391ae1d104c49536984d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 12 Oct 2020 12:56:34 +0200 Subject: [PATCH 304/560] [poincare/zoom] Method FullRange Change-Id: Ibfaa0b694afecab312199b20b102a57901f34ae7 --- poincare/include/poincare/zoom.h | 1 + poincare/src/zoom.cpp | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 958c8875642..ff375415c8e 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -21,6 +21,7 @@ class Zoom { static bool InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary); static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); 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); /* 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.*/ diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 75e23640177..f20a596bab0 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -262,6 +262,25 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f SetToRatio(yxRatio, xMin, xMax, yMin, yMax, true); } +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 (value < *fMin) { + *fMin = value; + } + if (value > *fMax) { + *fMax = value; + } + t += tStep; + } + if (*fMin == *fMax) { + RangeFromSingleValue(*fMin, fMin, fMax); + } +} + void Zoom::RangeFromSingleValue(float value, float * boundMin, float * boundMax) { constexpr float margin = 0.2f; float delta = margin * std::fabs(value); From 8572f4953c393b92a74f531f2e5a02d372608b1a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 13 Oct 2020 11:15:57 +0200 Subject: [PATCH 305/560] [poincare/zoom] Method CombineRanges The logic behind combining several ranges will be handled by the Zoom class directly. Change-Id: I4de3f020d94b9bc1a21953b0c416158c65beb606 --- poincare/include/poincare/zoom.h | 2 ++ poincare/src/zoom.cpp | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index ff375415c8e..2c0b7b89147 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -23,6 +23,8 @@ class Zoom { 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); + static void CombineRanges(int length, const float * mins, const float * maxs, float * minRes, float * maxRes); + /* 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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index f20a596bab0..b7004baa472 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -204,6 +204,25 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } } +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 (min < *minRes) { + *minRes = min; + } + if (max > *maxRes) { + *maxRes = max; + } + if (*minRes == *maxRes) { + RangeFromSingleValue(*minRes, minRes, maxRes); + } +} + void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, bool shrink) { float currentRatio = (*yMax - *yMin) / (*xMax - *xMin); @@ -276,9 +295,6 @@ void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float t } t += tStep; } - if (*fMin == *fMax) { - RangeFromSingleValue(*fMin, fMin, fMax); - } } void Zoom::RangeFromSingleValue(float value, float * boundMin, float * boundMax) { From 6be5e7d62cea749edbda7eab888024026fd6a048 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 13 Oct 2020 12:19:26 +0200 Subject: [PATCH 306/560] [apps/shared] Change Zoom API Moved some code around to decrease redundancy and put more of the logic into Poincare::Zoom Change-Id: I4804cf39493ac7f2f0b3c4eb554e5c15c3cef1c9 --- apps/regression/store.cpp | 36 ++++++++--------- apps/sequence/graph/graph_controller.cpp | 29 -------------- apps/sequence/graph/graph_controller.h | 1 - apps/shared/continuous_function.cpp | 49 +++++++++++------------ apps/shared/function.cpp | 31 ++++++-------- apps/shared/function.h | 2 +- apps/shared/function_graph_controller.cpp | 27 +++++++------ apps/shared/sequence.cpp | 10 +++++ apps/shared/sequence.h | 2 +- poincare/include/poincare/zoom.h | 2 +- poincare/src/zoom.cpp | 28 ++++++------- 11 files changed, 91 insertions(+), 126 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 06f486095fa..0dc813ea7e5 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -139,31 +139,27 @@ int Store::nextDot(int series, int direction, int dot) { /* Window */ void Store::setDefault() { - float minX = FLT_MAX; - float maxX = -FLT_MAX; + float min, max; + 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, &min, &max); + float range = max - min; + setXMin(min - k_displayHorizontalMarginRatio * range); + setXMax(max + k_displayHorizontalMarginRatio * range); - float minY = FLT_MAX; - float maxY = -FLT_MAX; for (int series = 0; series < k_numberOfSeries; series++) { - for (int k = 0; k < numberOfPairsOfSeries(series); k++) { - if (xMin() <= get(series, 0, k) && get(series, 0, k) <= xMax()) { - minY = std::min(minY, get(series, 1, k)); - maxY = std::max(maxY, get(series, 1, k)); - } - } + bool empty = seriesIsEmpty(series); + mins[series] = empty ? NAN : minValueOfColumn(series, 1); + maxs[series] = empty ? NAN : maxValueOfColumn(series, 1); } - range = maxY - minY; - setYMin(m_delegate->addMargin(minY, range, true, true)); - setYMax(m_delegate->addMargin(maxY, range, true, false)); + Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &min, &max); + range = max - min; + setYMin(m_delegate->addMargin(min, range, true, true)); + setYMax(m_delegate->addMargin(max, range, true, false)); } /* Series */ diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index dda21f17b73..4bf2833828e 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -46,35 +46,6 @@ float GraphController::interestingXMin() const { return nmin; } -void GraphController::interestingRanges(InteractiveCurveViewRange * range) const { - int nmin = INT_MAX; - int nmax = 0; - int nbOfActiveModels = functionStore()->numberOfActiveFunctions(); - for (int i = 0; i < nbOfActiveModels; i++) { - Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - int firstInterestingIndex = s->initialRank(); - nmin = std::min(nmin, firstInterestingIndex); - nmax = std::max(nmax, firstInterestingIndex + static_cast(k_defaultXHalfRange)); - } - assert(nmax - nmin >= k_defaultXHalfRange); - - range->setXMin(nmin); - range->setXMax(nmax); - - Context * context = textFieldDelegateApp()->localContext(); - float yMin = FLT_MAX, yMax = -FLT_MAX; - for (int i = 0; i < nbOfActiveModels; i++) { - Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { - return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); - }; - Zoom::RefinedYRangeForDisplay(evaluation, nmin, nmax, &yMin, &yMax, context, s); - } - - range->setYMin(yMin); - range->setYMax(yMax); -} - bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp(); double floatBody; diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index cbb17d9484c..7bc98500391 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -20,7 +20,6 @@ class GraphController final : public Shared::FunctionGraphController { TermSumController * termSumController() { return &m_termSumController; } // InteractiveCurveViewRangeDelegate float interestingXMin() const override; - void interestingRanges(Shared::InteractiveCurveViewRange * range) const override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; private: Shared::XYBannerView * bannerView() override { return &m_bannerView; } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 7fd9b47c398..616866239f7 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -262,36 +262,33 @@ void ContinuousFunction::setTMax(float tMax) { } void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const { - if (plotType() == PlotType::Cartesian) { - protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, true); - } else { - fullXYRange(xMin, xMax, yMin, yMax, context); + 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; } -} -void ContinuousFunction::fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Context * context) const { - assert(yMin && yMax); - assert(!(std::isinf(tMin()) || std::isinf(tMax()) || std::isnan(rangeStep()))); + 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); - float resultXMin = FLT_MAX, resultXMax = - FLT_MAX, resultYMin = FLT_MAX, resultYMax = - FLT_MAX; - for (float t = tMin(); t <= tMax(); t += rangeStep()) { - Coordinate2D xy = privateEvaluateXYAtParameter(t, context); - if (!std::isfinite(xy.x1()) || !std::isfinite(xy.x2())) { - continue; - } - resultXMin = std::min(xy.x1(), resultXMin); - resultXMax = std::max(xy.x1(), resultXMax); - resultYMin = std::min(xy.x2(), resultYMin); - resultYMax = std::max(xy.x2(), resultYMax); - } - if (xMin) { - *xMin = resultXMin; - } - if (xMax) { - *xMax = resultXMax; + evaluation = [](float x, Context * context, const void * auxiliary) { + return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; + + if (fullyComputed) { + Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this); + } else { + Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this); } - *yMin = resultYMin; - *yMax = resultYMax; } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 0ff2ec9b2c7..2347e1199bb 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -80,26 +80,19 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(d.buffer)); } -void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const { - 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) { - Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this, boundByMagnitude); +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 { - Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this); + 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 83e65f273db..60a214be37e 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -93,7 +93,7 @@ class Function : public ExpressionModelHandle { bool m_active; }; - void protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const; + void protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const; private: RecordDataBuffer * recordData() const; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index df603d7775b..bb50d07d52b 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -138,22 +138,23 @@ int FunctionGraphController::numberOfCurves() const { void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const { Poincare::Context * context = textFieldDelegateApp()->localContext(); - float resultXMin = FLT_MAX; - float resultXMax = -FLT_MAX; - float resultYMin = FLT_MAX; - float resultYMax = -FLT_MAX; - assert(functionStore()->numberOfActiveFunctions() > 0); - int functionsCount = functionStore()->numberOfActiveFunctions(); - for (int i = 0; i < functionsCount; i++) { + 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(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context); + f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, context); } - range->setXMin(resultXMin); - range->setXMax(resultXMax); - range->setYMin(resultYMin); - range->setYMax(resultYMax); - /* We can only call this method once the X range has been fully computed. */ + float xMin, xMax, yMin, yMax; + Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax); + Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax); + range->setXMin(xMin); + range->setXMax(xMax); + range->setYMin(yMin); + range->setYMax(yMax); + yRangeForCursorFirstMove(range); } diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index d9ffbb59122..683aa728851 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../shared/poincare_helpers.h" #include #include @@ -310,6 +311,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, 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(); diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index 2918f704122..cd9421c6b93 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -76,7 +76,7 @@ friend class SequenceStore; constexpr static int k_initialRankNumberOfDigits = 3; // m_initialRank is capped by 999 //Range - void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, false); }; + void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override; private: constexpr static const KDFont * k_layoutFont = KDFont::LargeFont; diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 2c0b7b89147..fa19e6ef4b1 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -19,7 +19,7 @@ class Zoom { /* 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); - static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false); + static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary); 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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b7004baa472..baa87f4ff1e 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -147,16 +147,15 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, resultX[0] -= k_breathingRoom * xRange; resultX[1] += k_breathingRoom * xRange; } - *xMin = std::min(resultX[0], *xMin); - *xMax = std::max(resultX[1], *xMax); - - *yMin = std::min(resultYMin, *yMin); - *yMax = std::max(resultYMax, *yMax); + *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, bool boundByMagnitude) { +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.*/ @@ -184,13 +183,12 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float /* 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 (boundByMagnitude) { - float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; - sampleYMin = std::max(sampleYMin, - bound); - sampleYMax = std::min(sampleYMax, bound); - } - *yMin = std::min(*yMin, sampleYMin); - *yMax = std::max(*yMax, sampleYMax); + + float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; + sampleYMin = std::max(sampleYMin, - bound); + sampleYMax = std::min(sampleYMax, bound); + *yMin = sampleYMin; + *yMax = sampleYMax; if (*yMin == *yMax) { RangeFromSingleValue(*yMin, yMin, yMax); } @@ -257,7 +255,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f while (xMagnitude < k_maximalDistance) { for(const float unit : units) { const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, true); + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary); float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight; if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { @@ -274,7 +272,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f if (bestGrade == FLT_MAX) { *xMin = -k_defaultHalfRange; *xMax = k_defaultHalfRange; - RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary, true); + RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary); return; } From 16707aa518a8c8f40f291b821e40d4e8252125bd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 13 Oct 2020 15:35:26 +0200 Subject: [PATCH 307/560] [poincare/zoom] Tweak orthonormal range Change some parameters to improve the output of RangeWithRatioForDisplay. Notably, change how the size of the range is weighed. Change-Id: I7695c61c3f93482f3fed9c7d2183642573b02cff --- poincare/src/zoom.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index baa87f4ff1e..0744ed60550 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -175,7 +175,7 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } sampleYMin = std::min(sampleYMin, y); sampleYMax = std::max(sampleYMax, y); - if (std::fabs(y) > FLT_EPSILON) { + if (y != 0.f) { sum += std::log(std::fabs(y)); pop++; } @@ -247,7 +247,7 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, f void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { constexpr float units[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; constexpr float rangeMagnitudeWeight = 0.2f; - constexpr float maxMagnitudeDifference = 1.2f; + constexpr float maxMagnitudeDifference = 2.f; float bestGrade = FLT_MAX; float xMagnitude = k_minimalDistance; @@ -257,7 +257,9 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f const float xRange = unit * xMagnitude; RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary); float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); - float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight; + float grade = std::fabs(std::log(currentRatio / yxRatio)); + /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ + grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { bestGrade = grade; *xMin = -xRange; @@ -265,7 +267,6 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f *yMin = yMinRange; *yMax = yMaxRange; } - } xMagnitude *= 10.f; } @@ -276,7 +277,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f return; } - SetToRatio(yxRatio, xMin, xMax, yMin, yMax, true); + SetToRatio(yxRatio, xMin, xMax, yMin, yMax); } void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float tStep, float * fMin, float * fMax, Context * context, const void * auxiliary) { From 190568ea846ef843d5056448cd3d57901138a318 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 14 Oct 2020 09:46:21 +0200 Subject: [PATCH 308/560] [interactive_curve_view_controller] Fix Cursor Restore the permanence of the cursor between accesses to the graph window. The cursor used to be reset when the models had been modified. As there is no longer a system in place to track these modifications, we manually check if there is a curve beneath the cursor before reseting it. Change-Id: I6f71fc17744b5bf1ee552c82126bb4a08b823629 --- apps/regression/graph_controller.cpp | 15 +++++++++++++++ apps/regression/graph_controller.h | 1 + apps/shared/function_graph_controller.cpp | 10 ++++++++++ apps/shared/function_graph_controller.h | 1 + apps/shared/interactive_curve_view_controller.cpp | 6 +++++- apps/shared/interactive_curve_view_controller.h | 1 + 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index aad2a78e532..6ccc4f1f460 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -259,6 +259,21 @@ void GraphController::initCursorParameters() { *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } +bool GraphController::isCursorHanging() { + if (m_store->seriesIsEmpty(*m_selectedSeriesIndex)) { + return true; + } + 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 { + xy = Coordinate2D(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); + } + return xy.x1() != m_cursor->x() || xy.x2() != m_cursor->y(); +} + bool GraphController::moveCursorVertically(int direction) { Poincare::Context * context = globalContext(); double x = m_cursor->x(); diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 4adf50f9c4b..622ac76bcb2 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -40,6 +40,7 @@ class GraphController : public Shared::InteractiveCurveViewController { // InteractiveCurveViewController void initCursorParameters() override; + bool isCursorHanging() override; uint32_t rangeVersion() override; int selectedCurveIndex() const override { return *m_selectedSeriesIndex; } bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index bb50d07d52b..267b45d73f5 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -116,6 +116,16 @@ bool FunctionGraphController::moveCursorVertically(int direction) { return true; } +bool FunctionGraphController::isCursorHanging() { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + if (indexFunctionSelectedByCursor() >= functionStore()->numberOfActiveFunctions()) { + return true; + } + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); + Coordinate2D xy = f->evaluateXYAtParameter(m_cursor->t(), context); + return xy.x1() != m_cursor->x() || xy.x2() != m_cursor->y(); +} + CurveView * FunctionGraphController::curveView() { return functionGraphView(); } diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 6c3469a25c0..0516b0c87eb 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -37,6 +37,7 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu Poincare::Coordinate2D xyValues(int curveIndex, double t, Poincare::Context * context) const override; int numberOfCurves() const override; void initCursorParameters() override; + bool isCursorHanging() override; CurveView * curveView() override; void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index d091aea8dae..e97a3d9dc32 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -156,7 +156,11 @@ void InteractiveCurveViewController::viewWillAppear() { /* Warning: init cursor parameter before reloading banner view. Indeed, * reloading banner view needs an updated cursor to load the right data. */ - initCursorParameters(); + uint32_t newRangeVersion = rangeVersion(); + if ((*m_rangeVersion != newRangeVersion && !isCursorVisible()) || isCursorHanging()) { + initCursorParameters(); + } + *m_rangeVersion = newRangeVersion; curveView()->setOkView(&m_okView); if (!curveView()->isMainViewSelected()) { diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 62a6b24381c..8a7c43cf3eb 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -40,6 +40,7 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll virtual bool moveCursorVertically(int direction) = 0; virtual uint32_t rangeVersion() = 0; bool isCursorVisible(); + virtual bool isCursorHanging() = 0; // Closest vertical curve helper int closestCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const; From 482ca1036382c715bc041567bbcae58a4cdd37ad Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 14 Oct 2020 10:24:47 +0200 Subject: [PATCH 309/560] [poincare/zoom] Improve null range When displaying f(x) = undef, the range now defaults to an orthonormal range, centered on the origin, with axis unit 1. Change-Id: Ie3515be0572af4849b8ebd113449f4444755b34f --- poincare/src/zoom.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 0744ed60550..83430c6601d 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -184,11 +184,16 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float * its average order of magnitude. Then, bound is the value for the next * order of magnitude and is used to cut the Y range. */ - float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX; - sampleYMin = std::max(sampleYMin, - bound); - sampleYMax = std::min(sampleYMax, bound); - *yMin = sampleYMin; - *yMax = sampleYMax; + if (pop == 0) { + *yMin = 0; + *yMax = 0; + } else { + float bound = std::exp(sum / pop + 1.f); + sampleYMin = std::max(sampleYMin, - bound); + sampleYMax = std::min(sampleYMax, bound); + *yMin = sampleYMin; + *yMax = sampleYMax; + } if (*yMin == *yMax) { RangeFromSingleValue(*yMin, yMin, yMax); } From 8ce9f363ada28362a77c26d86d02f1e650ef0638 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 14 Oct 2020 11:15:35 +0200 Subject: [PATCH 310/560] [interactive_curve_view_range] Force equal axes Added a tolerance when checking orthonormality, so that ranges that are nearly orthonormal will be made orthonormal. Change-Id: Ie3bf076086561e3ff6374e7daa9dd1a884c52d5a --- apps/shared/interactive_curve_view_range.cpp | 8 ++++---- apps/shared/interactive_curve_view_range.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index a0c91610da5..90b35fa0efd 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -115,7 +115,7 @@ void InteractiveCurveViewRange::setDefault() { // Compute the interesting range m_delegate->interestingRanges(this); - bool revertToNormalized = isOrthonormal(); + bool revertToNormalized = isOrthonormal(k_orthonormalTolerance); // Add margins float xRange = xMax() - xMin(); @@ -198,8 +198,8 @@ void InteractiveCurveViewRange::checkForNormalizedRange() { } bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { - float pixelHeight = std::round(Ion::Display::Height * (NormalizedYHalfRange(100.f) / Ion::Display::HeightInTenthOfMillimeter)); - float pixelWidth = std::round(Ion::Display::Width * (NormalizedXHalfRange(100.f) / Ion::Display::WidthInTenthOfMillimeter)); - return std::fabs(std::round(pixelHeight * (xMax() - xMin())) - std::round(pixelWidth * (yMax() - yMin()))) <= tolerance; + float ratio = (yMax() - yMin()) / (xMax() - xMin()); + float ratioDifference = std::fabs(std::log(ratio / NormalYXRatio())); + return ratioDifference <= tolerance; } } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index b0c8d38c94c..f9c933e8564 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -19,7 +19,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { {} static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } - bool isOrthonormal(float tolerance = 0.f) const; + bool isOrthonormal(float tolerance = 2 * FLT_EPSILON) const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } uint32_t rangeChecksum() override; @@ -52,6 +52,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float k_upperMaxFloat = 1E+8f; constexpr static float k_lowerMaxFloat = 9E+7f; constexpr static float k_maxRatioPositionRange = 1E5f; + constexpr static float k_orthonormalTolerance = 0.2f; 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. From 1bfeeb58424da1de52c3e6eb3e69d3416290d059 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 14 Oct 2020 11:51:31 +0200 Subject: [PATCH 311/560] [poincare/zoom] Detect explosions Add a clause in InterestingRangesForDisplay to detect strong variations. This improve the graph for x^x or x^2+x+1 for instance. Change-Id: I74214c2382cfe4b0e2603556dd8c6cbd450effc0 --- poincare/include/poincare/zoom.h | 1 + poincare/src/zoom.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index fa19e6ef4b1..079f9a38ee9 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -38,6 +38,7 @@ class Zoom { 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.1f; static constexpr float k_breathingRoom = 0.3f; static constexpr float k_forceXAxisThreshold = 0.2f; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 83430c6601d..e989340affd 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -11,6 +11,7 @@ constexpr float Zoom::k_maximalDistance, Zoom::k_minimalDistance, Zoom::k_asymptoteThreshold, + Zoom::k_explosionThreshold, Zoom::k_stepFactor, Zoom::k_breathingRoom, Zoom::k_forceXAxisThreshold, @@ -37,6 +38,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, 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; float xFallback, yFallback[2] = {NAN, NAN}; float firstResult; @@ -106,6 +108,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, } 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)) { @@ -117,11 +120,16 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, 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)) { + } 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)) { @@ -142,6 +150,8 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, resultX[1] = k_defaultHalfRange; return false; } else { + resultX[0] = std::min(resultX[0], explosion[0]); + resultX[1] = std::max(resultX[1], explosion[1]); /* Add breathing room around points of interest. */ float xRange = resultX[1] - resultX[0]; resultX[0] -= k_breathingRoom * xRange; From 0a6ac0b40b14bf867ebf406b23e3940ba38ce5a2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 14 Oct 2020 16:18:55 +0200 Subject: [PATCH 312/560] [apps/graph] Remove tests on ranges As zooming has moved to Poincare, so will the tests. Change-Id: I6c1fe581edc903369779e46a295447273a89e517 --- apps/graph/Makefile | 1 - apps/graph/test/range.cpp | 182 -------------------------------------- 2 files changed, 183 deletions(-) delete mode 100644 apps/graph/test/range.cpp diff --git a/apps/graph/Makefile b/apps/graph/Makefile index de03db3d67d..2c87734dcb9 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -42,7 +42,6 @@ i18n_files += $(call i18n_without_universal_for,graph/base) tests_src += $(addprefix apps/graph/test/,\ caching.cpp \ helper.cpp \ - range.cpp \ ) $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png)) diff --git a/apps/graph/test/range.cpp b/apps/graph/test/range.cpp deleted file mode 100644 index defda15e721..00000000000 --- a/apps/graph/test/range.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include -#include "helper.h" -#include - -using namespace Poincare; -using namespace Shared; - -namespace Graph { - -void assert_range_inclusion_predicate(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length, bool testInclusion) { - /* Test that all points (x, f(x)) for x in xList are either included in the - * range if testIncluded is true, or exculded from the range if testInclusion - * is false. - * If f(x) is not finite, only the presence of x in the horizontal range is - * tested. */ - GlobalContext globalContext; - ContinuousFunctionStore functionStore; - ContinuousFunction * f = addFunction(definition, type, &functionStore, &globalContext); - - float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; - f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); - assert(xMin <= xMax && yMin <= yMax); - - for (size_t i = 0; i < length; i++) { - float x = xList[i]; - float y = f->evaluateXYAtParameter(x, &globalContext).x2(); - assert(std::isfinite(x)); - if (!testInclusion) { - quiz_assert(xMin > x || x > xMax || (std::isfinite(y) && (yMin > y || y > yMax))); - } else { - /* The program can miss the exact abscissa of an extremum, resulting in - * bounds that are close from its value but that do not encompass it. We - * thus test the inclusion of (x, f(x)) along with two neighbouring - * points. */ - float dx = (xMax - xMin) / (Ion::Display::Width / 2); - float y1 = f->evaluateXYAtParameter(x - dx, &globalContext).x2(), y2 = f->evaluateXYAtParameter(x + dx, &globalContext).x2(); - quiz_assert(xMin <= x - && x <= xMax - && (!std::isfinite(y) - || (yMin <= y && y <= yMax) - || (yMin <= y1 && y1 <= yMax) - || (yMin <= y2 && y2 <= yMax))); - } - } -} - -void assert_range_includes_points(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length) { assert_range_inclusion_predicate(definition, type, xList, length, true); } -void assert_range_excludes_points(const char * definition, ContinuousFunction::PlotType type, const float * xList, size_t length) { assert_range_inclusion_predicate(definition, type, xList, length, false); } - -void assert_range_includes_single_point(const char * definition, ContinuousFunction::PlotType type, float x) { assert_range_includes_points(definition, type, &x, 1); } -void assert_range_excludes_single_point(const char * definition, ContinuousFunction::PlotType type, float x) { assert_range_excludes_points(definition, type, &x, 1); } - -void assert_range_includes_and_bounds_asymptotes(const char * definition, const float * asymptotesXList, size_t length) { - /* The value for delta is the step the old algorithm used to sample a - * cartesian function, causing functions such as 1/x to be evaluated too - * close to 0. */ - constexpr float delta = 1.f / 32.f; - for (size_t i = 0; i < length; i++) { - float x = asymptotesXList[i]; - assert_range_includes_single_point(definition, Cartesian, x); - assert_range_excludes_single_point(definition, Cartesian, x - delta); - assert_range_excludes_single_point(definition, Cartesian, x + delta); - } -} - -void assert_range_shows_enough_periods(const char * definition, float period, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Degree) { - /* The graph should display at least 3 periods, but no more than 7. */ - constexpr int lowNumberOfPeriods = 3, highNumberOfPeriods = 8; - - GlobalContext globalContext; - ContinuousFunctionStore functionStore; - ContinuousFunction * f = addFunction(definition, Cartesian, &functionStore, &globalContext); - - Preferences::sharedPreferences()->setAngleUnit(angleUnit); - if (angleUnit != Preferences::AngleUnit::Degree) { - f->setPlotType(Cartesian, angleUnit, &globalContext); - } - - float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; - f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); - assert(xMin <= xMax && yMin <= yMax); - float numberOfPeriods = (xMax - xMin) / period; - - quiz_assert(lowNumberOfPeriods <= numberOfPeriods && numberOfPeriods <= highNumberOfPeriods); -} - -void assert_range_displays_entire_polar_function(const char * definition) { - GlobalContext globalContext; - ContinuousFunctionStore functionStore; - ContinuousFunction * f = addFunction(definition, Polar, &functionStore, &globalContext); - - float xMin = FLT_MAX, xMax = - FLT_MAX, yMin = FLT_MAX, yMax = - FLT_MAX; - f->rangeForDisplay(&xMin, &xMax, &yMin, &yMax, &globalContext); - assert(xMin <= xMax && yMin <= yMax); - - for (float t = f->tMin(); t < f->tMax(); t += f->rangeStep()) { - const Coordinate2D xy = f->evaluateXYAtParameter(t, &globalContext); - if (!std::isfinite(xy.x1()) || !std::isfinite(xy.x2())) { - continue; - } - quiz_assert(xMin <= xy.x1() && xy.x1() <= xMax && yMin <= xy.x2() && xy.x2() <= yMax); - } -} - -QUIZ_CASE(graph_range_polynomes) { - assert_range_includes_single_point("37", Cartesian, 0.f); - assert_range_includes_single_point("x-1", Cartesian, 1.f); - assert_range_includes_single_point("100+x", Cartesian, -100.f); - assert_range_includes_single_point("x^2-20*x+101", Cartesian, 10.f); - - constexpr float array1[2] = {-2.f, 3.f}; - assert_range_includes_points("(x+2)*(x-3)", Cartesian, array1, 2); - - constexpr float array2[2] = {-1.f, 0.5f}; - assert_range_includes_points("2*x^2+x-1", Cartesian, array2, 2); - - constexpr float array3[6] = {-3.f, 3.f, -2.f, 2.f, -1.f, 1.f}; - assert_range_includes_points("(x+3)(x+2)(x+1)(x-1)(x-2)(x-3)", Cartesian, array3, 6); - - constexpr float array4[3] = {0.f, 4.f, 5.f}; - assert_range_includes_points("x^5-5*x^4", Cartesian, array4, 3); -} - -QUIZ_CASE(graph_range_exponential) { - assert_range_includes_single_point("ℯ^x", Cartesian, 0.f); - assert_range_excludes_single_point("ℯ^x", Cartesian, 4.f); - - assert_range_includes_single_point("ℯ^(-x)", Cartesian, 0.f); - assert_range_excludes_single_point("ℯ^(-x)", Cartesian, -4.f); - - constexpr float array1[3] = {1.f, 5.f, 1e-1f}; - assert_range_includes_points("ln(x)", Cartesian, array1, 3); - assert_range_excludes_single_point("ln(x)", Cartesian, 1e-3f); - - constexpr float array2[2] = {-1.f, 3.f}; - assert_range_includes_points("2-ℯ^(-x)", Cartesian, array2, 2); - assert_range_excludes_single_point("2-ℯ^(-x)", Cartesian, 50.f); - assert_range_excludes_single_point("2-ℯ^(-x)", Cartesian, -10.f); - - assert_range_includes_single_point("x^x", Cartesian, 0.5f); - assert_range_excludes_single_point("x^x", Cartesian, -5.f); - assert_range_includes_single_point("x^(1/x)", Cartesian, 1e-3f); -} - -QUIZ_CASE(graph_range_fractions) { - constexpr float array1[1] = {0.f}; - assert_range_includes_and_bounds_asymptotes("1/x", array1, 1); - assert_range_includes_and_bounds_asymptotes("1/x^2", array1, 1); - - constexpr float array2[2] = {-2.f, 2.f}; - assert_range_includes_and_bounds_asymptotes("1/(x^2-4)", array2, 2); - - constexpr float array3[1] = {-100.f}; - assert_range_includes_and_bounds_asymptotes("1/(x+100)", array3, 1); -} - -QUIZ_CASE(graph_range_periodic) { - assert_range_shows_enough_periods("sin(x)", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("sin(x)", 360.f); - assert_range_shows_enough_periods("sin(x)+10", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("sin(x)+10", 360.f); - assert_range_shows_enough_periods("cos(x)", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("cos(x)", 360.f); - assert_range_shows_enough_periods("sin(x)/x", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("sin(x)/x", 360.f); - assert_range_shows_enough_periods("x*sin(x)", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("x*sin(x)", 360.f); - assert_range_shows_enough_periods("ln(sin(x))", 2*M_PI, Preferences::AngleUnit::Radian); - assert_range_shows_enough_periods("ln(sin(x))", 360.f); - constexpr float array1[2] = {-1.f, 1.f}; - assert_range_includes_points("x*sin(1/x)", Cartesian, array1, 2); -} - -QUIZ_CASE(graph_range_polar) { - assert_range_displays_entire_polar_function("1"); - assert_range_displays_entire_polar_function("θ"); - assert_range_displays_entire_polar_function("sin(θ)"); - assert_range_displays_entire_polar_function("sin(10θ)"); - assert_range_displays_entire_polar_function("ln(θ)"); -} - -} From 20cbefad41e3e2f2701e297798958d28a91a7b4c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 10:48:25 +0200 Subject: [PATCH 313/560] [function_graph_controller] Fix y for first move Method yForCursorFirstMove is supposed to expand the window so that the first move of the cursor won't cause it to pan. It now filters extreme values to prevent the original window to be totally squashed, such as with the drawing of e^x and (x-100)(x+10) at the same time. Change-Id: Icd6c26b01475a112c4231f293e03ab31d80d5e51 --- apps/shared/function_graph_controller.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 267b45d73f5..adeba5bd4ad 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -174,16 +174,27 @@ void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange int functionsCount = functionStore()->numberOfActiveFunctions(); float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; - float yN, yP; + float y[2]; // Left and Right bool normalized = range->isOrthonormal(); + constexpr float maximalExpansion = 10.f; + float yRange = range->yMax() - range->yMin(); + for (int i = 0; i < functionsCount; i++) { ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - yN = f->evaluateXYAtParameter(range->xCenter() - cursorStep, context).x2(); - yP = f->evaluateXYAtParameter(range->xCenter() + cursorStep, context).x2(); - range->setYMin(std::min(range->yMin(), std::min(yN, yP))); - range->setYMax(std::max(range->yMax(), std::max(yN, yP))); + for (int i = 0; i < 2; i++) { + float step = cursorStep * (i == 0 ? -1 : 1); + y[i] = f->evaluateXYAtParameter(range->xCenter() + step, context).x2(); + if (std::isfinite(y[i])) { + if (y[i] < range->yMin() && (range->yMax() - y[i]) < maximalExpansion * yRange) { + range->setYMin(y[i]); + } + if (y[i] > range->yMax() && (y[i] - range->yMin()) < maximalExpansion * yRange) { + range->setYMax(y[i]); + } + } + } } if (normalized) { From 958b172d08fd3616b156bfb552a7ea7dc374e2d8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 11:34:28 +0200 Subject: [PATCH 314/560] [poincare/zoom] Handle undefined functions Rework the logic so that : - an undefined function will be displayed with a clealry defined null range. - display both an undefined function and a correct function at the same time will not affect the correct range. Change-Id: Ife9dc0d2ace667cab5a6b8826347078fca33e4d5 --- apps/shared/continuous_function.cpp | 33 ++++++++++++++++++-- apps/shared/function_graph_controller.cpp | 15 +++++---- apps/shared/interactive_curve_view_range.cpp | 8 +++++ apps/shared/interactive_curve_view_range.h | 1 + poincare/src/zoom.cpp | 20 +++++++----- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 616866239f7..8006980377e 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -285,10 +285,39 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi }; if (fullyComputed) { + /* The function has points of interest. */ Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this); - } else { - Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this); + return; + } + + /* Try to display an orthonormal range. */ + Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), 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 is probably undefined. */ + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; } void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const { diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index adeba5bd4ad..73ed5b298de 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -160,12 +160,15 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang float xMin, xMax, yMin, yMax; Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax); Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax); - range->setXMin(xMin); - range->setXMax(xMax); - range->setYMin(yMin); - range->setYMax(yMax); - - yRangeForCursorFirstMove(range); + if (std::isfinite(xMin) && std::isfinite(xMax) && std::isfinite(yMin) && std::isfinite(yMax) && xMax > xMin && yMax > yMin) { + range->setXMin(xMin); + range->setXMax(xMax); + range->setYMin(yMin); + range->setYMax(yMax); + yRangeForCursorFirstMove(range); + } else { + range->setNullRange(); + } } void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const { diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 90b35fa0efd..20eb45544d1 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -134,6 +134,14 @@ void InteractiveCurveViewRange::setDefault() { normalize(); } +void InteractiveCurveViewRange::setNullRange() { + m_xRange.setMin(- Range1D::k_default); + setXMax(Range1D::k_default); + m_yRange.setMin(0); + m_yRange.setMax(0); + normalize(); +} + void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { if (std::isnan(position)) { return; diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index f9c933e8564..4ea7b9c586b 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -44,6 +44,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { void panWithVector(float x, float y); virtual void normalize(); virtual void setDefault(); + void setNullRange(); void centerAxisAround(Axis axis, float position); void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); void checkForNormalizedRange(); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index e989340affd..d03cb5ff0b3 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -146,8 +146,8 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, resultX[1] = std::max(resultX[1], asymptote[1]); if (resultX[0] >= resultX[1]) { /* Fallback to default range. */ - resultX[0] = - k_defaultHalfRange; - resultX[1] = k_defaultHalfRange; + resultX[0] = NAN; + resultX[1] = NAN; return false; } else { resultX[0] = std::min(resultX[0], explosion[0]); @@ -195,8 +195,9 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float * order of magnitude and is used to cut the Y range. */ if (pop == 0) { - *yMin = 0; - *yMax = 0; + *yMin = NAN; + *yMax = NAN; + return; } else { float bound = std::exp(sum / pop + 1.f); sampleYMin = std::max(sampleYMin, - bound); @@ -286,9 +287,10 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f xMagnitude *= 10.f; } if (bestGrade == FLT_MAX) { - *xMin = -k_defaultHalfRange; - *xMax = k_defaultHalfRange; - RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary); + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; return; } @@ -309,6 +311,10 @@ void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float t } t += tStep; } + if (*fMin > *fMax) { + *fMin = NAN; + *fMax = NAN; + } } void Zoom::RangeFromSingleValue(float value, float * boundMin, float * boundMax) { From 3ba4f974f70c6311cdd46f9fc8f5c5f87c2275da Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 14:22:34 +0200 Subject: [PATCH 315/560] [interactive_curve_view_range] Move status update The methods setDefault and normalize now update the zoomAuto and zoomNormalize status. Change-Id: I0400c22816c17d38fd1b3dee5c8a2f1ccfa79340 --- apps/shared/interactive_curve_view_controller.cpp | 6 +----- apps/shared/interactive_curve_view_range.cpp | 15 +++++++-------- apps/shared/interactive_curve_view_range.h | 1 - apps/shared/range_parameter_controller.cpp | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index e97a3d9dc32..9dbad9ac88b 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -151,7 +151,6 @@ void InteractiveCurveViewController::viewWillAppear() { if (m_interactiveRange->zoomAuto()) { m_interactiveRange->setDefault(); - m_interactiveRange->checkForNormalizedRange(); } /* Warning: init cursor parameter before reloading banner view. Indeed, @@ -298,8 +297,6 @@ bool InteractiveCurveViewController::autoButtonAction() { m_interactiveRange->setZoomAuto(false); } else { m_interactiveRange->setDefault(); - m_interactiveRange->setZoomAuto(true); - m_interactiveRange->checkForNormalizedRange(); initCursorParameters(); setCurveViewAsMainView(); } @@ -310,9 +307,8 @@ bool InteractiveCurveViewController::normalizeButtonAction() { if (m_interactiveRange->zoomNormalize()) { m_interactiveRange->setZoomNormalize(false); } else { - m_interactiveRange->normalize(); m_interactiveRange->setZoomAuto(false); - m_interactiveRange->setZoomNormalize(true); + m_interactiveRange->normalize(); setCurveViewAsMainView(); } return m_interactiveRange->zoomNormalize(); diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 20eb45544d1..ac3ce0b8f19 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -102,6 +102,9 @@ void InteractiveCurveViewRange::normalize() { MemoizedCurveViewRange::protectedSetXMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); + + assert(isOrthonormal()); + setZoomNormalize(true); } void InteractiveCurveViewRange::setDefault() { @@ -126,12 +129,12 @@ void InteractiveCurveViewRange::setDefault() { 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); - if (!(m_delegate->defaultRangeIsNormalized() || revertToNormalized)) { - return; + if (m_delegate->defaultRangeIsNormalized() || revertToNormalized) { + // Normalize the axes, so that a polar circle is displayed as a circle + normalize(); } - // Normalize the axes, so that a polar circle is displayed as a circle - normalize(); + setZoomAuto(true); } void InteractiveCurveViewRange::setNullRange() { @@ -201,10 +204,6 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } } -void InteractiveCurveViewRange::checkForNormalizedRange() { - setZoomNormalize(isOrthonormal()); -} - bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { float ratio = (yMax() - yMin()) / (xMax() - xMin()); float ratioDifference = std::fabs(std::log(ratio / NormalYXRatio())); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 4ea7b9c586b..ce23d261b0f 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -47,7 +47,6 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { void setNullRange(); void centerAxisAround(Axis axis, float position); void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); - void checkForNormalizedRange(); protected: constexpr static float k_upperMaxFloat = 1E+8f; diff --git a/apps/shared/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index 3d8b6a45810..2e2712ed2cb 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -86,7 +86,7 @@ int RangeParameterController::reusableParameterCellCount(int type) { void RangeParameterController::buttonAction() { *m_interactiveRange = m_tempInteractiveRange; m_interactiveRange->setZoomAuto(false); - m_interactiveRange->checkForNormalizedRange(); + m_interactiveRange->setZoomNormalize(m_interactiveRange->isOrthonormal()); StackViewController * stack = stackController(); stack->pop(); From 71e7070657fa7d94295a23e523a836c0ef4ea20f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 15:06:01 +0200 Subject: [PATCH 316/560] [function_graph_controller] Remove yRangeForCursorFirstMove This method, that prevented the graph from panning when moving the cursor for the first time, caused more problems than it solved. For instance, the graph for 1/(1-x) was not symmetrical because of it. Change-Id: Ibb22e38ec0ace6b219c0e42dda481c98b5f717f0 --- apps/shared/function_graph_controller.cpp | 35 ----------------------- 1 file changed, 35 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 73ed5b298de..c634ed493e9 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -165,44 +165,9 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang range->setXMax(xMax); range->setYMin(yMin); range->setYMax(yMax); - yRangeForCursorFirstMove(range); } else { range->setNullRange(); } } -void FunctionGraphController::yRangeForCursorFirstMove(InteractiveCurveViewRange * range) const { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - assert(functionStore()->numberOfActiveFunctions() > 0); - int functionsCount = functionStore()->numberOfActiveFunctions(); - - float cursorStep = range->xGridUnit() / k_numberOfCursorStepsInGradUnit; - float y[2]; // Left and Right - - bool normalized = range->isOrthonormal(); - - constexpr float maximalExpansion = 10.f; - float yRange = range->yMax() - range->yMin(); - - for (int i = 0; i < functionsCount; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - for (int i = 0; i < 2; i++) { - float step = cursorStep * (i == 0 ? -1 : 1); - y[i] = f->evaluateXYAtParameter(range->xCenter() + step, context).x2(); - if (std::isfinite(y[i])) { - if (y[i] < range->yMin() && (range->yMax() - y[i]) < maximalExpansion * yRange) { - range->setYMin(y[i]); - } - if (y[i] > range->yMax() && (y[i] - range->yMin()) < maximalExpansion * yRange) { - range->setYMax(y[i]); - } - } - } - } - - if (normalized) { - range->normalize(); - } -} - } From b10be2c60cb4f2ebfb234fa79245db223cb2254e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 15:41:34 +0200 Subject: [PATCH 317/560] [poincare/zoom] Method SanitizeRange Create a method to clean up an ill-formed range, ie a range whose bounds are not finite, or where max <= min. Change-Id: If4525e65f95385cfa970be72bbcc21ad84286bfa --- poincare/include/poincare/zoom.h | 1 + poincare/src/zoom.cpp | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 079f9a38ee9..132071d2094 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -24,6 +24,7 @@ class Zoom { static void FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float tStep, float * fMin, float * fMax, Context * context, const void * auxiliary); static void CombineRanges(int length, const float * mins, const float * maxs, float * minRes, float * maxRes); + 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.*/ diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index d03cb5ff0b3..e18c0ebfbe3 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -237,6 +237,40 @@ void Zoom::CombineRanges(int length, const float * mins, const float * maxs, flo } } +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); From c4cd3ecffaaff9f04a33b00e14952562222b6c74 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 16:04:25 +0200 Subject: [PATCH 318/560] [poincare/zoom] Factor range sanitation Checking wether the range that has been computed is suitable is now done in Poincare::Zoom by SanitizeRange. Change-Id: Ib7ff73a3beae29996b1a773744021ad85c6ba946 --- apps/shared/function_graph_controller.cpp | 14 ++++++-------- apps/shared/interactive_curve_view_range.cpp | 8 -------- apps/shared/interactive_curve_view_range.h | 1 - poincare/include/poincare/zoom.h | 3 --- poincare/src/zoom.cpp | 16 ---------------- 5 files changed, 6 insertions(+), 36 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index c634ed493e9..a87f128fb11 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -160,14 +160,12 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang float xMin, xMax, yMin, yMax; Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax); Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax); - if (std::isfinite(xMin) && std::isfinite(xMax) && std::isfinite(yMin) && std::isfinite(yMax) && xMax > xMin && yMax > yMin) { - range->setXMin(xMin); - range->setXMax(xMax); - range->setYMin(yMin); - range->setYMax(yMax); - } else { - range->setNullRange(); - } + Poincare::Zoom::SanitizeRange(&xMin, &xMax, &yMin, &yMax, range->NormalYXRatio()); + + range->setXMin(xMin); + range->setXMax(xMax); + range->setYMin(yMin); + range->setYMax(yMax); } } diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index ac3ce0b8f19..7a3d70d9775 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -137,14 +137,6 @@ void InteractiveCurveViewRange::setDefault() { setZoomAuto(true); } -void InteractiveCurveViewRange::setNullRange() { - m_xRange.setMin(- Range1D::k_default); - setXMax(Range1D::k_default); - m_yRange.setMin(0); - m_yRange.setMax(0); - normalize(); -} - void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { if (std::isnan(position)) { return; diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index ce23d261b0f..0014d3dc183 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -44,7 +44,6 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { void panWithVector(float x, float y); virtual void normalize(); virtual void setDefault(); - void setNullRange(); void centerAxisAround(Axis axis, float position); void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation, float pixelWidth); diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 132071d2094..bd3fb9bcaaa 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -30,9 +30,6 @@ class Zoom { * 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); - /* Compute a default range so that boundMin < value < boundMax */ - static void RangeFromSingleValue(float value, float * boundMin, float * boundMax); - private: static constexpr int k_peakNumberOfPointsOfInterest = 3; static constexpr int k_sampleSize = Ion::Display::Width / 4; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index e18c0ebfbe3..cb17dc50927 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -205,9 +205,6 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float *yMin = sampleYMin; *yMax = sampleYMax; } - if (*yMin == *yMax) { - RangeFromSingleValue(*yMin, yMin, yMax); - } /* 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. */ @@ -232,9 +229,6 @@ void Zoom::CombineRanges(int length, const float * mins, const float * maxs, flo if (max > *maxRes) { *maxRes = max; } - if (*minRes == *maxRes) { - RangeFromSingleValue(*minRes, minRes, maxRes); - } } void Zoom::SanitizeRange(float * xMin, float * xMax, float * yMin, float * yMax, float normalRatio) { @@ -351,16 +345,6 @@ void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float t } } -void Zoom::RangeFromSingleValue(float value, float * boundMin, float * boundMax) { - constexpr float margin = 0.2f; - float delta = margin * std::fabs(value); - if (delta < k_minimalRangeLength) { - delta = 1.f; - } - *boundMin = value - delta; - *boundMax = value + delta; -} - 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; From 6f8d80f8ae9d637882a30c40231faef8b67b99ae Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 16:32:59 +0200 Subject: [PATCH 319/560] [regression/store] Sanitize default range Add range sanitation and checks for orthonormal ranges in setDefault. Change-Id: I733a8808e944a477573a76b52fef479853b8ad6d --- apps/regression/store.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 0dc813ea7e5..33474812d51 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -139,27 +139,41 @@ int Store::nextDot(int series, int direction, int dot) { /* Window */ void Store::setDefault() { - float min, max; + float xMin, xMax, yMin, yMax; float mins[k_numberOfSeries], maxs[k_numberOfSeries]; for (int series = 0; series < k_numberOfSeries; series++) { bool empty = seriesIsEmpty(series); mins[series] = empty ? NAN : minValueOfColumn(series, 0); maxs[series] = empty ? NAN : maxValueOfColumn(series, 0); } - Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &min, &max); - float range = max - min; - setXMin(min - k_displayHorizontalMarginRatio * range); - setXMax(max + 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, &min, &max); - range = max - min; - setYMin(m_delegate->addMargin(min, range, true, true)); - setYMax(m_delegate->addMargin(max, range, true, false)); + 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 = isOrthonormal(k_orthonormalTolerance); + + float range = xMax - xMin; + setXMin(xMin - k_displayHorizontalMarginRatio * range); + setXMax(xMax + k_displayHorizontalMarginRatio * range); + + range = yMax - yMin; + setYMin(m_delegate->addMargin(yMin, range, true, true)); + setYMax(m_delegate->addMargin(yMax, range, true, false)); + + if (revertToOrthonormal) { + normalize(); + } } /* Series */ From 91e1154bb82d16e1ec867a97520123f4f3fcd076 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 16:43:28 +0200 Subject: [PATCH 320/560] [sequence/curve_view_range] Fix normalize Change-Id: I1e4aa49dd84f12fe4e0f2ab59912fc68fe00b2fa --- apps/sequence/graph/curve_view_range.cpp | 30 ++++-------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index c08a82e1265..fb9d861f978 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -16,33 +16,13 @@ CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) : } void CurveViewRange::normalize() { - float xMean = xCenter(); - float yMean = yCenter(); + Shared::InteractiveCurveViewRange::normalize(); - const float unit = std::max(xGridUnit(), yGridUnit()); - - // Compute the X - const float newXHalfRange = NormalizedXHalfRange(unit); - float newXMin = xMean - newXHalfRange; - float newXMax = xMean + newXHalfRange; + /* 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*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 - 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); - } + float xRange = xMax() - xMin(); + m_xRange.setMin(interestingXMin - k_displayLeftMarginRatio * xRange); + setXMax(xMin() + xRange); } } From c647bfe566b635e71867d0e9a4ab39afa4920518 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 16:53:54 +0200 Subject: [PATCH 321/560] [poincare/zoom] Clean up and comment API Change-Id: I58347d7188551471817fb334bcb54d5c5b398f72 --- poincare/include/poincare/zoom.h | 12 +++- poincare/src/zoom.cpp | 114 +++++++++++++++---------------- 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index bd3fb9bcaaa..18995fe2769 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -16,14 +16,22 @@ class Zoom { typedef float (*ValueAtAbscissa)(float abscissa, Context * context, const void * auxiliary); - /* Return false if the X range was given a default value because there were - * no points of interest. */ + /* 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 diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index cb17dc50927..98ec28c67e3 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -215,6 +215,63 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } } +void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { + constexpr float units[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; + constexpr float rangeMagnitudeWeight = 0.2f; + constexpr float maxMagnitudeDifference = 2.f; + + float bestGrade = FLT_MAX; + float xMagnitude = k_minimalDistance; + float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; + while (xMagnitude < k_maximalDistance) { + for(const float unit : units) { + const float xRange = unit * xMagnitude; + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary); + float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); + float grade = std::fabs(std::log(currentRatio / yxRatio)); + /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ + grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; + if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { + bestGrade = grade; + *xMin = -xRange; + *xMax = xRange; + *yMin = yMinRange; + *yMax = yMaxRange; + } + } + xMagnitude *= 10.f; + } + if (bestGrade == FLT_MAX) { + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; + return; + } + + SetToRatio(yxRatio, xMin, xMax, yMin, yMax); +} + +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 (value < *fMin) { + *fMin = value; + } + if (value > *fMax) { + *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); @@ -288,63 +345,6 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, f *tMin = center - newRange / 2.f; } -void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { - constexpr float units[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; - constexpr float rangeMagnitudeWeight = 0.2f; - constexpr float maxMagnitudeDifference = 2.f; - - float bestGrade = FLT_MAX; - float xMagnitude = k_minimalDistance; - float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; - while (xMagnitude < k_maximalDistance) { - for(const float unit : units) { - const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary); - float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); - float grade = std::fabs(std::log(currentRatio / yxRatio)); - /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ - grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; - if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { - bestGrade = grade; - *xMin = -xRange; - *xMax = xRange; - *yMin = yMinRange; - *yMax = yMaxRange; - } - } - xMagnitude *= 10.f; - } - if (bestGrade == FLT_MAX) { - *xMin = NAN; - *xMax = NAN; - *yMin = NAN; - *yMax = NAN; - return; - } - - SetToRatio(yxRatio, xMin, xMax, yMin, yMax); -} - -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 (value < *fMin) { - *fMin = value; - } - if (value > *fMax) { - *fMax = value; - } - t += tStep; - } - if (*fMin > *fMax) { - *fMin = NAN; - *fMax = NAN; - } -} - 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; From 9c320185dd578219e9c0209dc067d05423190f74 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 16:59:41 +0200 Subject: [PATCH 322/560] [poincare/zoom] Return value for boring curve Change-Id: Iad983da0aa3066f5be0c75c5b8e38e62a907d5a1 --- poincare/src/zoom.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 98ec28c67e3..25586f25707 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -146,8 +146,10 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, resultX[1] = std::max(resultX[1], asymptote[1]); if (resultX[0] >= resultX[1]) { /* Fallback to default range. */ - resultX[0] = NAN; - resultX[1] = NAN; + *xMin = NAN; + *xMax = NAN; + *yMin = NAN; + *yMax = NAN; return false; } else { resultX[0] = std::min(resultX[0], explosion[0]); From 31e694f41a6d62fb80b65902597d70094e4c24b5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 15 Oct 2020 12:07:54 +0200 Subject: [PATCH 323/560] [poincare/zoom] Add tests Change-Id: Ia7229d790ef1d2039071e35accb102804c94cb01 --- poincare/Makefile | 1 + poincare/test/zoom.cpp | 248 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 poincare/test/zoom.cpp diff --git a/poincare/Makefile b/poincare/Makefile index 61ee32fe3bb..0e368f2f558 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -191,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/test/zoom.cpp b/poincare/test/zoom.cpp new file mode 100644 index 00000000000..3f74ff600ec --- /dev/null +++ b/poincare/test/zoom.cpp @@ -0,0 +1,248 @@ +#include +#include "helper.h" +#include +#include + +using namespace Poincare; + +struct 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; +}; + +constexpr float NormalRatio = 306.f / 576.f; + +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 bracket(float a, float b, float d) { + assert(std::isfinite(a) && std::isfinite(b) && std::isfinite(d)); + return a - d <= b && b <= a + d; +} + +bool range_matches(float min, float max, float targetMin, float targetMax, float tolerance) { + assert(std::isfinite(targetMin) == std::isfinite(targetMax)); + + const float rangeTolerance = tolerance * (targetMax - targetMin); + + if (std::isfinite(targetMin) && (targetMin <= targetMax)) { + return std::isfinite(min) && std::isfinite(max) + && bracket(min, targetMin, rangeTolerance) && bracket(max, targetMax, rangeTolerance); + } else { + return (!std::isfinite(min) && !std::isfinite(max)) || (max < min); + } +} + +bool window_is_similar(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool addXMargin = false, float tolerance = 0.1f) { + assert(std::isfinite(targetXMin) == std::isfinite(targetXMax) && std::isfinite(targetYMin) == std::isfinite(targetYMax)); + + /* The target window given in the test should reflect the points of + * interest we try to display, and should not take into account the + * breathing room added by the algorithm. We handle it here. */ + constexpr float margin = 0.3f; // Zoom::k_breathingRoom; + float xDelta = addXMargin ? (xMax - xMin) * margin : 0.f; + + return range_matches(xMin, xMax, targetXMin - xDelta, targetXMax + xDelta, tolerance) + && range_matches(yMin, yMax, targetYMin, targetYMax, tolerance); +} + +void assert_interesting_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin = FLT_MAX, float targetYMax = -FLT_MAX, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + float xMin, xMax, yMin, yMax; + ParametersPack aux(e, symbol, angleUnit); + Zoom::InterestingRangesForDisplay(&evaluate_expression, &xMin, &xMax, &yMin, &yMax, -INFINITY, INFINITY, &globalContext, &aux); + + bool test = window_is_similar(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, true); + quiz_assert_print_if_failure(test, definition); +} + +void assert_has_no_interesting_range(const char * definition, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + assert_interesting_range_is(definition, NAN, NAN, NAN, NAN, angleUnit, symbol); +} + +void breaker() {} + +QUIZ_CASE(poincare_zoom_interesting_ranges) { + assert_has_no_interesting_range(Undefined::Name()); + assert_has_no_interesting_range("0"); + assert_has_no_interesting_range("1"); + assert_has_no_interesting_range("-100"); + assert_has_no_interesting_range("x"); + assert_has_no_interesting_range("x^2"); + assert_has_no_interesting_range("-(x^3)"); + assert_has_no_interesting_range("10×x^4"); + assert_has_no_interesting_range("ℯ^(-x)"); + + assert_interesting_range_is("x-21", 20.5, 22.5); + assert_interesting_range_is("-11x+100", 8.7, 9.5); + assert_interesting_range_is("x^2-1", -5, 5, -1, -1); + assert_interesting_range_is("(x+10)(x-10)", -10.5, 10.5, -100, -100); + assert_interesting_range_is("x(x-1)(x-2)(x-3)(x-4)(x-5)", 0, 5, -16, 5); + assert_interesting_range_is("1/x", -2.5, 2.5); + assert_interesting_range_is("1/(x-10)", 8, 14); + assert_interesting_range_is("√(x)", 0, 7); + assert_interesting_range_is("ln(x)", 0, 5); + assert_interesting_range_is("sin(x)", -9, 9, -1, 1); + assert_interesting_range_is("sin(x)", -480, 480, -1, 1, Degree); + assert_interesting_range_is("cos(x)", -10, 10, -1, 1); + assert_interesting_range_is("cos(x)", -580, 580, -1, 1, Degree); + assert_interesting_range_is("tan(x)", -9, 9); + assert_interesting_range_is("tan(x)", -500, 500, FLT_MAX, -FLT_MAX, Degree); + assert_interesting_range_is("asin(x)", -1, 1); + assert_interesting_range_is("acos(x)", -1, 1, FLT_MAX, -FLT_MAX, Degree); + assert_interesting_range_is("atan(x)", -2, 2); + assert_interesting_range_is("sin(x)/x", -12, 12, -0.2, 1); + assert_interesting_range_is("x×sin(x)", -9, 9, -5, 8); +} + +void assert_refined_range_is(const char * definition, float targetXMin, float targetXMax, 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; + ParametersPack aux(e, symbol, angleUnit); + Zoom::RefinedYRangeForDisplay(&evaluate_expression, targetXMin, targetXMax, &yMin, &yMax, &globalContext, &aux); + + bool test = window_is_similar(targetXMin, targetXMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax); + quiz_assert_print_if_failure(test, definition); +} + +QUIZ_CASE(poincare_zoom_refined_ranges) { + assert_refined_range_is(Undefined::Name(), NAN, NAN, NAN, NAN); + assert_refined_range_is("0", NAN, NAN, NAN, NAN); + + assert_refined_range_is("1", -10, 10, 1, 1); + assert_refined_range_is("x", -10, 10, -10, 10); + assert_refined_range_is("x^2", -10, 10, 0, 37); + assert_refined_range_is("1/x", -10, 10, -0.73, 0.73); + assert_refined_range_is("(x-100)(x+100)", -120, 120, -10000, 4400); + assert_refined_range_is("sin(x)", -300, 300, -1, 1, Degree); + assert_refined_range_is("ℯ^x", -10, 10, 0, 3); + assert_refined_range_is("atan(x)", -100, 100, -90, 90, Degree); +} + +bool can_find_normal_range(const char * definition, Preferences::AngleUnit angleUnit, const char * symbol) { + Shared::GlobalContext globalContext; + Expression e = parse_expression(definition, &globalContext, false); + float xMin, xMax, yMin, yMax; + ParametersPack aux(e, symbol, angleUnit); + Zoom::RangeWithRatioForDisplay(&evaluate_expression, NormalRatio, &xMin, &xMax, &yMin, &yMax, &globalContext, &aux); + return std::isfinite(xMin) && std::isfinite(xMax) && std::isfinite(yMin) && std::isfinite(yMax); +} + +void assert_range_is_normalized(const char * definition, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + quiz_assert_print_if_failure(can_find_normal_range(definition, angleUnit, symbol), definition); +} + +QUIZ_CASE(poincare_zoom_ratio_ranges) { + assert_range_is_normalized("x"); + assert_range_is_normalized("x^2"); + assert_range_is_normalized("-(x^3)"); + assert_range_is_normalized("ℯ^x"); + assert_range_is_normalized("ℯ^(-x)"); +} + +void assert_full_range_is(const char * definition, float targetXMin, float targetXMax, 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 = (targetXMax - targetXMin) / stepDivisor; + ParametersPack aux(e, symbol, angleUnit); + Zoom::FullRange(&evaluate_expression, targetXMin, targetXMax, step, &yMin, &yMax, &globalContext, &aux); + quiz_assert_print_if_failure(window_is_similar(targetXMin, targetXMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); +} + +QUIZ_CASE(poincare_zoom_full_ranges) { + 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, 22000); + assert_full_range_is("sin(x)", -3600, 3600, -1, 1, Degree); + assert_full_range_is("acos(x)", -10, 10, 0, 3.14); +} + +void assert_ranges_combine(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(float xMin, float xMax, float yMin, float yMax, float yxRatio) { + { + float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; + Zoom::SetToRatio(yxRatio, &tempXMin, &tempXMax, &tempYMin, &tempYMax, false); + quiz_assert((tempYMax - tempYMin) / (tempXMax - tempXMin) == yxRatio); + quiz_assert((tempYMax - tempYMin) > (yMax - yMin) || (tempXMax - tempXMin) > (xMax - xMin)); + } + { + float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; + Zoom::SetToRatio(yxRatio, &tempXMin, &tempXMax, &tempYMin, &tempYMax, true); + quiz_assert((tempYMax - tempYMin) / (tempXMax - tempXMin) == yxRatio); + quiz_assert((tempYMax - tempYMin) < (yMax - yMin) || (tempXMax - tempXMin) < (xMax - xMin)); + } +} + +QUIZ_CASE(poincare_zoom_utility) { + // Ranges combinations + { + constexpr int length = 1; + float mins[length] = {-10}; + float maxs[length] = {10}; + assert_ranges_combine(length, mins, maxs, -10, 10); + } + { + constexpr int length = 2; + float mins[length] = {-1, -2}; + float maxs[length] = {1, 2}; + assert_ranges_combine(length, mins, maxs, -2, 2); + } + { + constexpr int length = 2; + float mins[length] = {-1, 9}; + float maxs[length] = {1, 11}; + assert_ranges_combine(length, mins, maxs, -1, 11); + } + { + constexpr int length = 3; + float mins[length] = {-3, -2, -1}; + float maxs[length] = {1, 2, 3}; + assert_ranges_combine(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, 94.6875, 105.3125); + assert_sanitized_range_is(3, -3, -10, 10, -18.8235302, 18.8235302, -10, 10); + assert_sanitized_range_is(3, -3, 2, 2, -10, 10, -3.3125, 7.3125); + assert_sanitized_range_is(NAN, NAN, NAN, NAN, -10, 10, -5.3125, 5.3125); + + // Ratio + assert_ratio_is_set(-10, 10, -10, 10, 0.1); + assert_ratio_is_set(-10, 10, -10, 10, 0.5); + assert_ratio_is_set(-10, 10, -10, 10, NormalRatio); + assert_ratio_is_set(-10, 10, -10, 10, 3); + + +} From f491be2db5bade224f737a21c1cc9edb08c221db Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 15:06:49 +0200 Subject: [PATCH 324/560] [poincare/zoom] Accelerate normal range search Change-Id: I4a7f2e024ca82a3996ad3e395c8f62ca4170ec9e --- poincare/include/poincare/zoom.h | 2 +- poincare/src/zoom.cpp | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 18995fe2769..591e64b20dd 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -22,7 +22,7 @@ class Zoom { 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); + static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, int sampleSize = k_sampleSize); /* 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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 25586f25707..2d238c4983d 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -167,19 +167,19 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, return true; } -void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { +void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, int sampleSize) { /* 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 sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; - const float step = (xMax - xMin) / (k_sampleSize - 1); + const float step = (xMax - xMin) / (sampleSize - 1); float x, y; float sum = 0.f; int pop = 0; - for (int i = 1; i < k_sampleSize; i++) { + for (int i = 1; i < sampleSize; i++) { x = xMin + i * step; y = evaluation(x, context, auxiliary); if (!std::isfinite(y)) { @@ -222,13 +222,18 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f constexpr float rangeMagnitudeWeight = 0.2f; constexpr float maxMagnitudeDifference = 2.f; + /* RefinedYRange for display, by default, evaluates the function 80 times, + * and we call it 21 times. As we only need a rough estimate of the range to + * compare it to the others, we save some time by only sampling the function + * 20 times. */ + constexpr int sampleSize = k_sampleSize / 4; float bestGrade = FLT_MAX; float xMagnitude = k_minimalDistance; float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; while (xMagnitude < k_maximalDistance) { for(const float unit : units) { const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary); + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); float grade = std::fabs(std::log(currentRatio / yxRatio)); /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ From 0e4de43b8fef69c31a4ec5d1434a24671b9e6156 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 16:07:01 +0200 Subject: [PATCH 325/560] [shared] Rename ZoomParameterController Change-Id: I64a6500b66db4455217920a1e73efd5848216411 --- apps/shared/Makefile | 2 +- ...on_zoom_and_pan_curve_view_controller.cpp} | 36 +++++++++---------- ...tion_zoom_and_pan_curve_view_controller.h} | 8 ++--- .../interactive_curve_view_controller.h | 4 +-- 4 files changed, 25 insertions(+), 25 deletions(-) rename apps/shared/{zoom_parameter_controller.cpp => function_zoom_and_pan_curve_view_controller.cpp} (69%) rename apps/shared/{zoom_parameter_controller.h => function_zoom_and_pan_curve_view_controller.h} (82%) diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 8fb5e859c56..bee5a07d620 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -43,6 +43,7 @@ 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 \ @@ -89,7 +90,6 @@ 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) diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp similarity index 69% rename from apps/shared/zoom_parameter_controller.cpp rename to apps/shared/function_zoom_and_pan_curve_view_controller.cpp index 90ed8ddd045..cd1edbf60da 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp @@ -1,21 +1,21 @@ -#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) { } -const char * ZoomParameterController::title() { +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,16 +23,16 @@ 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) { +void FunctionZoomAndPanCurveViewController::adaptCurveRange(bool viewWillAppear) { float currentYMin = m_interactiveRange->yMin(); float currentRange = m_interactiveRange->yMax() - m_interactiveRange->yMin(); float newYMin = 0; @@ -47,16 +47,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 +67,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 +95,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::GrayBright); } -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 +111,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 82% rename from apps/shared/zoom_parameter_controller.h rename to apps/shared/function_zoom_and_pan_curve_view_controller.h index 293f166cf02..5ea5767ffa4 100644 --- a/apps/shared/zoom_parameter_controller.h +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.h @@ -1,14 +1,14 @@ -#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; diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 8a7c43cf3eb..585ba08ee44 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -5,7 +5,7 @@ #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 namespace Shared { @@ -77,7 +77,7 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll uint32_t * m_rangeVersion; RangeParameterController m_rangeParameterController; - ZoomParameterController m_zoomParameterController; + FunctionZoomAndPanCurveViewController m_zoomParameterController; InteractiveCurveViewRange * m_interactiveRange; Button m_autoButton; Button m_normalizeButton; From 3a87e47de5ec7eceba99c67027cebf2a82cca5e1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 16:10:51 +0200 Subject: [PATCH 326/560] [interactive_curve_view_controller] Zoom button Renamed zoom button Change-Id: I476c0991d5ab88c6642b793fbe10debbcca4e014 --- apps/shared/interactive_curve_view_controller.cpp | 4 ++-- apps/shared/interactive_curve_view_controller.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 9dbad9ac88b..e77693b4c31 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -25,7 +25,7 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren graphController->normalizeButtonAction(); return true; }, this), KDFont::SmallFont), - m_zoomButton(this, I18n::Message::Navigate, Invocation([](void * context, void * sender) { + m_navigationButton(this, I18n::Message::Navigate, Invocation([](void * context, void * sender) { InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; graphController->navigationButtonAction(); return true; @@ -138,7 +138,7 @@ int InteractiveCurveViewController::numberOfButtons(ButtonRowController::Positio } Button * InteractiveCurveViewController::buttonAtIndex(int index, ButtonRowController::Position position) const { - const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_zoomButton, &m_rangeButton}; + const Button * buttons[] = {&m_autoButton, &m_normalizeButton, &m_navigationButton, &m_rangeButton}; return (Button *)buttons[index]; } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 585ba08ee44..455fb9284b7 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -81,7 +81,7 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll InteractiveCurveViewRange * m_interactiveRange; Button m_autoButton; Button m_normalizeButton; - Button m_zoomButton; + Button m_navigationButton; Button m_rangeButton; }; From 29f47124b12c0afecdf049731313d5c41ab98842 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 16:21:12 +0200 Subject: [PATCH 327/560] [poincare/zoom] Use std::min & max Change-Id: If3983c16d3bfa0de31aefe8a3f5c650e1858f822 --- poincare/src/zoom.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 2d238c4983d..a649ed3688f 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -265,11 +265,9 @@ void Zoom::FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float t *fMax = -FLT_MAX; while (t <= tMax) { float value = evaluation(t, context, auxiliary); - if (value < *fMin) { - *fMin = value; - } - if (value > *fMax) { - *fMax = value; + if (std::isfinite(value)) { + *fMin = std::min(*fMin, value); + *fMax = std::max(*fMax, value); } t += tStep; } @@ -287,11 +285,11 @@ void Zoom::CombineRanges(int length, const float * mins, const float * maxs, flo FullRange(evaluation, 0, length - 1, 1, minRes, maxRes, nullptr, mins); float min, max; FullRange(evaluation, 0, length - 1, 1, &min, &max, nullptr, maxs); - if (min < *minRes) { - *minRes = min; + if (std::isfinite(min)) { + *minRes = std::min(min, *minRes); } - if (max > *maxRes) { - *maxRes = max; + if (std::isfinite(max)) { + *maxRes = std::max(max, *maxRes); } } From 194cd0491cb5959c27c3240b528732f863c2620e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 16:21:36 +0200 Subject: [PATCH 328/560] [sequence] Format comment Change-Id: Iaff562cca9af3eb903d6cc8a529c730184235e4b --- apps/sequence/graph/curve_view_range.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index fb9d861f978..c82e766c8ff 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -18,7 +18,9 @@ CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) : void CurveViewRange::normalize() { Shared::InteractiveCurveViewRange::normalize(); - /* 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. */ + /* 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(); float xRange = xMax() - xMin(); m_xRange.setMin(interestingXMin - k_displayLeftMarginRatio * xRange); From a76e54642c3a201997ce322f0a64807aa6d5e153 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 16 Oct 2020 16:31:57 +0200 Subject: [PATCH 329/560] [poincare/zoom] Change struct to class Change-Id: I06d1850074b266a694d74e4db4c754ff156c8098 --- poincare/test/zoom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 3f74ff600ec..5edc65b5a8a 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -5,7 +5,7 @@ using namespace Poincare; -struct ParametersPack { +class ParametersPack { public: ParametersPack(Expression expression, const char * symbol, Preferences::AngleUnit angleUnit) : m_expression(expression), From 2f97dab6d1fd20b50e9f8d4dff06d022ac44e4fb Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 8 Oct 2020 16:54:32 +0200 Subject: [PATCH 330/560] [apps/shared] Add toggle buttons in Graph view Change-Id: I7548d11fb114b2605ce34d3bda0776277b79ff9d --- apps/regression/graph_view.cpp | 2 +- apps/shared/curve_view.cpp | 8 +-- apps/shared/curve_view.h | 4 +- apps/shared/dots.cpp | 4 +- apps/shared/dots.h | 6 +-- .../interactive_curve_view_controller.cpp | 9 ++-- .../interactive_curve_view_controller.h | 6 ++- apps/shared/interactive_curve_view_range.cpp | 31 ++++++++++-- apps/shared/interactive_curve_view_range.h | 14 ++++-- .../interactive_curve_view_range_delegate.h | 1 + escher/Makefile | 3 ++ escher/include/escher/button.h | 2 +- escher/include/escher/button_state.h | 24 +++++++++ escher/include/escher/switch_view.h | 15 +++--- escher/include/escher/toggleable_dot_view.h | 16 ++++++ escher/include/escher/toggleable_view.h | 15 ++++++ escher/src/button_state.cpp | 35 +++++++++++++ escher/src/switch_view.cpp | 50 ++++++++----------- escher/src/toggleable_dot_view.cpp | 41 +++++++++++++++ escher/src/toggleable_view.cpp | 6 +++ 20 files changed, 226 insertions(+), 66 deletions(-) create mode 100644 escher/include/escher/button_state.h create mode 100644 escher/include/escher/toggleable_dot_view.h create mode 100644 escher/include/escher/toggleable_view.h create mode 100644 escher/src/button_state.cpp create mode 100644 escher/src/toggleable_dot_view.cpp create mode 100644 escher/src/toggleable_view.cpp diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index 37ee68097ef..bcd1bb8c9f3 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), KDColorWhite); } } diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 676c179fb28..851883d862b 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -458,14 +458,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; diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index cc882b8f792..faf17ea5720 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -75,11 +75,11 @@ class CurveView : public View { 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 diff --git a/apps/shared/dots.cpp b/apps/shared/dots.cpp index 0f178b44ee5..1dc85266070 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 07d65ff4431..c42be15dc89 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/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index e77693b4c31..391435d01e3 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -69,6 +69,11 @@ float InteractiveCurveViewController::addMargin(float y, float range, bool isVer return y + ratio * range; } +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); } @@ -304,9 +309,7 @@ bool InteractiveCurveViewController::autoButtonAction() { } bool InteractiveCurveViewController::normalizeButtonAction() { - if (m_interactiveRange->zoomNormalize()) { - m_interactiveRange->setZoomNormalize(false); - } else { + if (!m_interactiveRange->zoomNormalize()) { m_interactiveRange->setZoomAuto(false); m_interactiveRange->normalize(); setCurveViewAsMainView(); diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 455fb9284b7..91f8fa5e10f 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -7,6 +7,7 @@ #include "range_parameter_controller.h" #include "function_zoom_and_pan_curve_view_controller.h" #include +#include namespace Shared { @@ -66,6 +67,7 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll // InteractiveCurveViewRangeDelegate float addMargin(float x, float range, bool isVertical, bool isMin) override; + void updateZoomButtons() override; void setCurveViewAsMainView(); @@ -79,8 +81,8 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll RangeParameterController m_rangeParameterController; FunctionZoomAndPanCurveViewController m_zoomParameterController; InteractiveCurveViewRange * m_interactiveRange; - Button m_autoButton; - Button m_normalizeButton; + ButtonState m_autoButton; + ButtonState m_normalizeButton; Button m_navigationButton; Button m_rangeButton; }; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 7a3d70d9775..20793e95684 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -11,6 +11,13 @@ using namespace Poincare; namespace Shared { +void InteractiveCurveViewRange::setDelegate(InteractiveCurveViewRangeDelegate * delegate) { + m_delegate = delegate; + if (delegate) { + m_delegate->updateZoomButtons(); + } +} + uint32_t InteractiveCurveViewRange::rangeChecksum() { float data[] = {xMin(), xMax(), yMin(), yMax()}; size_t dataLengthInBytes = sizeof(data); @@ -18,6 +25,20 @@ uint32_t InteractiveCurveViewRange::rangeChecksum() { return Ion::crc32Word((uint32_t *)data, dataLengthInBytes/sizeof(uint32_t)); } +void InteractiveCurveViewRange::setZoomAuto(bool v) { + m_zoomAuto = v; + if (m_delegate) { + m_delegate->updateZoomButtons(); + } +} + +void InteractiveCurveViewRange::setZoomNormalize(bool v) { + m_zoomNormalize = v; + if (m_delegate) { + m_delegate->updateZoomButtons(); + } +} + void InteractiveCurveViewRange::setXMin(float xMin) { MemoizedCurveViewRange::protectedSetXMin(xMin, k_lowerMaxFloat, k_upperMaxFloat); } @@ -55,7 +76,7 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { float xMa = xMax(); float yMi = yMin(); float yMa = yMax(); - m_zoomAuto = false; + setZoomAuto(false); if (ratio*std::fabs(xMa-xMi) < Range1D::k_minFloat || ratio*std::fabs(yMa-yMi) < Range1D::k_minFloat) { return; } @@ -163,7 +184,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; if (x < xMin() + leftMargin) { - m_zoomAuto = false; + 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(); @@ -172,7 +193,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } const float rightMargin = rightMarginRatio * xRange; if (x > xMax() - rightMargin) { - m_zoomAuto = false; + 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); @@ -182,14 +203,14 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to const float yRange = yMax() - yMin(); const float bottomMargin = bottomMarginRatio * yRange; if (y < yMin() + bottomMargin) { - m_zoomAuto = 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_zoomAuto = false; + setZoomAuto(false); m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat); } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 0014d3dc183..9608f1bee2d 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -13,21 +13,25 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { public: InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) : MemoizedCurveViewRange(), - m_delegate(delegate), + m_delegate(nullptr), m_zoomAuto(true), m_zoomNormalize(false) - {} + { + if (delegate) { + setDelegate(delegate); + } + } static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } bool isOrthonormal(float tolerance = 2 * FLT_EPSILON) const; - void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; } + void setDelegate(InteractiveCurveViewRangeDelegate * delegate); uint32_t rangeChecksum() override; bool zoomAuto() const { return m_zoomAuto; } - void setZoomAuto(bool v) { m_zoomAuto = v; } + void setZoomAuto(bool v); bool zoomNormalize() const { return m_zoomNormalize; } - void setZoomNormalize(bool v) { m_zoomNormalize = v; } + void setZoomNormalize(bool v); // MemoizedCurveViewRange float xGridUnit() const override { return m_zoomNormalize ? yGridUnit() : MemoizedCurveViewRange::xGridUnit(); } diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index 0fcbea73566..6dfb2c9a0fd 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -14,6 +14,7 @@ class InteractiveCurveViewRangeDelegate { virtual bool defaultRangeIsNormalized() const { return false; } virtual void interestingRanges(InteractiveCurveViewRange * range) const { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; + virtual void updateZoomButtons() = 0; }; } diff --git a/escher/Makefile b/escher/Makefile index 421fbed1e12..bc76d893758 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -7,6 +7,7 @@ escher_src += $(addprefix escher/src/,\ bordered.cpp \ buffer_text_view.cpp \ button.cpp \ + button_state.cpp \ button_row_controller.cpp \ chevron_view.cpp \ clipboard.cpp \ @@ -81,6 +82,8 @@ escher_src += $(addprefix escher/src/,\ text_view.cpp \ tiled_view.cpp \ timer.cpp \ + toggleable_dot_view.cpp \ + toggleable_view.cpp \ toolbox.cpp \ transparent_view.cpp \ view.cpp \ diff --git a/escher/include/escher/button.h b/escher/include/escher/button.h index f773c5bfc07..aa6abbe8fe5 100644 --- a/escher/include/escher/button.h +++ b/escher/include/escher/button.h @@ -21,13 +21,13 @@ class Button : public HighlightCell, public Responder { KDSize minimalSizeForOptimalDisplay() const override; protected: MessageTextView m_messageTextView; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_verticalMargin = 5; constexpr static KDCoordinate k_horizontalMarginSmall = 10; constexpr static KDCoordinate k_horizontalMarginLarge = 20; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews(bool force = false) override; Invocation m_invocation; const KDFont * m_font; }; diff --git a/escher/include/escher/button_state.h b/escher/include/escher/button_state.h new file mode 100644 index 00000000000..34bb5b55f5d --- /dev/null +++ b/escher/include/escher/button_state.h @@ -0,0 +1,24 @@ +#ifndef ESCHER_BUTTON_STATE_H +#define ESCHER_BUTTON_STATE_H + +#include +#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/switch_view.h b/escher/include/escher/switch_view.h index 482697cc5b3..23fa375f4b3 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/toggleable_dot_view.h b/escher/include/escher/toggleable_dot_view.h new file mode 100644 index 00000000000..c6ec4d5c0c8 --- /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 00000000000..34a7ef9960b --- /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/src/button_state.cpp b/escher/src/button_state.cpp new file mode 100644 index 00000000000..e5c276ff47c --- /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() : KDColorWhite; + 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/switch_view.cpp b/escher/src/switch_view.cpp index e336d30cf15..b59be0c6ace 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::YellowDark : Palette::GrayDark, + reinterpret_cast(switchMask), + workingBuffer); - KDColor mainColor = m_state ? Palette::YellowDark : Palette::GrayDark; - 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, KDColorWhite, (const uint8_t *)onOffMask, switchWorkingBuffer); -} -KDSize SwitchView::minimalSizeForOptimalDisplay() const { - return KDSize(k_switchWidth, k_switchHeight); -} + KDCoordinate onOffX = width - (m_state ? k_onOffSize : k_switchWidth); + KDRect onOffFrame(onOffX, heightCenter - halfHeight, k_onOffSize, k_onOffSize); + ctx->blendRectWithMask( + onOffFrame, + KDColorWhite, + reinterpret_cast(onOffMask), + workingBuffer); +} \ No newline at end of file diff --git a/escher/src/toggleable_dot_view.cpp b/escher/src/toggleable_dot_view.cpp new file mode 100644 index 00000000000..3195ae1f45e --- /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 00000000000..2e266027733 --- /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 From 3f25a37cc8640c60321aaaf6d4424dcdcd965e83 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 8 Oct 2020 16:56:19 +0200 Subject: [PATCH 331/560] [i18n] Update Graph view parameters wording Change-Id: Icd65c4e19b78cd8751d6b1b583c112abb08c9237 --- apps/shared.en.i18n | 4 ++-- apps/shared.nl.i18n | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index bc9344b50ad..32f99936398 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -55,7 +55,7 @@ Move = " Move: " NameCannotStartWithNumber = "A name cannot start with a number" NameTaken = "This name has already been taken" NameTooLong = "This name is too long" -Navigate = "Navigate" +Navigate = "Pan" Next = "Next" NoDataToPlot = "No data to draw" NoFunctionToDelete = "No function to delete" @@ -64,7 +64,7 @@ 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." diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index d5b526ffa59..55eb53adaec 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -55,7 +55,7 @@ Move = " Verplaats: " NameCannotStartWithNumber = "Een naam kan niet beginnen met een nummer" NameTaken = "Deze naam is al in gebruik" NameTooLong = "Deze naam is te lang" -Navigate = "Bladeren" +Navigate = "Navigeren" Next = "Volgende" NoDataToPlot = "Geen gegevens om te plotten" NoFunctionToDelete = "Geen functie om te verwijderen" From bb8a28ade0f76259d3909d71b85096f0d8db59e5 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 8 Oct 2020 18:22:58 +0200 Subject: [PATCH 332/560] [poincare/zoom] Simplify plot ranges after adding margins Change-Id: If8904ca4e7d306376de785a125fe5fba168de718 --- apps/regression/store.cpp | 7 ++++-- apps/shared/interactive_curve_view_range.cpp | 24 ++++++++++++++++---- apps/shared/interactive_curve_view_range.h | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 33474812d51..a794ef3c6a6 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -139,6 +139,8 @@ int Store::nextDot(int series, int direction, int dot) { /* Window */ void Store::setDefault() { + setZoomNormalize(false); + float xMin, xMax, yMin, yMax; float mins[k_numberOfSeries], maxs[k_numberOfSeries]; for (int series = 0; series < k_numberOfSeries; series++) { @@ -168,12 +170,13 @@ void Store::setDefault() { setXMax(xMax + k_displayHorizontalMarginRatio * range); range = yMax - yMin; - setYMin(m_delegate->addMargin(yMin, range, true, true)); - setYMax(m_delegate->addMargin(yMax, range, true, false)); + 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 */ diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 20793e95684..77126f4b065 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,19 @@ void InteractiveCurveViewRange::setZoomNormalize(bool v) { } } +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); } @@ -141,14 +155,14 @@ void InteractiveCurveViewRange::setDefault() { m_delegate->interestingRanges(this); bool revertToNormalized = isOrthonormal(k_orthonormalTolerance); - // Add margins + // Add margins, then round limits. 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(roundLimit(m_delegate->addMargin(xMin(), xRange, false, true), xRange, true), 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(roundLimit(m_delegate->addMargin(xMax(), xRange, false, false), xRange, false), k_lowerMaxFloat, k_upperMaxFloat); + 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); if (m_delegate->defaultRangeIsNormalized() || revertToNormalized) { // Normalize the axes, so that a polar circle is displayed as a circle diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 9608f1bee2d..5e4607b2917 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -32,6 +32,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { 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(); } From 14e2f6247a290fdfee9ca424d04b565445491bd6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 16 Oct 2020 12:43:36 +0200 Subject: [PATCH 333/560] [apps/shared] Update translation size -> frequency Change-Id: I841bd22077cccacc7d2a4a541cca2e463f13925b --- apps/shared.en.i18n | 2 +- apps/statistics/base.en.i18n | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 32f99936398..19fbbbe4755 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -71,7 +71,7 @@ PoolMemoryFull2 = "Try again." Rename = "Rename" Sci = "sci" SortValues = "Sort by increasing values" -SortSizes = "Sort by increasing sizes" +SortSizes = "Sort by increasing frequencies" SquareSum = "Sum of squares" StandardDeviation = "Standard deviation" StoreExpressionNotAllowed = "'store' is not allowed" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 839ab4624a8..26a5a454f90 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -5,20 +5,20 @@ BoxTab = "Box" Values1 = "Values V1" Values2 = "Values V2" Values3 = "Values V3" -Sizes1 = "Sizes N1" -Sizes2 = "Sizes N2" -Sizes3 = "Sizes N3" +Sizes1 = "Frequencies N1" +Sizes2 = "Frequencies N2" +Sizes3 = "Frequencies N3" ImportList = "Import from a list" Interval = " Interval " -Size = " Size" -Frequency = "Frequency" +Size = " Frequency" +Frequency = "Relative" HistogramSet = "Histogram settings" RectangleWidth = "Bin width" BarStart = "X start" FirstQuartile = "First quartile" Median = "Median" ThirdQuartile = "Third quartile" -TotalSize = "Total size" +TotalSize = "Total frequency" Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" From 52adea0c1f80a01cf342a2209374ce1efe073580 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Tue, 13 Oct 2020 10:10:59 +0200 Subject: [PATCH 334/560] [NL] correct spelling mistake --- apps/variables.nl.i18n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/variables.nl.i18n b/apps/variables.nl.i18n index 3f80a107b3c..d951401cf8e 100644 --- a/apps/variables.nl.i18n +++ b/apps/variables.nl.i18n @@ -5,8 +5,8 @@ Sequences = "Rijen" EmptyExpressionBox0 = "Je hebt geen variabelen gedefinieerd." EmptyFunctionBox0 = "Je hebt geen functies gedefinieerd." EmptySequenceBox0 = "Je hebt geen rij gedefinieerd." -EmptyExpressionBox1 = "Om een variabele to definiëren, typ:" -EmptyFunctionBox1 = "Om een functie to definiëren, typ:" +EmptyExpressionBox1 = "Om een variabele te definiëren, typ:" +EmptyFunctionBox1 = "Om een functie te definiëren, typ:" EmptyExpressionBox2 = "De naam van de variabele kan bevatten:" EmptyFunctionBox2 = "De naam van de functie kan bevatten:" EnableCharacters = "A..Z, a..z, 0..9 en _" From 6628e76d36d7a0de890545b5461d967c651d4812 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Tue, 20 Oct 2020 14:39:34 +0200 Subject: [PATCH 335/560] [NL] adjust translation in python app --- apps/code/toolbox.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n index 3fb63412955..15c3ebb290b 100644 --- a/apps/code/toolbox.nl.i18n +++ b/apps/code/toolbox.nl.i18n @@ -1,4 +1,4 @@ Functions = "Functies" Catalog = "Catalogus" Modules = "Modules" -LoopsAndTests = "Loops and tests" +LoopsAndTests = "Herhalingen en testen" From 81616950f3dfc436ad5fcd6fdf6ccf32419305cf Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 19 Oct 2020 10:59:20 +0200 Subject: [PATCH 336/560] [apps/statistics] Fix horizontal alignement for size legend Change-Id: I834444aee7dd547c71254df4658c0db05eb101d7 --- apps/statistics/base.de.i18n | 4 ++-- apps/statistics/base.en.i18n | 4 ++-- apps/statistics/base.es.i18n | 4 ++-- apps/statistics/base.fr.i18n | 4 ++-- apps/statistics/base.it.i18n | 4 ++-- apps/statistics/base.nl.i18n | 4 ++-- apps/statistics/base.pt.i18n | 4 ++-- apps/statistics/histogram_banner_view.cpp | 2 +- apps/statistics/histogram_controller.cpp | 8 -------- 9 files changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index a28b5e8f5ca..ed222f8d4ac 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -10,8 +10,8 @@ Sizes2 = "Häufigkeiten N2" Sizes3 = "Häufigkeiten N3" ImportList = "Laden einer Liste" Interval = " Intervall" -Size = " Häufigkeit" -Frequency = "Relative" +Size = " Häufigkeit:" +Frequency = "Relative:" HistogramSet = "Einstellung des Histogramms" RectangleWidth = "Breite der Rechtecke" BarStart = "Beginn der Serie" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 26a5a454f90..defc6b6f745 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -10,8 +10,8 @@ Sizes2 = "Frequencies N2" Sizes3 = "Frequencies N3" ImportList = "Import from a list" Interval = " Interval " -Size = " Frequency" -Frequency = "Relative" +Size = " Frequency:" +Frequency = "Relative:" HistogramSet = "Histogram settings" RectangleWidth = "Bin width" BarStart = "X start" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index e10be87dc58..5fd7103e026 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -10,8 +10,8 @@ Sizes2 = "Frecuencias N2" Sizes3 = "Frecuencias N3" ImportList = "Importar una lista" Interval = " Intervalo" -Size = " Frecuencia" -Frequency = "Relativa" +Size = " Frecuencia:" +Frequency = "Relativa:" HistogramSet = "Parámetros del histograma" RectangleWidth = "Ancho del rectangulo" BarStart = "Principio de la serie" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index f90b4d60ba7..05892eb4ba4 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -10,8 +10,8 @@ Sizes2 = "Effectifs N2" Sizes3 = "Effectifs N3" ImportList = "Importer une liste" Interval = " Intervalle " -Size = " Effectif" -Frequency = "Fréquence" +Size = " Effectif:" +Frequency = "Fréquence:" HistogramSet = "Réglage de l'histogramme" RectangleWidth = "Largeur des rectangles" BarStart = "Début de la série" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 1603707e9b3..c2b01ac977b 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -10,8 +10,8 @@ Sizes2 = "Frequenze N2" Sizes3 = "Frequenze N3" ImportList = "Importare una lista" Interval = " Intervallo " -Size = " Frequenza" -Frequency = "Relativa" +Size = " Frequenza:" +Frequency = "Relativa:" HistogramSet = "Regolazione dell'istogramma" RectangleWidth = "Larghezza dei rettangoli" BarStart = "Inizio della serie" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 0af49417e0c..884aad2c4a3 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -10,8 +10,8 @@ Sizes2 = "Frequenties N2" Sizes3 = "Frequenties N3" ImportList = "Importeren uit een lijst" Interval = " Interval " -Size = " Frequentie" -Frequency = "Proportie" +Size = " Frequentie:" +Frequency = "Proportie:" HistogramSet = "Histogram instellingen" RectangleWidth = "Kolombreedte" BarStart = "X start" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index b2a64d1a36e..505efa06f1c 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -10,8 +10,8 @@ Sizes2 = "Frequências N2" Sizes3 = "Frequências N3" ImportList = "Importar de uma lista" Interval = " Intervalo" -Size = " Frequência" -Frequency = "Relativa" +Size = " Frequência:" +Frequency = "Relativa:" HistogramSet = "Configurando o histograma" RectangleWidth = "Largura dos retângulos" BarStart = "Início da série" diff --git a/apps/statistics/histogram_banner_view.cpp b/apps/statistics/histogram_banner_view.cpp index f4575c6f2e7..ccfc19d0db2 100644 --- a/apps/statistics/histogram_banner_view.cpp +++ b/apps/statistics/histogram_banner_view.cpp @@ -9,7 +9,7 @@ HistogramBannerView::HistogramBannerView() : m_intervalView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()), m_sizeLegendView(Font(), I18n::Message::Size, 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::Frequency, 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 47638f57afd..d0294d206ef 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -131,10 +131,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 +142,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); From 2740056472491c7788deb97e206ced05e578157f Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 21 Oct 2020 10:51:17 +0200 Subject: [PATCH 337/560] [apps/statiastics] Rename i18n messages accurately Change-Id: If88f91d6e925dbb2ad293b537661eb3d137ea826 --- apps/statistics/base.de.i18n | 12 ++++++------ apps/statistics/base.en.i18n | 12 ++++++------ apps/statistics/base.es.i18n | 12 ++++++------ apps/statistics/base.fr.i18n | 12 ++++++------ apps/statistics/base.it.i18n | 12 ++++++------ apps/statistics/base.nl.i18n | 12 ++++++------ apps/statistics/base.pt.i18n | 12 ++++++------ apps/statistics/calculation_controller.cpp | 2 +- apps/statistics/histogram_banner_view.cpp | 4 ++-- apps/statistics/store_controller.cpp | 2 +- 10 files changed, 46 insertions(+), 46 deletions(-) diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index ed222f8d4ac..3d106e3752e 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -5,20 +5,20 @@ 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" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index defc6b6f745..88d5cc9aec1 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -5,20 +5,20 @@ BoxTab = "Box" Values1 = "Values V1" Values2 = "Values V2" Values3 = "Values V3" -Sizes1 = "Frequencies N1" -Sizes2 = "Frequencies N2" -Sizes3 = "Frequencies N3" +Frequencies1 = "Frequencies N1" +Frequencies2 = "Frequencies N2" +Frequencies3 = "Frequencies N3" ImportList = "Import from a list" Interval = " Interval " -Size = " Frequency:" -Frequency = "Relative:" +Frequency = " Frequency:" +RelativeFrequency = "Relative:" HistogramSet = "Histogram settings" RectangleWidth = "Bin width" BarStart = "X start" FirstQuartile = "First quartile" Median = "Median" ThirdQuartile = "Third quartile" -TotalSize = "Total frequency" +TotalFrequency = "Total frequency" Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 5fd7103e026..adedc341bb7 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -5,20 +5,20 @@ 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" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index 05892eb4ba4..50096719153 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -5,20 +5,20 @@ 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" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index c2b01ac977b..a7159cf6128 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -5,20 +5,20 @@ 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" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 884aad2c4a3..aaf3b7f8cdd 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -5,20 +5,20 @@ 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 = "Proportie:" HistogramSet = "Histogram instellingen" RectangleWidth = "Kolombreedte" BarStart = "X start" FirstQuartile = "Eerste kwartiel" Median = "Mediaan" ThirdQuartile = "Derde kwartiel" -TotalSize = "Totale omvang" +TotalFrequency = "Totale omvang" Range = "Bereik" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 505efa06f1c..9d46b27cf65 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -5,20 +5,20 @@ 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" diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index 2a0f52e0a05..da22e200f03 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, diff --git a/apps/statistics/histogram_banner_view.cpp b/apps/statistics/histogram_banner_view.cpp index ccfc19d0db2..2bd704f4795 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, 0.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/store_controller.cpp b/apps/statistics/store_controller.cpp index ad7ae5a49a4..dce6f5d760c 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::GrayDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GrayDark with graph/list_controller From 3a90ed61090fa3adfe5bd44c11a366108ca93a60 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 21 Oct 2020 14:21:46 +0200 Subject: [PATCH 338/560] [ion/events_keyboard] Fix selection on long press A previous fix to prevent AlphaLock from interfering with the long press feature broke the long press selection. Revert the changes and find another solution to the previous problem. We compare the alpha status to both the state of the Alpha key and the Lock, which makes sense : Lock is supposed to mimic Alpha being continuously pressed. Change-Id: I1349eb83f8971d3a5efcb10de020bb6c0aed64a1 --- ion/src/shared/events_keyboard.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 2c1cba7e3d1..2c7c91c568c 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -50,10 +50,6 @@ void resetLongRepetition() { ComputeAndSetRepetionFactor(sEventRepetitionCount); } -static Keyboard::Key keyFromState(Keyboard::State state) { - return static_cast(63 - __builtin_clzll(state)); -} - Event getEvent(int * timeout) { assert(*timeout > delayBeforeRepeat); assert(*timeout > delayBetweenRepeat); @@ -70,9 +66,6 @@ Event getEvent(int * timeout) { keysSeenUp |= ~state; keysSeenTransitionningFromUpToDown = keysSeenUp & state; - Keyboard::Key key; - bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); - bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); bool lock = isLockActive(); if (keysSeenTransitionningFromUpToDown != 0) { @@ -84,7 +77,9 @@ Event getEvent(int * timeout) { * processors have an instruction (ARM thumb uses CLZ). * Unfortunately there's no way to express this in standard C, so we have * to resort to using a builtin function. */ - key = keyFromState(keysSeenTransitionningFromUpToDown); + Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); + bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); + bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); Event event(key, shift, alpha, lock); sLastEventShift = shift; sLastEventAlpha = alpha; @@ -103,11 +98,10 @@ Event getEvent(int * timeout) { // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero // In other words, no new key has been pressed - key = keyFromState(state); - Event event(key, shift, alpha, lock); if (canRepeatEvent(sLastEvent) && state == sLastKeyboardState - && sLastEvent == event) + && sLastEventShift == state.keyDown(Keyboard::Key::Shift) + && sLastEventAlpha == (state.keyDown(Keyboard::Key::Alpha) || lock)) { int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); if (time >= delay) { From 82c4fe2190819dcdf19e793f496c90374357c70c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 19 Oct 2020 17:33:57 +0200 Subject: [PATCH 339/560] [poincare/zoom] Limit explosion detection Do not take strong variations into account if they would erase more interesting variations. Change-Id: I6299a64bed449a611f90eda4234af10a183958d1 --- poincare/src/zoom.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index a649ed3688f..b9683b6f2ee 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -152,8 +152,12 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, *yMax = NAN; return false; } else { - resultX[0] = std::min(resultX[0], explosion[0]); - resultX[1] = std::max(resultX[1], explosion[1]); + 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; + } /* Add breathing room around points of interest. */ float xRange = resultX[1] - resultX[0]; resultX[0] -= k_breathingRoom * xRange; From 93b5a3f63a837691d244cf534254bf82d2021f7a Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 22 Oct 2020 10:54:33 +0200 Subject: [PATCH 340/560] [poincare] Handle equal negative numbers in GCD LCM Change-Id: Ia46966270418a339f8a37e8a1971a7f7dd046034 --- poincare/src/arithmetic.cpp | 23 +++++++++++++---------- poincare/test/simplification.cpp | 2 ++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/poincare/src/arithmetic.cpp b/poincare/src/arithmetic.cpp index 67319546990..e6587938539 100644 --- a/poincare/src/arithmetic.cpp +++ b/poincare/src/arithmetic.cpp @@ -9,13 +9,13 @@ 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; @@ -35,14 +35,17 @@ Integer Arithmetic::LCM(const Integer & a, const Integer & b) { if (a.isZero() || b.isZero()) { return Integer(0); } - if (a.isEqualTo(b)) { - return a; + Integer i = a; + Integer j = b; + i.setNegative(false); + j.setNegative(false); + if (i.isEqualTo(j)) { + return i; } - /* Using LCM(a,b) = a*(b/GCD(a,b)). Knowing that GCD(a, b) divides b, - * division is performed before multiplication to be more efficient. */ - Integer signResult = Integer::Multiplication(a, Integer::Division(b, GCD(a, b)).quotient); - signResult.setNegative(false); - return signResult; + /* 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) { diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index fda691e2750..28a406092e3 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -727,10 +727,12 @@ QUIZ_CASE(poincare_simplification_function) { 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)"); From 65156c8e5a4a6e1b6d00beb66e33c43d568a9329 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 22 Oct 2020 11:24:28 +0200 Subject: [PATCH 341/560] [poincare/zoom] Add test for x*ln(x) Change-Id: Ia28a7ffb826a9b6e3618b222b6ed9d0d43de308a --- poincare/test/zoom.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 5edc65b5a8a..999517346a9 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -75,8 +75,6 @@ void assert_has_no_interesting_range(const char * definition, Preferences::Angle assert_interesting_range_is(definition, NAN, NAN, NAN, NAN, angleUnit, symbol); } -void breaker() {} - QUIZ_CASE(poincare_zoom_interesting_ranges) { assert_has_no_interesting_range(Undefined::Name()); assert_has_no_interesting_range("0"); @@ -108,6 +106,7 @@ QUIZ_CASE(poincare_zoom_interesting_ranges) { assert_interesting_range_is("atan(x)", -2, 2); assert_interesting_range_is("sin(x)/x", -12, 12, -0.2, 1); assert_interesting_range_is("x×sin(x)", -9, 9, -5, 8); + assert_interesting_range_is("x×ln(x)", 0.22, 0.88, -0.367828071, -0.367828071); } void assert_refined_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { From 96b8e1859ad8e1aaf0cc810b2230dd8ba368de91 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 15:49:23 +0200 Subject: [PATCH 342/560] [ion/events] Create event ExternalText Added an event to represent the typing of text that is not on the device's keyboard. ExternalText event's text is read from a buffer, that will be filled when the event is generated. ExternalText only exists on the simulator. Change-Id: Ie78d2c7c2de91da986a1ce2130a5ecd123db48ee --- ion/include/ion/events.h | 3 +++ ion/src/device/shared/events.cpp | 16 ++++++++++++++++ ion/src/shared/events.cpp | 15 --------------- ion/src/simulator/shared/events.cpp | 27 ++++++++++++++++++++++++++- ion/src/simulator/shared/events.h | 9 +++++++++ 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index b587273998c..12079586a7f 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -229,6 +229,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/src/device/shared/events.cpp b/ion/src/device/shared/events.cpp index 6eab2b3fc18..12cb1c272b1 100644 --- a/ion/src/device/shared/events.cpp +++ b/ion/src/device/shared/events.cpp @@ -1,4 +1,5 @@ #include +#include namespace Ion { namespace Events { @@ -6,5 +7,20 @@ namespace Events { void didPressNewKey() { } +bool Event::isDefined() const { + if (isKeyboardEvent()) { + return s_dataForEvent[m_id].isDefined(); + } else { + return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); + } +} + +const char * Event::text() const { + if (m_id >= 4*PageSize) { + return nullptr; + } + return s_dataForEvent[m_id].text(); +} + } } diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index 519c4acd3c5..d095018227e 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -48,24 +48,9 @@ Event::Event(Keyboard::Key key, bool shift, bool alpha, bool lock) { assert(m_id != Events::None.m_id); } -const char * Event::text() const { - 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/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index a522fe7dd80..5fb318e73c3 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -1,5 +1,7 @@ -#include +#include "events.h" #include "haptics.h" +#include +#include #include namespace Ion { @@ -9,5 +11,28 @@ void didPressNewKey() { Simulator::Haptics::rumble(); } +char * sharedExternalTextBuffer() { + static char buffer[32]; + return buffer; +} + +const char * Event::text() const { + if (m_id >= 4*PageSize) { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return nullptr; + } + return s_dataForEvent[m_id].text(); +} + +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); + } +} + } } diff --git a/ion/src/simulator/shared/events.h b/ion/src/simulator/shared/events.h index 5245e0a2049..609d09b522d 100644 --- a/ion/src/simulator/shared/events.h +++ b/ion/src/simulator/shared/events.h @@ -1,6 +1,8 @@ #ifndef ION_SIMULATOR_EVENTS_H #define ION_SIMULATOR_EVENTS_H +#include + namespace Ion { namespace Simulator { namespace Events { @@ -9,6 +11,13 @@ void dumpEventCount(int i); void logAfter(int numberOfEvents); } +} + +namespace Events { + +char * sharedExternalTextBuffer(); +constexpr Event ExternalText = Event::Special(6); + } } From 81817535530e1fc3ae0b625ac61fba1aef414a2c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 16:28:19 +0200 Subject: [PATCH 343/560] [ion/utf8_helper] Method to check text compliance Change-Id: I9683a8ba819b1a4aad18bcc4759160509e424d4e --- ion/include/ion/unicode/utf8_helper.h | 4 ++++ ion/src/shared/unicode/utf8_helper.cpp | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index edc3793259c..3f3492b9cec 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -124,6 +124,10 @@ 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); +/* Returns false if one of text's code points does not have a corresponding + * glyph in Epsilon's fonts.*/ +bool CanBeWrittenWithGlyphs(const char * text); + }; #endif diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 44f034d4160..b1f57a3a255 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 @@ -488,4 +489,18 @@ void countGlyphsInLine(const char * text, int * before, int * after, const char UTF8Helper::PerformAtCodePoints(afterLocation, UCodePointLineFeed, nullptr, countGlyph, after, 0, 0, UCodePointLineFeed); } +bool CanBeWrittenWithGlyphs(const char * text) { + UTF8Decoder decoder(text); + CodePoint cp = decoder.nextCodePoint(); + while(cp != UCodePointNull) { + if (KDFont::LargeFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint + || KDFont::SmallFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint) + { + return false; + } + cp = decoder.nextCodePoint(); + } + return true; +} + } From 4ea826318558dfdea5b244e80777c5b4f0ac4d7c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 1 Oct 2020 15:56:27 +0200 Subject: [PATCH 344/560] [ion/simulator/events_keyboard] Serve ExternalText Generate an ExternalText event when catching a SDL_TextInputEvent whose text cannot be boiled down to one of the regular events. Change-Id: I036fa2a153c8351979521d7f8cba6b62aa64604b --- ion/src/simulator/shared/events_keyboard.cpp | 34 +++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index 79cfb89302a..fcb6f16b4a1 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 @@ -148,20 +150,28 @@ 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]; + if (!UTF8Helper::CanBeWrittenWithGlyphs(event.text)) { + return None; } - return None; + Ion::Events::removeShift(); + strlcpy(sharedExternalTextBuffer(), event.text, strlen(event.text) + 1); + return ExternalText; } Event getPlatformEvent() { From b0b6fe33c99b1caae8aec3992e6f5f27fcbf5b19 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 6 Oct 2020 16:11:28 +0200 Subject: [PATCH 345/560] [ion/events] Factor includes of layout_events.h Change-Id: Id7f68f4dfb4727453a02437a0b824492a8c94730 --- ion/include/ion/events.h | 3 +++ ion/src/device/shared/events.cpp | 12 ++---------- ion/src/shared/events.cpp | 15 +++++++++++++++ ion/src/simulator/shared/events.cpp | 17 ++++++----------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 12079586a7f..fdffb1cef46 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -36,6 +36,9 @@ class Event { bool isDefined() const; static constexpr int PageSize = Keyboard::NumberOfKeys; private: + bool defaultIsDefined() const; + const char * defaultText() const; + uint8_t m_id; }; diff --git a/ion/src/device/shared/events.cpp b/ion/src/device/shared/events.cpp index 12cb1c272b1..c19b6b12d08 100644 --- a/ion/src/device/shared/events.cpp +++ b/ion/src/device/shared/events.cpp @@ -1,5 +1,4 @@ #include -#include namespace Ion { namespace Events { @@ -8,18 +7,11 @@ void didPressNewKey() { } bool Event::isDefined() const { - if (isKeyboardEvent()) { - return s_dataForEvent[m_id].isDefined(); - } else { - return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); - } + return defaultIsDefined(); } const char * Event::text() const { - if (m_id >= 4*PageSize) { - return nullptr; - } - return s_dataForEvent[m_id].text(); + return defaultText(); } } diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index d095018227e..7346afd09f0 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -52,5 +52,20 @@ bool Event::hasText() const { return text() != nullptr; } +bool Event::defaultIsDefined() const { + if (isKeyboardEvent()) { + return s_dataForEvent[m_id].isDefined(); + } else { + return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); + } +} + +const char * Event::defaultText() const { + if (m_id >= 4*PageSize) { + return nullptr; + } + return s_dataForEvent[m_id].text(); +} + } } diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index 5fb318e73c3..e2e3e766a04 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -1,7 +1,6 @@ #include "events.h" #include "haptics.h" #include -#include #include namespace Ion { @@ -17,21 +16,17 @@ char * sharedExternalTextBuffer() { } const char * Event::text() const { - if (m_id >= 4*PageSize) { - if (*this == ExternalText) { - return const_cast(sharedExternalTextBuffer()); - } - return nullptr; + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); } - return s_dataForEvent[m_id].text(); + return defaultText(); } 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); + if (*this == ExternalText) { + return true; } + return defaultIsDefined(); } } From aadf8f57162f5ec184970358982fab08a815039f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 22 Oct 2020 12:29:47 +0200 Subject: [PATCH 346/560] [ion/events] Fix ExternalText buffer size The size of the ExternalText events shared buffer is now defined as the size of the SDL_TextInputEvent 'text' field, as it should. Also fix a wrong parameter being passed to a strlcpy. Change-Id: I6a57d49d61fec8a009c4711efce564c65544e571 --- ion/src/simulator/shared/events.cpp | 3 +-- ion/src/simulator/shared/events.h | 2 ++ ion/src/simulator/shared/events_keyboard.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index e2e3e766a04..6300c99999b 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -1,7 +1,6 @@ #include "events.h" #include "haptics.h" #include -#include namespace Ion { namespace Events { @@ -11,7 +10,7 @@ void didPressNewKey() { } char * sharedExternalTextBuffer() { - static char buffer[32]; + static char buffer[sharedExternalTextBufferSize]; return buffer; } diff --git a/ion/src/simulator/shared/events.h b/ion/src/simulator/shared/events.h index 609d09b522d..9896b88e7bb 100644 --- a/ion/src/simulator/shared/events.h +++ b/ion/src/simulator/shared/events.h @@ -2,6 +2,7 @@ #define ION_SIMULATOR_EVENTS_H #include +#include namespace Ion { namespace Simulator { @@ -15,6 +16,7 @@ void logAfter(int numberOfEvents); namespace Events { +static constexpr size_t sharedExternalTextBufferSize = sizeof(SDL_TextInputEvent::text); char * sharedExternalTextBuffer(); constexpr Event ExternalText = Event::Special(6); diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index fcb6f16b4a1..f4867cdf845 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -170,7 +170,7 @@ static Event eventFromSDLTextInputEvent(SDL_TextInputEvent event) { return None; } Ion::Events::removeShift(); - strlcpy(sharedExternalTextBuffer(), event.text, strlen(event.text) + 1); + strlcpy(sharedExternalTextBuffer(), event.text, sharedExternalTextBufferSize); return ExternalText; } From cab22fcbcebae14e0322c3b66f14ecfc95361d60 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 21 Oct 2020 15:04:04 +0200 Subject: [PATCH 347/560] [apps/shared] Set buttonAction method as protected Change-Id: Iaef80fdbb6083be688fadc2b5fc515dbbd0d004c --- apps/shared/float_parameter_controller.h | 2 +- apps/shared/range_parameter_controller.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/shared/float_parameter_controller.h b/apps/shared/float_parameter_controller.h index 21dc7f94bc2..0d7f2495ecb 100644 --- a/apps/shared/float_parameter_controller.h +++ b/apps/shared/float_parameter_controller.h @@ -38,11 +38,11 @@ class FloatParameterController : public ViewController, public ListViewDataSourc 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/range_parameter_controller.cpp b/apps/shared/range_parameter_controller.cpp index 2e2712ed2cb..38d00d3652d 100644 --- a/apps/shared/range_parameter_controller.cpp +++ b/apps/shared/range_parameter_controller.cpp @@ -88,8 +88,7 @@ void RangeParameterController::buttonAction() { m_interactiveRange->setZoomAuto(false); m_interactiveRange->setZoomNormalize(m_interactiveRange->isOrthonormal()); - StackViewController * stack = stackController(); - stack->pop(); + FloatParameterController::buttonAction(); } } From a922e44558425da924e725c22ed82dd463c3f4e6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 21 Oct 2020 15:05:33 +0200 Subject: [PATCH 348/560] [apps/statistics] Add temporary parameters for histogram Change-Id: I67c21d59263b7eddd7ee8ee9e61c168e6b013d13 --- .../histogram_parameter_controller.cpp | 40 ++++++++++++++++--- .../histogram_parameter_controller.h | 11 ++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index c9e3a8b452e..17e527e8c10 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -18,6 +18,13 @@ HistogramParameterController::HistogramParameterController(Responder * parentRes } } +void HistogramParameterController::viewWillAppear() { + // Initialize temporary parameters to the extracted value. + setParameterAtIndex(0, extractParameterAtIndex(0)); + setParameterAtIndex(1, extractParameterAtIndex(1)); + FloatParameterController::viewWillAppear(); +} + const char * HistogramParameterController::title() { return I18n::translate(I18n::Message::HistogramSet); } @@ -32,11 +39,27 @@ void HistogramParameterController::willDisplayCellForIndex(HighlightCell * cell, FloatParameterController::willDisplayCellForIndex(cell, index); } -double HistogramParameterController::parameterAtIndex(int index) { +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,8 +70,8 @@ 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); @@ -63,9 +86,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 +98,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 7688b7b27c4..dae05c1ca35 100644 --- a/apps/statistics/histogram_parameter_controller.h +++ b/apps/statistics/histogram_parameter_controller.h @@ -10,17 +10,24 @@ 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; - HighlightCell * reusableParameterCell(int index, int type) override; - int reusableParameterCellCount(int type) override { return k_numberOfCells; } + double extractParameterAtIndex(int index); 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; } + void buttonAction() override; MessageTableCellWithEditableText m_cells[k_numberOfCells]; Store * m_store; + // Temporary parameters + double m_tempBarWidth; + double m_tempFirstDrawnBarAbscissa; }; } From b2d7ee800a83753d47d9e2ef1f99c13512d7d6d8 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 21 Oct 2020 15:35:45 +0200 Subject: [PATCH 349/560] [apps/statistics] Add pop-up to confirm parameter discard Change-Id: I0d0d7dca0e167cfcb40f4b26d8208e12056ebf40 --- .../histogram_parameter_controller.cpp | 16 +++++++++++++++- apps/statistics/histogram_parameter_controller.h | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index 17e527e8c10..f7d538d0b84 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -10,7 +10,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); @@ -39,6 +44,15 @@ void HistogramParameterController::willDisplayCellForIndex(HighlightCell * cell, FloatParameterController::willDisplayCellForIndex(cell, 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(); diff --git a/apps/statistics/histogram_parameter_controller.h b/apps/statistics/histogram_parameter_controller.h index dae05c1ca35..78e00216c56 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 { @@ -17,6 +18,7 @@ class HistogramParameterController : public Shared::FloatParameterController Date: Fri, 23 Oct 2020 17:37:48 +0200 Subject: [PATCH 350/560] [apps/regression] Add escape case if data is not suitable for regression Change-Id: Ie2e028a5030e1b0d3f133efdde971645d5b4687b --- apps/regression/store.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index a794ef3c6a6..e77e7547ffd 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -349,7 +349,12 @@ double Store::computeDeterminationCoefficient(int series, Poincare::Context * gl const int numberOfPairs = numberOfPairsOfSeries(series); for (int k = 0; k < numberOfPairs; k++) { // Difference between the observation and the estimated value of the model - double residual = m_data[series][1][k] - yValueForXValue(series, m_data[series][0][k], globalContext); + 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; From f2ba5daab9e9ce85687f0ac93262b827d91c8264 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 26 Oct 2020 11:42:13 +0100 Subject: [PATCH 351/560] [apps/regression] Comment assert that cannot be satisfied Change-Id: Iaae5ee4292e33f923f47590ee4520bac44c5d750 --- apps/regression/store.cpp | 4 ++-- apps/regression/test/model.cpp | 23 +++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index e77e7547ffd..18f09489e5e 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -367,8 +367,8 @@ double Store::computeDeterminationCoefficient(int series, Poincare::Context * gl } double r2 = 1.0 - ssr / sst; // Check if regression fit was optimal. - // TODO : Optimize Logistic regression. - assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional || seriesRegressionType(series) == Model::Type::Logistic); + // TODO : Optimize regression fitting so that r2 cannot be negative. + // assert(r2 >= 0 || seriesRegressionType(series) == Model::Type::Proportional); return r2; } diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index b914ec1f70f..03d3bd98828 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -166,6 +166,13 @@ QUIZ_CASE(exponential_regression3) { double coefficients[] = {-1, -1}; 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) { @@ -174,6 +181,13 @@ QUIZ_CASE(power_regression) { double coefficients[] = {71.8, 2.7}; 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 @@ -196,10 +210,11 @@ QUIZ_CASE(logistic_regression) { assert_regression_is(x2, y2, 10, Model::Type::Logistic, coefficients2, r22); // TODO : This data produce a wrong fit currently - // double x3[] = {4.0, 3.0, 21.0, 1.0, 6.0}; - // double y3[] = {0.0, 4.0, 5.0, 4.0, 58.0}; - // double coefficients3[] = {370162529.359743, 4.266439, 31.445238}; - // double r23 = 0.401040; + // 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); } From 82a77df0887feb18dc566b750e1b9603a57c9f33 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 26 Oct 2020 15:01:12 +0100 Subject: [PATCH 352/560] [interactive_curve_view_range] Missing resets Added some missing resets of the Auto and Normalize statuses Change-Id: I5514a2566c1f6ba73d04b526402b428b2edce4b4 --- apps/shared/interactive_curve_view_range.cpp | 22 ++++++++++++++++++-- apps/shared/interactive_curve_view_range.h | 4 +++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 77126f4b065..b2801652168 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -138,8 +138,14 @@ void InteractiveCurveViewRange::normalize() { m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - assert(isOrthonormal()); - setZoomNormalize(true); + /* When the coordinates reach 10^7, the float type is not precise enough to + * properly normalize. */ + if (isOrthonormal()) { + setZoomNormalize(true); + } else { + assert(xMin() < -1e7f || xMax() > 1e7f || yMin() < -1e7f || yMax() > 1e7f); + setZoomNormalize(false); + } } void InteractiveCurveViewRange::setDefault() { @@ -173,6 +179,7 @@ void InteractiveCurveViewRange::setDefault() { } void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { + setZoomAuto(false); if (std::isnan(position)) { return; } @@ -191,9 +198,14 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { m_yRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); } + + if (!isOrthonormal()) { + setZoomNormalize(false); + } } void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio, float pixelWidth) { + setZoomAuto(false); if (!std::isinf(x) && !std::isnan(x)) { const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; @@ -229,6 +241,12 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to 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.*/ + if (!isOrthonormal()) { + setZoomNormalize(false); + } } bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 5e4607b2917..b649ed9a728 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -23,7 +23,9 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { } static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } - bool isOrthonormal(float tolerance = 2 * FLT_EPSILON) const; + /* A tolerance of 0.001 is necessary to cover the imprecision with the + * largest ranges, around 10^7 */ + bool isOrthonormal(float tolerance = 1e-3f) const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate); uint32_t rangeChecksum() override; From 5a5955b5136853db339161221a13169a53d56054 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 27 Oct 2020 10:02:29 +0100 Subject: [PATCH 353/560] [interactive_curve_view_range] Status unchanged Only notify the delegate to the refresh the button when the status has changed. Change-Id: I6689d2c292ff96039a68cd1b437b18df5fb98829 --- apps/shared/interactive_curve_view_range.cpp | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index b2801652168..2e2a2590a30 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -27,6 +27,9 @@ uint32_t InteractiveCurveViewRange::rangeChecksum() { } void InteractiveCurveViewRange::setZoomAuto(bool v) { + if (m_zoomAuto == v) { + return; + } m_zoomAuto = v; if (m_delegate) { m_delegate->updateZoomButtons(); @@ -34,6 +37,9 @@ void InteractiveCurveViewRange::setZoomAuto(bool v) { } void InteractiveCurveViewRange::setZoomNormalize(bool v) { + if (m_zoomNormalize == v) { + return; + } m_zoomNormalize = v; if (m_delegate) { m_delegate->updateZoomButtons(); @@ -140,12 +146,9 @@ void InteractiveCurveViewRange::normalize() { /* When the coordinates reach 10^7, the float type is not precise enough to * properly normalize. */ - if (isOrthonormal()) { - setZoomNormalize(true); - } else { - assert(xMin() < -1e7f || xMax() > 1e7f || yMin() < -1e7f || yMax() > 1e7f); - setZoomNormalize(false); - } + constexpr float limit = 1e7f; + assert(isOrthonormal() || xMin() < -limit || xMax() > limit || yMin() < -limit || yMax() > limit); + setZoomNormalize(isOrthonormal()); } void InteractiveCurveViewRange::setDefault() { @@ -199,9 +202,7 @@ void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat); } - if (!isOrthonormal()) { - setZoomNormalize(false); - } + setZoomNormalize(isOrthonormal()); } void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio, float pixelWidth) { @@ -244,9 +245,7 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to /* Panning to a point greater than the maximum range of 10^8 could make the * graph not normalized.*/ - if (!isOrthonormal()) { - setZoomNormalize(false); - } + setZoomNormalize(isOrthonormal()); } bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { From 3b24f9c2c37e75099c2bf491711ed4cc52c431f1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 27 Oct 2020 10:15:09 +0100 Subject: [PATCH 354/560] [interactive_curve_view_range] Check auto status Deactivate Auto status every time the range effectively changes. Change-Id: I695b840d5e72061a73a229a6e726433660bdfdbf --- apps/shared/interactive_curve_view_range.cpp | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 2e2a2590a30..18da1b8e181 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -96,7 +96,6 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { float xMa = xMax(); float yMi = yMin(); float yMa = yMax(); - setZoomAuto(false); if (ratio*std::fabs(xMa-xMi) < Range1D::k_minFloat || ratio*std::fabs(yMa-yMi) < Range1D::k_minFloat) { return; } @@ -105,12 +104,14 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { float newXMin = centerX*(1.0f-ratio)+ratio*xMi; float newXMax = centerX*(1.0f-ratio)+ratio*xMa; if (!std::isnan(newXMin) && !std::isnan(newXMax)) { + setZoomAuto(false); m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(newXMin, 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)) { + setZoomAuto(false); m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); } @@ -120,6 +121,9 @@ void InteractiveCurveViewRange::panWithVector(float x, float y) { 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); @@ -130,6 +134,12 @@ void InteractiveCurveViewRange::normalize() { /* We center the ranges on the current range center, and put each axis so that * 1cm = 2 current units. */ + if (isOrthonormal()) { + return; + } + + setZoomAuto(false); + float newXMin = xMin(), newXMax = xMax(), newYMin = yMin(), newYMax = yMax(); const float unit = std::max(xGridUnit(), yGridUnit()); @@ -182,7 +192,6 @@ void InteractiveCurveViewRange::setDefault() { } void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { - setZoomAuto(false); if (std::isnan(position)) { return; } @@ -191,22 +200,29 @@ 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 { 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, float pixelWidth) { - setZoomAuto(false); if (!std::isinf(x) && !std::isnan(x)) { const float xRange = xMax() - xMin(); const float leftMargin = leftMarginRatio * xRange; From aba09e1a1fe300ef859ba10a05e560dc16e33b7b Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 10:13:56 +0200 Subject: [PATCH 355/560] [poincare] Fix multiplication.cpp comment typos Change-Id: I1553144fb45f45cbdb4e021b14ef20cb319984cc --- poincare/src/multiplication.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index e3dfcf3f71c..b222630a2d6 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -630,7 +630,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) { @@ -690,7 +690,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) { @@ -815,7 +815,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext /* Step 7: 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); From 0d883bfb19f9ca4f8bdaa24aa540f5763dbdce0b Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 12:03:09 +0200 Subject: [PATCH 356/560] [poincare] Handle division by 0 when combining powers Change-Id: I108ba8131ef2f8d3d210a769322a815121311f6b --- poincare/include/poincare/multiplication.h | 1 + poincare/src/multiplication.cpp | 52 +++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index fbd62e1438f..bdefb2c0486 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -107,6 +107,7 @@ class Multiplication : public NAryExpression { 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/src/multiplication.cpp b/poincare/src/multiplication.cpp index b222630a2d6..a1b13c96d5d 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -721,13 +721,23 @@ 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 @@ -1101,6 +1111,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()); + + if (baseSign != ExpressionNode::Sign::Unknown && !base.isRationalZero()) { + // x cannot be null + return true; + } + + Expression exponent1 = CreateExponent(e1); + ExpressionNode::Sign exponentSign1 = exponent1.sign(reductionContext.context()); + Expression exponent2 = CreateExponent(e2); + ExpressionNode::Sign exponentSign2 = exponent2.sign(reductionContext.context()); + + if (exponentSign1 == ExpressionNode::Sign::Positive && !exponent1.isRationalZero() + && exponentSign2 == ExpressionNode::Sign::Positive && !exponent2.isRationalZero()) { + // a and b are strictly positive + return true; + } + + Expression sum = Addition::Builder(exponent1, exponent2).shallowReduce(reductionContext); + ExpressionNode::Sign sumSign = sum.sign(reductionContext.context()); + + if (sumSign == ExpressionNode::Sign::Negative || sum.isRationalZero()) { + // 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(); } From 9d077c672d4c6c5e43e78f53e5000262397bff24 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 14:48:26 +0200 Subject: [PATCH 357/560] [poincare/test] Add tests for multiplication reduction Change-Id: I54186c0a7c12f7fb0122c0a67f4a6d0d462feb82 --- poincare/test/simplification.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 28a406092e3..3b01d582216 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -194,6 +194,24 @@ 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) { From 58114255e5ace468cbf2b4c1c6ac487eb957b1c4 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 16:23:31 +0200 Subject: [PATCH 358/560] [poincare] implement isRationalZero for other numbers Change-Id: I40f61958f9e51adb376407b2a512097962979417 --- poincare/include/poincare/based_integer.h | 1 + poincare/include/poincare/decimal.h | 1 + poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/expression_node.h | 1 + poincare/include/poincare/float.h | 1 + poincare/include/poincare/rational.h | 1 + poincare/src/expression.cpp | 4 ---- 7 files changed, 6 insertions(+), 5 deletions(-) diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index a3660e08c0a..6419be9d99f 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -28,6 +28,7 @@ class BasedIntegerNode final : public NumberNode { // Expression subclassing Type type() const override { return Type::BasedInteger; } Sign sign(Context * context) const override { return Sign::Positive; } + bool isRationalZero() const override { return integer().isZero(); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index ee1b0cd5bf6..ddd4178d570 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -43,6 +43,7 @@ class DecimalNode final : public NumberNode { Type type() const override { return Type::Decimal; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } Expression setSign(Sign s, ReductionContext reductionContext) override; + bool isRationalZero() const override { return unsignedMantissa().isZero(); } // Approximation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index f94cf79a68c..da2f18f6781 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -153,7 +153,7 @@ class Expression : public TreeHandle { ExpressionNode::Sign sign(Context * context) const { return node()->sign(context); } bool isUndefined() const { return node()->type() == ExpressionNode::Type::Undefined || node()->type() == ExpressionNode::Type::Unreal; } bool isNumber() const { return node()->isNumber(); } - bool isRationalZero() const; + bool isRationalZero() const { return node()->isRationalZero(); } bool isRationalOne() const; bool isRandom() const { return node()->isRandom(); } bool isParameteredExpression() const { return node()->isParameteredExpression(); } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 3393d8d267c..741d5627469 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -187,6 +187,7 @@ class ExpressionNode : public TreeNode { virtual Sign sign(Context * context) const { return Sign::Unknown; } virtual bool isNumber() const { return false; } + virtual bool isRationalZero() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } /* childAtIndexNeedsUserParentheses checks if parentheses are required by mathematical rules: diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 400e3cc48ac..3f9011e5b36 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -40,6 +40,7 @@ class FloatNode final : public NumberNode { Sign sign(Context * context) const override { return m_value < 0 ? Sign::Negative : Sign::Positive; } Expression setSign(Sign s, ReductionContext reductionContext) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; + bool isRationalZero() const override { return m_value == 0.0; } // Layout int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index c1af28415ad..6774aa1836e 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -17,6 +17,7 @@ class RationalNode final : public NumberNode { bool isNegative() const { return m_negative; } void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } + bool isRationalZero() const override { return isZero(); } // TreeNode size_t size() const override; diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index cc407c89238..a389510e294 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(); } From 98499f6c9f2f4b76603e268db100e8a9d5448861 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 16:28:13 +0200 Subject: [PATCH 359/560] [poincare] Rename isRationalZero to isNumberZero Change-Id: I554dd5e9b9ab3af4364ca23cde590f9e0a458ef8 --- .../additional_outputs/matrix_list_controller.cpp | 2 +- apps/solver/equation_store.cpp | 10 +++++----- poincare/include/poincare/based_integer.h | 2 +- poincare/include/poincare/decimal.h | 2 +- poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/expression_node.h | 2 +- poincare/include/poincare/float.h | 2 +- .../poincare/hyperbolic_trigonometric_function.h | 2 +- poincare/include/poincare/rational.h | 2 +- poincare/src/complex_cartesian.cpp | 12 ++++++------ poincare/src/matrix.cpp | 6 +++--- poincare/src/multiplication.cpp | 8 ++++---- poincare/src/power.cpp | 2 +- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index eed72ad189f..5b4f3051da7 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -51,7 +51,7 @@ void MatrixListController::setExpression(Poincare::Expression e) { m_layouts[index++] = getLayoutFromExpression(determinant, context, preferences); // 2. Matrix inverse if invertible matrix // A squared matrix is invertible if and only if determinant is non null - if (!determinant.isUndefined() && !determinant.isRationalZero()) { + if (!determinant.isUndefined() && !determinant.isNumberZero()) { m_indexMessageMap[index] = messageIndex++; m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); } diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 2ad2a7d531f..86f38fefb95 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -316,12 +316,12 @@ 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).isNumberZero()) { rowWithNullCoefficients = false; break; } } - if (rowWithNullCoefficients && !Ab.matrixChild(j, n).isRationalZero()) { + if (rowWithNullCoefficients && !Ab.matrixChild(j, n).isNumberZero()) { m_numberOfSolutions = 0; } } @@ -348,7 +348,7 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } - if (delta.isRationalZero()) { + if (delta.isNumberZero()) { // 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; @@ -383,8 +383,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->isNumberZero()) { + if (delta0->isNumberZero()) { // 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/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index 6419be9d99f..ead8a0f14e0 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -28,7 +28,7 @@ class BasedIntegerNode final : public NumberNode { // Expression subclassing Type type() const override { return Type::BasedInteger; } Sign sign(Context * context) const override { return Sign::Positive; } - bool isRationalZero() const override { return integer().isZero(); } + bool isNumberZero() const override { return integer().isZero(); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index ddd4178d570..317f2f3552a 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -43,7 +43,7 @@ class DecimalNode final : public NumberNode { Type type() const override { return Type::Decimal; } Sign sign(Context * context) const override { return m_negative ? Sign::Negative : Sign::Positive; } Expression setSign(Sign s, ReductionContext reductionContext) override; - bool isRationalZero() const override { return unsignedMantissa().isZero(); } + bool isNumberZero() const override { return unsignedMantissa().isZero(); } // Approximation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index da2f18f6781..f20d4ddeb16 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -153,7 +153,7 @@ class Expression : public TreeHandle { ExpressionNode::Sign sign(Context * context) const { return node()->sign(context); } bool isUndefined() const { return node()->type() == ExpressionNode::Type::Undefined || node()->type() == ExpressionNode::Type::Unreal; } bool isNumber() const { return node()->isNumber(); } - bool isRationalZero() const { return node()->isRationalZero(); } + bool isNumberZero() const { return node()->isNumberZero(); } bool isRationalOne() const; bool isRandom() const { return node()->isRandom(); } bool isParameteredExpression() const { return node()->isParameteredExpression(); } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 741d5627469..3147929e8b6 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -187,7 +187,7 @@ class ExpressionNode : public TreeNode { virtual Sign sign(Context * context) const { return Sign::Unknown; } virtual bool isNumber() const { return false; } - virtual bool isRationalZero() const { return false; } + virtual bool isNumberZero() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } /* childAtIndexNeedsUserParentheses checks if parentheses are required by mathematical rules: diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 3f9011e5b36..31695ac4a70 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -40,7 +40,7 @@ class FloatNode final : public NumberNode { Sign sign(Context * context) const override { return m_value < 0 ? Sign::Negative : Sign::Positive; } Expression setSign(Sign s, ReductionContext reductionContext) override; int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; - bool isRationalZero() const override { return m_value == 0.0; } + bool isNumberZero() const override { return m_value == 0.0; } // Layout int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/hyperbolic_trigonometric_function.h b/poincare/include/poincare/hyperbolic_trigonometric_function.h index 62a896c49d6..176c9e83433 100644 --- a/poincare/include/poincare/hyperbolic_trigonometric_function.h +++ b/poincare/include/poincare/hyperbolic_trigonometric_function.h @@ -17,7 +17,7 @@ class HyperbolicTrigonometricFunctionNode : public ExpressionNode { 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) const { return e.isNumberZero(); } virtual Expression imageOfNotableValue() const { return Rational::Builder(0); } }; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 6774aa1836e..f9c73d3004d 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -17,7 +17,7 @@ class RationalNode final : public NumberNode { bool isNegative() const { return m_negative; } void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } - bool isRationalZero() const override { return isZero(); } + bool isNumberZero() const override { return isZero(); } // TreeNode size_t size() const override; diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index da5db5d7419..486e866f181 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -60,7 +60,7 @@ Expression ComplexCartesian::shallowReduce() { return e; } } - if (imag().isRationalZero()) { + if (imag().isNumberZero()) { Expression r = real(); replaceWithInPlace(r); return r; @@ -128,9 +128,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().isNumberZero()) { a = real(); - } else if (real().isRationalZero()) { + } else if (real().isNumberZero()) { a = imag(); } if (!a.isUninitialized()) { @@ -149,7 +149,7 @@ Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionCont Expression ComplexCartesian::argument(ExpressionNode::ReductionContext reductionContext) { Expression a = real(); Expression b = imag(); - if (!b.isRationalZero()) { + if (!b.isNumberZero()) { // 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 +242,11 @@ ComplexCartesian ComplexCartesian::powerInteger(int n, ExpressionNode::Reduction Expression a = real(); Expression b = imag(); assert(n > 0); - assert(!b.isRationalZero()); + assert(!b.isNumberZero()); // 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.isNumberZero()) { ComplexCartesian result; Expression bpow = Power::Builder(b, Rational::Builder(n)); if (n/2%2 == 1) { diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index a212b35a6df..17e0054f965 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -136,7 +136,7 @@ int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Pr 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).isNumberZero()) { j--; } if (j == i-1) { @@ -225,7 +225,7 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex // 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()); // Handle very low pivots - if (pivot == 0.0f && !matrixChild(iPivot_temp, k).isRationalZero()) { + if (pivot == 0.0f && !matrixChild(iPivot_temp, k).isNumberZero()) { pivot = FLT_MIN; } @@ -241,7 +241,7 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex } iPivot_temp++; } - if (matrixChild(iPivot, k).isRationalZero()) { + if (matrixChild(iPivot, k).isNumberZero()) { // No non-null coefficient in this column, skip k++; if (determinant) { diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index a1b13c96d5d..fc699cff9c4 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1123,7 +1123,7 @@ bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression base = Base(e1); ExpressionNode::Sign baseSign = base.sign(reductionContext.context()); - if (baseSign != ExpressionNode::Sign::Unknown && !base.isRationalZero()) { + if (baseSign != ExpressionNode::Sign::Unknown && !base.isNumberZero()) { // x cannot be null return true; } @@ -1133,8 +1133,8 @@ bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression exponent2 = CreateExponent(e2); ExpressionNode::Sign exponentSign2 = exponent2.sign(reductionContext.context()); - if (exponentSign1 == ExpressionNode::Sign::Positive && !exponent1.isRationalZero() - && exponentSign2 == ExpressionNode::Sign::Positive && !exponent2.isRationalZero()) { + if (exponentSign1 == ExpressionNode::Sign::Positive && !exponent1.isNumberZero() + && exponentSign2 == ExpressionNode::Sign::Positive && !exponent2.isNumberZero()) { // a and b are strictly positive return true; } @@ -1142,7 +1142,7 @@ bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression sum = Addition::Builder(exponent1, exponent2).shallowReduce(reductionContext); ExpressionNode::Sign sumSign = sum.sign(reductionContext.context()); - if (sumSign == ExpressionNode::Sign::Negative || sum.isRationalZero()) { + if (sumSign == ExpressionNode::Sign::Negative || sum.isNumberZero()) { // a+b is negative or null return true; } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 3ecb7587ea7..55c3b8bb09a 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -501,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.isNumberZero() || baseType == ExpressionNode::Type::Infinity) { return replaceWithUndefinedInPlace(); } // x^0 From b2945c3f8b55984c723e19ff4e3c7e4b3afd7e99 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 20 Oct 2020 16:28:40 +0200 Subject: [PATCH 360/560] [poincare] Add isNumberZero tests Change-Id: Ia8906e27c5c28f96c87ed39f522f4b1028ad80b8 --- poincare/test/expression_properties.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index d6a80ce21bc..0222d22c091 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -17,6 +17,26 @@ QUIZ_CASE(poincare_properties_is_number) { quiz_assert(!Addition::Builder(Rational::Builder(1), Rational::Builder(2)).isNumber()); } +QUIZ_CASE(poincare_properties_is_number_zero) { + quiz_assert(!BasedInteger::Builder("2",Integer::Base::Binary).isNumberZero()); + quiz_assert(!BasedInteger::Builder("2",Integer::Base::Decimal).isNumberZero()); + quiz_assert(!BasedInteger::Builder("2",Integer::Base::Hexadecimal).isNumberZero()); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Binary).isNumberZero()); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Decimal).isNumberZero()); + quiz_assert(BasedInteger::Builder("0",Integer::Base::Hexadecimal).isNumberZero()); + quiz_assert(!Decimal::Builder("2",3).isNumberZero()); + quiz_assert(Decimal::Builder("0",0).isNumberZero()); + quiz_assert(!Float::Builder(1.0f).isNumberZero()); + quiz_assert(Float::Builder(0.0f).isNumberZero()); + quiz_assert(!Infinity::Builder(true).isNumberZero()); + quiz_assert(!Undefined::Builder().isNumberZero()); + quiz_assert(!Rational::Builder(2,3).isNumberZero()); + quiz_assert(Rational::Builder(0,1).isNumberZero()); + quiz_assert(!Symbol::Builder('a').isNumberZero()); + quiz_assert(!Multiplication::Builder(Rational::Builder(1), Rational::Builder(0)).isNumberZero()); + quiz_assert(!Addition::Builder(Rational::Builder(1), Rational::Builder(-1)).isNumberZero()); +} + QUIZ_CASE(poincare_properties_is_random) { quiz_assert(Random::Builder().isRandom()); quiz_assert(Randint::Builder(Rational::Builder(1), Rational::Builder(2)).isRandom()); From 4a3f749cc68f1e61cfef67003c3dd03fd8a3f467 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 23 Oct 2020 12:04:35 +0200 Subject: [PATCH 361/560] [poincare] Add NullStatus for expressions Change-Id: Ibaba72e3e3589ba259c7b22d402e2b27937f27c1 --- .../matrix_list_controller.cpp | 2 +- apps/solver/equation_store.cpp | 12 ++++--- poincare/include/poincare/based_integer.h | 2 +- poincare/include/poincare/constant.h | 1 + poincare/include/poincare/decimal.h | 2 +- poincare/include/poincare/expression.h | 3 +- poincare/include/poincare/expression_node.h | 7 +++- poincare/include/poincare/float.h | 2 +- .../include/poincare/hyperbolic_arc_cosine.h | 2 +- .../hyperbolic_trigonometric_function.h | 2 +- poincare/include/poincare/infinity.h | 1 + poincare/include/poincare/rational.h | 2 +- poincare/src/complex_cartesian.cpp | 13 +++---- .../src/hyperbolic_trigonometric_function.cpp | 2 +- poincare/src/matrix.cpp | 8 +++-- poincare/src/multiplication.cpp | 12 +++---- poincare/src/power.cpp | 2 +- poincare/test/expression_properties.cpp | 35 ++++++++++--------- 18 files changed, 62 insertions(+), 48 deletions(-) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 5b4f3051da7..803fb69ce14 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -51,7 +51,7 @@ void MatrixListController::setExpression(Poincare::Expression e) { 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.isNumberZero()) { + if (!determinant.isUndefined() && determinant.nullStatus(context) != ExpressionNode::NullStatus::Null) { m_indexMessageMap[index] = messageIndex++; m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences); } diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 86f38fefb95..e5865a19a74 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -316,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).isNumberZero()) { + if (Ab.matrixChild(j, i).nullStatus(context) != ExpressionNode::NullStatus::Null) { rowWithNullCoefficients = false; break; } } - if (rowWithNullCoefficients && !Ab.matrixChild(j, n).isNumberZero()) { + if (rowWithNullCoefficients && Ab.matrixChild(j, n).nullStatus(context) != ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown m_numberOfSolutions = 0; } } @@ -348,11 +349,12 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } - if (delta.isNumberZero()) { + 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) @@ -383,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->isNumberZero()) { - if (delta0->isNumberZero()) { + 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/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index ead8a0f14e0..f553902c11e 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -28,7 +28,7 @@ class BasedIntegerNode final : public NumberNode { // Expression subclassing Type type() const override { return Type::BasedInteger; } Sign sign(Context * context) const override { return Sign::Positive; } - bool isNumberZero() const override { return integer().isZero(); } + NullStatus nullStatus(Context * context) const override { return integer().isZero() ? NullStatus::Null : NullStatus::NonNull; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index 59d1a4d48ae..ed04a922c1c 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -28,6 +28,7 @@ class ConstantNode final : public SymbolAbstractNode { // 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; diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index 317f2f3552a..6f9267efd74 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -42,8 +42,8 @@ class DecimalNode final : public NumberNode { // 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; - bool isNumberZero() const override { return unsignedMantissa().isZero(); } // Approximation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index f20d4ddeb16..dff4782edd1 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -151,9 +151,10 @@ class Expression : public TreeHandle { 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 isNumberZero() const { return node()->isNumberZero(); } bool isRationalOne() const; bool isRandom() const { return node()->isRandom(); } bool isParameteredExpression() const { return node()->isParameteredExpression(); } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 3147929e8b6..c971e28b78c 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -156,6 +156,11 @@ class ExpressionNode : public TreeNode { Unknown = 0, Positive = 1 }; + enum class NullStatus { + Unknown = -1, + NonNull = 0, + Null = 1, + }; class ReductionContext { public: @@ -186,8 +191,8 @@ class ExpressionNode : public TreeNode { }; 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 isNumberZero() const { return false; } virtual bool isRandom() const { return false; } virtual bool isParameteredExpression() const { return false; } /* childAtIndexNeedsUserParentheses checks if parentheses are required by mathematical rules: diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 31695ac4a70..d1eda088732 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -38,9 +38,9 @@ class FloatNode final : public NumberNode { // Properties 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; - bool isNumberZero() const override { return m_value == 0.0; } // Layout int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/hyperbolic_arc_cosine.h b/poincare/include/poincare/hyperbolic_arc_cosine.h index ac0a6aee885..67855d9079c 100644 --- a/poincare/include/poincare/hyperbolic_arc_cosine.h +++ b/poincare/include/poincare/hyperbolic_arc_cosine.h @@ -21,7 +21,7 @@ class HyperbolicArcCosineNode final : public HyperbolicTrigonometricFunctionNode 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; diff --git a/poincare/include/poincare/hyperbolic_trigonometric_function.h b/poincare/include/poincare/hyperbolic_trigonometric_function.h index 176c9e83433..10828793eac 100644 --- a/poincare/include/poincare/hyperbolic_trigonometric_function.h +++ b/poincare/include/poincare/hyperbolic_trigonometric_function.h @@ -17,7 +17,7 @@ class HyperbolicTrigonometricFunctionNode : public ExpressionNode { 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.isNumberZero(); } + 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/infinity.h b/poincare/include/poincare/infinity.h index 2d434f266b8..a1e8ec5bfdf 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -23,6 +23,7 @@ class InfinityNode final : public NumberNode { // 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 diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index f9c73d3004d..abde3b3e0cc 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -17,7 +17,7 @@ class RationalNode final : public NumberNode { bool isNegative() const { return m_negative; } void setNegative(bool negative) { m_negative = negative; } bool isInteger() const { return denominator().isOne(); } - bool isNumberZero() const override { return isZero(); } + NullStatus nullStatus(Context * context) const override { return isZero() ? NullStatus::Null : NullStatus::NonNull; } // TreeNode size_t size() const override; diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index 486e866f181..df7cea0c576 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -60,7 +60,7 @@ Expression ComplexCartesian::shallowReduce() { return e; } } - if (imag().isNumberZero()) { + if (imag().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { Expression r = real(); replaceWithInPlace(r); return r; @@ -128,9 +128,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().isNumberZero()) { + if (imag().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { a = real(); - } else if (real().isNumberZero()) { + } else if (real().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { a = imag(); } if (!a.isUninitialized()) { @@ -149,7 +149,8 @@ Expression ComplexCartesian::norm(ExpressionNode::ReductionContext reductionCont Expression ComplexCartesian::argument(ExpressionNode::ReductionContext reductionContext) { Expression a = real(); Expression b = imag(); - if (!b.isNumberZero()) { + 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 +243,11 @@ ComplexCartesian ComplexCartesian::powerInteger(int n, ExpressionNode::Reduction Expression a = real(); Expression b = imag(); assert(n > 0); - assert(!b.isNumberZero()); + 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.isNumberZero()) { + 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/hyperbolic_trigonometric_function.cpp b/poincare/src/hyperbolic_trigonometric_function.cpp index 676d6a72417..6ed6d6baaa5 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; diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 17e0054f965..1c7fea1d3ae 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -136,7 +136,8 @@ int Matrix::rank(Context * context, Preferences::ComplexFormat complexFormat, Pr int i = rank-1; while (i >= 0) { int j = m.numberOfColumns()-1; - while (j >= i && matrixChild(i,j).isNumberZero()) { + while (j >= i && matrixChild(i,j).nullStatus(context) == ExpressionNode::NullStatus::Null) { + // TODO: Handle ExpressionNode::NullStatus::Unknown j--; } if (j == i-1) { @@ -225,7 +226,7 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex // 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()); // Handle very low pivots - if (pivot == 0.0f && !matrixChild(iPivot_temp, k).isNumberZero()) { + if (pivot == 0.0f && matrixChild(iPivot_temp, k).nullStatus(reductionContext.context()) != ExpressionNode::NullStatus::Null) { pivot = FLT_MIN; } @@ -241,7 +242,8 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex } iPivot_temp++; } - if (matrixChild(iPivot, k).isNumberZero()) { + 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) { diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index fc699cff9c4..f0d07307af1 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -1122,27 +1122,27 @@ bool Multiplication::TermsCanSafelyCombineExponents(const Expression & e1, const Expression base = Base(e1); ExpressionNode::Sign baseSign = base.sign(reductionContext.context()); + ExpressionNode::NullStatus baseNullStatus = base.nullStatus(reductionContext.context()); - if (baseSign != ExpressionNode::Sign::Unknown && !base.isNumberZero()) { + if (baseSign != ExpressionNode::Sign::Unknown && baseNullStatus == ExpressionNode::NullStatus::NonNull) { // x cannot be null return true; } Expression exponent1 = CreateExponent(e1); - ExpressionNode::Sign exponentSign1 = exponent1.sign(reductionContext.context()); Expression exponent2 = CreateExponent(e2); - ExpressionNode::Sign exponentSign2 = exponent2.sign(reductionContext.context()); - if (exponentSign1 == ExpressionNode::Sign::Positive && !exponent1.isNumberZero() - && exponentSign2 == ExpressionNode::Sign::Positive && !exponent2.isNumberZero()) { + 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 || sum.isNumberZero()) { + if (sumSign == ExpressionNode::Sign::Negative || sumNullStatus == ExpressionNode::NullStatus::Null) { // a+b is negative or null return true; } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 55c3b8bb09a..a259bd46e5e 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -501,7 +501,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex // x^0 if (rationalIndex.isZero()) { // 0^0 = undef or (±inf)^0 = undef - if (base.isNumberZero() || baseType == ExpressionNode::Type::Infinity) { + if (base.nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null || baseType == ExpressionNode::Type::Infinity) { return replaceWithUndefinedInPlace(); } // x^0 diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 0222d22c091..379775b2f5d 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -18,23 +18,24 @@ QUIZ_CASE(poincare_properties_is_number) { } QUIZ_CASE(poincare_properties_is_number_zero) { - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Binary).isNumberZero()); - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Decimal).isNumberZero()); - quiz_assert(!BasedInteger::Builder("2",Integer::Base::Hexadecimal).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Binary).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Decimal).isNumberZero()); - quiz_assert(BasedInteger::Builder("0",Integer::Base::Hexadecimal).isNumberZero()); - quiz_assert(!Decimal::Builder("2",3).isNumberZero()); - quiz_assert(Decimal::Builder("0",0).isNumberZero()); - quiz_assert(!Float::Builder(1.0f).isNumberZero()); - quiz_assert(Float::Builder(0.0f).isNumberZero()); - quiz_assert(!Infinity::Builder(true).isNumberZero()); - quiz_assert(!Undefined::Builder().isNumberZero()); - quiz_assert(!Rational::Builder(2,3).isNumberZero()); - quiz_assert(Rational::Builder(0,1).isNumberZero()); - quiz_assert(!Symbol::Builder('a').isNumberZero()); - quiz_assert(!Multiplication::Builder(Rational::Builder(1), Rational::Builder(0)).isNumberZero()); - quiz_assert(!Addition::Builder(Rational::Builder(1), Rational::Builder(-1)).isNumberZero()); + 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_CASE(poincare_properties_is_random) { From 61d33be2a7d1eda3594d6405a3a07f65a68a46ff Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 23 Oct 2020 15:34:54 +0200 Subject: [PATCH 362/560] [poincare] Add NullStatus and sign for more expressions Change-Id: I4443a28532f5b728afe169a3d2a70d9026bc1909 --- poincare/include/poincare/absolute_value.h | 1 + poincare/include/poincare/arc_cosine.h | 1 + poincare/include/poincare/arc_sine.h | 2 + poincare/include/poincare/arc_tangent.h | 2 + poincare/include/poincare/ceiling.h | 1 + poincare/include/poincare/complex_cartesian.h | 2 + poincare/include/poincare/conjugate.h | 2 + .../include/poincare/division_remainder.h | 1 + poincare/include/poincare/factor.h | 2 + poincare/include/poincare/factorial.h | 1 + poincare/include/poincare/floor.h | 1 + poincare/include/poincare/frac_part.h | 3 +- .../include/poincare/great_common_divisor.h | 1 + poincare/include/poincare/imaginary_part.h | 2 +- .../include/poincare/least_common_multiple.h | 1 + poincare/include/poincare/opposite.h | 1 + poincare/include/poincare/parenthesis.h | 2 + .../include/poincare/permute_coefficient.h | 2 +- poincare/include/poincare/real_part.h | 2 + poincare/include/poincare/round.h | 1 + poincare/include/poincare/sign_function.h | 3 +- poincare/include/poincare/unit.h | 3 +- poincare/include/poincare/vector_norm.h | 1 + poincare/src/complex_cartesian.cpp | 9 +++++ poincare/src/sign_function.cpp | 4 -- poincare/src/unit.cpp | 4 -- poincare/test/expression_properties.cpp | 38 +++++++++++++++++++ 27 files changed, 79 insertions(+), 14 deletions(-) diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index bdbfc37d579..6e841e7a76c 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -21,6 +21,7 @@ class AbsoluteValueNode final : public ExpressionNode { // 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 diff --git a/poincare/include/poincare/arc_cosine.h b/poincare/include/poincare/arc_cosine.h index 43bac523ff3..389ac4c7982 100644 --- a/poincare/include/poincare/arc_cosine.h +++ b/poincare/include/poincare/arc_cosine.h @@ -20,6 +20,7 @@ class ArcCosineNode final : public ExpressionNode { #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: diff --git a/poincare/include/poincare/arc_sine.h b/poincare/include/poincare/arc_sine.h index 2747bda139a..a567d55a1a4 100644 --- a/poincare/include/poincare/arc_sine.h +++ b/poincare/include/poincare/arc_sine.h @@ -20,6 +20,8 @@ class ArcSineNode final : public ExpressionNode { #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 diff --git a/poincare/include/poincare/arc_tangent.h b/poincare/include/poincare/arc_tangent.h index 47b6f56384f..44c0ff887e1 100644 --- a/poincare/include/poincare/arc_tangent.h +++ b/poincare/include/poincare/arc_tangent.h @@ -20,6 +20,8 @@ class ArcTangentNode final : public ExpressionNode { #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: diff --git a/poincare/include/poincare/ceiling.h b/poincare/include/poincare/ceiling.h index ff1bc5a108d..a8589770645 100644 --- a/poincare/include/poincare/ceiling.h +++ b/poincare/include/poincare/ceiling.h @@ -19,6 +19,7 @@ class CeilingNode final : public ExpressionNode { #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Ceiling; } private: // Layout diff --git a/poincare/include/poincare/complex_cartesian.h b/poincare/include/poincare/complex_cartesian.h index f9a71c5fdb5..7d973048207 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -19,6 +19,8 @@ class ComplexCartesianNode : public ExpressionNode { #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 diff --git a/poincare/include/poincare/conjugate.h b/poincare/include/poincare/conjugate.h index 61b442fc5c3..e2f90f17067 100644 --- a/poincare/include/poincare/conjugate.h +++ b/poincare/include/poincare/conjugate.h @@ -19,6 +19,8 @@ class ConjugateNode /*final*/ : public ExpressionNode { #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 diff --git a/poincare/include/poincare/division_remainder.h b/poincare/include/poincare/division_remainder.h index 3794c370d54..e8262c7a2b8 100644 --- a/poincare/include/poincare/division_remainder.h +++ b/poincare/include/poincare/division_remainder.h @@ -20,6 +20,7 @@ class DivisionRemainderNode final : public ExpressionNode { #endif // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::DivisionRemainder; } diff --git a/poincare/include/poincare/factor.h b/poincare/include/poincare/factor.h index 39e1ce4d55e..329fe190268 100644 --- a/poincare/include/poincare/factor.h +++ b/poincare/include/poincare/factor.h @@ -18,6 +18,8 @@ class FactorNode /*final*/ : public ExpressionNode { 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 */ diff --git a/poincare/include/poincare/factorial.h b/poincare/include/poincare/factorial.h index 212a94c57f1..2f39b20621d 100644 --- a/poincare/include/poincare/factorial.h +++ b/poincare/include/poincare/factorial.h @@ -19,6 +19,7 @@ class FactorialNode final : public ExpressionNode { #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; diff --git a/poincare/include/poincare/floor.h b/poincare/include/poincare/floor.h index 65115adaae4..5d360389f34 100644 --- a/poincare/include/poincare/floor.h +++ b/poincare/include/poincare/floor.h @@ -19,6 +19,7 @@ class FloorNode /*final*/ : public ExpressionNode { #endif // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Floor; } private: diff --git a/poincare/include/poincare/frac_part.h b/poincare/include/poincare/frac_part.h index 0158bb9e71c..54e95817e7f 100644 --- a/poincare/include/poincare/frac_part.h +++ b/poincare/include/poincare/frac_part.h @@ -19,10 +19,9 @@ class FracPartNode final : public ExpressionNode { #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; diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index d5a6c4ae3cc..09ef262bab0 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -17,6 +17,7 @@ class GreatCommonDivisorNode final : public NAryExpressionNode { #endif // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::GreatCommonDivisor; } private: diff --git a/poincare/include/poincare/imaginary_part.h b/poincare/include/poincare/imaginary_part.h index 345ddbc500a..6fd7209ffd1 100644 --- a/poincare/include/poincare/imaginary_part.h +++ b/poincare/include/poincare/imaginary_part.h @@ -19,9 +19,9 @@ class ImaginaryPartNode final : public ExpressionNode { #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; diff --git a/poincare/include/poincare/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index 1feda8cb7bd..fe0e15a8e4c 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -17,6 +17,7 @@ class LeastCommonMultipleNode final : public NAryExpressionNode { #endif // ExpressionNode + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::LeastCommonMultiple; } private: diff --git a/poincare/include/poincare/opposite.h b/poincare/include/poincare/opposite.h index c4fc891667b..de8e0996fd7 100644 --- a/poincare/include/poincare/opposite.h +++ b/poincare/include/poincare/opposite.h @@ -23,6 +23,7 @@ class OppositeNode /*final*/ : public ExpressionNode { #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; diff --git a/poincare/include/poincare/parenthesis.h b/poincare/include/poincare/parenthesis.h index 66fe39ca29a..bf1fcae893b 100644 --- a/poincare/include/poincare/parenthesis.h +++ b/poincare/include/poincare/parenthesis.h @@ -19,6 +19,8 @@ class ParenthesisNode /*final*/ : public ExpressionNode { #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); } diff --git a/poincare/include/poincare/permute_coefficient.h b/poincare/include/poincare/permute_coefficient.h index 1bc05924b68..5ebfb0032fa 100644 --- a/poincare/include/poincare/permute_coefficient.h +++ b/poincare/include/poincare/permute_coefficient.h @@ -22,7 +22,7 @@ class PermuteCoefficientNode final : public ExpressionNode { // Properties Type type() const override{ return Type::PermuteCoefficient; } - + Sign sign(Context * context) const override { return Sign::Positive; } private: // Layout diff --git a/poincare/include/poincare/real_part.h b/poincare/include/poincare/real_part.h index ddcbd24a70f..a35db4b9491 100644 --- a/poincare/include/poincare/real_part.h +++ b/poincare/include/poincare/real_part.h @@ -19,6 +19,8 @@ class RealPartNode final : public ExpressionNode { #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; } diff --git a/poincare/include/poincare/round.h b/poincare/include/poincare/round.h index bb4a769aa74..1d4ec886ba6 100644 --- a/poincare/include/poincare/round.h +++ b/poincare/include/poincare/round.h @@ -20,6 +20,7 @@ class RoundNode final : public ExpressionNode { // Properties + Sign sign(Context * context) const override { return childAtIndex(0)->sign(context); } Type type() const override { return Type::Round; } private: // Layout diff --git a/poincare/include/poincare/sign_function.h b/poincare/include/poincare/sign_function.h index d5379058a4c..dbb590c3550 100644 --- a/poincare/include/poincare/sign_function.h +++ b/poincare/include/poincare/sign_function.h @@ -20,7 +20,8 @@ class SignFunctionNode final : public ExpressionNode { // 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; diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index c44138a4153..2437e83d43b 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -456,7 +456,8 @@ class UnitNode final : public ExpressionNode { // 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 */ diff --git a/poincare/include/poincare/vector_norm.h b/poincare/include/poincare/vector_norm.h index c13a6a1d87c..3d2da252a1a 100644 --- a/poincare/include/poincare/vector_norm.h +++ b/poincare/include/poincare/vector_norm.h @@ -18,6 +18,7 @@ class VectorNormNode final : public ExpressionNode { #endif // Properties + Sign sign(Context * context) const override { return Sign::Positive; } Type type() const override { return Type::VectorNorm; } private: // Layout diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index df7cea0c576..b02cd4199c5 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -19,6 +19,15 @@ namespace Poincare { +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::shallowReduce(ReductionContext reductionContext) { return ComplexCartesian(this).shallowReduce(); } diff --git a/poincare/src/sign_function.cpp b/poincare/src/sign_function.cpp index ba476541362..335c02543ac 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); diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 44649c45041..d6d355da789 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -653,10 +653,6 @@ int UnitNode::SpeedRepresentative::setAdditionalExpressions(double value, Expres } // UnitNode -ExpressionNode::Sign UnitNode::sign(Context * context) const { - return Sign::Positive; -} - Expression UnitNode::removeUnit(Expression * unit) { return Unit(this).removeUnit(unit); } diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 379775b2f5d..2f83a5c7d5b 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -36,6 +36,19 @@ QUIZ_CASE(poincare_properties_is_number_zero) { 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_CASE(poincare_properties_is_random) { @@ -154,6 +167,31 @@ 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(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); From 42f20fb58d466c26190690b917ebb38590817d13 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 23 Oct 2020 16:14:54 +0200 Subject: [PATCH 363/560] [poincare] Add NullStatus and sign for more complex expressions Change-Id: Ic593517bf7a983985fe3c521e10c19ab0bca4191 --- poincare/include/poincare/division.h | 5 +++++ poincare/include/poincare/division_quotient.h | 1 + poincare/include/poincare/power.h | 4 ++++ poincare/include/poincare/square_root.h | 2 ++ poincare/src/division.cpp | 9 +++++++++ poincare/src/division_quotient.cpp | 9 +++++++++ poincare/test/expression_properties.cpp | 5 +++++ 7 files changed, 35 insertions(+) diff --git a/poincare/include/poincare/division.h b/poincare/include/poincare/division.h index 4b924844db0..b48a4d883a0 100644 --- a/poincare/include/poincare/division.h +++ b/poincare/include/poincare/division.h @@ -23,6 +23,11 @@ template #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); } diff --git a/poincare/include/poincare/division_quotient.h b/poincare/include/poincare/division_quotient.h index e55fcec6543..2db2f95dba3 100644 --- a/poincare/include/poincare/division_quotient.h +++ b/poincare/include/poincare/division_quotient.h @@ -19,6 +19,7 @@ class DivisionQuotientNode final : public ExpressionNode { #endif // ExpressionNode + Sign sign(Context * context) const override; Type type() const override { return Type::DivisionQuotient; } // Simplification diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index 0363718a2d1..e8f7f384bbd 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -27,6 +27,10 @@ class PowerNode final : public ExpressionNode { // 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; diff --git a/poincare/include/poincare/square_root.h b/poincare/include/poincare/square_root.h index 6a46aee29e0..a6f9a69d157 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 diff --git a/poincare/src/division.cpp b/poincare/src/division.cpp index 77ff4c8250e..488d3f23a52 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 dfbc04c3fba..f081031de5f 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()); } diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 2f83a5c7d5b..0fdc1e49309 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -49,6 +49,9 @@ QUIZ_CASE(poincare_properties_is_number_zero) { 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) { @@ -189,6 +192,8 @@ QUIZ_CASE(poincare_properties_expression_sign) { 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); } From 6ba88921b917fa0c5084d605145f70eebc40cb16 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 27 Oct 2020 11:30:09 +0100 Subject: [PATCH 364/560] [apps/calculation] Add Unknown NullStatus Todo Change-Id: Ic67eca0b95350c72167057c40f18ae969793ef18 --- apps/calculation/additional_outputs/matrix_list_controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/calculation/additional_outputs/matrix_list_controller.cpp b/apps/calculation/additional_outputs/matrix_list_controller.cpp index 803fb69ce14..a8bf46559a8 100644 --- a/apps/calculation/additional_outputs/matrix_list_controller.cpp +++ b/apps/calculation/additional_outputs/matrix_list_controller.cpp @@ -52,6 +52,7 @@ void MatrixListController::setExpression(Poincare::Expression e) { // 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); } From b9c34ace3ac9778c1d6d9c725ce8e9da0833fb58 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 11 Sep 2020 16:21:55 +0200 Subject: [PATCH 365/560] [ion/clipboard] Use system clipboard on simulator Add methods to interface Epsilon's clipboard with the system's when running a simulator. Note that the clipboard still uses a buffer located in the Escher::Clipboard class, as some features require a buffer to execute computations on the clipboard's content. Change-Id: I14c19615805d38735e64d481c617863db22db9bc --- escher/src/clipboard.cpp | 6 ++++++ ion/include/ion.h | 1 + ion/include/ion/clipboard.h | 18 ++++++++++++++++ ion/src/device/shared/drivers/Makefile | 1 + ion/src/device/shared/drivers/clipboard.cpp | 11 ++++++++++ ion/src/simulator/Makefile | 1 + ion/src/simulator/shared/clipboard.cpp | 24 +++++++++++++++++++++ 7 files changed, 62 insertions(+) create mode 100644 ion/include/ion/clipboard.h create mode 100644 ion/src/device/shared/drivers/clipboard.cpp create mode 100644 ion/src/simulator/shared/clipboard.cpp diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index c69c072105a..dc38d2d7020 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -1,5 +1,6 @@ #include #include +#include #include static Clipboard s_clipboard; @@ -10,9 +11,12 @@ 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() { + Ion::Clipboard::read(m_textBuffer, TextField::maxBufferSize()); + /* 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 @@ -39,6 +43,8 @@ const char * Clipboard::storedText() { 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) { diff --git a/ion/include/ion.h b/ion/include/ion.h index e01bc03c1fb..54e3671ce1b 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/ion/include/ion/clipboard.h b/ion/include/ion/clipboard.h new file mode 100644 index 00000000000..92bbe041445 --- /dev/null +++ b/ion/include/ion/clipboard.h @@ -0,0 +1,18 @@ +#ifndef ION_CLIPBOARD_H +#define ION_CLIPBOARD_H + +#include + +namespace Ion { +namespace Clipboard { + +/* Write the text to the system clipboard. */ +void write(const char * text); + +/* Fill the buffer with text from the system clipboard. */ +void read(char * buffer, size_t bufferSize); + +} +} + +#endif diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 548632a8e09..a73470f0e7c 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 00000000000..88e321f9ff7 --- /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) {} +void Clipboard::read(char * buffer, size_t bufferSize) {} + +} diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 10f10fb1ece..473c0c7b32a 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 \ crc32.cpp \ display.cpp:-headless \ diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp new file mode 100644 index 00000000000..e1854683344 --- /dev/null +++ b/ion/src/simulator/shared/clipboard.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +namespace Ion { + +void Clipboard::write(const char * text) { + /* FIXME : Handle the error if need be. */ + SDL_SetClipboardText(text); +} + +void Clipboard::read(char * buffer, size_t bufferSize) { + if (!SDL_HasClipboardText()) { + buffer[0] = '\0'; + return; + } + char * text = SDL_GetClipboardText(); + if (text) { + strlcpy(buffer, text, bufferSize); + SDL_free(text); + } +} + +} From 010b474f77296de6aad7bf43bbc5989b169f2127 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 17 Sep 2020 11:34:23 +0200 Subject: [PATCH 366/560] [ion/clipboard] Add support for web simulator Wrote methods for accessing the system clipboard from the web simulator, as the methods in SDL_Clipboard do not work when the video device uses emscripten. Change-Id: Ib2e66530a6b013eca0cf69fb52372e9e3a21c8bb --- build/toolchain.emscripten.mak | 9 +++ ion/src/simulator/Makefile | 1 - ion/src/simulator/android/Makefile | 1 + ion/src/simulator/ios/Makefile | 1 + ion/src/simulator/linux/Makefile | 1 + ion/src/simulator/macos/Makefile | 1 + ion/src/simulator/shared/clipboard.cpp | 3 + ion/src/simulator/web/Makefile | 1 + ion/src/simulator/web/clipboard.cpp | 87 ++++++++++++++++++++++++++ ion/src/simulator/windows/Makefile | 1 + 10 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 ion/src/simulator/web/clipboard.cpp diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index 5ccc9b7b99e..ac6116fe5bd 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -77,6 +77,7 @@ _closure_call \ _do_load \ _do_load_from_lexer \ _emscripten_sleep \ +_emscripten_sleep_with_yield \ _fun_bc_call \ _fun_builtin_1_call \ _fun_builtin_var_call \ @@ -103,6 +104,14 @@ _mp_execute_bytecode \ _mp_hal_input \ _mp_import_name \ _mp_parse_compile_execute \ +_get_clipboard_text \ +__ZN3Ion9Clipboard4readEPcm \ +__ZN9Clipboard10storedTextEv \ +__ZN11LayoutField18privateHandleEventEN3Ion6Events5EventE \ +__ZN11LayoutField11handleEventEN3Ion6Events5EventE \ +__ZN8TextArea18privateHandleEventEN3Ion6Events5EventE \ +__ZN8TextArea11handleEventEN3Ion6Events5EventE \ +__ZN15ExpressionField11handleEventEN3Ion6Events5EventE \ _msleep EMTERPRETIFY_WHITELIST = $(foreach sym,$(EMSCRIPTEN_ASYNC_SYMBOLS),"$(sym)",)END diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 473c0c7b32a..10f10fb1ece 100644 --- a/ion/src/simulator/Makefile +++ b/ion/src/simulator/Makefile @@ -8,7 +8,6 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/serial_number.cpp \ dummy/stack.cpp \ dummy/usb.cpp \ - clipboard.cpp \ console_stdio.cpp:-consoledisplay \ crc32.cpp \ display.cpp:-headless \ diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index 2fb97edbb53..210130d8afe 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.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index e3496ac8019..7a52d972b6c 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.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 25668574509..304068aad94 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.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 8171ffff202..7b90d0525fb 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.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ haptics.cpp \ diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp index e1854683344..550b18d9d75 100644 --- a/ion/src/simulator/shared/clipboard.cpp +++ b/ion/src/simulator/shared/clipboard.cpp @@ -2,6 +2,9 @@ #include #include +/* This file implements the methods to access the system clipboard on all + * targets but the web simulator. */ + namespace Ion { void Clipboard::write(const char * text) { diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index e8c7293717b..dcfcb44d888 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.cpp \ helpers.cpp \ ) diff --git a/ion/src/simulator/web/clipboard.cpp b/ion/src/simulator/web/clipboard.cpp new file mode 100644 index 00000000000..1775e76edfb --- /dev/null +++ b/ion/src/simulator/web/clipboard.cpp @@ -0,0 +1,87 @@ +#include +#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 API. + * However, we still call the SDL_Clipboard methods as a fallback to preserve + * the copy-paste feature in case of calls to set_clipboard_text and + * get_clipboard_text failing. */ + +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 { + +void Clipboard::write(const char * text) { + /* FIXME : Handle the error if need be. */ + /* 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); + /* Store a local copy of the text in case the browser does not grant access + * to the clipboard. */ + SDL_SetClipboardText(text); +} + +void Clipboard::read(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; + } + /* If the browser does not grant access to the clipboard, read from a local + * copy to at least maintain the basic copy-paste functionnality. */ + if (!SDL_HasClipboardText()) { + buffer[0] = '\0'; + return; + } + char * text = SDL_GetClipboardText(); + if (text) { + strlcpy(buffer, text, bufferSize); + SDL_free(text); + } +} + +} diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index a057924f591..5c23860f9c6 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.cpp \ haptics.cpp \ ) From f66bde6d31f9ca289c8b40905bc72779dfd216fe Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 26 Oct 2020 18:06:16 +0100 Subject: [PATCH 367/560] [ion/clipboard] Rework clipboard handling Change the way the clipboard is fetched, so as to preserve the Poincare/Python translation feature, and remove the cap on the length of text being imported into the simulator. Change-Id: I0f3e83c0c8aa4b64eb08d882aa6891b31f191e22 --- build/toolchain.emscripten.mak | 5 +++ escher/src/clipboard.cpp | 5 ++- ion/include/ion/clipboard.h | 7 ++-- ion/src/device/shared/drivers/clipboard.cpp | 2 +- ion/src/simulator/Makefile | 1 + ion/src/simulator/android/Makefile | 2 +- ion/src/simulator/ios/Makefile | 2 +- ion/src/simulator/linux/Makefile | 2 +- ion/src/simulator/macos/Makefile | 2 +- ion/src/simulator/shared/clipboard.cpp | 34 +++++++++++-------- ion/src/simulator/shared/clipboard_helper.cpp | 28 +++++++++++++++ ion/src/simulator/shared/clipboard_helper.h | 15 ++++++++ ion/src/simulator/web/Makefile | 2 +- .../{clipboard.cpp => clipboard_helper.cpp} | 23 +++---------- ion/src/simulator/windows/Makefile | 2 +- 15 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 ion/src/simulator/shared/clipboard_helper.cpp create mode 100644 ion/src/simulator/shared/clipboard_helper.h rename ion/src/simulator/web/{clipboard.cpp => clipboard_helper.cpp} (77%) diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index ac6116fe5bd..5de7d76cf7a 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -105,7 +105,12 @@ _mp_hal_input \ _mp_import_name \ _mp_parse_compile_execute \ _get_clipboard_text \ +_set_clipboard_text \ +__ZN3Ion9Clipboard24fetchFromSystemClipboardEPcm \ +__ZN3Ion9Clipboard21sendToSystemClipboardEPKc \ __ZN3Ion9Clipboard4readEPcm \ +__ZN3Ion9Clipboard5writeEPKc \ +__ZN9Clipboard5storeEPKci \ __ZN9Clipboard10storedTextEv \ __ZN11LayoutField18privateHandleEventEN3Ion6Events5EventE \ __ZN11LayoutField11handleEventEN3Ion6Events5EventE \ diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index dc38d2d7020..2dd6e54ab21 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -15,7 +15,10 @@ void Clipboard::store(const char * storedText, int length) { } const char * Clipboard::storedText() { - Ion::Clipboard::read(m_textBuffer, TextField::maxBufferSize()); + 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 diff --git a/ion/include/ion/clipboard.h b/ion/include/ion/clipboard.h index 92bbe041445..c60b9ece6a7 100644 --- a/ion/include/ion/clipboard.h +++ b/ion/include/ion/clipboard.h @@ -1,16 +1,15 @@ #ifndef ION_CLIPBOARD_H #define ION_CLIPBOARD_H -#include - namespace Ion { namespace Clipboard { /* Write the text to the system clipboard. */ void write(const char * text); -/* Fill the buffer with text from the system clipboard. */ -void read(char * buffer, size_t bufferSize); +/* Returns the system's clipboard text if it differs from the text previously + * copied, and nullptr otherwise. */ +const char * read(); } } diff --git a/ion/src/device/shared/drivers/clipboard.cpp b/ion/src/device/shared/drivers/clipboard.cpp index 88e321f9ff7..eaccc5bdbce 100644 --- a/ion/src/device/shared/drivers/clipboard.cpp +++ b/ion/src/device/shared/drivers/clipboard.cpp @@ -6,6 +6,6 @@ namespace Ion { * On the device, the clipboard is fully handled by Escher::Clipboard. */ void Clipboard::write(const char * text) {} -void Clipboard::read(char * buffer, size_t bufferSize) {} +const char * Clipboard::read() { return nullptr; } } diff --git a/ion/src/simulator/Makefile b/ion/src/simulator/Makefile index 10f10fb1ece..473c0c7b32a 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 \ crc32.cpp \ display.cpp:-headless \ diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index 210130d8afe..3ccba89a9a2 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -6,7 +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.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/ios/Makefile b/ion/src/simulator/ios/Makefile index 7a52d972b6c..a6b114ad221 100644 --- a/ion/src/simulator/ios/Makefile +++ b/ion/src/simulator/ios/Makefile @@ -6,7 +6,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ - clipboard.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 304068aad94..1ff0ebcff04 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -16,7 +16,7 @@ ion_src += $(addprefix ion/src/simulator/linux/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ - clipboard.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 7b90d0525fb..a465b83c21c 100644 --- a/ion/src/simulator/macos/Makefile +++ b/ion/src/simulator/macos/Makefile @@ -6,7 +6,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \ apple/language.m \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ - clipboard.cpp \ + clipboard_helper.cpp \ collect_registers_x86_64.s \ collect_registers.cpp \ haptics.cpp \ diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp index 550b18d9d75..5f596c1da31 100644 --- a/ion/src/simulator/shared/clipboard.cpp +++ b/ion/src/simulator/shared/clipboard.cpp @@ -1,27 +1,33 @@ #include -#include +#include "clipboard_helper.h" +#include #include -/* This file implements the methods to access the system clipboard on all - * targets but the web simulator. */ - namespace Ion { +namespace Clipboard { + +uint32_t localClipboardVersion; -void Clipboard::write(const char * text) { +void write(const char * text) { /* FIXME : Handle the error if need be. */ - SDL_SetClipboardText(text); + sendToSystemClipboard(text); + localClipboardVersion = crc32Byte(reinterpret_cast(text), strlen(text)); } -void Clipboard::read(char * buffer, size_t bufferSize) { - if (!SDL_HasClipboardText()) { - buffer[0] = '\0'; - return; +const char * read() { + constexpr size_t bufferSize = 32768; + static char buffer[bufferSize]; + fetchFromSystemClipboard(buffer, bufferSize); + if (buffer[0] == '\0') { + return nullptr; } - char * text = SDL_GetClipboardText(); - if (text) { - strlcpy(buffer, text, bufferSize); - SDL_free(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 00000000000..f9231697804 --- /dev/null +++ b/ion/src/simulator/shared/clipboard_helper.cpp @@ -0,0 +1,28 @@ +#include "clipboard_helper.h" +#include +#include + +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 00000000000..529b6bd3ef9 --- /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/web/Makefile b/ion/src/simulator/web/Makefile index dcfcb44d888..4032ef4dfa2 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -15,7 +15,7 @@ LDFLAGS += --pre-js ion/src/simulator/web/preamble_env.js ion_src += $(addprefix ion/src/simulator/web/, \ callback.cpp \ - clipboard.cpp \ + clipboard_helper.cpp \ helpers.cpp \ ) diff --git a/ion/src/simulator/web/clipboard.cpp b/ion/src/simulator/web/clipboard_helper.cpp similarity index 77% rename from ion/src/simulator/web/clipboard.cpp rename to ion/src/simulator/web/clipboard_helper.cpp index 1775e76edfb..c23111087c6 100644 --- a/ion/src/simulator/web/clipboard.cpp +++ b/ion/src/simulator/web/clipboard_helper.cpp @@ -1,4 +1,4 @@ -#include +#include "../shared/clipboard_helper.h" #include #include #include @@ -48,20 +48,17 @@ EM_JS(void, get_clipboard_text, (char * buffer, uint32_t bufferSize, AsyncStatus }); namespace Ion { +namespace Clipboard { -void Clipboard::write(const char * text) { - /* FIXME : Handle the error if need be. */ +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); - /* Store a local copy of the text in case the browser does not grant access - * to the clipboard. */ - SDL_SetClipboardText(text); } -void Clipboard::read(char * buffer, size_t bufferSize) { +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); @@ -71,17 +68,7 @@ void Clipboard::read(char * buffer, size_t bufferSize) { if (lock == AsyncStatus::Success) { return; } - /* If the browser does not grant access to the clipboard, read from a local - * copy to at least maintain the basic copy-paste functionnality. */ - if (!SDL_HasClipboardText()) { - buffer[0] = '\0'; - return; - } - char * text = SDL_GetClipboardText(); - if (text) { - strlcpy(buffer, text, bufferSize); - SDL_free(text); - } } } +} diff --git a/ion/src/simulator/windows/Makefile b/ion/src/simulator/windows/Makefile index 5c23860f9c6..70f6dc4ca65 100644 --- a/ion/src/simulator/windows/Makefile +++ b/ion/src/simulator/windows/Makefile @@ -7,7 +7,7 @@ ion_src += $(addprefix ion/src/simulator/windows/, \ ion_src += $(addprefix ion/src/simulator/shared/, \ dummy/callback.cpp \ dummy/haptics_enabled.cpp \ - clipboard.cpp \ + clipboard_helper.cpp \ haptics.cpp \ ) From 8bb0b47b9f3b22c5cf14842bffd403b33afdf454 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 27 Oct 2020 14:12:15 +0100 Subject: [PATCH 368/560] [ion/clipboard] Update comments Change-Id: I320f8130b7bc897b6e8e3bf7d79cfcda837a9984 --- ion/src/simulator/shared/clipboard.cpp | 5 ++++- ion/src/simulator/shared/clipboard_helper.cpp | 2 ++ ion/src/simulator/web/clipboard_helper.cpp | 21 +++++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp index 5f596c1da31..4acace06015 100644 --- a/ion/src/simulator/shared/clipboard.cpp +++ b/ion/src/simulator/shared/clipboard.cpp @@ -15,7 +15,10 @@ void write(const char * text) { } const char * read() { - constexpr size_t bufferSize = 32768; + /* The buffer's size is chosen to be around the size of a typical large + * python script, allowing the user the 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') { diff --git a/ion/src/simulator/shared/clipboard_helper.cpp b/ion/src/simulator/shared/clipboard_helper.cpp index f9231697804..dfe185c93f4 100644 --- a/ion/src/simulator/shared/clipboard_helper.cpp +++ b/ion/src/simulator/shared/clipboard_helper.cpp @@ -2,6 +2,8 @@ #include #include +/* This implementation is used for all targets but the web simulator. */ + namespace Ion { namespace Clipboard { diff --git a/ion/src/simulator/web/clipboard_helper.cpp b/ion/src/simulator/web/clipboard_helper.cpp index c23111087c6..cb38c85e969 100644 --- a/ion/src/simulator/web/clipboard_helper.cpp +++ b/ion/src/simulator/web/clipboard_helper.cpp @@ -14,10 +14,23 @@ enum class AsyncStatus : uint32_t { /* 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 API. - * However, we still call the SDL_Clipboard methods as a fallback to preserve - * the copy-paste feature in case of calls to set_clipboard_text and - * get_clipboard_text failing. */ + * 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 { From eb5f4b333ade0c69868c54e13a4d407a7b69b2a0 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 28 Oct 2020 16:15:07 +0100 Subject: [PATCH 369/560] [ion/clipboard] Update comments 2 Change-Id: Ib628a888f6712ad6ea62ccb20192845d19e84986 --- ion/src/simulator/shared/clipboard.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ion/src/simulator/shared/clipboard.cpp b/ion/src/simulator/shared/clipboard.cpp index 4acace06015..896def716db 100644 --- a/ion/src/simulator/shared/clipboard.cpp +++ b/ion/src/simulator/shared/clipboard.cpp @@ -15,8 +15,8 @@ void write(const char * text) { } const char * read() { - /* The buffer's size is chosen to be around the size of a typical large - * python script, allowing the user the insert most scripts into the + /* 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]; @@ -25,6 +25,10 @@ const char * read() { 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; From aa50a5eb7c4827a3ebe56667d4d8354a6850627d Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 28 Oct 2020 15:49:35 +0100 Subject: [PATCH 370/560] [apps/shared] Silence warning for unused variable Change-Id: I1df4a8c111688e00a82d4448d47fd571fec738e4 --- apps/shared/interactive_curve_view_range.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 18da1b8e181..ce505385476 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -158,6 +158,7 @@ void InteractiveCurveViewRange::normalize() { * properly normalize. */ constexpr float limit = 1e7f; assert(isOrthonormal() || xMin() < -limit || xMax() > limit || yMin() < -limit || yMax() > limit); + (void) limit; // Silence compilation warning about unused variable. setZoomNormalize(isOrthonormal()); } From f789bb156d6f8122a24ccf3e4bcfdd24a4220a52 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 28 Oct 2020 15:50:42 +0100 Subject: [PATCH 371/560] [ion/keyboard] Fix const const array Change-Id: I75cfc94ed36493a78bd104f8c4734f2ecd3d042d --- ion/include/ion/keyboard/layout_events.h | 2 +- ion/src/shared/keyboard/layout_B2/layout_events.cpp | 2 +- ion/src/shared/keyboard/layout_B3/layout_events.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/include/ion/keyboard/layout_events.h b/ion/include/ion/keyboard/layout_events.h index 21cdaeed038..978752a8c43 100644 --- a/ion/include/ion/keyboard/layout_events.h +++ b/ion/include/ion/keyboard/layout_events.h @@ -11,7 +11,7 @@ namespace Events { extern const EventData s_dataForEvent[4*Event::PageSize]; #ifndef NDEBUG -extern const const char * s_nameForEvent[255]; +extern const char * const s_nameForEvent[255]; inline const char * Event::name() const { return s_nameForEvent[m_id]; diff --git a/ion/src/shared/keyboard/layout_B2/layout_events.cpp b/ion/src/shared/keyboard/layout_B2/layout_events.cpp index 24518533e43..5a6d4f25cde 100644 --- a/ion/src/shared/keyboard/layout_B2/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -49,7 +49,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { #ifndef NDEBUG -const char * s_nameForEvent[255] = { +const char * const s_nameForEvent[255] = { // Plain "Left", "Up", "Down", "Right", "OK", "Back", "Home", "OnOff", nullptr, nullptr, nullptr, nullptr, diff --git a/ion/src/shared/keyboard/layout_B3/layout_events.cpp b/ion/src/shared/keyboard/layout_B3/layout_events.cpp index 6133c57874b..deff7ca0143 100644 --- a/ion/src/shared/keyboard/layout_B3/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B3/layout_events.cpp @@ -49,7 +49,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { #ifndef NDEBUG -const char * s_nameForEvent[255] = { +const char * const s_nameForEvent[255] = { // Plain "Left", "Up", "Down", "Right", "OK", "Back", "Home", "nullptr", "OnOff", nullptr, nullptr, nullptr, From ad7890a19c5c80b208230afa8b433b7ed33f40a2 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 29 Oct 2020 10:13:46 +0100 Subject: [PATCH 372/560] [interactive_curve_view_range] Missing refresh Fixes the following bug : - Plot ln(x) (or any other curve that's normlalized by default) - Go back and replace ln(x) with log(x) (or any other curve that's NOT normalized by default) --> The 'Equal Axes' button is still on. Change-Id: I0ef50dc9866e908894bd4f5c3ade198f9b3ea318 --- apps/shared/interactive_curve_view_range.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index ce505385476..5a6f7491ccf 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -168,8 +168,10 @@ void InteractiveCurveViewRange::setDefault() { } /* If m_zoomNormalize was left active, xGridUnit() would return the value of - * yGridUnit, even if the ranger were not truly normalized. */ - m_zoomNormalize = false; + * 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); From 9e9033537ca0e8fefeb7e58a97be1830f2326989 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 29 Oct 2020 10:21:42 +0100 Subject: [PATCH 373/560] [WIP][interactive_curve_view_range] Tweak tolerance Change-Id: Iacfb7de81a4ab8a7fb73770053e189d596016201 --- apps/shared/interactive_curve_view_range.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index b649ed9a728..a441699862b 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -58,7 +58,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float k_upperMaxFloat = 1E+8f; constexpr static float k_lowerMaxFloat = 9E+7f; constexpr static float k_maxRatioPositionRange = 1E5f; - constexpr static float k_orthonormalTolerance = 0.2f; + /* The tolerance is chosen to normalize sqrt(x) */ + constexpr static float k_orthonormalTolerance = 0.7f; 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. From fe950386a0ecda3361f060e3f9b187657a104ff5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 29 Oct 2020 12:53:26 +0100 Subject: [PATCH 374/560] [interactive_curve_view_range] Fix assert Change-Id: I2ff3cfe90f10b33db3e18b321cf931c9f0946479 --- apps/shared/interactive_curve_view_range.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 5a6f7491ccf..a43cbfe4d69 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -154,9 +154,10 @@ void InteractiveCurveViewRange::normalize() { m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - /* When the coordinates reach 10^7, the float type is not precise enough to + /* When the coordinates reach 10^6, the float type is not precise enough to * properly normalize. */ - constexpr float limit = 1e7f; + // FIXME : Fine a more precise way to filter the edge cases + constexpr float limit = 1e6f; assert(isOrthonormal() || xMin() < -limit || xMax() > limit || yMin() < -limit || yMax() > limit); (void) limit; // Silence compilation warning about unused variable. setZoomNormalize(isOrthonormal()); From 8dc6391e9297092539f8e225ab83c650c3bbf551 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 29 Oct 2020 17:05:24 +0100 Subject: [PATCH 375/560] [apps/shared] Prevent Array index overflow Change-Id: I67ffcb71d0d62a3e67e6d63d2b30b0696800cb42 --- apps/shared/sequence.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index 683aa728851..fbc3d873261 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -233,7 +233,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIn int offset = independentRank - sqctx->independentSequenceRank(i); if (offset != 0) { for (int j = MaxRecurrenceDepth; j >= 0; j--) { - values[i][j] = j-offset < 0 ? NAN : sqctx->independentSequenceValue(i, j-offset); + values[i][j] = j-offset < 0 || j-offset > MaxRecurrenceDepth ? NAN : sqctx->independentSequenceValue(i, j-offset); } } } else { From d5e810f2b8f7ff8d95cc739dc1df1250a340a2da Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 29 Oct 2020 15:31:13 +0100 Subject: [PATCH 376/560] [ion/shared] Remove contexpr array definition in header Change-Id: I620e977b000ee53c3db97510e666dfda30f4db30 --- ion/include/ion/keyboard/event_data.h | 3 +-- ion/src/shared/events.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ion/include/ion/keyboard/event_data.h b/ion/include/ion/keyboard/event_data.h index 58037d7212f..01771105110 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/src/shared/events.cpp b/ion/src/shared/events.cpp index 7346afd09f0..e74130efc5e 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; From 0185e0562ccb1be2e6eed9dc4abe6151c585da7a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 27 Oct 2020 14:40:18 +0100 Subject: [PATCH 377/560] [escher/run_loop] Move kandinksy include To check whether an ExternalText could be written with Epsilon's fonts, UTF8Helper made a reference to Kandinsky, which is prohibited. This check is now done in Escher, before dispatching the event. Change-Id: I55e9db1ba43c3115775499db47b90a6bdd7cc7b3 --- escher/src/run_loop.cpp | 6 ++++++ ion/include/ion/events.h | 2 +- ion/include/ion/unicode/utf8_helper.h | 4 ---- ion/src/shared/unicode/utf8_helper.cpp | 14 -------------- ion/src/simulator/shared/events.h | 1 - ion/src/simulator/shared/events_keyboard.cpp | 3 --- kandinsky/include/kandinsky/font.h | 2 ++ kandinsky/src/font.cpp | 14 ++++++++++++++ 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp index 4653324ffad..f5d8c3b9734 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/ion/include/ion/events.h b/ion/include/ion/events.h index fdffb1cef46..c2326ff6d19 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -234,7 +234,7 @@ 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); +constexpr Event ExternalText = Event::Special(6); } } diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 3f3492b9cec..edc3793259c 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -124,10 +124,6 @@ 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); -/* Returns false if one of text's code points does not have a corresponding - * glyph in Epsilon's fonts.*/ -bool CanBeWrittenWithGlyphs(const char * text); - }; #endif diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index b1f57a3a255..40bf19bb265 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -489,18 +489,4 @@ void countGlyphsInLine(const char * text, int * before, int * after, const char UTF8Helper::PerformAtCodePoints(afterLocation, UCodePointLineFeed, nullptr, countGlyph, after, 0, 0, UCodePointLineFeed); } -bool CanBeWrittenWithGlyphs(const char * text) { - UTF8Decoder decoder(text); - CodePoint cp = decoder.nextCodePoint(); - while(cp != UCodePointNull) { - if (KDFont::LargeFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint - || KDFont::SmallFont->indexForCodePoint(cp) == KDFont::IndexForReplacementCharacterCodePoint) - { - return false; - } - cp = decoder.nextCodePoint(); - } - return true; -} - } diff --git a/ion/src/simulator/shared/events.h b/ion/src/simulator/shared/events.h index 9896b88e7bb..1770c40ba93 100644 --- a/ion/src/simulator/shared/events.h +++ b/ion/src/simulator/shared/events.h @@ -18,7 +18,6 @@ namespace Events { static constexpr size_t sharedExternalTextBufferSize = sizeof(SDL_TextInputEvent::text); char * sharedExternalTextBuffer(); -constexpr Event ExternalText = Event::Special(6); } } diff --git a/ion/src/simulator/shared/events_keyboard.cpp b/ion/src/simulator/shared/events_keyboard.cpp index f4867cdf845..1087528f19a 100644 --- a/ion/src/simulator/shared/events_keyboard.cpp +++ b/ion/src/simulator/shared/events_keyboard.cpp @@ -166,9 +166,6 @@ static Event eventFromSDLTextInputEvent(SDL_TextInputEvent event) { } } } - if (!UTF8Helper::CanBeWrittenWithGlyphs(event.text)) { - return None; - } Ion::Events::removeShift(); strlcpy(sharedExternalTextBuffer(), event.text, sharedExternalTextBufferSize); return ExternalText; diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index a405370f326..0b871ea1c59 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -33,6 +33,8 @@ class KDFont { 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); } diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index cae40c78983..25d82e93a0b 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -159,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; +} From 993de56d594b7fb4cf67633ad87507924d21ca89 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 30 Oct 2020 13:00:33 +0100 Subject: [PATCH 378/560] [ion/events] Remove method defaultIsDefined Change-Id: Ic5c057a451bfc49d99f4b2a70ec5dbff16844a86 --- ion/include/ion/events.h | 1 - ion/src/device/shared/events.cpp | 4 ---- ion/src/shared/events.cpp | 4 ++-- ion/src/simulator/shared/events.cpp | 7 ------- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index c2326ff6d19..95ad80ff4be 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -36,7 +36,6 @@ class Event { bool isDefined() const; static constexpr int PageSize = Keyboard::NumberOfKeys; private: - bool defaultIsDefined() const; const char * defaultText() const; uint8_t m_id; diff --git a/ion/src/device/shared/events.cpp b/ion/src/device/shared/events.cpp index c19b6b12d08..761b00413de 100644 --- a/ion/src/device/shared/events.cpp +++ b/ion/src/device/shared/events.cpp @@ -6,10 +6,6 @@ namespace Events { void didPressNewKey() { } -bool Event::isDefined() const { - return defaultIsDefined(); -} - const char * Event::text() const { return defaultText(); } diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index e74130efc5e..3aed09216dc 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -52,11 +52,11 @@ bool Event::hasText() const { return text() != nullptr; } -bool Event::defaultIsDefined() const { +bool Event::isDefined() const { if (isKeyboardEvent()) { return s_dataForEvent[m_id].isDefined(); } else { - return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); + return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging || *this == ExternalText); } } diff --git a/ion/src/simulator/shared/events.cpp b/ion/src/simulator/shared/events.cpp index 6300c99999b..eb3e0d220bc 100644 --- a/ion/src/simulator/shared/events.cpp +++ b/ion/src/simulator/shared/events.cpp @@ -21,12 +21,5 @@ const char * Event::text() const { return defaultText(); } -bool Event::isDefined() const { - if (*this == ExternalText) { - return true; - } - return defaultIsDefined(); -} - } } From 6f21310e0850d598ebe3a3ee8de9e362e89794ec Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 30 Oct 2020 14:17:33 +0100 Subject: [PATCH 379/560] [ion/events] Add comment about defaultText method Change-Id: Ief2b53395a7921d85b116ded935c9dfeb10d08fd --- ion/src/shared/events.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index 3aed09216dc..e16a37c333f 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -61,6 +61,8 @@ bool Event::isDefined() const { } 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; } From fbf38f891d42d199916107ce713ab293b8c66440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 30 Oct 2020 13:51:11 +0100 Subject: [PATCH 380/560] [apps/shared] Rephrase "Pan" -> "Navigate", "Move" -> "Pan" --- apps/shared.en.i18n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index 19fbbbe4755..8436f6ffa1c 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -51,11 +51,11 @@ 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 = "Pan" +Navigate = "Navigate" Next = "Next" NoDataToPlot = "No data to draw" NoFunctionToDelete = "No function to delete" From bf79d4e4386da9d17268dcf5c3548a389983c7ba Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Fri, 30 Oct 2020 17:07:45 +0100 Subject: [PATCH 381/560] [nl] stats definitions improved --- apps/statistics/base.nl.i18n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index aaf3b7f8cdd..a7533b1b8be 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -18,8 +18,8 @@ BarStart = "X start" FirstQuartile = "Eerste kwartiel" Median = "Mediaan" ThirdQuartile = "Derde kwartiel" -TotalFrequency = "Totale omvang" -Range = "Bereik" +TotalFrequency = "Totale frequentie" +Range = "Spreidingsbreedte" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" InterquartileRange = "Interkwartielafstand" \ No newline at end of file From 547b4b2d713ccc4071986f6465ff4a092902800b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 10:33:54 +0100 Subject: [PATCH 382/560] [.github/workflows] CI: trigger CI manually or on PR only --- .github/workflows/ci-workflow.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 9c74c0ca095..5a3a787fd31 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -1,5 +1,16 @@ name: Continuous integration -on: [pull_request, push] +#on: [pull_request, push] +on: + pull_request: + workflow_dispatch: + triggerIos: + description: 'Run iOS tests' + required: true + default: 'no' + triggerMacos: + description: 'Run macOS tests' + required: true + default: 'no' jobs: android: @@ -94,6 +105,7 @@ jobs: name: epsilon-linux.bin path: output/release/simulator/linux/epsilon.bin macos: + if: github.event.inputs.triggerMacos == 'yes' runs-on: macOS-latest steps: - run: brew install numworks/tap/epsilon-sdk @@ -107,6 +119,7 @@ jobs: 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 From 291941f4e29d98e4c896882e7fbc491db079e14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 10:54:12 +0100 Subject: [PATCH 383/560] [.github/workflows] CI: use msys2/setup-msys2 instead of numworks/setup-msys2 --- .github/workflows/ci-workflow.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 5a3a787fd31..849adb7766f 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -64,14 +64,17 @@ jobs: path: output/release/device/n0110/epsilon.dfu windows: runs-on: windows-latest + defaults: + run: + shell: msys2 {0} steps: - - uses: numworks/setup-msys2@v1 + - uses: msys2/setup-msys2@v2 - uses: actions/checkout@v2 - - run: msys2do 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: msys2do make -j2 PLATFORM=simulator - - run: msys2do make -j2 PLATFORM=simulator epsilon.official.exe - - run: msys2do make -j2 PLATFORM=simulator test.headless.exe - - run: cmd /c output\release\simulator\windows\test.headless.exe + - 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 From 58867b66ca601eff7689be8915084ea7757d2999 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 30 Oct 2020 12:15:47 +0100 Subject: [PATCH 384/560] [sequence] Add tests on self referencing sequences Change-Id: I031f0e8f166aa2d017c0f86679bc9e13e6a432bd --- apps/sequence/test/sequence.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 337f060ad54..7d1fdc06fac 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -412,6 +412,37 @@ QUIZ_CASE(sequence_evaluation) { 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); + } QUIZ_CASE(sequence_sum_evaluation) { From 75dc415e27eccc65b0fc01ddfc0543e81c11c0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 2 Nov 2020 17:27:46 +0100 Subject: [PATCH 385/560] [poincare] Step I: add a parameter to approximation routines to indicate if we're within a reduction routine --- poincare/include/poincare/expression.h | 6 ++-- poincare/include/poincare/expression_node.h | 4 +-- poincare/src/absolute_value.cpp | 2 +- poincare/src/complex_argument.cpp | 2 +- poincare/src/expression.cpp | 31 +++++++++---------- .../src/hyperbolic_trigonometric_function.cpp | 9 ++++-- poincare/src/logarithm.cpp | 2 +- poincare/src/matrix.cpp | 2 +- poincare/src/n_ary_expression.cpp | 2 +- poincare/src/power.cpp | 2 +- poincare/src/sign_function.cpp | 2 +- poincare/src/trigonometry.cpp | 4 +-- poincare/src/trigonometry_cheat_table.cpp | 2 +- poincare/src/variable_context.cpp | 2 +- 14 files changed, 37 insertions(+), 35 deletions(-) diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index dff4782edd1..9c3307846ec 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -260,8 +260,8 @@ class Expression : public TreeHandle { /* 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 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 */ @@ -433,7 +433,7 @@ class Expression : public TreeHandle { 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 c971e28b78c..a9f91ed8a06 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -239,8 +239,8 @@ class ExpressionNode : public TreeNode { 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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) const = 0; + virtual Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) const = 0; /* Simplification */ /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); diff --git a/poincare/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index 905f08d2223..dd37f3e1fdc 100644 --- a/poincare/src/absolute_value.cpp +++ b/poincare/src/absolute_value.cpp @@ -52,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(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true).toScalar(); if (!std::isnan(app)) { if ((c.isNumber() && app >= 0) || app >= Expression::Epsilon()) { /* abs(a) = a with a >= 0 diff --git a/poincare/src/complex_argument.cpp b/poincare/src/complex_argument.cpp index 9e1ef1bf8bc..02f258abc04 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(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), 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/expression.cpp b/poincare/src/expression.cpp index a389510e294..fb57e0dd379 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -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(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), 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). */ @@ -445,11 +445,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(), context, complexFormat, angleUnit, withinReduce); if (complexFormat == Preferences::ComplexFormat::Real && sApproximationEncounteredComplex) { e = Complex::Undefined(); } @@ -854,14 +854,13 @@ 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); +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(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return approximateToEvaluation(context, complexFormat, angleUnit).toScalar(); +U Expression::approximateToScalar(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const { + return approximateToEvaluation(context, complexFormat, angleUnit, withinReduce).toScalar(); } template @@ -1155,17 +1154,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, 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 float Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, bool withinReduce); +template double Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, bool withinReduce); -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/hyperbolic_trigonometric_function.cpp b/poincare/src/hyperbolic_trigonometric_function.cpp index 6ed6d6baaa5..76dfa541f02 100644 --- a/poincare/src/hyperbolic_trigonometric_function.cpp +++ b/poincare/src/hyperbolic_trigonometric_function.cpp @@ -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/logarithm.cpp b/poincare/src/logarithm.cpp index d207bc9890e..1a093958ac8 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -300,7 +300,7 @@ Expression Logarithm::simpleShallowReduce(Context * context, Preferences::Comple } bool isNegative = true; Expression result; - Evaluation baseApproximation = b.node()->approximate(1.0f, context, complexFormat, angleUnit); + Evaluation baseApproximation = b.node()->approximate(1.0f, context, complexFormat, angleUnit, true); std::complex logDenominator = std::log10(static_cast&>(baseApproximation).stdComplex()); if (logDenominator.imag() != 0.0f || logDenominator.real() == 0.0f) { result = Undefined::Builder(); diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 1c7fea1d3ae..8aa47751cd2 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -224,7 +224,7 @@ Matrix Matrix::rowCanonize(ExpressionNode::ReductionContext reductionContext, Ex 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()); + 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; diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index a64be96c9be..a7fba4e9b71 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -89,7 +89,7 @@ Expression NAryExpression::checkChildrenAreRationalIntegersAndUpdate(ExpressionN 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()); + float app = c.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true); if (std::isfinite(app) && app != std::round(app)) { return replaceWithUndefinedInPlace(); } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index a259bd46e5e..5a76875eb74 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -779,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); diff --git a/poincare/src/sign_function.cpp b/poincare/src/sign_function.cpp index 335c02543ac..69447828d5a 100644 --- a/poincare/src/sign_function.cpp +++ b/poincare/src/sign_function.cpp @@ -67,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, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), 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/trigonometry.cpp b/poincare/src/trigonometry.cpp index a3eb841776d..e0ff2bc934d 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -286,7 +286,7 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expression // Step 1. Look for an expression of type "acos(cos(x))", return x if (AreInverseFunctions(e.childAtIndex(0), e)) { - float x = e.childAtIndex(0).childAtIndex(0).node()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), angleUnit).toScalar(); + float x = e.childAtIndex(0).childAtIndex(0).nodex()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), angleUnit, 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) @@ -314,7 +314,7 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expression // 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(), reductionContext.context(), reductionContext.complexFormat(), angleUnit, true).toScalar(); if (trigoOp >= -pi/2.0f && trigoOp <= pi/2.0f) { Expression result = e.childAtIndex(0).childAtIndex(1).childAtIndex(0); e.replaceWithInPlace(result); diff --git a/poincare/src/trigonometry_cheat_table.cpp b/poincare/src/trigonometry_cheat_table.cpp index 6cf2ca3e973..bcd1e290501 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()->approximation(float(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true).toScalar(); if (std::isnan(eValue) || std::isinf(eValue)) { return Expression(); } diff --git a/poincare/src/variable_context.cpp b/poincare/src/variable_context.cpp index 92d54c3644a..1020e3cd162 100644 --- a/poincare/src/variable_context.cpp +++ b/poincare/src/variable_context.cpp @@ -33,7 +33,7 @@ const Expression VariableContext::expressionForSymbolAbstract(const SymbolAbstra 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()); + unknownSymbolValue = m_value.approximateToScalar(this, Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit(), true); } return ContextWithParent::expressionForSymbolAbstract(symbol, clone, unknownSymbolValue); } From 644cf4dcf37619b82b7e589ea874a7c9453483fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 2 Nov 2020 17:58:17 +0100 Subject: [PATCH 386/560] [poincare] Step II: create ApproximateContext --- poincare/include/poincare/expression_node.h | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index a9f91ed8a06..03a4f009fd1 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -190,6 +190,29 @@ class ExpressionNode : public TreeNode { UnitConversion m_unitConversion; }; + class ApproximationContext { + public: + ApproximationContext( + Context * context, + Preferences::ComplexFormat complexFormat, + Preferences::AngleUnit angleUnit, + bool withinReduce = false) : + m_context(context), + m_complexFormat(complexFormat), + m_angleUnit(angleUnit), + m_withinReduce(withinReduce) + {} + Context * context() { return m_context; } + Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } + Preferences::AngleUnit angleUnit() const { return m_angleUnit; } + bool withinReduce() const { return m_withinReduce; } + private: + Context * m_context; + Preferences::ComplexFormat m_complexFormat; + Preferences::AngleUnit m_angleUnit; + 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; } @@ -239,8 +262,8 @@ class ExpressionNode : public TreeNode { 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, bool withinReduce = false) const = 0; - virtual Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) 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); From f8b3156f199ee0f5395a3122b00b8a4f04294b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 2 Nov 2020 18:00:08 +0100 Subject: [PATCH 387/560] [poincare] Start replacing approximateContext --- poincare/include/poincare/absolute_value.h | 4 ++-- poincare/include/poincare/addition.h | 4 ++-- poincare/include/poincare/arc_cosine.h | 4 ++-- poincare/include/poincare/arc_sine.h | 4 ++-- poincare/include/poincare/arc_tangent.h | 4 ++-- poincare/include/poincare/based_integer.h | 4 ++-- poincare/include/poincare/binom_cdf.h | 4 ++-- poincare/include/poincare/binom_pdf.h | 4 ++-- poincare/include/poincare/binomial_coefficient.h | 4 ++-- poincare/include/poincare/ceiling.h | 4 ++-- poincare/include/poincare/complex_argument.h | 4 ++-- poincare/include/poincare/complex_cartesian.h | 4 ++-- poincare/include/poincare/confidence_interval.h | 4 ++-- poincare/include/poincare/conjugate.h | 4 ++-- poincare/include/poincare/constant.h | 4 ++-- poincare/include/poincare/cosine.h | 4 ++-- poincare/include/poincare/decimal.h | 4 ++-- poincare/include/poincare/derivative.h | 4 ++-- poincare/include/poincare/determinant.h | 4 ++-- poincare/include/poincare/division.h | 4 ++-- poincare/include/poincare/division_quotient.h | 4 ++-- poincare/include/poincare/division_remainder.h | 4 ++-- poincare/include/poincare/empty_expression.h | 4 ++-- poincare/include/poincare/equal.h | 4 ++-- poincare/include/poincare/factor.h | 4 ++-- poincare/include/poincare/factorial.h | 4 ++-- poincare/include/poincare/float.h | 4 ++-- poincare/include/poincare/floor.h | 4 ++-- poincare/include/poincare/frac_part.h | 4 ++-- poincare/include/poincare/function.h | 4 ++-- poincare/include/poincare/great_common_divisor.h | 4 ++-- poincare/include/poincare/hyperbolic_arc_cosine.h | 4 ++-- poincare/include/poincare/hyperbolic_arc_sine.h | 4 ++-- poincare/include/poincare/hyperbolic_arc_tangent.h | 4 ++-- poincare/include/poincare/hyperbolic_cosine.h | 4 ++-- poincare/include/poincare/hyperbolic_sine.h | 4 ++-- poincare/include/poincare/hyperbolic_tangent.h | 4 ++-- poincare/include/poincare/imaginary_part.h | 4 ++-- poincare/include/poincare/infinity.h | 4 ++-- poincare/include/poincare/integral.h | 4 ++-- poincare/include/poincare/inv_binom.h | 4 ++-- poincare/include/poincare/inv_norm.h | 4 ++-- poincare/include/poincare/least_common_multiple.h | 4 ++-- poincare/include/poincare/logarithm.h | 4 ++-- poincare/include/poincare/matrix.h | 4 ++-- poincare/include/poincare/matrix_dimension.h | 4 ++-- poincare/include/poincare/matrix_echelon_form.h | 4 ++-- poincare/include/poincare/matrix_identity.h | 4 ++-- poincare/include/poincare/matrix_inverse.h | 4 ++-- poincare/include/poincare/matrix_trace.h | 4 ++-- poincare/include/poincare/matrix_transpose.h | 4 ++-- poincare/include/poincare/multiplication.h | 4 ++-- poincare/include/poincare/naperian_logarithm.h | 4 ++-- poincare/include/poincare/norm_cdf.h | 4 ++-- poincare/include/poincare/norm_cdf2.h | 4 ++-- poincare/include/poincare/norm_pdf.h | 4 ++-- poincare/include/poincare/nth_root.h | 4 ++-- poincare/include/poincare/opposite.h | 4 ++-- poincare/include/poincare/parenthesis.h | 4 ++-- poincare/include/poincare/permute_coefficient.h | 4 ++-- poincare/include/poincare/power.h | 4 ++-- poincare/include/poincare/prediction_interval.h | 4 ++-- poincare/include/poincare/randint.h | 4 ++-- poincare/include/poincare/random.h | 4 ++-- poincare/include/poincare/rational.h | 4 ++-- poincare/include/poincare/real_part.h | 4 ++-- poincare/include/poincare/round.h | 4 ++-- poincare/include/poincare/sequence.h | 4 ++-- poincare/include/poincare/sign_function.h | 4 ++-- poincare/include/poincare/sine.h | 4 ++-- poincare/include/poincare/square_root.h | 4 ++-- poincare/include/poincare/store.h | 4 ++-- poincare/include/poincare/subtraction.h | 4 ++-- poincare/include/poincare/sum_and_product.h | 4 ++-- poincare/include/poincare/symbol.h | 4 ++-- poincare/include/poincare/tangent.h | 4 ++-- poincare/include/poincare/undefined.h | 4 ++-- poincare/include/poincare/unit.h | 4 ++-- poincare/include/poincare/unit_convert.h | 4 ++-- poincare/include/poincare/unreal.h | 4 ++-- poincare/include/poincare/vector_cross.h | 4 ++-- poincare/include/poincare/vector_dot.h | 4 ++-- poincare/include/poincare/vector_norm.h | 4 ++-- 83 files changed, 166 insertions(+), 166 deletions(-) diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index 6e841e7a76c..a9324ee3fe8 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -28,10 +28,10 @@ class AbsoluteValueNode final : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 346c6c11a83..58e3c1bae29 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -64,10 +64,10 @@ class AdditionNode final : public NAryInfixExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } }; diff --git a/poincare/include/poincare/arc_cosine.h b/poincare/include/poincare/arc_cosine.h index 389ac4c7982..f95f34827fa 100644 --- a/poincare/include/poincare/arc_cosine.h +++ b/poincare/include/poincare/arc_cosine.h @@ -35,10 +35,10 @@ class ArcCosineNode final : public ExpressionNode { //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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/arc_sine.h b/poincare/include/poincare/arc_sine.h index a567d55a1a4..f5c5c07e1d5 100644 --- a/poincare/include/poincare/arc_sine.h +++ b/poincare/include/poincare/arc_sine.h @@ -34,10 +34,10 @@ class ArcSineNode final : public ExpressionNode { //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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/arc_tangent.h b/poincare/include/poincare/arc_tangent.h index 44c0ff887e1..31147cc4af1 100644 --- a/poincare/include/poincare/arc_tangent.h +++ b/poincare/include/poincare/arc_tangent.h @@ -35,10 +35,10 @@ class ArcTangentNode final : public ExpressionNode { //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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/based_integer.h b/poincare/include/poincare/based_integer.h index f553902c11e..5de001d3d78 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -34,8 +34,8 @@ class BasedIntegerNode final : public NumberNode { 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, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } template T templatedApproximate() const; private: diff --git a/poincare/include/poincare/binom_cdf.h b/poincare/include/poincare/binom_cdf.h index 83204081818..0d017fe072b 100644 --- a/poincare/include/poincare/binom_cdf.h +++ b/poincare/include/poincare/binom_cdf.h @@ -29,8 +29,8 @@ class BinomCDFNode final : public BinomialDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/binom_pdf.h b/poincare/include/poincare/binom_pdf.h index c316e5aeda5..515abbb415c 100644 --- a/poincare/include/poincare/binom_pdf.h +++ b/poincare/include/poincare/binom_pdf.h @@ -29,8 +29,8 @@ class BinomPDFNode final : public BinomialDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/binomial_coefficient.h b/poincare/include/poincare/binomial_coefficient.h index 992cf88d007..c00bc81790e 100644 --- a/poincare/include/poincare/binomial_coefficient.h +++ b/poincare/include/poincare/binomial_coefficient.h @@ -30,8 +30,8 @@ class BinomialCoefficientNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Complex templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/ceiling.h b/poincare/include/poincare/ceiling.h index a8589770645..c6acc967cd9 100644 --- a/poincare/include/poincare/ceiling.h +++ b/poincare/include/poincare/ceiling.h @@ -31,10 +31,10 @@ class CeilingNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/complex_argument.h b/poincare/include/poincare/complex_argument.h index 4264428c648..04c99178955 100644 --- a/poincare/include/poincare/complex_argument.h +++ b/poincare/include/poincare/complex_argument.h @@ -31,10 +31,10 @@ class ComplexArgumentNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/complex_cartesian.h b/poincare/include/poincare/complex_cartesian.h index 7d973048207..37defc849ed 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -26,8 +26,8 @@ class ComplexCartesianNode : public ExpressionNode { // 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } // Simplification Expression shallowReduce(ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext reductionContext) override; diff --git a/poincare/include/poincare/confidence_interval.h b/poincare/include/poincare/confidence_interval.h index f0d78e2d369..970b155124b 100644 --- a/poincare/include/poincare/confidence_interval.h +++ b/poincare/include/poincare/confidence_interval.h @@ -32,8 +32,8 @@ class ConfidenceIntervalNode : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/conjugate.h b/poincare/include/poincare/conjugate.h index e2f90f17067..e019b77fe82 100644 --- a/poincare/include/poincare/conjugate.h +++ b/poincare/include/poincare/conjugate.h @@ -33,10 +33,10 @@ class ConjugateNode /*final*/ : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index ed04a922c1c..33741479ece 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -34,8 +34,8 @@ class ConstantNode final : public SymbolAbstractNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(); } /* Symbol properties */ bool isPi() const { return isConstantCodePoint(UCodePointGreekSmallLetterPi); } diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index bca04a2ed6e..9f262ebc108 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -38,10 +38,10 @@ class CosineNode final : public ExpressionNode { Expression unaryFunctionDifferential() override; // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index 6f9267efd74..b00af47b880 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -46,10 +46,10 @@ class DecimalNode final : public NumberNode { 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, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index 0456c54b599..4398331276d 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -34,8 +34,8 @@ class DerivativeNode final : public ParameteredExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; diff --git a/poincare/include/poincare/determinant.h b/poincare/include/poincare/determinant.h index 1a1a043e477..b672bcaf374 100644 --- a/poincare/include/poincare/determinant.h +++ b/poincare/include/poincare/determinant.h @@ -28,8 +28,8 @@ class DeterminantNode /*final*/ : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/division.h b/poincare/include/poincare/division.h index b48a4d883a0..75fdf84ab7a 100644 --- a/poincare/include/poincare/division.h +++ b/poincare/include/poincare/division.h @@ -33,13 +33,13 @@ template 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, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce( this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - virtual Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + virtual Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce( this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, diff --git a/poincare/include/poincare/division_quotient.h b/poincare/include/poincare/division_quotient.h index 2db2f95dba3..6d4444698df 100644 --- a/poincare/include/poincare/division_quotient.h +++ b/poincare/include/poincare/division_quotient.h @@ -33,8 +33,8 @@ class DivisionQuotientNode final : public ExpressionNode { // 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/division_remainder.h b/poincare/include/poincare/division_remainder.h index e8262c7a2b8..183e4e3206d 100644 --- a/poincare/include/poincare/division_remainder.h +++ b/poincare/include/poincare/division_remainder.h @@ -35,8 +35,8 @@ class DivisionRemainderNode final : public ExpressionNode { // 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/empty_expression.h b/poincare/include/poincare/empty_expression.h index 0ac5a21e71c..ac3d849ac62 100644 --- a/poincare/include/poincare/empty_expression.h +++ b/poincare/include/poincare/empty_expression.h @@ -33,8 +33,8 @@ class EmptyExpressionNode final : public ExpressionNode { // 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/equal.h b/poincare/include/poincare/equal.h index b81e0b0f3d9..60af231820a 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -28,8 +28,8 @@ class EqualNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/factor.h b/poincare/include/poincare/factor.h index 329fe190268..d899399ba9b 100644 --- a/poincare/include/poincare/factor.h +++ b/poincare/include/poincare/factor.h @@ -33,8 +33,8 @@ class FactorNode /*final*/ : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/factorial.h b/poincare/include/poincare/factorial.h index 2f39b20621d..10ed3373ff4 100644 --- a/poincare/include/poincare/factorial.h +++ b/poincare/include/poincare/factorial.h @@ -37,10 +37,10 @@ class FactorialNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index d1eda088732..fce3cc20e0e 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -47,8 +47,8 @@ class FloatNode final : public NumberNode { /* 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } private: // Simplification LayoutShape leftLayoutShape() const override { return LayoutShape::Decimal; } diff --git a/poincare/include/poincare/floor.h b/poincare/include/poincare/floor.h index 5d360389f34..fee31eb698c 100644 --- a/poincare/include/poincare/floor.h +++ b/poincare/include/poincare/floor.h @@ -32,10 +32,10 @@ class FloorNode /*final*/ : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/frac_part.h b/poincare/include/poincare/frac_part.h index 54e95817e7f..63745f56f63 100644 --- a/poincare/include/poincare/frac_part.h +++ b/poincare/include/poincare/frac_part.h @@ -33,10 +33,10 @@ class FracPartNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/function.h b/poincare/include/poincare/function.h index ddd11f81b87..e1e4a839929 100644 --- a/poincare/include/poincare/function.h +++ b/poincare/include/poincare/function.h @@ -42,8 +42,8 @@ class FunctionNode : public SymbolAbstractNode { 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; + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override; + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override; template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index 09ef262bab0..62346b6f86d 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -30,8 +30,8 @@ class GreatCommonDivisorNode final : public NAryExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/hyperbolic_arc_cosine.h b/poincare/include/poincare/hyperbolic_arc_cosine.h index 67855d9079c..16db5644cdb 100644 --- a/poincare/include/poincare/hyperbolic_arc_cosine.h +++ b/poincare/include/poincare/hyperbolic_arc_cosine.h @@ -27,10 +27,10 @@ class HyperbolicArcCosineNode final : public HyperbolicTrigonometricFunctionNode 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_arc_sine.h b/poincare/include/poincare/hyperbolic_arc_sine.h index 2981ba62279..91fba959eef 100644 --- a/poincare/include/poincare/hyperbolic_arc_sine.h +++ b/poincare/include/poincare/hyperbolic_arc_sine.h @@ -25,10 +25,10 @@ class HyperbolicArcSineNode final : public HyperbolicTrigonometricFunctionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_arc_tangent.h b/poincare/include/poincare/hyperbolic_arc_tangent.h index d59827696c4..fada17c7b58 100644 --- a/poincare/include/poincare/hyperbolic_arc_tangent.h +++ b/poincare/include/poincare/hyperbolic_arc_tangent.h @@ -25,10 +25,10 @@ class HyperbolicArcTangentNode final : public HyperbolicTrigonometricFunctionNod 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_cosine.h b/poincare/include/poincare/hyperbolic_cosine.h index c37d90fb6e7..0e50b832726 100644 --- a/poincare/include/poincare/hyperbolic_cosine.h +++ b/poincare/include/poincare/hyperbolic_cosine.h @@ -30,10 +30,10 @@ class HyperbolicCosineNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_sine.h b/poincare/include/poincare/hyperbolic_sine.h index 95ffaf72062..47f0b00c97e 100644 --- a/poincare/include/poincare/hyperbolic_sine.h +++ b/poincare/include/poincare/hyperbolic_sine.h @@ -28,10 +28,10 @@ class HyperbolicSineNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/hyperbolic_tangent.h b/poincare/include/poincare/hyperbolic_tangent.h index b69e6ec1393..36db08f5dcc 100644 --- a/poincare/include/poincare/hyperbolic_tangent.h +++ b/poincare/include/poincare/hyperbolic_tangent.h @@ -28,10 +28,10 @@ class HyperbolicTangentNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/imaginary_part.h b/poincare/include/poincare/imaginary_part.h index 6fd7209ffd1..f43de3ee072 100644 --- a/poincare/include/poincare/imaginary_part.h +++ b/poincare/include/poincare/imaginary_part.h @@ -34,10 +34,10 @@ class ImaginaryPartNode final : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/infinity.h b/poincare/include/poincare/infinity.h index a1e8ec5bfdf..3936a3267c4 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -27,10 +27,10 @@ class InfinityNode final : public NumberNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/integral.h b/poincare/include/poincare/integral.h index e55f30e3abd..16472434118 100644 --- a/poincare/include/poincare/integral.h +++ b/poincare/include/poincare/integral.h @@ -31,8 +31,8 @@ class IntegralNode final : public ParameteredExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; template struct DetailedResult diff --git a/poincare/include/poincare/inv_binom.h b/poincare/include/poincare/inv_binom.h index c3b959d6080..e4f30699b74 100644 --- a/poincare/include/poincare/inv_binom.h +++ b/poincare/include/poincare/inv_binom.h @@ -30,8 +30,8 @@ class InvBinomNode final : public BinomialDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/inv_norm.h b/poincare/include/poincare/inv_norm.h index a06d622e86f..dc27eadf7f4 100644 --- a/poincare/include/poincare/inv_norm.h +++ b/poincare/include/poincare/inv_norm.h @@ -30,8 +30,8 @@ class InvNormNode final : public NormalDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index fe0e15a8e4c..f73dc7e1329 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -30,8 +30,8 @@ class LeastCommonMultipleNode final : public NAryExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index b5fb4633068..1c0ab9edea6 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -42,8 +42,8 @@ class LogarithmNode final : public ExpressionNode { * (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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 06013fffe05..25456b7fdad 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -40,10 +40,10 @@ class MatrixNode /*final*/ : public ExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } diff --git a/poincare/include/poincare/matrix_dimension.h b/poincare/include/poincare/matrix_dimension.h index 07c40840d72..579ddd89006 100644 --- a/poincare/include/poincare/matrix_dimension.h +++ b/poincare/include/poincare/matrix_dimension.h @@ -29,8 +29,8 @@ class MatrixDimensionNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/matrix_echelon_form.h b/poincare/include/poincare/matrix_echelon_form.h index 1a2ff0f7c4f..cda533715e0 100644 --- a/poincare/include/poincare/matrix_echelon_form.h +++ b/poincare/include/poincare/matrix_echelon_form.h @@ -21,8 +21,8 @@ class MatrixEchelonFormNode : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; // Properties diff --git a/poincare/include/poincare/matrix_identity.h b/poincare/include/poincare/matrix_identity.h index 8d50b39c3bf..7755422895b 100644 --- a/poincare/include/poincare/matrix_identity.h +++ b/poincare/include/poincare/matrix_identity.h @@ -27,8 +27,8 @@ class MatrixIdentityNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/matrix_inverse.h b/poincare/include/poincare/matrix_inverse.h index 53fefdf30d9..dff666e1a63 100644 --- a/poincare/include/poincare/matrix_inverse.h +++ b/poincare/include/poincare/matrix_inverse.h @@ -28,8 +28,8 @@ class MatrixInverseNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/matrix_trace.h b/poincare/include/poincare/matrix_trace.h index 7805cf0d59b..6ff79e471ac 100644 --- a/poincare/include/poincare/matrix_trace.h +++ b/poincare/include/poincare/matrix_trace.h @@ -28,8 +28,8 @@ class MatrixTraceNode /*final*/ : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/matrix_transpose.h b/poincare/include/poincare/matrix_transpose.h index e404c840cc0..fc710e9337e 100644 --- a/poincare/include/poincare/matrix_transpose.h +++ b/poincare/include/poincare/matrix_transpose.h @@ -28,8 +28,8 @@ class MatrixTransposeNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index bdefb2c0486..2007f258be9 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -56,10 +56,10 @@ class MultiplicationNode final : public NAryInfixExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } }; diff --git a/poincare/include/poincare/naperian_logarithm.h b/poincare/include/poincare/naperian_logarithm.h index b466af839d0..6c10d3e3ae1 100644 --- a/poincare/include/poincare/naperian_logarithm.h +++ b/poincare/include/poincare/naperian_logarithm.h @@ -35,10 +35,10 @@ class NaperianLogarithmNode final : public ExpressionNode { * (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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/norm_cdf.h b/poincare/include/poincare/norm_cdf.h index ae9eee78cd6..c0c113ce8a8 100644 --- a/poincare/include/poincare/norm_cdf.h +++ b/poincare/include/poincare/norm_cdf.h @@ -29,8 +29,8 @@ class NormCDFNode final : public NormalDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/norm_cdf2.h b/poincare/include/poincare/norm_cdf2.h index 2db2965a4a5..067663eb9c1 100644 --- a/poincare/include/poincare/norm_cdf2.h +++ b/poincare/include/poincare/norm_cdf2.h @@ -31,8 +31,8 @@ class NormCDF2Node final : public NormalDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/norm_pdf.h b/poincare/include/poincare/norm_pdf.h index 5467a478eb5..f9bd816480b 100644 --- a/poincare/include/poincare/norm_pdf.h +++ b/poincare/include/poincare/norm_pdf.h @@ -29,8 +29,8 @@ class NormPDFNode final : public NormalDistributionFunctionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/nth_root.h b/poincare/include/poincare/nth_root.h index b1280494a69..fbb729e9a18 100644 --- a/poincare/include/poincare/nth_root.h +++ b/poincare/include/poincare/nth_root.h @@ -29,8 +29,8 @@ class NthRootNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/opposite.h b/poincare/include/poincare/opposite.h index de8e0996fd7..441521a66d9 100644 --- a/poincare/include/poincare/opposite.h +++ b/poincare/include/poincare/opposite.h @@ -30,10 +30,10 @@ class OppositeNode /*final*/ : public ExpressionNode { bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; // Approximation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, compute); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, compute); } diff --git a/poincare/include/poincare/parenthesis.h b/poincare/include/poincare/parenthesis.h index bf1fcae893b..f11810b5aa3 100644 --- a/poincare/include/poincare/parenthesis.h +++ b/poincare/include/poincare/parenthesis.h @@ -33,8 +33,8 @@ class ParenthesisNode /*final*/ : public ExpressionNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } private: template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/permute_coefficient.h b/poincare/include/poincare/permute_coefficient.h index 5ebfb0032fa..a2886babb21 100644 --- a/poincare/include/poincare/permute_coefficient.h +++ b/poincare/include/poincare/permute_coefficient.h @@ -33,8 +33,8 @@ class PermuteCoefficientNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index e8f7f384bbd..72c8d0404d6 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -64,10 +64,10 @@ class PowerNode final : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/include/poincare/prediction_interval.h b/poincare/include/poincare/prediction_interval.h index 734b2ba22b6..4691311b568 100644 --- a/poincare/include/poincare/prediction_interval.h +++ b/poincare/include/poincare/prediction_interval.h @@ -31,8 +31,8 @@ class PredictionIntervalNode /*final*/ : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/randint.h b/poincare/include/poincare/randint.h index b8f03edc314..e7091027c66 100644 --- a/poincare/include/poincare/randint.h +++ b/poincare/include/poincare/randint.h @@ -28,10 +28,10 @@ class RandintNode /*final*/ : public ExpressionNode { 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, ApproximateContext approximateContext) const override { return templateApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templateApproximate(context, complexFormat, angleUnit); } template Evaluation templateApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool * inputIsUndefined = nullptr) const; diff --git a/poincare/include/poincare/random.h b/poincare/include/poincare/random.h index 564a758ed14..712b421ab44 100644 --- a/poincare/include/poincare/random.h +++ b/poincare/include/poincare/random.h @@ -32,10 +32,10 @@ class RandomNode final : public ExpressionNode { 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, ApproximateContext approximateContext) const override { return templateApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templateApproximate(); } template Evaluation templateApproximate() const; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index abde3b3e0cc..43dce23006e 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -39,8 +39,8 @@ class RationalNode final : public NumberNode { 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, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } template T templatedApproximate() const; // Basic test diff --git a/poincare/include/poincare/real_part.h b/poincare/include/poincare/real_part.h index a35db4b9491..b4a5c728ec2 100644 --- a/poincare/include/poincare/real_part.h +++ b/poincare/include/poincare/real_part.h @@ -36,10 +36,10 @@ class RealPartNode final : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/round.h b/poincare/include/poincare/round.h index 1d4ec886ba6..9de713c55d1 100644 --- a/poincare/include/poincare/round.h +++ b/poincare/include/poincare/round.h @@ -31,8 +31,8 @@ class RoundNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/sequence.h b/poincare/include/poincare/sequence.h index 043d71f4cc6..f2bd6ee557d 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -34,8 +34,8 @@ class SequenceNode : public SymbolAbstractNode { 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; + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override; + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override; template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/sign_function.h b/poincare/include/poincare/sign_function.h index dbb590c3550..968abf164ca 100644 --- a/poincare/include/poincare/sign_function.h +++ b/poincare/include/poincare/sign_function.h @@ -35,10 +35,10 @@ class SignFunctionNode final : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/sine.h b/poincare/include/poincare/sine.h index 169db870bd2..4579180cc5b 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -39,10 +39,10 @@ class SineNode final : public ExpressionNode { Expression unaryFunctionDifferential() override; // Evaluation - Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/square_root.h b/poincare/include/poincare/square_root.h index a6f9a69d157..45dfe04363d 100644 --- a/poincare/include/poincare/square_root.h +++ b/poincare/include/poincare/square_root.h @@ -32,10 +32,10 @@ class SquareRootNode /*final*/ : public ExpressionNode { 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/store.h b/poincare/include/poincare/store.h index 95bd3c14685..5613c1e15c9 100644 --- a/poincare/include/poincare/store.h +++ b/poincare/include/poincare/store.h @@ -23,8 +23,8 @@ class StoreNode /*final*/ : public RightwardsArrowExpressionNode { // 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/subtraction.h b/poincare/include/poincare/subtraction.h index 92e213ba99a..5ceeab30a6a 100644 --- a/poincare/include/poincare/subtraction.h +++ b/poincare/include/poincare/subtraction.h @@ -28,10 +28,10 @@ class SubtractionNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::MapReduce(this, context, complexFormat, angleUnit, compute, computeOnComplexAndMatrix, computeOnMatrixAndComplex, computeOnMatrices); } diff --git a/poincare/include/poincare/sum_and_product.h b/poincare/include/poincare/sum_and_product.h index a74ecc0d20c..63ea7a7b63b 100644 --- a/poincare/include/poincare/sum_and_product.h +++ b/poincare/include/poincare/sum_and_product.h @@ -17,8 +17,8 @@ class SumAndProductNode : public ParameteredExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; virtual float emptySumAndProductValue() const = 0; virtual Evaluation evaluateWithNextTerm(SinglePrecision p, Evaluation a, Evaluation b, Preferences::ComplexFormat complexFormat) const = 0; diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 8b800a5fce1..ca05847999b 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -40,8 +40,8 @@ class SymbolNode final : public SymbolAbstractNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } bool isUnknown() const; private: diff --git a/poincare/include/poincare/tangent.h b/poincare/include/poincare/tangent.h index 0576191b992..7bda46d9784 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -37,10 +37,10 @@ class TangentNode final : public ExpressionNode { // 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 { + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit,computeOnComplex); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } }; diff --git a/poincare/include/poincare/undefined.h b/poincare/include/poincare/undefined.h index 3636e6751d9..0a4f34a7ecb 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -22,10 +22,10 @@ class UndefinedNode : public NumberNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 2437e83d43b..77f63fd9158 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -465,8 +465,8 @@ class UnitNode final : public ExpressionNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } // Comparison int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const override; diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index 73379fb1cb0..1a046500bbb 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -25,8 +25,8 @@ class UnitConvertNode /*final*/ : public RightwardsArrowExpressionNode { void deepReduceChildren(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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index 6757c8a0bdb..8ab669aaefc 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -20,10 +20,10 @@ class UnrealNode final : public UndefinedNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/vector_cross.h b/poincare/include/poincare/vector_cross.h index e1f75e03eb0..c878ddb939f 100644 --- a/poincare/include/poincare/vector_cross.h +++ b/poincare/include/poincare/vector_cross.h @@ -28,8 +28,8 @@ class VectorCrossNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/vector_dot.h b/poincare/include/poincare/vector_dot.h index 3b9c8034224..fe4a45fd5f0 100644 --- a/poincare/include/poincare/vector_dot.h +++ b/poincare/include/poincare/vector_dot.h @@ -28,8 +28,8 @@ class VectorDotNode final : public ExpressionNode { 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; diff --git a/poincare/include/poincare/vector_norm.h b/poincare/include/poincare/vector_norm.h index 3d2da252a1a..0e0cf600ffc 100644 --- a/poincare/include/poincare/vector_norm.h +++ b/poincare/include/poincare/vector_norm.h @@ -28,8 +28,8 @@ class VectorNormNode final : public ExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; 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); } + Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } + Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; }; From 7142f9545e8ab08e77d2cbc455f0ce63b2d0723b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 17:39:44 +0100 Subject: [PATCH 388/560] [poincare] Finish replacing approximateContext --- poincare/include/poincare/absolute_value.h | 8 ++-- poincare/include/poincare/addition.h | 8 ++-- .../include/poincare/approximation_helper.h | 6 +-- poincare/include/poincare/arc_cosine.h | 8 ++-- poincare/include/poincare/arc_sine.h | 8 ++-- poincare/include/poincare/arc_tangent.h | 8 ++-- poincare/include/poincare/arithmetic.h | 4 +- poincare/include/poincare/based_integer.h | 4 +- poincare/include/poincare/binom_cdf.h | 6 +-- poincare/include/poincare/binom_pdf.h | 6 +-- .../include/poincare/binomial_coefficient.h | 6 +-- poincare/include/poincare/ceiling.h | 8 ++-- poincare/include/poincare/complex_argument.h | 8 ++-- poincare/include/poincare/complex_cartesian.h | 6 +-- .../include/poincare/confidence_interval.h | 6 +-- poincare/include/poincare/conjugate.h | 8 ++-- poincare/include/poincare/constant.h | 4 +- poincare/include/poincare/cosine.h | 8 ++-- poincare/include/poincare/decimal.h | 4 +- poincare/include/poincare/derivative.h | 12 ++--- poincare/include/poincare/determinant.h | 6 +-- poincare/include/poincare/division.h | 8 ++-- poincare/include/poincare/division_quotient.h | 6 +-- .../include/poincare/division_remainder.h | 6 +-- poincare/include/poincare/empty_expression.h | 6 +-- poincare/include/poincare/equal.h | 6 +-- poincare/include/poincare/expression_node.h | 44 +++++++++++-------- poincare/include/poincare/factor.h | 8 ++-- poincare/include/poincare/factorial.h | 8 ++-- poincare/include/poincare/float.h | 6 +-- poincare/include/poincare/floor.h | 8 ++-- poincare/include/poincare/frac_part.h | 8 ++-- poincare/include/poincare/function.h | 6 +-- .../include/poincare/great_common_divisor.h | 6 +-- .../include/poincare/hyperbolic_arc_cosine.h | 8 ++-- .../include/poincare/hyperbolic_arc_sine.h | 8 ++-- .../include/poincare/hyperbolic_arc_tangent.h | 8 ++-- poincare/include/poincare/hyperbolic_cosine.h | 8 ++-- poincare/include/poincare/hyperbolic_sine.h | 8 ++-- .../include/poincare/hyperbolic_tangent.h | 8 ++-- poincare/include/poincare/imaginary_part.h | 8 ++-- poincare/include/poincare/infinity.h | 4 +- poincare/include/poincare/integral.h | 12 ++--- poincare/include/poincare/inv_binom.h | 6 +-- poincare/include/poincare/inv_norm.h | 6 +-- .../include/poincare/least_common_multiple.h | 6 +-- poincare/include/poincare/logarithm.h | 8 ++-- poincare/include/poincare/matrix.h | 10 ++--- poincare/include/poincare/matrix_dimension.h | 6 +-- .../include/poincare/matrix_echelon_form.h | 6 +-- poincare/include/poincare/matrix_identity.h | 6 +-- poincare/include/poincare/matrix_inverse.h | 6 +-- poincare/include/poincare/matrix_trace.h | 6 +-- poincare/include/poincare/matrix_transpose.h | 6 +-- poincare/include/poincare/multiplication.h | 8 ++-- .../include/poincare/naperian_logarithm.h | 8 ++-- poincare/include/poincare/norm_cdf.h | 6 +-- poincare/include/poincare/norm_cdf2.h | 6 +-- poincare/include/poincare/norm_pdf.h | 6 +-- poincare/include/poincare/nth_root.h | 6 +-- poincare/include/poincare/opposite.h | 8 ++-- poincare/include/poincare/parenthesis.h | 6 +-- .../include/poincare/permute_coefficient.h | 6 +-- poincare/include/poincare/power.h | 10 ++--- .../include/poincare/prediction_interval.h | 6 +-- poincare/include/poincare/randint.h | 10 ++--- poincare/include/poincare/random.h | 4 +- poincare/include/poincare/rational.h | 4 +- poincare/include/poincare/real_part.h | 8 ++-- poincare/include/poincare/round.h | 6 +-- poincare/include/poincare/sequence.h | 6 +-- poincare/include/poincare/sign_function.h | 8 ++-- poincare/include/poincare/sine.h | 8 ++-- poincare/include/poincare/square_root.h | 8 ++-- poincare/include/poincare/store.h | 8 ++-- poincare/include/poincare/subtraction.h | 8 ++-- poincare/include/poincare/sum_and_product.h | 6 +-- poincare/include/poincare/symbol.h | 6 +-- poincare/include/poincare/tangent.h | 8 ++-- poincare/include/poincare/undefined.h | 4 +- poincare/include/poincare/unit.h | 6 +-- poincare/include/poincare/unit_convert.h | 6 +-- poincare/include/poincare/unreal.h | 4 +- poincare/include/poincare/vector_cross.h | 6 +-- poincare/include/poincare/vector_dot.h | 6 +-- poincare/include/poincare/vector_norm.h | 6 +-- poincare/src/absolute_value.cpp | 2 +- poincare/src/approximation_helper.cpp | 38 ++++++++-------- poincare/src/arithmetic.cpp | 22 +++++----- poincare/src/binom_cdf.cpp | 8 ++-- poincare/src/binom_pdf.cpp | 8 ++-- poincare/src/binomial_coefficient.cpp | 6 +-- poincare/src/complex_argument.cpp | 2 +- poincare/src/complex_cartesian.cpp | 6 +-- poincare/src/confidence_interval.cpp | 6 +-- poincare/src/derivative.cpp | 27 ++++++------ poincare/src/determinant.cpp | 4 +- poincare/src/division_quotient.cpp | 6 +-- poincare/src/division_remainder.cpp | 6 +-- poincare/src/empty_expression.cpp | 2 +- poincare/src/equal.cpp | 2 +- poincare/src/expression.cpp | 8 ++-- poincare/src/factor.cpp | 10 ++--- poincare/src/function.cpp | 16 +++---- poincare/src/great_common_divisor.cpp | 8 ++-- .../src/hyperbolic_trigonometric_function.cpp | 4 +- poincare/src/integral.cpp | 37 ++++++++-------- poincare/src/inv_binom.cpp | 8 ++-- poincare/src/inv_norm.cpp | 8 ++-- poincare/src/least_common_multiple.cpp | 8 ++-- poincare/src/logarithm.cpp | 32 +++++++------- poincare/src/matrix.cpp | 4 +- poincare/src/matrix_dimension.cpp | 4 +- poincare/src/matrix_echelon_form.cpp | 4 +- poincare/src/matrix_identity.cpp | 4 +- poincare/src/matrix_inverse.cpp | 4 +- poincare/src/matrix_trace.cpp | 4 +- poincare/src/matrix_transpose.cpp | 4 +- poincare/src/norm_cdf.cpp | 8 ++-- poincare/src/norm_cdf2.cpp | 10 ++--- poincare/src/norm_pdf.cpp | 8 ++-- poincare/src/nth_root.cpp | 10 ++--- poincare/src/parenthesis.cpp | 4 +- poincare/src/permute_coefficient.cpp | 6 +-- poincare/src/power.cpp | 14 +++--- poincare/src/prediction_interval.cpp | 6 +-- poincare/src/randint.cpp | 8 ++-- poincare/src/round.cpp | 6 +-- poincare/src/sequence.cpp | 16 +++---- poincare/src/sign_function.cpp | 2 +- poincare/src/store.cpp | 10 ++--- poincare/src/sum_and_product.cpp | 17 +++---- poincare/src/symbol.cpp | 6 +-- poincare/src/symbol_abstract.cpp | 2 +- poincare/src/trigonometry.cpp | 4 +- poincare/src/trigonometry_cheat_table.cpp | 2 +- poincare/src/unit.cpp | 6 +-- poincare/src/unit_convert.cpp | 6 +-- poincare/src/vector_cross.cpp | 6 +-- poincare/src/vector_dot.cpp | 6 +-- poincare/src/vector_norm.cpp | 4 +- 141 files changed, 556 insertions(+), 547 deletions(-) diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index a9324ee3fe8..f16a3508bc4 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -28,11 +28,11 @@ class AbsoluteValueNode final : public ExpressionNode { template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::abs(c)); } - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 58e3c1bae29..7a17468eb50 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -64,11 +64,11 @@ class AdditionNode final : public NAryInfixExpressionNode { template static MatrixComplex computeOnMatrixAndComplex(const MatrixComplex m, const std::complex c, Preferences::ComplexFormat complexFormat) { return MatrixComplex::Undefined(); } - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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); } }; diff --git a/poincare/include/poincare/approximation_helper.h b/poincare/include/poincare/approximation_helper.h index b168699482f..2ddc6289d2c 100644 --- a/poincare/include/poincare/approximation_helper.h +++ b/poincare/include/poincare/approximation_helper.h @@ -10,17 +10,17 @@ namespace Poincare { namespace ApproximationHelper { template T Epsilon(); - template int PositiveIntegerApproximationIfPossible(const ExpressionNode * expression, bool * isUndefined, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + 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 f95f34827fa..c3267ad2625 100644 --- a/poincare/include/poincare/arc_cosine.h +++ b/poincare/include/poincare/arc_cosine.h @@ -35,11 +35,11 @@ class ArcCosineNode final : public ExpressionNode { //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 f5c5c07e1d5..b2a8945d66c 100644 --- a/poincare/include/poincare/arc_sine.h +++ b/poincare/include/poincare/arc_sine.h @@ -34,11 +34,11 @@ class ArcSineNode final : public ExpressionNode { //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 31147cc4af1..5522d144438 100644 --- a/poincare/include/poincare/arc_tangent.h +++ b/poincare/include/poincare/arc_tangent.h @@ -35,11 +35,11 @@ class ArcTangentNode final : public ExpressionNode { //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 edfb361db4d..348e5806f11 100644 --- a/poincare/include/poincare/arithmetic.h +++ b/poincare/include/poincare/arithmetic.h @@ -14,8 +14,8 @@ class Arithmetic final { 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, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - template static Evaluation LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + 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/based_integer.h b/poincare/include/poincare/based_integer.h index 5de001d3d78..4dd2627d978 100644 --- a/poincare/include/poincare/based_integer.h +++ b/poincare/include/poincare/based_integer.h @@ -34,8 +34,8 @@ class BasedIntegerNode final : public NumberNode { Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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: diff --git a/poincare/include/poincare/binom_cdf.h b/poincare/include/poincare/binom_cdf.h index 0d017fe072b..6d6ce8cc32c 100644 --- a/poincare/include/poincare/binom_cdf.h +++ b/poincare/include/poincare/binom_cdf.h @@ -29,9 +29,9 @@ class BinomCDFNode final : public BinomialDistributionFunctionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 515abbb415c..6f99d1180f5 100644 --- a/poincare/include/poincare/binom_pdf.h +++ b/poincare/include/poincare/binom_pdf.h @@ -29,9 +29,9 @@ class BinomPDFNode final : public BinomialDistributionFunctionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 c00bc81790e..9d5ae2b752c 100644 --- a/poincare/include/poincare/binomial_coefficient.h +++ b/poincare/include/poincare/binomial_coefficient.h @@ -30,9 +30,9 @@ class BinomialCoefficientNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/ceiling.h b/poincare/include/poincare/ceiling.h index c6acc967cd9..d027f23ecf0 100644 --- a/poincare/include/poincare/ceiling.h +++ b/poincare/include/poincare/ceiling.h @@ -31,11 +31,11 @@ class CeilingNode final : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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_argument.h b/poincare/include/poincare/complex_argument.h index 04c99178955..2169a6013a4 100644 --- a/poincare/include/poincare/complex_argument.h +++ b/poincare/include/poincare/complex_argument.h @@ -31,11 +31,11 @@ class ComplexArgumentNode final : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 37defc849ed..5f6734dcee5 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -26,8 +26,8 @@ class ComplexCartesianNode : public ExpressionNode { // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { assert(false); return Layout(); } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; @@ -38,7 +38,7 @@ class ComplexCartesianNode : public ExpressionNode { 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 { diff --git a/poincare/include/poincare/confidence_interval.h b/poincare/include/poincare/confidence_interval.h index 970b155124b..889a292beaf 100644 --- a/poincare/include/poincare/confidence_interval.h +++ b/poincare/include/poincare/confidence_interval.h @@ -32,9 +32,9 @@ class ConfidenceIntervalNode : public ExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 e019b77fe82..c7edcbee728 100644 --- a/poincare/include/poincare/conjugate.h +++ b/poincare/include/poincare/conjugate.h @@ -33,11 +33,11 @@ class ConjugateNode /*final*/ : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 33741479ece..879ae4b8d0a 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -34,8 +34,8 @@ class ConstantNode final : public SymbolAbstractNode { Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Approximation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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); } diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index 9f262ebc108..ae14e37abc2 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -38,11 +38,11 @@ class CosineNode final : public ExpressionNode { Expression unaryFunctionDifferential() override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/decimal.h b/poincare/include/poincare/decimal.h index b00af47b880..60a217438ff 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -46,10 +46,10 @@ class DecimalNode final : public NumberNode { Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return Complex::Builder(templatedApproximate()); } diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index 4398331276d..f75e7268382 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -34,12 +34,12 @@ class DerivativeNode final : public ParameteredExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; diff --git a/poincare/include/poincare/determinant.h b/poincare/include/poincare/determinant.h index b672bcaf374..6d0d2100fbf 100644 --- a/poincare/include/poincare/determinant.h +++ b/poincare/include/poincare/determinant.h @@ -28,9 +28,9 @@ class DeterminantNode /*final*/ : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } /* Approximation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 75fdf84ab7a..e03369017f4 100644 --- a/poincare/include/poincare/division.h +++ b/poincare/include/poincare/division.h @@ -33,15 +33,15 @@ template Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Approximation - virtual Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 6d4444698df..7f99045c208 100644 --- a/poincare/include/poincare/division_quotient.h +++ b/poincare/include/poincare/division_quotient.h @@ -33,9 +33,9 @@ class DivisionQuotientNode final : public ExpressionNode { // Simplification Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 183e4e3206d..f5f47538e77 100644 --- a/poincare/include/poincare/division_remainder.h +++ b/poincare/include/poincare/division_remainder.h @@ -35,9 +35,9 @@ class DivisionRemainderNode final : public ExpressionNode { // Simplification Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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_expression.h b/poincare/include/poincare/empty_expression.h index ac3d849ac62..a069965a20c 100644 --- a/poincare/include/poincare/empty_expression.h +++ b/poincare/include/poincare/empty_expression.h @@ -33,9 +33,9 @@ class EmptyExpressionNode final : public ExpressionNode { // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/equal.h b/poincare/include/poincare/equal.h index 60af231820a..fa067652f8e 100644 --- a/poincare/include/poincare/equal.h +++ b/poincare/include/poincare/equal.h @@ -28,9 +28,9 @@ class EqualNode final : public ExpressionNode { 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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 03a4f009fd1..37fb0ae2750 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -162,54 +162,60 @@ class ExpressionNode : public TreeNode { Null = 1, }; - class ReductionContext { + class 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 * context, + Preferences::ComplexFormat complexFormat, + Preferences::AngleUnit angleUnit) : m_context(context), m_complexFormat(complexFormat), - m_angleUnit(angleUnit), + 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; } + 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) {} - Context * context() { return m_context; } - Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } - Preferences::AngleUnit angleUnit() const { return m_angleUnit; } 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: - Context * m_context; - Preferences::ComplexFormat m_complexFormat; - Preferences::AngleUnit m_angleUnit; Preferences::UnitFormat m_unitFormat; ReductionTarget m_target; SymbolicComputation m_symbolicComputation; UnitConversion m_unitConversion; }; - class ApproximationContext { + class ApproximationContext : public ComputationContext { public: ApproximationContext( Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, bool withinReduce = false) : - m_context(context), - m_complexFormat(complexFormat), - m_angleUnit(angleUnit), + ComputationContext(context, complexFormat, angleUnit), m_withinReduce(withinReduce) {} - Context * context() { return m_context; } - Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } - Preferences::AngleUnit angleUnit() const { return m_angleUnit; } + ApproximationContext(ReductionContext reductionContext, bool withinReduce) : + ApproximationContext(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), withinReduce) {} bool withinReduce() const { return m_withinReduce; } private: - Context * m_context; - Preferences::ComplexFormat m_complexFormat; - Preferences::AngleUnit m_angleUnit; bool m_withinReduce; }; diff --git a/poincare/include/poincare/factor.h b/poincare/include/poincare/factor.h index d899399ba9b..3e16af3ab3f 100644 --- a/poincare/include/poincare/factor.h +++ b/poincare/include/poincare/factor.h @@ -33,9 +33,9 @@ class FactorNode /*final*/ : public ExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } /* Evaluation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { @@ -45,7 +45,7 @@ class Factor final : public Expression { 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); diff --git a/poincare/include/poincare/factorial.h b/poincare/include/poincare/factorial.h index 10ed3373ff4..2720bb8fff7 100644 --- a/poincare/include/poincare/factorial.h +++ b/poincare/include/poincare/factorial.h @@ -37,11 +37,11 @@ class FactorialNode final : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 fce3cc20e0e..1c08efe3bba 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -47,13 +47,13 @@ class FloatNode final : public NumberNode { /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Evaluation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 fee31eb698c..d2a097a8119 100644 --- a/poincare/include/poincare/floor.h +++ b/poincare/include/poincare/floor.h @@ -32,11 +32,11 @@ class FloorNode /*final*/ : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 63745f56f63..f9ae55cb6b5 100644 --- a/poincare/include/poincare/frac_part.h +++ b/poincare/include/poincare/frac_part.h @@ -33,11 +33,11 @@ class FracPartNode final : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/function.h b/poincare/include/poincare/function.h index e1e4a839929..24ffa6d4bac 100644 --- a/poincare/include/poincare/function.h +++ b/poincare/include/poincare/function.h @@ -42,9 +42,9 @@ class FunctionNode : public SymbolAbstractNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override; - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 62346b6f86d..28a4665a768 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -30,9 +30,9 @@ class GreatCommonDivisorNode final : public NAryExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 NAryExpression { diff --git a/poincare/include/poincare/hyperbolic_arc_cosine.h b/poincare/include/poincare/hyperbolic_arc_cosine.h index 16db5644cdb..0647ec03061 100644 --- a/poincare/include/poincare/hyperbolic_arc_cosine.h +++ b/poincare/include/poincare/hyperbolic_arc_cosine.h @@ -27,11 +27,11 @@ class HyperbolicArcCosineNode final : public HyperbolicTrigonometricFunctionNode 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 91fba959eef..b3a98bcc89e 100644 --- a/poincare/include/poincare/hyperbolic_arc_sine.h +++ b/poincare/include/poincare/hyperbolic_arc_sine.h @@ -25,11 +25,11 @@ class HyperbolicArcSineNode final : public HyperbolicTrigonometricFunctionNode { 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 fada17c7b58..496e2922825 100644 --- a/poincare/include/poincare/hyperbolic_arc_tangent.h +++ b/poincare/include/poincare/hyperbolic_arc_tangent.h @@ -25,11 +25,11 @@ class HyperbolicArcTangentNode final : public HyperbolicTrigonometricFunctionNod 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 0e50b832726..c33368fd068 100644 --- a/poincare/include/poincare/hyperbolic_cosine.h +++ b/poincare/include/poincare/hyperbolic_cosine.h @@ -30,11 +30,11 @@ class HyperbolicCosineNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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_sine.h b/poincare/include/poincare/hyperbolic_sine.h index 47f0b00c97e..408eae86d90 100644 --- a/poincare/include/poincare/hyperbolic_sine.h +++ b/poincare/include/poincare/hyperbolic_sine.h @@ -28,11 +28,11 @@ class HyperbolicSineNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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_tangent.h b/poincare/include/poincare/hyperbolic_tangent.h index 36db08f5dcc..4b1fa04bc7a 100644 --- a/poincare/include/poincare/hyperbolic_tangent.h +++ b/poincare/include/poincare/hyperbolic_tangent.h @@ -28,11 +28,11 @@ class HyperbolicTangentNode final : public HyperbolicTrigonometricFunctionNode { Expression unaryFunctionDifferential() override; //Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/imaginary_part.h b/poincare/include/poincare/imaginary_part.h index f43de3ee072..373f93a34f1 100644 --- a/poincare/include/poincare/imaginary_part.h +++ b/poincare/include/poincare/imaginary_part.h @@ -34,11 +34,11 @@ class ImaginaryPartNode final : public ExpressionNode { template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::imag(c)); } - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 3936a3267c4..b4d6aa02c5d 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -27,10 +27,10 @@ class InfinityNode final : public NumberNode { Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/integral.h b/poincare/include/poincare/integral.h index 16472434118..c7200180135 100644 --- a/poincare/include/poincare/integral.h +++ b/poincare/include/poincare/integral.h @@ -31,9 +31,9 @@ class IntegralNode final : public ParameteredExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; LayoutShape rightLayoutShape() const override { return LayoutShape::MoreLetters; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 @@ class IntegralNode final : public ParameteredExpressionNode { #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/inv_binom.h b/poincare/include/poincare/inv_binom.h index e4f30699b74..229aa982c7f 100644 --- a/poincare/include/poincare/inv_binom.h +++ b/poincare/include/poincare/inv_binom.h @@ -30,9 +30,9 @@ class InvBinomNode final : public BinomialDistributionFunctionNode { Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 dc27eadf7f4..981a0188226 100644 --- a/poincare/include/poincare/inv_norm.h +++ b/poincare/include/poincare/inv_norm.h @@ -30,9 +30,9 @@ class InvNormNode final : public NormalDistributionFunctionNode { Expression shallowReduce(ReductionContext reductionContext) override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index f73dc7e1329..10545e171e4 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -30,9 +30,9 @@ class LeastCommonMultipleNode final : public NAryExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 LeastCommonMultiple final : public NAryExpression { diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index 1c0ab9edea6..f56f740d0c6 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -42,9 +42,9 @@ class LogarithmNode final : public ExpressionNode { * (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, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { @@ -61,7 +61,7 @@ class Logarithm final : public Expression { 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 25456b7fdad..35ef396298b 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -40,18 +40,18 @@ class MatrixNode /*final*/ : public ExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; + template Evaluation templatedApproximate(ApproximationContext approximationContext) 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. */ diff --git a/poincare/include/poincare/matrix_dimension.h b/poincare/include/poincare/matrix_dimension.h index 579ddd89006..7fbb30af3d5 100644 --- a/poincare/include/poincare/matrix_dimension.h +++ b/poincare/include/poincare/matrix_dimension.h @@ -29,9 +29,9 @@ class MatrixDimensionNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 index cda533715e0..1dac327d99c 100644 --- a/poincare/include/poincare/matrix_echelon_form.h +++ b/poincare/include/poincare/matrix_echelon_form.h @@ -21,9 +21,9 @@ class MatrixEchelonFormNode : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; // Properties virtual const char * functionHelperName() const = 0; diff --git a/poincare/include/poincare/matrix_identity.h b/poincare/include/poincare/matrix_identity.h index 7755422895b..d9a0a1ea196 100644 --- a/poincare/include/poincare/matrix_identity.h +++ b/poincare/include/poincare/matrix_identity.h @@ -27,9 +27,9 @@ class MatrixIdentityNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 dff666e1a63..4eae80e3220 100644 --- a/poincare/include/poincare/matrix_inverse.h +++ b/poincare/include/poincare/matrix_inverse.h @@ -28,9 +28,9 @@ class MatrixInverseNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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_trace.h b/poincare/include/poincare/matrix_trace.h index 6ff79e471ac..781ec702ee5 100644 --- a/poincare/include/poincare/matrix_trace.h +++ b/poincare/include/poincare/matrix_trace.h @@ -28,9 +28,9 @@ class MatrixTraceNode /*final*/ : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 fc710e9337e..62a1e015fa2 100644 --- a/poincare/include/poincare/matrix_transpose.h +++ b/poincare/include/poincare/matrix_transpose.h @@ -28,9 +28,9 @@ class MatrixTransposeNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 2007f258be9..452cfcb61ac 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -56,11 +56,11 @@ class MultiplicationNode final : public NAryInfixExpressionNode { 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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); } }; diff --git a/poincare/include/poincare/naperian_logarithm.h b/poincare/include/poincare/naperian_logarithm.h index 6c10d3e3ae1..799c58596ce 100644 --- a/poincare/include/poincare/naperian_logarithm.h +++ b/poincare/include/poincare/naperian_logarithm.h @@ -35,11 +35,11 @@ class NaperianLogarithmNode final : public ExpressionNode { * (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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 c0c113ce8a8..511786f2ef2 100644 --- a/poincare/include/poincare/norm_cdf.h +++ b/poincare/include/poincare/norm_cdf.h @@ -29,9 +29,9 @@ class NormCDFNode final : public NormalDistributionFunctionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 067663eb9c1..404d0fe68a8 100644 --- a/poincare/include/poincare/norm_cdf2.h +++ b/poincare/include/poincare/norm_cdf2.h @@ -31,9 +31,9 @@ class NormCDF2Node final : public NormalDistributionFunctionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 f9bd816480b..ae9a2280fc1 100644 --- a/poincare/include/poincare/norm_pdf.h +++ b/poincare/include/poincare/norm_pdf.h @@ -29,9 +29,9 @@ class NormPDFNode final : public NormalDistributionFunctionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/nth_root.h b/poincare/include/poincare/nth_root.h index fbb729e9a18..dc29c752539 100644 --- a/poincare/include/poincare/nth_root.h +++ b/poincare/include/poincare/nth_root.h @@ -29,9 +29,9 @@ class NthRootNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::NthRoot; }; LayoutShape rightLayoutShape() const override { return LayoutShape::Root; }; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/opposite.h b/poincare/include/poincare/opposite.h index 441521a66d9..87352d21173 100644 --- a/poincare/include/poincare/opposite.h +++ b/poincare/include/poincare/opposite.h @@ -30,11 +30,11 @@ class OppositeNode /*final*/ : public ExpressionNode { bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 f11810b5aa3..e2fcd0a5152 100644 --- a/poincare/include/poincare/parenthesis.h +++ b/poincare/include/poincare/parenthesis.h @@ -33,10 +33,10 @@ class ParenthesisNode /*final*/ : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/permute_coefficient.h b/poincare/include/poincare/permute_coefficient.h index a2886babb21..7af4d70de41 100644 --- a/poincare/include/poincare/permute_coefficient.h +++ b/poincare/include/poincare/permute_coefficient.h @@ -33,9 +33,9 @@ class PermuteCoefficientNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 72c8d0404d6..c9a25279d0d 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -64,13 +64,13 @@ class PowerNode final : public ExpressionNode { 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, ApproximateContext approximateContext) const override { - return templatedApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templatedApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { diff --git a/poincare/include/poincare/prediction_interval.h b/poincare/include/poincare/prediction_interval.h index 4691311b568..4ec7cdf586e 100644 --- a/poincare/include/poincare/prediction_interval.h +++ b/poincare/include/poincare/prediction_interval.h @@ -31,9 +31,9 @@ class PredictionIntervalNode /*final*/ : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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/randint.h b/poincare/include/poincare/randint.h index e7091027c66..af068d593fb 100644 --- a/poincare/include/poincare/randint.h +++ b/poincare/include/poincare/randint.h @@ -28,13 +28,13 @@ class RandintNode /*final*/ : public ExpressionNode { 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, ApproximateContext approximateContext) const override { - return templateApproximate(context, complexFormat, angleUnit); + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { + return templateApproximate(approximationContext); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 712b421ab44..c6a7dae9e89 100644 --- a/poincare/include/poincare/random.h +++ b/poincare/include/poincare/random.h @@ -32,10 +32,10 @@ class RandomNode final : public ExpressionNode { 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, ApproximateContext approximateContext) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templateApproximate(); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 43dce23006e..9215e2e2a7e 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -39,8 +39,8 @@ class RationalNode final : public NumberNode { Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return Complex::Builder(templatedApproximate()); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 diff --git a/poincare/include/poincare/real_part.h b/poincare/include/poincare/real_part.h index b4a5c728ec2..1200cb9c652 100644 --- a/poincare/include/poincare/real_part.h +++ b/poincare/include/poincare/real_part.h @@ -36,11 +36,11 @@ class RealPartNode final : public ExpressionNode { template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { return Complex::Builder(std::real(c)); } - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/round.h b/poincare/include/poincare/round.h index 9de713c55d1..3e3dc596254 100644 --- a/poincare/include/poincare/round.h +++ b/poincare/include/poincare/round.h @@ -31,9 +31,9 @@ class RoundNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 f2bd6ee557d..a5e7656def0 100644 --- a/poincare/include/poincare/sequence.h +++ b/poincare/include/poincare/sequence.h @@ -34,9 +34,9 @@ class SequenceNode : public SymbolAbstractNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override; - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 Sequence : public SymbolAbstract { diff --git a/poincare/include/poincare/sign_function.h b/poincare/include/poincare/sign_function.h index 968abf164ca..b8445938d7c 100644 --- a/poincare/include/poincare/sign_function.h +++ b/poincare/include/poincare/sign_function.h @@ -35,11 +35,11 @@ class SignFunctionNode final : public ExpressionNode { 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 4579180cc5b..1858479e83c 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -39,11 +39,11 @@ class SineNode final : public ExpressionNode { Expression unaryFunctionDifferential() override; // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/square_root.h b/poincare/include/poincare/square_root.h index 45dfe04363d..6f40f3f7b97 100644 --- a/poincare/include/poincare/square_root.h +++ b/poincare/include/poincare/square_root.h @@ -32,11 +32,11 @@ class SquareRootNode /*final*/ : public ExpressionNode { 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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 5613c1e15c9..cc89d40ec71 100644 --- a/poincare/include/poincare/store.h +++ b/poincare/include/poincare/store.h @@ -23,9 +23,9 @@ class StoreNode /*final*/ : public RightwardsArrowExpressionNode { // Simplification Expression shallowReduce(ReductionContext reductionContext) override; // Evalutation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 @@ friend class StoreNode; 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 5ceeab30a6a..ed74653d21a 100644 --- a/poincare/include/poincare/subtraction.h +++ b/poincare/include/poincare/subtraction.h @@ -28,11 +28,11 @@ class SubtractionNode final : public ExpressionNode { // 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, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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_and_product.h b/poincare/include/poincare/sum_and_product.h index 63ea7a7b63b..54f038bfed6 100644 --- a/poincare/include/poincare/sum_and_product.h +++ b/poincare/include/poincare/sum_and_product.h @@ -17,9 +17,9 @@ class SumAndProductNode : public ParameteredExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; }; /* Approximation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; 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; diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index ca05847999b..4f18e6176e4 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -40,15 +40,15 @@ class SymbolNode final : public SymbolAbstractNode { bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; /* Approximation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { diff --git a/poincare/include/poincare/tangent.h b/poincare/include/poincare/tangent.h index 7bda46d9784..b83d5171f9f 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -37,11 +37,11 @@ class TangentNode final : public ExpressionNode { // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Radian); - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) 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, ApproximateContext approximateContext) 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/undefined.h b/poincare/include/poincare/undefined.h index 0a4f34a7ecb..9a09fe95f64 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -22,10 +22,10 @@ class UndefinedNode : public NumberNode { Expression setSign(Sign s, ReductionContext reductionContext) override; // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 77f63fd9158..ded37f0b89e 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -465,8 +465,8 @@ class UnitNode final : public ExpressionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; /* Approximation */ - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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; @@ -482,7 +482,7 @@ class UnitNode final : public ExpressionNode { void setPrefix(const Prefix * prefix) { m_prefix = prefix; } private: - template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + template Evaluation templatedApproximate(ApproximationContext approximationContext) const; const Representative * m_representative; const Prefix * m_prefix; diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index 1a046500bbb..c4cccf592de 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -25,9 +25,9 @@ class UnitConvertNode /*final*/ : public RightwardsArrowExpressionNode { void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override; Expression shallowBeautify(ReductionContext reductionContext) override; // Evalutation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 { diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index 8ab669aaefc..7e4290e3eab 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -20,10 +20,10 @@ class UnrealNode final : public UndefinedNode { Type type() const override { return Type::Unreal; } // Approximation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) const override { + Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(); } diff --git a/poincare/include/poincare/vector_cross.h b/poincare/include/poincare/vector_cross.h index c878ddb939f..8969bd8b885 100644 --- a/poincare/include/poincare/vector_cross.h +++ b/poincare/include/poincare/vector_cross.h @@ -28,9 +28,9 @@ class VectorCrossNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 VectorCross final : public Expression { diff --git a/poincare/include/poincare/vector_dot.h b/poincare/include/poincare/vector_dot.h index fe4a45fd5f0..25c1aa670d8 100644 --- a/poincare/include/poincare/vector_dot.h +++ b/poincare/include/poincare/vector_dot.h @@ -28,9 +28,9 @@ class VectorDotNode final : public ExpressionNode { LayoutShape leftLayoutShape() const override { return LayoutShape::MoreLetters; }; LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 VectorDot final : public Expression { diff --git a/poincare/include/poincare/vector_norm.h b/poincare/include/poincare/vector_norm.h index 0e0cf600ffc..10eae04f3cb 100644 --- a/poincare/include/poincare/vector_norm.h +++ b/poincare/include/poincare/vector_norm.h @@ -28,9 +28,9 @@ class VectorNormNode final : public ExpressionNode { Expression shallowReduce(ReductionContext reductionContext) override; LayoutShape leftLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Evaluation - Evaluation approximate(SinglePrecision p, ApproximateContext approximateContext) const override { return templatedApproximate(context, complexFormat, angleUnit); } - Evaluation approximate(DoublePrecision p, ApproximateContext approximateContext) 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 VectorNorm final : public Expression { diff --git a/poincare/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index dd37f3e1fdc..a6e112232c8 100644 --- a/poincare/src/absolute_value.cpp +++ b/poincare/src/absolute_value.cpp @@ -52,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(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true).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 diff --git a/poincare/src/approximation_helper.cpp b/poincare/src/approximation_helper.cpp index 8224024fa6c..77b924eb4f6 100644 --- a/poincare/src/approximation_helper.cpp +++ b/poincare/src/approximation_helper.cpp @@ -44,8 +44,8 @@ T ApproximationHelper::Epsilon() { return precision; } -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); +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; @@ -79,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()) { @@ -145,14 +145,14 @@ template MatrixComplex ApproximationHelper::ElementWiseOnComplexM } template float Poincare::ApproximationHelper::Epsilon(); template double Poincare::ApproximationHelper::Epsilon(); -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 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 e6587938539..79bbd7adfbe 100644 --- a/poincare/src/arithmetic.cpp +++ b/poincare/src/arithmetic.cpp @@ -109,15 +109,15 @@ Expression Arithmetic::LCM(const Expression & expression) { } template -Evaluation applyAssociativeFunctionOnChildren(const ExpressionNode & expressionNode, int (*f)(int, int), Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +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, context, complexFormat, angleUnit); + 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, context, complexFormat, angleUnit); + int b = ApproximationHelper::PositiveIntegerApproximationIfPossible(expressionNode.childAtIndex(i), &isUndefined, approximationContext); if (isUndefined) { return Complex::RealUndefined(); } @@ -127,15 +127,15 @@ Evaluation applyAssociativeFunctionOnChildren(const ExpressionNode & expressi } template -Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext) { // Evaluate GCD of expression's children - return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::GCD, context, complexFormat, angleUnit); + return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::GCD, approximationContext); } template -Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { +Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, ExpressionNode::ApproximationContext approximationContext) { // Evaluate LCM of expression's children - return applyAssociativeFunctionOnChildren(expressionNode, Arithmetic::LCM, context, complexFormat, angleUnit); + 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, @@ -200,9 +200,9 @@ int Arithmetic::PrimeFactorization(const Integer & n, Integer outputFactors[], I return t+1; } -template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); -template Evaluation Arithmetic::GCD(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); -template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); -template Evaluation Arithmetic::LCM(const ExpressionNode & expressionNode, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); +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/binom_cdf.cpp b/poincare/src/binom_cdf.cpp index 43e8d87f9f5..7a83ebf5bac 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 12305f0bb06..f285721d071 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 a14ef202913..0cdf5208af1 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)); diff --git a/poincare/src/complex_argument.cpp b/poincare/src/complex_argument.cpp index 02f258abc04..2c761454b14 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(), true).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 b02cd4199c5..c0dbf3e57ae 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -37,9 +37,9 @@ Expression ComplexCartesianNode::shallowBeautify(ReductionContext reductionConte } 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(); diff --git a/poincare/src/confidence_interval.cpp b/poincare/src/confidence_interval.cpp index 688cee557c2..c4d546f4314 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/derivative.cpp b/poincare/src/derivative.cpp index aba4808b4ce..660d7b13b12 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -40,10 +40,10 @@ Expression DerivativeNode::shallowReduce(ReductionContext 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(); @@ -55,7 +55,7 @@ Evaluation DerivativeNode::templatedApproximate(Context * context, Preference 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++) { diff --git a/poincare/src/determinant.cpp b/poincare/src/determinant.cpp index f8b2e5c2dd2..cb8d588c2c4 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_quotient.cpp b/poincare/src/division_quotient.cpp index f081031de5f..8a57e309916 100644 --- a/poincare/src/division_quotient.cpp +++ b/poincare/src/division_quotient.cpp @@ -33,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 842315fe1f9..99eb8c1dcba 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 a72775bffa4..d1fe2166962 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/equal.cpp b/poincare/src/equal.cpp index 1d5091d88ae..4468a2e32d4 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -39,7 +39,7 @@ 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(); } diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index fb57e0dd379..6154d763535 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -375,7 +375,7 @@ Expression Expression::defaultHandleUnitsInChildren() { } Expression Expression::shallowReduceUsingApproximation(ExpressionNode::ReductionContext reductionContext) { - double approx = node()->approximate(double(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true).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). */ @@ -449,7 +449,7 @@ Evaluation Expression::approximateToEvaluation(Context * context, Preferences sApproximationEncounteredComplex = false; // Reset interrupting flag because some evaluation methods use it sSimplificationHasBeenInterrupted = false; - Evaluation e = node()->approximate(U(), context, complexFormat, angleUnit, withinReduce); + Evaluation e = node()->approximate(U(), ExpressionNode::ApproximationContext(context, complexFormat, angleUnit, withinReduce)); if (complexFormat == Preferences::ComplexFormat::Real && sApproximationEncounteredComplex) { e = Complex::Undefined(); } @@ -1160,8 +1160,8 @@ template Expression Expression::approximate(Context * context, Preferenc 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, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, bool withinReduce); -template double Expression::ApproximateToScalar(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, Preferences::UnitFormat unitFormat, ExpressionNode::SymbolicComputation symbolicComputation, bool withinReduce); +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, bool withinReduce) const; template Evaluation Expression::approximateToEvaluation(Context * context, Preferences::ComplexFormat, Preferences::AngleUnit angleUnit, bool withinReduce) const; diff --git a/poincare/src/factor.cpp b/poincare/src/factor.cpp index 85ee0adb561..bff8a70f69d 100644 --- a/poincare/src/factor.cpp +++ b/poincare/src/factor.cpp @@ -37,8 +37,8 @@ Expression FactorNode::shallowBeautify(ReductionContext 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(); @@ -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/function.cpp b/poincare/src/function.cpp index 1d966416db9..023a5ed0f03 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -60,26 +60,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 51d6a6fcc61..5641df6c162 100644 --- a/poincare/src/great_common_divisor.cpp +++ b/poincare/src/great_common_divisor.cpp @@ -24,8 +24,8 @@ Expression GreatCommonDivisorNode::shallowBeautify(ReductionContext reductionCon } template -Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Arithmetic::GCD(*this, context, complexFormat, angleUnit); +Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const { + return Arithmetic::GCD(*this, approximationContext); } Expression GreatCommonDivisor::shallowBeautify(Context * context) { @@ -64,7 +64,7 @@ Expression GreatCommonDivisor::shallowReduce(ExpressionNode::ReductionContext re return result; } -template Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation GreatCommonDivisorNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation GreatCommonDivisorNode::templatedApproximate(ApproximationContext approximationContext) const; } diff --git a/poincare/src/hyperbolic_trigonometric_function.cpp b/poincare/src/hyperbolic_trigonometric_function.cpp index 76dfa541f02..34b63548911 100644 --- a/poincare/src/hyperbolic_trigonometric_function.cpp +++ b/poincare/src/hyperbolic_trigonometric_function.cpp @@ -81,8 +81,8 @@ Expression HyperbolicTrigonometricFunction::shallowReduce(ExpressionNode::Reduct && std::fabs(e.approximateToScalar( reductionContext.context(), reductionContext.complexFormat(), - reductionContext.angleUnit()), - true) < 1.0)) + reductionContext.angleUnit(), + true)) < 1.0)) { result = e; } diff --git a/poincare/src/integral.cpp b/poincare/src/integral.cpp index 55641980a54..7c4643855f1 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/inv_binom.cpp b/poincare/src/inv_binom.cpp index 0b5cc6e7cb0..3e75b3e1ce3 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 1e1de33828d..1fbbec3d76c 100644 --- a/poincare/src/inv_norm.cpp +++ b/poincare/src/inv_norm.cpp @@ -26,10 +26,10 @@ 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 sigmaEvaluation = 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(); diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 6d49b02a493..6702aaac805 100644 --- a/poincare/src/least_common_multiple.cpp +++ b/poincare/src/least_common_multiple.cpp @@ -24,8 +24,8 @@ Expression LeastCommonMultipleNode::shallowBeautify(ReductionContext reductionCo } template -Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return Arithmetic::LCM(*this, context, complexFormat, angleUnit); +Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const { + return Arithmetic::LCM(*this, approximationContext); } Expression LeastCommonMultiple::shallowBeautify(Context * context) { @@ -64,7 +64,7 @@ Expression LeastCommonMultiple::shallowReduce(ExpressionNode::ReductionContext r return result; } -template Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation LeastCommonMultipleNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation LeastCommonMultipleNode::templatedApproximate(ApproximationContext approximationContext) const; } diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 1a093958ac8..bc9d96ad1a6 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -105,19 +105,19 @@ Expression LogarithmNode<2>::shallowBeautify(ReductionContext reductionContext) } 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); } @@ -171,7 +171,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; } @@ -264,7 +264,7 @@ 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 @@ -275,7 +275,7 @@ Expression Logarithm::simpleShallowReduce(Context * context, Preferences::Comple if (b.type() == ExpressionNode::Type::Rational && static_cast(b).isOne()) { return replaceWithUndefinedInPlace(); } - bool infiniteArg = c.recursivelyMatches(Expression::IsInfinity, context); + 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(); @@ -293,14 +293,14 @@ Expression Logarithm::simpleShallowReduce(Context * context, Preferences::Comple const Rational r = static_cast(c); // log(0, x) = -inf if x > 1 && x != inf || inf x < 1 || undef if x < 0 if (r.isZero()) { - bool infiniteBase = b.recursivelyMatches(Expression::IsInfinity, context); + bool infiniteBase = b.recursivelyMatches(Expression::IsInfinity, reductionContext.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, true); + Evaluation baseApproximation = b.node()->approximate(1.0f, ExpressionNode::ApproximationContext(reductionContext, true)); std::complex logDenominator = std::log10(static_cast&>(baseApproximation).stdComplex()); if (logDenominator.imag() != 0.0f || logDenominator.real() == 0.0f) { result = Undefined::Builder(); @@ -401,7 +401,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); } @@ -425,10 +425,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 8aa47751cd2..5dd9f56a017 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -99,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); diff --git a/poincare/src/matrix_dimension.cpp b/poincare/src/matrix_dimension.cpp index b6514bab825..aa294244a26 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 index cffcad69593..6f8eed8936e 100644 --- a/poincare/src/matrix_echelon_form.cpp +++ b/poincare/src/matrix_echelon_form.cpp @@ -21,8 +21,8 @@ int MatrixEchelonFormNode::serialize(char * buffer, int bufferSize, Preferences: } template -Evaluation MatrixEchelonFormNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +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()); diff --git a/poincare/src/matrix_identity.cpp b/poincare/src/matrix_identity.cpp index f9dd65b67d2..526e55576e4 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 ec994bb7c62..bd7f795036e 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_trace.cpp b/poincare/src/matrix_trace.cpp index f678e3bbee6..427e6c528d0 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); } diff --git a/poincare/src/matrix_transpose.cpp b/poincare/src/matrix_transpose.cpp index 8c7452b8b55..ea648386872 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/norm_cdf.cpp b/poincare/src/norm_cdf.cpp index 2775cc41899..90b3135242e 100644 --- a/poincare/src/norm_cdf.cpp +++ b/poincare/src/norm_cdf.cpp @@ -24,10 +24,10 @@ 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 sigmaEvaluation = 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(); diff --git a/poincare/src/norm_cdf2.cpp b/poincare/src/norm_cdf2.cpp index 625451406d8..a1cbb63e4e9 100644 --- a/poincare/src/norm_cdf2.cpp +++ b/poincare/src/norm_cdf2.cpp @@ -24,11 +24,11 @@ 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 sigmaEvaluation = 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(); diff --git a/poincare/src/norm_pdf.cpp b/poincare/src/norm_pdf.cpp index bb3ccee3fdf..5ab9011a8b4 100644 --- a/poincare/src/norm_pdf.cpp +++ b/poincare/src/norm_pdf.cpp @@ -24,10 +24,10 @@ 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 sigmaEvaluation = 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(); diff --git a/poincare/src/nth_root.cpp b/poincare/src/nth_root.cpp index b5e63ca0177..c1a8be9f6a8 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/parenthesis.cpp b/poincare/src/parenthesis.cpp index ca514b07983..cd06aeb3bd8 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/permute_coefficient.cpp b/poincare/src/permute_coefficient.cpp index 9207a1b1f03..50121a18870 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 5a76875eb74..6738c1d2470 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -293,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; } @@ -334,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 @@ -1393,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()}; diff --git a/poincare/src/prediction_interval.cpp b/poincare/src/prediction_interval.cpp index 0893818e68c..6aad86c95c4 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/randint.cpp b/poincare/src/randint.cpp index 52d15e7f959..16864410f3a 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/round.cpp b/poincare/src/round.cpp index 8b370ca9247..a5acc4aaf05 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 e6177bc1978..82f2023aaa7 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -53,24 +53,24 @@ Expression SequenceNode::shallowReduce(ReductionContext reductionContext) { return Sequence(this).shallowReduce(reductionContext); } -Evaluation SequenceNode::approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return templatedApproximate(context, complexFormat, angleUnit); +Evaluation SequenceNode::approximate(SinglePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); } -Evaluation SequenceNode::approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - return templatedApproximate(context, complexFormat, angleUnit); +Evaluation SequenceNode::approximate(DoublePrecision p, ApproximationContext approximationContext) const { + return templatedApproximate(approximationContext); } template -Evaluation SequenceNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - if (childAtIndex(0)->approximate((T)1, context, complexFormat, angleUnit).isUndefined()) { +Evaluation SequenceNode::templatedApproximate(ApproximationContext approximationContext) const { + if (childAtIndex(0)->approximate((T)1, approximationContext).isUndefined()) { return Complex::Undefined(); } - Expression e = context->expressionForSymbolAbstract(this, false); + Expression e = approximationContext.context()->expressionForSymbolAbstract(this, false); if (e.isUninitialized()) { return Complex::Undefined(); } - return e.node()->approximate(T(), context, complexFormat, angleUnit); + return e.node()->approximate(T(), approximationContext); } Sequence Sequence::Builder(const char * name, size_t length, Expression child) { diff --git a/poincare/src/sign_function.cpp b/poincare/src/sign_function.cpp index 69447828d5a..49bea3f6b02 100644 --- a/poincare/src/sign_function.cpp +++ b/poincare/src/sign_function.cpp @@ -67,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(), true); + 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/store.cpp b/poincare/src/store.cpp index ddd1daa641b..1c0d82aa839 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_and_product.cpp b/poincare/src/sum_and_product.cpp index c5f6cd5b359..2cf0f1f4f11 100644 --- a/poincare/src/sum_and_product.cpp +++ b/poincare/src/sum_and_product.cpp @@ -24,16 +24,16 @@ Expression SumAndProductNode::shallowReduce(ReductionContext reductionContext) { } template -Evaluation SumAndProductNode::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 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(), context); + 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()) { @@ -47,7 +47,8 @@ Evaluation SumAndProductNode::templatedApproximate(Context * context, Prefere * have. We can then evaluate its value */ child.childAtIndex(0).replaceSymbolWithExpression(symbol, Float::Builder(i)); } - result = evaluateWithNextTerm(T(), result, child.node()->approximate(T(), &nContext, complexFormat, angleUnit), complexFormat); + approximationContext.setContext(&nContext); + result = evaluateWithNextTerm(T(), result, child.node()->approximate(T(), approximationContext), approximationContext.complexFormat()); if (result.isUndefined()) { return Complex::Undefined(); } @@ -70,7 +71,7 @@ Expression SumAndProduct::shallowReduce(Context * context) { return *this; } -template Evaluation SumAndProductNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; -template Evaluation SumAndProductNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; +template Evaluation SumAndProductNode::templatedApproximate(ApproximationContext approximationContext) const; +template Evaluation SumAndProductNode::templatedApproximate(ApproximationContext approximationContext) const; -} \ No newline at end of file +} diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 29e0c28afeb..efa74e01658 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -91,13 +91,13 @@ bool SymbolNode::derivate(ReductionContext reductionContext, Expression symbol, } 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 { diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index 09b87aff048..c9127000fae 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -20,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; } diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index e0ff2bc934d..51ccdce306b 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -286,7 +286,7 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expression // Step 1. Look for an expression of type "acos(cos(x))", return x if (AreInverseFunctions(e.childAtIndex(0), e)) { - float x = e.childAtIndex(0).childAtIndex(0).nodex()->approximate(float(), reductionContext.context(), reductionContext.complexFormat(), angleUnit, true).toScalar(); + 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) @@ -314,7 +314,7 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Expression // 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, true).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); diff --git a/poincare/src/trigonometry_cheat_table.cpp b/poincare/src/trigonometry_cheat_table.cpp index bcd1e290501..60515d6c5db 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()->approximation(float(), reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), true).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 d6d355da789..8ba9a1d9ce4 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -706,7 +706,7 @@ Expression UnitNode::shallowBeautify(ReductionContext reductionContext) { } template -Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { +Evaluation UnitNode::templatedApproximate(ApproximationContext approximationContext) const { return Complex::Undefined(); } @@ -951,7 +951,7 @@ void Unit::chooseBestRepresentativeAndPrefix(double * value, double exponent, Ex } } -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 c2984c386a6..9d5ea930937 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -29,7 +29,7 @@ void UnitConvertNode::deepReduceChildren(ExpressionNode::ReductionContext reduct } 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. */ @@ -109,7 +109,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti return result.shallowBeautify(reductionContextWithoutUnits); } -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/vector_cross.cpp b/poincare/src/vector_cross.cpp index dc5f6dc196e..6ee28808376 100644 --- a/poincare/src/vector_cross.cpp +++ b/poincare/src/vector_cross.cpp @@ -24,9 +24,9 @@ int VectorCrossNode::serialize(char * buffer, int bufferSize, Preferences::Print } template -Evaluation VectorCrossNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input0 = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation input1 = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +Evaluation VectorCrossNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input0 = childAtIndex(0)->approximate(T(), approximationContext); + Evaluation input1 = childAtIndex(1)->approximate(T(), approximationContext); return input0.cross(&input1); } diff --git a/poincare/src/vector_dot.cpp b/poincare/src/vector_dot.cpp index 5dc5d23f1a2..aaabfe3c1b8 100644 --- a/poincare/src/vector_dot.cpp +++ b/poincare/src/vector_dot.cpp @@ -24,9 +24,9 @@ int VectorDotNode::serialize(char * buffer, int bufferSize, Preferences::PrintFl } template -Evaluation VectorDotNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input0 = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); - Evaluation input1 = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); +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)); } diff --git a/poincare/src/vector_norm.cpp b/poincare/src/vector_norm.cpp index 85a3ff59b28..c428d8b5e92 100644 --- a/poincare/src/vector_norm.cpp +++ b/poincare/src/vector_norm.cpp @@ -25,8 +25,8 @@ int VectorNormNode::serialize(char * buffer, int bufferSize, Preferences::PrintF } template -Evaluation VectorNormNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { - Evaluation input = childAtIndex(0)->approximate(T(), context, complexFormat, angleUnit); +Evaluation VectorNormNode::templatedApproximate(ApproximationContext approximationContext) const { + Evaluation input = childAtIndex(0)->approximate(T(), approximationContext); return Complex::Builder(input.norm()); } From 4cc3542a99249c5650253cb0b54bdfd5558e21f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 17:40:16 +0100 Subject: [PATCH 389/560] [poincare] Sequence: when approximating within a reduce routine, escape to avoid infinite loop --- poincare/src/sequence.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index 82f2023aaa7..a86ed08ea52 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -63,7 +63,16 @@ Evaluation SequenceNode::approximate(DoublePrecision p, ApproximationCon template Evaluation SequenceNode::templatedApproximate(ApproximationContext approximationContext) const { - if (childAtIndex(0)->approximate((T)1, approximationContext).isUndefined()) { + if (childAtIndex(0)->approximate((T)1, approximationContext).isUndefined() || approximationContext.withinReduce()) { + /* 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(); } Expression e = approximationContext.context()->expressionForSymbolAbstract(this, false); From cf2cbd109eedaf61e1946bc0f9e84c21955caff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 18:06:53 +0100 Subject: [PATCH 390/560] [apps/sequence] Add breaking test --- apps/sequence/test/sequence.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 7d1fdc06fac..c7bc064e935 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -443,6 +443,16 @@ QUIZ_CASE(sequence_evaluation) { 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) { From 5e83bee589e7f5f7a1a46e84478efd68ca74fa5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 3 Nov 2020 18:07:29 +0100 Subject: [PATCH 391/560] [apps/shared] ExpressionModel: expressionReduced can call itself so it need to keep a valid m_expression --- apps/shared/expression_model.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index 307956e02d1..54170b6447a 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; } } } From c5beb6fdd24dffa27ac5a06b6c6aa602970f5f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 4 Nov 2020 12:17:32 +0100 Subject: [PATCH 392/560] [app/sequence] Test: avoid duplicating sequence stores and ensure to tidy it (empty the Poincare Pool) between quiz_cases --- apps/sequence/test/sequence.cpp | 21 +++++++++++++-------- apps/shared/global_context.cpp | 2 -- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index c7bc064e935..fb729ab14c1 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -30,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++) { @@ -46,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) { diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 18bacf7b749..5df9889538f 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -18,8 +18,6 @@ SequenceStore * GlobalContext::sequenceStore() { return &sequenceStore; } - - bool GlobalContext::SymbolAbstractNameIsFree(const char * baseName) { return SymbolAbstractRecordWithBaseName(baseName).isNull(); } From a7b55776d60516e1c4ad2ab08b509ff1e7551617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 4 Nov 2020 12:18:26 +0100 Subject: [PATCH 393/560] [sequence] Remove unused function --- apps/shared/sequence.cpp | 9 --------- apps/shared/sequence.h | 1 - 2 files changed, 10 deletions(-) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index fbc3d873261..9f47baa1a83 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -55,15 +55,6 @@ int Sequence::nameWithArgumentAndType(char * buffer, size_t bufferSize) { return result; } -void Sequence::tidy() { - m_definition.tidyName(); - Function::tidy(); // m_definitionName.tidy() - m_firstInitialCondition.tidy(); - m_firstInitialCondition.tidyName(); - m_secondInitialCondition.tidy(); - m_secondInitialCondition.tidyName(); -} - Sequence::Type Sequence::type() const { return recordData()->type(); } diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index cd9421c6b93..b0c3f1f5dfb 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -31,7 +31,6 @@ friend class SequenceStore; CodePoint symbol() const override { return 'n'; } int nameWithArgument(char * buffer, size_t bufferSize) override; int nameWithArgumentAndType(char * buffer, size_t bufferSize); - void tidy() override; // MetaData getters Type type() const; int initialRank() const; From c7711b74c2f59bfe6a70d3523e4eb97ef773711f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 4 Nov 2020 12:20:07 +0100 Subject: [PATCH 394/560] [poincare] Sequence: reorder condition (optimization) --- poincare/src/sequence.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/sequence.cpp b/poincare/src/sequence.cpp index a86ed08ea52..bae19166c4e 100644 --- a/poincare/src/sequence.cpp +++ b/poincare/src/sequence.cpp @@ -63,7 +63,7 @@ Evaluation SequenceNode::approximate(DoublePrecision p, ApproximationCon template Evaluation SequenceNode::templatedApproximate(ApproximationContext approximationContext) const { - if (childAtIndex(0)->approximate((T)1, approximationContext).isUndefined() || approximationContext.withinReduce()) { + 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), From 95aa47625c9f99f23273294c19c85987f825f08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 4 Nov 2020 13:44:04 +0100 Subject: [PATCH 395/560] [.github] ci-workflow: missing line --- .github/workflows/ci-workflow.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 849adb7766f..17613d75eb3 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -3,11 +3,12 @@ name: Continuous integration on: pull_request: workflow_dispatch: - triggerIos: + inputs: + triggerIos: description: 'Run iOS tests' required: true default: 'no' - triggerMacos: + triggerMacos: description: 'Run macOS tests' required: true default: 'no' From 7f6a3f0fc12709699a1876b7be22061fd564da7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 4 Nov 2020 11:08:15 +0100 Subject: [PATCH 396/560] [apps/curve_view] Fix KDCoordinate overflow Scenario: (with the current auto zoom algorithm) Graph f(x) = x! -> Auto axes are x=[-13,17647;13,17647], y=[3628793;2628807] Press "0" "EXE" to go to x = 0 -> Axes are x=[-13,17647;13,17647], y=[-0.8612717;13.13873] Press auto Press "0" "EXE" to go to x = 0 -> the assertion about KDCoordinates breaks. --- apps/shared/curve_view.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 851883d862b..202ff72b725 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -1006,12 +1006,30 @@ KDRect CurveView::cursorFrame() { KDRect cursorFrame = KDRectZero; 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; From 4f9c37e96b1a3bac19ea3f52e250fc24516cc769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 4 Nov 2020 16:14:28 +0100 Subject: [PATCH 397/560] build: Version 15.0.0 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index e4b5c10d438..68055912b45 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 14.4.1 +EPSILON_VERSION ?= 15.0.0 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US From 703015953092a85e7854bc735514e30502955157 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Nov 2020 10:53:15 -0500 Subject: [PATCH 398/560] [fuzzer] make the afl.py script executable --- build/scenario/afl.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 build/scenario/afl.py diff --git a/build/scenario/afl.py b/build/scenario/afl.py old mode 100644 new mode 100755 From cca750a1c43e66fc612846631e48035dfb99beea Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Nov 2020 10:53:30 -0500 Subject: [PATCH 399/560] [fuzzer] The fuzzer detects the number of CPUs --- build/scenario/afl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/scenario/afl.py b/build/scenario/afl.py index 04c7a2a2e3a..2ca9165d63c 100755 --- a/build/scenario/afl.py +++ b/build/scenario/afl.py @@ -1,7 +1,8 @@ #!/usr/bin/env python import subprocess +import multiprocessing -NUMBER_OF_FUZZERS=8 +NUMBER_OF_FUZZERS=multiprocessing.cpu_count() def afl_command(name): master_option = "-M" if name.startswith("master") else "-S" From 55615c10e3a674d52a113af63cc3f3b03fc9b88a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Nov 2020 10:53:44 -0500 Subject: [PATCH 400/560] [fuzzer] Always keep assert() when building with AFL --- build/toolchain.afl.mak | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/toolchain.afl.mak b/build/toolchain.afl.mak index 28fa524266d..63b4e0002f3 100644 --- a/build/toolchain.afl.mak +++ b/build/toolchain.afl.mak @@ -6,3 +6,7 @@ LD = afl-clang++ ifeq ($(ASAN),1) export AFL_USE_ASAN = 1 endif + +# Prevent NDEBUG from being defined since we will always want assertions to run +# when the code is being fuzzed +SFLAGS := $(filter-out -DNDEBUG,$(SFLAGS)) From d9b5acc70d0fb4bb14010dc3f4ac131cac1011d8 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 4 Nov 2020 10:57:11 -0500 Subject: [PATCH 401/560] [fuzzer] Invoke Python3 --- build/scenario/afl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/scenario/afl.py b/build/scenario/afl.py index 2ca9165d63c..f9ad6cb5806 100755 --- a/build/scenario/afl.py +++ b/build/scenario/afl.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import subprocess import multiprocessing From 5afdec00b826ae04691039e5385a5ce4d0728c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 5 Nov 2020 10:19:32 +0100 Subject: [PATCH 402/560] [poincare] Avoid reading "garbage" value: result variable can be read in the while condition without being set to an initial value (if the first ridderApproximation was undefined for instance) Found by clang-analyzer --- poincare/src/derivative.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index 660d7b13b12..536c71723d5 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -50,7 +50,7 @@ Evaluation DerivativeNode::templatedApproximate(ApproximationContext approxim } 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 { From 7acec8800625eb752d15b6188eec5ec5e984df29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 5 Nov 2020 10:21:46 +0100 Subject: [PATCH 403/560] [python] Port: we used the wrong variable Found by clang-analyzer --- python/port/port.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/port/port.cpp b/python/port/port.cpp index c9880aed103..1964aac9ced 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -177,7 +177,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){ From f96e4e92ceeb5b36991a2be1b28e07679f0a93da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 5 Nov 2020 11:57:21 +0100 Subject: [PATCH 404/560] [ion/utf8_helper] Fix bad variable use --- ion/src/shared/unicode/utf8_helper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 40bf19bb265..731268c3fcf 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -203,7 +203,7 @@ void TryAndReplacePatternsInStringByPatterns(char * text, int textMaxLength, Tex size_t replacingStringLength = strlen(replacingString); if (strncmp(&text[i], matchedString, matchedStringLength) == 0) { - didReplace = replaceFirstCharsByPattern(&text[i], matchedStringLength, replacingString, textMaxLength); + didReplace = replaceFirstCharsByPattern(&text[i], matchedStringLength, replacingString, textMaxLength - i); if (didReplace) { int delta = replacingStringLength - matchedStringLength; textLength += delta; From e00cb91f74dd12a498bc96d7ce05d07971ee41ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 5 Nov 2020 11:58:27 +0100 Subject: [PATCH 405/560] [ion/utf8_helper] Reuse strlen value --- ion/src/shared/unicode/utf8_helper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 731268c3fcf..9393069a6d2 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -143,14 +143,14 @@ void RemoveCodePoint(char * buffer, CodePoint c, const char * * pointerToUpdate, } bool SlideStringByNumberOfChar(char * text, int slidingSize, size_t textMaxLength) { - size_t lenText = strlen(text); - if (lenText + slidingSize > textMaxLength || lenText + slidingSize < 0) { + const size_t textLen = strlen(text); + if (textLen + slidingSize > textMaxLength || textLen + slidingSize < 0) { return false; } if (slidingSize > 0) { - memmove(text+slidingSize, text, strlen(text)+1); + memmove(text+slidingSize, text, textLen + 1); } else if (slidingSize < 0) { - memmove(text, text-slidingSize, strlen(text)+1); + memmove(text, text-slidingSize, textLen + 1); } // In case slidingSize = 0, there is nothing to do return true; From 50bc31fcfae57a7df03b0aeec57be543d1561000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 5 Nov 2020 17:23:02 +0100 Subject: [PATCH 406/560] [poincare] Test: add a simplification test regarding unit --- poincare/test/simplification.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 3b01d582216..63578070a97 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -369,6 +369,7 @@ QUIZ_CASE(poincare_simplification_units) { 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()); From d789e8da9716948555692fe2812484463ef3f01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 5 Nov 2020 17:24:13 +0100 Subject: [PATCH 407/560] [poincare] In Multiplication::shallowBeautify, when extracting the unit, reduce without changing the unit in order to extract the right unit. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes the following bug: simplify "πππ23*_cK" --- poincare/include/poincare/expression_node.h | 11 +++++++++++ poincare/src/multiplication.cpp | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 37fb0ae2750..e3000d8f76f 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -191,6 +191,17 @@ class ExpressionNode : public TreeNode { 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; } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index f0d07307af1..8bcb13ad23b 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -423,7 +423,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 + /* 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 = deepReduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(reductionContext)); self = removeUnit(&units); if (self.isUndefined() || units.isUninitialized()) { From 6e0528deac6529b94bfee53c6d74b23820b8dc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 9 Nov 2020 10:28:50 +0100 Subject: [PATCH 408/560] [apps] it i18n: ortogonale --> ortonormale --- apps/shared.it.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 439622e84e9..b00d97b08a2 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -64,7 +64,7 @@ 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." From d7f83eca1c19b7a0757a36d043fcb5598c42e4e7 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Mon, 9 Nov 2020 09:53:57 +0100 Subject: [PATCH 409/560] [nl] fix spelling mistakes --- apps/graph/base.nl.i18n | 2 +- apps/sequence/base.nl.i18n | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n index ebc756b54f7..6142be27919 100644 --- a/apps/graph/base.nl.i18n +++ b/apps/graph/base.nl.i18n @@ -13,7 +13,7 @@ IntervalX = "x interval" FunctionDomain = "Plotbereik" FunctionColor = "Functiekleur" NoFunction = "Geen functie" -NoActivatedFunction = "Geen functie is ingeschakelt" +NoActivatedFunction = "Geen functie is ingeschakeld" PlotOptions = "Plot opties" Compute = "Bereken" Zeros = "Nulpunten" diff --git a/apps/sequence/base.nl.i18n b/apps/sequence/base.nl.i18n index 50792318308..34a63d53d43 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" From 3a0796d3c5eca12430d0ea170bc383116e34c516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 9 Nov 2020 12:17:26 +0100 Subject: [PATCH 410/560] [poincare/test] Add tests about negtive unit convert simplification --- poincare/test/simplification.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 63578070a97..3d54e214f0e 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1238,6 +1238,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()); From 5df60e946a781fb17a2325f29d90e281b3fc53bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 9 Nov 2020 11:46:16 +0100 Subject: [PATCH 411/560] [poincare] shallowBeautify can modify the reduction context UnitConvert must set UnitConversion to None, otherwise the unit asked for in the conversion might get changed after being properly set in UnitConvert::shallowBeautify. --- poincare/include/poincare/addition.h | 4 +- poincare/include/poincare/complex_cartesian.h | 4 +- poincare/include/poincare/decimal.h | 2 +- poincare/include/poincare/expression.h | 6 +- poincare/include/poincare/expression_node.h | 3 +- poincare/include/poincare/factor.h | 4 +- .../include/poincare/great_common_divisor.h | 2 +- .../include/poincare/least_common_multiple.h | 2 +- poincare/include/poincare/logarithm.h | 2 +- poincare/include/poincare/multiplication.h | 4 +- poincare/include/poincare/power.h | 4 +- poincare/include/poincare/rational.h | 2 +- poincare/include/poincare/unit.h | 4 +- poincare/include/poincare/unit_convert.h | 6 +- poincare/src/addition.cpp | 9 ++- poincare/src/complex_cartesian.cpp | 8 +-- poincare/src/decimal.cpp | 2 +- poincare/src/expression.cpp | 26 ++++---- poincare/src/expression_node.cpp | 6 +- poincare/src/factor.cpp | 4 +- poincare/src/great_common_divisor.cpp | 4 +- poincare/src/least_common_multiple.cpp | 4 +- poincare/src/logarithm.cpp | 4 +- poincare/src/multiplication.cpp | 22 +++---- poincare/src/power.cpp | 8 +-- poincare/src/rational.cpp | 2 +- poincare/src/unit.cpp | 8 +-- poincare/src/unit_convert.cpp | 61 +++++++++++++------ 28 files changed, 128 insertions(+), 89 deletions(-) diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 7a17468eb50..73860051042 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -55,7 +55,7 @@ class AdditionNode final : public NAryInfixExpressionNode { // 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; @@ -83,7 +83,7 @@ class Addition final : public NAryExpression { 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) { diff --git a/poincare/include/poincare/complex_cartesian.h b/poincare/include/poincare/complex_cartesian.h index 5f6734dcee5..c19de35fa41 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -30,7 +30,7 @@ class ComplexCartesianNode : public ExpressionNode { 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. */ @@ -54,7 +54,7 @@ class ComplexCartesian final : public Expression { // Simplification Expression shallowReduce(); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); // Common operations (done in-place) Expression squareNorm(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/decimal.h b/poincare/include/poincare/decimal.h index 60a217438ff..f1c0db8489c 100644 --- a/poincare/include/poincare/decimal.h +++ b/poincare/include/poincare/decimal.h @@ -61,7 +61,7 @@ class DecimalNode final : public NumberNode { // 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/expression.h b/poincare/include/poincare/expression.h index 9c3307846ec..84a7632575b 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -390,7 +390,7 @@ class Expression : public TreeHandle { 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); @@ -429,6 +429,10 @@ class Expression : public TreeHandle { 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; } diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index e3000d8f76f..aee2cd79f52 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -284,8 +284,9 @@ class ExpressionNode : public TreeNode { /* Simplification */ /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); + /*!*/ virtual void deepBeautifyChildren(ReductionContext reductionContext); /*!*/ virtual Expression shallowReduce(ReductionContext reductionContext); - /*!*/ virtual Expression shallowBeautify(ReductionContext reductionContext); + /*!*/ virtual Expression shallowBeautify(ReductionContext * reductionContext); /*!*/ virtual bool derivate(ReductionContext, Expression symbol, Expression symbolValue); virtual Expression unaryFunctionDifferential(); /* Return a clone of the denominator part of the expression */ diff --git a/poincare/include/poincare/factor.h b/poincare/include/poincare/factor.h index 3e16af3ab3f..c9203bdf206 100644 --- a/poincare/include/poincare/factor.h +++ b/poincare/include/poincare/factor.h @@ -27,7 +27,7 @@ class FactorNode /*final*/ : public ExpressionNode { /* 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; } @@ -49,7 +49,7 @@ class Factor final : public Expression { // Expression Expression shallowReduce(Context * context); - Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); }; } diff --git a/poincare/include/poincare/great_common_divisor.h b/poincare/include/poincare/great_common_divisor.h index 28a4665a768..739b5db3af8 100644 --- a/poincare/include/poincare/great_common_divisor.h +++ b/poincare/include/poincare/great_common_divisor.h @@ -26,7 +26,7 @@ class GreatCommonDivisorNode final : public NAryExpressionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Simplification 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; } // Evaluation diff --git a/poincare/include/poincare/least_common_multiple.h b/poincare/include/poincare/least_common_multiple.h index 10545e171e4..169f14e4a5c 100644 --- a/poincare/include/poincare/least_common_multiple.h +++ b/poincare/include/poincare/least_common_multiple.h @@ -26,7 +26,7 @@ class LeastCommonMultipleNode final : public NAryExpressionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; // Simplification 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; } // Evaluation diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index f56f740d0c6..1c9465c14c6 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -29,7 +29,7 @@ class LogarithmNode final : public ExpressionNode { // 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 diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 452cfcb61ac..d8b80bf0b9c 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -47,7 +47,7 @@ class MultiplicationNode final : public NAryInfixExpressionNode { // 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; @@ -85,7 +85,7 @@ class Multiplication : public NAryExpression { // 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); diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index c9a25279d0d..74e21ea8ef4 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -53,7 +53,7 @@ class PowerNode final : public ExpressionNode { // 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; @@ -83,7 +83,7 @@ class Power final : public Expression { 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: diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 9215e2e2a7e..2b7bf627149 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -58,7 +58,7 @@ class RationalNode final : public NumberNode { 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/unit.h b/poincare/include/poincare/unit.h index ded37f0b89e..4d4eee249d9 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -473,7 +473,7 @@ class UnitNode final : public ExpressionNode { // 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 Representative * representative() const { return m_representative; } @@ -686,7 +686,7 @@ class Unit : public Expression { // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - Expression shallowBeautify(ExpressionNode::ReductionContext 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); diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index c4cccf592de..c97eb3cdf5d 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -23,7 +23,8 @@ class UnitConvertNode /*final*/ : public RightwardsArrowExpressionNode { 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, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } Evaluation approximate(DoublePrecision p, ApproximationContext approximationContext) const override { return templatedApproximate(approximationContext); } @@ -38,7 +39,8 @@ friend class UnitConvertNode; // 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/src/addition.cpp b/poincare/src/addition.cpp index 070168b28b3..a6d4da2cacb 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -46,7 +46,7 @@ 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); } @@ -84,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 @@ -98,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 @@ -130,7 +130,6 @@ Expression Addition::shallowBeautify(ExpressionNode::ReductionContext reductionC } Expression result = squashUnaryHierarchyInPlace(); - return result; } diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index c0dbf3e57ae..ad4470cf480 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -32,7 +32,7 @@ Expression ComplexCartesianNode::shallowReduce(ReductionContext reductionContext return ComplexCartesian(this).shallowReduce(); } -Expression ComplexCartesianNode::shallowBeautify(ReductionContext reductionContext) { +Expression ComplexCartesianNode::shallowBeautify(ReductionContext * reductionContext) { return ComplexCartesian(this).shallowBeautify(reductionContext); } @@ -77,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, diff --git a/poincare/src/decimal.cpp b/poincare/src/decimal.cpp index 82330919dd1..30ee799ebea 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/expression.cpp b/poincare/src/expression.cpp index 6154d763535..685242180c1 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -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; } @@ -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(); @@ -833,16 +845,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; } diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index 4bd4b9823c1..a58a127754e 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -103,11 +103,15 @@ 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(); } diff --git a/poincare/src/factor.cpp b/poincare/src/factor.cpp index bff8a70f69d..549e6229795 100644 --- a/poincare/src/factor.cpp +++ b/poincare/src/factor.cpp @@ -31,7 +31,7 @@ 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); } @@ -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(); diff --git a/poincare/src/great_common_divisor.cpp b/poincare/src/great_common_divisor.cpp index 5641df6c162..88637076fb6 100644 --- a/poincare/src/great_common_divisor.cpp +++ b/poincare/src/great_common_divisor.cpp @@ -19,8 +19,8 @@ Expression GreatCommonDivisorNode::shallowReduce(ReductionContext reductionConte return GreatCommonDivisor(this).shallowReduce(reductionContext); } -Expression GreatCommonDivisorNode::shallowBeautify(ReductionContext reductionContext) { - return GreatCommonDivisor(this).shallowBeautify(reductionContext.context()); +Expression GreatCommonDivisorNode::shallowBeautify(ReductionContext * reductionContext) { + return GreatCommonDivisor(this).shallowBeautify(reductionContext->context()); } template diff --git a/poincare/src/least_common_multiple.cpp b/poincare/src/least_common_multiple.cpp index 6702aaac805..03437d58fc1 100644 --- a/poincare/src/least_common_multiple.cpp +++ b/poincare/src/least_common_multiple.cpp @@ -19,8 +19,8 @@ Expression LeastCommonMultipleNode::shallowReduce(ReductionContext reductionCont return LeastCommonMultiple(this).shallowReduce(reductionContext); } -Expression LeastCommonMultipleNode::shallowBeautify(ReductionContext reductionContext) { - return LeastCommonMultiple(this).shallowBeautify(reductionContext.context()); +Expression LeastCommonMultipleNode::shallowBeautify(ReductionContext * reductionContext) { + return LeastCommonMultiple(this).shallowBeautify(reductionContext->context()); } template diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index bc9d96ad1a6..182747767f0 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -95,12 +95,12 @@ Expression LogarithmNode<1>::unaryFunctionDifferential() { /**/ 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(); } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 8bcb13ad23b..a0ba391ea9c 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -211,7 +211,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); } @@ -400,7 +400,7 @@ static bool CanSimplifyUnitProduct( 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 @@ -408,7 +408,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(); @@ -425,7 +425,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu Expression 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 = deepReduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(reductionContext)); + self = deepReduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext)); self = removeUnit(&units); if (self.isUndefined() || units.isUninitialized()) { @@ -434,7 +434,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. @@ -501,7 +501,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu // Apply simplifications if (unitsAccu.numberOfChildren() > 0) { // Divide by derived units - units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext); + units = Division::Builder(units, unitsAccu.clone()).deepReduce(*reductionContext); Expression newUnits; // Separate units and generated values units = units.removeUnit(&newUnits); @@ -528,7 +528,7 @@ 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(); @@ -538,7 +538,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu /* 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 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 @@ -546,9 +546,9 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu repr = UnitNode::VolumeRepresentative::Default().representativesOfSameDimension(); units = Unit::Builder(repr, UnitNode::Prefix::EmptyPrefix()); value /= repr->ratio(); - Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, reductionContext); + Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, *reductionContext); } else { - Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, reductionContext); + Unit::ChooseBestRepresentativeAndPrefixForValue(units, &value, *reductionContext); } } // Build final Expression @@ -558,7 +558,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; } diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 6738c1d2470..dc8a93e3e65 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -225,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); } @@ -985,14 +985,14 @@ 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), unless base is a unit diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index 58f07cc3e7a..6757bee85bf 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/unit.cpp b/poincare/src/unit.cpp index 8ba9a1d9ce4..8b5df09043f 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -701,8 +701,8 @@ 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(); } template @@ -824,7 +824,7 @@ Expression Unit::BuildSplit(double value, const Unit * units, int length, Expres ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); - return res.squashUnaryHierarchyInPlace().shallowBeautify(keepUnitsContext); + return res.squashUnaryHierarchyInPlace().shallowBeautify(&keepUnitsContext); } Expression Unit::ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode::ReductionContext reductionContext) { @@ -904,7 +904,7 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext 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.)); diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index 9d5ea930937..86252bda45a 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,6 +28,10 @@ void UnitConvertNode::deepReduceChildren(ExpressionNode::ReductionContext reduct UnitConvert(this).deepReduceChildren(reductionContext); } +void UnitConvertNode::deepBeautifyChildren(ExpressionNode::ReductionContext reductionContext) { + UnitConvert(this).deepBeautifyChildren(reductionContext); +} + template Evaluation UnitConvertNode::templatedApproximate(ApproximationContext approximationContext) const { /* If we are here, it means that the unit convert node was not shallowReduced. @@ -50,15 +54,27 @@ void UnitConvert::deepReduceChildren(ExpressionNode::ReductionContext reductionC 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.unitFormat(), - 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); @@ -68,14 +84,6 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti } } // Find the unit - ExpressionNode::ReductionContext reductionContextWithoutUnits = ExpressionNode::ReductionContext( - reductionContext.context(), - reductionContext.complexFormat(), - reductionContext.angleUnit(), - reductionContext.unitFormat(), - reductionContext.target(), - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, - ExpressionNode::UnitConversion::None); Expression unit; childAtIndex(1).removeUnit(&unit); if (unit.isUninitialized()) { @@ -88,7 +96,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti 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); + Expression result = Unit::ConvertTemperatureUnits(childAtIndex(0), unitRef, *reductionContext); replaceWithInPlace(result); return result; } @@ -96,7 +104,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti // Divide the left member by the new unit Expression division = Division::Builder(childAtIndex(0), unit.clone()); - division = division.deepReduce(reductionContext); + division = division.deepReduce(*reductionContext); Expression divisionUnit; division = division.removeUnit(&divisionUnit); if (!divisionUnit.isUninitialized()) { @@ -105,8 +113,25 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reducti } Expression result = Multiplication::Builder(division, unit); replaceWithInPlace(result); + 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); - return result.shallowBeautify(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(ApproximationContext approximationContext) const; From 4df996cfd24c6b4e3a344650fb4ccaa1109e29a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 9 Nov 2020 15:30:41 +0100 Subject: [PATCH 412/560] [poincare/expression_node] TODO: use ReductionContext via pointers --- poincare/include/poincare/expression_node.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index aee2cd79f52..6ec6c1d0529 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -286,6 +286,12 @@ class ExpressionNode : public TreeNode { /*!*/ virtual void deepReduceChildren(ReductionContext reductionContext); /*!*/ virtual void deepBeautifyChildren(ReductionContext reductionContext); /*!*/ virtual Expression shallowReduce(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(); From f42b7e4b204fdb8e3882524791d1451edd672089 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Mon, 9 Nov 2020 17:17:28 +0100 Subject: [PATCH 413/560] [nl] fix imperial unit translation --- apps/toolbox.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index bdf5a50ff01..f0cc8afe49e 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -86,7 +86,7 @@ UnitVolumeLiterCenti = "Centiliter" UnitVolumeLiterMilli = "Milliliter" UnitVolumeTeaspoon = "Theelepel" UnitVolumeTablespoon= "Eetlepel" -UnitVolumeFluidOunce = "Ounce" +UnitVolumeFluidOunce = "Vloeibare ons" UnitVolumeCup = "Kop" UnitVolumePint = "Pint" UnitVolumeQuart = "Quart" From 6c508d9c29cd6b14f96c46227ee7b0e1426a6fb4 Mon Sep 17 00:00:00 2001 From: Roberta Rabotti Date: Mon, 9 Nov 2020 17:38:21 +0100 Subject: [PATCH 414/560] [IT]translation changes: imperial units Change translation Imperial units --- apps/toolbox.it.i18n | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n index aa96c0474df..d67976c8cdc 100644 --- a/apps/toolbox.it.i18n +++ b/apps/toolbox.it.i18n @@ -21,8 +21,8 @@ UnitDistanceMeterNano = "Nanometro" UnitDistanceMeterPico = "Picometro" UnitDistanceAstronomicalUnit = "Unità astronomica" UnitDistanceLightYear = "Anno luce" -UnitDistanceMile = "Milla" -UnitDistanceYard = "Iarda" +UnitDistanceMile = "Miglio" +UnitDistanceYard = "Yard" UnitDistanceFoot = "Piede" UnitDistanceInch = "Pollice" UnitMassMenu = "Massa" @@ -77,7 +77,7 @@ UnitConductanceSiemensMilli = "Millisiemens" UnitMagneticFieldMenu = "Induzione elettromagnetica" InductanceMenu = "Induttanza" UnitSurfaceMenu = "Superficie" -UnitSurfaceAcre = "Acre" +UnitSurfaceAcre = "Acro" UnitSurfaceHectar = "Ettaro" UnitVolumeMenu = "Volume" UnitVolumeLiter = "Litro" @@ -86,7 +86,7 @@ UnitVolumeLiterCenti = "Centilitro" UnitVolumeLiterMilli = "Millilitro" UnitVolumeTeaspoon = "Cucchiaino" UnitVolumeTablespoon= "Cucchiaio" -UnitVolumeFluidOunce = "Oncia fluida" +UnitVolumeFluidOunce = "Oncia liquida" UnitVolumeCup = "Tazza" UnitVolumePint = "Pinta" UnitVolumeQuart = "Quarto" From 0dcb4e9affa9746ce415f032f92412eff059cb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 9 Nov 2020 15:48:02 +0100 Subject: [PATCH 415/560] [docs] Improve SDK doc: executables are not in the root folder anymore --- docs/build/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/build/index.md b/docs/build/index.md index 18a34197b2c..9827314db9b 100644 --- a/docs/build/index.md +++ b/docs/build/index.md @@ -58,8 +58,7 @@ Once the SDK has been installed, just open your terminal (Msys2, Terminal.app, x ``` make PLATFORM=simulator clean -make PLATFORM=simulator -./epsilon.elf +make PLATFORM=simulator epsilon_run ``` ## Run Epsilon on your calculator From 80f3af389fc1247c65b124e3f2d7ff83eb6a1e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 10 Nov 2020 09:52:54 +0100 Subject: [PATCH 416/560] [build] Add %_run targets to linux, macos and windows platforms --- build/targets.simulator.linux.mak | 4 ++++ build/targets.simulator.macos.mak | 4 ++++ build/targets.simulator.windows.mak | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 build/targets.simulator.linux.mak create mode 100644 build/targets.simulator.macos.mak create mode 100644 build/targets.simulator.windows.mak diff --git a/build/targets.simulator.linux.mak b/build/targets.simulator.linux.mak new file mode 100644 index 00000000000..e7e76ade8f8 --- /dev/null +++ b/build/targets.simulator.linux.mak @@ -0,0 +1,4 @@ +.PHONY: %_run +%_run: $(BUILD_DIR)/%.$(EXE) + $(call rule_label,EXE) + $(Q) ./$^ diff --git a/build/targets.simulator.macos.mak b/build/targets.simulator.macos.mak new file mode 100644 index 00000000000..ead29edfcae --- /dev/null +++ b/build/targets.simulator.macos.mak @@ -0,0 +1,4 @@ +.PHONY: %_run +%_run: $(BUILD_DIR)/%.app + $(call rule_label,OPEN) + $(Q) open $^ diff --git a/build/targets.simulator.windows.mak b/build/targets.simulator.windows.mak new file mode 100644 index 00000000000..eef0ca30ea3 --- /dev/null +++ b/build/targets.simulator.windows.mak @@ -0,0 +1,4 @@ +PHONY: %_run +t %_run: $(BUILD_DIR)/%.$(EXE) + $(call rule_label,EXE) + $(Q) ./$^ From 4233f197d3a479b8252004fe9fb2bfebfad52905 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 12 Nov 2020 14:43:17 +0100 Subject: [PATCH 417/560] [shared] Keep graph units when using navigation When using the Navigate menu on graphs, the grid units are computed using the normal graph's range, so that the units won't change. Change-Id: I8ff8853868e5c90319619a6271d6a7d87ae82ab0 --- apps/shared/curve_view_range.h | 3 ++- apps/shared/function_zoom_and_pan_curve_view_controller.cpp | 5 ++++- apps/shared/interactive_curve_view_range.h | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/shared/curve_view_range.h b/apps/shared/curve_view_range.h index 40c0709bb69..5867ab566d2 100644 --- a/apps/shared/curve_view_range.h +++ b/apps/shared/curve_view_range.h @@ -24,7 +24,7 @@ class CurveViewRange { 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; @@ -32,6 +32,7 @@ class CurveViewRange { 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.*/ diff --git a/apps/shared/function_zoom_and_pan_curve_view_controller.cpp b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp index cd1edbf60da..72fb708eda1 100644 --- a/apps/shared/function_zoom_and_pan_curve_view_controller.cpp +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp @@ -37,9 +37,12 @@ void FunctionZoomAndPanCurveViewController::adaptCurveRange(bool viewWillAppear) 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->setYMin(newYMin); m_contentView.curveView()->reload(); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index a441699862b..ced28f46d1a 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -46,6 +46,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { 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); @@ -77,6 +79,9 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } InteractiveCurveViewRangeDelegate * m_delegate; private: + float offscreenYAxis() const override { return m_offscreenYAxis; } + + float m_offscreenYAxis; bool m_zoomAuto; bool m_zoomNormalize; }; From ea3e3287dc68afdab7b250aed16a29d2423c5498 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 13 Nov 2020 16:44:49 +0100 Subject: [PATCH 418/560] [graph] Keep Auto when opening and closing Navigate If the user opens the Navigate menu, then closes it immediately, the Auto status is not turned off. Change-Id: I2d1ca0cfdae1b9c957dede7bf9f3c2890dea1bb0 --- ...tion_zoom_and_pan_curve_view_controller.cpp | 18 +++++++++++++++++- ...nction_zoom_and_pan_curve_view_controller.h | 2 ++ .../interactive_curve_view_controller.cpp | 2 -- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/shared/function_zoom_and_pan_curve_view_controller.cpp b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp index 72fb708eda1..d32e102e8f3 100644 --- a/apps/shared/function_zoom_and_pan_curve_view_controller.cpp +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.cpp @@ -7,7 +7,8 @@ namespace Shared { FunctionZoomAndPanCurveViewController::FunctionZoomAndPanCurveViewController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : ZoomAndPanCurveViewController(parentResponder), m_contentView(curveView), - m_interactiveRange(interactiveRange) + m_interactiveRange(interactiveRange), + m_restoreZoomAuto(false) { } @@ -32,6 +33,19 @@ void FunctionZoomAndPanCurveViewController::didBecomeFirstResponder() { m_contentView.layoutSubviews(); } +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(); @@ -43,6 +57,8 @@ void FunctionZoomAndPanCurveViewController::adaptCurveRange(bool viewWillAppear) } 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(); diff --git a/apps/shared/function_zoom_and_pan_curve_view_controller.h b/apps/shared/function_zoom_and_pan_curve_view_controller.h index 5ea5767ffa4..8acfd040ab3 100644 --- a/apps/shared/function_zoom_and_pan_curve_view_controller.h +++ b/apps/shared/function_zoom_and_pan_curve_view_controller.h @@ -14,6 +14,7 @@ class FunctionZoomAndPanCurveViewController : public ZoomAndPanCurveViewControll 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 @@ class FunctionZoomAndPanCurveViewController : public ZoomAndPanCurveViewControll ContentView m_contentView; InteractiveCurveViewRange * m_interactiveRange; + bool m_restoreZoomAuto; }; } diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 391435d01e3..49b2850ef14 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -318,9 +318,7 @@ bool InteractiveCurveViewController::normalizeButtonAction() { } void InteractiveCurveViewController::navigationButtonAction() { - m_interactiveRange->setZoomAuto(false); stackController()->push(zoomParameterController()); } - } From 41778433b1e5ee2c52659b4e62333408a843743c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 16 Nov 2020 14:50:00 +0100 Subject: [PATCH 419/560] [graph] Fix a bug when zooming When entering the Navigate menu, a portion of the Y axis is memoized to preserve the grid units. However, this portion was not modified when zooming, causing some glitches. Change-Id: I06b4ee044eaa75d48b8f338c9ef6ea33bb299e39 --- apps/shared/interactive_curve_view_range.cpp | 1 + apps/shared/interactive_curve_view_range.h | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index a43cbfe4d69..f8f82d12505 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -115,6 +115,7 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); } + m_offscreenYAxis *= ratio; } void InteractiveCurveViewRange::panWithVector(float x, float y) { diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index ced28f46d1a..eec43098932 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -14,6 +14,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) : MemoizedCurveViewRange(), m_delegate(nullptr), + m_offscreenYAxis(0.f), m_zoomAuto(true), m_zoomNormalize(false) { From a0497d3bd7ca7d8348d178d6c683800e24e8864f Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 14:41:33 +0100 Subject: [PATCH 420/560] [poincare] Prevent Bad access on abstract symbol context Change-Id: I3077db5fdc2430ee11ceb1c6eb651e4331fb2ba0 --- poincare/src/symbol_abstract.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index c9127000fae..c7758717af0 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -73,6 +73,11 @@ Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * conte { return clone ? symbol.clone() : *const_cast(&symbol); } + if (context == nullptr) { + // A context is required + assert(false); + return Undefined::Builder(); + } Expression e = context->expressionForSymbolAbstract(symbol, clone); /* Replace all the symbols iteratively. This prevents a memory failure when * symbols are defined circularly. */ From 73b5b0775a580584d37e3a55eba3180863715dd3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 14:49:36 +0100 Subject: [PATCH 421/560] [poincare] Avoid nullptr context on non-Numbers sign and nullStatus methods Change-Id: I3361344c1e0b50cdbe999d6ea50025022a51f603 --- poincare/include/poincare/complex_cartesian.h | 2 +- poincare/src/addition.cpp | 2 +- poincare/src/complex_cartesian.cpp | 10 +++++----- poincare/src/multiplication.cpp | 2 +- poincare/src/power.cpp | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/poincare/include/poincare/complex_cartesian.h b/poincare/include/poincare/complex_cartesian.h index c19de35fa41..1d90a6a7545 100644 --- a/poincare/include/poincare/complex_cartesian.h +++ b/poincare/include/poincare/complex_cartesian.h @@ -53,7 +53,7 @@ class ComplexCartesian final : public Expression { Expression imag() { return childAtIndex(1); } // Simplification - Expression shallowReduce(); + Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext * reductionContext); // Common operations (done in-place) diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index a6d4da2cacb..5352cd85169 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -313,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. diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index ad4470cf480..758829eabff 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -29,7 +29,7 @@ ExpressionNode::NullStatus ComplexCartesianNode::nullStatus(Context * context) c } Expression ComplexCartesianNode::shallowReduce(ReductionContext reductionContext) { - return ComplexCartesian(this).shallowReduce(); + return ComplexCartesian(this).shallowReduce(reductionContext); } Expression ComplexCartesianNode::shallowBeautify(ReductionContext * reductionContext) { @@ -61,7 +61,7 @@ Complex ComplexCartesianNode::templatedApproximate(ApproximationContext appro return Complex::Builder(a.real(), b.real()); } -Expression ComplexCartesian::shallowReduce() { +Expression ComplexCartesian::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { Expression e = Expression::defaultShallowReduce(); e = e.defaultHandleUnitsInChildren(); @@ -69,7 +69,7 @@ Expression ComplexCartesian::shallowReduce() { return e; } } - if (imag().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { + if (imag().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { Expression r = real(); replaceWithInPlace(r); return r; @@ -137,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().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { + if (imag().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { a = real(); - } else if (real().nullStatus(nullptr) == ExpressionNode::NullStatus::Null) { + } else if (real().nullStatus(reductionContext.context()) == ExpressionNode::NullStatus::Null) { a = imag(); } if (!a.isUninitialized()) { diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index a0ba391ea9c..29f9860fcb5 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -912,7 +912,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; diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index dc8a93e3e65..b5f8de0214b 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -589,7 +589,7 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex } if (!result.isUninitialized()) { replaceWithInPlace(result); - return result.shallowReduce(); + return result.shallowReduce(reductionContext); } } } @@ -606,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 From 04b2683af275d962ffc1ffcab5a22535dc30b4a3 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 17:07:33 +0100 Subject: [PATCH 422/560] [poincare] Replaced an escape case with an assert only Change-Id: I5ea501beeb42577a25dd752f8b9492210abd38fc --- poincare/src/symbol_abstract.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index c7758717af0..be598f97bad 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -73,11 +73,7 @@ Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * conte { return clone ? symbol.clone() : *const_cast(&symbol); } - if (context == nullptr) { - // A context is required - assert(false); - return Undefined::Builder(); - } + assert(context); Expression e = context->expressionForSymbolAbstract(symbol, clone); /* Replace all the symbols iteratively. This prevents a memory failure when * symbols are defined circularly. */ From 62e2a71da1cf8c8b849389abe9c44903095599db Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 12 Nov 2020 12:06:51 +0100 Subject: [PATCH 423/560] [poincare/matrix_layout] Remove empty rows Empty columns are automatically removed ; the same is now done to empty rows. Change-Id: Ib66dfe83f1ea712d3f9a450eedd7ade709296413 --- poincare/src/matrix_layout.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index e946fe98a8c..9fc54bfa32d 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -314,6 +314,7 @@ 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; @@ -322,6 +323,10 @@ void MatrixLayoutNode::didReplaceChildAtIndex(int index, LayoutCursor * cursor, deleteColumnAtIndex(columnIndex); newIndex -= rowIndex; } + if (rowIsEmpty && 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)); From 7184202a6ed4704df147ef08c48d409e6f7957a3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 12 Nov 2020 12:09:42 +0100 Subject: [PATCH 424/560] [poincare/matrix_layout] Keep median rows and columns Empty columns and rows are only removed automatically when they are the last row/column. This way, the user can edit a median row/column without the matrix collapsing. Change-Id: If611fa0133f270d785809d433ae879879b6b093f --- poincare/src/matrix_layout.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index 9fc54bfa32d..9300ac14865 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -318,12 +318,12 @@ void MatrixLayoutNode::didReplaceChildAtIndex(int index, LayoutCursor * cursor, 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) { + if (rowIsEmpty && m_numberOfRows > 2 && rowIndex == m_numberOfRows - 2) { // If the row is now empty, delete it deleteRowAtIndex(rowIndex); } From 323e72a50ca5253bdbe1d772f7ecd479de4402b9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 12 Nov 2020 12:21:06 +0100 Subject: [PATCH 425/560] [poincare/matrix_layout] Remove columns with backspace Allow removing empty columns with backspace, the same way one can remove empty rows. This allows deleting the internal columns that are not automatically deleted. Change-Id: I52def7939257942a8396721d01c4d1531ef90361 --- poincare/src/matrix_layout.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index 9300ac14865..c57af612608 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -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); + int columnIndex = columnAtChildIndex(indexOfPointedLayout); + int rowIndex = rowAtChildIndex(indexOfPointedLayout); + bool deleted = false; + if (columnIndex == 0) { if (rowIndex < m_numberOfRows - 1 && isRowEmpty(rowIndex) && m_numberOfRows > 2) { deleteRowAtIndex(rowIndex); + deleted = true; + } + } + if (rowIndex == 0) { + if (columnIndex < m_numberOfColumns - 1 && isColumnEmpty(columnIndex) && m_numberOfColumns > 2) { + 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); From 0c44deb35fb4f42e88c8011757b1c7c317c2e5b1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 13 Nov 2020 17:12:24 +0100 Subject: [PATCH 426/560] [poincare/matrix_layout] Change comparison order Change-Id: Iaa048d82005a7dfa380357e80bbac447c0976e55 --- poincare/src/matrix_layout.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index c57af612608..26c90d6900a 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -102,13 +102,13 @@ void MatrixLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { int rowIndex = rowAtChildIndex(indexOfPointedLayout); bool deleted = false; if (columnIndex == 0) { - if (rowIndex < m_numberOfRows - 1 && isRowEmpty(rowIndex) && m_numberOfRows > 2) { + if (m_numberOfRows > 2 && rowIndex < m_numberOfRows - 1 && isRowEmpty(rowIndex)) { deleteRowAtIndex(rowIndex); deleted = true; } } if (rowIndex == 0) { - if (columnIndex < m_numberOfColumns - 1 && isColumnEmpty(columnIndex) && m_numberOfColumns > 2) { + if (m_numberOfColumns > 2 && columnIndex < m_numberOfColumns - 1 && isColumnEmpty(columnIndex)) { deleteColumnAtIndex(columnIndex); deleted = true; } From 0ecfa0012c4f4e3b5dbd056b76172abb8630a63c Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Tue, 17 Nov 2020 12:14:50 +0100 Subject: [PATCH 427/560] [nl] improved translation statistics --- apps/statistics/base.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index a7533b1b8be..4d3d2ffbb46 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -11,7 +11,7 @@ Frequencies3 = "Frequenties N3" ImportList = "Importeren uit een lijst" Interval = " Interval " Frequency = " Frequentie:" -RelativeFrequency = "Proportie:" +RelativeFrequency = "Relatieve:" HistogramSet = "Histogram instellingen" RectangleWidth = "Kolombreedte" BarStart = "X start" From bee8d8531bfcdce6d95f1ff058c9d9fcda471f11 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 11:09:49 +0100 Subject: [PATCH 428/560] [poincare] Handle horizontal vectors Change-Id: I98088b2b9f2dbc0549795a5c3eed4787fea70068 --- poincare/include/poincare/matrix.h | 2 ++ poincare/include/poincare/matrix_complex.h | 2 ++ poincare/src/matrix.cpp | 18 +++++++++--------- poincare/src/matrix_complex.cpp | 6 +++--- poincare/src/vector_cross.cpp | 4 ++-- poincare/src/vector_dot.cpp | 4 ++-- poincare/src/vector_norm.cpp | 4 ++-- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 35ef396298b..24aabf490f5 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -12,6 +12,7 @@ class MatrixNode /*final*/ : public ExpressionNode { m_numberOfColumns(0) {} bool hasMatrixChild(Context * context) const; + bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } int numberOfRows() const { return m_numberOfRows; } int numberOfColumns() const { return m_numberOfColumns; } virtual void setNumberOfRows(int rows) { assert(rows >= 0); m_numberOfRows = rows; } @@ -67,6 +68,7 @@ class Matrix final : public Expression { static Matrix Builder() { return TreeHandle::NAryBuilder(); } void setDimensions(int rows, int columns); + bool isVector() const { return node()->isVector(); } int numberOfRows() const { return node()->numberOfRows(); } int numberOfColumns() const { return node()->numberOfColumns(); } using TreeHandle::addChildAtIndexInPlace; diff --git a/poincare/include/poincare/matrix_complex.h b/poincare/include/poincare/matrix_complex.h index 275785cd4ae..50d7c5922e1 100644 --- a/poincare/include/poincare/matrix_complex.h +++ b/poincare/include/poincare/matrix_complex.h @@ -36,6 +36,7 @@ class MatrixComplexNode final : public EvaluationNode { // EvaluationNode typename EvaluationNode::Type type() const override { return EvaluationNode::Type::MatrixComplex; } + bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } int numberOfRows() const { return m_numberOfRows; } int numberOfColumns() const { return m_numberOfColumns; } virtual void setNumberOfRows(int rows) { assert(rows >= 0); m_numberOfRows = rows; } @@ -71,6 +72,7 @@ class MatrixComplex final : public Evaluation { std::complex complexAtIndex(int index) const { return node()->complexAtIndex(index); } + bool isVector() const { return node()->isVector(); } int numberOfRows() const { return node()->numberOfRows(); } int numberOfColumns() const { return node()->numberOfColumns(); } void setDimensions(int rows, int columns); diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 5dd9f56a017..2f2d0bfb7ed 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -494,10 +494,10 @@ Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext } Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const { - assert(numberOfColumns() == 1); + assert(isVector()); Addition sum = Addition::Builder(); - for (int j = 0; j < numberOfRows(); j++) { - Expression absValue = AbsoluteValue::Builder(const_cast(this)->matrixChild(0, j).clone()); + 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()); @@ -510,10 +510,10 @@ Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { // Dot product is defined between two vectors of same size - assert(numberOfRows() == b->numberOfRows() && numberOfColumns() == 1 && b->numberOfColumns() == 1); + assert(isVector() && b->isVector() && numberOfChildren() == b->numberOfChildren()); Addition sum = Addition::Builder(); - for (int j = 0; j < numberOfRows(); j++) { - Expression product = Multiplication::Builder(const_cast(this)->matrixChild(0, j).clone(), const_cast(b)->matrixChild(0, j).clone()); + 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); } @@ -522,13 +522,13 @@ Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionCon Matrix Matrix::cross(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { // Cross product is defined between two vectors of size 3 - assert(numberOfRows() == 3 && numberOfColumns() == 1 && b->numberOfRows() == 3 && b->numberOfColumns() == 1); + assert(isVector() && b->isVector() && 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)->matrixChild(0, j1).clone(), const_cast(b)->matrixChild(0, j2).clone()); - Expression a2b1 = Multiplication::Builder(const_cast(this)->matrixChild(0, j2).clone(), const_cast(b)->matrixChild(0, j1).clone()); + 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); diff --git a/poincare/src/matrix_complex.cpp b/poincare/src/matrix_complex.cpp index eb94bab4012..f0f087068e3 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -137,7 +137,7 @@ MatrixComplex MatrixComplexNode::ref(bool reduced) const { template std::complex MatrixComplexNode::norm() const { - if (numberOfChildren() == 0 || numberOfColumns() > 1) { + if (!isVector()) { return std::complex(NAN, NAN); } std::complex sum = 0; @@ -153,7 +153,7 @@ std::complex MatrixComplexNode::dot(Evaluation * e) const { return std::complex(NAN, NAN); } MatrixComplex * b = static_cast*>(e); - if (numberOfChildren() == 0 || numberOfColumns() > 1 || b->numberOfChildren() == 0 || b->numberOfColumns() > 1 || numberOfRows() != b->numberOfRows()) { + if (!isVector() || !b->isVector() || numberOfChildren() != b->numberOfChildren()) { return std::complex(NAN, NAN); } std::complex sum = 0; @@ -169,7 +169,7 @@ Evaluation MatrixComplexNode::cross(Evaluation * e) const { return MatrixComplex::Undefined(); } MatrixComplex * b = static_cast*>(e); - if (numberOfChildren() == 0 || numberOfColumns() != 1 || numberOfRows() != 3 || b->numberOfChildren() == 0 || b->numberOfColumns() != 1 || b->numberOfRows() != 3) { + if (!isVector() || !b->isVector() || numberOfChildren() != 3 || b->numberOfChildren() != 3) { return MatrixComplex::Undefined(); } std::complex operandsCopy[3]; diff --git a/poincare/src/vector_cross.cpp b/poincare/src/vector_cross.cpp index 6ee28808376..4208be412a4 100644 --- a/poincare/src/vector_cross.cpp +++ b/poincare/src/vector_cross.cpp @@ -44,8 +44,8 @@ Expression VectorCross::shallowReduce(ExpressionNode::ReductionContext reduction 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 column matrices of size 3 - if (matrixChild0.numberOfColumns() != 1 || matrixChild1.numberOfColumns() != 1 || matrixChild0.numberOfRows() != 3 || matrixChild1.numberOfRows() != 3) { + // Cross product is defined between two vectors of size 3 + if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != 3 || matrixChild1.numberOfChildren() != 3) { return replaceWithUndefinedInPlace(); } Expression a = matrixChild0.cross(&matrixChild1, reductionContext); diff --git a/poincare/src/vector_dot.cpp b/poincare/src/vector_dot.cpp index aaabfe3c1b8..820abf7c1df 100644 --- a/poincare/src/vector_dot.cpp +++ b/poincare/src/vector_dot.cpp @@ -44,8 +44,8 @@ Expression VectorDot::shallowReduce(ExpressionNode::ReductionContext reductionCo 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 column matrices of the same dimensions - if (matrixChild0.numberOfColumns() != 1 || matrixChild1.numberOfColumns() != 1 || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { + // Dot product is defined between two vectors of the same dimensions + if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != matrixChild1.numberOfChildren()) { return replaceWithUndefinedInPlace(); } Expression a = matrixChild0.dot(&matrixChild1, reductionContext); diff --git a/poincare/src/vector_norm.cpp b/poincare/src/vector_norm.cpp index c428d8b5e92..1f280ff3d44 100644 --- a/poincare/src/vector_norm.cpp +++ b/poincare/src/vector_norm.cpp @@ -42,8 +42,8 @@ Expression VectorNorm::shallowReduce(ExpressionNode::ReductionContext reductionC Expression c = childAtIndex(0); if (c.type() == ExpressionNode::Type::Matrix) { Matrix matrixChild = static_cast(c); - if (matrixChild.numberOfColumns() != 1) { - // Norm is only defined on column matrices + if (!matrixChild.isVector()) { + // Norm is only defined on vectors return replaceWithUndefinedInPlace(); } Expression a = matrixChild.norm(reductionContext); From cb54e22272f295ee72745fa398f13f1e8c7ddf59 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 11:28:20 +0100 Subject: [PATCH 429/560] [poincare/test] Add tests on horizontal vectors Change-Id: If2d6293ede044ffe71e632212eff155b80ad683f --- poincare/test/approximation.cpp | 10 ++++++++++ poincare/test/simplification.cpp | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index e15204106c0..7b7ec97b621 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -440,12 +440,22 @@ QUIZ_CASE(poincare_approximation_function) { 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("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("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"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 3d54e214f0e..c54ae8ecc6d 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1091,18 +1091,18 @@ QUIZ_CASE(poincare_simplification_matrix) { // 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]])", "[[-5][4][-1]]"); - assert_parsed_expression_simplify_to("cross([[1][π][𝐢]],[[𝐢π][𝐢π^2][-π]])", "[[0][0][0]]"); + 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]])", "0"); - assert_parsed_expression_simplify_to("dot([[1][1][1]],[[0][π][𝐢]])", "π+𝐢"); + 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)"); + assert_parsed_expression_simplify_to("norm([[1,𝐢+1,π,-5]])", "√(π^2+28)"); // Expressions with unreduced matrix assert_reduce("confidence(cos(2)/25,3)→a"); From c0413709b77d52885b9fe0406ccede0b25e2b302 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 16 Nov 2020 17:44:46 +0100 Subject: [PATCH 430/560] [poincare] Require same vector orientation for dot and cross products Change-Id: I4cf248cf564899314a1efb1c5e39a041395ba583 --- poincare/src/matrix.cpp | 11 ++++++----- poincare/src/matrix_complex.cpp | 6 +++--- poincare/src/vector_cross.cpp | 2 +- poincare/src/vector_dot.cpp | 5 +++-- poincare/test/approximation.cpp | 8 ++------ poincare/test/simplification.cpp | 8 +++++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index 2f2d0bfb7ed..b2ddc89880e 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -509,8 +509,8 @@ Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const } Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { - // Dot product is defined between two vectors of same size - assert(isVector() && b->isVector() && numberOfChildren() == b->numberOfChildren()); + // Dot product is defined between two vectors of same size and orientation + assert(isVector() && b->isVector() && numberOfChildren() == b->numberOfChildren() && numberOfRows() == b->numberOfRows()); 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()); @@ -521,8 +521,9 @@ Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionCon } Matrix Matrix::cross(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { - // Cross product is defined between two vectors of size 3 - assert(isVector() && b->isVector() && numberOfChildren() == 3 && b->numberOfChildren() == 3); + /* Cross product is defined between two vectors of size 3 and of same + * orientation */ + assert(isVector() && b->isVector() && numberOfChildren() == 3 && b->numberOfChildren() == 3 && numberOfRows() == b->numberOfRows()); Matrix matrix = Matrix::Builder(); for (int j = 0; j < 3; j++) { int j1 = (j+1)%3; @@ -535,7 +536,7 @@ Matrix Matrix::cross(Matrix * b, ExpressionNode::ReductionContext reductionConte matrix.addChildAtIndexInPlace(difference, matrix.numberOfChildren(), matrix.numberOfChildren()); difference.shallowReduce(reductionContext); } - matrix.setDimensions(3, 1); + matrix.setDimensions(numberOfRows(), numberOfColumns()); return matrix; } diff --git a/poincare/src/matrix_complex.cpp b/poincare/src/matrix_complex.cpp index f0f087068e3..de32b2620c8 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -153,7 +153,7 @@ std::complex MatrixComplexNode::dot(Evaluation * e) const { return std::complex(NAN, NAN); } MatrixComplex * b = static_cast*>(e); - if (!isVector() || !b->isVector() || numberOfChildren() != b->numberOfChildren()) { + if (!isVector() || !b->isVector() || numberOfChildren() != b->numberOfChildren() || numberOfRows() != b->numberOfRows()) { return std::complex(NAN, NAN); } std::complex sum = 0; @@ -169,14 +169,14 @@ Evaluation MatrixComplexNode::cross(Evaluation * e) const { return MatrixComplex::Undefined(); } MatrixComplex * b = static_cast*>(e); - if (!isVector() || !b->isVector() || numberOfChildren() != 3 || b->numberOfChildren() != 3) { + if (!isVector() || !b->isVector() || numberOfChildren() != 3 || b->numberOfChildren() != 3 || numberOfRows() != b->numberOfRows()) { 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, 3, 1); + return MatrixComplex::Builder(operandsCopy, numberOfRows(), numberOfColumns()); } // MATRIX COMPLEX REFERENCE diff --git a/poincare/src/vector_cross.cpp b/poincare/src/vector_cross.cpp index 4208be412a4..750f82e968c 100644 --- a/poincare/src/vector_cross.cpp +++ b/poincare/src/vector_cross.cpp @@ -45,7 +45,7 @@ Expression VectorCross::shallowReduce(ExpressionNode::ReductionContext reduction Matrix matrixChild0 = static_cast(c0); Matrix matrixChild1 = static_cast(c1); // Cross product is defined between two vectors of size 3 - if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != 3 || matrixChild1.numberOfChildren() != 3) { + if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != 3 || matrixChild1.numberOfChildren() != 3 || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { return replaceWithUndefinedInPlace(); } Expression a = matrixChild0.cross(&matrixChild1, reductionContext); diff --git a/poincare/src/vector_dot.cpp b/poincare/src/vector_dot.cpp index 820abf7c1df..ce179157567 100644 --- a/poincare/src/vector_dot.cpp +++ b/poincare/src/vector_dot.cpp @@ -44,8 +44,9 @@ Expression VectorDot::shallowReduce(ExpressionNode::ReductionContext reductionCo 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 dimensions - if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != matrixChild1.numberOfChildren()) { + /* Dot product is defined between two vectors of the same dimension and + * orientation */ + if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != matrixChild1.numberOfChildren() || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { return replaceWithUndefinedInPlace(); } Expression a = matrixChild0.dot(&matrixChild1, reductionContext); diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index 7b7ec97b621..741f88a3e7d 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -440,15 +440,11 @@ QUIZ_CASE(poincare_approximation_function) { 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("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("dot([[1,2,3]],[[4,7,8]])", "42"); assert_expression_approximates_to("dot([[1,2,3]],[[4,7,8]])", "42"); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index c54ae8ecc6d..dbd8780d867 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1091,12 +1091,14 @@ QUIZ_CASE(poincare_simplification_matrix) { // 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]])", "[[-5][4][-1]]"); - assert_parsed_expression_simplify_to("cross([[1,π,𝐢]],[[𝐢π,𝐢π^2,-π]])", "[[0][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]])", "0"); + 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 From 522456677a73d0a714da33d567cd9fd7c9b13dcc Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 18 Nov 2020 10:04:36 +0100 Subject: [PATCH 431/560] [poincare] Add an Array class to factorize Matrix and Grid methods Change-Id: Ia61caf0bf182c1111119740d43d957f203c0975c --- poincare/include/poincare/array.h | 29 ++++++++++++++++++++++ poincare/include/poincare/grid_layout.h | 26 ++++++------------- poincare/include/poincare/matrix.h | 21 ++++------------ poincare/include/poincare/matrix_complex.h | 29 ++++++---------------- 4 files changed, 48 insertions(+), 57 deletions(-) create mode 100644 poincare/include/poincare/array.h diff --git a/poincare/include/poincare/array.h b/poincare/include/poincare/array.h new file mode 100644 index 00000000000..09f2b2dda0d --- /dev/null +++ b/poincare/include/poincare/array.h @@ -0,0 +1,29 @@ +#ifndef POINCARE_ARRAY_H +#define POINCARE_ARRAY_H + +#include +#include + +namespace Poincare { + +class Array { +public: + Array() : + m_numberOfRows(0), + m_numberOfColumns(0) {} + bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } + 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/grid_layout.h b/poincare/include/poincare/grid_layout.h index 9f5ec32966b..6b11378e925 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 @@ class GridLayoutNode : public LayoutNode { 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 @@ class GridLayout : public Layout { 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/matrix.h b/poincare/include/poincare/matrix.h index 24aabf490f5..45ad18b7222 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -1,22 +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; - bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } - 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); } @@ -53,11 +47,6 @@ class MatrixNode /*final*/ : public ExpressionNode { int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode = Preferences::PrintFloatMode::Decimal, int numberOfSignificantDigits = 0) const override; private: template Evaluation templatedApproximate(ApproximationContext approximationContext) 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; }; class Matrix final : public Expression { @@ -98,8 +87,8 @@ class Matrix final : public Expression { 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 row echelon form, reduced or not. Matrix rowCanonize(ExpressionNode::ReductionContext reductionContext, Expression * determinant, bool reduced = true); diff --git a/poincare/include/poincare/matrix_complex.h b/poincare/include/poincare/matrix_complex.h index 50d7c5922e1..57cf48f5f06 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,11 +36,6 @@ class MatrixComplexNode final : public EvaluationNode { // EvaluationNode typename EvaluationNode::Type type() const override { return EvaluationNode::Type::MatrixComplex; } - bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } - 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; @@ -51,10 +46,6 @@ class MatrixComplexNode final : public EvaluationNode { std::complex norm() const override; std::complex dot(Evaluation * e) const override; Evaluation cross(Evaluation * e) const override; -private: - // See comment on Matrix - uint16_t m_numberOfRows; - uint16_t m_numberOfColumns; }; template @@ -79,14 +70,8 @@ class MatrixComplex final : public Evaluation { 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()); } }; From d8527b02ad7ce2f9ccb6373c72a60ce87b0eca32 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 18 Nov 2020 10:35:10 +0100 Subject: [PATCH 432/560] [poincare] Add a vector type for matrix product and norm operations Change-Id: I28b0956273f8c0a3a9bdc4389956caa106d6b8af --- poincare/include/poincare/array.h | 7 ++++++- poincare/include/poincare/matrix.h | 2 +- poincare/include/poincare/matrix_complex.h | 2 +- poincare/src/matrix.cpp | 12 ++++++------ poincare/src/matrix_complex.cpp | 6 +++--- poincare/src/vector_cross.cpp | 4 ++-- poincare/src/vector_dot.cpp | 5 ++--- poincare/src/vector_norm.cpp | 4 ++-- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/poincare/include/poincare/array.h b/poincare/include/poincare/array.h index 09f2b2dda0d..d3f001a41e1 100644 --- a/poincare/include/poincare/array.h +++ b/poincare/include/poincare/array.h @@ -8,10 +8,15 @@ namespace Poincare { class Array { public: + enum class VectorType { + None, + Vertical, + Horizontal + }; Array() : m_numberOfRows(0), m_numberOfColumns(0) {} - bool isVector() const { return m_numberOfRows == 1 || m_numberOfColumns == 1; } + 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; } diff --git a/poincare/include/poincare/matrix.h b/poincare/include/poincare/matrix.h index 45ad18b7222..ee443e1bd59 100644 --- a/poincare/include/poincare/matrix.h +++ b/poincare/include/poincare/matrix.h @@ -57,7 +57,7 @@ class Matrix final : public Expression { static Matrix Builder() { return TreeHandle::NAryBuilder(); } void setDimensions(int rows, int columns); - bool isVector() const { return node()->isVector(); } + Array::VectorType vectorType() const { return node()->vectorType(); } int numberOfRows() const { return node()->numberOfRows(); } int numberOfColumns() const { return node()->numberOfColumns(); } using TreeHandle::addChildAtIndexInPlace; diff --git a/poincare/include/poincare/matrix_complex.h b/poincare/include/poincare/matrix_complex.h index 57cf48f5f06..d24dbfa3e98 100644 --- a/poincare/include/poincare/matrix_complex.h +++ b/poincare/include/poincare/matrix_complex.h @@ -63,7 +63,7 @@ class MatrixComplex final : public Evaluation { std::complex complexAtIndex(int index) const { return node()->complexAtIndex(index); } - bool isVector() const { return node()->isVector(); } + 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); diff --git a/poincare/src/matrix.cpp b/poincare/src/matrix.cpp index b2ddc89880e..48422d6b6bd 100644 --- a/poincare/src/matrix.cpp +++ b/poincare/src/matrix.cpp @@ -494,7 +494,8 @@ Expression Matrix::determinant(ExpressionNode::ReductionContext reductionContext } Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const { - assert(isVector()); + // 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()); @@ -509,8 +510,8 @@ Expression Matrix::norm(ExpressionNode::ReductionContext reductionContext) const } Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { - // Dot product is defined between two vectors of same size and orientation - assert(isVector() && b->isVector() && numberOfChildren() == b->numberOfChildren() && numberOfRows() == b->numberOfRows()); + // 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()); @@ -521,9 +522,8 @@ Expression Matrix::dot(Matrix * b, ExpressionNode::ReductionContext reductionCon } Matrix Matrix::cross(Matrix * b, ExpressionNode::ReductionContext reductionContext) const { - /* Cross product is defined between two vectors of size 3 and of same - * orientation */ - assert(isVector() && b->isVector() && numberOfChildren() == 3 && b->numberOfChildren() == 3 && numberOfRows() == b->numberOfRows()); + // 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; diff --git a/poincare/src/matrix_complex.cpp b/poincare/src/matrix_complex.cpp index de32b2620c8..5099f7dfede 100644 --- a/poincare/src/matrix_complex.cpp +++ b/poincare/src/matrix_complex.cpp @@ -137,7 +137,7 @@ MatrixComplex MatrixComplexNode::ref(bool reduced) const { template std::complex MatrixComplexNode::norm() const { - if (!isVector()) { + if (vectorType() == Array::VectorType::None) { return std::complex(NAN, NAN); } std::complex sum = 0; @@ -153,7 +153,7 @@ std::complex MatrixComplexNode::dot(Evaluation * e) const { return std::complex(NAN, NAN); } MatrixComplex * b = static_cast*>(e); - if (!isVector() || !b->isVector() || numberOfChildren() != b->numberOfChildren() || numberOfRows() != b->numberOfRows()) { + if (vectorType() == Array::VectorType::None || vectorType() != b->vectorType() || numberOfChildren() != b->numberOfChildren()) { return std::complex(NAN, NAN); } std::complex sum = 0; @@ -169,7 +169,7 @@ Evaluation MatrixComplexNode::cross(Evaluation * e) const { return MatrixComplex::Undefined(); } MatrixComplex * b = static_cast*>(e); - if (!isVector() || !b->isVector() || numberOfChildren() != 3 || b->numberOfChildren() != 3 || numberOfRows() != b->numberOfRows()) { + if (vectorType() == Array::VectorType::None || vectorType() != b->vectorType() || numberOfChildren() != 3 || b->numberOfChildren() != 3) { return MatrixComplex::Undefined(); } std::complex operandsCopy[3]; diff --git a/poincare/src/vector_cross.cpp b/poincare/src/vector_cross.cpp index 750f82e968c..6aeb6e7a190 100644 --- a/poincare/src/vector_cross.cpp +++ b/poincare/src/vector_cross.cpp @@ -44,8 +44,8 @@ Expression VectorCross::shallowReduce(ExpressionNode::ReductionContext reduction 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 size 3 - if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != 3 || matrixChild1.numberOfChildren() != 3 || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { + // 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); diff --git a/poincare/src/vector_dot.cpp b/poincare/src/vector_dot.cpp index ce179157567..ed8305fafa5 100644 --- a/poincare/src/vector_dot.cpp +++ b/poincare/src/vector_dot.cpp @@ -44,9 +44,8 @@ Expression VectorDot::shallowReduce(ExpressionNode::ReductionContext reductionCo 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 - * orientation */ - if (!matrixChild0.isVector() || !matrixChild1.isVector() || matrixChild0.numberOfChildren() != matrixChild1.numberOfChildren() || matrixChild0.numberOfRows() != matrixChild1.numberOfRows()) { + // 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); diff --git a/poincare/src/vector_norm.cpp b/poincare/src/vector_norm.cpp index 1f280ff3d44..cccd7f0bc9a 100644 --- a/poincare/src/vector_norm.cpp +++ b/poincare/src/vector_norm.cpp @@ -42,8 +42,8 @@ Expression VectorNorm::shallowReduce(ExpressionNode::ReductionContext reductionC Expression c = childAtIndex(0); if (c.type() == ExpressionNode::Type::Matrix) { Matrix matrixChild = static_cast(c); - if (!matrixChild.isVector()) { - // Norm is only defined on vectors + // Norm is only defined on vectors only + if (matrixChild.vectorType() == Array::VectorType::None) { return replaceWithUndefinedInPlace(); } Expression a = matrixChild.norm(reductionContext); From 3ac3fba4683ae228087e1d6352518da9154a9d77 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 19 Nov 2020 15:24:14 +0100 Subject: [PATCH 433/560] [poincare] Prevent system parenthesis text insertion Change-Id: I5963d2fda37ed9776059b29edced75f88b4ccb2b --- poincare/src/layout_cursor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 17fc57fc56d..b90c6922e89 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -223,12 +223,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 From 5e679b7bc8d32dd86a9a84ea54e3550868f7d521 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 16 Nov 2020 12:18:13 +0100 Subject: [PATCH 434/560] [graph] Change cursor initial position The cursor now initially aligns with the grid, and tries to avoid points where the function is not defined. Change-Id: Ie3b304281f5848102c2f3c0aa5c031b351418186 --- apps/shared/function_graph_controller.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index a87f128fb11..0a44fc3c049 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -68,9 +68,21 @@ void FunctionGraphController::reloadBannerView() { reloadBannerViewForCursorOnFunction(m_cursor, record, functionStore(), AppsContainer::sharedAppsContainer()->globalContext()); } - 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 { From 56e03aae0ce7113e411a14099f5afdc279be2182 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 16 Nov 2020 12:26:56 +0100 Subject: [PATCH 435/560] [shared] Remove angleUnitVersion The angleUnitVersion check is no longer needed, as changing the angle unit with a trigonometric function set would leave the cursor hanging. Change-Id: I17d99c05daeacfec953865158cdfe7706635cd32 --- apps/graph/app.cpp | 2 +- apps/graph/graph/graph_controller.cpp | 4 ++-- apps/graph/graph/graph_controller.h | 2 +- apps/sequence/app.cpp | 2 +- apps/sequence/graph/graph_controller.cpp | 4 ++-- apps/sequence/graph/graph_controller.h | 2 +- apps/shared/function_app.cpp | 3 +-- apps/shared/function_app.h | 2 -- apps/shared/function_graph_controller.cpp | 9 ++------- apps/shared/function_graph_controller.h | 3 +-- 10 files changed, 12 insertions(+), 21 deletions(-) diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index e64c100ac5b..38918a61c01 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -54,7 +54,7 @@ App::App(Snapshot * snapshot) : 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->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/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 36b5a9e7c5e..a6de8426862 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 * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : - FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, 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), diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 0d558f211e4..433ab9ebe23 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -15,7 +15,7 @@ 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 * 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; } diff --git a/apps/sequence/app.cpp b/apps/sequence/app.cpp index 3a4ee7191eb..1a07b99c161 100644 --- a/apps/sequence/app.cpp +++ b/apps/sequence/app.cpp @@ -50,7 +50,7 @@ App::App(Snapshot * snapshot) : 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->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), diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index 4bf2833828e..906d56dfa20 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -12,8 +12,8 @@ using namespace Poincare; namespace Sequence { -GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : - FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, graphRange, &m_view, cursor, indexFunctionSelectedByCursor, 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), diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index 7bc98500391..cc31b328503 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -14,7 +14,7 @@ namespace Sequence { class GraphController final : public Shared::FunctionGraphController { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::SequenceStore * sequenceStore, CurveViewRange * graphRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, 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; } diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index b66b01d1a0f..fbdc4f43aeb 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -7,8 +7,7 @@ namespace Shared { FunctionApp::Snapshot::Snapshot() : m_cursor(), m_indexFunctionSelectedByCursor(0), - m_rangeVersion(0), - m_angleUnitVersion(Preferences::AngleUnit::Radian) + m_rangeVersion(0) { } diff --git a/apps/shared/function_app.h b/apps/shared/function_app.h index df932d2af74..6ee6dce2a6a 100644 --- a/apps/shared/function_app.h +++ b/apps/shared/function_app.h @@ -17,7 +17,6 @@ class FunctionApp : public ExpressionFieldDelegateApp { Snapshot(); CurveViewCursor * cursor() { return &m_cursor; } 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,7 +26,6 @@ class FunctionApp : public ExpressionFieldDelegateApp { private: int m_indexFunctionSelectedByCursor; uint32_t m_rangeVersion; - Poincare::Preferences::AngleUnit m_angleUnitVersion; }; static FunctionApp * app() { return static_cast(Container::activeApp()); diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 0a44fc3c049..7004faf0cb3 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -11,9 +11,8 @@ using namespace Poincare; namespace Shared { -FunctionGraphController::FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, Preferences::AngleUnit * 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_angleUnitVersion(angleUnitVersion), m_indexFunctionSelectedByCursor(indexFunctionSelectedByCursor) { } @@ -42,11 +41,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(); } diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 0516b0c87eb..2f7c7472380 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -12,7 +12,7 @@ namespace Shared { class FunctionGraphController : public InteractiveCurveViewController, public FunctionBannerDelegate { public: - FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, 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; void didBecomeFirstResponder() override; void viewWillAppear() override; @@ -50,7 +50,6 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu bool moveCursorVertically(int direction) override; uint32_t rangeVersion() override; - Poincare::Preferences::AngleUnit * m_angleUnitVersion; int * m_indexFunctionSelectedByCursor; }; From 4aa19ccd78cf6e6805501535620a933a76195b32 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 24 Nov 2020 10:03:33 +0100 Subject: [PATCH 436/560] [i18n] Fix PT translation for imperial units Pint -> "Pinto" Cup -> "Copo" Change-Id: I96dea0c6f1551db617f017d2121aaf0f2b6d0095 --- apps/toolbox.pt.i18n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index f0779e3d586..e2093e4af5e 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -87,8 +87,8 @@ UnitVolumeLiterMilli = "Mililitro" UnitVolumeTeaspoon = "Colher de chá" UnitVolumeTablespoon= "Colher de sopa" UnitVolumeFluidOunce = "Onça fluída" -UnitVolumeCup = "Xícara de chá" -UnitVolumePint = "Quartilho" +UnitVolumeCup = "Copo" +UnitVolumePint = "Pinto" UnitVolumeQuart = "Quarto" UnitVolumeGallon = "Galão" UnitMetricMenu = "Métrico" From b08a29cab41b2c99e88c8982cc17d90add8a08c6 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 23 Nov 2020 16:54:34 +0100 Subject: [PATCH 437/560] [apps/sequence] Fix typo Change-Id: Ic34b84073ef268eaddfe4224c3171c9aaa7ca395 --- apps/sequence/list/sequence_toolbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index d268fcc6489..fedcc3966f3 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -69,7 +69,7 @@ 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 From 387d3c9aa1a8f5e0213f9a338fe774421a16b406 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 23 Nov 2020 17:33:38 +0100 Subject: [PATCH 438/560] [apps/sequence] Fix row offset when returning to previous menu Change-Id: Ic3eee6bb1ecb991b97dc9190fab95774938d294e --- apps/sequence/list/sequence_toolbox.h | 6 ++++++ escher/include/escher/nested_menu_controller.h | 1 + escher/src/nested_menu_controller.cpp | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/sequence/list/sequence_toolbox.h b/apps/sequence/list/sequence_toolbox.h index c67122a9fbe..8f67caa4923 100644 --- a/apps/sequence/list/sequence_toolbox.h +++ b/apps/sequence/list/sequence_toolbox.h @@ -17,6 +17,12 @@ class SequenceToolbox : public MathToolbox { int typeAtLocation(int i, int j) override; void buildExtraCellsLayouts(const char * sequenceName, int recurrenceDepth); private: + int stackRowIndex(int selectedRow) override { + /* At 0 depth, mathToolboxIndex() offset must be removed when calling + * NestedMenuController::push() so that the pushed row is correct when + * popped in NestedMenuController::returnToPreviousMenu(). */ + return stackDepth() == 0 ? selectedRow + m_numberOfAddedCells : selectedRow; + }; bool selectAddedCell(int selectedRow); int mathToolboxIndex(int index); ExpressionTableCell m_addedCells[k_maxNumberOfDisplayedRows]; diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index 6711c5eb17c..b449a96aa4f 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -66,6 +66,7 @@ class NestedMenuController : public StackViewController, public ListViewDataSour virtual bool selectSubMenu(int selectedRow); virtual bool returnToPreviousMenu(); virtual bool selectLeaf(int selectedRow) = 0; + virtual int stackRowIndex(int selectedRow) { return selectedRow; } InputEventHandler * sender() { return m_sender; } virtual HighlightCell * leafCellAtIndex(int index) = 0; virtual HighlightCell * nodeCellAtIndex(int index) = 0; diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 652420e2ced..09ad5c002cc 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -157,7 +157,7 @@ bool NestedMenuController::handleEventForRow(Ion::Events::Event event, int rowIn } bool NestedMenuController::selectSubMenu(int selectedRow) { - m_stack.push(selectedRow, m_selectableTableView.contentOffset().y()); + m_stack.push(stackRowIndex(selectedRow), m_selectableTableView.contentOffset().y()); m_listController.setFirstSelectedRow(0); Container::activeApp()->setFirstResponder(&m_listController); return true; From cfc37f1bac54acc23b28f9b687b08e5e2ac59b0e Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 24 Nov 2020 11:43:14 +0100 Subject: [PATCH 439/560] [apps/sequence] Factorize sequence row index offsets Change-Id: Id6fa632521858c48fe7d3daf35fd846bf24a05eb --- apps/sequence/list/sequence_toolbox.cpp | 21 +++++-------------- apps/sequence/list/sequence_toolbox.h | 11 ++++------ .../include/escher/nested_menu_controller.h | 2 +- escher/src/nested_menu_controller.cpp | 4 ++-- 4 files changed, 12 insertions(+), 26 deletions(-) diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index fedcc3966f3..8b87de89c8d 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -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) { @@ -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 8f67caa4923..de0fe60474d 100644 --- a/apps/sequence/list/sequence_toolbox.h +++ b/apps/sequence/list/sequence_toolbox.h @@ -17,14 +17,11 @@ class SequenceToolbox : public MathToolbox { int typeAtLocation(int i, int j) override; void buildExtraCellsLayouts(const char * sequenceName, int recurrenceDepth); private: - int stackRowIndex(int selectedRow) override { - /* At 0 depth, mathToolboxIndex() offset must be removed when calling - * NestedMenuController::push() so that the pushed row is correct when - * popped in NestedMenuController::returnToPreviousMenu(). */ - return stackDepth() == 0 ? selectedRow + m_numberOfAddedCells : selectedRow; - }; + /* 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/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index b449a96aa4f..44ac41eca47 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -66,7 +66,7 @@ class NestedMenuController : public StackViewController, public ListViewDataSour virtual bool selectSubMenu(int selectedRow); virtual bool returnToPreviousMenu(); virtual bool selectLeaf(int selectedRow) = 0; - virtual int stackRowIndex(int selectedRow) { return selectedRow; } + 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/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 09ad5c002cc..3d1b0df50c4 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -157,7 +157,7 @@ bool NestedMenuController::handleEventForRow(Ion::Events::Event event, int rowIn } bool NestedMenuController::selectSubMenu(int selectedRow) { - m_stack.push(stackRowIndex(selectedRow), m_selectableTableView.contentOffset().y()); + m_stack.push(selectedRow, m_selectableTableView.contentOffset().y()); m_listController.setFirstSelectedRow(0); Container::activeApp()->setFirstResponder(&m_listController); return true; @@ -166,7 +166,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); From e4162976e40b1cefb2b5bdd53a4d53c9b66d8853 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 17 Nov 2020 11:03:34 +0100 Subject: [PATCH 440/560] [poincare/zoom] Method NextUnit Change-Id: I8200c38fd44b662dcffa8e9850fc272f3e2dc7e8 --- poincare/include/poincare/zoom.h | 1 + poincare/src/zoom.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 591e64b20dd..dae41620316 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -67,6 +67,7 @@ class Zoom { * 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); + static void NextUnit(float * mantissa, float * exponent); }; } diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b9683b6f2ee..20cf54bb1a1 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -371,4 +371,21 @@ bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2 return true; } +void Zoom::NextUnit(float * mantissa, float * exponent) { + if (*mantissa == k_smallUnitMantissa) { + *mantissa = k_mediumUnitMantissa; + return; + } + if (*mantissa == k_mediumUnitMantissa) { + *mantissa = k_largeUnitMantissa; + return; + } + if (*mantissa == k_largeUnitMantissa) { + *mantissa = k_smallUnitMantissa; + *exponent = 10.f * *exponent; + return; + } + assert(false); +} + } From 540209f1a9e2ffb14b49bff511d3d2dd1db7a709 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 17 Nov 2020 11:37:44 +0100 Subject: [PATCH 441/560] [poincare/zoom] Preserve standard bound when normalizing Change-Id: I6d736e945eda6982ad26836763feded8a55134a0 --- poincare/src/zoom.cpp | 48 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 20cf54bb1a1..2b5ed19a163 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -221,36 +221,37 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } } +static float smoothToPowerOfTen(float x) { + return std::pow(10.f, std::round(std::log10(x))); +} + void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { - constexpr float units[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; constexpr float rangeMagnitudeWeight = 0.2f; constexpr float maxMagnitudeDifference = 2.f; - /* RefinedYRange for display, by default, evaluates the function 80 times, * and we call it 21 times. As we only need a rough estimate of the range to * compare it to the others, we save some time by only sampling the function * 20 times. */ constexpr int sampleSize = k_sampleSize / 4; - float bestGrade = FLT_MAX; + float bestGrade = FLT_MAX, bestUnit, bestMagnitude; + float unit = k_smallUnitMantissa; float xMagnitude = k_minimalDistance; float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; while (xMagnitude < k_maximalDistance) { - for(const float unit : units) { - const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); - float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); - float grade = std::fabs(std::log(currentRatio / yxRatio)); - /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ - grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; - if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { - bestGrade = grade; - *xMin = -xRange; - *xMax = xRange; - *yMin = yMinRange; - *yMax = yMaxRange; - } + const float xRange = unit * xMagnitude; + RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); + float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); + float grade = std::fabs(std::log(currentRatio / yxRatio)); + /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ + grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; + if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { + bestGrade = grade; + bestUnit = unit; + bestMagnitude = xMagnitude; + *yMin = yMinRange; + *yMax = yMaxRange; } - xMagnitude *= 10.f; + NextUnit(&unit, &xMagnitude); } if (bestGrade == FLT_MAX) { *xMin = NAN; @@ -260,6 +261,17 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f return; } + /* The X bounds are preset, user-friendly values : we do not want them to be + * altered by the normalization. To that end, we use a larger unit for the + * horizontal axis, so that the Y axis is the one that will be extended. */ + float xRange = bestUnit * bestMagnitude; + while ((*yMax - *yMin) / (2 * xRange) > yxRatio) { + NextUnit(&bestUnit, &bestMagnitude); + xRange = bestUnit * bestMagnitude; + } + xRange = bestUnit * smoothToPowerOfTen(bestMagnitude); + *xMin = -xRange; + *xMax = xRange; SetToRatio(yxRatio, xMin, xMax, yMin, yMax); } From ca9cb63909da015c6ba39ff38e04c4b396f79497 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 18 Nov 2020 10:06:48 +0100 Subject: [PATCH 442/560] [poincare/zoom] Reduce the step for searching points of interest The algorithm to search for points of interest will miss some. We reduce the step to make it more precise, so that tan(x) and tan(x-90) have the same profile. Change-Id: Ia1bac1d4e7b98d2d6f36f8ce12ed9dac67d40198 --- poincare/include/poincare/zoom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index dae41620316..415e83fb6ec 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -45,7 +45,7 @@ class Zoom { 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.1f; + 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; From a41860276372e05fdc841b9401663ed4c2632fb3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 19 Nov 2020 11:31:41 +0100 Subject: [PATCH 443/560] [poincare/zoom] Remember points of interest when refining Change-Id: I229cbffa2577d23bfa0a4c0632bc9ec32ae338e7 --- poincare/src/zoom.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 2b5ed19a163..5569c9ec283 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -208,8 +208,8 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float float bound = std::exp(sum / pop + 1.f); sampleYMin = std::max(sampleYMin, - bound); sampleYMax = std::min(sampleYMax, bound); - *yMin = sampleYMin; - *yMax = sampleYMax; + *yMin = std::min(*yMin, sampleYMin); + *yMax = std::max(*yMax, 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 From adf28345b1389d15a9527359fce380eddec5aeac Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 13 Nov 2020 10:36:02 +0100 Subject: [PATCH 444/560] [poincare/zoom] Reduce zoom artifact Do not use the bounds of the interval when computing a refined range. As the bounds are often integers, the zoom would give strange result on some non-continuous functions (e.g. f(x) = x!). Change-Id: Ie127fe15191cb3951cff223adc8dc3172188c789 --- poincare/src/zoom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 5569c9ec283..eb8a9142863 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -183,7 +183,7 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float float sum = 0.f; int pop = 0; - for (int i = 1; i < sampleSize; i++) { + for (int i = 1; i < sampleSize - 1; i++) { x = xMin + i * step; y = evaluation(x, context, auxiliary); if (!std::isfinite(y)) { From 291b400595484613059773d924f53f017f7bb324 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 19 Nov 2020 17:05:21 +0100 Subject: [PATCH 445/560] [poincare/zoom] Ignore imprecise float value When looking for extrema in a function, we need to discard results where the function growth rate is smaller than the precision of the float type itself : these results are likely to be too noisy, and can cause false positives. e.g. : The sqrt(x^2+1)-x function Change-Id: I6e2c002d7308b41a4c226d274cbb5d9efe4ea7db --- poincare/src/zoom.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index eb8a9142863..922071254fb 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -22,6 +22,16 @@ constexpr float 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.4e-7f; // 2^-22 ~ 10^-6.6 + 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); @@ -84,7 +94,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, /* Check for a change in the profile. */ const PointOfInterest variation = BoundOfIntervalOfDefinitionIsReached(yPrev, yNext) ? PointOfInterest::Bound : RootExistsOnInterval(yPrev, yNext) ? PointOfInterest::Root : - ExtremumExistsOnInterval(yOld, yPrev, yNext) ? PointOfInterest::Extremum : + (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 From 229865aff85586e6595cfd54c123ea43f3e80ff1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 19 Nov 2020 11:25:23 +0100 Subject: [PATCH 446/560] [poincare/zoom] Rework unit tests Change-Id: Id0ff36517d14710701f6a4c1cec8817442b6f946 --- poincare/test/zoom.cpp | 287 ++++++++++++++++++++++------------------- 1 file changed, 151 insertions(+), 136 deletions(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 999517346a9..cf0ba52e4dc 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -5,6 +5,9 @@ using namespace Poincare; +// When adding the graph window margins, this ratio gives an orthonormal window +constexpr float NormalRatio = 0.442358822; + class ParametersPack { public: ParametersPack(Expression expression, const char * symbol, Preferences::AngleUnit angleUnit) : @@ -16,168 +19,157 @@ class ParametersPack { 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; }; -constexpr float NormalRatio = 306.f / 576.f; - 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 bracket(float a, float b, float d) { - assert(std::isfinite(a) && std::isfinite(b) && std::isfinite(d)); - return a - d <= b && b <= a + d; +bool float_equal(float a, float b, float tolerance = 0.f) { + assert(std::isfinite(tolerance)); + return !(std::isnan(a) || std::isnan(b)) + && a <= b + tolerance && a >= b - tolerance; } -bool range_matches(float min, float max, float targetMin, float targetMax, float tolerance) { - assert(std::isfinite(targetMin) == std::isfinite(targetMax)); - - const float rangeTolerance = tolerance * (targetMax - targetMin); - - if (std::isfinite(targetMin) && (targetMin <= targetMax)) { - return std::isfinite(min) && std::isfinite(max) - && bracket(min, targetMin, rangeTolerance) && bracket(max, targetMax, rangeTolerance); - } else { - return (!std::isfinite(min) && !std::isfinite(max)) || (max < min); - } +bool range1D_matches(float min, float max, float targetMin, float targetMax, float tolerance = 0.f) { + return (float_equal(min, targetMin, tolerance) && float_equal(max, targetMax, tolerance)) + || (std::isnan(min) && std::isnan(max) && std::isnan(targetMin) && std::isnan(targetMax)); } -bool window_is_similar(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool addXMargin = false, float tolerance = 0.1f) { - assert(std::isfinite(targetXMin) == std::isfinite(targetXMax) && std::isfinite(targetYMin) == std::isfinite(targetYMax)); - - /* The target window given in the test should reflect the points of - * interest we try to display, and should not take into account the - * breathing room added by the algorithm. We handle it here. */ - constexpr float margin = 0.3f; // Zoom::k_breathingRoom; - float xDelta = addXMargin ? (xMax - xMin) * margin : 0.f; - - return range_matches(xMin, xMax, targetXMin - xDelta, targetXMax + xDelta, tolerance) - && range_matches(yMin, yMax, targetYMin, targetYMax, tolerance); +bool ranges_match(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, float tolerance = 0.f) { + 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 = FLT_MAX, float targetYMax = -FLT_MAX, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { +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); - float xMin, xMax, yMin, yMax; ParametersPack aux(e, symbol, angleUnit); - Zoom::InterestingRangesForDisplay(&evaluate_expression, &xMin, &xMax, &yMin, &yMax, -INFINITY, INFINITY, &globalContext, &aux); - - bool test = window_is_similar(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, true); - quiz_assert_print_if_failure(test, definition); + 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, FLT_EPSILON), definition); } -void assert_has_no_interesting_range(const char * definition, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { - assert_interesting_range_is(definition, NAN, NAN, NAN, NAN, angleUnit, symbol); +QUIZ_CASE(poincare_zoom_interesting_ranges) { + assert_interesting_range_is("0", NAN, NAN, NAN, NAN); + assert_interesting_range_is("1", NAN, NAN, NAN, NAN); + assert_interesting_range_is("-100", NAN, NAN, NAN, NAN); + assert_interesting_range_is("x", NAN, NAN, NAN, NAN); + assert_interesting_range_is("x^2", NAN, NAN, NAN, NAN); + assert_interesting_range_is("-(x^3)", NAN, NAN, NAN, NAN); + assert_interesting_range_is("10×x^4", NAN, NAN, 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", 19.126959, 21.957670, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("-11x+100", 8.806580, 10.109919, FLT_MAX, -FLT_MAX); + assert_interesting_range_is("x^2-1", -8.634861, 8.634861, -1, -1); + 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); } -QUIZ_CASE(poincare_zoom_interesting_ranges) { - assert_has_no_interesting_range(Undefined::Name()); - assert_has_no_interesting_range("0"); - assert_has_no_interesting_range("1"); - assert_has_no_interesting_range("-100"); - assert_has_no_interesting_range("x"); - assert_has_no_interesting_range("x^2"); - assert_has_no_interesting_range("-(x^3)"); - assert_has_no_interesting_range("10×x^4"); - assert_has_no_interesting_range("ℯ^(-x)"); - - assert_interesting_range_is("x-21", 20.5, 22.5); - assert_interesting_range_is("-11x+100", 8.7, 9.5); - assert_interesting_range_is("x^2-1", -5, 5, -1, -1); - assert_interesting_range_is("(x+10)(x-10)", -10.5, 10.5, -100, -100); - assert_interesting_range_is("x(x-1)(x-2)(x-3)(x-4)(x-5)", 0, 5, -16, 5); - assert_interesting_range_is("1/x", -2.5, 2.5); - assert_interesting_range_is("1/(x-10)", 8, 14); - assert_interesting_range_is("√(x)", 0, 7); - assert_interesting_range_is("ln(x)", 0, 5); - assert_interesting_range_is("sin(x)", -9, 9, -1, 1); - assert_interesting_range_is("sin(x)", -480, 480, -1, 1, Degree); - assert_interesting_range_is("cos(x)", -10, 10, -1, 1); - assert_interesting_range_is("cos(x)", -580, 580, -1, 1, Degree); - assert_interesting_range_is("tan(x)", -9, 9); - assert_interesting_range_is("tan(x)", -500, 500, FLT_MAX, -FLT_MAX, Degree); - assert_interesting_range_is("asin(x)", -1, 1); - assert_interesting_range_is("acos(x)", -1, 1, FLT_MAX, -FLT_MAX, Degree); - assert_interesting_range_is("atan(x)", -2, 2); - assert_interesting_range_is("sin(x)/x", -12, 12, -0.2, 1); - assert_interesting_range_is("x×sin(x)", -9, 9, -5, 8); - assert_interesting_range_is("x×ln(x)", 0.22, 0.88, -0.367828071, -0.367828071); -} - -void assert_refined_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { + +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); - float yMin, yMax; ParametersPack aux(e, symbol, angleUnit); - Zoom::RefinedYRangeForDisplay(&evaluate_expression, targetXMin, targetXMax, &yMin, &yMax, &globalContext, &aux); - - bool test = window_is_similar(targetXMin, targetXMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax); - quiz_assert_print_if_failure(test, definition); + Zoom::RefinedYRangeForDisplay(evaluate_expression, xMin, xMax, &yMin, &yMax, &globalContext, &aux); + quiz_assert_print_if_failure(range1D_matches(yMin, yMax, targetYMin, targetYMax, FLT_EPSILON), definition); } -QUIZ_CASE(poincare_zoom_refined_ranges) { - assert_refined_range_is(Undefined::Name(), NAN, NAN, NAN, NAN); - assert_refined_range_is("0", NAN, NAN, NAN, NAN); - +QUIZ_CASE(poincare_zoom_refined_range) { assert_refined_range_is("1", -10, 10, 1, 1); - assert_refined_range_is("x", -10, 10, -10, 10); - assert_refined_range_is("x^2", -10, 10, 0, 37); - assert_refined_range_is("1/x", -10, 10, -0.73, 0.73); - assert_refined_range_is("(x-100)(x+100)", -120, 120, -10000, 4400); - assert_refined_range_is("sin(x)", -300, 300, -1, 1, Degree); - assert_refined_range_is("ℯ^x", -10, 10, 0, 3); - assert_refined_range_is("atan(x)", -100, 100, -90, 90, Degree); + 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); } -bool can_find_normal_range(const char * definition, Preferences::AngleUnit angleUnit, const char * symbol) { +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, FLT_EPSILON)); + float xMin, xMax, yMin, yMax; Shared::GlobalContext globalContext; Expression e = parse_expression(definition, &globalContext, false); - float xMin, xMax, yMin, yMax; ParametersPack aux(e, symbol, angleUnit); - Zoom::RangeWithRatioForDisplay(&evaluate_expression, NormalRatio, &xMin, &xMax, &yMin, &yMax, &globalContext, &aux); - return std::isfinite(xMin) && std::isfinite(xMax) && std::isfinite(yMin) && std::isfinite(yMax); -} - -void assert_range_is_normalized(const char * definition, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { - quiz_assert_print_if_failure(can_find_normal_range(definition, angleUnit, symbol), definition); + 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, FLT_EPSILON), definition); } -QUIZ_CASE(poincare_zoom_ratio_ranges) { - assert_range_is_normalized("x"); - assert_range_is_normalized("x^2"); - assert_range_is_normalized("-(x^3)"); - assert_range_is_normalized("ℯ^x"); - assert_range_is_normalized("ℯ^(-x)"); +QUIZ_CASE(poincare_zoom_range_with_ratio) { + assert_orthonormal_range_is("1", NAN, NAN, NAN, NAN); + assert_orthonormal_range_is("x", -5, 5, -2.21179414, 2.21179414); + assert_orthonormal_range_is("x^2", -2, 2, -0.172234654, 1.59720063); + assert_orthonormal_range_is("x^3", -5, 5, -2.21179414, 2.21179414); + assert_orthonormal_range_is("ℯ^x", -5, 5, -0.852653265, 3.57093501); + assert_orthonormal_range_is("ℯ^x+4", -5, 5, 3.21590924, 7.63949776); } -void assert_full_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") { +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 = (targetXMax - targetXMin) / stepDivisor; + const float step = (xMax - xMin) / stepDivisor; ParametersPack aux(e, symbol, angleUnit); - Zoom::FullRange(&evaluate_expression, targetXMin, targetXMax, step, &yMin, &yMax, &globalContext, &aux); - quiz_assert_print_if_failure(window_is_similar(targetXMin, targetXMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); + 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_ranges) { +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, 22000); + 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.14); + assert_full_range_is("acos(x)", -10, 10, 0, 3.1415925); } -void assert_ranges_combine(int length, float * mins, float * maxs, float targetMin, float targetMax) { +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); @@ -188,19 +180,19 @@ void assert_sanitized_range_is(float xMin, float xMax, float yMin, float yMax, f quiz_assert(xMin == targetXMin && xMax == targetXMax && yMin == targetYMin && yMax == targetYMax); } -void assert_ratio_is_set(float xMin, float xMax, float yMin, float yMax, float yxRatio) { - { - float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; - Zoom::SetToRatio(yxRatio, &tempXMin, &tempXMax, &tempYMin, &tempYMax, false); - quiz_assert((tempYMax - tempYMin) / (tempXMax - tempXMin) == yxRatio); - quiz_assert((tempYMax - tempYMin) > (yMax - yMin) || (tempXMax - tempXMin) > (xMax - xMin)); - } - { - float tempXMin = xMin, tempXMax = xMax, tempYMin = yMin, tempYMax = yMax; - Zoom::SetToRatio(yxRatio, &tempXMin, &tempXMax, &tempYMin, &tempYMax, true); - quiz_assert((tempYMax - tempYMin) / (tempXMax - tempXMin) == yxRatio); - quiz_assert((tempYMax - tempYMin) < (yMax - yMin) || (tempXMax - tempXMin) < (xMax - xMin)); - } +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, FLT_EPSILON)); +} + +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); } QUIZ_CASE(poincare_zoom_utility) { @@ -209,39 +201,62 @@ QUIZ_CASE(poincare_zoom_utility) { constexpr int length = 1; float mins[length] = {-10}; float maxs[length] = {10}; - assert_ranges_combine(length, mins, maxs, -10, 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(length, mins, maxs, -2, 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(length, mins, maxs, -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(length, mins, maxs, -3, 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, 94.6875, 105.3125); - assert_sanitized_range_is(3, -3, -10, 10, -18.8235302, 18.8235302, -10, 10); - assert_sanitized_range_is(3, -3, 2, 2, -10, 10, -3.3125, 7.3125); - assert_sanitized_range_is(NAN, NAN, NAN, NAN, -10, 10, -5.3125, 5.3125); + 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_ratio_is_set(-10, 10, -10, 10, 0.1); - assert_ratio_is_set(-10, 10, -10, 10, 0.5); - assert_ratio_is_set(-10, 10, -10, 10, NormalRatio); - assert_ratio_is_set(-10, 10, -10, 10, 3); - - + 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); } + From 48693832638513fac143a093abf0ae2bf8c0540d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 25 Nov 2020 14:40:34 +0100 Subject: [PATCH 447/560] [poincare/zoom] Better handling of single points of interest Functions with a single point of inerest used to be drawn on a tiny interval around this point. Now, an orthonormal window is built around the point of interest, giving a much nicer view. Change-Id: I7793797aead2695532ddea44d93d62bb7ef870f4 --- poincare/src/zoom.cpp | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 922071254fb..018cc48e733 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -49,7 +49,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float resultYMin = FLT_MAX, resultYMax = - FLT_MAX; float asymptote[2] = {FLT_MAX, - FLT_MAX}; float explosion[2] = {FLT_MAX, - FLT_MAX}; - int numberOfPoints; + int numberOfPoints, totalNumberOfPoints = 0; float xFallback, yFallback[2] = {NAN, NAN}; float firstResult; float dXOld, dXPrev, dXNext, yOld, yPrev, yNext; @@ -63,6 +63,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, 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; } @@ -124,6 +125,7 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, if (std::isnan(firstResult)) { firstResult = dXNext; } + totalNumberOfPoints++; break; default: const float slopeNext = (yNext - yPrev) / (dXNext - dXPrev), slopePrev = (yPrev - yOld) / (dXPrev - dXOld); @@ -151,23 +153,33 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, } } + 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 = NAN; - *xMax = NAN; + *xMin = resultX[0]; + *xMax = resultX[1]; *yMin = NAN; *yMax = NAN; return false; } else { - 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; - } /* Add breathing room around points of interest. */ float xRange = resultX[1] - resultX[0]; resultX[0] -= k_breathingRoom * xRange; @@ -247,9 +259,10 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f float unit = k_smallUnitMantissa; float xMagnitude = k_minimalDistance; float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; + float center = *xMin == *xMax ? *xMin : 0.f; while (xMagnitude < k_maximalDistance) { const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); + RefinedYRangeForDisplay(evaluation, center - xRange, center + xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); float grade = std::fabs(std::log(currentRatio / yxRatio)); /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ @@ -280,8 +293,8 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f xRange = bestUnit * bestMagnitude; } xRange = bestUnit * smoothToPowerOfTen(bestMagnitude); - *xMin = -xRange; - *xMax = xRange; + *xMin = center - xRange; + *xMax = center + xRange; SetToRatio(yxRatio, xMin, xMax, yMin, yMax); } From fdcccde9963dfb5f5c593ea72c6e579c3c15129e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 25 Nov 2020 15:45:04 +0100 Subject: [PATCH 448/560] [poincare/zoom] Update unit tests Change-Id: Ic108bfbacd54d43ef6c721589ada813ca648e88e --- poincare/test/zoom.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index cf0ba52e4dc..ab4757dedab 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -57,19 +57,20 @@ void assert_interesting_range_is(const char * definition, float targetXMin, floa } QUIZ_CASE(poincare_zoom_interesting_ranges) { - assert_interesting_range_is("0", NAN, NAN, NAN, NAN); - assert_interesting_range_is("1", NAN, NAN, NAN, NAN); - assert_interesting_range_is("-100", NAN, NAN, NAN, NAN); - assert_interesting_range_is("x", NAN, NAN, NAN, NAN); - assert_interesting_range_is("x^2", NAN, NAN, NAN, NAN); - assert_interesting_range_is("-(x^3)", NAN, NAN, NAN, NAN); - assert_interesting_range_is("10×x^4", NAN, NAN, NAN, NAN); + 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", 19.126959, 21.957670, FLT_MAX, -FLT_MAX); - assert_interesting_range_is("-11x+100", 8.806580, 10.109919, FLT_MAX, -FLT_MAX); + 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); @@ -87,6 +88,7 @@ QUIZ_CASE(poincare_zoom_interesting_ranges) { 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); } From 746a7810165bf2f3d8c53c724d6ca02ddd2d16ad Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 25 Nov 2020 16:03:05 +0100 Subject: [PATCH 449/560] [poincare/zoom] Fix coding style Change-Id: I263e1a95023f650376bf6a1c4bb45426d52d5e4c --- poincare/src/zoom.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 018cc48e733..28f1d8f96cc 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace Poincare { @@ -28,7 +29,7 @@ static bool DoesNotOverestimatePrecision(float dx, float y1, float y2, float y3) * 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.4e-7f; // 2^-22 ~ 10^-6.6 + constexpr float maxPrecision = 2 * FLT_EPSILON; return (yMax - yMin) / std::fabs(dx) > maxPrecision; } @@ -284,7 +285,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f return; } - /* The X bounds are preset, user-friendly values : we do not want them to be + /* The X bounds are preset, user-friendly values: we do not want them to be * altered by the normalization. To that end, we use a larger unit for the * horizontal axis, so that the Y axis is the one that will be extended. */ float xRange = bestUnit * bestMagnitude; @@ -407,20 +408,17 @@ bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2 } void Zoom::NextUnit(float * mantissa, float * exponent) { - if (*mantissa == k_smallUnitMantissa) { - *mantissa = k_mediumUnitMantissa; - return; - } - if (*mantissa == k_mediumUnitMantissa) { - *mantissa = k_largeUnitMantissa; - return; - } - if (*mantissa == k_largeUnitMantissa) { - *mantissa = k_smallUnitMantissa; - *exponent = 10.f * *exponent; - return; + float mantissaUnits[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; + size_t numberOfUnits = sizeof(mantissaUnits) / sizeof(float); + for (size_t i = 0; i < numberOfUnits; i++) { + if (*mantissa == mantissaUnits[i]) { + *mantissa = mantissaUnits[(i + 1) % numberOfUnits]; + if (*mantissa == mantissaUnits[0]) { + *exponent *= 10.0f; + } + return; + } } - assert(false); } } From ddfde60982c984657e07f236563bffea8f237974 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 25 Nov 2020 18:06:21 +0100 Subject: [PATCH 450/560] [poincare/zoom] Update tests for the device Change-Id: Ied73829ff294dc243ca177c5c2acfb374a7446b9 --- poincare/test/zoom.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index ab4757dedab..ddeea399f09 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -7,6 +7,7 @@ using namespace Poincare; // When adding the graph window margins, this ratio gives an orthonormal window constexpr float NormalRatio = 0.442358822; +constexpr float StandardTolerance = 10 * FLT_EPSILON; class ParametersPack { public: @@ -31,18 +32,18 @@ float evaluate_expression(float x, Context * context, const void * auxiliary) { return pack->expression().approximateWithValueForSymbol(pack->symbol(), x, context, Real, pack->angleUnit()); } -bool float_equal(float a, float b, float tolerance = 0.f) { +bool float_equal(float a, float b, float tolerance = StandardTolerance) { assert(std::isfinite(tolerance)); return !(std::isnan(a) || std::isnan(b)) - && a <= b + tolerance && a >= b - tolerance; + && std::fabs(a - b) <= tolerance * std::fabs(a + b); } -bool range1D_matches(float min, float max, float targetMin, float targetMax, float tolerance = 0.f) { +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 = 0.f) { +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); } @@ -53,7 +54,7 @@ void assert_interesting_range_is(const char * definition, float targetXMin, floa 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, FLT_EPSILON), definition); + quiz_assert_print_if_failure(ranges_match(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); } QUIZ_CASE(poincare_zoom_interesting_ranges) { @@ -98,7 +99,7 @@ void assert_refined_range_is(const char * definition, float xMin, float xMax, fl 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, FLT_EPSILON), definition); + quiz_assert_print_if_failure(range1D_matches(yMin, yMax, targetYMin, targetYMax), definition); } QUIZ_CASE(poincare_zoom_refined_range) { @@ -131,13 +132,13 @@ QUIZ_CASE(poincare_zoom_refined_range) { 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, FLT_EPSILON)); + || 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, FLT_EPSILON), definition); + quiz_assert_print_if_failure(ranges_match(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax), definition); } QUIZ_CASE(poincare_zoom_range_with_ratio) { @@ -186,7 +187,7 @@ void assert_ratio_is_set_to(float yxRatio, float xMin, float xMax, float yMin, f 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, FLT_EPSILON)); + 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) { From 2ba1941270c4a972ddac3a1f919c9014b69989af Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 17:37:51 +0100 Subject: [PATCH 451/560] [poincare/zoom] Float litterals Change-Id: Ifea5f356b9cee271c43b3d9b9168fd38db78d87a --- poincare/src/zoom.cpp | 2 +- poincare/test/zoom.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 28f1d8f96cc..5a848d6f8b1 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -29,7 +29,7 @@ static bool DoesNotOverestimatePrecision(float dx, float y1, float y2, float y3) * 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 * FLT_EPSILON; + constexpr float maxPrecision = 2.f * FLT_EPSILON; return (yMax - yMin) / std::fabs(dx) > maxPrecision; } diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index ddeea399f09..c5b157f2f03 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -7,7 +7,7 @@ using namespace Poincare; // When adding the graph window margins, this ratio gives an orthonormal window constexpr float NormalRatio = 0.442358822; -constexpr float StandardTolerance = 10 * FLT_EPSILON; +constexpr float StandardTolerance = 10.f * FLT_EPSILON; class ParametersPack { public: From c55eb6b028c7458f0938175c835da9d50e0fb9b4 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 24 Nov 2020 14:54:42 +0100 Subject: [PATCH 452/560] [apps/regression] Fix angleUnit dependent coefficients for trigonometric model Change-Id: I8027c131b0fd7b020eb502168ac068a0b84da1e4 --- apps/regression/model/trigonometric_model.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index b6ddd6359d4..a612f79781b 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -41,8 +41,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(Poincare::Preferences::sharedPreferences()->angleUnit()); + // sin() is here defined for radians, so b*x+c are converted in radians. + return a * sin(radian * (b * x + c)) + d; } double TrigonometricModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { @@ -54,19 +55,20 @@ 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(Poincare::Preferences::sharedPreferences()->angleUnit()); + /* 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 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 * 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 * cos(radian * (b * x + c)); } void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { @@ -79,8 +81,8 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic * * 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); From 1025fd99377a375381156d7a3d1176a63ab1f2ae Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 25 Nov 2020 11:41:05 +0100 Subject: [PATCH 453/560] [apps/regression] Use cmath for sin and cos functions Change-Id: I670eda49a73d9bf1d1d75194178344adda9b1014 --- apps/regression/model/trigonometric_model.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index a612f79781b..37d9f225dfc 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -9,8 +9,8 @@ #include #include #include -#include #include +#include using namespace Poincare; using namespace Shared; @@ -43,7 +43,7 @@ double TrigonometricModel::evaluate(double * modelCoefficients, double x) const double d = modelCoefficients[3]; double radian = toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); // sin() is here defined for radians, so b*x+c are converted in radians. - return a * sin(radian * (b * x + c)) + d; + return a * std::sin(radian * (b * x + c)) + d; } double TrigonometricModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { @@ -60,15 +60,15 @@ double TrigonometricModel::partialDerivate(double * modelCoefficients, int deriv * radians. The added coefficient also appear in derivatives. */ if (derivateCoefficientIndex == 0) { // Derivate with respect to a: sin(b*x+c) - return sin(radian * (b * x + c)); + return std::sin(radian * (b * x + c)); } if (derivateCoefficientIndex == 1) { // Derivate with respect to b: x*a*cos(b*x+c); - return radian * x * a * cos(radian * (b * x + c)); + return radian * x * a * std::cos(radian * (b * x + c)); } assert(derivateCoefficientIndex == 2); // Derivate with respect to c: a*cos(b*x+c) - return radian * a * cos(radian * (b * x + c)); + return radian * a * std::cos(radian * (b * x + c)); } void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { From 06d71047765814b3190cd4d9019d9bbfd5538a8f Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 25 Nov 2020 12:41:05 +0100 Subject: [PATCH 454/560] [apps/regression] Improve initial coefficient value Change-Id: I2026b8de7e031f7e22921be2def800fa71d7a4e4 --- apps/regression/model/trigonometric_model.cpp | 12 ++++++++++++ apps/regression/store.h | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 37d9f225dfc..ec094e2bc05 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -86,6 +86,18 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic 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); + 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). */ + double radian = toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + modelCoefficients[1] = (2.0 * M_PI / radian) / rangeX; + } } Expression TrigonometricModel::expression(double * modelCoefficients) { diff --git a/apps/regression/store.h b/apps/regression/store.h index ddcb5dd43a0..41c1a057acb 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -73,12 +73,12 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai double yValueForXValue(int series, double x, Poincare::Context * globalContext); double xValueForYValue(int series, double y, Poincare::Context * globalContext); double correlationCoefficient(int series) const; // R + float maxValueOfColumn(int series, int i) const; //TODO LEA why float ? + float minValueOfColumn(int series, int i) const; //TODO LEA why float ? 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]; From 72e8ac7d0223a551204da52f33f2179e07457540 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 25 Nov 2020 15:23:38 +0100 Subject: [PATCH 455/560] [apps/regression] Add tests for Trigonometric regressions Change-Id: Ie4363d79a53c09369b24924b35bf26a66e9a8f93 --- apps/regression/test/model.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 03d3bd98828..60fb061f9e5 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -190,7 +190,34 @@ QUIZ_CASE(power_regression) { // assert_regression_is(x2, y2, 4, Model::Type::Power, coefficients2, r22); } -// No case for trigonometric regression, because it has no unique solution +QUIZ_CASE(trigonometric_regression) { + Preferences::AngleUnit previousAngleUnit = Preferences::sharedPreferences()->angleUnit(); + 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 = 13; + + // TODO : Ensure unicity with trigonometric coefficients. + Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Radian); + // a*sin(b*x+c)+d = -a*sin(b*x+c+π)+d + double coefficientsRad[] = {-coefficients[0], coefficients[1], coefficients[2] + M_PI, coefficients[3]}; + assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsRad, r2); + + Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Degree); + double radToDeg = 180.0 / M_PI; + // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d + double coefficientsDeg[] = {coefficients[0], coefficients[1] * radToDeg, (coefficients[2] - 2.0 * M_PI) * radToDeg, coefficients[3]}; + assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsDeg, r2); + + Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Gradian); + double radToGrad = 200.0 / M_PI; + // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d + double coefficientsGrad[] = {coefficients[0], coefficients[1] * radToGrad, (coefficients[2] - 2.0 * M_PI) * radToGrad, coefficients[3]}; + assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsGrad, r2); + + Poincare::Preferences::sharedPreferences()->setAngleUnit(previousAngleUnit); +} QUIZ_CASE(logistic_regression) { From e9662e4c4505d438da4eaf77504d351afd0ed4ba Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 25 Nov 2020 16:08:16 +0100 Subject: [PATCH 456/560] [apps/regression] Compute numberOfPoints automatically Change-Id: I35a1902aeda06cc80ae8808d886b3e161bae8ad9 --- apps/regression/test/model.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 60fb061f9e5..850e097b069 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -196,7 +196,8 @@ QUIZ_CASE(trigonometric_regression) { 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 = 13; + int numberOfPoints = sizeof(x) / sizeof(double); + assert(sizeof(y) == sizeof(double) * numberOfPoints); // TODO : Ensure unicity with trigonometric coefficients. Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Radian); From 1fd970fe501fc89da970167c2006cfdadf4cd3d1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 25 Nov 2020 16:15:39 +0100 Subject: [PATCH 457/560] [apps/regression] Remove TODO and update comment Change-Id: I049645e03c82e953dde14f363d113b718408efd5 --- apps/regression/store.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/regression/store.h b/apps/regression/store.h index 41c1a057acb..8402dbecf58 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -73,8 +73,10 @@ class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePai double yValueForXValue(int series, double x, Poincare::Context * globalContext); double xValueForYValue(int series, double y, Poincare::Context * globalContext); double correlationCoefficient(int series) const; // R - float maxValueOfColumn(int series, int i) const; //TODO LEA why float ? - float minValueOfColumn(int series, int i) const; //TODO LEA why float ? + + // 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; From bd22c06d98911e057bd93b61223641066ec403b5 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 17 Nov 2020 15:00:38 -0500 Subject: [PATCH 458/560] Use semantic branches on setup-arm-toolchain --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/metrics-workflow.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 17613d75eb3..2f12fa36df9 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config - - uses: numworks/setup-arm-toolchain@v1 + - uses: numworks/setup-arm-toolchain@2020-q2 - uses: actions/checkout@v2 - run: make -j2 MODEL=n0100 epsilon.dfu - run: make -j2 MODEL=n0100 epsilon.onboarding.dfu @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest steps: - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config - - uses: numworks/setup-arm-toolchain@v1 + - uses: numworks/setup-arm-toolchain@2020-q2 - uses: actions/checkout@v2 - run: make -j2 epsilon.dfu - run: make -j2 epsilon.onboarding.dfu diff --git a/.github/workflows/metrics-workflow.yml b/.github/workflows/metrics-workflow.yml index 5341444b424..3becd5e369d 100644 --- a/.github/workflows/metrics-workflow.yml +++ b/.github/workflows/metrics-workflow.yml @@ -8,7 +8,7 @@ jobs: - name: Install dependencies run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config - name: Install ARM toolchain - uses: numworks/setup-arm-toolchain@v1 + uses: numworks/setup-arm-toolchain@2020-q2 - name: Checkout PR base uses: actions/checkout@v2 with: From 312c02fd6931d05d9c65c841193fb34c4f856de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 20 Nov 2020 14:04:16 +0100 Subject: [PATCH 459/560] [.github/workflows] ci-workflows: update setup-emscripten --- .github/workflows/ci-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 2f12fa36df9..ac3d0667dd7 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -83,7 +83,7 @@ jobs: web: runs-on: ubuntu-latest steps: - - uses: numworks/setup-emscripten@v2 + - uses: numworks/setup-emscripten@v1 with: sdk: 1.39.16-fastcomp - uses: actions/checkout@v2 From 36742789b08004d109ad5c568f3cb27802c4b003 Mon Sep 17 00:00:00 2001 From: Joachim Le Fournis <43498612+RedGl0w@users.noreply.github.com> Date: Tue, 17 Nov 2020 19:10:43 +0100 Subject: [PATCH 460/560] [py] Support round on int --- python/port/mpconfigport.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 53c3bb90a22..5fb249736a3 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -105,6 +105,9 @@ // Whether to include: randrange, randint, choice, random, uniform #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +// Whether to support rounding of integers (incl bignum); eg round(123,-1)=120 +#define MICROPY_PY_BUILTINS_ROUND_INT (1) + // Function to seed URANDOM with on init #define MICROPY_PY_URANDOM_SEED_INIT_FUNC micropython_port_random() From e295e9ead04a1b30b230a134563d0700c9234788 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 23 Nov 2020 18:09:02 +0100 Subject: [PATCH 461/560] [github/workflows] Update Metrics to remove NumWorksBot Change-Id: Icfd19cbad5472f6bf81a2a1accb88e79ab7a115b --- .github/workflows/metrics-workflow.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/metrics-workflow.yml b/.github/workflows/metrics-workflow.yml index 3becd5e369d..ad0082b8d1f 100644 --- a/.github/workflows/metrics-workflow.yml +++ b/.github/workflows/metrics-workflow.yml @@ -1,5 +1,5 @@ name: Metrics -on: [pull_request] +on: [pull_request_target] jobs: binary-size: @@ -26,11 +26,14 @@ jobs: - name: Retrieve binary size analysis id: binary_size run: echo "::set-output name=table::$(python3 head/build/metrics/binary_size.py base/output/release/device/n0110/epsilon.elf head/output/release/device/n0110/epsilon.elf --labels Base Head --sections .text .rodata .bss .data --escape)" - - name: Prepare comment auth - run: echo "::set-env name=GITHUB_TOKEN::$(echo ZGExNWM1YzNlMjVkMWU5ZGFmOWQyY2UxMmRhYjJiN2ZhMWM4ODVhMA== | base64 --decode)" - name: Add comment - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }} + uses: actions/github-script@v3.0.0 with: - args: comment ${{ steps.binary_size.outputs.table }} + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `${{ steps.binary_size.outputs.table }}`, + }); From 71ac56f8a750f99476154b9d0fcddbbbb239c4eb Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 30 Nov 2020 15:17:16 +0100 Subject: [PATCH 462/560] [ion/simulator] Update calculator keyboard image Remove the "sto" from the x^y/sto->/F key, and add the % symbol to the Backspace key. --- ion/src/simulator/assets/background.jpg | Bin 320442 -> 293200 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ion/src/simulator/assets/background.jpg b/ion/src/simulator/assets/background.jpg index 95e5c82146342e791278e29e85d5d7af8410bb01..6c2962f0f6e6b0ffd52f847db7bda92140f441d3 100644 GIT binary patch literal 293200 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⁡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д^)Kg&UMvzNYN$z>1UuZ3^%8B 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
dK+H^9d|l~`6a(5i{) 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 z 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 literal 320442 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�oA9#|cP#yDd;|El@snim#5g)l*ZC763A$6hJ0W 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 z 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 z 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 >qt=tFR(#5dT*|nuw4lc9sO#0B!5>t_#R&k z=9MgwH4r6CGN>fjwNFbcez|&P#{Hec-vMfo_?WaRpx*Wi561V`mbmI+Jf69jCu(3} z`s_`eg%fD?u{_|xL$<{-uDqTJmCTtBj9O|;1$g2S;SAqG{%z}1iboK`@v9=5<2EN7 z6PVvo2}oINh+Vtaz-LT;lFXdJWsxT{Io^8?!(fB)L@(rlnHxYXNTxk?!-=5{$19pz_0%MjkiZR7_boq1!ad4) z9H!GzDoSh>K7(yef^3%8>{HezH%n~IP$0EhJ!0q=GrenGr`+NxIy}FF2`~vR0~Q=b z-LhFYiD?hUN!na&i3T~Q(WngC@S~A>Z$mr&^E=*a9#o0E@y!b($L^~a@_rn1(-561+=W}pj*{Guq%z#7>1?(gvCo5P~deQ4x9K# zs0`{f`eNC-K&ZrEeR5Zt7XT~jDOj}_ce~y{?6KZ396Kh&Tu>W7H8~gkLmi7up+A!1 zpw~X7So&2mBl$8qqAl+hzka~=lG?Rp(;u!dGI3C7&>2j?;usdM#tgDfo{jiI#bHGo zzUje&JscaHLq!H7u-CmIq%aEmbD137!Ji^i#I0|xw-0+nHiYrVmyC4_#QtEa^f7VJ zFys!{v(J8x|B+;HLm=8vf7C5#Bj$eS4`DyxO` z2R)wQ$1tBKgjv!r8-my7%%EwEFH_boj3*4n* z(~c^R^mcj@z((t-*kBfXlAiq!by6`BE;6`vD>0#9_FVcVWL}>3;yTbL|wY2YQPzdVO-V+lC}o+EazQMLJ|Gutphm^a^uT# zg^L!Ej5`XYw?==T4MD%Wv)(wJl=_;6;Y1vm%AJuEcsMRvOL|<<{;{TrA^L3mmdT^7 z5za?CiOmP8o|7=#iKDBlb-O}pKwGQT$P~`^58P)U)DZWts{EXpl*0TWIn!^pGCEZQgNXrJ-8If|XqJn?4t)rqU*PvzV4?suk`|3-20M7z2biWP!H zEBPqqzF)JOScxnNZy7=0X7F*tUIvd4oFk>La{+F>alMF56Vb_7OSXDDIH1t3T>)Mow`ou$Y!+Hb6b zxJ06Hxd5Y(fRCIxSYca-YvZ5vYbc^EnjZ1S=5+4x<}Z!8K(wZ-msl-IdaXyPY~I)=t%JL zFI)_3&bKm87H!U+OOxXhPMf|GE-{rm)XzU)g(muYUvlH(@(2*S$M_VN8r^lE(@cMU zVKE+r9t!GY^EvNs7kPl{oGxkzkmWs8m2DMXyK@bc?Jc?@=Y}FcUj7?}CrAB1iq6BG z%K!i4XF2Cs$FVmD$IRX%I)`I(?6Ms?_Q+O7o#WVhg@ld~2PM0ZvNIwwq9}^ugrrca z&-dr|7o6)p*L_|0`~7-7p3eujFK=R}b__U!hcMHTfE0aRf?)zID+%9zp#}aY>o;q) zxAWRK@4=T{0TSJ7713C|GbFxu&x~!Y#e3#TbC|VbAED58Vb{}yuCvbWBo}Ae!Mi2| zgv+U@1JCD}G6y;u2ezPMt=7@;aDbq)K*~QJ;t58FCGI|D=kig2Wf?uG_0r9=z$l{YK(H)7u?EV{ROej;fc%M8jFaUJQdt*7+9a zhG*TG1=@FlWvC2q?A?izO9A>hs(j>M<<5HD26r9dP==N(wGoAluX9vrH?Br60+`#M zHf9?)FKlx9qk49_GylJz*dZLHC`K+oQlQ=w4d}LOxbet+ic z^63z`!ky@``qVeDU3e~lRrVo6u3( za;*}<$%pKk2;)WNu`ZQnf<{@?I-_1MZBSitBxc%j64=9lh^M}`4dbdUl?+-dv;b8NFF3nh|IhtM1x&A1b;0BFb91- zcCr7?4n9++n6I}&6pp!OBYW_!I+?y~?s1?te?V3gnVdhOJB`^ry{{OL(o{ zGHOLqyHmjYm7e<-E$wp@GbBecP!|L4?G-Am6Jq`3C!7Hk?`R1Smmd*3GA#;(*&0tY zOxE4zYWm*ml@jqt<{c zEczU}h+}&T=0Yg6M#eP zE{jNVa?DPM!gFceg)IEMXI$pZ?|_cD-lS9`=RUFf>mQAmbiD+r0);`(2NwG35briIylVshdg^5aEnMl+R-3VIjrh)YeD6I`vdB-%w}_mCON z>ZPa~a{R$BjC#8^;cp52%@YwuBdi`$v+)D* z&+G4dU(HseeN^Jk@T2d=Ky49;2W?lst%t0MgYp-j6WzG@{@^9kn3chfF9qxF2rFlM zbX+*gS^C&@U9JpONPrD{r%3~+sQ_iK=wnGEtGT5(?A|NeAM1eMyRAsb8lFkE4oX!M zV4>1KT!M#6jXKma*uOHR`aT>eWfk}my{|s6`5?l@m{j?HU2c z6=ZyiI@}=#G3m!C5MwG=#NR{)I@{R>qvxm_gY>!MTx77oHay`qDRzp}$Ms$Y2yZ>e zaFzmXOazVf75 zWi&d5`e4Qq9#F|G$=R^7>Z*?Jq zLok9niDrI4Bvd0Fi&*!zS;~smYH8 zz!YGW;Vf1DrO@nqqi|}t@QxxqOI`4Cv=t_n?&Af$f|4&%bshKN^=h( zRRD}#s@j{`5dKbjs8z*H3M6`ZI-wEMDsgGq0#8pka(cn0U#zqB>W96zs74{|O3c}k zjpnV2tw$-wiOW-(Oy4d;*`M)L+~Z(D!2#b`7n$UsogTV=i|PJr;w;G?R-DP@{4IlW z2KUu3nh!{-9*xo+*|v!51ZLF0Wfjh(TV`dWq!exxJUX0rz8;u^o4B zjC|U>_QO?*T6~YO_g+UROuvw8N%e9?ufXe;S9DNG$lb^BKD!}Ds?uS;#nemM$C(Y~#=yrKbv>AWF zyDr)edmEp}mI_}0|5u0$VC80zLUHv3{Kq=4+r7`{r@kb%AFk2{$kLDZi~Um)p;YZv zO&;rbB7HCx4}-EYvkwAeuhc>jn;|_Jl)N%&LDcAI*Ggg|$fy9PY={Ve)>3Zt`Rti1 z0oq60h=@@RRgg*6N8YP1QQcpH_N#4h@*aa|)6n7@XBcUkkW}j@@?Yeuih0e2>ddDD zVN-%>kN-;b=-xEXefVRZiTf88TEMs^;q)le!&{Cjq4EewE z^IOfub%dS_=T*lLb)6HLkw2u0y*<|Kf`42j01O~kVAan2DIDo-yMXtiEHw-2M1h^(*)3$HFxO?xm)0-H`_(3v$%MxV9u)+jML^~qiy0dUe zB}->SI^Q7;4n9mI-?h_c3ZCHF0kEE4D}jDy0WpGR6gj!FHLk8A8C8Ek)Hs*eSxm)r z#U2ISSOTyNZpe)Q=Qssv7HowZ-i(raoB}#TzEW}ZU9S>eqRkad_<@699v^@wB-#n5 z1t|Md-_gAgnY2^ASYESkz~+=W@UH~+0C+8W-FWH9HUmh|t}dD|5gG@ezOtD0fjXh; zz1*7s=~4t%uyzAM3m+`y2|NB!fU3 z3~gF-C58c>zeNLn-hheon`uEs9tg^xL2%t!YvrSvyNVfBY6JiQ4yK+9ED|M>sy>82 z?=d-`wBa8iT(md|=K8~n85tJ~o_S?0w-Qp&ezMKuDP2-4c1mi%GL99I9- zA3P0I_CBRCdOtCeSg%Tc!KhBk?{VC} zRK&qW!pH!q0#aV^s)`hW6-dg*eyxRk;ZeJiJSz+)a&Tahs?CRk#j2%+WwEsLp#d6^ zr0%fy;~5vy)scYsilFR`d61Wc?G09-tT*W^pmD~*ZOzTx&iQ?Qdx^;FXuCJO>mW~q zMLp&C&hrwgx+cd_Jl*QNKNo^wgxG%Q&>cnt%=-9y-r&H1xrtI&%6Z$O=KFYAt4On7 z!R%rN&I;xJ18-#RgOyQto^oyJZI|esf(CL>$KB4E7)X01OMHzXAQC3HdeK|bHsNb4 z=M7scR@QR{ZXFb2`rb)qL&&Ic^*ixP>rD@y{ks?VLb~#k+dld7hCOF|XHvSopj~s9 z##&%C3i#%?3I~xVP&xMWRcN0*AQrI7Z9aBhEp!vDJnZd+2U;rDGIE>={S|5s_uA-d+MI5acB#Gy5 z&!O4L?$O2z0I#?-$}gmqjj11RANp4%<%*5Z$33z}Xoj8l+o}cb%4@e$KB;))zAcmA zZGzdYpEvDg29uK^(rwBuh>b{ zzypZNglxX}Z;m(ygg7WNxayMORR9Rkw$$1*+4~k8ARJ(V=k5ZC;KkFBw#3yZ-#pZF`dS(f9!4zL|)KbO74-z73gF%1RoB*|GW z7E3uC>#v$T^LV$Z5^vs>H%tBcG@92jAVlx41k$N?JtTOq(&ee{uKQ4^ z2y(LmEW5)6uffpzS%xj0w^OqejOMM;!=X}PX zRFhg0#Yb1FGA3Cv*<$P5#nd2$Pw?#2Jtv7su0l$Y>a2y)%HUcgXN9`$+jzs$!D${z z6Y*mZSEfWSUWWPCg_+2%nwOUx5f|zbS1s-CilQEzYF=6#2DSQMnc%UZbu%LYm^M4@ zze?Yj1m504mcCzOp>fMUjQ*Pyx&F5U*$o9g!fmIu)l60mtVC`1D-#%-v5@AU>o!KSY_~fFD8HFpn0cp63tr{?t{oESbk2 zTxrh558<#h;J)TGb>k|hq17q;TqES|b1L)3fx|A1WC7nm6{~GK_L5WD;{-|$4>Ww- z8k{Z&?siPf-WCmhR&p){f(dH1V~SqUzRv5vY*9{Kd2>1sj* zKC?6bHs;CWVc!YZR6siFHN%>yFEl=b+y_x(j0dW6RZxR&5vTNs+9}T6$&~Z?zuaU% zZwXqcpa}p@Kf#CH;{M$Cs+&{8&1l9}lV^DE`yOcoAj6Pn_~3&K2!JpraeM(9{6ph$ zTd2d3?8nXq^3}v+mDlY1FIAMd0?;}I+2TW}L2>9gV|hRxm6IZsG1jw4|7d~@UT8bw zY>)NPJ&wkl6lZWeMQ<_!u2MXQcpUYdUHSx@;v^^i{KXG)X*cHpF#Y0kVcm+M8N@`< z*85&(4uz%@U(nTB?~LVlkdI#`Pkl8T&-=gtD+UGige2{R4hQj>`Z=oUU9L#1>6kU% z30=*1UKFmW#kaTtub~)=sq;-qPPY|bezvVWp%_`!0;@!w%sO9cr=@cMpqNk58$M=G z^C#7E5`EV7m&9JcxAT3K2Oc`8##~2ccBi?XMXny7Q z+ZMr^dokTJRGf4DtsGR5f&a}|THo!3hZkwz?EeQCgYcav{jNCr6v5X&5YO64R=T_pqnaMh1>6|PbnGBuW@VwYoO#2L{63|d&MhBlO@vtH}x zG7G?Sv!gxI##DceQ%}B=jnCZ^5V?;CTtZ_-@mgX>xQttb=WNxZBZrZ>#9g9U{|pF{ z#WUK-b-f9Cm-@-H#qgo~&p-D2DZ)s>jFh!F*nnzu0Sq5-XexeGkX>XBeEDivvb=1r z;wKUMc9;?P7dggNz{F;Vz^<|*zCSe>u3y94C&l29{sbgcOlOVaW2}z3Sdd+=IQfRL z+VVMW*$2GH&a0!&+~sX>Ya&4@O#k6Z-PCnI;R4t3H#^M?!h>Y@<{s5YF%vJmKHhCh z#M&T?Sd8)_vmqK%<}2=va%e%WKVpePPnpS*Yq$Lg#*KYhCPOTlJQs)QTB4)BpQ~m| zmb)MU{9@K$gFA8d*nS=y@J~yyE`tc+Z9~KCb0Z-w(~F=M4v^%Jb+)!ovGr9U!D8;LBg3HdMM<$)dv9G#W&~U+rE!STID8{vx4NxA z<`ITpv&#I`>CLV1sYP;Z6=R)P2ntw}bHlXF#_cZC&Xn-ypwDjuQrwB#KTVZ43FeeZ z43Q6~VWBn$G3#d@Mye3PCr@CNE<8_#35_fVd~c@%!BN8JBlfAIxL@-^KIiUBevkE3 z0Fnl~Ie|QMU(2W8w^o%j{hD8b0MjMk_TZ{swMxgWcS_v z<+<4E|E64T7Q1{gI(2&byePAxZyikV2iV{aH`?f&`7NbZAN=zB?u(sqb_fg_1YAul zY_2g6U>H3$*&y2T7FaFPsiv=7u%j`KZsOy{G9kL^v}ANHeh27Bcy+Aaub0r!;Vu0) zz2ju#7Gh>m5Vztdwv9toutUKN7{vM4D8LJXVx=%)K?qecH)MzZIoH$vuQD2}3T!f& zG8M(QU5c2=euhnWcR8dI+QMc{T*!X0^#@X6Iqy7ALt1R+V{_8$z9Rw9}8yi(jfH zrn#7r!kZl5xLUUlVr2HGVNSw9!U=ZPNR_ijHl``ii>ed(c_=w<~JVV z$aM*-M2Z>^VA%m&;l%|aWSG-^CtjrdiwVC>4IaDA3h}7I^7G4%Rqgn%a{vkeX7T_I zb4?(!eq&OMkU-+n!F~pYTxCGlAt^;txL>Xx^aAd&t^W_u;z}eR)V4o=9jyFCukpz) zR(6=Sv0w<-Fu{PJb9R&F|JxopU61yQL8Ft_q<40w9GOxVdG$*M(ijQEgx%DV5EFzXoU^tnDW^q`#nOG3uJwO*>YwUL?SA? z%AM6_dFFn;ATCJs%(L7dq#z}nuPd4wZxHYK_WudUw8_w4{HcE?KH47l1Qai$8Qb#e z^~jsLj-CqPQdFc=nVlKyrcNj4fT^z%g{;!bwP>{VB4zWza9S1DwLLE<_48_}o3<}` z#29Zt6B_fPx+1MXI&Nl0K$XI#6+4b_C5>85#O`3cv>ev|s<-j>?jzz!4~rcq!pyt& zfqHf4d|6Ls?&xk*2bhX5N1yKm&DF0l_CYTe$!`Qy+}pdHM!Xki{&Y@}8iHc6kd{0& zek$EhE|Pv?YRk~MH{B+vdp-8zpNDd)28HD=8K0HF>)N-Cth!R5M0t)4atGl849z~@ zddD6;SOG4Hsd^u*4BkQ+OZHrgi)N<(5ZRq+#2Q{v*%CMEoTY3UxC=eG=9??a`f>N5 z1Cri8+H17%cwmoDKdUSsOOwQ$*dd*4LU2?HDtFkw!>{j4Uqprbi1f;yi)(vPCdU`U zykf>WQzrHOv=(WapY`|KTInroJ0TTGZ&e6bdZYP()cFU)>U9X)cj5RVZ?}U#T>k^4 zDF3!&*T=5H+LCno^gHW@F5WQY7;?zDEBcVdf~0asIf(Nw?uAtkl4(_IRGIbD{H5=o z#xIk*A`EtqSO@f|bC7IJ#j4M5wD4hP7~ulz6=W3!a4Tht^QVMj3QWz}vMdI99<;Pz z^w(9dTA!9 z@w@x~0EcOszuFtx0HqHL@_^#bdKOpbUejIo6mVCq4-!u7e->okDc%MY5+D_J!YO4W zCFzxv<$dS&p&0&%E0Qm(yD(%6f>8hsl}WSPcieIfsk=0aOIK9F7=5d~PeuhElAhS+ z!#}ov`upkju>6@8%k@ zvN{u(o6||k1VQ;+IgZTSmuYb_&@N+5Y1ox_pXnvJqrYtxM~q$u3>=4h5&Bg2|JfiV zh7%lDbnwGDdMS}tntVwR^OT?EUH6z>$+iZLtPJjk`X&!UpE-P1#wt$GH-c3u0h)g} z=lOC^2TtK)T>_e|P-HMhOJx;Kc8Eg74C|rLso4`chAK6310sMO|&x$jHJnr@m ze|^0^%qcGLP^FJ0e#K#@VSaLfjs(nC)b~UA+mj#k0zJ2NTAtnfu+PZm5%iZ{&8plKH_l}?D*ka_ zc1@3Wu%Y4@6ZFgH-(i|{CV!D|GyT76XAYMG!(xV`7Z}+-Mbt^8bo0YmehSp-Z+_Qy zg%MPQ7yn`x{7Wq6hnUQ7kN3LVIZ)u!f<)~BfFuuUXYfud4}kqDyjCxphuqC;8ze-~ zO8NGT{dgbZ(|oI;pL)&n;JyKFC&0jA{w24X)Z1h$p3#6P$sr>V+jUz)nAH8XTV})+ za?!EK?sqF8dw2A8?qDr1xYG-xizT{25vv9^e&D(bMs(1?l2zv#RAj=8S7_eBv@QLQ zRf=lk)AezG{Grg^O~+3@H*U|J6p<%ic%^u}P1`E|{BL0v-tt1Psq@)AnUDeu_^8rh z2Xo4;O7NW+BH@)iN$)3@Gt?B(>a`r;{la@i&BD2Xl&6VxFzHCc?C5(mn}|JF$DQ0E z*tT9^dOm|OpT?-FH)*Buwy2A~RUH0JV2kB(>N{JY^bJyF!NDiRA1%`JO_!FC#`31e zu$IdyUL8tlK{s89aIpPI2V<@X>~(7Ejx_Mn%1$9NT4B-#Jxfg&X!`1Zn%EfF)0#xT z4D~cVm+J^>gRN>1-?YYuJZGyRo!w4}B24*S7s4)lyjv?e`nLc_d2}pfl~vyO9d1UL z?@Y(eV%D}qw)IAe7)-+rY@<*{h?g~M41?>b!}04UNlO+RsG%Ufom|U7B0w(^F?lx3 z(6w~2ziQ!wSITCGdh<-h;4pfv)4j2M&v3Cq>|P1W6{c}OXh3F>t7u0;dd0xs3oC*= zhrB8$eG>;<{a!d{m#Ixo6aZ6`Ai))7T{DUJ{E`F+8ia9;ru+~$E7IVh346W%rawmAf(HUu!q5sl|9 zNmu6~B)mx}2F&cs*vqms70(@rOAm+63QiA4zWk@4Jyr2Dwz{L`a9+?NYjT3mhPLZT zd;El2Q4|QD@z+N1l2!o#bf@cvmhD*~@*q20b(jQVt^fQwLyf)7A-7%2$>J?9bQ4To zA~?puUnns#fN}*Nms~chU>PMtK};&q(EInZ!b5m6?VJJ}&INkI`ZJ>zyg5&SfawzV zB9Fj`Z!Y-Cmfwzfa>Wn(_n={m5bj1RfVZio-OHJeWA$~Q_7uz8cF8DVR zyz5+2iZ(jNx*#iQ5!Oiz4+H`>0Zq2f``HjG<*?sk4Tbm;5v!O!L!JN*H_VRV)Ld+# z*wD%N|Ar1b)}zP8{;^fCxU^JUn0|n#hlxh~!{c6mH*q+=_c7wlrO{wzwXm%C z*OYyomdD0T-_DgrR)0Ej#!GHK$Z`QivF??#k8uDq#l*+I5trB8E;sQzk}s#3FtVYu z)@YPoE`ku{pBvTQU_nhghH{%&28+-SsS_kO`bPEgD$DwiFleV#uUp-yqKIwi8q&Od zu*jA*0YpLqo~Qx@!nl>z4C6G{kVy+;e1W%8#mTK;p7YD2T#R7UCP;oh+_bdzt(%W- z!?oo1yJN!%-H}aDXlnvfBVSU}e3*&8r2jvv85GT2(MZSnPt{P|ZDN$nc^_e> zE;+R(^(jy{WOvkA-*_yi&X%~2Z~q&PkpIJ!mueJg2mSO^`kbID$XfwL;-XD_v05rF zwxVc21`yRm6e)XkD8v510+EJ91B6Kpz$^Rci$Afe6)qDpcBar58`?D8mJ%s;G=B`< zH6TPN`QCwR(<}EUhpDc*k3a5V*ZL3y2f*`V1Ht-h#u}sx=fq%X8=8ARURsOp$}jXg zXeYA&>m~aM5(frSEEu`d#D8X9(mGSUo09G4SW>Vv+|Uk=_IzwFy3S{;6{qXXBSzxz zO*OHU`Z2)3&-C=GLW#iX5bM4b^k$9aboJ->*3>@ThadR$7sx++&gToCr~gO?hU=zZ zZoJYFTOlfG;9I4?cqeZ6X69Fg0^w&%UF!Z-S!?9Zz5<~+$BBmL0g{ib$S+k^;!8{z zAEDLC^lAZ!3fnKH*admYNp@p_frD-l9}Ax;j+Ft}MXAncD_V2D%K5jD$fbk6tg*;# z&06fLL+} zpQ(Es6K`ob6dGRH*}KTBo7m%ADq?#Be1~Ga;dS6G%40?;*n#uu^egXBkLJKVsf!d7 zsV>1btANz!oD_U|(p1D%@jP;!ln@_%{mudq(9kX&EjF1hSNTtFR%NvDZ`6$2gp-X` zGj2bXV@3pKjE1R4y?*$!wzKA*3}|_2dc3t_7_V^ziNu`X*MK}RiA)C;qQSqR6N_!k1|s?3pNQuCR&reBrJ}t&CNk*NtwpF($IZWm*}hPPk8g> zm(8;W!H0ch8fPE+1GSl9*F*aK&em&=aWpUvTY5uIKoGpAVIo<%vo%>PzvIJK^gntSE=b zTR*4yA|z{a;qBN&@fO+1(R$K}A)IBVH%1e90ME?m!S5wj`YY zeXjm7PU75GicuiSL!f#<$3IU|sCD(n9!u(Uw|*tChS)V5$xHUv9F{J3zS~y`RqeBO zI_8SHl%@VYHS81dU@9gFDbZeFuE|icopEOjjx-x-Mso%o7;qu8luKI@Yk+=XCs;av z4f!41Qb96fKKq(*HBGkAK7Fosxe{I4RF^bGC70`i>m1 zzU&cp5!lrLRF{LlL+*4Bd&iffj%3oa1-eAx`bO4@+LCG+bSY_62YgwF@++hMj)@31 zoa#LUM+jvS5jYYg9c+jn4Y{i<_wDlBg}E5x)_)-_wx5R<-L2m;rQHyJ+qjg*%9Ae= z>-dE4l6e|mR60KJWA!V!`eyekzRo&{SGU63ZVPjE#6lp&A-Yg@#xCWh0)pcHLMv_T z+{;_zn4~2BmYYrS6Yr{kRd)B=qspDg;Po=+YoS85OZroUgjTF$(({QMYr$9I`sy#j znqvf|CWn>LMXz^RA}NyeQopDZ5)H`olp{tq6EUl3eTr>O6fmKM36K*nSjhkzzdMP9 zRxP&orO8Zw8GSLt;CKl|6TT%)bfFUd{o{cQQLYM;vfqFDk1KB9ze6*6?O9>f)D3T| zA73`ot%q43AKfa!Lp2~x28?`3-{!2->xy2E+&?Qyt7kETNmhTjldD5JlA!S6r9 zEs>O3WkjbiXMJ^JEhM4xt_BmFMZxhG0h8?eA*hMfmQiDYh}#mNva>c9Ylubi7Y|Jf zy%eIC^A$(mfETX(l)3h$EhO1c1gtTmu=!e;4Hu4>d0VTWqHHMDdWj$&4{O&4Pm+|K5YkA|qDivb^v;NE1;VBZf=gOzmXtv7wKOhuSU6E6@^mITYb5JU6iJ2+* z<^)OrUIVh+^_8T%MeM^#%B7jX#Rf3KsnXO)tHT_+lBs=|ABeD~+c9KupeNAWZojuX z5o$HeTcZYx!gJ+f9d`4`ql;pXz8rRs6x}jQxQ41tKMLFHwRv3%P>Lf}_;lwOkh1XX zh!4vujMx-&N!zb~LQ-!%klvt}0~ibwQs#M7T&eIH@s|ygFD&mIFp@jL*N-@DNuVVQ zUF?{dvs;So%q;F24*^lyl-48^6 zHbwSJ*xdB81h4*;P?m7pYaC7>NmM*g&w&+lGaTqj1oGEcJf8=d90=C()pmFc+P{)) zJyOSzglsPpLOV(!r50=-{j^!VdG&o6ulK+}Qz)bAoZWN?V{zoWO=S4kmnJV0UYQkd z&z@V^+Yt&^S}emze4&OryC`FzsJa*1qQIK)0jS(_L>iK#2jr@#^ zWEnm%?fiBi1-z{Y$J%>KmO+N}g{2DcVWqYY1-eG6l{gg2Sc>Ysb z9Qz?@072qkF=iyo9k|`VSi=;v1Lj8)116la&uksb;@U;&2wov%`We6EpP)h`sD`Kv z{h{oddR}3g2IF=QJ2hEra8nz8{zQ+i2Ok9=wR{g^OMdh#_$UBS$s4sMo|ld~oJ>hS zcTA80gMl_O^62aUI<|sJ)LLwKQd=fG6<3`srY+^nLo}FzntTp(2~dmA)ZsHmOW1x0 zK&oXI+;e#@GY1F*4p&PNPp^>C^WFKU}H@#+s*r9pVxBmk73%}@Z$ zWXF)7;#|FbUgW8ehD--jAy;fOuSZeaimGODt;{<1mH}mKB^YH`3_f!r+?~_jj5E0= zkSRk#Q(iId!L~+2`Kn!dud&pJt&4fEmZUPj-5kfftjDn*hBMQpp~}BZbj|%#>RgEI zQt2BGOueAjugc)&BRcKB;Oy7bg%^&YGJ9Q-zhg1K679TgkamK`Vc&Ts=f3OU$G%Q4&(4}g58Z#8gHX=k?s=^ON4X4)_@oo&)B6-(l@`yl`*(=)GXr&{!E==( zgI56hQW^gP_;f4gkvq3R9)tVB1*vIA#w_3`|K=16m+7O*Nk#!s znb6Y3ki_aL-5d=LWe-fL4!IwvS8A)zyyg}CHEo$jqQqExLBxMPOO_xWS{7)rTQA~Y znuX7Iq}{LVpdK0CDB?%&!&OP((xR$Xf$lgaPorYHR$&zuz{-YZ(^wvNP|3la$uG@Z z-hi@T3jtVwTYc?@Ip7zo9SBvKac12XT7b&X1v{;T5=|)I;EZ+EJviJQH{z6fR)xc% znV{fhY`Q;2PYR>Nqn(J7v-mCpFfwb9v6Cd?-CXd4yp*~4MsbM8P<7L^Ua00st$EW| z>S}?u1{o54B~wS)$c<3x%HWbA?12;^T;LGdsc2BK>(eTWz?e=EdG0cp;er$YHuM0G za{uw3ST_k2d+v6jKMr0h5fB2h7I#Qo4+0^X(%cIjN7(uPLxEm?;rhyJg$eG-^{^)O zF${hyIU0^HT+-WBhXM~WD)l4(Spvw-k1jK7?Py%uDy=r-AK z-YE&Z0ztDDbho$OL?#LExUF7)m|{{zQL5>LB8AMaI}WNeJ?;w}+LHvv0yo|= zq)dCSOX;rZjo` z;}czJI2FsqR`c z1gCt?5GD(Z_bToLX=|%eh!Q$~4f$-dfbzdgFk$^a$=+?W{+^sWBaCMO^?Jq4fJa2W zx$(i@t_CE5OE_n&ydriG-c|to?DhP+dcXGCVo?5-tAYWkwWo~xxk()PUIo|HE^}l1 zg0DDwzfD=>bks^){LkCcJhCep%B1{cXV34f@do0g_dgmBDrj3{tER3=g%`~;(ICia zKfa7no|`7|1N`MT?X3)GIus81YV)}HXuve9D%W~;k%Ek-;C)7?Opweep4M7B6@BnH zIp^r1&$X!9a2&JXFzQg%+o40!5Uvh*Gfb!fyVk;+1iAnYf$^(ziNe;GkbL4Y!vl+z zP>HMKO~_H6($*~iQXwvU0%)S}l*%9kout$v6vvKk+Hx4r{+I`u4!Y&ZrkW#sNU7jT zlFKEQRbD*Tz!+!1%aE^T2P|BEo3xkFwJ3DaGCwdX$~&6PHJkPa;_g`@e%IR3oZHrT z^7UM%f=aPNb6Lg5jZ}@tS{*saQ%OyruKEF!_YEFJ=iMd@0Dye!G!N`54p1Z!FKR6@ zZRY+r#hZ7GA%`2jn7uswyIvvm87D1~O}2HvUsrV}B&n{%``iN+y(s2ZU$ql}5y++p_+= z^sbqbY@G`WX|&~0_o;;N1S|C7*Ud4@XaW{v7Pbww=WxxwM@O;MOThT!84`7m+5BA<`!fG7H%EFMJuJ@Ah{ znh&D@&Yw7ZsHr4@K@w_|!X^9+c9d(PQbc9T*kpCy?;N-n+k6CVX~29xjQam#D>xIz z%?rWyCcoY1TX>bCx&bT&f?f+{j~b9QQlHt0cZ}~Nro#EG{yhwBNni6-*MA_KPXua5 zCBHwx0GCg}L@R*tTP6mxy4b--;C&g-F z?}n*rqi$l&Z1wJJ)Rel|R-a0Dk-T7p@g_6-+#qGuVjol2E?#9vDgbZ=9a|aceBm{1 zAUQOLop)p`4gsD=zc^D_^p1*%xe(-Du19tGBA-iG$tQ~Z+R}KllI+d{{5B_o)EGk; z|KVVQsA@g_X)RJ+7Nb>mCU&}IbNe5sz~9@>Ix}_(4>_O*GIw?(g#RdPTD-#4eXP1t zkpZwaE%;9ytdkF2MdHf)!G|rTpivOpT`^TaL`A@$li^Dmx9&VN=|%!du)umm*+esr zZZgIis^{>2E7}>nJ_lmSIKqTxD?AsS*Pp%NqGNqMAS~z8nQYB_%f_Q44-S40o{_>f zE#}O<4v?~Qr{RS~|SHdTh$6mL_M(5P6aN?7dhyx&R&eszRr{CIvYK5HZz7xyq<;-i{^WWJ7IygKj77dHN zb@viwINWKjYmkfsSW`M#d8-{39T-2q1zhOyA*z;uawsS?WQ zo_XK9k_PKMMJVQR~W{mMG(<{j8gs8(>wU(ylzaHudUBrZ(>vq$6HFsd`|x`3EKZ zIo)^y@BnG2p+M#$um?iyU3#+a8pd+?1KkW;Wp`pBw3!A{C;5%MgcP>B4pA8 z@TCdE-N?FW3az#X z1}10;kSuR9Y5}35nSvT;trpZ?s}L@>Y=}+X1>>u5s2&TkP9wu-#!b18IAJmr|+8^e@kV5gNotLte`-%-49WLnMeIHvbqJxgACP`*YU#htVLuYK| zhG)lEj6-B7wphdD)ej-lHfbi+VNfgJCf-t_HT~Gr6~G*LlpbfS8m6 zy*G0Fr6o#f+M|>AtNs-zae-P*RV$$*drbCe*yV4bTU4P^ID)yryFkSJ?NtoGW+5c8 z>Z-K-1LDi#&A=NGDSwx?hTPL*ehj_p7X$G29_0X}kY*l35$mE%Pn#bz5-W9p*R3E{ z0G+K8k(&kpRzL<(`Z}lIP1+MFTqZguMje<;6(BZT;E-?GPXz(M3XvUtKBc{E;!( z-`91Iu*yOXnO|(^gf!PG$8R|g{hNd{=-I>o%x4C5(k*@-)eIrg4#gjK_f_dK_+#$B zcHBvG;Ba_5FWpX_&z{{0@7fiJ9>=3s*vCp0w}LE7NXboY>h=7R;8U-uc3vZXsEw?ObVh5F) z3{gdnownd1x_BB3Qm97@2PJ$Qv}_QU@8!ajvuP6e_DGPTWWrP|35fP;hFCaGOYVxQ z*vj|ex0>S6HdY7Ob3bE$8IM+H4)OD3(;wd)d}J_z3_G(sk&Ihg*e{6BYdj;Rdq2~= zjyd+^9Nl9mIS*|P9$z@8ZrjY9L_6PCqdpoB!p$hq2;Fs#91r=GC3R?}(t<2S3)hx3 z$Jv71Pl0ftG*bmIMdBPx3y;!09?i5j8H8dgi?%=X%diP+OgdEzY!|%7AJw~2A6;eq z_%ea(0-Flw`m+0S=l7H^bcZTGmw~0Xzqr?lnje2`>V0A}k@+^P%y-s)emML=jDON+ zV2HfLBvS^_qcgRQ4`Kvsg@*bx7xBVd?irZZqpd_zvKe=ARi^btLq-F}w7I|@v#{e; zu>6|v1H9y9WXqEFE#yz9li;7G4SVZa);%uuL7ClqG6_&zT4bQ#r65h{bF2;hE``QH zslYqi8IT5O4LPQ!hnW(a=|PMTv7UBAEb!$@q<91HCwV9@@7Wm1Z<4a{G0rq_-0AJ$(NZaa@D@bL}sLsUrjH@D0#%kzt($#NB%{J|D~D0 z1Bo)sPkvWSlQjB{$4}Oy%JmOkWx2%_mIVEh*Kw+3kXNMs5PUK+PV2xSJ-*nU^JVq@ zlB*`%HlJQZ|879V;smPrylXN!QivGHVaBSSV~^zcpGSfQcGn5B#lotsc66byrna7! z1#WxmGf87aO;SY2cO-{->$Ge5Ot>Q1n{zElR(9jf_3QKEpF0Qi8vO$%Z;|eKp6)yr z;LK}jbU!sQ9c^3nEbc)1u)Y=Mj4ST83x(MoRqhvT3)-KIQ*)Yd7+ls>DfqCR?g8tc z=ApRGqIZT3>+ufj$@nf4r1_7`!OmgiDVv$_PgB)5({v51@E`96OeTUP0M_Szp}s7Z z79AP5p8Mfzh8>({H8Qc}00nwgGevBmG=d=>jv|T*5!#m5aXrm71MyFY^1G zC@09!LOo3um2tn} zjqJsVBhIHDK4Yincnv5$4faTqvA(y;ZOL_jQf7C9hv8pVi{H?@8AtQ5|KsXE!`b}b zKYl!kOcG*mf|w0ruhN9r#E4zgtgW`T=sU!U)flyDj1sd}Tcg8jjiR))wAG@mqKbOA zy5FB)zW@L6yYsvCI&$5~aplT!zOL8ve4fuo$;XGzdHyVi_ zM$Kt!^&}oSCE%a&)h@jIY3jRVQN5Gz%Jj(Qx#d_F8t9-rK~yyjy{uW&|BbsL{1G<$ z>JG_swJ;Pupw_sZk#}q#+E%v`{6E0s>oObRsrnfsMS=k1sIruFOK6v)z-Q;rXH|dQ zJJ*=Y{bYm*7uEu{88Zffxg}DCarDzq$ZqzGRQ0Wf=Vp3IPb8##%@kVgfueHwU^X8$TS% z!r%Qv%2SN98d4nM4EtwN>QOYO*mbNd-~kAYz$H>GNpL?T34j?cbyC<@-=~#Y3i79B z5)+$0Ge%$=N#m$k6Qo|D&h%Ko;K8TdBIM_lIPulp+S<8&dlnE&=7DD3VOddjn1 zJKh#8V{j)%;4V4=Z^%6P4c{5}o8j~$g3;7T!fo&<7l425(O9DX_2%R4bpcq9jOQCr z>}Ub+eq5nf^E&Lj17;!}wa!1*eB0Ur%b(o&g1T4l?e5jGv**=}tiI@!^YU$YV{voS zK>ok3Jl(u$db6!IXdk!RZJ|eV*NyI#=8WT;&wQcLm8hPE7wcc(H9n`CKVrO4u3&Vn zq|Lni-Fr1C0hB2rt|O|EI+~5ikPskQ`G?n{BCDw7^6`?q{P;1M4-2{D6lxG6TI3Rm zroHT__%M>KOn;HNS5{NG9ooPeeJj1Z;crH6Uzq&#E%Vo7nR|Wp+9MZF5nfSd--f5k zzA$Es4rCRcH?v|^qPhPme~u6Ou+{#{JY6dQ{Fj2m1D3gR*FXr`Jw}RxlHXb3{akd9 zA1)MPy3YEfMyzH)Hj`e6G3hi~SA|9QSu5h8h@@`$JpZp(ySJgw0$Amx3 zX5@SOj@OtkrY6qi+mimD%Dn<0q_aM8W?R5J%H*B7UL3ikrfZalh;VFy7>0ntzXd@i zvU?AeN=OW)c$v=TX@%)qxT<+y>bj!{w6WeP2g?U<&=bzc1=g*YVf#-*-l{bHa}1ky zJK%Y_c*rsY8A|4EE{%>AFpFhi<5DY72nyuB+G155!P1 zUI$jzu_M&y<8DeaKD|HX!DVp8RN>)Uodofy0Rk5=GPUC!mJ{r?o&4MFX|+l7n9p#= zW53l7b}S4&Uh%c8Z&&Xb>u<&ciOj7p4Htqgr}Gt%zD82IOLVJ*4UFW}B%*uIz#RdD z4S*D0Eap@UCntnR#dl0TIdAt9rvBGXj01(M)tc~C{@P@EDeGR`bd)Ymx{u3?G|`Y` z51G}!-a`YH$jD^~Ye+tO=6J@7+ar0EJ0A~!58WtS5gej+wPDJoaim-annbnVS6vq~ ziC;m*_SIO19_N(R%E}wcgHLx`BmN{Oa-WYcc|7*b5`Oy9U=k3|y6N@`V_O1`cVxbW zkHR#Cl+L*56KA}Ki~;1sq>jpcUWL!i0RBC%jXSf+Bb#&FyYrSwN#dDLp$aHSACZp? z*hnI92>?_^xj`y$_jI|o9k&h56?Z9~mo)}+lwJFKZ5@z?Ley+Za-cm!Pe2%N8tV>F zy_#btSSgCVN|hBT<9a?ZMmlMJ)>@4jOl5 zxF8gORFpkr@!t!8=onRTS!%TWJ7#4_@G+(ewF4DHj300yEB^-wocQ3izWy5%lfs_q z2C;4KS8GetSzDJ|LSvy@rk9eiOA~ql1(iFI%+cK#o#4fLxk=*rjkt$&M-yKA8A1P} zo6o=7|EKsx`P+4wtykCZv(Kvb>bZt6E~$M3sFxgPDVbAg2;_G}cUWeb%GjH?PpLV) ztnUub?rBmiHt=Fv4eS(XjzT=ds zedA4C>0Nyhk0mH;%QgHgz{P~`QE>F{_ORO4 zN#o21El`O5VxUaON)rv++ukD?|BTPV17*>H+bl zgBLwq`ZCi2oObBy;=>C~rI&*=d#!Rox9sh{2XC4Ku5#i+v%%sz!x%(=|IkI0i{{U3 zhHTFp5Gp8yypV8+oiD0sTRCY=rO%+=cLayq+bDdof)+S@IemKS{EA6)p70Njb!{91 z|Em(iA{cSnUcdJKU(Ozh0{8Vgld&PAr0vw}=&$#Nu5qis0=P|G9(p@*~# zwmQHj7o@-0j$Sd30mOsox} zZMJR;*zajTIZGImdr1iBjfdu^GhHb&FC8CCz{LTap_k+|d`k-G4Ec`3qskin-iya| zpGN?dqTRR7o+O(w${BMTDsoqQ=vC=mI&f>h)PR?HqhP1~v@V zmb7!V2)vwBw(nd0uggi=5NDmY>FbQ5Up#;iFdv0s0xKUPA3f>(Ek`w4dOE#j;62Y* zyR(M<`CqGGvTSy{RYR0y_KByaRI_vm?LJSEYx7pH@UPT;SG=BdvT3!S=s~&xEf*W@(fXC7$Phj%Xf@zm{2aoc@B1TBiujeSmP3SQ6lO+Z_u6+4x&{?dleo=I- z5`l|n;Mp?wlOWC3OMWleZZS0u%8##v-cc-n0O0_Ax2jO)4M8zTN3{z7B(QO?TCN%f zT(1Mo%<6+}`5v8z!v;y%nY!(7i%fl?III>A{M2xjq6MLF;J*}wTn*(rmxY!@SSe<#G~iw&BBa zP;9xk%%+hYN&*6l{Fj7!%i7+3l>&OVF03gRmwgJcBQy3`gfpK!s`BxFfQdZufnX5~ zAD6X`*fo-^NSH34`2c zx=B70BW8*KL??&PtGuet(5uF!lN8fSrv{_F`xJ5k(gS6&<~OS5=GEwdFnpW`Etpav z(ExnveSWfms=40As4N*jG%;2ENAa_Hyy?WG&F)ttM?Uuuxcy9SE_~Jxngs|9;l~3W zuCn)l{TnccyHbgg(1ryoQxP2ZJ+F119j#SvrJg_<_*GEP>o@IWf>C+eczL!UKx*1# z>Emb&-s?Hf_?2!~{v6qP6FSmlE(03EryInLE|h3Z3lT`8>G z4XF&A6_L9)=}SDSPab;U`XK22>!Di?7ai)Kq#WEzX9=1gn}7B#DhMU-NxcJW*t?xB zZ^YRR#rW>^${yGzB8mYeLCfGLK?do{o+$;2lMz?aH^cp+3u zpIC`3E8sV{;wr`>C(h5HE$A@jq@06NVRw-~ytGp>6V~GU##L{8^s-pSG=B+d!Kh== zpiOHHK(hgP+$j4i^0*ZI5f?hwzD3O@fVN|e(u+G8_Bu-00juDM6A#6~oH;14Pge=X z#G?4A&>c?VW9uCU(k@&}gN)lbH7W6SxOlof)n(hLG48MnX1?Bor@G8vb-oU&e1;dZ zu2SK^3#F^AIb4!NzZO&h2Uf=6bIDH!hEt2s`UMs4*X4FiY(LDRpY8bZZBiXwF9HXc zQfqOwYIb@`oDc83(atW73Eh#&ewo!X`aggym5&{syX+n4<=e5#`@SykVI56~3QnaE3i{BP3Ke|v58^o*MpA9oADQJL2r#%KV|bKfIf`G zEaB81-VYibh=)COlNmE{@Ib2U-uq27=trdhJZ_NG_2|R(8^&5Lo1GIV&Xlj;6Z`9S zYE24f_S1)+_)Abh4|!NnVZFCC;9Ca#Dx2_+?p*< zUNRZ~vcce%Hc#T0vr^sFYxN(0Q89mgFya5L5Lux|mqT5`_+JaJ@Rkspp#Idl-Iz(2 zWb=owN&pY*z$X|NnV5)Y37;{E2G8FJxZLiR8}F*E$w@kK*!T)@)K7PBP1uTcrRaOp zH1PJOt=ErUdQ4hnSmD?0Nwrt^6{EwMqNq*Zf~fm4rt2Ssb-?NVe-l@-!w%u~3bP-VDOlPzkR z9s%q$bjC)TIbDe`FxqaBi)(pjl6j7G8VBB?CF9;(Kgy;W2JqJ;-qYb8y2T}-Z3K4l zhh;s157BQZK7>5m14417w8jTHjfs^_{W_Cyi9o-2T<}+%ErlME?v2{AYtRjhR5b0S z4l-q=0VSa<&*#iR1=bg3AF-%F?Oy0_p;$6EcOU1h@{qSVe*3KIneKgg|lUobsdW&j?%oM_T%b6;&`~>)? zE3JgKWfI7^4aI#=W05+P6TBCb-oj$L5vgAGb*mXl9MDBER*H-0h^&>6e{GKi4)y59 z0Quh@;u#ynoLPn&mDs$oJqz_U?9;m>|9&{dhg7d)(*ORnm@8{Uu_xS?;)1=FR zRx_s0D;$irKG*jB^`|kLIHT=9tgPP?&jrt!0ZifH4{c^f4ff5+lK2p$`FN(^9$Lo8 zTnBDd5AotlAoImv#BPbpN_}3-r*Zw@%$U@#0^>8exwbsMlS-Yr)($Jl>g2+O-5p{i zfBZck+fN_cV_&xX6%~I*;ZAn#Nela6Q2r?xRHPO3!MLve;6Tsi70P!en^wn*;bI47 zO_5A128kK3+Rx8)v-L-Lh3G0R`r10)M|-$k2C&Tyc=xn-M{>g+gB6H!D{`> zQ-jHehsRmmD0{T3RX>Ns!JYJuA4-hLlJSp3rBRN835NOFjC0evHSgn(Y$BkpZ4yOs z`7k&J6TV%CuHE-=)CPk$z?y0iLj_1?l8w3_eNMX|s|ohp;R{IhgmzbGjrxVOo%y|f zrj>EI>_hu!!kr-NA-T)n`C1MTV|TAb;UvZ@oa|%+eD0cox8T^K_)RWA77sADVb2j`BsqPNP4f*H+p~&nm^~zX4wN;k z{Fv%uJRVjaPtTywrg2aZA6~JDhc3UfYJW3dhJIOPwcdFd-+%BBPJKs&-?F-XWl!2G z5B#63{B^Uk?W^|I-`msd!kHsOy&c2wR?B8b{3rgb5|zAaM=CnyMS!NqUF|wX%-Hy& zs|jXey?t3$PvLPP0sQyjj?qzh_(z?55gl@}t-h?8H;@%MFNHbHe<1(&CqL^RlC9kH z{pQvsj}V@qsgRghJ+>!Q&JpX7@Nso)gDyXD?XjvDoi+q(s2jsy3M^4x2gC$OVW0T$ z<2VjBQ*U_g<~MD~tYqd@oV4>#6-Xxzd_VCqd%xHQsc~G6s8B~t{dk%*t5?a?+S4K zE5(C?-aI=1G?l~D9-=j7(rxM8s&^#b$MB8*r%x>=G?K%@(O)M~c1-Q6)Q*xq=M{Ob z##4WWLt;X>3Lm;VTl($pdq|{V%!skE-$`K#(03m~pZQ-??`pYhT1Yf@4SV@Nz)B=f z(xYee2Mh6g(vF+gpBR_cR!yXt8kR1&iiv^zzf-zb4!Ro01a6JAsEsEi zm%T}pD5_BUxug<|$Nb)o$tYakhQPMlWdk&$G6CUeRM7f3Gk|1%wlT`g?>8ei#=^kI zK!z>_Dbw}pGR~90F8WBW`XN;oaGxHi+y}Z`e!}>Dl(cu9^XOUBQ5|Z~Ld*}2lW1XV zivvHq*=v4r@_nfg(!smVi{l(9hxBu1{AAchl>x&`Df&mevYA6oqR8x};w1qU2WJO7 zr~M*I7AL2WlD>{M%%m^-ubX5>SoT67sx)K%ErIdMa1IHf;{5DB=^))Cdhwd^$92R@ z38~T3PC;2z%x|7G#KC`9l`}O#LWGyiJ3@e0p_Tse1i9WmrUCjxj%jDY;oq}VpWHL; zN7gb#ht<)OF%9Ie=|0b=E2WXD++|B=?Ole;C z!Jp-R(g(BlTR&h5I(2@1&?m^9IsHk)O@XJMPAD}Ep4^46%2wMERRbN<(8)2TwbwY4 z2>3&W=;@x)9GL0mdaf&rKU+{9VDZt#8f{BdH`MIpYWM$V51S(|nXSRMdt@pA9z6vt zUE#)jt>w7z6OO4okQTW?>Bu8{5o>Yj1TF@!i|a{>tcL-XW?Gg#2rBsPWV2SySq2vc zsSeW=r$|znl9eXqGBvGMTxX8i0aLVM>PU1^)0??FmZc^CB~%}I&Qp&}Sy5W`t7%I$?zFGOi@;YI+f38s5Qa_$QIadP+Dx8$5GoR# z?(=07u)pE>@q?)1sW*Y`NZ#3+dkt?uX*xBE*ar!$U*>NPasvCTq!3E}M0cCU-|v$L z$(I$qHEK)qW3Erw`b2JTKf-GF6>N-DSuxF zpx^h9*xd!#)7-EnyK***wX<)3qxE@e&U%u{%Kho-nOQMn3OGnONmtLIl1G^tO4P(u7n?twBG`R+VyX3DEyCz;nQQWI)sTPso+tlS-a9-YX0GI3k5!6k!m0b& zlO)@-@(qzvEGl+Q8>1)-^XUC2*oNS|5xV0(=B!Jidep8IxDs@?<8+mzw~u`g8`CaA zNE;B5o7smi!NsKWS)7S6x0X-3?(cQL9u4a}Jn>ri|E%omK}yz~+SlVUfXp)i7cjpD zWGn=h@FG^;W^8L}V6(m^d#3X;v@bXaG6|@KC4rZZV)oMk5{XcMmiZjREa#$nCnnPn zHbbz|)K+Q4&QWVIS_k|`#go4|4c7mg4IlYi-p{^O_A>JSyW{`+1myFwma+-r7MbdC zfSFL@R4Hz+Wg14m9Mb?0+5;Ec^ck_rm6BFsDh5Fj@Ht7-!95EE&} zueN%Jr1Z@u9y1lBz1fgb?5~k@J)pkz?f$L0{~kO2{})g8vi{#=pSSxNbkxV!Ek_c8 z(m-JPIv3j9s!s$gh(2Ty0NH`w(Yauai$cA8Jv0I`XFO)c7!Zym$;#)52_yx1BeFcM z8>6SuNLZjqrlyss0Ag`EblPcr++_EM^ZP4EI{Q)nzdtU;mHmxZAFPEN{Vi{LuqFmh z%s5^49KuzQLM?);OUPiLy!MWdezwP;5j^17=bgC{fZEYz3iR@0A(uuoqrNh^O@i;D zi)3NBMd#WQoXxi38tN%q6e-cx9VuuyV>f(XPnIH{Rjd7rU3PyV2WD?0MBFn(ZmA_% z28#aXM3JvW|4hIk0dZ6J`3Exae||F$ksJ;=voTgNsn$!K^&-4_EGKV|E6)h_EV0Xg zZ0~Q}p&8c5k0BPPK7Y-^F)R!!l{0S%s$;$ zE@G#CG~oED(U4hM@)I+f&W!eMow6f%o$Os53l98APC3&x{ulDqrC@>yzYKV;@T#pn zn~_flY)e>TVm;)#^WVE(O;sE^2}tDbS-^jjL?gM-AO#BEjj$gCND?wt01j+T?4qlK zf{UF`Rm6}h$XUbX(X(SFhTZ-PJ9~-1@!z>zN|7o(wjXI(zn&8e^Hlog)w04&a>e+$ zcl=Vd7jy`Omd)$k^yzpr?U;G zcnRxubIh%Lt*>O26JRCBkTW*{nP0XM2eFi&&DzmC^|GxjMp2}18N2>=P`OO2%Wced z13U{d^K?+*kaJ-iClOS5J1u5%&&%=DImY^Z_g@F(l&IOkf+h&yQEjy<7R)h9ag%}! zm4d(UmbI$DxDz9lZ6!sW6J@27?Cb%WkLcCf=>aAJw4uvsiXcf{XfBB6Q6&RQdaY;v zBDh;zspwu#T;;yxLtrB}lP5)pxG*ZzQU0AAA~V`lonRK4r!^kV09U57!dsqyht70} z%1RD1IWQ4!8#Vef+#6{ayfuhAm3d%`){AUO1Lz;eu{7fGl0pS=R`QHbWOK)I+ttb;FB=A&`+13e0*pA^rS^xHJvClh z>)zRlFyK}?rytQ21_xW<70^XNrZ}wk328}?X{UNs&CK2xU{hP&JoQk5sHnhWI-gd&*TpF z1cZJUKP@XNUeDulu48@OU)?X)|2pPbN0e-%n*`aj?Wt9>S0p-iG)gPhAKjBl(YXa%BO0?+#sEEu#OT`93a)*RL{Kh(nUnP|3g%vW+R#&ziFPs`y11M*4(YKX$nB zWCE$g@cAW+cNpJ-y&O$f+?WAAp6Z@PF}&sIhxSs7N+Jne0G|%$4bXPl(Nm%l72?Lk zu3e+wt31ySq~`XNd3%&I7;zLSZL~@oG%eRa-iqOkyZeEFtM+)Q$ZfA9ho%B-@zV~2 zBu&4OTUVBiIV?VNx`@M4D~2I7J+O&WOc+GmY|u_}wA9p~%+Ijv;Xd6e-|2Hds|_Ot zLs}G<0rIu{M0?Qo9{HNQ!1SR$JtlpH(%AWh#qRWf-zpS3ycIZ222X`2t@wHHhHyV= zpv0LO?A4e8Zt4+k$$9E|vo70g=Kl3sK7{m5;~M}om6gT>!^)jpLD*c5>!MiRux32D zM0(gT<0WTwzA<;h1ap~NPe;~=>!+(C(HpcOG4+V!8kUMXvLoTF6D?>G*n&;Sj_a9 z>T3v7m}DD5IP~fH(arkVgEuot^Y(z=wLBiiNyu6Bi>fI8CR=<1$;SuIKGsf1u78u*FtDQ2rfjlrxJ8^WK6o+>pX=Ju`9#igT)EVXfkPJ5c@QnGKa3eL4OS71O|MG4!=Iw zH0oL@fiPKh7I7WgufrSC5-m|G_AkgMU>g&e;ss=EDlV%@6q_chcx z=Xy30`kcc|i7UQ0Wt;SRv{6W9dPu=pOWQGfO%0*JA?||&M+ja7{vbbspRrtr zIZ8Yg?~4nC^)R_ggS6|f2%tC60tSg>B*uIk+EblDwp$D%aqXPq<8cmNN)sn-xVDHj zJeqlp;q5t_s5U3Pnn>dHrCP%xWfTdUjC@yy|5MC4>hi7UR0yZ zec+DDqg;(7sE9@#iqeAyKr<0hXz<8s+?OUPhkp6zH$uAQBBX~*ez_%2|zCh~9KXuw9DwfNBdez5@A@OCb3%>2% zQ0TZ_Qf`o$XtUGeDodA#MgnIyvz)ja91a^)VS)vB3lVYsc;4Y`_}#~N^k%PfJ&yJw!}Q7$}m2HZuNP%?mV z=0q(Z2p*H<5vfueRGPs{$B=CwB#P=<9g?iuicJqZcCYDZ3=Cs2&)j3^U;{NZ`#xwc zhns(CukAoB?Wk_Yd$qbPN!H|iV`%QgV9&VBB^GF3S4Cnp)^W%b5pf3Y?V1rJ$+)Cx zHTZ|o%qQGyVoJUiA*eRTVJkm?GVe(Ym@T09BNm?8PQzh|dYAl_>PxO?txY&|{*?L@ z!hP$Cq%Q9JPeg$)$Fz2;!lywuZPGx=1}2g_N~zaMi10@ijw7boCaBfY_#!7|L33gT zelIlxYzM2uQ#3DUev5l9$4$pf%RH6xooWXjapAH}5g47ZkL}q}+!7`EC8SEtR5@kv z{@xf-OTK$vGZ#CQp|Vk;twhXWbJcCpW6ivbTCs%_xg@f|#neL1_V?RkQ4B1G57~3g zBlYRI4vbPzB%#Akb_Mo$BB^ie(z4lbs20fc@$hTWzmlaO2|xifx?MDLgKLjk^3-lL zfHL06gw~YV8a+LcqOjrCTFx*0{c5*MKq4QW?Wf@5UO4)StHoV7t1_>d*v^eQ1!y|Z zWbKdn+2FjeoO~V$K%Hu$Wq;yum`F*HacTMGeDl26u%QAIM_D_0B)G>ZB^&wb8dJ3| zuN5%I@!TSgjx{iH8U;Yab?oXH`On?K6G_h5yrHc@6G{A{qlA*kUzy2Ly_~=dX5o^2Q2@r6!LUMf8nvQ>Y#1Q4WaY5_VrNmXM$q<1*81t0 z7?x(L>@hMw<5U|EV3Hf`d^x+uCWWzhCtk@fK*_6G6O6482CV!fJoE@%+rSX4}o=a;@Xo^&0BFnojj2amuQknuAb1CS%P zt~J&9r>{%EWd=t#9|M%@1Ssjbf%by5X8{>!Wz&t4$Snk1oG+`?T9^CtY$FiJ?0wWW zqxPsG?na9QpXFIrvN4e+)~YokxDTZ)j5>`{+1b8>AEey7A38 z__ykg%3?AX(Jg!g1cxr@3hA+uY6iE@!JJ_^|3bOn+Q$@f0O^Mm|Hke zc7jQO%`d5IB7{QvAWY{BDM~lT4szX5{%w1KbL7~Dk;P7b5)s=XuyEhH_Ek`#&Z4SK zkVpQl>V<>yBlABt$){0@-gydw$A&jI-zjH`d!%)VhBR@>CP_CgZ;fs9ot1W9f-@~{W)<<)9zC_-J`7_+lpJpc`+S)Saj_2y3|Kf z9>=nGVz8fbV9DQ=1le-Q;x3=__+PEDITd)RSBeTEyNx{V#TKo>6!{k**q759X@=6!Kd;JZ^EsQU2RCgu^z*tZ8v zr!T6nU%21rArH6e;d;`QsY4SP?{)3V%oy=xw!O2N$?p~zznIL9Sf-3XE$5jq zJL2bnO=83Yn6Si3tvsWZMI9)LcIF$JorCsJ zpn6bt4Y9Bxh!xHoU$#si`sIMPZQ|a|=IZ(<4rc|t*UPjEq5_E8P#Jzeg*M^{l2M0v zoI9adgS0GZSOq9_%G_G+?4_0LIE``uqkj&^MCe0S6IDPSy}8CXc5d|Zne`2YW51}B4 zt@>+b^EGtcpp!Qlzf&Bmo|#PE#y;Z_(I+qb&Ba6^rUhe^T!pDZrfME9)PCBXkAcqc zWaKZFh_!TTla&amFyo~*&^JJple3i$;N4q!zG*ers8a2o*t3R_3j5hb^-^M!j;#W5 z=loRTSeU*|)RMVk!}Llh!8m-aulYq!fiuf_J7GvE^cW@mUYReMO^&|d{)pY!X?>gb z!}lC#u9*qjSPl#PU=0~>q;h36O2BmjJR}wb1;x_^9&ftf)l6=?Qn59sD>@X;H%$mK zG-zNsS*v(wpX-eZK|}75x6wUZ7TE@#psi*lLq%D!ltGOZmr!iJ2Ae=$8LoogedJNu z05bI>5A(wMGJ>_)g`GE|X|N#psDZZ#g(P)DRPC5KA=dkC8Ft~RGbPP{@ zr94K2IC?&I)t60W3}io1Z+AEEBy(yx zepLD!BntipXGTFtI6i2r>A}lps@dM*5UE(cv=U;Fp=wrlhdf6>3N@{mB|*=* zY&LHw#Ws-i;|L1IvnbHl-rK`*j8Qgqylpr@Z0BPi6F&W6~2b)iC@ zj{hpD6-}09Ml-1Z9ctW5#gcEjx4&?j=PD4Xhv~;RIXEua525UT%O%SX04Wn6{!>0S z{*mKwDqh$jk6gRFbQzX}rT$_ElVJQ*5@m zp7SX#w3J&3g!I_^OdnMKWx)GCnTPK57%?5Y1ENT&i`5->-XAsGx^eGVh+5)#MDZHA z!-9S=S!nj}@D0UFDpJ4CH6ED%yO_!ni0$p*&5eoch$c=#K^f}5n&jt(aMCVcv~|La zq6<-k&Z#oO3!n+moFm;m$K8QF%^E7c2EhFf5Y^f2_xW_$)`5fLPFMQ9KxSj!vQAN3 zb?f7Lo0l)0d?o8lb21W34vIta&owSJITC**s)wWxC2U&0HJ+^=Oj9;g^h@MC`j^Jt zZvSh9Ty#CD7wPY2(F@9d2t&@yHz_yJf_^4w-w#_%X)@H-rY)7$H*u^#tHYSzz1upL zVs}-!y16vj@|q%2>5FvOcm2>rO^Ca?p<&FcA=kyi+Op#f;R)iZ0K7B*F%RU@WX8E& zgW5vOeqTKH%xI%9hH;{}>Vi=FHqxu($=?CjHTe^_GltflTq(Ur8$dIX;?$I8TpJbIp zD24~|h#G|}lz;c&8z@vn5?xz@RDsm|Qoi|`pVAN}_Fw`dVx9KHcDB>uPMK&`ZgS$| zE?~a*ZC--PYWK$vHjPrvIv)7{0r*P;P++~pfVru*G{M4k3vd1gxzA=p$)n+|Asw&j z`vO|l(9sNUMWbkVVkArcn_BXCM6M+Ac3b!6qp3(14TtmOLssyt>e!9H!j`FgWoNpr z@rSf)MA2lil7&UpxgaUZ2rPzD!c<=MwZNO<7d^%`W2*%(sShvEB9S8{`l9HsC zH|rY@xS-<-8((^mA2>1|y?1?f55WVVE2d&5M-WN!EIJ@J%u%vT1OR@`$C(WVIF+0Y zrecA)ne$`ZA%@)9jqEj@NHRxhqke;Wm-SMqK2pS!_MLMV^O`Fh^^6hjof6$L`hz>V zd0#3$78g^sw-H?1r4o6v7;&1+0j`04#L{ss98e`tZv*CWI=-yb6UXTxR`{$GsckeK!!mOb+4tg0 z3gH+Ai!ihWjv6yj()6*Cg-H2FJ8!I$GEjw&e%SdfZKfmHuY4$ATT43}o2Y94rY%d1 zaU!ELnrDnye})9~j}qo>H+@a<9#tJ}_gOhGY<13s6m^C4zr$)On&^-|1qqKi_gU^Z zz~%1wlUnKl=n+-1Y2q;LJSYfYX3_Q;ZhN~2YSfk#&ua45SGl`b&9}U-w1&tiUOx?3dt8L-pfZpOK;{5=hPwdj@y@ksqh_~ku;W-UP~bQHFhS7Y z#8X5XP0RIOPutou9hha7|II!QyY=_VQk;D$rAe01X3Qb+ncCcO&H1BcgZX1Y5uR(1 z9TSg)XoTj$*aagN--nuAQp8b5k*Z1mhm?kix$gOA99E#|X|?^87jWc84E&Bu5IgG- zgjh3Ld=Nie$OUC-7Dx?W&m42((41p^w#~bJCYP9p!%TU#SA@tmJKhJynxZtpI0>Pi z?3C8Kf;f&k7doacHza7|ln_QFt-)#a*=p(6{AI+8A7tA~4(Elb#AGb|xx-ZAQnOB? z`Aw~I;69~hNG)?>DBbj;m;$t^gTn>UrUJrberVrZ-Lnv8nm4U;YCwB|Qx;`oO!QWb zvA?;E(q1H0)^jCpfA4)Z^@5A-`kNmMJLOi6vEnvJ&mM!-)4FulTmYzsu2OQULFR4H7}I#nTrj{i!%J8BJ!I@XCs*Sm%=m&Af3fL1j{Vl9pnsQu z=ooC~yqdIy!Eh1@l`gpyqmWmALiS{xO%iM21D110dU|q4!G>cZl_XnTfCjoTO3$;X z>0Wm-34_VJhT6(8s5}%`L#S-z@ovjJftu)7nE0bVl#ZE{VB&uUiHKl$c`|(nEQj7} z&p?uOH9?3T#rJpieM)HT(E~RzFesG!eHU-1R_ijojSLl!X2UgsB z6NNE(nbobyv1wMwUpqAPXei)Ct)iTo_1&{Uo2I93d$7J_Ph8j&YHT*-#T$>_UpB3T zV_sR>T{g_DP;FAaOgA?BHXL|Kmu`&4`Jvnl)GG*hRnT^f>3M-OSh#>iw&8G6pna6m zfa88z>$G^)y7j8U5611k zk!8F8pW3d)AIh!!@3|N=Ow71NxjZxOa+xqLDQ1}5av7mlr9zTd9Std!ler)UxkRBT zM4~I5P9Y5;R zaF#kdn-A^jc?e>;dGcU)_Z;7FM!VD9o!@z*|3-hHA9-qj@}N=Y{5#iy1X~#hq#Bd( zPBEOV_jW0rF>O&Kdo7ms;f`fnAAfLvUHl{3;7_N5E*zzb{bD-vK2`Za@w+@&_N0y5 z8ft^D8ag*P8XQjitMH$isPH&l`&}||C?u059B%Q(U;RKdH|z-RHov+@r%W_hBM&J? zrqah+ZQNJw{Rb3nVge9o>yTGz5iN@# z3hQI$V-)=#n-)RaOfcQkzu=pn97_PL=5{O@;PVhCa4rT*c>dwCLHY0>IopPAJ`3nx zK&ZH&BF59j=CfPSFDZ6as$<(a9Ixjs94+0DZ);5vxKW?+dTayeB%Cmx+6TTFwv-VScwe-t%yLw6Sy&JWv$nH4ja@Ug=+!Pwq zcNwUob)3et9-oF7n^S1MZW|zamCAw+o_hzX>2|L?#=u$P?nA$AW?s^v$KCC_{mW%u zt6iGIk+iHHlYBvha#VR|rl?W$Ch%Nh7}-<44qdgrgjI(& zR;bVI@B}ly1~xFN%pRW7Y8WZJ?*i7C1V=(NJw2hq95*rGY(b6{PS5Gq__oJ+;Gu3~ z57Ue1{~R5JiqIODb4{~m@tdC@OPizTaTi&?7aQIiMc7+xZX)A3q6B2G`^m;EganTM zv()|Ux1pZWdS6209-(4bB1Wc09Mx^Dl1NJ1U7$&oU%ver#)<*ajDhqO1|m!S@bS@* zPKav*)niDu^G4TSATYtDq-nj~5!Q@6fU_b9Hi-mQB7yujYPx%^x>s8CF1cuXRdUh$ zs8+9fQ~xE2Rjb5m;5(hH5duf#nfnr}=Mt-SiIMc)uvmFm{ApNh^{qT#NnN)} zlWWbH{PuMwA#@)Kul+hA&OGwQbY(<5=eWOfTVXcX}eQUHd&6Wh= z%NoCHGSF7`pH1#R`>lLNMKdls>-X(1BB}j?)`NS0wa=+d0-~$@rHFW*}b~fk0JcFyLF&IPX9z|=D zWZ(?b?XyuE@mH6rksr$htqQ=E=-p!JCKbKjav~+MYlV*Cmic9b^?hue_QX#d4!E~M z*_*;DRZ5pk>;D0#EuYmInXM`cc%E_~mXNElqQ|RJW)56Gz}{b~#!Q#Y$4`!&iZU9n zbUGFH^HZKffmO;PKLh7KSyu(0o!Zp;YV=*&pWE`$N81Y6*tB?B`=Vk`(wVCuT`F2s z^?z}zQ}K0@K7w39=)U%XA{46qtCu1PD@^;3eD!L zZ!Ji#T;BioMtjWR>ZgNxUj{tuPMnxp_0GUkLkBY3?rKeP=xtu116SN^hEE*)oiQN; zlNa}EZ%Cylb(38Mvj`Kxcv&6*7zMP87*Uu5)lqX?$@jSv4|fFphdi%b>@dX67wU%g z_`>FoBpb$>e*NFqrZ}3XvJBvk1+pc>l@`Y-Ms@4HG?7f#2Zum!=eBusuqN+T^puFM zBgBCqPEzW!0;eJ7la!`CHdJIs=yYBczdTr#ohE-(1{P*DA;_>_n;_)Qe-=cZ z?LViWsjq^Y&js@$7R{u^fr!u~Sn5f10dHTJ_TTN;kJ8!c+L%0TrJI?)bE&xNJiW+v zBf^`Ea=D%mdD9QWW_Y4BH{5`tliw02!5XN-Q>Hh}pyCx18H_MjME4noFeb~l6uxIp zIoZ-I17FU0ax}X}MLeK_IDe~YspZz|wbTT%rZP!0Z$u-Nx5+6Iq^o;lpJkg|j+6t3h&hb`4M)GED0?4(5}i@sXX~Q&6!@X^!CI z7wT?mRghne*cJ6-H=~bPw9WJ^k)?|rGsWNUR>jpgYpC1gY%cMNfFNQ|5 zb!6a8#$z|c11^Q1PMB4D-!MOh_K-ujzOAQl8UX>2u4y;sipuAigNM9qdg`!@yGCBj zRx{9ZVOEDwp`&y%a389`A<*5ktImjaA@sO3H6L-SWDqg%G12e!N$UpxVWTny>d(G^ z_Pva>Rpfk@f!{sU{v~*&8qrASghvtz9?8I5A)3O9j*bA}nIyzbd1Wtk;qJehmYxnN zkHyYL7sIh#se~=n@%QlLo4mw-3E<>=&B1cM{c5&3U5|PZ;{@k`Frb-{t^c^5bo!d1 z5&@r}?Ib80%AbKnrtyymn#_YvC^m%^ zT)IZm^b22snrZj`l7XU`yw6hh($@pT%3}B$Mud4}ceD)js%Zm|QIdA%G z(enV$3#oJb4juknCKzvN&q^fE%v2IPoXt3(mA%{%SZ=LBcG}8*ck;NiS&C1co(yOS zccjV>#rtdr~|h1nstl%IO( zr20aDyV+wcP1mXe%B2Pq>iuGf%=#-Q@)sYu_g5I_83H?+dxgVQ$6k4GGwbu8(;Nx=_Q{ zfi?}!UI%=aWXDgz&dw%du`zI45H~lLsiyyZM*?fUZ>X4l=d_dbhPzEB`>(8@f+^gS zboA9r{w>NgZrV_5+}m#4>XQ}M{WFCTdwEp&@#5k(|8-hG1xu;1jqG^# zMy^TKW@ESw(xNeaoT48EAVI@#lrT0}mDxWIzTA zCcism)PEdu7(8OmM2}fV_7oWTT!B4d$v>OlSL_u{EPB^*gcj}WRi?SBNO@S2A`*m2 ztX{k9`261;GG}wZj4p9^N2)8_Nm`MP6oz5d@$k>7mS^~ap8PIip1bz5yF^^3%a=BX z-80;C#4i1)Q_pPKeo-F9R*|rW_fV zzP-R|$!|LNJQ=RxqL4TFg^Mb;zXKorsqEaTZC9r^=XCvQsi5Ho`__Ot99Ba;y_})@ zXdLVP_PYPtY~lOSkGh1zV7{>oaLiA*qUB2`eif;)ux#m5BR7mCH6tc0*evv1PKWpAv z7YARVFc>jZPL~1K-T2V0qlqqJxe@c|Tj`o6WFCRqKBCC9pglx>UuE1S95oEeT1WiJ z7*2+1PvebT;goQ`y}y-LI6srOyU4&>PrO%eT*Ge=w{C`@t`HGw+qCiH9k%!nn!l zbwpJ6hfVCPPOjUfBW9lmPp{9FE@0}XF#%c#b*F_Y^dAr|NME1HDav4=g4h+z?K{t7 zksuhXXZ+gLf_esVX0)wrQ=;+xtJma$|CAnOi4s;WQz98pO6vNgB$`ch8JHN|2<@XD zi#$rdkXQg)%O=%>nw_Hp4k3}yQ5MCUgw|kU;!6lzYLpDDZ#B3boii8>2eO&WQbKq? z0<|Re6xfMXGHXLq3?oVa1x+7i6{}#T$l0jEc<<;k85l0l@x+>C$v*@QWY5>$6Jw7y zT*rcSA;ApJoNE|Gjetdj`-?OI!Xu#keFHMG!DmX#_Qv9iCTI=RIe0!kCQYd5a3F*Q z;2XngeI_L#fEKn`TQX#2Eb(h}l!3HL9VY$>oE1a0!Vecx;bV}h$fxt+?rx3?Qe0^6 z9d73#y(|x5RYR>*r}P!P685IPGOhX`5E~XFV@-NKI+YP~1jEZb)1yRG{bGhml!<}~ za*F`Qzy^bbK{hX>`Tz%K%veZ)2X9BzCU=|+Jl|8FFr+Up1KGRMuEPV&?^gOUpa;v$ zXhkImtJv?cdXW*R)9`I_{^q#i(|&3=i#ltfXTm#f3e*|ZY|PEC*U8$zUKY>ndm&CX zmg?tB?|RLCUsKOm>ym-YFeLqsgpP4lKxOzzyVpTu%QYn&j;;ID?y*o088EL@NHs_? zmKr_VuvH+c`6%D!hiI(R-Q7)R1mfgYq~)PMH@-_T)7>OB2nZs zhPHQo$S?F}e<>;jd`v9zY9*?F=t;4;3!LrywmtSCVFsREdEt|^vf+w>yq(@k3cJdT z_>{f0F5E%Qh__gZYG(hHvULb`!}{EEF>1v40|-jC&^wyV iu2rATfHc`l>hY1Z!w>aVDhK-NQvQ&E<#ZR>*#80EXPoQ+ From 9775953142c0a01fd127a34776cf280f11dda111 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 1 Dec 2020 17:52:32 +0100 Subject: [PATCH 463/560] [poincare/logarithm] ln(0) = undef As a consequence, log(x,0) = ln(x)/ln(0) = undef --- poincare/src/logarithm.cpp | 46 +++++++++++--------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 182747767f0..c3807668558 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -267,48 +267,20 @@ Expression Logarithm::shallowReduce(ExpressionNode::ReductionContext reductionCo 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, 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; - } - // log(x,0)->0 with x != inf and log(inf,0) = undef + // log(x,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; + 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, reductionContext.context()); - // Special case: log(0,inf) -> undef - if (infiniteBase) { - return replaceWithUndefinedInPlace(); - } - bool isNegative = true; - Expression result; - Evaluation baseApproximation = b.node()->approximate(1.0f, ExpressionNode::ApproximationContext(reductionContext, true)); - 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; if (r.isOne()) { @@ -317,6 +289,14 @@ Expression Logarithm::simpleShallowReduce(ExpressionNode::ReductionContext reduc 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; } From f09f06d7acf23d256f3e234fda8d88c95f9ec97f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 1 Dec 2020 17:59:03 +0100 Subject: [PATCH 464/560] [poincare/logarithm] Update tests --- poincare/test/simplification.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index dbd8780d867..630759d7d70 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -440,7 +440,7 @@ QUIZ_CASE(poincare_simplification_units) { 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", "13.083978345207×_ps"); - assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); + assert_parsed_expression_simplify_to("log(0)×_s", "undef"); assert_parsed_expression_simplify_to("log(undef)*_s", "undef"); /* Units with invalid exponent */ @@ -645,10 +645,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()); From 48bd7b6a770cfdea8dd86ac85495036bf73f7f5c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 15:19:49 +0100 Subject: [PATCH 465/560] [poincare/logarithm] Factor corner case log(x,0) and log(x,1) --- poincare/src/logarithm.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index c3807668558..a9faf80207d 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -268,21 +268,17 @@ Expression Logarithm::simpleShallowReduce(ExpressionNode::ReductionContext reduc Expression c = childAtIndex(0); Expression b = childAtIndex(1); - // log(x,1)->Undefined - if (b.type() == ExpressionNode::Type::Rational && static_cast(b).isOne()) { - return replaceWithUndefinedInPlace(); - } - // log(x,0) -> undef - if (b.type() == ExpressionNode::Type::Rational && static_cast(b).isZero()) { + // 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) = undef + // log(0,x) = undef if (r.isZero()) { return replaceWithUndefinedInPlace(); } - // log(1) = 0; + // log(1,x) = 0; if (r.isOne()) { Expression result = Rational::Builder(0); replaceWithInPlace(result); @@ -290,7 +286,7 @@ Expression Logarithm::simpleShallowReduce(ExpressionNode::ReductionContext reduc } } bool infiniteArg = c.recursivelyMatches(Expression::IsInfinity, reductionContext.context()); - // log(x,x)->1 with x != inf and log(inf,inf) = undef + // 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); From 3501146e35aab9da0d32b53fdf5050f3d2b3328e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 1 Dec 2020 15:21:37 +0100 Subject: [PATCH 466/560] [shared/continuous_function_cache] Detach cache from function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When three functions or more, the third function and later don't use caching. But it is still possible for them to have been linked to a cache (for instance if previous functions had been deactivated earlier). To avoid a function tapping into another function's cache, we reset the cache. e.g. Define 3 functions f(x)=1, g(x)=2, h(x)=3, and deactivate f. Draw the graph, come back, reactivate f, then draw again. ---> The space between y=2 and y=3 will be filled with the color of h, as h "remembers" using cache n°2, wich currently contains the values of function g. --- apps/shared/continuous_function_cache.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 9b3657960a7..1c578e44bfc 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -10,14 +10,16 @@ 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 do nothing.*/ + * available caches, so we just tell the function to not lookup any cache. */ + function->setCache(nullptr); return; } - ContinuousFunction * function = static_cast(fun); 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 @@ -29,9 +31,7 @@ void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCa if (function->cache() != cache) { cache->clear(); function->setCache(cache); - } - - if (tStep != 0. && tStep != cache->step()) { + } else if (tStep != 0. && tStep != cache->step()) { cache->clear(); } From 937979503aac3ae1274fe256833fd773dc106ac8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 1 Dec 2020 16:13:33 +0100 Subject: [PATCH 467/560] [shared/continous_function] Detach cache of inactive function When a function that had previously been cached is deactivated, its cache can be used by another function. When the function is reactivated, if it tries to reuse its previous cache, it will be filled with values from another function, and will not be cleared. By detaching the cache of a function that becomes inactive, we ensure that the next time this function is cached, the chache will be cleared. e.g. Define to functions f(x)=x, g(x)=-x, draw them both. Deactivate f, the redraw the graph Reactivate f, the draw again --- apps/shared/continuous_function.h | 1 + apps/shared/function.cpp | 3 +++ apps/shared/function.h | 1 + 3 files changed, 5 insertions(+) diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 34642fb8ab2..8b270fcec5b 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -90,6 +90,7 @@ class ContinuousFunction : public Function { 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 functionBecameInactive() override { m_cache = nullptr; } void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 2347e1199bb..3a8ca6da38d 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -50,6 +50,9 @@ KDColor Function::color() const { void Function::setActive(bool active) { recordData()->setActive(active); + if (!active) { + functionBecameInactive(); + } } int Function::printValue(double cursorT, double cursorX, double cursorY, char * buffer, int bufferSize, int precision, Poincare::Context * context) { diff --git a/apps/shared/function.h b/apps/shared/function.h index 60a214be37e..6f76e940a6f 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -94,6 +94,7 @@ class Function : public ExpressionModelHandle { }; void protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const; + virtual void functionBecameInactive() {} private: RecordDataBuffer * recordData() const; From 591f47d6a9e62ab50e56985e8ecf238a6f5b8de3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 15:09:57 +0100 Subject: [PATCH 468/560] [continuous_function_cache] Coding style --- apps/shared/continuous_function.h | 2 +- apps/shared/continuous_function_cache.cpp | 2 +- apps/shared/function.cpp | 2 +- apps/shared/function.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 8b270fcec5b..3d9aa8dd0db 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -90,7 +90,7 @@ class ContinuousFunction : public Function { 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 functionBecameInactive() override { m_cache = nullptr; } + void didBecomeInactive() override { m_cache = nullptr; } void fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const; diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 1c578e44bfc..00a8cd687d3 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -31,7 +31,7 @@ void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCa if (function->cache() != cache) { cache->clear(); function->setCache(cache); - } else if (tStep != 0. && tStep != cache->step()) { + } else if (tStep != 0.f && tStep != cache->step()) { cache->clear(); } diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 3a8ca6da38d..3f3150d6f14 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -51,7 +51,7 @@ KDColor Function::color() const { void Function::setActive(bool active) { recordData()->setActive(active); if (!active) { - functionBecameInactive(); + didBecomeInactive(); } } diff --git a/apps/shared/function.h b/apps/shared/function.h index 6f76e940a6f..3ef12a822d8 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -94,7 +94,7 @@ class Function : public ExpressionModelHandle { }; void protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const; - virtual void functionBecameInactive() {} + virtual void didBecomeInactive() {} private: RecordDataBuffer * recordData() const; From a178c88e54722580cb1e4438c04037539f249f05 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 1 Dec 2020 09:45:27 +0100 Subject: [PATCH 469/560] [poincare/logarithm] Fix derivative domain of definition The logarithm function is undefined for negative numbers, but its derivative, the inverse function, is defined everywhere. We thus need to virtually limit the domain of definition of the derivative. --- poincare/src/logarithm.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index a9faf80207d..4eaf6174dfb 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -345,8 +346,18 @@ bool Logarithm::derivate(ExpressionNode::ReductionContext reductionContext, Expr Expression Logarithm::unaryFunctionDifferential() { /* 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())), Rational::Builder(-1)); + 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) { From 764d14fed1740cadd73d79696872a3fa51d56c1a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 11:31:05 +0100 Subject: [PATCH 470/560] [poincare/constant] Constants differentiate to 0 --- poincare/include/poincare/constant.h | 4 ++++ poincare/src/constant.cpp | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index 879ae4b8d0a..fe05d0a514d 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -50,6 +50,9 @@ class ConstantNode final : public SymbolAbstractNode { 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 @@ -70,6 +73,7 @@ class Constant final : public SymbolAbstract { // 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/src/constant.cpp b/poincare/src/constant.cpp index eb53a14d999..b21d5ba2d94 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; +} + } From 1531f96bb8486ceaa45c36996068baca4b2955a3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 11:31:43 +0100 Subject: [PATCH 471/560] [poincare/derivative] Update test on derivation --- poincare/test/derivative.cpp | 197 ++++++++--------------------------- 1 file changed, 46 insertions(+), 151 deletions(-) diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index b9a9100142e..f64b5b30101 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -1,169 +1,64 @@ #include -#include -#include -#include -#include #include "helper.h" using namespace Poincare; -void fillGlobalContext() { - assert_reduce("-1→y"); - assert_reduce("9→a"); - assert_reduce("2→b"); - assert_reduce("3→c"); - assert_reduce("2x→f"); - assert_reduce("x^2→g"); +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); } -void emptyGlobalContext() { - Ion::Storage::sharedStorage()->recordNamed("y.exp").destroy(); +QUIZ_CASE(poincare_derivative_formal) { + 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(cos(x),x,x)", "-sin(x)"); + assert_reduces_to_formal_expression("diff(tan(x),x,x)", "1/cos(x)^2"); + 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(); - Ion::Storage::sharedStorage()->recordNamed("g.exp").destroy(); } -void assert_parses_and_reduces_as(const char * expression, const char * simplifiedDerivative) { -#define SYMBOLIC 0 -#if SYMBOLIC - ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition; -#else - ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllSymbolsWithDefinitionsOrUndefined; -#endif - - assert_parsed_expression_simplify_to(expression, simplifiedDerivative, User, Radian, Metric, Cartesian, symbolicComputation); -} - -QUIZ_CASE(poincare_derivative_literals) { - fillGlobalContext(); - - assert_parses_and_reduces_as("diff(undef,x,0)", "undef"); - - assert_parses_and_reduces_as("diff(1,x,1)", "0"); - assert_parses_and_reduces_as("diff(1,x,y)", "0"); - - assert_parses_and_reduces_as("diff(a,x,1)", "0"); - assert_parses_and_reduces_as("diff(a,x,y)", "0"); - - assert_parses_and_reduces_as("diff(x,x,1)", "1"); - assert_parses_and_reduces_as("diff(x,x,y)", "1"); - -#if SYMBOLIC - assert_parses_and_reduces_as("diff(1,x,z)", "0"); - assert_parses_and_reduces_as("diff(a,x,z)", "0"); - assert_parses_and_reduces_as("diff(x,x,z)", "1"); -#else - assert_parses_and_reduces_as("diff(1,x,z)", "undef"); - assert_parses_and_reduces_as("diff(a,x,z)", "undef"); - assert_parses_and_reduces_as("diff(x,x,z)", "undef"); -#endif - - emptyGlobalContext(); +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_additions) { - fillGlobalContext(); - - assert_parses_and_reduces_as("diff(1+x,x,1)", "1"); - assert_parses_and_reduces_as("diff(a+b,x,y)", "0"); - -#if SYMBOLIC - assert_parses_and_reduces_as("diff(x+a,x,z)", "1"); -#else - assert_parses_and_reduces_as("diff(x+a,x,z)", "undef"); -#endif - - emptyGlobalContext(); -} - -QUIZ_CASE(poincare_derivative_multiplications) { - fillGlobalContext(); - - assert_parses_and_reduces_as("diff(2x,x,1)", "2"); - assert_parses_and_reduces_as("diff(x*a,x,y)", "9"); - assert_parses_and_reduces_as("diff(a*b+c,x,1)", "0"); - assert_parses_and_reduces_as("diff(-x,x,y)", "-1"); - assert_parses_and_reduces_as("diff(f,x,1)", "2"); - -#if SYMBOLIC - assert_parses_and_reduces_as("diff(a*x+b,x,z)", "9"); - assert_parses_and_reduces_as("diff(2-5x,x,z)", "-5"); -#else - assert_parses_and_reduces_as("diff(a*x+b,x,z)", "undef"); - assert_parses_and_reduces_as("diff(2-5x,x,z)", "undef"); -#endif - - emptyGlobalContext(); -} - -QUIZ_CASE(poincare_derivative_powers) { - fillGlobalContext(); - - assert_parses_and_reduces_as("diff(x*x,x,1)", "2"); - assert_parses_and_reduces_as("diff(x^2,x,y)", "-2"); - assert_parses_and_reduces_as("diff(x^3/3,x,y)", "1"); - assert_parses_and_reduces_as("diff(1/x,x,1)", "-1"); - assert_parses_and_reduces_as("diff(2^x,x,y)", "ln(2)/2"); - assert_parses_and_reduces_as("diff(x^(-2),x,2)", "-1/4"); - assert_parses_and_reduces_as("diff(a^b,x,1)", "0"); - assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,1)", "20"); - assert_parses_and_reduces_as("diff((1+x)(2-x),x,1)", "-1"); - assert_parses_and_reduces_as("diff(g,x,3)", "6"); - -#if SYMBOLIC - assert_parses_and_reduces_as("diff(x^3/3,x,z)", "z^2"); - assert_parses_and_reduces_as("diff(x^(-2),x,z)", "-2/z^3"); - assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,z)", "18×z+2"); - assert_parses_and_reduces_as("diff(x^c,x,z)", "3×z^2"); - assert_parses_and_reduces_as("diff(diff(x^3/6,x,t),t,z)", "z"); -#else - assert_parses_and_reduces_as("diff(x^3/3,x,z)", "undef"); - assert_parses_and_reduces_as("diff(x^(-2),x,z)", "undef"); - assert_parses_and_reduces_as("diff(a*x^2+b*x+c,x,z)", "undef"); - assert_parses_and_reduces_as("diff(x^c,x,z)", "undef"); - assert_parses_and_reduces_as("diff(diff(x^3/6,x,t),t,z)", "undef"); -#endif - - emptyGlobalContext(); -} - -QUIZ_CASE(poincare_derivative_functions) { - fillGlobalContext(); - - assert_parses_and_reduces_as("diff(tan(x),x,0)", "1"); - assert_parses_and_reduces_as("diff(sin(a)+cos(b)+tan(c),x,y)", "0"); - assert_parses_and_reduces_as("diff(sin(cos(x)),x,y)", "sin(1)×cos(cos(1))"); - assert_parses_and_reduces_as("diff(ln(a),x,1)", "0"); - assert_parses_and_reduces_as("diff(tanh(x),x,0)", "1"); - assert_parses_and_reduces_as("diff(ln(cosh(x)),x,0)", "0"); - assert_parses_and_reduces_as("diff(log(x,7),x,1/2)", "2/ln(7)"); - -#if SYMBOLIC - assert_parses_and_reduces_as("diff(sin(x),x,z)", "cos(z)"); - assert_parses_and_reduces_as("diff(cos(x),x,z)", "-sin(z)"); - assert_parses_and_reduces_as("diff(ln(x),x,z)", "1/z"); - assert_parses_and_reduces_as("diff(ln(c*x),x,z)", "1/z"); - assert_parses_and_reduces_as("diff(ln(cos(x)),x,z)", "-tan(z)"); - assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,z)", "1/cos(z)^2"); - assert_parses_and_reduces_as("diff(sinh(x),x,z)", "cosh(z)"); - assert_parses_and_reduces_as("diff(cosh(x),x,z)", "sinh(z)"); -#else - assert_parses_and_reduces_as("diff(sin(x),x,z)", "undef"); - assert_parses_and_reduces_as("diff(cos(x),x,z)", "undef"); - assert_parses_and_reduces_as("diff(ln(x),x,z)", "undef"); - assert_parses_and_reduces_as("diff(ln(c*x),x,z)", "undef"); - assert_parses_and_reduces_as("diff(ln(cos(x)),x,z)", "undef"); - assert_parses_and_reduces_as("diff(diff(ln(x),x,1/tan(x)),x,z)", "undef"); - assert_parses_and_reduces_as("diff(sinh(x),x,z)", "undef"); - assert_parses_and_reduces_as("diff(cosh(x),x,z)", "undef"); - - assert_parses_and_reduces_as("diff(abs(x),x,0)", "undef"); - assert_parses_and_reduces_as("diff(abs(x),x,1)", "1"); - assert_parses_and_reduces_as("diff(abs(-2x),x,1)", "2"); -#endif +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()); - emptyGlobalContext(); + 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()); } From b69f06b772e586486e589f26a3253be570da9fcd Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 11:33:28 +0100 Subject: [PATCH 472/560] [poincare/logarithm] Comment clarity --- poincare/src/logarithm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 4eaf6174dfb..7f45f9c1b76 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -350,7 +350,7 @@ Expression Logarithm::unaryFunctionDifferential() { * 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 : + * 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) From 636e8633235d6db7a1942546157b6d7262e8a682 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 27 Nov 2020 14:55:06 +0100 Subject: [PATCH 473/560] [escher] Moved repetition factor logic up in Escher from poincare Change-Id: I7c8f932e3e7d04c928ca881113ba6b5df05b94e7 --- escher/src/layout_field.cpp | 27 +++++++++++++---------- poincare/include/poincare/layout_cursor.h | 2 +- poincare/src/layout_cursor.cpp | 26 +--------------------- 3 files changed, 17 insertions(+), 38 deletions(-) diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index a4c4c3a0721..acc4256c162 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -641,20 +641,23 @@ bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * s if (!IsSelectionEvent(event)) { return false; } - Layout addedSelection; int step = Ion::Events::repetitionFactor(); - LayoutCursor result = m_contentView.cursor()->selectAtDirection( - DirectionForSelectionEvent(event), - shouldRecomputeLayout, - &addedSelection, - step - ); - if (addedSelection.isUninitialized()) { - return false; + // 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; + } + m_contentView.addSelection(addedSelection); + assert(result.isDefined()); + m_contentView.setCursor(result); } - m_contentView.addSelection(addedSelection); - assert(result.isDefined()); - m_contentView.setCursor(result); return true; } diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index c4ec2885d79..3569aa0f6c6 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -99,7 +99,7 @@ class LayoutCursor final { /* Select */ void select(Direction direction, bool * shouldRecomputeLayout, Layout * selection); - LayoutCursor selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection, int step = 1); + LayoutCursor selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection); /* Layout modification */ void addEmptyExponentialLayout(); diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index b90c6922e89..78a7491e6c4 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -106,33 +106,9 @@ void LayoutCursor::select(Direction direction, bool * shouldRecomputeLayout, Lay } } -LayoutCursor LayoutCursor::selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection, int step) { +LayoutCursor LayoutCursor::selectAtDirection(Direction direction, bool * shouldRecomputeLayout, Layout * selection) { LayoutCursor result = clone(); - if (step <= 0) { - return result; - } - // First step result.select(direction, shouldRecomputeLayout, selection); - - if (step == 1 || selection->isUninitialized()) { - // If first step failed, result is returned so the situation is handled - return result; - } - // Otherwise, as many steps as possible are performed - // Shallow copy to temporary variables - LayoutCursor result_temp = result; - Layout selection_temp = *selection; - for (int i = 1; i < step; ++i) { - result_temp.select(direction, shouldRecomputeLayout, &selection_temp); - if (selection_temp.isUninitialized()) { - // Return last successful result - return result; - } - // Update last successful result - result = result_temp; - *selection = selection_temp; - assert(result.isDefined()); - } return result; } From 8cd3fe679f83397b25d2dfc481a2a40df53e83b4 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 2 Dec 2020 17:14:13 +0100 Subject: [PATCH 474/560] [escher/src] Add TODO comment --- escher/src/layout_field.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index acc4256c162..33d202ba394 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -654,6 +654,9 @@ bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * s // 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); From f54c5a8172b9cefefb08b2506395a15af560a866 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 26 Nov 2020 14:48:00 +0100 Subject: [PATCH 475/560] [apps/statistics] Update wording Change-Id: I4e3a042e1da6c7264932ff87fba0529f7aa1091d --- apps/statistics/base.de.i18n | 2 ++ apps/statistics/base.en.i18n | 16 +++++++++------- apps/statistics/base.es.i18n | 2 ++ apps/statistics/base.fr.i18n | 2 ++ apps/statistics/base.it.i18n | 2 ++ apps/statistics/base.nl.i18n | 2 ++ apps/statistics/base.pt.i18n | 2 ++ apps/statistics/calculation_controller.cpp | 4 ++-- 8 files changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index 3d106e3752e..8a84b549edd 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -22,4 +22,6 @@ 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 88d5cc9aec1..a25f566febb 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -2,12 +2,12 @@ StatsApp = "Statistics" StatsAppCapital = "STATISTICS" HistogramTab = "Histogram" BoxTab = "Box" -Values1 = "Values V1" -Values2 = "Values V2" -Values3 = "Values V3" -Frequencies1 = "Frequencies N1" -Frequencies2 = "Frequencies N2" -Frequencies3 = "Frequencies 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 " Frequency = " Frequency:" @@ -18,8 +18,10 @@ BarStart = "X start" FirstQuartile = "First quartile" Median = "Median" ThirdQuartile = "Third quartile" -TotalFrequency = "Total frequency" +TotalFrequency = "Number of data points" Range = "Range" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" +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 adedc341bb7..648c851693d 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -22,4 +22,6 @@ TotalFrequency = "Población" Range = "Rango" StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" +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 50096719153..c6fbf2966be 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -22,4 +22,6 @@ 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 a7159cf6128..2879e7e637a 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -22,4 +22,6 @@ TotalFrequency = "Dimensione totale" Range = "Ampiezza" StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" +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 4d3d2ffbb46..2e43cbc204c 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -22,4 +22,6 @@ TotalFrequency = "Totale frequentie" Range = "Spreidingsbreedte" StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" +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 9d46b27cf65..29ac75e6e57 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -22,4 +22,6 @@ TotalFrequency = "Dimensão" Range = "Amplitude" StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" +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 da22e200f03..a44598c9219 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -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]); From f329ca825f7b6887450632ed055e14c231bc1611 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 17:07:51 +0100 Subject: [PATCH 476/560] [poincare] Add ReductionContext to unaryFunctionDifferential Change-Id: I99ba90f8c38cab5d8fe2139f49e4f94c1d52d296 --- poincare/include/poincare/cosine.h | 4 ++-- poincare/include/poincare/derivative.h | 2 +- poincare/include/poincare/expression.h | 2 +- poincare/include/poincare/expression_node.h | 2 +- poincare/include/poincare/hyperbolic_cosine.h | 4 ++-- poincare/include/poincare/hyperbolic_sine.h | 4 ++-- poincare/include/poincare/hyperbolic_tangent.h | 4 ++-- poincare/include/poincare/logarithm.h | 4 ++-- poincare/include/poincare/sine.h | 4 ++-- poincare/include/poincare/tangent.h | 4 ++-- poincare/src/cosine.cpp | 8 ++++---- poincare/src/derivative.cpp | 4 ++-- poincare/src/expression_node.cpp | 2 +- poincare/src/hyperbolic_cosine.cpp | 8 ++++---- poincare/src/hyperbolic_sine.cpp | 8 ++++---- poincare/src/hyperbolic_tangent.cpp | 8 ++++---- poincare/src/logarithm.cpp | 10 +++++----- poincare/src/sine.cpp | 8 ++++---- poincare/src/tangent.cpp | 8 ++++---- 19 files changed, 49 insertions(+), 49 deletions(-) diff --git a/poincare/include/poincare/cosine.h b/poincare/include/poincare/cosine.h index ae14e37abc2..0472af8d41f 100644 --- a/poincare/include/poincare/cosine.h +++ b/poincare/include/poincare/cosine.h @@ -35,7 +35,7 @@ class CosineNode final : public ExpressionNode { // Derivation bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; - Expression unaryFunctionDifferential() override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; // Evaluation Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { @@ -56,7 +56,7 @@ class Cosine final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/derivative.h b/poincare/include/poincare/derivative.h index f75e7268382..9e4b92fa2b1 100644 --- a/poincare/include/poincare/derivative.h +++ b/poincare/include/poincare/derivative.h @@ -52,7 +52,7 @@ class Derivative final : public ParameteredExpression { 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); + static void DerivateUnaryFunction(Expression function, Expression symbol, Expression symbolValue, ExpressionNode::ReductionContext reductionContext); Expression shallowReduce(ExpressionNode::ReductionContext context); }; diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 84a7632575b..4818643e6c9 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -400,7 +400,7 @@ class Expression : public TreeHandle { * 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() { return node()->unaryFunctionDifferential(); } + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return node()->unaryFunctionDifferential(reductionContext); } private: static constexpr int k_maxSymbolReplacementsCount = 10; diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 6ec6c1d0529..305c0099e4d 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -294,7 +294,7 @@ class ExpressionNode : public TreeNode { * the reduction context. */ /*!*/ virtual Expression shallowBeautify(ReductionContext * reductionContext); /*!*/ virtual bool derivate(ReductionContext, Expression symbol, Expression symbolValue); - virtual Expression unaryFunctionDifferential(); + 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/hyperbolic_cosine.h b/poincare/include/poincare/hyperbolic_cosine.h index c33368fd068..569e363619c 100644 --- a/poincare/include/poincare/hyperbolic_cosine.h +++ b/poincare/include/poincare/hyperbolic_cosine.h @@ -27,7 +27,7 @@ class HyperbolicCosineNode final : public HyperbolicTrigonometricFunctionNode { 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() 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, ApproximationContext approximationContext) const override { @@ -44,7 +44,7 @@ class HyperbolicCosine final : public HyperbolicTrigonometricFunction { static HyperbolicCosine Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + 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 408eae86d90..8fb1cea7cb3 100644 --- a/poincare/include/poincare/hyperbolic_sine.h +++ b/poincare/include/poincare/hyperbolic_sine.h @@ -25,7 +25,7 @@ class HyperbolicSineNode final : public HyperbolicTrigonometricFunctionNode { 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() 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, ApproximationContext approximationContext) const override { @@ -42,7 +42,7 @@ class HyperbolicSine final : public HyperbolicTrigonometricFunction { static HyperbolicSine Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + 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 4b1fa04bc7a..f3bdf3540d5 100644 --- a/poincare/include/poincare/hyperbolic_tangent.h +++ b/poincare/include/poincare/hyperbolic_tangent.h @@ -25,7 +25,7 @@ class HyperbolicTangentNode final : public HyperbolicTrigonometricFunctionNode { 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() 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, ApproximationContext approximationContext) const override { @@ -42,7 +42,7 @@ class HyperbolicTangent final : public HyperbolicTrigonometricFunction { static HyperbolicTangent Builder(Expression child) { return TreeHandle::FixedArityBuilder({child}); } bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("tanh", 1, &UntypedBuilderOneChild); }; diff --git a/poincare/include/poincare/logarithm.h b/poincare/include/poincare/logarithm.h index 1c9465c14c6..369bd41faea 100644 --- a/poincare/include/poincare/logarithm.h +++ b/poincare/include/poincare/logarithm.h @@ -34,7 +34,7 @@ class LogarithmNode final : public ExpressionNode { LayoutShape rightLayoutShape() const override { return LayoutShape::BoundaryPunctuation; } // Derivation bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; - Expression unaryFunctionDifferential() 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 @@ -57,7 +57,7 @@ class Logarithm final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(); bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); private: void deepReduceChildren(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/sine.h b/poincare/include/poincare/sine.h index 1858479e83c..b10e3113ae4 100644 --- a/poincare/include/poincare/sine.h +++ b/poincare/include/poincare/sine.h @@ -36,7 +36,7 @@ class SineNode final : public ExpressionNode { // Derivation bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; - Expression unaryFunctionDifferential() override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; // Evaluation Evaluation approximate(SinglePrecision p, ApproximationContext approximationContext) const override { @@ -57,7 +57,7 @@ class Sine final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/include/poincare/tangent.h b/poincare/include/poincare/tangent.h index b83d5171f9f..263bc46bf38 100644 --- a/poincare/include/poincare/tangent.h +++ b/poincare/include/poincare/tangent.h @@ -33,7 +33,7 @@ class TangentNode final : public ExpressionNode { // Derivation bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override; - Expression unaryFunctionDifferential() override; + Expression unaryFunctionDifferential(ReductionContext reductionContext) override; // Evaluation template static Complex computeOnComplex(const std::complex c, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit = Preferences::AngleUnit::Radian); @@ -55,7 +55,7 @@ class Tangent final : public Expression { Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); bool derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue); - Expression unaryFunctionDifferential(); + Expression unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext); }; } diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index 16a47ef1f8b..ea609ddde82 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -38,8 +38,8 @@ bool CosineNode::derivate(ReductionContext reductionContext, Expression symbol, return Cosine(this).derivate(reductionContext, symbol, symbolValue); } -Expression CosineNode::unaryFunctionDifferential() { - return Cosine(this).unaryFunctionDifferential(); +Expression CosineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Cosine(this).unaryFunctionDifferential(reductionContext); } Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { @@ -55,11 +55,11 @@ Expression Cosine::shallowReduce(ExpressionNode::ReductionContext reductionConte bool Cosine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression Cosine::unaryFunctionDifferential() { +Expression Cosine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return Multiplication::Builder(Rational::Builder(-1), Sine::Builder(childAtIndex(0).clone())); } diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index 536c71723d5..d4159e606b8 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -187,8 +187,8 @@ Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionC return derivand; } -void Derivative::DerivateUnaryFunction(Expression function, Expression symbol, Expression symbolValue) { - Expression df = function.unaryFunctionDifferential(); +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)); diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index a58a127754e..08149a1959c 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -119,7 +119,7 @@ bool ExpressionNode::derivate(ReductionContext reductionContext, Expression symb return Expression(this).defaultDidDerivate(); } -Expression ExpressionNode::unaryFunctionDifferential() { +Expression ExpressionNode::unaryFunctionDifferential(ReductionContext reductionContext) { return Expression(this).defaultUnaryFunctionDifferential(); } diff --git a/poincare/src/hyperbolic_cosine.cpp b/poincare/src/hyperbolic_cosine.cpp index cba369cbe76..abbf1797f71 100644 --- a/poincare/src/hyperbolic_cosine.cpp +++ b/poincare/src/hyperbolic_cosine.cpp @@ -25,17 +25,17 @@ bool HyperbolicCosineNode::derivate(ReductionContext reductionContext, Expressio return HyperbolicCosine(this).derivate(reductionContext, symbol, symbolValue); } -Expression HyperbolicCosineNode::unaryFunctionDifferential() { - return HyperbolicCosine(this).unaryFunctionDifferential(); +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); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression HyperbolicCosine::unaryFunctionDifferential() { +Expression HyperbolicCosine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return HyperbolicSine::Builder(childAtIndex(0).clone()); } diff --git a/poincare/src/hyperbolic_sine.cpp b/poincare/src/hyperbolic_sine.cpp index e1e73c6a42d..15b47767428 100644 --- a/poincare/src/hyperbolic_sine.cpp +++ b/poincare/src/hyperbolic_sine.cpp @@ -25,16 +25,16 @@ bool HyperbolicSineNode::derivate(ReductionContext reductionContext, Expression return HyperbolicSine(this).derivate(reductionContext, symbol, symbolValue); } -Expression HyperbolicSineNode::unaryFunctionDifferential() { - return HyperbolicSine(this).unaryFunctionDifferential(); +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); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression HyperbolicSine::unaryFunctionDifferential() { +Expression HyperbolicSine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return HyperbolicCosine::Builder(childAtIndex(0).clone()); } diff --git a/poincare/src/hyperbolic_tangent.cpp b/poincare/src/hyperbolic_tangent.cpp index c27d15a686d..c8dff63cf80 100644 --- a/poincare/src/hyperbolic_tangent.cpp +++ b/poincare/src/hyperbolic_tangent.cpp @@ -26,16 +26,16 @@ bool HyperbolicTangentNode::derivate(ReductionContext reductionContext, Expressi return HyperbolicTangent(this).derivate(reductionContext, symbol, symbolValue); } -Expression HyperbolicTangentNode::unaryFunctionDifferential() { - return HyperbolicTangent(this).unaryFunctionDifferential(); +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); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression HyperbolicTangent::unaryFunctionDifferential() { +Expression HyperbolicTangent::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return Power::Builder(HyperbolicCosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2)); } diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 7f45f9c1b76..c2cd788c388 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -76,8 +76,8 @@ bool LogarithmNode<2>::derivate(ReductionContext reductionContext, Expression sy } template <> -Expression LogarithmNode<2>::unaryFunctionDifferential() { - return Logarithm(this).unaryFunctionDifferential(); +Expression LogarithmNode<2>::unaryFunctionDifferential(ReductionContext reductionContext) { + return Logarithm(this).unaryFunctionDifferential(reductionContext); } /* Those two methods will not be called, as CommonLogarithm disappears in @@ -89,7 +89,7 @@ bool LogarithmNode<1>::derivate(ReductionContext reductionContext, Expression sy } template <> -Expression LogarithmNode<1>::unaryFunctionDifferential() { +Expression LogarithmNode<1>::unaryFunctionDifferential(ReductionContext reductionContext) { assert(false); return Expression(); } @@ -339,11 +339,11 @@ bool Logarithm::derivate(ExpressionNode::ReductionContext reductionContext, Expr if (childAtIndex(1).polynomialDegree(reductionContext.context(), symbol.convert().name()) != 0) { return false; } - Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression Logarithm::unaryFunctionDifferential() { +Expression Logarithm::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { /* log(x, b)` = (ln(x)/ln(b))` * = 1 / (x * ln(b)) * diff --git a/poincare/src/sine.cpp b/poincare/src/sine.cpp index 17b77a1335f..1e567449b97 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -36,8 +36,8 @@ bool SineNode::derivate(ReductionContext reductionContext, Expression symbol, Ex return Sine(this).derivate(reductionContext, symbol, symbolValue); } -Expression SineNode::unaryFunctionDifferential() { - return Sine(this).unaryFunctionDifferential(); +Expression SineNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Sine(this).unaryFunctionDifferential(reductionContext); } Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext) { @@ -52,11 +52,11 @@ Expression Sine::shallowReduce(ExpressionNode::ReductionContext reductionContext } bool Sine::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression Sine::unaryFunctionDifferential() { +Expression Sine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return Cosine::Builder(childAtIndex(0).clone()); } diff --git a/poincare/src/tangent.cpp b/poincare/src/tangent.cpp index d1d1578053c..8bcd0ed3c6b 100644 --- a/poincare/src/tangent.cpp +++ b/poincare/src/tangent.cpp @@ -40,8 +40,8 @@ bool TangentNode::derivate(ReductionContext reductionContext, Expression symbol, return Tangent(this).derivate(reductionContext, symbol, symbolValue); } -Expression TangentNode::unaryFunctionDifferential() { - return Tangent(this).unaryFunctionDifferential(); +Expression TangentNode::unaryFunctionDifferential(ReductionContext reductionContext) { + return Tangent(this).unaryFunctionDifferential(reductionContext); } Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionContext) { @@ -67,11 +67,11 @@ Expression Tangent::shallowReduce(ExpressionNode::ReductionContext reductionCont } bool Tangent::derivate(ExpressionNode::ReductionContext reductionContext, Expression symbol, Expression symbolValue) { - Derivative::DerivateUnaryFunction(*this, symbol, symbolValue); + Derivative::DerivateUnaryFunction(*this, symbol, symbolValue, reductionContext); return true; } -Expression Tangent::unaryFunctionDifferential() { +Expression Tangent::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { return Power::Builder(Cosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2)); } From 64208a7f3c77b45e7b9ea826eac2c1173e257990 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 17:09:05 +0100 Subject: [PATCH 477/560] [poincare/trigonometry] Method UnitConversionFactor Change-Id: I4ef3e4404071a305354b572e15e5199040f9bf48 --- poincare/include/poincare/trigonometry.h | 1 + poincare/src/trigonometry.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/poincare/include/poincare/trigonometry.h b/poincare/include/poincare/trigonometry.h index 86e49da2a69..8c038e24e69 100644 --- a/poincare/include/poincare/trigonometry.h +++ b/poincare/include/poincare/trigonometry.h @@ -16,6 +16,7 @@ class Trigonometry final { static bool isDirectTrigonometryFunction(const Expression & e); static bool isInverseTrigonometryFunction(const Expression & e); static bool AreInverseFunctions(const Expression & directFunction, const Expression & inverseFunction); + 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/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index 51ccdce306b..a9c849c33c4 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,13 @@ bool Trigonometry::AreInverseFunctions(const Expression & directFunction, const return inverseFunction.type() == correspondingType; } +Expression Trigonometry::UnitConversionFactor(Preferences::AngleUnit fromUnit, Preferences::AngleUnit toUnit) { + if (fromUnit == toUnit) { + 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); From a113801ec2a1080325b9a283445f2945a5bffa11 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 24 Nov 2020 15:33:29 +0100 Subject: [PATCH 478/560] [poincare/derivative] Fix derivative of trigonometric functions The classic differentiation forumals for trigonometric functions assume a variable in radians. A multiplicative constant must be added when another unit is used. Change-Id: Iec428acd7d93e415fddb184300437ae09d1d997c --- poincare/src/cosine.cpp | 2 +- poincare/src/sine.cpp | 3 ++- poincare/src/tangent.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/poincare/src/cosine.cpp b/poincare/src/cosine.cpp index ea609ddde82..6d64cf0c8bc 100644 --- a/poincare/src/cosine.cpp +++ b/poincare/src/cosine.cpp @@ -60,7 +60,7 @@ bool Cosine::derivate(ExpressionNode::ReductionContext reductionContext, Express } Expression Cosine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { - return Multiplication::Builder(Rational::Builder(-1), Sine::Builder(childAtIndex(0).clone())); + return Multiplication::Builder(Rational::Builder(-1), Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Sine::Builder(childAtIndex(0).clone())); } } diff --git a/poincare/src/sine.cpp b/poincare/src/sine.cpp index 1e567449b97..96e9051abee 100644 --- a/poincare/src/sine.cpp +++ b/poincare/src/sine.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -57,7 +58,7 @@ bool Sine::derivate(ExpressionNode::ReductionContext reductionContext, Expressio } Expression Sine::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { - return Cosine::Builder(childAtIndex(0).clone()); + return Multiplication::Builder(Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Cosine::Builder(childAtIndex(0).clone())); } } diff --git a/poincare/src/tangent.cpp b/poincare/src/tangent.cpp index 8bcd0ed3c6b..f62ecd67be7 100644 --- a/poincare/src/tangent.cpp +++ b/poincare/src/tangent.cpp @@ -72,7 +72,7 @@ bool Tangent::derivate(ExpressionNode::ReductionContext reductionContext, Expres } Expression Tangent::unaryFunctionDifferential(ExpressionNode::ReductionContext reductionContext) { - return Power::Builder(Cosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2)); + return Multiplication::Builder(Trigonometry::UnitConversionFactor(reductionContext.angleUnit(), Preferences::AngleUnit::Radian), Power::Builder(Cosine::Builder(childAtIndex(0).clone()), Rational::Builder(-2))); } } From 1f0a18f94cb5da1295a6736bc679005cc3a6d033 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 10:07:23 +0100 Subject: [PATCH 479/560] [poincare/derivative] Update tests for trigonometric derivatives --- poincare/test/derivative.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index f64b5b30101..1621c893b97 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -21,8 +21,11 @@ QUIZ_CASE(poincare_derivative_formal) { 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"); From 1a47207beec53803fed992dbc4dfda249dc7851c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 4 Dec 2020 11:38:46 +0100 Subject: [PATCH 480/560] [poincare/trigonometry] Add comments --- poincare/include/poincare/trigonometry.h | 2 ++ poincare/src/trigonometry.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/poincare/include/poincare/trigonometry.h b/poincare/include/poincare/trigonometry.h index 8c038e24e69..424f1e80fe4 100644 --- a/poincare/include/poincare/trigonometry.h +++ b/poincare/include/poincare/trigonometry.h @@ -16,6 +16,8 @@ class Trigonometry final { 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); diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index a9c849c33c4..bdaa0a1652c 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -90,6 +90,7 @@ bool Trigonometry::AreInverseFunctions(const Expression & directFunction, const 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)); From 3f6e4444a5527c618a92e71000f756f750e6d748 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 15:33:59 +0100 Subject: [PATCH 481/560] [poincare/derivative] Reduce before replacing symbol The general formula for deriving a power makes use of the logarithm, which often disappears at simplification. However, replacing the symbol before simplifying can lead to applying an invalid argument to the logarithm, making the whole expression invalid. e.g. diff(1/x,x,-2) If x is replaced by -2 before reducing the power derivative, ln(-2) will reduce to Unreal, as will the rest of the expression. --- poincare/src/derivative.cpp | 8 ++++++-- poincare/test/derivative.cpp | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/poincare/src/derivative.cpp b/poincare/src/derivative.cpp index d4159e606b8..0d139fd730c 100644 --- a/poincare/src/derivative.cpp +++ b/poincare/src/derivative.cpp @@ -176,8 +176,12 @@ Expression Derivative::shallowReduce(ExpressionNode::ReductionContext reductionC return *this; } /* Updates the value of derivand, because derivate may call - * replaceWithInplace on it */ - derivand = childAtIndex(0); + * 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. */ diff --git a/poincare/test/derivative.cpp b/poincare/test/derivative.cpp index 1621c893b97..89e9a1aee69 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -64,4 +64,6 @@ QUIZ_CASE(poincare_derivative_approximation) { 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"); } From 76eb155ed5ca551664465a607fb45074a69cff0b Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Thu, 3 Dec 2020 12:20:53 +0100 Subject: [PATCH 482/560] [NL] translation fixes --- apps/graph/base.nl.i18n | 4 ++-- apps/regression/base.nl.i18n | 6 +++--- apps/solver/base.nl.i18n | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n index 6142be27919..5dbd3a0ed41 100644 --- a/apps/graph/base.nl.i18n +++ b/apps/graph/base.nl.i18n @@ -14,7 +14,7 @@ FunctionDomain = "Plotbereik" FunctionColor = "Functiekleur" NoFunction = "Geen functie" NoActivatedFunction = "Geen functie is ingeschakeld" -PlotOptions = "Plot opties" +PlotOptions = "Plotopties" Compute = "Bereken" Zeros = "Nulpunten" Tangent = "Raaklijn" @@ -30,4 +30,4 @@ 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/regression/base.nl.i18n b/apps/regression/base.nl.i18n index b3176cd5aeb..1aaa848e8fe 100644 --- a/apps/regression/base.nl.i18n +++ b/apps/regression/base.nl.i18n @@ -10,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/solver/base.nl.i18n b/apps/solver/base.nl.i18n index adcfadc87da..89e9d3d4da3 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" From 7ae03975ccdc2492d8727f7ef33ad97ca4360a5d Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 14:41:00 +0100 Subject: [PATCH 483/560] [poincare/derivative] Derivate special numbers Derivate the numbers Undefined, Unreal, Infinity symbolically : Undefined -> Undefined Unreal -> Unreal Infinity -> Undefined --- poincare/include/poincare/infinity.h | 6 ++++++ poincare/include/poincare/undefined.h | 4 ++-- poincare/include/poincare/unreal.h | 2 +- poincare/src/infinity.cpp | 9 +++++++++ poincare/test/derivative.cpp | 3 +++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/poincare/include/poincare/infinity.h b/poincare/include/poincare/infinity.h index b4d6aa02c5d..03740eead17 100644 --- a/poincare/include/poincare/infinity.h +++ b/poincare/include/poincare/infinity.h @@ -37,6 +37,11 @@ class InfinityNode final : public NumberNode { // 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; } @@ -56,6 +61,7 @@ class Infinity final : public Number { 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/undefined.h b/poincare/include/poincare/undefined.h index 9a09fe95f64..7dfb094f35c 100644 --- a/poincare/include/poincare/undefined.h +++ b/poincare/include/poincare/undefined.h @@ -30,8 +30,8 @@ class UndefinedNode : public NumberNode { } /* Derivation - * Overrides NumberNode's derivate to revert to a non-derivable state */ - bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + * 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; diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index 7e4290e3eab..d74d253b1da 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -28,7 +28,7 @@ class UnrealNode final : public UndefinedNode { } /* Derivation - * Overrides NumberNode's derivate to revert to a non-derivable state */ + * Unlike Numbers that derivate to 0, Unreal derivates to Unreal. */ bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } // Layout diff --git a/poincare/src/infinity.cpp b/poincare/src/infinity.cpp index 8a537a68e55..312e7433450 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/test/derivative.cpp b/poincare/test/derivative.cpp index 89e9a1aee69..5cde15597f6 100644 --- a/poincare/test/derivative.cpp +++ b/poincare/test/derivative.cpp @@ -8,6 +8,9 @@ void assert_reduces_to_formal_expression(const char * expression, const char * r } 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"); From bd302bb67bdca1f54cff4e32ec484dfc0ab299f1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 7 Dec 2020 14:41:38 +0100 Subject: [PATCH 484/560] [poincare/unreal] Type in derivate method Unreal's derivate method returns true, to signify that it is unchanged by the derivation operation. This implementation mostly exists for documentation, as an Unreal derivand will be handled by the defaultShallowReduce method. --- poincare/include/poincare/unreal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/include/poincare/unreal.h b/poincare/include/poincare/unreal.h index d74d253b1da..d53facec441 100644 --- a/poincare/include/poincare/unreal.h +++ b/poincare/include/poincare/unreal.h @@ -29,7 +29,7 @@ class UnrealNode final : public UndefinedNode { /* Derivation * Unlike Numbers that derivate to 0, Unreal derivates to Unreal. */ - bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return false; } + bool derivate(ReductionContext reductionContext, Expression symbol, Expression symbolValue) override { return true; } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; From 6e9195fafa3eb36dd81f3b96110fd2a2e10ae0c8 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 13 Nov 2020 11:49:12 +0100 Subject: [PATCH 485/560] [shared] Add forceChangeY parameter to normalize() Change-Id: Iff5417cd765f3fd09825b81cb41f883530b39233 --- apps/sequence/graph/curve_view_range.cpp | 4 ++-- apps/sequence/graph/curve_view_range.h | 2 +- apps/shared/interactive_curve_view_range.cpp | 8 ++++++-- apps/shared/interactive_curve_view_range.h | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index c82e766c8ff..983bda7b34b 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -15,8 +15,8 @@ CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) : MemoizedCurveViewRange::protectedSetXMin(-k_displayLeftMarginRatio * xMax(), k_lowerMaxFloat, k_upperMaxFloat); } -void CurveViewRange::normalize() { - Shared::InteractiveCurveViewRange::normalize(); +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 diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index 5611b6056b1..93561f3a9d2 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -8,7 +8,7 @@ namespace Sequence { class CurveViewRange : public Shared::InteractiveCurveViewRange { public: CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr); - void normalize() override; + void normalize(bool forceChangeY = false) override; private: constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index f8f82d12505..e91a681dda1 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -131,7 +131,7 @@ void InteractiveCurveViewRange::panWithVector(float x, float y) { MemoizedCurveViewRange::protectedSetYMin(yMin() + y, k_lowerMaxFloat, k_upperMaxFloat); } -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. */ @@ -148,7 +148,11 @@ void InteractiveCurveViewRange::normalize() { const float newYHalfRange = NormalizedYHalfRange(unit); float normalizedYXRatio = newYHalfRange/newXHalfRange; - Zoom::SetToRatio(normalizedYXRatio, &newXMin, &newXMax, &newYMin, &newYMax); + /* 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); m_xRange.setMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index eec43098932..1c89c9e2c54 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -52,7 +52,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { // Window void zoom(float ratio, float x, float y); void panWithVector(float x, float y); - virtual void normalize(); + 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, float pixelWidth); From ff220b7103814c0c8ff304da8e5b6ed7b7d52179 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Fri, 13 Nov 2020 12:41:09 +0100 Subject: [PATCH 486/560] [graph] Preserve default X range When computing the automatic zoom, if the X axis has integer bounds (most likely because the range has been built to be orthonormal), do not add margins to the X axis, so that it keeps user friendly values. Change-Id: I49d99b79c68fbd8a49e5c2521b250c40aad75d48 --- apps/shared/interactive_curve_view_range.cpp | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index e91a681dda1..6258ff5c6ef 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -182,19 +182,29 @@ void InteractiveCurveViewRange::setDefault() { // Compute the interesting range m_delegate->interestingRanges(this); bool revertToNormalized = isOrthonormal(k_orthonormalTolerance); + /* If the horizontal bounds are integers, they are preset values and should + * not be changed. */ + bool isDefaultRange = (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); // Add margins, then round limits. - float xRange = xMax() - xMin(); - float yRange = yMax() - yMin(); - m_xRange.setMin(roundLimit(m_delegate->addMargin(xMin(), xRange, false, true), xRange, true), k_lowerMaxFloat, k_upperMaxFloat); + 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_xRange.setMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); // Use MemoizedCurveViewRange::protectedSetXMax to update xGridUnit - MemoizedCurveViewRange::protectedSetXMax(roundLimit(m_delegate->addMargin(xMax(), xRange, false, false), xRange, 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); if (m_delegate->defaultRangeIsNormalized() || revertToNormalized) { - // Normalize the axes, so that a polar circle is displayed as a circle - normalize(); + /* 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); From a8858023ba3e66d7393862eeb8fc53c7f32a73ba Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 17 Nov 2020 12:17:22 +0100 Subject: [PATCH 487/560] [shared] Remove const qualifiers from interestingRanges Change-Id: I794aa144af9dc9eab3090eea7fd54a7cdac2fd68 --- apps/shared/function_graph_controller.cpp | 2 +- apps/shared/function_graph_controller.h | 2 +- apps/shared/interactive_curve_view_range_delegate.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 7004faf0cb3..9c1887decf0 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -153,7 +153,7 @@ int FunctionGraphController::numberOfCurves() const { return functionStore()->numberOfActiveFunctions(); } -void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const { +void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) { Poincare::Context * context = textFieldDelegateApp()->localContext(); constexpr int maxLength = 10; float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength]; diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 2f7c7472380..c78086b9616 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -17,7 +17,7 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu void didBecomeFirstResponder() override; void viewWillAppear() override; - void interestingRanges(Shared::InteractiveCurveViewRange * range) const override; + void interestingRanges(Shared::InteractiveCurveViewRange * range) override; protected: float cursorTopMarginRatio() override { return 0.068f; } diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index 6dfb2c9a0fd..d09961b33a6 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -12,7 +12,7 @@ class InteractiveCurveViewRangeDelegate { static constexpr float k_defaultXHalfRange = 10.0f; virtual float interestingXMin() const { return -k_defaultXHalfRange; } virtual bool defaultRangeIsNormalized() const { return false; } - virtual void interestingRanges(InteractiveCurveViewRange * range) const { assert(false); } + virtual void interestingRanges(InteractiveCurveViewRange * range) { assert(false); } virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; virtual void updateZoomButtons() = 0; }; From c89a7bc4960d6a66343811f8daf3e5f93218e47c Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 17 Nov 2020 12:18:38 +0100 Subject: [PATCH 488/560] [graph] Take margins into account for normalization When building an orthonormal range for the automatic zoom, we provide a ratio for the screen without the margins. This way, adding the margins will make the graph orthonormal, and the banner cannot cover the function. Change-Id: If3a3f799d4e7e3e81ab77c6b418d70b734a6fbca --- apps/shared/continuous_function.cpp | 4 ++-- apps/shared/continuous_function.h | 2 +- apps/shared/function.h | 2 +- apps/shared/function_graph_controller.cpp | 4 +++- apps/shared/interactive_curve_view_range.cpp | 3 +-- apps/shared/sequence.cpp | 2 +- apps/shared/sequence.h | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 8006980377e..74a718d6968 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -261,7 +261,7 @@ void ContinuousFunction::setTMax(float tMax) { setCache(nullptr); } -void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const { +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); @@ -291,7 +291,7 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi } /* Try to display an orthonormal range. */ - Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this); + Zoom::RangeWithRatioForDisplay(evaluation, targetRatio, xMin, xMax, yMin, yMax, context, this); if (std::isfinite(*xMin) && std::isfinite(*xMax) && std::isfinite(*yMin) && std::isfinite(*yMax)) { return; } diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 3d9aa8dd0db..ac4826714e5 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -70,7 +70,7 @@ class ContinuousFunction : public Function { 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, Poincare::Context * context) const override; + 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; diff --git a/apps/shared/function.h b/apps/shared/function.h index 3ef12a822d8..a5daa9c5917 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -55,7 +55,7 @@ class Function : public ExpressionModelHandle { 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, Poincare::Context * context) const = 0; + 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 diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 9c1887decf0..d99098cad00 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -159,9 +159,11 @@ void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * rang float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength]; int length = functionStore()->numberOfActiveFunctions(); + float ratio = InteractiveCurveViewRange::NormalYXRatio() / (1 + cursorTopMarginRatio() + cursorBottomMarginRatio()); + for (int i = 0; i < length; i++) { ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, context); + f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, ratio, context); } float xMin, xMax, yMin, yMax; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 6258ff5c6ef..17fd8ede7b3 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -181,7 +181,6 @@ void InteractiveCurveViewRange::setDefault() { // Compute the interesting range m_delegate->interestingRanges(this); - bool revertToNormalized = isOrthonormal(k_orthonormalTolerance); /* If the horizontal bounds are integers, they are preset values and should * not be changed. */ bool isDefaultRange = (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); @@ -200,7 +199,7 @@ void InteractiveCurveViewRange::setDefault() { 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); - if (m_delegate->defaultRangeIsNormalized() || revertToNormalized) { + if (m_delegate->defaultRangeIsNormalized() || isOrthonormal(k_orthonormalTolerance)) { /* 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. */ diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index 9f47baa1a83..a962e5e22a1 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -302,7 +302,7 @@ Expression Sequence::sumBetweenBounds(double start, double end, Poincare::Contex return Float::Builder(result); } -void Sequence::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const { +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()); }; diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index b0c3f1f5dfb..b20eb644bb9 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -75,7 +75,7 @@ friend class SequenceStore; constexpr static int k_initialRankNumberOfDigits = 3; // m_initialRank is capped by 999 //Range - void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override; + 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; From b1da6031c6d53dd3f800b14da6eacd5712e40b95 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 23 Nov 2020 14:20:13 +0100 Subject: [PATCH 489/560] [shared] Do not compute range for convoluted functions Evaluating a function containing a sequence, an integral or a derivative, is time consuming. Computing a range requires a plethora of evaluations, as such ranges for these functions cannot be evaluated in a timely fashion. Change-Id: I088a0e896dbc26e6563291cafdfe9ceba36dd5d0 --- apps/shared/continuous_function.cpp | 89 ++++++++++++++++------------- apps/shared/continuous_function.h | 1 + 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 74a718d6968..4a22f028e63 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -269,51 +269,54 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi return; } - 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); + 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; + } - evaluation = [](float x, Context * context, const void * auxiliary) { - return static_cast(auxiliary)->evaluateXYAtParameter(x, context).x2(); - }; + /* 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; + } - if (fullyComputed) { - /* The function has points of interest. */ + /* 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); - 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; - } + 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'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 is probably undefined. */ + /* 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; @@ -415,6 +418,14 @@ Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, 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; diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index ac4826714e5..e5f5a57c8e7 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -93,6 +93,7 @@ class ContinuousFunction : public Function { 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 From 7f63daa28ecafad91135b3a7c753d44ebcdebac9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 12:14:43 +0100 Subject: [PATCH 490/560] [poincare/zoom] Change weight function in RangeWithRatioForDisplay Change-Id: I21520d8220083b40baa2e4928632632f2b73cb9b --- poincare/src/zoom.cpp | 5 +++-- poincare/test/zoom.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 5a848d6f8b1..eabf47192e2 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -266,8 +266,9 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f RefinedYRangeForDisplay(evaluation, center - xRange, center + xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); float grade = std::fabs(std::log(currentRatio / yxRatio)); - /* When in doubt, favor ranges between [-1, 1] and [-10, 10] */ - grade += std::fabs(std::log(xRange / 10.f) * std::log(xRange)) * rangeMagnitudeWeight; + /* The weigth function favors the [-5, 5] range, which will expand into + * the [-10, 10] range */ + grade += std::fabs(std::log(xRange / k_largeUnitMantissa)) * rangeMagnitudeWeight; if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { bestGrade = grade; bestUnit = unit; diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index c5b157f2f03..f8fde77597d 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -143,7 +143,7 @@ void assert_orthonormal_range_is(const char * definition, float targetXMin, floa QUIZ_CASE(poincare_zoom_range_with_ratio) { assert_orthonormal_range_is("1", NAN, NAN, NAN, NAN); - assert_orthonormal_range_is("x", -5, 5, -2.21179414, 2.21179414); + assert_orthonormal_range_is("x", -20, 20, -8.84717655, 8.84717655); assert_orthonormal_range_is("x^2", -2, 2, -0.172234654, 1.59720063); assert_orthonormal_range_is("x^3", -5, 5, -2.21179414, 2.21179414); assert_orthonormal_range_is("ℯ^x", -5, 5, -0.852653265, 3.57093501); From 1e7babadb8a026e576f5497dce20cae5b79b7197 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 14:56:00 +0100 Subject: [PATCH 491/560] [solver] Change angle unit before test A test in equation_solve relies on the angle unit being set to Degree, but doesn't actually set it. Changing the angle unit to another one in a test prior would break this test. Change-Id: I6785b087f171d46226d484ebaa3ebdc9e791cedc --- apps/solver/test/equation_store.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index 2b74241338e..608f81f6fe9 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -78,6 +78,7 @@ 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"); From a9aeae94dfd1a1b9df7ebb4689b07c587a1728f1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 15:01:08 +0100 Subject: [PATCH 492/560] [shared] Create helper functions for ranges These functions are used to test the full algorithm used to compute ranges. Change-Id: I48069e245aa6e879f66aecc29709fc6f992f5220 --- apps/shared/function_graph_controller.cpp | 21 +------ .../interactive_curve_view_controller.cpp | 27 +-------- .../interactive_curve_view_range_delegate.cpp | 58 +++++++++++++++++++ .../interactive_curve_view_range_delegate.h | 6 ++ 4 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 apps/shared/interactive_curve_view_range_delegate.cpp diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index d99098cad00..1146873716b 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -154,27 +154,8 @@ int FunctionGraphController::numberOfCurves() const { } void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) { - Poincare::Context * context = textFieldDelegateApp()->localContext(); - constexpr int maxLength = 10; - float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength]; - int length = functionStore()->numberOfActiveFunctions(); - float ratio = InteractiveCurveViewRange::NormalYXRatio() / (1 + cursorTopMarginRatio() + cursorBottomMarginRatio()); - - for (int i = 0; i < length; i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, ratio, context); - } - - 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); + InterestingRangesHelper(range, textFieldDelegateApp()->localContext(), functionStore(), ratio); } } diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 49b2850ef14..4373fed6631 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -41,32 +41,7 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren } 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() : cursorRightMarginRatio(); - float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : 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 AddMarginHelper(y, range, isVertical, isMin, cursorTopMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), cursorRightMarginRatio()); } void InteractiveCurveViewController::updateZoomButtons() { diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp new file mode 100644 index 00000000000..b82eb80bf36 --- /dev/null +++ b/apps/shared/interactive_curve_view_range_delegate.cpp @@ -0,0 +1,58 @@ +#include "interactive_curve_view_range_delegate.h" +#include "interactive_curve_view_range.h" +#include "function_store.h" +#include + +namespace Shared { + +void InteractiveCurveViewRangeDelegate::InterestingRangesHelper(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); + } + + 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::AddMarginHelper(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 d09961b33a6..8a163feaa90 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -1,15 +1,21 @@ #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: static constexpr float k_defaultXHalfRange = 10.0f; + + static void InterestingRangesHelper(InteractiveCurveViewRange * range, Poincare::Context * context, FunctionStore * functionStore, float targetRatio); + static float AddMarginHelper(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); } From f3b770cb29c0e6f6591527974f683123df5b3711 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 26 Nov 2020 15:14:08 +0100 Subject: [PATCH 493/560] [graph] Add tests for the full range computation Change-Id: I040a36c035ec75ee950f1998667b70a417dea2f3 --- apps/graph/Makefile | 1 + apps/graph/test/helper.h | 4 ++ apps/graph/test/ranges.cpp | 124 +++++++++++++++++++++++++++++++++++++ apps/shared/Makefile | 1 + 4 files changed, 130 insertions(+) create mode 100644 apps/graph/test/ranges.cpp diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 2c87734dcb9..cc8bc34ad2d 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -42,6 +42,7 @@ 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/test/helper.h b/apps/graph/test/helper.h index 2f5e9fb5861..69fe91edb64 100644 --- a/apps/graph/test/helper.h +++ b/apps/graph/test/helper.h @@ -12,6 +12,10 @@ constexpr ContinuousFunction::PlotType Cartesian = ContinuousFunction::PlotType: constexpr ContinuousFunction::PlotType Polar = ContinuousFunction::PlotType::Polar; constexpr ContinuousFunction::PlotType Parametric = ContinuousFunction::PlotType::Parametric; +constexpr Preferences::AngleUnit Radian = Preferences::AngleUnit::Radian; +constexpr Preferences::AngleUnit Degree = Preferences::AngleUnit::Degree; +constexpr Preferences::AngleUnit Gradian = Preferences::AngleUnit::Gradian; + ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context); } diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp new file mode 100644 index 00000000000..3326ea77e02 --- /dev/null +++ b/apps/graph/test/ranges.cpp @@ -0,0 +1,124 @@ +#include +#include "helper.h" + +using namespace Poincare; +using namespace Shared; + +namespace Graph { + +class AdHocGraphController : public InteractiveCurveViewRangeDelegate { +public: + 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 { InterestingRangesHelper(range, context(), functionStore(), Ratio()); } + float addMargin(float x, float range, bool isVertical, bool isMin) override { return AddMarginHelper(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 std::fabs(a - b) <= tolerance * std::fabs(a + b); +} + +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", -20, 20, -11.124999, 10.124999); + assert_best_cartesian_range_is("x+1", -23, 21, -12.187499, 11.187499); + assert_best_cartesian_range_is("-x+5", -17.0882378, 28.0882378, -13, 11); + assert_best_cartesian_range_is("x/2+2", -15, 7, -6.19374943, 5.49374962); + + + assert_best_cartesian_range_is("x^2", -2, 2, -0.412499845, 1.71249986); + assert_best_cartesian_range_is("x^3", -5, 5, -2.80624962, 2.5062499); + assert_best_cartesian_range_is("-2x^6", -2, 2, -1.51249993, 0.612499833); + assert_best_cartesian_range_is("3x^2+x+10", -1.30000007, 1, 9.58406258, 10.8059368); + + 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", -5, 5, -1.50624979, 3.80624962); + assert_best_cartesian_range_is("ℯ^x+4", -5, 5, 2.59375024, 7.90625); + assert_best_cartesian_range_is("ℯ^(-x)", -5, 5, -1.50624979, 3.80624962); + + 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", -5, 5, -1.50624979, 3.80624962); + 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, -6.52941275, 8.52941322, -3.79999995, 4.20000029); + } + { + const char * definitions[] = {"x/2+2", "-x+5"}; + ContinuousFunction::PlotType types[] = {Cartesian, Cartesian}; + assert_best_range_is(definitions, types, -17.0882378, 28.0882378, -13, 11); + } + { + const char * definitions[] = {"sin(θ)", "cos(θ)"}; + ContinuousFunction::PlotType types[] = {Polar, Polar}; + assert_best_range_is(definitions, types, -1.63235319, 2.13235331, -0.800000011, 1.20000005); + } +} + +} diff --git a/apps/shared/Makefile b/apps/shared/Makefile index bee5a07d620..bd421f508a6 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -12,6 +12,7 @@ app_shared_test_src = $(addprefix apps/shared/,\ function.cpp \ global_context.cpp \ interactive_curve_view_range.cpp \ + interactive_curve_view_range_delegate.cpp \ labeled_curve_view.cpp \ memoized_curve_view_range.cpp \ range_1D.cpp \ From 316d054935620744bae9554498b20f3c244d6b68 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 2 Dec 2020 12:32:29 +0100 Subject: [PATCH 494/560] [sequence] Restore margins on the sequence graph --- apps/sequence/graph/curve_view_range.h | 1 + apps/shared/interactive_curve_view_range.cpp | 2 +- apps/shared/interactive_curve_view_range.h | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index 93561f3a9d2..42cef213c3d 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -10,6 +10,7 @@ class CurveViewRange : public Shared::InteractiveCurveViewRange { CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr); void normalize(bool forceChangeY = false) override; private: + virtual bool defaultRangeCriteria() const override { return false; } constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 17fd8ede7b3..8e2739a42e6 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -183,7 +183,7 @@ void InteractiveCurveViewRange::setDefault() { m_delegate->interestingRanges(this); /* If the horizontal bounds are integers, they are preset values and should * not be changed. */ - bool isDefaultRange = (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); + bool isDefaultRange = defaultRangeCriteria(); // Add margins, then round limits. float newXMin = xMin(), newXMax = xMax(); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 1c89c9e2c54..52718eaac6b 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -78,6 +78,8 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { * 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; } + virtual bool defaultRangeCriteria() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); } + InteractiveCurveViewRangeDelegate * m_delegate; private: float offscreenYAxis() const override { return m_offscreenYAxis; } From 072fdda6b8d2197677437ade2d463f003619c2ae Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 11:58:21 +0100 Subject: [PATCH 495/560] [poincare/zoom] Redraft search of orthonormal range To find the optimal range with a specified ratio, we compute the values of the function on a default range, then center the Y axis to display the maximum number of points of the function. --- poincare/include/poincare/zoom.h | 1 - poincare/src/zoom.cpp | 115 +++++++++++++++---------------- 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 415e83fb6ec..6bd08ca88a9 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -67,7 +67,6 @@ class Zoom { * 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); - static void NextUnit(float * mantissa, float * exponent); }; } diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index eabf47192e2..3ebdfe4128a 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -219,6 +220,7 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float 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. */ @@ -231,8 +233,8 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float float bound = std::exp(sum / pop + 1.f); sampleYMin = std::max(sampleYMin, - bound); sampleYMax = std::min(sampleYMax, bound); - *yMin = std::min(*yMin, sampleYMin); - *yMax = std::max(*yMax, sampleYMax); + *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 @@ -244,41 +246,57 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } } -static float smoothToPowerOfTen(float x) { - return std::pow(10.f, std::round(std::log10(x))); -} - void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { - constexpr float rangeMagnitudeWeight = 0.2f; - constexpr float maxMagnitudeDifference = 2.f; - /* RefinedYRange for display, by default, evaluates the function 80 times, - * and we call it 21 times. As we only need a rough estimate of the range to - * compare it to the others, we save some time by only sampling the function - * 20 times. */ - constexpr int sampleSize = k_sampleSize / 4; - float bestGrade = FLT_MAX, bestUnit, bestMagnitude; - float unit = k_smallUnitMantissa; - float xMagnitude = k_minimalDistance; - float yMinRange = FLT_MAX, yMaxRange = -FLT_MAX; - float center = *xMin == *xMax ? *xMin : 0.f; - while (xMagnitude < k_maximalDistance) { - const float xRange = unit * xMagnitude; - RefinedYRangeForDisplay(evaluation, center - xRange, center + xRange, &yMinRange, &yMaxRange, context, auxiliary, sampleSize); - float currentRatio = (yMaxRange - yMinRange) / (2 * xRange); - float grade = std::fabs(std::log(currentRatio / yxRatio)); - /* The weigth function favors the [-5, 5] range, which will expand into - * the [-10, 10] range */ - grade += std::fabs(std::log(xRange / k_largeUnitMantissa)) * rangeMagnitudeWeight; - if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) { - bestGrade = grade; - bestUnit = unit; - bestMagnitude = xMagnitude; - *yMin = yMinRange; - *yMax = yMaxRange; + 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); + + float yRange = yxRatio * xRange; + int j = 1; + int bestIndex, 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; } - NextUnit(&unit, &xMagnitude); } - if (bestGrade == FLT_MAX) { + + if (bestBreadth < minimalXCoverage * sampleSize + || sample[bestIndex + bestBreadth] - sample[bestIndex] < minimalYCoverage * yRange) { *xMin = NAN; *xMax = NAN; *yMin = NAN; @@ -286,18 +304,9 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f return; } - /* The X bounds are preset, user-friendly values: we do not want them to be - * altered by the normalization. To that end, we use a larger unit for the - * horizontal axis, so that the Y axis is the one that will be extended. */ - float xRange = bestUnit * bestMagnitude; - while ((*yMax - *yMin) / (2 * xRange) > yxRatio) { - NextUnit(&bestUnit, &bestMagnitude); - xRange = bestUnit * bestMagnitude; - } - xRange = bestUnit * smoothToPowerOfTen(bestMagnitude); - *xMin = center - xRange; - *xMax = center + xRange; - SetToRatio(yxRatio, xMin, xMax, yMin, yMax); + 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) { @@ -408,18 +417,4 @@ bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2 return true; } -void Zoom::NextUnit(float * mantissa, float * exponent) { - float mantissaUnits[] = {k_smallUnitMantissa, k_mediumUnitMantissa, k_largeUnitMantissa}; - size_t numberOfUnits = sizeof(mantissaUnits) / sizeof(float); - for (size_t i = 0; i < numberOfUnits; i++) { - if (*mantissa == mantissaUnits[i]) { - *mantissa = mantissaUnits[(i + 1) % numberOfUnits]; - if (*mantissa == mantissaUnits[0]) { - *exponent *= 10.0f; - } - return; - } - } -} - } From 8726bbda3e6b8b387b6a70bc5d88253d9f271504 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 12:38:31 +0100 Subject: [PATCH 496/560] [poincare/zoom] Update tests --- poincare/test/zoom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index f8fde77597d..66d34c463a5 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -143,11 +143,11 @@ void assert_orthonormal_range_is(const char * definition, float targetXMin, floa QUIZ_CASE(poincare_zoom_range_with_ratio) { assert_orthonormal_range_is("1", NAN, NAN, NAN, NAN); - assert_orthonormal_range_is("x", -20, 20, -8.84717655, 8.84717655); - assert_orthonormal_range_is("x^2", -2, 2, -0.172234654, 1.59720063); - assert_orthonormal_range_is("x^3", -5, 5, -2.21179414, 2.21179414); - assert_orthonormal_range_is("ℯ^x", -5, 5, -0.852653265, 3.57093501); - assert_orthonormal_range_is("ℯ^x+4", -5, 5, 3.21590924, 7.63949776); + 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") { From 1f8ab10ae54ec0c1b7a3fae65c4e6c10f7439493 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 12:38:47 +0100 Subject: [PATCH 497/560] [graph] Update tests on automatic zoom --- apps/graph/test/ranges.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index 3326ea77e02..6e3952c41d4 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -68,19 +68,19 @@ QUIZ_CASE(graph_ranges_single_function) { 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("-100", -10, 10, -105.662506, -95.0375061); assert_best_cartesian_range_is("0.01", -10, 10, -5.81249952, 4.81249952); - assert_best_cartesian_range_is("x", -20, 20, -11.124999, 10.124999); - assert_best_cartesian_range_is("x+1", -23, 21, -12.187499, 11.187499); - assert_best_cartesian_range_is("-x+5", -17.0882378, 28.0882378, -13, 11); + 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", -2, 2, -0.412499845, 1.71249986); - assert_best_cartesian_range_is("x^3", -5, 5, -2.80624962, 2.5062499); - assert_best_cartesian_range_is("-2x^6", -2, 2, -1.51249993, 0.612499833); - assert_best_cartesian_range_is("3x^2+x+10", -1.30000007, 1, 9.58406258, 10.8059368); + 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); @@ -91,15 +91,15 @@ QUIZ_CASE(graph_ranges_single_function) { 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", -5, 5, -1.50624979, 3.80624962); - assert_best_cartesian_range_is("ℯ^x+4", -5, 5, 2.59375024, 7.90625); - assert_best_cartesian_range_is("ℯ^(-x)", -5, 5, -1.50624979, 3.80624962); + 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("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", -5, 5, -1.50624979, 3.80624962); + 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); } @@ -107,12 +107,12 @@ QUIZ_CASE(graph_ranges_several_functions) { { const char * definitions[] = {"ℯ^x", "ln(x)"}; ContinuousFunction::PlotType types[] = {Cartesian, Cartesian}; - assert_best_range_is(definitions, types, -6.52941275, 8.52941322, -3.79999995, 4.20000029); + 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, -17.0882378, 28.0882378, -13, 11); + assert_best_range_is(definitions, types, -16, 17, -9.21562386, 8.31562424); } { const char * definitions[] = {"sin(θ)", "cos(θ)"}; From ffebd2e987a014196b3a614634f4e7c62c404fb1 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 16:42:02 +0100 Subject: [PATCH 498/560] [shared] Rename methods --- apps/graph/test/ranges.cpp | 4 ++-- apps/sequence/graph/curve_view_range.h | 2 +- apps/shared/function_graph_controller.cpp | 2 +- apps/shared/interactive_curve_view_controller.cpp | 2 +- apps/shared/interactive_curve_view_range.cpp | 2 +- apps/shared/interactive_curve_view_range.h | 2 +- apps/shared/interactive_curve_view_range_delegate.cpp | 4 ++-- apps/shared/interactive_curve_view_range_delegate.h | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index 6e3952c41d4..85c6605dff7 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -20,8 +20,8 @@ class AdHocGraphController : public InteractiveCurveViewRangeDelegate { // InteractiveCurveViewRangeDelegate bool defaultRangeIsNormalized() const override { return functionStore()->displaysNonCartesianFunctions(); } - void interestingRanges(InteractiveCurveViewRange * range) override { InterestingRangesHelper(range, context(), functionStore(), Ratio()); } - float addMargin(float x, float range, bool isVertical, bool isMin) override { return AddMarginHelper(x, range, isVertical, isMin, k_topMargin, k_bottomMargin, k_leftMargin, k_rightMargin); } + 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: diff --git a/apps/sequence/graph/curve_view_range.h b/apps/sequence/graph/curve_view_range.h index 42cef213c3d..8205a9139c1 100644 --- a/apps/sequence/graph/curve_view_range.h +++ b/apps/sequence/graph/curve_view_range.h @@ -10,7 +10,7 @@ class CurveViewRange : public Shared::InteractiveCurveViewRange { CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr); void normalize(bool forceChangeY = false) override; private: - virtual bool defaultRangeCriteria() const override { return false; } + virtual bool hasDefaultRange() const override { return false; } constexpr static float k_displayLeftMarginRatio = 0.1f; }; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 1146873716b..1f59575c394 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -155,7 +155,7 @@ int FunctionGraphController::numberOfCurves() const { void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) { float ratio = InteractiveCurveViewRange::NormalYXRatio() / (1 + cursorTopMarginRatio() + cursorBottomMarginRatio()); - InterestingRangesHelper(range, textFieldDelegateApp()->localContext(), functionStore(), ratio); + DefaultInterestingRanges(range, textFieldDelegateApp()->localContext(), functionStore(), ratio); } } diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 4373fed6631..59492e4d0f6 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -41,7 +41,7 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren } float InteractiveCurveViewController::addMargin(float y, float range, bool isVertical, bool isMin) { - return AddMarginHelper(y, range, isVertical, isMin, cursorTopMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), cursorRightMarginRatio()); + return DefaultAddMargin(y, range, isVertical, isMin, cursorTopMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), cursorRightMarginRatio()); } void InteractiveCurveViewController::updateZoomButtons() { diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 8e2739a42e6..504356225bf 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -183,7 +183,7 @@ void InteractiveCurveViewRange::setDefault() { m_delegate->interestingRanges(this); /* If the horizontal bounds are integers, they are preset values and should * not be changed. */ - bool isDefaultRange = defaultRangeCriteria(); + bool isDefaultRange = hasDefaultRange(); // Add margins, then round limits. float newXMin = xMin(), newXMax = xMax(); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 52718eaac6b..dfde5c4840c 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -78,7 +78,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { * 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; } - virtual bool defaultRangeCriteria() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); } + virtual bool hasDefaultRange() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); } InteractiveCurveViewRangeDelegate * m_delegate; private: diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp index b82eb80bf36..01ea2baf5e5 100644 --- a/apps/shared/interactive_curve_view_range_delegate.cpp +++ b/apps/shared/interactive_curve_view_range_delegate.cpp @@ -5,7 +5,7 @@ namespace Shared { -void InteractiveCurveViewRangeDelegate::InterestingRangesHelper(InteractiveCurveViewRange * range, Poincare::Context * context, FunctionStore * functionStore, float targetRatio) { +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(); @@ -26,7 +26,7 @@ void InteractiveCurveViewRangeDelegate::InterestingRangesHelper(InteractiveCurve range->setYMax(yMax); } -float InteractiveCurveViewRangeDelegate::AddMarginHelper(float x, float range, bool isVertical, bool isMin, float top, float bottom, float left, float right) { +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. diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index 8a163feaa90..d21964000c0 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -13,8 +13,8 @@ class InteractiveCurveViewRangeDelegate { public: static constexpr float k_defaultXHalfRange = 10.0f; - static void InterestingRangesHelper(InteractiveCurveViewRange * range, Poincare::Context * context, FunctionStore * functionStore, float targetRatio); - static float AddMarginHelper(float x, float range, bool isVertical, bool isMin, float top, float bottom, float left, float right); + 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; } From 71be09b4e78f35c46bbfa6889527b180513ea1e0 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 3 Dec 2020 17:41:12 +0100 Subject: [PATCH 499/560] [poincare, graph] Factor helper function --- apps/graph/test/caching.cpp | 2 +- apps/graph/test/helper.h | 5 +---- apps/graph/test/ranges.cpp | 5 ++++- poincare/test/zoom.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/graph/test/caching.cpp b/apps/graph/test/caching.cpp index bef20e35dc3..5448699fc42 100644 --- a/apps/graph/test/caching.cpp +++ b/apps/graph/test/caching.cpp @@ -10,7 +10,7 @@ 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 a == b || std::abs(a - b) <= tolerance * std::abs(a + b) / 2.f || (std::isnan(a) && std::isnan(b)); + 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) { diff --git a/apps/graph/test/helper.h b/apps/graph/test/helper.h index 69fe91edb64..727022bf663 100644 --- a/apps/graph/test/helper.h +++ b/apps/graph/test/helper.h @@ -2,6 +2,7 @@ #define APPS_GRAPH_TEST_HELPER_H #include "../app.h" +#include "../../poincare/test/helper.h" using namespace Poincare; using namespace Shared; @@ -12,10 +13,6 @@ constexpr ContinuousFunction::PlotType Cartesian = ContinuousFunction::PlotType: constexpr ContinuousFunction::PlotType Polar = ContinuousFunction::PlotType::Polar; constexpr ContinuousFunction::PlotType Parametric = ContinuousFunction::PlotType::Parametric; -constexpr Preferences::AngleUnit Radian = Preferences::AngleUnit::Radian; -constexpr Preferences::AngleUnit Degree = Preferences::AngleUnit::Degree; -constexpr Preferences::AngleUnit Gradian = Preferences::AngleUnit::Gradian; - ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context); } diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index 85c6605dff7..5056fd34357 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -8,6 +8,9 @@ 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; @@ -30,7 +33,7 @@ class AdHocGraphController : public InteractiveCurveViewRangeDelegate { }; bool float_equal(float a, float b, float tolerance = 10.f * FLT_EPSILON) { - return std::fabs(a - b) <= tolerance * std::fabs(a + b); + return IsApproximatelyEqual(a, b, tolerance, 0.); } template diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 66d34c463a5..2f43edfb70a 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -7,7 +7,7 @@ using namespace Poincare; // When adding the graph window margins, this ratio gives an orthonormal window constexpr float NormalRatio = 0.442358822; -constexpr float StandardTolerance = 10.f * FLT_EPSILON; +constexpr float StandardTolerance = 50.f * FLT_EPSILON; class ParametersPack { public: @@ -35,7 +35,7 @@ float evaluate_expression(float x, Context * context, const void * auxiliary) { bool float_equal(float a, float b, float tolerance = StandardTolerance) { assert(std::isfinite(tolerance)); return !(std::isnan(a) || std::isnan(b)) - && std::fabs(a - b) <= tolerance * std::fabs(a + b); + && IsApproximatelyEqual(a, b, tolerance, 0.); } bool range1D_matches(float min, float max, float targetMin, float targetMax, float tolerance = StandardTolerance) { From 89979b4f509a6b086074872ebb8d9710ad06935f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 7 Dec 2020 15:40:34 +0100 Subject: [PATCH 500/560] [poincare/zoom] Comment on the RangeWithRatio method --- poincare/src/zoom.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 3ebdfe4128a..b999a4d1723 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -247,6 +247,12 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float } 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; @@ -274,6 +280,18 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f 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, bestBreadth = 0, bestDistanceToCenter; @@ -295,6 +313,9 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f } } + /* 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] - sample[bestIndex] < minimalYCoverage * yRange) { *xMin = NAN; From 55f21f127c00609834e9d622cf610fba9d8ee909 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 7 Dec 2020 16:40:06 +0100 Subject: [PATCH 501/560] [poincare/zoom] Fix array overflow --- apps/graph/test/ranges.cpp | 2 +- poincare/src/zoom.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index 5056fd34357..a2f5b8edf16 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -71,7 +71,7 @@ QUIZ_CASE(graph_ranges_single_function) { 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.662506, -95.0375061); + 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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b999a4d1723..ac9eb63905a 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -317,7 +317,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f * 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] - sample[bestIndex] < minimalYCoverage * yRange) { + || sample[bestIndex + bestBreadth - 1] - sample[bestIndex] < minimalYCoverage * yRange) { *xMin = NAN; *xMax = NAN; *yMin = NAN; From 0f95b579a4ac27ede0538fe8191312ac5e2a8296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 7 Dec 2020 17:12:38 +0100 Subject: [PATCH 502/560] build: Version 15.1.0 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index 68055912b45..137d0cf7a68 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 15.0.0 +EPSILON_VERSION ?= 15.1.0 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US From c3ceb0074fd5cbe1a73a1d05fd2dae258211e965 Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Tue, 8 Dec 2020 17:36:03 +0100 Subject: [PATCH 503/560] [NL] fix translation mistakes --- apps/code/catalog.nl.i18n | 2 +- apps/graph/base.nl.i18n | 4 ++-- apps/shared.nl.i18n | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index 8fbcb094a13..84be6987829 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -79,7 +79,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" diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n index 5dbd3a0ed41..21d52275d11 100644 --- a/apps/graph/base.nl.i18n +++ b/apps/graph/base.nl.i18n @@ -12,8 +12,8 @@ IntervalTheta = "θ interval" IntervalX = "x interval" FunctionDomain = "Plotbereik" FunctionColor = "Functiekleur" -NoFunction = "Geen functie" -NoActivatedFunction = "Geen functie is ingeschakeld" +NoFunction = "Geen functie gedefinieerd" +NoActivatedFunction = "Geen functie ingeschakeld" PlotOptions = "Plotopties" Compute = "Bereken" Zeros = "Nulpunten" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index 55eb53adaec..30cde4aeacf 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -52,7 +52,7 @@ 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" @@ -79,7 +79,7 @@ StatTab = "Stats" Step = "Stap" StorageMemoryFull1 = "Het geheugen is vol." StorageMemoryFull2 = "Wis de gegevens en probeer opnieuw." -SyntaxError = "Syntax error" +SyntaxError = "Syntaxisfout" TEnd = "T einde" ThetaEnd = "θ einde" ThetaStart = "θ begin" From fe4e2e3e9d1b4d59bf2f0dc3b4e1fe438aac9059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 8 Dec 2020 10:45:49 +0100 Subject: [PATCH 504/560] Misc. clang static analyzer fixes --- apps/regression/model/model.cpp | 1 + apps/shared/localization_controller.cpp | 1 + apps/title_bar_view.cpp | 1 + escher/src/table_cell.cpp | 1 + poincare/src/zoom.cpp | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/regression/model/model.cpp b/apps/regression/model/model.cpp index 13f71157414..2d223ec7e0b 100644 --- a/apps/regression/model/model.cpp +++ b/apps/regression/model/model.cpp @@ -64,6 +64,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/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index 92ff1bd2c71..a0a99d43ee0 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -15,6 +15,7 @@ LocalizationController::ContentView::ContentView(LocalizationController * contro { 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); diff --git a/apps/title_bar_view.cpp b/apps/title_bar_view.cpp index ed6be0b4ae9..3295a0f5e43 100644 --- a/apps/title_bar_view.cpp +++ b/apps/title_bar_view.cpp @@ -107,6 +107,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); // Layout the exam mode icon if needed diff --git a/escher/src/table_cell.cpp b/escher/src/table_cell.cpp index 1bf94944860..1fc07ea798d 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/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index ac9eb63905a..ab278aaa5b5 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -294,7 +294,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f * dealing with linear functions. */ float yRange = yxRatio * xRange; int j = 1; - int bestIndex, bestBreadth = 0, bestDistanceToCenter; + int bestIndex = 0, bestBreadth = 0, bestDistanceToCenter; for (int i = 0; i < sampleSize; i++) { if (sampleSize - i < bestBreadth) { break; From d0f40f01776ba1f5d7e2f353a05b4cedd2eb0fa2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 14:36:44 +0100 Subject: [PATCH 505/560] [apps/regression] Set coefficient c initial value to 0 --- apps/regression/model/trigonometric_model.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index ec094e2bc05..30b46ef3e7e 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -73,9 +73,6 @@ double TrigonometricModel::partialDerivate(double * modelCoefficients, int deriv 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. * @@ -88,6 +85,7 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic modelCoefficients[k_numberOfCoefficients - 1] = store->meanOfColumn(series, 1); // Init the b coefficient double rangeX = store->maxValueOfColumn(series, 0) - store->minValueOfColumn(series, 0); + double radian = toRadians(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 @@ -95,9 +93,14 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic * 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). */ - double radian = toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); modelCoefficients[1] = (2.0 * M_PI / radian) / rangeX; + } else { + // Coefficient b must not depend on angleUnit. + modelCoefficients[1] = defaultValue * M_PI / radian; } + /* 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] = 0.0 * M_PI / radian; } Expression TrigonometricModel::expression(double * modelCoefficients) { From e17ff842b5eb418604bdd7b21758363e5de0ee95 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 14:37:19 +0100 Subject: [PATCH 506/560] [apps/regression/test] Update trigonometric tests --- apps/regression/test/model.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 850e097b069..3184f083763 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -202,19 +202,19 @@ QUIZ_CASE(trigonometric_regression) { // TODO : Ensure unicity with trigonometric coefficients. Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Radian); // a*sin(b*x+c)+d = -a*sin(b*x+c+π)+d - double coefficientsRad[] = {-coefficients[0], coefficients[1], coefficients[2] + M_PI, coefficients[3]}; + double coefficientsRad[] = {coefficients[0], coefficients[1], coefficients[2], coefficients[3]}; assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsRad, r2); Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Degree); double radToDeg = 180.0 / M_PI; // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d - double coefficientsDeg[] = {coefficients[0], coefficients[1] * radToDeg, (coefficients[2] - 2.0 * M_PI) * radToDeg, coefficients[3]}; + double coefficientsDeg[] = {coefficients[0], coefficients[1] * radToDeg, coefficients[2] * radToDeg, coefficients[3]}; assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsDeg, r2); Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Gradian); double radToGrad = 200.0 / M_PI; // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d - double coefficientsGrad[] = {coefficients[0], coefficients[1] * radToGrad, (coefficients[2] - 2.0 * M_PI) * radToGrad, coefficients[3]}; + double coefficientsGrad[] = {coefficients[0], coefficients[1] * radToGrad, coefficients[2] * radToGrad, coefficients[3]}; assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsGrad, r2); Poincare::Preferences::sharedPreferences()->setAngleUnit(previousAngleUnit); From 357db4493d6862eb574f7679aee01d9bffa37a93 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 14:38:02 +0100 Subject: [PATCH 507/560] [apps/regression] Add TODO to improve coefficient update --- apps/regression/store.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 18f09489e5e..f2615aceabc 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -196,6 +196,8 @@ void Store::updateCoefficients(int series, Poincare::Context * globalContext) { 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; } } From a08e3e10245dfc315dfe965078afb291b5e474ce Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 17:13:03 +0100 Subject: [PATCH 508/560] [apps/regression] Update comment --- apps/regression/model/trigonometric_model.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 30b46ef3e7e..aba971665c1 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -99,8 +99,9 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic modelCoefficients[1] = defaultValue * M_PI / radian; } /* 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] = 0.0 * M_PI / radian; + * If it were to be non-null, angleUnit must be taken into account. + * modelCoefficients[2] = initialCValue * M_PI / radian; */ + modelCoefficients[2] = 0.0; } Expression TrigonometricModel::expression(double * modelCoefficients) { From c28a5198d6feacc6562c3a8da779024fee80235b Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 17:19:46 +0100 Subject: [PATCH 509/560] [apps/regression/test] Add a test case, factorize logic --- apps/regression/test/model.cpp | 46 ++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index 3184f083763..eebec477603 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -190,8 +190,23 @@ QUIZ_CASE(power_regression) { // assert_regression_is(x2, y2, 4, Model::Type::Power, coefficients2, r22); } -QUIZ_CASE(trigonometric_regression) { - Preferences::AngleUnit previousAngleUnit = Preferences::sharedPreferences()->angleUnit(); +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}; @@ -199,25 +214,18 @@ QUIZ_CASE(trigonometric_regression) { int numberOfPoints = sizeof(x) / sizeof(double); assert(sizeof(y) == sizeof(double) * numberOfPoints); - // TODO : Ensure unicity with trigonometric coefficients. - Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Radian); - // a*sin(b*x+c)+d = -a*sin(b*x+c+π)+d - double coefficientsRad[] = {coefficients[0], coefficients[1], coefficients[2], coefficients[3]}; - assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsRad, r2); - - Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Degree); - double radToDeg = 180.0 / M_PI; - // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d - double coefficientsDeg[] = {coefficients[0], coefficients[1] * radToDeg, coefficients[2] * radToDeg, coefficients[3]}; - assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsDeg, r2); + assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); +} - Poincare::Preferences::sharedPreferences()->setAngleUnit(Poincare::Preferences::AngleUnit::Gradian); - double radToGrad = 200.0 / M_PI; - // a*sin(b*x+c)+d = a*sin(b*x+c+2π)+d - double coefficientsGrad[] = {coefficients[0], coefficients[1] * radToGrad, coefficients[2] * radToGrad, coefficients[3]}; - assert_regression_is(x, y, numberOfPoints, Model::Type::Trigonometric, coefficientsGrad, r2); +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); - Poincare::Preferences::sharedPreferences()->setAngleUnit(previousAngleUnit); + assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); } From 71070ee052ec3f4db704663c6904ef1648161e0f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 10:18:17 +0100 Subject: [PATCH 510/560] [poincare/zoom] Helper method SetZoom Create a method SetZoom to zoom in and out on a window. --- poincare/include/poincare/zoom.h | 1 + poincare/src/zoom.cpp | 8 ++++++++ poincare/test/zoom.cpp | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 6bd08ca88a9..4bd5c016579 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -37,6 +37,7 @@ class Zoom { /* 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; diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index ab278aaa5b5..7cb7b46a6d0 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -421,6 +421,14 @@ void Zoom::SetToRatio(float yxRatio, float * xMin, float * xMax, float * yMin, 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; diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 2f43edfb70a..3fee10f16a7 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -198,6 +198,12 @@ void assert_expands_to(float yxRatio, float xMin, float xMax, float yMin, float 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 { @@ -261,5 +267,22 @@ QUIZ_CASE(poincare_zoom_utility) { 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); } From 3cdb076c2c8d6805b67a52a91d20806f7edfd2d9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 10:19:12 +0100 Subject: [PATCH 511/560] [shared/interactive_curve_view_range] Factor zoom The zoom method to zoom in and out on a curve view make use of the SetZoom method in Poincare::Zoom. --- apps/shared/interactive_curve_view_range.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 504356225bf..7f38e414213 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -101,19 +101,16 @@ 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; - if (!std::isnan(newXMin) && !std::isnan(newXMax)) { + Zoom::SetZoom(ratio, centerX, centerY, &xMi, &xMa, &yMi, &yMa); + if (!std::isnan(xMi) && !std::isnan(xMa)) { setZoomAuto(false); - m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); + 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)) { + if (!std::isnan(yMi) && !std::isnan(yMa)) { setZoomAuto(false); - m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); + m_yRange.setMax(yMa, k_lowerMaxFloat, k_upperMaxFloat); + MemoizedCurveViewRange::protectedSetYMin(yMi, k_lowerMaxFloat, k_upperMaxFloat); } m_offscreenYAxis *= ratio; } From 625a89e610686c97320f4581006e6ef63bc0a796 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 12:16:56 +0100 Subject: [PATCH 512/560] [poincare/zoom] RefinedYRangeForDisplay signature --- apps/shared/continuous_function.cpp | 4 ++-- poincare/include/poincare/zoom.h | 2 +- poincare/src/zoom.cpp | 8 ++++---- poincare/test/zoom.cpp | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 4a22f028e63..e127feed520 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -287,7 +287,7 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi if (fullyComputed) { /* The function has points of interest. */ - Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this); + Zoom::RefinedYRangeForDisplay(evaluation, xMin, xMax, yMin, yMax, context, this); return; } @@ -301,7 +301,7 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi * Try a basic range. */ *xMin = - Zoom::k_defaultHalfRange; *xMax = Zoom::k_defaultHalfRange; - Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this); + Zoom::RefinedYRangeForDisplay(evaluation, xMin, xMax, yMin, yMax, context, this); if (std::isfinite(*xMin) && std::isfinite(*xMax) && std::isfinite(*yMin) && std::isfinite(*yMax)) { return; } diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 4bd5c016579..ed61d072bf8 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -22,7 +22,7 @@ class Zoom { 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, int sampleSize = k_sampleSize); + 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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 7cb7b46a6d0..36bec8977c3 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -195,20 +195,20 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, return true; } -void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, int sampleSize) { +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 sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; - const float step = (xMax - xMin) / (sampleSize - 1); + const float step = (*xMax - *xMin) / (k_sampleSize - 1); float x, y; float sum = 0.f; int pop = 0; - for (int i = 1; i < sampleSize - 1; i++) { - x = xMin + i * step; + for (int i = 1; i < k_sampleSize - 1; i++) { + x = *xMin + i * step; y = evaluation(x, context, auxiliary); if (!std::isfinite(y)) { continue; diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 3fee10f16a7..49b53cb3870 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -98,7 +98,7 @@ void assert_refined_range_is(const char * definition, float xMin, float xMax, fl 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); + Zoom::RefinedYRangeForDisplay(evaluate_expression, &xMin, &xMax, &yMin, &yMax, &globalContext, &aux); quiz_assert_print_if_failure(range1D_matches(yMin, yMax, targetYMin, targetYMax), definition); } From 35bfb8ec16b3e9ac4be1a05620d66a3b804c4744 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 12:18:45 +0100 Subject: [PATCH 513/560] [poincare/zoom] Method ExpandSparseWindow This method is used to remove extraneous empty sapce in the middle of the window for functions that are discontinuous between their points of interest. --- apps/graph/test/ranges.cpp | 1 + poincare/include/poincare/zoom.h | 4 ++++ poincare/src/zoom.cpp | 33 ++++++++++++++++++++++++++++++++ poincare/test/zoom.cpp | 1 + 4 files changed, 39 insertions(+) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index a2f5b8edf16..4d13c154679 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -97,6 +97,7 @@ QUIZ_CASE(graph_ranges_single_function) { 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); diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index ed61d072bf8..5339d5c50fe 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -68,6 +68,10 @@ class Zoom { * 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); }; } diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 36bec8977c3..a786e658f2f 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -201,15 +201,19 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, flo * 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; } @@ -244,6 +248,8 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, flo } 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) { @@ -446,4 +452,31 @@ bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2 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]; + 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/zoom.cpp b/poincare/test/zoom.cpp index 49b53cb3870..2e1da2a8609 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -128,6 +128,7 @@ QUIZ_CASE(poincare_zoom_refined_range) { 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") { From 185c780215f6ad629bd55f9ed89d9d9c3ae4e463 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 16:08:26 +0100 Subject: [PATCH 514/560] [poincare/zoom] Handle NAN in ExpandSparseWindow --- poincare/src/zoom.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index a786e658f2f..326bc9f6b38 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -470,8 +470,13 @@ void Zoom::ExpandSparseWindow(float * sample, int length, float * xMin, float * for (int i = 0; i < length; i++) { float x = *xMin + i * step; float y = sample[i]; - float r = 2 * std::max(std::fabs(x - xCenter) / xRange, std::fabs(y - yCenter) / yRange); - emptyCenter = std::min(emptyCenter, r); + 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) { From 94b2da8e604b06e6cb6f3ac96ca3e9d95c4a4d95 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 16:08:46 +0100 Subject: [PATCH 515/560] [poincare/zoom] Comment on the representation of ranges --- poincare/include/poincare/zoom.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 5339d5c50fe..56bd4587180 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -4,6 +4,12 @@ #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 { From 79e59f525eeb0af90ff74c7c18d8205ce5cf8449 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 12:04:01 +0100 Subject: [PATCH 516/560] [apps/regression] Ensure unicity for trigonometric regression --- apps/regression/model/model.cpp | 1 + apps/regression/model/model.h | 1 + apps/regression/model/trigonometric_model.cpp | 23 +++++++++++++++++++ apps/regression/model/trigonometric_model.h | 1 + 4 files changed, 26 insertions(+) diff --git a/apps/regression/model/model.cpp b/apps/regression/model/model.cpp index 2d223ec7e0b..8aec7709acb 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); } diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index 78f14aab643..1b36fa6a8a0 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -64,6 +64,7 @@ class Model { 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 aba971665c1..b8338964e45 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -104,6 +104,29 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic modelCoefficients[2] = 0.0; } +void TrigonometricModel::uniformizeCoefficientsFromFit(double * modelCoefficients) const { + // Coefficients must be unique. + double piInAngleUnit = M_PI / toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + // A must be positive. + if (modelCoefficients[0] < 0) { + // A * sin(B * x + C) + D = -A * sin(B * x + C + π) + D + modelCoefficients[0] *= -1; + modelCoefficients[2] += piInAngleUnit; + } + // B must be positive. + if (modelCoefficients[1] < 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; + modelCoefficients[2] *= -1; + modelCoefficients[2] += piInAngleUnit; + } + // C must be between -π and π. + // 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 * piInAngleUnit * round(modelCoefficients[2] / (2 * piInAngleUnit)); +} + Expression TrigonometricModel::expression(double * modelCoefficients) { double a = modelCoefficients[0]; double b = modelCoefficients[1]; diff --git a/apps/regression/model/trigonometric_model.h b/apps/regression/model/trigonometric_model.h index 83ed05113f0..124a6353cdf 100644 --- a/apps/regression/model/trigonometric_model.h +++ b/apps/regression/model/trigonometric_model.h @@ -17,6 +17,7 @@ class TrigonometricModel : public Model { 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; }; From 6e318593aec43defb84fd096043b67f4136f469b Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 15:07:22 +0100 Subject: [PATCH 517/560] [apps/regression] Code review fixes --- apps/regression/model/trigonometric_model.cpp | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index b8338964e45..5b657dee102 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -17,15 +17,8 @@ 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,7 +34,7 @@ double TrigonometricModel::evaluate(double * modelCoefficients, double x) const double b = modelCoefficients[1]; double c = modelCoefficients[2]; double d = modelCoefficients[3]; - double radian = toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + 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; } @@ -55,7 +48,7 @@ double TrigonometricModel::partialDerivate(double * modelCoefficients, int deriv double a = modelCoefficients[0]; double b = modelCoefficients[1]; double c = modelCoefficients[2]; - double radian = 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) { @@ -85,7 +78,7 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic modelCoefficients[k_numberOfCoefficients - 1] = store->meanOfColumn(series, 1); // Init the b coefficient double rangeX = store->maxValueOfColumn(series, 0) - store->minValueOfColumn(series, 0); - double radian = toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + 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 @@ -93,38 +86,42 @@ void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoeffic * 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 * M_PI / radian) / rangeX; + modelCoefficients[1] = (2.0 * piInAngleUnit) / rangeX; } else { // Coefficient b must not depend on angleUnit. - modelCoefficients[1] = defaultValue * M_PI / radian; + 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 * M_PI / radian; */ + * modelCoefficients[2] = initialCValue * piInAngleUnit; */ modelCoefficients[2] = 0.0; } void TrigonometricModel::uniformizeCoefficientsFromFit(double * modelCoefficients) const { // Coefficients must be unique. - double piInAngleUnit = M_PI / toRadians(Poincare::Preferences::sharedPreferences()->angleUnit()); + double piInAngleUnit = Trigonometry::PiInAngleUnit(Poincare::Preferences::sharedPreferences()->angleUnit()); // A must be positive. - if (modelCoefficients[0] < 0) { + if (modelCoefficients[0] < 0.0) { // A * sin(B * x + C) + D = -A * sin(B * x + C + π) + D - modelCoefficients[0] *= -1; + modelCoefficients[0] *= -1.0; modelCoefficients[2] += piInAngleUnit; } // B must be positive. - if (modelCoefficients[1] < 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; - modelCoefficients[2] *= -1; + 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 -π and π. - // 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 * piInAngleUnit * round(modelCoefficients[2] / (2 * piInAngleUnit)); + /* C must be between -π (excluded) and π (included). + * 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) { From 78cb340065e5cb3ab2b848616a92925569d9c1d9 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 9 Dec 2020 17:27:59 +0100 Subject: [PATCH 518/560] [apps/regression] Only uniformize c if needed --- apps/regression/model/trigonometric_model.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 5b657dee102..5638d44cdc9 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -114,13 +114,15 @@ void TrigonometricModel::uniformizeCoefficientsFromFit(double * modelCoefficient modelCoefficients[2] *= -1.0; modelCoefficients[2] += piInAngleUnit; } - /* C must be between -π (excluded) and π (included). - * 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; + // 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; + } } } From c7b758f53654ee3c499d4e6140ff7b2bd8cadcf9 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 9 Dec 2020 15:50:01 +0100 Subject: [PATCH 519/560] [poincare/expression] Fix solutions to e^x=0 Because of the limitations of the floating-point representation, e^x is null for x <= 710, causing the nextRoot functions to find roots for it. To prevent this, we looking for places where a function changes sign, we actually require the function to take two non-null values of different signs. --- apps/solver/test/equation_store.cpp | 2 ++ poincare/src/expression.cpp | 24 +++++++++++++++++++++--- poincare/test/function_solver.cpp | 8 +++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index 608f81f6fe9..8ffbd0c5481 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -82,6 +82,8 @@ QUIZ_CASE(equation_solve) { 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"); diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 685242180c1..498e3da388d 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -1005,6 +1005,9 @@ 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 { + 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); @@ -1139,17 +1142,32 @@ 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 c = a; double b = start+step; + double fa = evaluation(a, context, complexFormat, angleUnit, this, symbol, &expression); + double fc = fa; + double fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); 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) { + if (fa == 0. && ((fc < 0. && fb > 0.) || (fc > 0. && fb < 0.))) { + /* If fa is null, we still check that the function changes sign on ]c,b[, + * and that fc and fb are not null. Otherwise, it's more likely those + * zeroes are caused by approximation errors. */ + result[0] = a; + result[1] = b; + return; + } else if (fb != 0. && ((fa < 0.) != (fb < 0.))) { + /* The function changes sign. + * The case fb = 0 is handled in the next pass with fa = 0. */ result[0] = a; result[1] = b; return; } + c = a; + fc = fa; a = b; + fa = fb; b = b+step; + fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); } result[0] = NAN; result[1] = NAN; diff --git a/poincare/test/function_solver.cpp b/poincare/test/function_solver.cpp index 4dee3c65ffe..11274a2b09c 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) { From efbbbe94ff405d36c03aaa12910718e1e20331cc Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 10 Dec 2020 14:11:28 +0100 Subject: [PATCH 520/560] [poincare/expression] Comment and variable names --- poincare/src/expression.cpp | 41 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 498e3da388d..b3377d60344 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -1005,6 +1005,9 @@ 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; } @@ -1141,33 +1144,33 @@ 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 c = a; - double b = start+step; - double fa = evaluation(a, context, complexFormat, angleUnit, this, symbol, &expression); - double fc = fa; + double b = start; + double a = b; + double c = start+step; double fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); - while (step > 0.0 ? b <= max : b >= max) { - if (fa == 0. && ((fc < 0. && fb > 0.) || (fc > 0. && fb < 0.))) { - /* If fa is null, we still check that the function changes sign on ]c,b[, - * and that fc and fb are not null. Otherwise, it's more likely those + 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] = a; - result[1] = b; + result[0] = b; + result[1] = c; return; - } else if (fb != 0. && ((fa < 0.) != (fb < 0.))) { + } else if (fc != 0. && ((fb < 0.) != (fc < 0.))) { /* The function changes sign. - * The case fb = 0 is handled in the next pass with fa = 0. */ - result[0] = a; - result[1] = b; + * The case fc = 0 is handled in the next pass with fb = 0. */ + result[0] = b; + result[1] = c; return; } - c = a; - fc = fa; a = b; fa = fb; - b = b+step; - fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); + b = c; + fb = fc; + c = c+step; + fc = evaluation(c, context, complexFormat, angleUnit, this, symbol, &expression); } result[0] = NAN; result[1] = NAN; From 56a6917cfd780612cba2c4b9e5d4009f89967cbb Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 11 Dec 2020 14:17:03 +0100 Subject: [PATCH 521/560] [apps/shared] Fix context pointer when sorting rows --- apps/shared/store_parameter_controller.cpp | 5 +++-- apps/shared/store_parameter_controller.h | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/shared/store_parameter_controller.cpp b/apps/shared/store_parameter_controller.cpp index 50212fee23f..97bfc3b55a2 100644 --- a/apps/shared/store_parameter_controller.cpp +++ b/apps/shared/store_parameter_controller.cpp @@ -82,10 +82,11 @@ bool StoreParameterController::handleEvent(Ion::Events::Event event) { 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, (m_store->data() + m_series), m_store->numberOfPairsOfSeries(m_series)); + Poincare::Helpers::Sort(swapRows, compareX, seriesContext, m_store->numberOfPairsOfSeries(m_series)); } else { - Poincare::Helpers::Sort(swapRows, compareY, (m_store->data() + m_series), m_store->numberOfPairsOfSeries(m_series)); + Poincare::Helpers::Sort(swapRows, compareY, seriesContext, m_store->numberOfPairsOfSeries(m_series)); } break; } diff --git a/apps/shared/store_parameter_controller.h b/apps/shared/store_parameter_controller.h index 3198b5239ba..efc8c6f1273 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -13,7 +13,10 @@ 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; From 7646f13ca808cb1d993176a739bd324bd0743fa1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 10 Dec 2020 18:09:56 +0100 Subject: [PATCH 522/560] [apps/shared] Fix NaN comparison in isCursorHanging method --- apps/shared/function_graph_controller.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 1f59575c394..528013d4d25 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -130,7 +130,9 @@ bool FunctionGraphController::isCursorHanging() { } ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); Coordinate2D xy = f->evaluateXYAtParameter(m_cursor->t(), context); - return xy.x1() != m_cursor->x() || xy.x2() != m_cursor->y(); + // NaN != Nan returns true, but cursor is not hanging if both values are NaN + return (xy.x1() != m_cursor->x() && !(std::isnan(xy.x1()) && std::isnan(m_cursor->x()))) + || (xy.x2() != m_cursor->y() && !(std::isnan(xy.x2()) && std::isnan(m_cursor->y()))); } CurveView * FunctionGraphController::curveView() { From b7bbb258a87ac3901c48e0311fb319b5bd6b21ab Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 16 Dec 2020 14:09:54 +0100 Subject: [PATCH 523/560] [apps/regression] Fix NaN comparison in isCursorHanging method --- apps/regression/graph_controller.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 6ccc4f1f460..a6934e32864 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -271,7 +271,9 @@ bool GraphController::isCursorHanging() { } else { xy = Coordinate2D(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); } - return xy.x1() != m_cursor->x() || xy.x2() != m_cursor->y(); + // NaN != Nan returns true, but cursor is not hanging if both values are NaN + return (xy.x1() != m_cursor->x() && !(std::isnan(xy.x1()) && std::isnan(m_cursor->x()))) + || (xy.x2() != m_cursor->y() && !(std::isnan(xy.x2()) && std::isnan(m_cursor->y()))); } bool GraphController::moveCursorVertically(int direction) { From 10c296e671ab421b0a18ae4ee630e848db031f37 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 16 Dec 2020 14:10:23 +0100 Subject: [PATCH 524/560] [apps/shared] Add comment for isCursorHanging method --- apps/shared/interactive_curve_view_controller.h | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 91f8fa5e10f..7ca1b9d81e0 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -41,6 +41,7 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll virtual bool moveCursorVertically(int direction) = 0; virtual uint32_t rangeVersion() = 0; bool isCursorVisible(); + // The cursor is hanging if the selected function has been edited or deleted virtual bool isCursorHanging() = 0; // Closest vertical curve helper From 479f34502f4aa5cce4fa223475350594c100428e Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Wed, 16 Dec 2020 15:59:12 +0100 Subject: [PATCH 525/560] [apps/shared] Rename methods and Factorize comparison logic --- apps/regression/graph_controller.cpp | 9 ++++----- apps/regression/graph_controller.h | 2 +- apps/shared/function_graph_controller.cpp | 9 ++++----- apps/shared/function_graph_controller.h | 2 +- apps/shared/interactive_curve_view_controller.cpp | 2 +- apps/shared/interactive_curve_view_controller.h | 4 ++-- apps/shared/poincare_helpers.h | 2 ++ 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index a6934e32864..5344fa60015 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 @@ -259,9 +260,9 @@ void GraphController::initCursorParameters() { *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } -bool GraphController::isCursorHanging() { +bool GraphController::cursorMatchesModel() { if (m_store->seriesIsEmpty(*m_selectedSeriesIndex)) { - return true; + return false; } Coordinate2D xy; if (*m_selectedDotIndex == -1) { @@ -271,9 +272,7 @@ bool GraphController::isCursorHanging() { } else { xy = Coordinate2D(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); } - // NaN != Nan returns true, but cursor is not hanging if both values are NaN - return (xy.x1() != m_cursor->x() && !(std::isnan(xy.x1()) && std::isnan(m_cursor->x()))) - || (xy.x2() != m_cursor->y() && !(std::isnan(xy.x2()) && std::isnan(m_cursor->y()))); + return PoincareHelpers::equalOrBothNan(xy.x1(), m_cursor->x()) && PoincareHelpers::equalOrBothNan(xy.x2(), m_cursor->y()); } bool GraphController::moveCursorVertically(int direction) { diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 622ac76bcb2..570c91ff59b 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -40,7 +40,7 @@ class GraphController : public Shared::InteractiveCurveViewController { // InteractiveCurveViewController void initCursorParameters() override; - bool isCursorHanging() override; + bool cursorMatchesModel() override; uint32_t rangeVersion() override; int selectedCurveIndex() const override { return *m_selectedSeriesIndex; } bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 528013d4d25..f43985e9fb8 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 @@ -123,16 +124,14 @@ bool FunctionGraphController::moveCursorVertically(int direction) { return true; } -bool FunctionGraphController::isCursorHanging() { +bool FunctionGraphController::cursorMatchesModel() { Poincare::Context * context = textFieldDelegateApp()->localContext(); if (indexFunctionSelectedByCursor() >= functionStore()->numberOfActiveFunctions()) { - return true; + return false; } ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor())); Coordinate2D xy = f->evaluateXYAtParameter(m_cursor->t(), context); - // NaN != Nan returns true, but cursor is not hanging if both values are NaN - return (xy.x1() != m_cursor->x() && !(std::isnan(xy.x1()) && std::isnan(m_cursor->x()))) - || (xy.x2() != m_cursor->y() && !(std::isnan(xy.x2()) && std::isnan(m_cursor->y()))); + return PoincareHelpers::equalOrBothNan(xy.x1(), m_cursor->x()) && PoincareHelpers::equalOrBothNan(xy.x2(), m_cursor->y()); } CurveView * FunctionGraphController::curveView() { diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index c78086b9616..cac996fe815 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -37,7 +37,7 @@ class FunctionGraphController : public InteractiveCurveViewController, public Fu Poincare::Coordinate2D xyValues(int curveIndex, double t, Poincare::Context * context) const override; int numberOfCurves() const override; void initCursorParameters() override; - bool isCursorHanging() override; + bool cursorMatchesModel() override; CurveView * curveView() override; void yRangeForCursorFirstMove(Shared::InteractiveCurveViewRange * range) const; diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 59492e4d0f6..30d77a3ce98 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -136,7 +136,7 @@ void InteractiveCurveViewController::viewWillAppear() { /* 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()) || isCursorHanging()) { + if ((*m_rangeVersion != newRangeVersion && !isCursorVisible()) || !cursorMatchesModel()) { initCursorParameters(); } *m_rangeVersion = newRangeVersion; diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index 7ca1b9d81e0..a64db50c0ba 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -41,8 +41,8 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll virtual bool moveCursorVertically(int direction) = 0; virtual uint32_t rangeVersion() = 0; bool isCursorVisible(); - // The cursor is hanging if the selected function has been edited or deleted - virtual bool isCursorHanging() = 0; + // 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; diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 372c66add09..2542173213b 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -108,6 +108,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)); } + } } From af3b2d4de47020c29cbaf9d6ade1acefa71ccd7a Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Thu, 17 Dec 2020 10:04:24 +0100 Subject: [PATCH 526/560] [apps/shared] Tidy model name on sequence type change --- apps/shared/sequence.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index a962e5e22a1..20ffb0ec62c 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -71,6 +71,7 @@ void Sequence::setType(Type t) { setInitialRank(0); } recordData()->setType(t); + m_definition.tidyName(); tidy(); /* Reset all contents */ switch (t) { From 24456780e6d3bbfa8b4c8a1842b29d47d93a32af Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Thu, 17 Dec 2020 10:29:10 +0100 Subject: [PATCH 527/560] [regression] Invalidate cursor when selected dot does not exist --- apps/regression/graph_controller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 5344fa60015..48cfd4a42ed 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -269,6 +269,8 @@ bool GraphController::cursorMatchesModel() { 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)); } From 60f875220e9c2fb10b27c11f651d40aaf97beda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 16 Dec 2020 16:46:50 +0100 Subject: [PATCH 528/560] build: Version 15.2.0 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index 137d0cf7a68..eee3c264119 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 15.1.0 +EPSILON_VERSION ?= 15.2.0 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US From 1939293cd285c9896f0e4f7b698da294383c48c1 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 21 Dec 2020 16:01:00 +0100 Subject: [PATCH 529/560] [apps/shared] Add margin to avoid visible approximation errors --- apps/shared/curve_view.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 202ff72b725..6ea0db84934 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -713,6 +713,10 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float 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 @@ -725,10 +729,11 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float * |-------|-------|-------|-------|-------|-------|-------|-- * 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 @@ -764,7 +769,7 @@ void CurveView::drawPolarCurve(KDContext * ctx, KDRect rect, float tStart, float int j = std::ceil((tE - tStart) / tStep); tCache2 = std::min(tStart + tStep * j, tEnd); - assert(tCache1 < tCache2); + assert(tCache1 <= tCache2); drawCurve(ctx, rect, tCache1, tCache2, tStep, xyFloatEvaluation, model, context, drawStraightLinesEarly, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation); } thetaOffset += piInAngleUnit; From fa523c25c93c0de1c70c82b25a1035a0cc307d5d Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Fri, 18 Dec 2020 10:11:21 +0100 Subject: [PATCH 530/560] [apps] Split italian text into two lines in variable box --- apps/math_variable_box_empty_controller.cpp | 1 + apps/variables.de.i18n | 1 + apps/variables.en.i18n | 1 + apps/variables.es.i18n | 1 + apps/variables.fr.i18n | 1 + apps/variables.it.i18n | 3 ++- apps/variables.nl.i18n | 1 + apps/variables.pt.i18n | 1 + 8 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/math_variable_box_empty_controller.cpp b/apps/math_variable_box_empty_controller.cpp index d025851ecd1..4c832c19557 100644 --- a/apps/math_variable_box_empty_controller.cpp +++ b/apps/math_variable_box_empty_controller.cpp @@ -48,6 +48,7 @@ void MathVariableBoxEmptyController::setType(Type type) { case Type::Sequence: { messages[0] = I18n::Message::EmptySequenceBox0; + messages[1] = I18n::Message::EmptySequenceBox1; messages[3] = I18n::Message::Default; break; } diff --git a/apps/variables.de.i18n b/apps/variables.de.i18n index 777ba78b733..c4e639a1cfb 100644 --- a/apps/variables.de.i18n +++ b/apps/variables.de.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "Sie haben keine Funktion definiert." EmptySequenceBox0 = "Sie haben keine Folge definiert." EmptyExpressionBox1 = "Um eine Variable zu definieren:" EmptyFunctionBox1 = "Um eine Funktion zu definieren:" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "Erlaubte Zeichen im Namen:" EmptyFunctionBox2 = "Erlaubte Zeichen im Namen:" EnableCharacters = "A..Z, a..z, 0..9 und _" diff --git a/apps/variables.en.i18n b/apps/variables.en.i18n index c4dbff4998b..ca72346e59a 100644 --- a/apps/variables.en.i18n +++ b/apps/variables.en.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "You have not defined any functions." EmptySequenceBox0 = "You have not defined any sequences." EmptyExpressionBox1 = "To define a variable, type:" EmptyFunctionBox1 = "To define a function, type:" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "The variable name can contain:" EmptyFunctionBox2 = "The function name can contain:" EnableCharacters = "A..Z, a..z, 0..9 and _" diff --git a/apps/variables.es.i18n b/apps/variables.es.i18n index 7d1f1ec4d86..c8f68d6f102 100644 --- a/apps/variables.es.i18n +++ b/apps/variables.es.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "Ninguna función definida." EmptySequenceBox0 = "Ninguna sucesión definida." EmptyExpressionBox1 = "Para definir una, teclear :" EmptyFunctionBox1 = "Para definir una, teclear :" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "El nombre de variable debe" EmptyFunctionBox2 = "El nombre de función debe" EnableCharacters = "contener : A..Z, a..z, 0..9 y _" diff --git a/apps/variables.fr.i18n b/apps/variables.fr.i18n index 243c1d205c0..1324ea3e8b5 100644 --- a/apps/variables.fr.i18n +++ b/apps/variables.fr.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "Vous n'avez défini aucune fonction." EmptySequenceBox0 = "Vous n'avez défini aucune suite." EmptyExpressionBox1 = "Pour définir une variable, tapez :" EmptyFunctionBox1 = "Pour définir une fonction, tapez :" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "Le nom de la variable peut" EmptyFunctionBox2 = "Le nom de la fonction peut" EnableCharacters = "contenir : A..Z, a..z, 0..9 et _" diff --git a/apps/variables.it.i18n b/apps/variables.it.i18n index e32ba5af017..83dfc8418d5 100644 --- a/apps/variables.it.i18n +++ b/apps/variables.it.i18n @@ -4,9 +4,10 @@ Functions = "Funzioni" Sequences = "Successioni" EmptyExpressionBox0 = "Non avete definito nessuna variabile." EmptyFunctionBox0 = "Non avete definito nessuna funzione." -EmptySequenceBox0 = "Non avete definito nessuna successione." +EmptySequenceBox0 = "Non avete definito nessuna" EmptyExpressionBox1 = "Per definire una variabile, digitare :" EmptyFunctionBox1 = "Per definire una funzione, digitare :" +EmptySequenceBox1 = "successione." EmptyExpressionBox2 = "Il nome della variabile può" EmptyFunctionBox2 = "Il nome della funzione può" EnableCharacters = "contenere : A..Z, a..z, 0..9 e _" diff --git a/apps/variables.nl.i18n b/apps/variables.nl.i18n index d951401cf8e..b3be263fbe5 100644 --- a/apps/variables.nl.i18n +++ b/apps/variables.nl.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "Je hebt geen functies gedefinieerd." EmptySequenceBox0 = "Je hebt geen rij gedefinieerd." EmptyExpressionBox1 = "Om een variabele te definiëren, typ:" EmptyFunctionBox1 = "Om een functie te definiëren, typ:" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "De naam van de variabele kan bevatten:" EmptyFunctionBox2 = "De naam van de functie kan bevatten:" EnableCharacters = "A..Z, a..z, 0..9 en _" diff --git a/apps/variables.pt.i18n b/apps/variables.pt.i18n index 5f080d0f989..6cf08c906d3 100644 --- a/apps/variables.pt.i18n +++ b/apps/variables.pt.i18n @@ -7,6 +7,7 @@ EmptyFunctionBox0 = "Nenhuma função definida." EmptySequenceBox0 = "Nenhuma sequência definida." EmptyExpressionBox1 = "Para definir uma, digite :" EmptyFunctionBox1 = "Para definir uma, digite :" +EmptySequenceBox1 = "" EmptyExpressionBox2 = "O nome da variável pode conter:" EmptyFunctionBox2 = "O nome da função pode conter:" EnableCharacters = "A..Z, a..z, 0..9 y _" From 9b3f7edfcdca1979bf093683419e46d3880d4803 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Dec 2020 14:25:28 +0100 Subject: [PATCH 531/560] [poincare/unit] Handle kelvin with prefix Units derived from kelvin (mK, cK, ...) were handled like Celsius and Fahrenheit, causing a bug where multiplications such as 2*pi*_cK to simplify as undef. --- poincare/src/unit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 8b5df09043f..48bbcd1864a 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -869,7 +869,7 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext * (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()) { + 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) From 02d7a02a93bf8a14391b36f5eed5163f9618931a Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Dec 2020 14:51:15 +0100 Subject: [PATCH 532/560] [poincare/unit] Add test --- poincare/test/simplification.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 630759d7d70..e0ce6ebbee0 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -556,6 +556,8 @@ QUIZ_CASE(poincare_simplification_units) { 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) { From e9c953fd6e56be6014c34d0b13cf41feab8b930e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Dec 2020 10:40:53 +0100 Subject: [PATCH 533/560] [shared/interact_curve_view_range] Check orthonormality after zoom Zooming in and out on a graph usually preserves orthonormality. However, if the window has its bounds close to the limits for the values on the axes, zooming can change the ratio. --- apps/shared/interactive_curve_view_range.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 7f38e414213..d1b6f697049 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -112,6 +112,7 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { m_yRange.setMax(yMa, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMi, k_lowerMaxFloat, k_upperMaxFloat); } + setZoomNormalize(isOrthonormal()); m_offscreenYAxis *= ratio; } From 5bc9579db67164a84d4fc1bcc5005065e55c90f4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 22 Dec 2020 16:29:43 +0100 Subject: [PATCH 534/560] [shared/interactive_curve_view_range] Test zoom method --- apps/graph/test/ranges.cpp | 48 ++++++++++++++++++++++ apps/shared/zoom_curve_view_controller.cpp | 2 +- apps/shared/zoom_curve_view_controller.h | 2 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index 4d13c154679..d73f1e3ccd8 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -125,4 +125,52 @@ QUIZ_CASE(graph_ranges_several_functions) { } } +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); +} + } diff --git a/apps/shared/zoom_curve_view_controller.cpp b/apps/shared/zoom_curve_view_controller.cpp index 45c25078d16..77778be78e2 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 97698ae76c1..da96a4d8d4c 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; From 7146eff7ee2967155c6238d81a56adf819b9f8c2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 22 Dec 2020 13:05:40 +0100 Subject: [PATCH 535/560] [poincare/src] Use reduce before removeUnit With deepReduce(), when a simplifiaction was interrupted, expression could contain Undefined children, which would trigger the assert in removeUnit(). With reduce(), if a simplification is interrupted, the entire expression becomes Undefined, which is handled by removeUnits(). --- poincare/src/multiplication.cpp | 6 +++--- poincare/src/unit_convert.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 29f9860fcb5..1be7fa526ae 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -425,8 +425,8 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * re Expression 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 = deepReduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext)); - self = removeUnit(&units); + self = self.reduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext)); + self = self.removeUnit(&units); if (self.isUndefined() || units.isUninitialized()) { // TODO: handle error "Invalid unit" @@ -501,7 +501,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * re // Apply simplifications if (unitsAccu.numberOfChildren() > 0) { // Divide by derived units - units = Division::Builder(units, unitsAccu.clone()).deepReduce(*reductionContext); + units = Division::Builder(units, unitsAccu.clone()).reduce(*reductionContext); Expression newUnits; // Separate units and generated values units = units.removeUnit(&newUnits); diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index 86252bda45a..76b384a16d3 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -50,7 +50,7 @@ void UnitConvert::deepReduceChildren(ExpressionNode::ReductionContext reductionC reductionContext.target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, ExpressionNode::UnitConversion::None); - // Don't transform the targetted unit + // Don't transform the targeted unit childAtIndex(1).deepReduce(reductionContextKeepUnitAsIs); } @@ -77,7 +77,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext * reduc reductionContext->target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined); Expression unit; - Expression childWithoutUnit = childAtIndex(1).clone().deepReduce(reductionContextWithUnits).removeUnit(&unit); + Expression childWithoutUnit = childAtIndex(1).clone().reduce(reductionContextWithUnits).removeUnit(&unit); if (childWithoutUnit.isUndefined() || unit.isUninitialized()) { // There is no unit on the right return replaceWithUndefinedInPlace(); @@ -104,7 +104,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext * reduc // Divide the left member by the new unit Expression division = Division::Builder(childAtIndex(0), unit.clone()); - division = division.deepReduce(*reductionContext); + division = division.reduce(*reductionContext); Expression divisionUnit; division = division.removeUnit(&divisionUnit); if (!divisionUnit.isUninitialized()) { From e09bd0a18cc15344653a02a4af8cdfaff1ca9946 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 22 Dec 2020 15:29:30 +0100 Subject: [PATCH 536/560] [poincare] Factorize reduce and removeUnit methods --- .../additional_outputs/unit_list_controller.cpp | 3 +-- apps/calculation/calculation.cpp | 3 +-- apps/shared/poincare_helpers.h | 5 +++++ poincare/include/poincare/expression.h | 1 + poincare/src/expression.cpp | 6 ++++++ poincare/src/multiplication.cpp | 9 +++------ poincare/src/unit_convert.cpp | 5 ++--- poincare/test/expression_properties.cpp | 9 +++------ 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index f3463b2488f..869826d5e4f 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -28,8 +28,7 @@ void UnitListController::setExpression(Poincare::Expression e) { 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); + PoincareHelpers::ReduceAndRemoveUnit(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &units); double value = Shared::PoincareHelpers::ApproximateToScalar(copy, App::app()->localContext()); ExpressionNode::ReductionContext reductionContext( App::app()->localContext(), diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index b4c898b916a..c1ee3692ed1 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -255,8 +255,7 @@ 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); + 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; } diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 2542173213b..6c4eb0a77e9 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -77,6 +77,11 @@ inline void Reduce(Poincare::Expression * e, Poincare::Context * context, Poinca *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); diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 4818643e6c9..d75a684cfbe 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -248,6 +248,7 @@ class Expression : public TreeHandle { 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 diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index b3377d60344..051a2c3d283 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -827,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); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 1be7fa526ae..bdf058e5b14 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -425,8 +425,7 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * re Expression 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.reduce(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext)); - self = self.removeUnit(&units); + self = self.reduceAndRemoveUnit(ExpressionNode::ReductionContext::NonInvasiveReductionContext(*reductionContext), &units); if (self.isUndefined() || units.isUninitialized()) { // TODO: handle error "Invalid unit" @@ -500,11 +499,9 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext * re } // Apply simplifications if (unitsAccu.numberOfChildren() > 0) { - // Divide by derived units - units = Division::Builder(units, unitsAccu.clone()).reduce(*reductionContext); Expression newUnits; - // Separate units and generated values - 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); diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index 76b384a16d3..876c3368098 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -77,7 +77,7 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext * reduc reductionContext->target(), ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined); Expression unit; - Expression childWithoutUnit = childAtIndex(1).clone().reduce(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(); @@ -104,9 +104,8 @@ Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext * reduc // Divide the left member by the new unit Expression division = Division::Builder(childAtIndex(0), unit.clone()); - division = division.reduce(*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(); diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 0fdc1e49309..c5883006aee 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -401,13 +401,11 @@ void assert_reduced_expression_unit_is(const char * expression, const char * uni Shared::GlobalContext globalContext; 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); } @@ -425,9 +423,8 @@ void assert_additional_results_compute_to(const char * expression, const char * assert(length <= maxNumberOfResults); Expression additional[maxNumberOfResults]; ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, unitFormat, User, ReplaceAllSymbolsWithUndefined, DefaultUnitConversion); - Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); Expression units; - e = e.removeUnit(&units); + Expression e = parse_expression(expression, &globalContext, false).reduceAndRemoveUnit(reductionContext, &units); double value = e.approximateToScalar(&globalContext, Cartesian, Degree); if (!Unit::ShouldDisplayAdditionalOutputs(value, units, unitFormat)) { From ea4770fc81835f7ce23f58ded4ea6f519ea4bfc3 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 28 Dec 2020 11:37:39 +0100 Subject: [PATCH 537/560] [shared/interactive_curve_view_range] Change normalization tolerance The loss of significance that occurs when subtracting the bounds of the axes must be taken into account when asserting whether the range is orthonormal. --- apps/shared/interactive_curve_view_range.cpp | 21 ++++++++++++-------- apps/shared/interactive_curve_view_range.h | 8 ++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index d1b6f697049..0f314b0c238 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -157,12 +157,8 @@ void InteractiveCurveViewRange::normalize(bool forceChangeY) { m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); - /* When the coordinates reach 10^6, the float type is not precise enough to - * properly normalize. */ - // FIXME : Fine a more precise way to filter the edge cases - constexpr float limit = 1e6f; - assert(isOrthonormal() || xMin() < -limit || xMax() > limit || yMin() < -limit || yMax() > limit); - (void) limit; // Silence compilation warning about unused variable. + /* The range should be close to orthonormal, unless it has been clipped because the maximum bounds have been reached. */ + assert(isOrthonormal() || xMin() <= - k_lowerMaxFloat || xMax() >= k_lowerMaxFloat || yMin() <= - k_lowerMaxFloat || yMax() >= k_lowerMaxFloat); setZoomNormalize(isOrthonormal()); } @@ -281,8 +277,17 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to } bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { + if (tolerance == 0.f) { + 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. */ + tolerance = std::pow(2.f, - std::log2(std::min(1.f - xr, 1.f - yr)) - 23.f + 2.f); + } float ratio = (yMax() - yMin()) / (xMax() - xMin()); - float ratioDifference = std::fabs(std::log(ratio / NormalYXRatio())); - return ratioDifference <= tolerance; + return ratio <= NormalYXRatio() + tolerance && ratio >= NormalYXRatio() - tolerance; } } diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index dfde5c4840c..f7b860090e1 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -24,9 +24,9 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { } static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } - /* A tolerance of 0.001 is necessary to cover the imprecision with the - * largest ranges, around 10^7 */ - bool isOrthonormal(float tolerance = 1e-3f) const; + /* If the tolerance is null, isOrthonormal will adapt the tolerance to take + * the loss of significance when changing the ratio into account. */ + bool isOrthonormal(float tolerance = 0.f) const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate); uint32_t rangeChecksum() override; @@ -62,7 +62,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { 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 = 0.7f; + constexpr static float k_orthonormalTolerance = 0.24f; 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. From 5ce5f4fbac9f25d2c67ecab46c6ff1cae8652096 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 28 Dec 2020 16:19:16 +0100 Subject: [PATCH 538/560] [statistics] Fix glitch with histogram parameters In the window to change the histogram parameters, it was possible to set a rectangle width that was considered valid, but would make the starting abscissa invalid, while triggering no error message. The message would only show if the user closed the window and opened it again. e.g. : Set a series with only one value 1e10 Change its starting abscissa to 0 Change its rectangle width to 1, confirm and close the menu Re-open the menu, a message appears, and stays onscreen when leaving the menu. --- apps/statistics/histogram_parameter_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index f7d538d0b84..6da2c4737e7 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -90,7 +90,7 @@ bool HistogramParameterController::setParameterAtIndex(int parameterIndex, doubl // 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); From fff514d41043dd9e6d558b7566219e0939cc8553 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 28 Dec 2020 14:22:05 +0100 Subject: [PATCH 539/560] [poincare/unit] Fix temperature conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The program would crash when trying to evaluate a unit conversion of the form "x→_U" where U was a unit of temperature and x did not have any unit. --- poincare/src/unit.cpp | 2 +- poincare/test/simplification.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 48bbcd1864a..35bc19e7042 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -834,7 +834,7 @@ Expression Unit::ConvertTemperatureUnits(Expression e, Unit unit, ExpressionNode Expression startUnit; e = e.removeUnit(&startUnit); - if (startUnit.type() != ExpressionNode::Type::Unit) { + if (startUnit.isUninitialized() || startUnit.type() != ExpressionNode::Type::Unit) { return Undefined::Builder(); } const Representative * startRepr = static_cast(startUnit).representative(); diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index e0ce6ebbee0..173f0164378 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -1267,6 +1267,7 @@ 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_parsed_expression_simplify_to("0_K→_°C", "-273.15×_°C"); assert_parsed_expression_simplify_to("0_°C→_K", "273.15×_K"); From 48d93584e742e34957b67b81454b8617930cf11e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 29 Dec 2020 10:55:43 +0100 Subject: [PATCH 540/560] [poincare/parametered_expression] Fix variables detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The method getVariables would not correctly remove the parameter from the list of variables. e.g. In the expression "box+y×int(z,x,0,1)", when trying to remove x from the list of variables, the letter x would be removed from the name "box" instead. --- poincare/src/parametered_expression.cpp | 9 ++++++--- poincare/test/expression_properties.cpp | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/poincare/src/parametered_expression.cpp b/poincare/src/parametered_expression.cpp index 57bc73dcfd9..289c32d0077 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/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index c5883006aee..57eaffdbf70 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -346,6 +346,11 @@ 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); } 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) { From d26ba0250b3b8dbd23d9a55661979f47c6701738 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 29 Dec 2020 11:17:12 +0100 Subject: [PATCH 541/560] [statistics] Missing include --- apps/statistics/histogram_parameter_controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index 6da2c4737e7..f057c922f31 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 From 4209f0a26c1a97edd190e407bb4b2f6099a6476f Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 29 Dec 2020 15:42:47 +0100 Subject: [PATCH 542/560] [poincare/function] getVariables always gets the argument When applied to a function f(x) = constant, the method getVariables would not find any, as the search is performed on the function's definition. Thus, when using the "Fill with formula" tool in Statistics, if the formula to fill V1 is "V1=f(V2)", the formula will be considered constant and applied to all values in V1. However, if V1 is longer than V2, the program will attempt to read undefined values of V2. With this change, the formula will only be applied to lines in V1 for which there is a corresponding line in V2. --- poincare/src/function.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index 023a5ed0f03..16832db3322 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -38,6 +38,10 @@ 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; } From f6c91f7dbe3dd947cba99ee31de58fcf73f90157 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Tue, 29 Dec 2020 17:16:10 +0100 Subject: [PATCH 543/560] [poincare/function] Add tests on getVariables --- poincare/test/expression_properties.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 57eaffdbf70..337bcca1753 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -336,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); @@ -351,6 +351,26 @@ QUIZ_CASE(poincare_properties_get_variables) { 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, Preferences::UnitFormat unitFormat = Metric, ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition) { From 7a6c0e6e7d8edde5018b2b9fee619fbcd73a922c Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Fri, 18 Dec 2020 08:31:01 +0100 Subject: [PATCH 544/560] [NL] fix translations --- apps/toolbox.nl.i18n | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index f0cc8afe49e..7cf219fbe25 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -134,12 +134,12 @@ Dot = "Inwendig product" Cross = "Kruisproduct" NormVector = "Norm" Sort = "Sorteer oplopend " -InvSort = "Sort aflopend" +InvSort = "Sorteer aflopend" Maximum = "Maximum" Minimum = "Minimum" -Floor = "Floor" -FracPart = "Fractioneel deel" -Ceiling = "Ceiling" +Floor = "Afronden naar beneden" +FracPart = "Decimaal deel" +Ceiling = "Afronden naar boven" Rounding = "Afronding op n cijfers" HyperbolicCosine = "Hyperbolische cosinus" HyperbolicSine = "Hyperbolische sinus" From f449db62c308751273bbe4aef947f8b770a04ccb Mon Sep 17 00:00:00 2001 From: Martijn Oost Date: Mon, 4 Jan 2021 09:51:49 +0100 Subject: [PATCH 545/560] [NL] fix omission sigma in toolbox While changing from using sigma squared to just sigma I guess we deleted one character too much. --- apps/toolbox.nl.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/toolbox.nl.i18n b/apps/toolbox.nl.i18n index 7cf219fbe25..b73b1e76f21 100644 --- a/apps/toolbox.nl.i18n +++ b/apps/toolbox.nl.i18n @@ -154,7 +154,7 @@ RandomAndApproximation = "Random en afronding" RandomFloat = "Decimaal getal in [0,1)" RandomInteger = "Random geheel getal in [a,b]" PrimeFactorDecomposition = "Ontbinden in factoren" -NormCDF = "P(X Date: Mon, 4 Jan 2021 15:19:24 +0100 Subject: [PATCH 546/560] [statistics] Fix use of uninitialized variable --- apps/statistics/histogram_parameter_controller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index f057c922f31..3ba5cff2baf 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -26,8 +26,12 @@ 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, extractParameterAtIndex(1)); + setParameterAtIndex(1, parameterAtIndex1); FloatParameterController::viewWillAppear(); } From 89107da1ae695cedb60867e18a3b9e08beb671e3 Mon Sep 17 00:00:00 2001 From: Roberta Rabotti Date: Tue, 5 Jan 2021 10:12:47 +0100 Subject: [PATCH 547/560] Update toolbox.it.i18n --- apps/code/toolbox.it.i18n | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index 5fd87594971..d35b67cffa1 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -1,4 +1,4 @@ Functions = "Funzioni" Catalog = "Catalogo" Modules = "Moduli" -LoopsAndTests = "Loops e test" +LoopsAndTests = "Cicli e test" From 89c50509d8408e5d6884a264a4aa393327aef2a4 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 4 Jan 2021 14:41:32 +0100 Subject: [PATCH 548/560] [shared] InteractiveCurveViewRange::isOrthonormal The old method isOrthonormal has been split into two : - shouldBeNormalized tests whether the range is close enough to a normal range - isOrthonormal tests whether the range is strictly orthonormal, but takes into account imprecisions of floating-point arithmetic --- apps/regression/store.cpp | 2 +- apps/shared/interactive_curve_view_range.cpp | 44 ++++++++++++++++---- apps/shared/interactive_curve_view_range.h | 10 +++-- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index f2615aceabc..e081924f586 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -163,7 +163,7 @@ void Store::setDefault() { m_xRange.setMax(xMax); m_yRange.setMin(yMin); m_yRange.setMax(yMax); - bool revertToOrthonormal = isOrthonormal(k_orthonormalTolerance); + bool revertToOrthonormal = shouldBeNormalized(); float range = xMax - xMin; setXMin(xMin - k_displayHorizontalMarginRatio * range); diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 0f314b0c238..a45bad573c0 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -157,8 +157,13 @@ void InteractiveCurveViewRange::normalize(bool forceChangeY) { 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. */ - assert(isOrthonormal() || xMin() <= - k_lowerMaxFloat || xMax() >= k_lowerMaxFloat || yMin() <= - k_lowerMaxFloat || yMax() >= k_lowerMaxFloat); + /* 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()); } @@ -193,7 +198,7 @@ void InteractiveCurveViewRange::setDefault() { 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); - if (m_delegate->defaultRangeIsNormalized() || isOrthonormal(k_orthonormalTolerance)) { + 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. */ @@ -276,8 +281,27 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to setZoomNormalize(isOrthonormal()); } -bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { - if (tolerance == 0.f) { +bool InteractiveCurveViewRange::shouldBeNormalized() const { + float ratio = (yMax() - yMin()) / (xMax() - xMin()); + return ratio >= NormalYXRatio() / k_orthonormalTolerance && ratio <= NormalYXRatio() * k_orthonormalTolerance; +} + +bool InteractiveCurveViewRange::isOrthonormal() const { + float significantBits = normalizationSignificantBits(); + if (significantBits <= 0) { + return false; + } + float ratio = (yMax() - yMin()) / (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) @@ -285,9 +309,11 @@ bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const { * 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. */ - tolerance = std::pow(2.f, - std::log2(std::min(1.f - xr, 1.f - yr)) - 23.f + 2.f); - } - float ratio = (yMax() - yMin()) / (xMax() - xMin()); - return ratio <= NormalYXRatio() + tolerance && ratio >= NormalYXRatio() - tolerance; + 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 f7b860090e1..d56f43bbcdb 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -24,9 +24,9 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { } static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); } - /* If the tolerance is null, isOrthonormal will adapt the tolerance to take - * the loss of significance when changing the ratio into account. */ - bool isOrthonormal(float tolerance = 0.f) const; + /* The method isOrthonormal takes the loss of significance when changing the + * ratio into account. */ + bool isOrthonormal() const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate); uint32_t rangeChecksum() override; @@ -62,7 +62,7 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { 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 = 0.24f; + 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. @@ -78,11 +78,13 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { * 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 shouldBeNormalized() const; virtual bool hasDefaultRange() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); } InteractiveCurveViewRangeDelegate * m_delegate; private: float offscreenYAxis() const override { return m_offscreenYAxis; } + int normalizationSignificantBits() const; float m_offscreenYAxis; bool m_zoomAuto; From abefc800d9f467f64cefed26e53f2edf415086de Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 4 Jan 2021 16:48:51 +0100 Subject: [PATCH 549/560] [interactive_curve_view_range] Fix variable type --- apps/shared/interactive_curve_view_range.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index a45bad573c0..770fd43efe4 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -287,7 +287,7 @@ bool InteractiveCurveViewRange::shouldBeNormalized() const { } bool InteractiveCurveViewRange::isOrthonormal() const { - float significantBits = normalizationSignificantBits(); + int significantBits = normalizationSignificantBits(); if (significantBits <= 0) { return false; } From 878685b83c58cf4417aee7c15a28361cc752aec7 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 4 Jan 2021 16:35:18 +0100 Subject: [PATCH 550/560] [interactive_curve_view_range] Test isOrthonormal --- apps/graph/test/ranges.cpp | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index d73f1e3ccd8..c1f75478ca7 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -173,4 +173,44 @@ QUIZ_CASE(graph_ranges_zoom) { 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); +} + } From 5b106f091d98c8396c838ab01560d06b470ab83e Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Mon, 4 Jan 2021 15:48:16 +0100 Subject: [PATCH 551/560] [interactive_curve_view_range] Fix isOrthonormal when using Navigate The method isOrthonormal takes into account the "offscreen" portion of the Y axis. This fixes a bug where zooming when using the Navigate option would remove the orthonormality marker while the graph was still orthonormal. --- apps/shared/interactive_curve_view_range.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 770fd43efe4..c4fec69bb96 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -112,8 +112,8 @@ void InteractiveCurveViewRange::zoom(float ratio, float x, float y) { m_yRange.setMax(yMa, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(yMi, k_lowerMaxFloat, k_upperMaxFloat); } - setZoomNormalize(isOrthonormal()); m_offscreenYAxis *= ratio; + setZoomNormalize(isOrthonormal()); } void InteractiveCurveViewRange::panWithVector(float x, float y) { @@ -291,7 +291,7 @@ bool InteractiveCurveViewRange::isOrthonormal() const { if (significantBits <= 0) { return false; } - float ratio = (yMax() - yMin()) / (xMax() - xMin()); + 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 From 990c45b661a9daa8e7fb87237c4e0459d0f3134c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Mon, 4 Jan 2021 17:56:44 +0100 Subject: [PATCH 552/560] [apps/shared] Prevent label method from being called on uninitialized chars --- apps/shared/curve_view.cpp | 13 ++++++++++--- apps/shared/curve_view_range.cpp | 4 ++-- apps/shared/curve_view_range.h | 2 +- apps/shared/interactive_curve_view_range.cpp | 7 ------- apps/shared/interactive_curve_view_range.h | 1 - 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 6ea0db84934..1f1b3835616 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -190,6 +190,8 @@ int CurveView::numberOfLabels(Axis axis) const { float minLabel = std::ceil(min(axis)/labelStep); float maxLabel = std::floor(max(axis)/labelStep); int numberOfLabels = maxLabel - minLabel + 1; + // Assert labels are up to date + assert(m_drawnRangeVersion == m_curveViewRange->rangeChecksum()); assert(numberOfLabels <= (axis == Axis::Horizontal ? k_maxNumberOfXLabels : k_maxNumberOfYLabels)); return numberOfLabels; } @@ -226,19 +228,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 { diff --git a/apps/shared/curve_view_range.cpp b/apps/shared/curve_view_range.cpp index 93ae6bc6d2d..f88e6b22e46 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 = 7*sizeof(float); 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 5867ab566d2..b463389ceef 100644 --- a/apps/shared/curve_view_range.h +++ b/apps/shared/curve_view_range.h @@ -12,7 +12,7 @@ class CurveViewRange { X, Y }; - virtual uint32_t rangeChecksum(); + uint32_t rangeChecksum(); virtual float xMin() const = 0; virtual float xMax() const = 0; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index c4fec69bb96..45b212eabff 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -19,13 +19,6 @@ void InteractiveCurveViewRange::setDelegate(InteractiveCurveViewRangeDelegate * } } -uint32_t InteractiveCurveViewRange::rangeChecksum() { - float data[] = {xMin(), xMax(), yMin(), yMax()}; - 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)); -} - void InteractiveCurveViewRange::setZoomAuto(bool v) { if (m_zoomAuto == v) { return; diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index d56f43bbcdb..9a4ae27c253 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -29,7 +29,6 @@ class InteractiveCurveViewRange : public MemoizedCurveViewRange { bool isOrthonormal() const; void setDelegate(InteractiveCurveViewRangeDelegate * delegate); - uint32_t rangeChecksum() override; bool zoomAuto() const { return m_zoomAuto; } void setZoomAuto(bool v); From 9f0c7a87fd1b3363c64d332715e887e270cc75d2 Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 5 Jan 2021 09:30:14 +0100 Subject: [PATCH 553/560] [apps/shared] Use sizeof data --- apps/shared/curve_view_range.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shared/curve_view_range.cpp b/apps/shared/curve_view_range.cpp index f88e6b22e46..46196263df3 100644 --- a/apps/shared/curve_view_range.cpp +++ b/apps/shared/curve_view_range.cpp @@ -11,7 +11,7 @@ namespace Shared { uint32_t CurveViewRange::rangeChecksum() { float data[7] = {xMin(), xMax(), yMin(), yMax(), xGridUnit(), yGridUnit(), offscreenYAxis()}; - size_t dataLengthInBytes = 7*sizeof(float); + 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)); } From a42208f773e5239a91c7802129d3473ef028418c Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 5 Jan 2021 16:21:00 +0100 Subject: [PATCH 554/560] [apps/shared] Handle null labelStep in numberOfLabels --- apps/shared/curve_view.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 1f1b3835616..9428d4e98a0 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -187,6 +187,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; From 83722342f7c4be6ebc599747388e48716055741a Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 5 Jan 2021 15:54:15 +0100 Subject: [PATCH 555/560] [apps/statistics] Reload CurveView before drawing histogram labels --- apps/shared/curve_view.cpp | 5 +++-- apps/statistics/histogram_controller.cpp | 3 +++ apps/statistics/histogram_view.cpp | 8 ++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 9428d4e98a0..e5f516ad0ba 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -193,8 +193,6 @@ int CurveView::numberOfLabels(Axis axis) const { float minLabel = std::ceil(min(axis)/labelStep); float maxLabel = std::floor(max(axis)/labelStep); int numberOfLabels = maxLabel - minLabel + 1; - // Assert labels are up to date - assert(m_drawnRangeVersion == m_curveViewRange->rangeChecksum()); assert(numberOfLabels <= (axis == Axis::Horizontal ? k_maxNumberOfXLabels : k_maxNumberOfYLabels)); return numberOfLabels; } @@ -378,6 +376,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))); diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index d0294d206ef..55c839c1695 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -27,6 +27,9 @@ HistogramController::HistogramController(Responder * parentResponder, InputEvent void HistogramController::setCurrentDrawnSeries(int series) { initYRangeParameters(series); + /* The range of it CurveView has changed, the CurveView must be reloaded. + * See comment in HistogramView::drawRect. */ + m_view.dataViewAtIndex(series)->CurveView::reload(); } StackViewController * HistogramController::stackController() { diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index 9b1d028f4a0..4166f1ab685 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -38,6 +38,14 @@ void HistogramView::reloadSelectedBar() { } void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { + /* When setting the current drawn series, the histogram's CurveView range is + * updated along the Vertical axis. To call drawLabelsAndGraduations, + * CurveView must be reloaded (in setCurrentDrawnSeries method) 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_controller->setCurrentDrawnSeries(m_series); ctx->fillRect(rect, KDColorWhite); drawAxis(ctx, rect, Axis::Horizontal); From 5e7d93b3312b25e9f1de14499f455199da06ee2f Mon Sep 17 00:00:00 2001 From: Hugo Saint-Vignes Date: Tue, 5 Jan 2021 16:41:53 +0100 Subject: [PATCH 556/560] [apps/statistics] Update and replace comment --- apps/statistics/histogram_controller.cpp | 10 ++++++++-- apps/statistics/histogram_view.cpp | 8 -------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index 55c839c1695..79f524a2745 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -27,8 +27,14 @@ HistogramController::HistogramController(Responder * parentResponder, InputEvent void HistogramController::setCurrentDrawnSeries(int series) { initYRangeParameters(series); - /* The range of it CurveView has changed, the CurveView must be reloaded. - * See comment in HistogramView::drawRect. */ + /* 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(); } diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index 4166f1ab685..9b1d028f4a0 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -38,14 +38,6 @@ void HistogramView::reloadSelectedBar() { } void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { - /* When setting the current drawn series, the histogram's CurveView range is - * updated along the Vertical axis. To call drawLabelsAndGraduations, - * CurveView must be reloaded (in setCurrentDrawnSeries method) 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_controller->setCurrentDrawnSeries(m_series); ctx->fillRect(rect, KDColorWhite); drawAxis(ctx, rect, Axis::Horizontal); From e8887a4e701771868255ef9321aaed12b8959bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 6 Jan 2021 16:30:07 +0100 Subject: [PATCH 557/560] [poincare] Remove unused variable --- poincare/src/expression.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index 051a2c3d283..a5c5f2b8fc3 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -1151,7 +1151,6 @@ 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 b = start; - double a = b; double c = start+step; double fb = evaluation(b, context, complexFormat, angleUnit, this, symbol, &expression); double fa = fb; @@ -1171,7 +1170,6 @@ void Expression::bracketRoot(const char * symbol, double start, double step, dou result[1] = c; return; } - a = b; fa = fb; b = c; fb = fc; From 49b74fb475f14c20119e57152ba75b53774c8d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 6 Jan 2021 16:31:07 +0100 Subject: [PATCH 558/560] build: Version 15.3.0 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index eee3c264119..cd87a62f6ee 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 15.2.0 +EPSILON_VERSION ?= 15.3.0 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US From ed2d5e3b2d2522a96cdd91bdbd524ef3bf9e5620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 7 Jan 2021 12:46:43 +0100 Subject: [PATCH 559/560] [simulator/android] Bump targetSdkVersion --- ion/src/simulator/android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/simulator/android/build.gradle b/ion/src/simulator/android/build.gradle index 8dfcf965a7b..5de311bce23 100644 --- a/ion/src/simulator/android/build.gradle +++ b/ion/src/simulator/android/build.gradle @@ -36,7 +36,7 @@ android { defaultConfig { applicationId "com.numworks.calculator" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 29 def (major, minor, patch) = System.getenv('EPSILON_VERSION').toLowerCase().tokenize('.').collect{it.toInteger()} versionCode major*1000000 + minor*10000 + patch * 100 versionName System.getenv('EPSILON_VERSION') From 11ef4bd99692aa1e45bdb57539a2eec8506552cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 7 Jan 2021 12:47:23 +0100 Subject: [PATCH 560/560] build: Version 15.3.1 --- build/config.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/config.mak b/build/config.mak index cd87a62f6ee..5a2b9d3196a 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 15.3.0 +EPSILON_VERSION ?= 15.3.1 EPSILON_APPS ?= calculation graph code statistics probability solver sequence regression settings EPSILON_I18N ?= en fr nl pt it de es EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US