mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[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
This commit is contained in:
committed by
Émilie Feral
parent
0308b18400
commit
552dca9494
@@ -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 \
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
#include "global_context.h"
|
||||
#include "continuous_function_cache.h"
|
||||
#include "function.h"
|
||||
#include "range_1D.h"
|
||||
#include <poincare/symbol.h>
|
||||
@@ -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 @@ public:
|
||||
Poincare::Coordinate2D<double> 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<ContinuousFunctionCache *>(&m_cache); }
|
||||
private:
|
||||
constexpr static float k_polarParamRangeSearchNumberOfPoints = 100.0f; // This is ad hoc, no special justification
|
||||
typedef Poincare::Coordinate2D<double> (*ComputePointOfInterest)(Poincare::Expression e, char * symbol, double start, double step, double max, Poincare::Context * context);
|
||||
@@ -115,6 +122,7 @@ private:
|
||||
RecordDataBuffer * recordData() const;
|
||||
template<typename T> Poincare::Coordinate2D<T> templatedApproximateAtParameter(T t, Poincare::Context * context) const;
|
||||
Model m_model;
|
||||
ContinuousFunctionCache m_cache;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
146
apps/shared/continuous_function_cache.cpp
Normal file
146
apps/shared/continuous_function_cache.cpp
Normal file
@@ -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<float> 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<float>(NAN, NAN);
|
||||
}
|
||||
if (function->plotType() == ContinuousFunction::PlotType::Cartesian) {
|
||||
return Poincare::Coordinate2D<float>(t, m_cache[iRes]);
|
||||
}
|
||||
assert(m_startOfCache == 0);
|
||||
return Poincare::Coordinate2D<float>(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<float> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
48
apps/shared/continuous_function_cache.h
Normal file
48
apps/shared/continuous_function_cache.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef SHARED_CONTINUOUS_FUNCTION_CACHE_H
|
||||
#define SHARED_CONTINUOUS_FUNCTION_CACHE_H
|
||||
|
||||
#include <ion/display.h>
|
||||
#include <poincare/context.h>
|
||||
#include <poincare/coordinate_2D.h>
|
||||
|
||||
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<float> 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
|
||||
Reference in New Issue
Block a user