[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
This commit is contained in:
Arthur Camouseigt
2020-07-03 17:15:38 +02:00
committed by Émilie Feral
parent 688394abce
commit 8434da60ef
11 changed files with 301 additions and 60 deletions

View File

@@ -1,5 +1,8 @@
#include "cache_context.h"
#include "sequence.h"
#include "sequence_store.h"
#include "../shared/poincare_helpers.h"
#include <poincare/serialization_helper.h>
#include <cmath>
using namespace Poincare;
@@ -16,13 +19,30 @@ CacheContext<T>::CacheContext(Context * parentContext) :
template<typename T>
const Expression CacheContext<T>::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<Symbol &>(static_cast<const Symbol &>(symbol));
return Float<T>::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]);
if (strcmp(symbol.name()+1, "(n)") == 0 || strcmp(symbol.name()+1, "(n+1)") == 0) {
return Float<T>::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<T>::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<T>();
return Float<T>::Builder(seq.valueAtRank<T>(x, m_sequenceContext));
}
}
return ContextWithParent::expressionForSymbolAbstract(symbol, clone);
}
@@ -34,7 +54,7 @@ void CacheContext<T>::setValueForSymbol(T value, const Poincare::Symbol & symbol
template<typename T>
int CacheContext<T>::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<T>::nameIndexForSymbol(const Poincare::Symbol & symbol) {
template<typename T>
int CacheContext<T>::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;
}

View File

@@ -14,10 +14,12 @@ public:
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;
};
}

View File

