import { Belief } from "../model/Assumption.js"; import { BothEnds, CollationTolerance, Displacement } from "./Collation.js"; import { Millimeters } from "../model/Quantity.js"; import { Histogram, Spread, Tail } from "./statistics.js"; import { AnySymbol } from "../model/Symbol.js"; import { Edition } from "../model/Edition.js"; /** * How far the copies of a roll disagree about where a symbol lies, and * the collation tolerance that follows from it. * * The tolerance decides what counts as a reading at all, and a number * someone picks decides it by fiat. The disagreement between copies is * measurable, and in the material examined so far it is close to * normal, so a displacement an editor put there is recognisable as a * departure from that curve rather than as a value above a guess. What * the measurement yields is a screen: it says which readings the curve * does not account for, and an editor says which of those are acts. */ /** * Where one copy puts a symbol, against where the copies read with it put it. * @category Collation and alignment */ export interface Reading { symbol: Readonly; /** The copies under test less the rest, at either end of the symbol. */ displacement: Displacement; } /** * How far the named copies put each of the symbols from where the * remaining copies put it. * * The comparison is between copies and not between two texts, because * a collated symbol is one symbol carrying the features of every copy * that reads it: no second symbol is left to match it against. Which * copies a version was established from is therefore not something the * edition still states once the collation has run, and the caller * names them. `sidesOf` says which copies attest each side of a * derivation, which is the way to name them rather than by hand. * * The copies named are one **side** of a comparison and the rest are * the other, so the displacement is signed and its sign is the caller's * choice. Naming a set that is not a side yields a well formed * measurement of something else: one copy's own noise against a mixture * of both sides, which is no window any collation used and is wider * than the truth. * * A symbol only one side carries measures nothing and is passed over, * which leaves out exactly the insertions and the deletions. * @category Collation and alignment */ export declare const readingsOf: (edition: Edition, symbols: readonly Readonly[], copies: ReadonlySet) => Reading[]; /** * What sample a reading belongs to. Notes and expressions are taken * apart by default, since in the present material they scatter * differently. * * That split is a stopgap and no distinction in the model. What it * stands in for is a skew: the offset between two copies runs across * the width of the paper, and the expression punches sit at the two * margins, where the gradient is largest and of opposite sign, while * the notes sit between them. Grouped by where the punch sits rather * than by what it says, the expression punches are no noisier than the * notes. Once the alignment carries a skew term, one sample will do, * which is why this is a parameter of the estimator and not a field * anywhere. * @category Collation and alignment */ export type Grouping = (symbol: Readonly) => string; /** * How `scatterOf` takes the readings apart into samples, counts them, * and reports on them. * @category Collation and alignment */ export interface ScatterOptions { /** What sample a reading belongs to. By default what the symbol is: a note, an expression, a text. */ groupOf?: Grouping; /** The width of the histogram's bins. */ binWidth?: Millimeters; /** The tolerance the derivation states at present, against which each departure is reported as still admitted or not. */ stated?: CollationTolerance; } /** * A reading the calculated tolerance does not admit. * @category Collation and alignment */ export interface Departure { /** The symbol read. */ symbol: string; /** How far the copies under test put it from where the rest put it. */ displacement: Displacement; /** That displacement in units of the scatter, at either end. */ z: BothEnds; /** * Which end put the reading outside the window. At least one is * true, and a reading far out at both is true twice. * * It is worth seeing apart. The onset, with the kind, decides * whether two copies read one command; the end decides whether that * command was lengthened or shortened. A separation the end alone * makes is as often a punch measured badly as a punch genuinely * prolonged, and an editor cannot tell the two apart without being * told which test did it. */ separatedBy: BothEnds; /** * Whether the tolerance the derivation states at present still admits * it. Absent where none was given to compare against. * * This sees one direction only. A departure is by definition a * reading the calculated window rejects, so this says which of * those the stated window joined and which would therefore be taken * apart. It cannot show the converse, a reading the stated window * rejects and the calculated one would join, because such a reading * is no departure and never reaches this list. `changesBetween` * reports both, and the ones it adds are the silent ones. */ admittedAsStated?: boolean; } /** * How far a sample departs from the normal shape the tolerance assumes of it. * @category Collation and alignment */ export interface Normality { /** How much heavier its tails are than a normal sample's. Nothing for a normal shape. */ excessKurtosis: number; /** What it puts beyond two, three and four times the scatter, against what a normal sample would. */ tails: Tail[]; } /** * A histogram with the curve its counts are held to follow. * @category Collation and alignment */ export interface FittedHistogram extends Histogram<'mm'> { /** * The normal curve, in the counts' own units, so that an overlay is * drawn from these numbers rather than fitted a second time: * `area / (sigma * sqrt(2 * PI)) * exp(-(((x - centre) / sigma) ** 2) / 2)`. */ curve: { centre: Millimeters; sigma: Millimeters; area: number; }; } /** * How far one sample of readings scatters, and the tolerance that follows from it. * @category Collation and alignment */ export interface Scatter { /** The sample, as the grouping named it. */ group: string; /** Where the readings sit and how far they scatter, at either end. */ spread: BothEnds>; /** * How many times the scatter a reading must lie out before it is * taken for a departure rather than for chance. It is fixed by the * size of the sample and not at a round three, so that fewer than * one of the sample's own readings is expected to pass it. */ k: number; /** * The window that follows: `k` times the scatter, centred on the * median. Its offsets run in the direction of the sample, the * copies named less the rest, so a window measured over the * parent's copies carries them negated. `toleranceAcross` is what * turns these into the window a derivation stores, and it asks * which side was named; storing one of these directly does not. */ tolerance: CollationTolerance; /** How far the sample departs from the normal shape, at either end. */ normality: BothEnds; /** The sample as counts per bin, with the curve to draw over it. */ histogram: BothEnds; /** The readings the calculated tolerance does not admit, the furthest out first. */ departures: Departure[]; } /** * How far out a reading must lie before it is held a departure: the * point beyond which fewer than one reading of a sample of this size is * expected to fall by chance. For the samples an edge of the stemma * yields, some hundreds of readings, this lands between 3.0 and 3.2. * @category Collation and alignment */ export declare const departureThreshold: (n: number) => number; /** * How the readings scatter, one sample per group. A group whose * readings all sit at one place yields nothing, since no scatter can be * read off such a sample. * @category Collation and alignment */ export declare const scatterOf: (readings: readonly Reading[], options?: ScatterOptions) => Scatter[]; /** * How one copy's readings of a version's text scatter against the readings of the copies it is collated with. * @category Collation and alignment */ export declare const scatterOfCopy: (edition: Edition, versionId: string, copyId: string, options?: ScatterOptions) => Scatter[]; /** * Which side of a derivation a measurement was taken over: the version * derived, or the version it is read against. * @category Collation and alignment */ export type Side = 'child' | 'parent'; /** * One window admitting what every sample's own window admits, in the * direction a collation applies it. * * A derivation states a single tolerance while the samples differ, and * covering them keeps each sample within the budget of chance * departures its own threshold was chosen for. * * `named` says whose copies the scatters were measured over, and it is * asked for rather than assumed because nothing else can tell. A window * measured over the parent's copies runs the other way, and stored * unturned its offsets sit on the wrong side of the readings: the width * is unaffected, so nothing in the magnitude looks wrong, and the * departures are unaffected too, since they are taken about the * sample's own median. Only the collation is wrong, and it is wrong in * both directions at once, separating readings that belong together and * merging readings that do not. Nothing where there is no sample. * * Covering has a cost where the samples sit at different centres, and * it falls on the sample that does not move. One window has one centre, * so where one sample's window contains another's the covering window * is simply the wider one, centred where that sample sits, and the * narrower sample is then judged against an offset it does not have. * On the Phillips edge of welte225.org the expression punches sit * 1.2 mm from the notes and the notes' onsets agree exactly, so the * covering window shifts every note onset by a displacement only the * expressions show. `changesBetween` makes that visible as readings the * window moves; it is the price of one tolerance per derivation, and it * is the same skew the grouping stands in for. * @category Collation and alignment */ export declare const toleranceAcross: (scatters: readonly Scatter[], named: Side) => CollationTolerance | undefined; /** * What putting one window in force in place of another would do to a collation. * @category Collation and alignment */ export interface Changes { /** Readings the window in force joins and the proposed one would take apart. */ separated: Reading[]; /** Readings the window in force takes apart and the proposed one would join. */ merged: Reading[]; } /** * Which readings two windows disagree about, taken apart by the * direction they disagree in. * * The two are worth seeing separately because they do not cost the * same. A separation leaves a visible edit in the apparatus that a * reader can challenge; a merge erases a reading and says nothing, and * the edition has no way to show what it lost. So the merges are the * list to read first, and an edge that only separates is the safer kind * of change however many readings it touches. * * This is what to ask before applying a calculated window, rather than * reading `admittedAsStated` off the departures, which sees separations * only. An edge both windows agree about throughout needs no * re-collation at all, and its tolerance may simply be stated. * @category Collation and alignment */ export declare const changesBetween: (readings: readonly Reading[], inForce: CollationTolerance, proposed: CollationTolerance) => Changes; /** * How many of a side's symbols a copy bears. * @category Collation and alignment */ export interface Attestation { copy: string; symbols: number; } /** * The copies attesting each side of a derivation, each side in order of how much it bears. * @category Collation and alignment */ export interface Sides { /** The copies bearing what the child inserts: the reading the derivation moves to. */ child: Attestation[]; /** The copies bearing what the child strikes from the parent: the reading it moves from. */ parent: Attestation[]; } /** * Which copies attest each side of a version's derivation, read off the * edits themselves: the copies bearing what it inserts stand for the * reading it moves to, and those bearing what it strikes for the * reading it moves from. * * This is the way to name a side. Counting only the copies that bear a * version's own text will not do it, because a collation hands a * child's carriers up to the parent's symbols, so a descendant's copies * come to bear an ancestor's readings and look like its own. The * deletions are not exposed to that: what a version strikes is attested * by the copies that read it before the version departed from it. * * A fully collated edge inserts and deletes nothing and so attests * neither side, which is the case where the edition has genuinely * stopped saying and an editor has to. * @category Collation and alignment */ export declare const sidesOf: (edition: Edition, versionId: string) => Sides | undefined; /** * The belief a calculated tolerance rests on, to annotate the * derivation's `collationTolerance` with: an inference from the scatter * of the readings, the size of the sample and the rule that fixed the * threshold. `used` names what the inference worked on, such as the * copies compared. * * It is held likely rather than true. The tolerance follows from the * sample only as far as the sample is normal, and a sample far from * that shape makes the number unreliable rather than wrong, which is * what `normality` is reported for. * @category Collation and alignment */ export declare const inferredTolerance: (scatters: readonly Scatter[], used?: readonly string[]) => Belief;