mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[apps/graph] Added tests on graph ranges
Change-Id: I6c080f40c934cd31083be3d19aa0a4d0bb33c5cc
This commit is contained in:
committed by
Émilie Feral
parent
17f39e5e39
commit
a1aefb0cb2
@@ -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))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <quiz.h>
|
||||
#include "../app.h"
|
||||
#include "helper.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace Poincare;
|
||||
@@ -13,17 +13,6 @@ bool floatEquals(float a, float b, float tolerance = 1.f/static_cast<float>(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<ContinuousFunction *>(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<float> 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(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(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(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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
apps/graph/test/helper.cpp
Normal file
16
apps/graph/test/helper.cpp
Normal file
@@ -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<ContinuousFunction *>(store->modelForRecord(record).operator->());
|
||||
f->setPlotType(type, Poincare::Preferences::AngleUnit::Degree, context);
|
||||
f->setContent(definition, context);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
19
apps/graph/test/helper.h
Normal file
19
apps/graph/test/helper.h
Normal file
@@ -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
|
||||
182
apps/graph/test/range.cpp
Normal file
182
apps/graph/test/range.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#include <quiz.h>
|
||||
#include "helper.h"
|
||||
#include <cmath>
|
||||
|
||||
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<float> 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(θ)");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user