@@ -118,7 +118,30 @@ T Sequence::templatedApproximateAtAbscissa(T x, SequenceContext * sqctx) const {
}
template<typename T>
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<T>(sequenceIndex) > n || sqctx->independantSequenceRank<T>(sequenceIndex) < 0) {
// Reset cache indexes and cache values
sqctx->setIndependantSequenceRank<T>(-1, sequenceIndex);
for (int i = 0 ; i < MaxRecurrenceDepth+1; i++) {
sqctx->setIndependantSequenceValue(NAN, sequenceIndex, i);
}
}
while(sqctx->independantSequenceRank<T>(sequenceIndex) < n) {
sqctx->stepSequenceAtIndex<T>(sequenceIndex);
}
/* In case we have sqctx->independantSequenceRank<T>(sequenceIndex) = n, we can return the
* value */
T value = sqctx->independantSequenceValue<T>(sequenceIndex, 0);
return value;
}
template<typename T>
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<T> ctx = CacheContext<T>(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<T>(sequenceIndex);
for (int i = 0; i < MaxNumberOfSequences; i++) {
for (int j = 0; j < MaxRecurrenceDepth+1; j++) {
values[i][j] = sqctx->valueOfSequenceAtPreviousRank<T>(i, j);
if (sequenceIndex != -1 && sqctx->independantSequenceRank<T>(i) != independantRank) {
int offset = independantRank - sqctx->independantSequenceRank<T>(i);
if (offset != 0) {
for (int j = MaxRecurrenceDepth; j >= 0; j--) {
values[i][j] = j-offset < 0 ? NAN : sqctx->independantSequenceValue<T>(i, j-offset);
}
}
} else {
for (int j = 0; j < MaxRecurrenceDepth+1; j++) {
values[i][j] = sequenceIndex != -1 ? sqctx->independantSequenceValue<T>(i, j) : sqctx->valueOfSequenceAtPreviousRank<T>(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<T>(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<T>(firstInitialConditionExpressionReduced(sqctx), sqctx);
return PoincareHelpers::ApproximateWithValueForSymbol(firstInitialConditionExpressionReduced(sqctx), unknownN, (T)NAN, &ctx);
}
if (n == initialRank()+1) {
return PoincareHelpers::ApproximateToScalar<T>(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>(double, SequenceContext*) const;
template float Sequence::templatedApproximateAtAbscissa<float>(float, SequenceContext*) const;
template double Sequence::approximateToNextRank<double>(int, SequenceContext*) const;
template float Sequence::approximateToNextRank<float>(int, SequenceContext*) const;
template double Sequence::approximateToNextRank<double>(int, SequenceContext*, int) const;
template float Sequence::approximateToNextRank<float>(int, SequenceContext*, int) const;
template double Sequence::valueAtRank<double>(int, SequenceContext *);
template float Sequence::valueAtRank<float>(int, SequenceContext *);
}

View File

@@ -65,7 +65,8 @@ public:
Poincare::Coordinate2D<double> evaluateXYAtParameter(double x, Poincare::Context * context) const override {
return Poincare::Coordinate2D<double>(x,templatedApproximateAtAbscissa(x, static_cast<SequenceContext *>(context)));
}
template<typename T> T approximateToNextRank(int n, SequenceContext * sqctx) const;
template<typename T> T approximateToNextRank(int n, SequenceContext * sqctx, int sequenceIndex = -1) const;
template<typename T> 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

View File

@@ -1,5 +1,7 @@
#include "sequence_context.h"
#include "sequence_store.h"
#include "cache_context.h"
#include "../shared/poincare_helpers.h"
#include <cmath>
using namespace Poincare;
@@ -9,63 +11,104 @@ namespace Sequence {
template<typename T>
TemplatedSequenceContext<T>::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<typename T>
T TemplatedSequenceContext<T>::valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const {
return m_values[sequenceIndex][rank];
return m_commonRankValues[sequenceIndex][rank];
}
template<typename T>
void TemplatedSequenceContext<T>::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<typename T>
bool TemplatedSequenceContext<T>::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<typename T>
void TemplatedSequenceContext<T>::step(SequenceStore * sequenceStore, SequenceContext * sqctx) {
/* Shift values */
for (int i = 0; i < MaxNumberOfSequences; i++) {
void TemplatedSequenceContext<T>::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<T*>(&m_commonRankValues);
rank = m_commonRank;
} else {
start = sequenceIndex;
stop = sequenceIndex + 1;
sequenceArray = reinterpret_cast<T*>(&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<T>(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<T>(rank, sqctx, sequenceIndex) : NAN;
}
}
}
@@ -73,5 +116,7 @@ void TemplatedSequenceContext<T>::step(SequenceStore * sequenceStore, SequenceCo
template class TemplatedSequenceContext<float>;
template class TemplatedSequenceContext<double>;
template void * SequenceContext::helper<float>();
template void * SequenceContext::helper<double>();
}

View File

@@ -20,17 +20,37 @@ public:
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 @@ public:
* 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<typename T> 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<typename T> T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) {
return static_cast<TemplatedSequenceContext<T>*>(helper<T>())->valueOfSequenceAtPreviousRank(sequenceIndex, rank);
}
void resetCache() {
m_floatSequenceContext.resetCache();
m_doubleSequenceContext.resetCache();
}
template<typename T> 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<TemplatedSequenceContext<T>*>(helper<T>())->iterateUntilRank(n, m_sequenceStore, this);
}
template<typename T> int independantSequenceRank(int sequenceIndex) {
return static_cast<TemplatedSequenceContext<T>*>(helper<T>())->independantSequenceRank(sequenceIndex);
}
template<typename T> void setIndependantSequenceRank(int rank, int sequenceIndex) {
static_cast<TemplatedSequenceContext<T>*>(helper<T>())->setIndependantSequenceRank(rank, sequenceIndex);
}
template<typename T> T independantSequenceValue(int sequenceIndex, int depth) {
return static_cast<TemplatedSequenceContext<T>*>(helper<T>())->independantSequenceValue(sequenceIndex, depth);
}
template<typename T> void setIndependantSequenceValue(T value, int sequenceIndex, int depth) {
static_cast<TemplatedSequenceContext<T>*>(helper<T>())->setIndependantSequenceValue(value, sequenceIndex, depth);
}
template<typename T> void stepSequenceAtIndex(int sequenceIndex) {
static_cast<TemplatedSequenceContext<T>*>(helper<T>())->step(this, sequenceIndex);
}
SequenceStore * sequenceStore() { return m_sequenceStore; }
private:
TemplatedSequenceContext<float> m_floatSequenceContext;
TemplatedSequenceContext<double> m_doubleSequenceContext;
SequenceStore * m_sequenceStore;
template<typename T> void * helper() { return sizeof(T) == sizeof(float) ? (void*) &m_floatSequenceContext : (void*) &m_doubleSequenceContext; }
};
}

View File

@@ -27,6 +27,7 @@ public:
static constexpr const char * k_sequenceNames[MaxNumberOfSequences] = {
"u", "v", "w"
};
Sequence sequenceAtIndex(int i) { assert(i < MaxNumberOfSequences && i >= 0); return m_sequences[i]; }
private:
const char * modelExtension() const override { return Ion::Storage::seqExtension; }

View File

@@ -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) {