What's going on
readAxivity(start = n - 1, end = n), where n is the number of data blocks in the file, errors with:
object 'prevStart' not found
readAxivity() reads blocks [start, end). The block at start is read into prevRaw before the main loop, while the block at end is used only to determine the ending timestamp when it exists. So start = n - 1, end = n requests exactly one block: the final one. Any range that starts earlier works, including a full read.
Reproducible example
Uses only the fixtures shipped with the package, on unmodified GGIRread 1.0.9:
library(GGIRread) # 1.0.9
f <- system.file("testfiles/ax3_testfile.cwa", package = "GGIRread")
n <- (file.info(f)$size - 1024) / 512 # 145 data blocks
readAxivity(f, start = n - 3, end = n, progressBar = FALSE) # ok, 362 rows
readAxivity(f, start = n - 1, end = n, progressBar = FALSE) # Error: object 'prevStart' not found
Same on ax6_testfile.cwa (283 blocks) with start = 282, end = 283.
Observed:
ax3_testfile.cwa start=144 end=145 -> ERROR: object 'prevStart' not found
ax3_testfile.cwa start=142 end=145 -> ok, 362 rows
ax3_testfile.cwa start= 1 end=145 -> ok, 17477 rows
ax6_testfile.cwa start=282 end=283 -> ERROR: object 'prevStart' not found
ax6_testfile.cwa start=280 end=283 -> ok, 120 rows
ax6_testfile.cwa start= 1 end=283 -> ok, 11388 rows
Cause
The main loop is:
|
for (ii in (start+1):end) { |
Its first branch is taken when ii == numDBlocks:
|
newTimes = (prevRaw$start - prevStart) / prevLength * prevRaw$length + prevRaw$start |
|
prevLength = prevRaw$length |
But prevStart and prevLength are normally assigned only in the other branch:
|
prevStart = prevRaw$start |
|
prevLength = prevRaw$length |
So when the loop's first iteration is already the terminal-block branch (aka when only the final block is requested) that branch assumes state that would normally have been established by an earlier ordinary loop iteration.
prevLength is also unassigned at that point; prevStart is simply evaluated first in the expression, which is why it is the name reported in the error.
This appears to be more than just two missing initializations. The terminal branch also relies on other state normally created during a preceding iteration. For example, the ordinary branch initializes the first timestamp in rawTime, and later code assigns:
where raw is normally created in that branch. In the single-final-block path, none of that setup has occurred.
This also explains why longer ranges work: at least one ordinary iteration runs before the final-block branch and establishes the expected state.
Regression point
In 0ef4cb5d the extrapolation was explicitly guarded on exactly this state:
if (prevRaw$start >= start & pos <= nr & exists("prevStart") & exists("prevLength")) {
By
Commit 0522630 "read requested cwa blocks, instead of requested 300-sample pages" the guard was gone and the terminal-block extrapolation had become unconditional.
So the refactor moved the loop onto requested CWA blocks, but retained terminal-block logic that assumes a preceding ordinary iteration has run. The earlier exists() guard suggests this missing-state case had previously been handled explicitly and was lost during the refactor.
Why it may matter in practice
GGIR reads CWA files in chunks, and the chunk size can be adjusted at runtime by updateBlocksize().
If a file happens to leave exactly one data block for the final chunk, that final call can become equivalent to:
readAxivity(..., start = n - 1, end = n)
and fail on that file even though a full read succeeds.
This is probably uncommon, but difficult to predict from user-facing settings because the effective chunk size can change during processing.
Fix considerations
I initially considered initializing prevStart and prevLength from prevRaw before the loop, but I do not think that is sufficient or necessarily correct.
For the single-final-block case, doing:
prevStart = prevRaw$start
prevLength = prevRaw$length
would make:
(prevRaw$start - prevStart)
equal to zero, so the extrapolated newTimes would collapse to prevRaw$start rather than estimating the end of the final block from a genuine preceding-block interval.
The terminal path also depends on additional state normally established by an earlier iteration.
A correct fix may therefore need either:
- to read the preceding block as timing context when the requested range contains only the final block, without returning that preceding block; or
- to define a separate fallback rule for extrapolating the final block's timestamps when no preceding block is available.
I have not opened a PR because I am not certain which timestamp semantics are intended for this boundary case. Happy to prepare one once that intent is confirmed.
Environment
GGIRread 1.0.9
R 4.5.2
Windows
Line references above are permalinks pinned to the 1.0.9 source.
What's going on
readAxivity(start = n - 1, end = n), wherenis the number of data blocks in the file, errors with:readAxivity()reads blocks[start, end). The block atstartis read intoprevRawbefore the main loop, while the block atendis used only to determine the ending timestamp when it exists. Sostart = n - 1, end = nrequests exactly one block: the final one. Any range that starts earlier works, including a full read.Reproducible example
Uses only the fixtures shipped with the package, on unmodified GGIRread 1.0.9:
Same on
ax6_testfile.cwa(283 blocks) withstart = 282, end = 283.Observed:
Cause
The main loop is:
GGIRread/R/readAxivity.R
Line 524 in 45ef623
Its first branch is taken when
ii == numDBlocks:GGIRread/R/readAxivity.R
Lines 529 to 530 in 45ef623
But
prevStartandprevLengthare normally assigned only in the other branch:GGIRread/R/readAxivity.R
Lines 570 to 571 in 45ef623
So when the loop's first iteration is already the terminal-block branch (aka when only the final block is requested) that branch assumes state that would normally have been established by an earlier ordinary loop iteration.
prevLengthis also unassigned at that point;prevStartis simply evaluated first in the expression, which is why it is the name reported in the error.This appears to be more than just two missing initializations. The terminal branch also relies on other state normally created during a preceding iteration. For example, the ordinary branch initializes the first timestamp in
rawTime, and later code assigns:GGIRread/R/readAxivity.R
Line 686 in 45ef623
where
rawis normally created in that branch. In the single-final-block path, none of that setup has occurred.This also explains why longer ranges work: at least one ordinary iteration runs before the final-block branch and establishes the expected state.
Regression point
In
0ef4cb5dthe extrapolation was explicitly guarded on exactly this state:By
Commit 0522630"read requested cwa blocks, instead of requested 300-sample pages" the guard was gone and the terminal-block extrapolation had become unconditional.So the refactor moved the loop onto requested CWA blocks, but retained terminal-block logic that assumes a preceding ordinary iteration has run. The earlier
exists()guard suggests this missing-state case had previously been handled explicitly and was lost during the refactor.Why it may matter in practice
GGIR reads CWA files in chunks, and the chunk size can be adjusted at runtime by
updateBlocksize().If a file happens to leave exactly one data block for the final chunk, that final call can become equivalent to:
and fail on that file even though a full read succeeds.
This is probably uncommon, but difficult to predict from user-facing settings because the effective chunk size can change during processing.
Fix considerations
I initially considered initializing
prevStartandprevLengthfromprevRawbefore the loop, but I do not think that is sufficient or necessarily correct.For the single-final-block case, doing:
would make:
equal to zero, so the extrapolated
newTimeswould collapse toprevRaw$startrather than estimating the end of the final block from a genuine preceding-block interval.The terminal path also depends on additional state normally established by an earlier iteration.
A correct fix may therefore need either:
I have not opened a PR because I am not certain which timestamp semantics are intended for this boundary case. Happy to prepare one once that intent is confirmed.
Environment
GGIRread 1.0.9
R 4.5.2
Windows
Line references above are permalinks pinned to the 1.0.9 source.