Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 2x 2x 160x 160x 160x 160x 160x 2x 64x 64x 64x 2x 13x 13x 13x | /**
* How a sequence is cut into tiles, for the readers that walk one.
*
* Its own module, and a small one, because two layers need the same arithmetic
* and neither may import the other. `analysis/run/blocks.ts` does the reading
* and lives in the analysis worker; `analysis/tasks/insulation.ts` prices the
* read before it happens and lives in the panel. A task whose estimate counts
* one thing while its runner reads another is worse than a task with no
* estimate — it is the number a reader decides on, and the gate that refuses
* (§18.6) is derived from it.
*
* Nothing here touches a file or a job. It is arithmetic about shapes.
*/
/**
* The largest tile any of these readers will ask the file for, in cells.
*
* 16 million cells is 64 MB as `Float32`, which is small enough to be nothing on
* any machine that opened the file and large enough that a chromosome is a few
* dozen reads rather than a few thousand. It is a *tile* bound and not a budget:
* the point of the banded readers is that what they hold does not grow with the
* sequence, so there is no machine-dependent number to consult.
*/
export const TILE_CELLS = 16_000_000
/**
* Rows per tile, for a reader that needs `halo` bins of margin around each one.
*
* The read is `(tile + extra)` wide however tall it is, so the cells a whole
* sequence costs are `n × (tile + extra)` — smallest when the tile is about the
* halo, and only twice the band itself there. A floor keeps a narrow band from
* turning a chromosome into thousands of tiny queries, and the cell cap keeps a
* wide one from asking for a tile it cannot hold.
*/
export function tileRows(halo: number, extra: number): number {
const wanted = Math.max(256, Math.min(halo, 4096))
let tile = wanted
while (tile > 1 && tile * (tile + extra) > TILE_CELLS) tile = Math.floor(tile / 2)
return Math.max(1, tile)
}
/**
* Squares along the diagonal a sequence of `bins` is walked in.
*
* The shape `forEachDiagonalTile` reads it in: each tile owns a body of bins and
* carries `halo` of margin on each side, so the bins at its seams are scored
* from the neighbouring tile instead. What this counts is the *bodies*, which is
* one file read each.
*/
export function diagonalTiles(bins: number, halo: number): number {
const body = tileRows(Math.max(0, halo) * 2, Math.max(0, halo) * 2)
return Math.max(1, Math.ceil(Math.max(1, bins) / body))
}
/**
* Strips a sequence of `bins` is read in as a **band** `hi` diagonals deep.
*
* `bandFor`'s walk rather than `forEachDiagonalTile`'s, and it is a different
* number: a band tile is a run of rows read out to `hi` past its own last row,
* with no overlap between tiles, where a diagonal tile carries a halo on both
* sides and its neighbours overlap it. The tasks that read a band price
* themselves with this one, for the reason at the top of this module — an
* estimate that counts the other walk's reads is the number a reader decides on
* and it is wrong by whatever the halo was.
*
* The wrapped corner a circular sequence costs is **not** counted here: it is one
* request, it is the same one request whatever the sequence, and the caller adds
* it where it knows whether the sequence is circular at all.
*/
export function bandTiles(bins: number, hi: number): number {
const depth = Math.max(0, hi)
return Math.max(1, Math.ceil(Math.max(1, bins) / tileRows(depth, depth)))
}
|