linked-rolls 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,22 @@ import { Millimeters, Seconds } from "../Quantity";
14
14
  * elapsed time, not paper, and the take-up spool accelerates the paper
15
15
  * as it fills, so `placeAt` has to put the time back onto the paper.
16
16
  * And a switch stays open longer than its perforation is long, so a
17
- * hole read here runs past the punched one.
17
+ * hole read here runs past the punched one: Phillips measures the
18
+ * pneumatic on-time as exceeding the apparent length of a perforation
19
+ * (p. 178), and names what it depends on as the length of the
20
+ * perforation, the height of the tracker bar hole and the paper speed
21
+ * (p. 180).
22
+ *
23
+ * That extension is not subtracted. Doing so would need the absolute
24
+ * hole height of the Welte bar, where he gives only the 0.5 mm by which
25
+ * it exceeds the Licensee's. So the end of a hole on this copy is a
26
+ * pneumatic on-time where the end of a scanned copy's hole is a punched
27
+ * slot, and the two are not the same quantity. It shows in a collation:
28
+ * across welte225.org his notes run about 1.8 mm longer than the other
29
+ * copies read them, while the onsets agree. Until it is corrected, a
30
+ * comparison of hole ends against this copy measures the reader as much
31
+ * as the roll, and `offsetEnd` on the derivation absorbs the systematic
32
+ * part of it.
18
33
  */
19
34
  /**
20
35
  * He numbers his files by two rules at once, which his free samples
@@ -18,7 +18,22 @@ import { inMetersPerMinute, mm, seconds, track } from "../Quantity";
18
18
  * elapsed time, not paper, and the take-up spool accelerates the paper
19
19
  * as it fills, so `placeAt` has to put the time back onto the paper.
20
20
  * And a switch stays open longer than its perforation is long, so a
21
- * hole read here runs past the punched one.
21
+ * hole read here runs past the punched one: Phillips measures the
22
+ * pneumatic on-time as exceeding the apparent length of a perforation
23
+ * (p. 178), and names what it depends on as the length of the
24
+ * perforation, the height of the tracker bar hole and the paper speed
25
+ * (p. 180).
26
+ *
27
+ * That extension is not subtracted. Doing so would need the absolute
28
+ * hole height of the Welte bar, where he gives only the 0.5 mm by which
29
+ * it exceeds the Licensee's. So the end of a hole on this copy is a
30
+ * pneumatic on-time where the end of a scanned copy's hole is a punched
31
+ * slot, and the two are not the same quantity. It shows in a collation:
32
+ * across welte225.org his notes run about 1.8 mm longer than the other
33
+ * copies read them, while the onsets agree. Until it is corrected, a
34
+ * comparison of hole ends against this copy measures the reader as much
35
+ * as the roll, and `offsetEnd` on the derivation absorbs the systematic
36
+ * part of it.
22
37
  */
23
38
  /**
24
39
  * He numbers his files by two rules at once, which his free samples
@@ -0,0 +1,164 @@
1
+ import { Belief } from "./Assumption";
2
+ import { BothEnds, CollationTolerance, Displacement } from "./Collation";
3
+ import { EditionView } from "./EditionView";
4
+ import { Millimeters } from "./Quantity";
5
+ import { Histogram, Spread, Tail } from "./statistics";
6
+ import { AnySymbol } from "./Symbol";
7
+ /**
8
+ * How far the copies of a roll disagree about where a symbol lies, and
9
+ * the collation tolerance that follows from it.
10
+ *
11
+ * The tolerance decides what counts as a reading at all, and a number
12
+ * someone picks decides it by fiat. The disagreement between copies is
13
+ * measurable, and in the material examined so far it is close to
14
+ * normal, so a displacement an editor put there is recognisable as a
15
+ * departure from that curve rather than as a value above a guess. What
16
+ * the measurement yields is a screen: it says which readings the curve
17
+ * does not account for, and an editor says which of those are acts.
18
+ */
19
+ /** Where one copy puts a symbol, against where the copies read with it put it. */
20
+ export interface Reading {
21
+ symbol: Readonly<AnySymbol>;
22
+ /** The copies under test less the rest, at either end of the symbol. */
23
+ displacement: Displacement;
24
+ }
25
+ /**
26
+ * How far the named copies put each of the symbols from where the
27
+ * remaining copies put it.
28
+ *
29
+ * The comparison is between copies and not between two texts, because
30
+ * a collated symbol is one symbol carrying the features of every copy
31
+ * that reads it: no second symbol is left to match it against. Which
32
+ * copies a version was established from is therefore not something the
33
+ * edition still states once the collation has run, and the caller
34
+ * names them.
35
+ *
36
+ * A symbol only one side carries measures nothing and is passed over,
37
+ * which leaves out exactly the insertions and the deletions.
38
+ */
39
+ export declare const readingsOf: (view: EditionView, symbols: readonly Readonly<AnySymbol>[], copies: ReadonlySet<string>) => Reading[];
40
+ /**
41
+ * What sample a reading belongs to. Notes and expressions are taken
42
+ * apart by default, since in the present material they scatter
43
+ * differently.
44
+ *
45
+ * That split is a stopgap and no distinction in the model. What it
46
+ * stands in for is a skew: the offset between two copies runs across
47
+ * the width of the paper, and the expression punches sit at the two
48
+ * margins, where the gradient is largest and of opposite sign, while
49
+ * the notes sit between them. Grouped by where the punch sits rather
50
+ * than by what it says, the expression punches are no noisier than the
51
+ * notes. Once the alignment carries a skew term, one sample will do,
52
+ * which is why this is a parameter of the estimator and not a field
53
+ * anywhere.
54
+ */
55
+ export type Grouping = (symbol: Readonly<AnySymbol>) => string;
56
+ export interface ScatterOptions {
57
+ /** What sample a reading belongs to. By default what the symbol is: a note, an expression, a text. */
58
+ groupOf?: Grouping;
59
+ /** The width of the histogram's bins. */
60
+ binWidth?: Millimeters;
61
+ /** The tolerance the edition states at present, against which each departure is reported as still admitted or not. */
62
+ stated?: CollationTolerance;
63
+ }
64
+ /** A reading the calculated tolerance does not admit. */
65
+ export interface Departure {
66
+ /** The symbol read. */
67
+ symbol: string;
68
+ /** How far the copies under test put it from where the rest put it. */
69
+ displacement: Displacement;
70
+ /** That displacement in units of the scatter, at either end. */
71
+ z: BothEnds<number>;
72
+ /**
73
+ * Which end put the reading outside the window. At least one is
74
+ * true, and a reading far out at both is true twice.
75
+ *
76
+ * It is worth seeing apart. The onset, with the kind, decides
77
+ * whether two copies read one command; the end decides whether that
78
+ * command was lengthened or shortened. A separation the end alone
79
+ * makes is as often a punch measured badly as a punch genuinely
80
+ * prolonged, and an editor cannot tell the two apart without being
81
+ * told which test did it.
82
+ */
83
+ separatedBy: BothEnds<boolean>;
84
+ /** Whether the tolerance the edition states at present still admits it. Absent where none was given to compare against. */
85
+ admittedAsStated?: boolean;
86
+ }
87
+ /** How far a sample departs from the normal shape the tolerance assumes of it. */
88
+ export interface Normality {
89
+ /** How much heavier its tails are than a normal sample's. Nothing for a normal shape. */
90
+ excessKurtosis: number;
91
+ /** What it puts beyond two, three and four times the scatter, against what a normal sample would. */
92
+ tails: Tail[];
93
+ }
94
+ /** A histogram with the curve its counts are held to follow. */
95
+ export interface FittedHistogram extends Histogram<'mm'> {
96
+ /**
97
+ * The normal curve, in the counts' own units, so that an overlay is
98
+ * drawn from these numbers rather than fitted a second time:
99
+ * `area / (sigma * sqrt(2 * PI)) * exp(-(((x - centre) / sigma) ** 2) / 2)`.
100
+ */
101
+ curve: {
102
+ centre: Millimeters;
103
+ sigma: Millimeters;
104
+ area: number;
105
+ };
106
+ }
107
+ /** How far one sample of readings scatters, and the tolerance that follows from it. */
108
+ export interface Scatter {
109
+ /** The sample, as the grouping named it. */
110
+ group: string;
111
+ /** Where the readings sit and how far they scatter, at either end. */
112
+ spread: BothEnds<Spread<'mm'>>;
113
+ /**
114
+ * How many times the scatter a reading must lie out before it is
115
+ * taken for a departure rather than for chance. It is fixed by the
116
+ * size of the sample and not at a round three, so that fewer than
117
+ * one of the sample's own readings is expected to pass it.
118
+ */
119
+ k: number;
120
+ /** The window that follows: `k` times the scatter, centred on the median. */
121
+ tolerance: CollationTolerance;
122
+ /** How far the sample departs from the normal shape, at either end. */
123
+ normality: BothEnds<Normality>;
124
+ /** The sample as counts per bin, with the curve to draw over it. */
125
+ histogram: BothEnds<FittedHistogram>;
126
+ /** The readings the calculated tolerance does not admit, the furthest out first. */
127
+ departures: Departure[];
128
+ }
129
+ /**
130
+ * How far out a reading must lie before it is held a departure: the
131
+ * point beyond which fewer than one reading of a sample of this size is
132
+ * expected to fall by chance. For the samples an edge of the stemma
133
+ * yields, some hundreds of readings, this lands between 3.0 and 3.2.
134
+ */
135
+ export declare const departureThreshold: (n: number) => number;
136
+ /**
137
+ * How the readings scatter, one sample per group. A group whose
138
+ * readings all sit at one place yields nothing, since no scatter can be
139
+ * read off such a sample.
140
+ */
141
+ export declare const scatterOf: (readings: readonly Reading[], options?: ScatterOptions) => Scatter[];
142
+ /** How one copy's readings of a version's text scatter against the readings of the copies it is collated with. */
143
+ export declare const scatterOfCopy: (view: EditionView, versionId: string, copyId: string, options?: ScatterOptions) => Scatter[];
144
+ /**
145
+ * One window admitting what every sample's own window admits. A
146
+ * derivation states a single tolerance while the samples differ, and
147
+ * covering them keeps each sample within the budget of chance
148
+ * departures its own threshold was chosen for. Nothing where there is
149
+ * no sample.
150
+ */
151
+ export declare const toleranceAcross: (scatters: readonly Scatter[]) => CollationTolerance | undefined;
152
+ /**
153
+ * The belief a calculated tolerance rests on, to annotate the
154
+ * derivation's `collationTolerance` with: an inference from the scatter
155
+ * of the readings, the size of the sample and the rule that fixed the
156
+ * threshold. `used` names what the inference worked on, such as the
157
+ * copies compared.
158
+ *
159
+ * It is held likely rather than true. The tolerance follows from the
160
+ * sample only as far as the sample is normal, and a sample far from
161
+ * that shape makes the number unreliable rather than wrong, which is
162
+ * what `normality` is reported for.
163
+ */
164
+ export declare const inferredTolerance: (scatters: readonly Scatter[], used?: readonly string[]) => Belief;
package/lib/scatter.js ADDED
@@ -0,0 +1,171 @@
1
+ import { v4 } from "uuid";
2
+ import { admits, offsetEndOf, offsetStartOf } from "./Collation";
3
+ import { mean, mm } from "./Quantity";
4
+ import { excessKurtosisOf, histogramOf, normalQuantile, spreadOf, standardise, tailOf } from "./statistics";
5
+ import { groupBy } from "./utils";
6
+ const placeOf = (carriers) => ({
7
+ from: mean(carriers.map(carrier => carrier.horizontal.from)),
8
+ to: mean(carriers.map(carrier => carrier.horizontal.to))
9
+ });
10
+ const sitsOn = (view, copies) => (feature) => {
11
+ const copy = view.copyOf(feature.id);
12
+ return copy !== undefined && copies.has(copy.id);
13
+ };
14
+ /**
15
+ * How far the named copies put each of the symbols from where the
16
+ * remaining copies put it.
17
+ *
18
+ * The comparison is between copies and not between two texts, because
19
+ * a collated symbol is one symbol carrying the features of every copy
20
+ * that reads it: no second symbol is left to match it against. Which
21
+ * copies a version was established from is therefore not something the
22
+ * edition still states once the collation has run, and the caller
23
+ * names them.
24
+ *
25
+ * A symbol only one side carries measures nothing and is passed over,
26
+ * which leaves out exactly the insertions and the deletions.
27
+ */
28
+ export const readingsOf = (view, symbols, copies) => {
29
+ const tested = sitsOn(view, copies);
30
+ return symbols.flatMap((symbol) => {
31
+ const carriers = view.carriersOf(symbol);
32
+ const here = carriers.filter(tested);
33
+ const there = carriers.filter(carrier => !tested(carrier));
34
+ if (here.length === 0 || there.length === 0)
35
+ return [];
36
+ const ours = placeOf(here);
37
+ const theirs = placeOf(there);
38
+ return [{ symbol, displacement: { from: mm(ours.from - theirs.from), to: mm(ours.to - theirs.to) } }];
39
+ });
40
+ };
41
+ const byWhatItIs = symbol => symbol.type;
42
+ /** The distances from the centre a sample's normality is read at. */
43
+ const TAILS = [2, 3, 4];
44
+ const DEFAULT_BIN_WIDTH = mm(0.25);
45
+ /**
46
+ * How far out a reading must lie before it is held a departure: the
47
+ * point beyond which fewer than one reading of a sample of this size is
48
+ * expected to fall by chance. For the samples an edge of the stemma
49
+ * yields, some hundreds of readings, this lands between 3.0 and 3.2.
50
+ */
51
+ export const departureThreshold = (n) => normalQuantile(1 - 1 / (2 * n));
52
+ const estimate = (values, binWidth) => {
53
+ const spread = spreadOf(values);
54
+ const histogram = histogramOf(values, binWidth);
55
+ if (!spread || !histogram || spread.sigma === 0)
56
+ return undefined;
57
+ const standardised = values.map(value => standardise(value, spread));
58
+ return {
59
+ spread,
60
+ standardised,
61
+ normality: {
62
+ excessKurtosis: excessKurtosisOf(standardised),
63
+ tails: TAILS.map(beyond => tailOf(standardised, beyond))
64
+ },
65
+ histogram: {
66
+ ...histogram,
67
+ curve: { centre: spread.median, sigma: spread.sigma, area: spread.n * binWidth }
68
+ }
69
+ };
70
+ };
71
+ const furthestOut = (departure) => Math.max(Math.abs(departure.z.from), Math.abs(departure.z.to));
72
+ const departuresIn = (sample, standardised, k, stated) => sample
73
+ .map((reading, i) => {
74
+ const z = { from: standardised.from[i], to: standardised.to[i] };
75
+ return {
76
+ symbol: reading.symbol.id,
77
+ displacement: reading.displacement,
78
+ z,
79
+ separatedBy: { from: Math.abs(z.from) > k, to: Math.abs(z.to) > k },
80
+ ...(stated && { admittedAsStated: admits(stated, reading.displacement) })
81
+ };
82
+ })
83
+ .filter(departure => furthestOut(departure) > k)
84
+ .sort((a, b) => furthestOut(b) - furthestOut(a));
85
+ const scatterIn = (group, sample, { binWidth = DEFAULT_BIN_WIDTH, stated }) => {
86
+ const from = estimate(sample.map(reading => reading.displacement.from), binWidth);
87
+ const to = estimate(sample.map(reading => reading.displacement.to), binWidth);
88
+ if (!from || !to)
89
+ return [];
90
+ const k = departureThreshold(sample.length);
91
+ return [{
92
+ group,
93
+ spread: { from: from.spread, to: to.spread },
94
+ k,
95
+ tolerance: {
96
+ offsetStart: from.spread.median,
97
+ offsetEnd: to.spread.median,
98
+ toleranceStart: mm(k * from.spread.sigma),
99
+ toleranceEnd: mm(k * to.spread.sigma)
100
+ },
101
+ normality: { from: from.normality, to: to.normality },
102
+ histogram: { from: from.histogram, to: to.histogram },
103
+ departures: departuresIn(sample, { from: from.standardised, to: to.standardised }, k, stated)
104
+ }];
105
+ };
106
+ /**
107
+ * How the readings scatter, one sample per group. A group whose
108
+ * readings all sit at one place yields nothing, since no scatter can be
109
+ * read off such a sample.
110
+ */
111
+ export const scatterOf = (readings, options = {}) => [...groupBy(readings, reading => (options.groupOf ?? byWhatItIs)(reading.symbol))]
112
+ .flatMap(([group, sample]) => scatterIn(group, sample, options));
113
+ /** How one copy's readings of a version's text scatter against the readings of the copies it is collated with. */
114
+ export const scatterOfCopy = (view, versionId, copyId, options = {}) => scatterOf(readingsOf(view, view.snapshot(versionId), new Set([copyId])), options);
115
+ const covering = (windows) => {
116
+ const lowest = Math.min(...windows.map(window => window.offset - window.tolerance));
117
+ const highest = Math.max(...windows.map(window => window.offset + window.tolerance));
118
+ return { offset: mm((lowest + highest) / 2), tolerance: mm((highest - lowest) / 2) };
119
+ };
120
+ /**
121
+ * One window admitting what every sample's own window admits. A
122
+ * derivation states a single tolerance while the samples differ, and
123
+ * covering them keeps each sample within the budget of chance
124
+ * departures its own threshold was chosen for. Nothing where there is
125
+ * no sample.
126
+ */
127
+ export const toleranceAcross = (scatters) => {
128
+ if (scatters.length === 0)
129
+ return undefined;
130
+ const start = covering(scatters.map(({ tolerance }) => ({ offset: offsetStartOf(tolerance), tolerance: tolerance.toleranceStart })));
131
+ const end = covering(scatters.map(({ tolerance }) => ({ offset: offsetEndOf(tolerance), tolerance: tolerance.toleranceEnd })));
132
+ return {
133
+ offsetStart: start.offset,
134
+ toleranceStart: start.tolerance,
135
+ offsetEnd: end.offset,
136
+ toleranceEnd: end.tolerance
137
+ };
138
+ };
139
+ const inMillimetres = (value) => `${value.toFixed(2)} mm`;
140
+ const sampleDescribed = (scatter) => `${scatter.group}: ${scatter.spread.from.n} readings, scattering by `
141
+ + `${inMillimetres(scatter.spread.from.sigma)} at the start and ${inMillimetres(scatter.spread.to.sigma)} at the end `
142
+ + `about a median of ${inMillimetres(scatter.spread.from.median)} and ${inMillimetres(scatter.spread.to.median)}, `
143
+ + `with a threshold of ${scatter.k.toFixed(2)} times the scatter`;
144
+ const warrantFor = (scatters) => 'The tolerance is calculated from the scatter of the readings themselves, taken as the median absolute '
145
+ + 'deviation of how far the collated copies put each symbol from where the copies read with it put it. '
146
+ + `${scatters.map(sampleDescribed).join('. ')}. `
147
+ + 'Each threshold is the point beyond which fewer than one reading of a sample of that size is expected to '
148
+ + 'fall by chance, and the window is the threshold times the scatter about its median.';
149
+ /**
150
+ * The belief a calculated tolerance rests on, to annotate the
151
+ * derivation's `collationTolerance` with: an inference from the scatter
152
+ * of the readings, the size of the sample and the rule that fixed the
153
+ * threshold. `used` names what the inference worked on, such as the
154
+ * copies compared.
155
+ *
156
+ * It is held likely rather than true. The tolerance follows from the
157
+ * sample only as far as the sample is normal, and a sample far from
158
+ * that shape makes the number unreliable rather than wrong, which is
159
+ * what `normality` is reported for.
160
+ */
161
+ export const inferredTolerance = (scatters, used = []) => ({
162
+ type: 'belief',
163
+ id: v4(),
164
+ certainty: 'likely',
165
+ reasons: [{
166
+ type: 'inference',
167
+ premises: [],
168
+ ...(used.length > 0 && { used: [...used] }),
169
+ note: warrantFor(scatters)
170
+ }]
171
+ });
package/lib/schema.json CHANGED
@@ -204,15 +204,23 @@
204
204
  "type": "string"
205
205
  },
206
206
  "CollationTolerance": {
207
- "description": "Tolerance used in collation of roll copies: the acceptable deviation at either end when aligning features across copies.",
207
+ "description": "The window two readings of one feature must fall in to be collated: how far apart they may lie at either end, and where that window is centred.\n\nThe centre is worth stating. Two copies of one roll differ by a systematic offset as well as by scatter, and a window centred on nothing has to be widened by the whole of that offset before it admits what the offset alone displaces. Naming the offset lets the tolerance stand for the scatter only. A window that names none is centred on nothing, which is what every window written before the offset was held here means.",
208
208
  "properties": {
209
+ "offsetEnd": {
210
+ "$ref": "#/definitions/Millimeters",
211
+ "description": "The same at the end of a feature."
212
+ },
213
+ "offsetStart": {
214
+ "$ref": "#/definitions/Millimeters",
215
+ "description": "How much later the collated copy puts the start of a feature than the copy it is read against, where the two differ systematically. Nothing where they do not."
216
+ },
209
217
  "toleranceEnd": {
210
218
  "$ref": "#/definitions/Millimeters",
211
- "description": "Tolerance at the end position of a feature."
219
+ "description": "How far they may lie apart at the end, measured from `offsetEnd`."
212
220
  },
213
221
  "toleranceStart": {
214
222
  "$ref": "#/definitions/Millimeters",
215
- "description": "Tolerance at the start position of a feature."
223
+ "description": "How far the two readings may lie apart at the start of a feature, measured from `offsetStart`."
216
224
  }
217
225
  },
218
226
  "required": [
@@ -222,34 +230,66 @@
222
230
  "type": "object"
223
231
  },
224
232
  "Concept": {
225
- "description": "A term from a vocabulary, such as a roll system or a kind of paper. A term the type vocabulary knows carries its IRI as `id`.",
226
- "properties": {
227
- "name": {
228
- "description": "The name, e.g. \"Grünfeld, Alfred\", \"Wien\", \"M. Welte & Söhne\".",
229
- "type": "string",
230
- "ontology": "rdfs:label"
231
- },
232
- "sameAs": {
233
- "description": "Authority records for the same thing: GND, Wikidata, geonames or similar.",
234
- "examples": [
235
- "https://d-nb.info/gnd/116888652"
236
- ],
237
- "items": {
238
- "type": "string"
233
+ "anyOf": [
234
+ {
235
+ "properties": {
236
+ "name": {
237
+ "description": "The name, e.g. \"Grünfeld, Alfred\", \"Wien\", \"M. Welte & Söhne\".",
238
+ "type": "string",
239
+ "ontology": "rdfs:label"
240
+ },
241
+ "sameAs": {
242
+ "description": "Authority records for the same thing: GND, Wikidata, geonames or similar.",
243
+ "examples": [
244
+ "https://d-nb.info/gnd/116888652"
245
+ ],
246
+ "items": {
247
+ "type": "string"
248
+ },
249
+ "type": "array",
250
+ "ontology": "owl:sameAs"
251
+ },
252
+ "@id": {
253
+ "description": "A unique identifier for this object.",
254
+ "type": "string"
255
+ }
239
256
  },
240
- "type": "array",
241
- "ontology": "owl:sameAs"
257
+ "required": [
258
+ "@id"
259
+ ],
260
+ "type": "object"
242
261
  },
243
- "@id": {
244
- "description": "A unique identifier for this object.",
245
- "type": "string"
262
+ {
263
+ "properties": {
264
+ "name": {
265
+ "description": "The name, e.g. \"Grünfeld, Alfred\", \"Wien\", \"M. Welte & Söhne\".",
266
+ "type": "string",
267
+ "ontology": "rdfs:label"
268
+ },
269
+ "sameAs": {
270
+ "description": "Authority records for the same thing: GND, Wikidata, geonames or similar.",
271
+ "examples": [
272
+ "https://d-nb.info/gnd/116888652"
273
+ ],
274
+ "items": {
275
+ "type": "string"
276
+ },
277
+ "type": "array",
278
+ "ontology": "owl:sameAs"
279
+ },
280
+ "@id": {
281
+ "description": "A unique identifier for this object.",
282
+ "type": "string"
283
+ }
284
+ },
285
+ "required": [
286
+ "name",
287
+ "sameAs"
288
+ ],
289
+ "type": "object"
246
290
  }
247
- },
248
- "required": [
249
- "name",
250
- "sameAs"
251
291
  ],
252
- "type": "object",
292
+ "description": "A term from a vocabulary, such as a roll system, a procedure or a kind of paper.\n\nA term the type vocabulary declares is named by its IRI, and what it is called stands in the vocabulary rather than in the edition, so it needs no name of its own; `nameOf` reads one either way. A term the vocabulary does not have is given by name, which is then all there is to go on.",
253
293
  "ontology": "crm:E55 Type"
254
294
  },
255
295
  "DateAssignment": {
@@ -382,8 +422,8 @@
382
422
  "type": "object"
383
423
  },
384
424
  "collationTolerance": {
385
- "$ref": "#/definitions/CollationTolerance",
386
- "description": "The tolerance at which the derived version was collated against the one it is based on. A derivation written before the tolerance was held here states none. Not exported to RDF."
425
+ "$ref": "#/definitions/ObjectAssumption%3CCollationTolerance%3E",
426
+ "description": "The tolerance at which the derived version was collated against the one it is based on. A derivation written before the tolerance was held here states none. Not exported to RDF.\n\nIt is an object assumption, so that a tolerance calculated from the scatter of the readings can carry the inference it was drawn by. It is the one quantity in the edition that decides what counts as a reading at all, and a number arrived at by a method should say so."
387
427
  },
388
428
  "@id": {
389
429
  "description": "A unique identifier for this object.",
@@ -544,7 +584,8 @@
544
584
  "properties": {
545
585
  "collationTolerance": {
546
586
  "$ref": "#/definitions/CollationTolerance",
547
- "description": "The tolerance parameters used when collating (aligning) the different roll copies for this edition. Not exported to RDF."
587
+ "deprecated": true,
588
+ "description": "The tolerance every version of the edition was collated at, before each derivation stated its own. How precisely two copies place a symbol depends on which two they are, so the tolerance belongs to the derivation, `Derivation.collationTolerance`, and migration writes this one onto every derivation that gives none. Nothing else reads it. A window's offset is the displacement between two particular copies and so can never be stated here. Not exported to RDF."
548
589
  },
549
590
  "editors": {
550
591
  "description": "The persons who prepared the edition, each with the part they took in the editorial work. An edition written before this field existed carries none.",
@@ -1867,6 +1908,51 @@
1867
1908
  ],
1868
1909
  "type": "object"
1869
1910
  },
1911
+ "ObjectAssumption<CollationTolerance>": {
1912
+ "description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
1913
+ "properties": {
1914
+ "@annotation": {
1915
+ "description": "An optional annotation expressing a belief about this assumption. Uses the JSON-LD-star `@annotation` mechanism to attach epistemic metadata (certainty and reasons) to any triple.",
1916
+ "properties": {
1917
+ "belief": {
1918
+ "$ref": "#/definitions/Belief",
1919
+ "description": "The belief held about the annotated statement.",
1920
+ "ontology": "crminf:J4i is subject of"
1921
+ },
1922
+ "@id": {
1923
+ "description": "A unique identifier for this object.",
1924
+ "type": "string"
1925
+ }
1926
+ },
1927
+ "required": [
1928
+ "belief",
1929
+ "@id"
1930
+ ],
1931
+ "type": "object"
1932
+ },
1933
+ "offsetEnd": {
1934
+ "$ref": "#/definitions/Millimeters",
1935
+ "description": "The same at the end of a feature."
1936
+ },
1937
+ "offsetStart": {
1938
+ "$ref": "#/definitions/Millimeters",
1939
+ "description": "How much later the collated copy puts the start of a feature than the copy it is read against, where the two differ systematically. Nothing where they do not."
1940
+ },
1941
+ "toleranceEnd": {
1942
+ "$ref": "#/definitions/Millimeters",
1943
+ "description": "How far they may lie apart at the end, measured from `offsetEnd`."
1944
+ },
1945
+ "toleranceStart": {
1946
+ "$ref": "#/definitions/Millimeters",
1947
+ "description": "How far the two readings may lie apart at the start of a feature, measured from `offsetStart`."
1948
+ }
1949
+ },
1950
+ "required": [
1951
+ "toleranceEnd",
1952
+ "toleranceStart"
1953
+ ],
1954
+ "type": "object"
1955
+ },
1870
1956
  "ObjectAssumption<ConditionState<\"general\">>": {
1871
1957
  "description": "An object assumption wraps a complex object with an optional annotation. Used for structured values (e.g. persons, conditions) whose properties may be uncertain.",
1872
1958
  "properties": {
@@ -3011,12 +3097,6 @@
3011
3097
  },
3012
3098
  "type": "array"
3013
3099
  },
3014
- "siglum": {
3015
- "deprecated": true,
3016
- "description": "A label a document carries from before the sigla were computed. Nothing should write one here and nothing should show one: what a version is called comes from `siglaOf`.",
3017
- "type": "string",
3018
- "ontology": "reo:siglum"
3019
- },
3020
3100
  "system": {
3021
3101
  "$ref": "#/definitions/Concept",
3022
3102
  "description": "The reproducing system this version is coded for. One roll was often issued for several of them, and a version is a reading in one system's words: its expression types are that system's vocabulary and its notes sit on that bar's positions. A system the type vocabulary knows carries the IRI of its concept as `id`, from which the export takes the system's own context.",
@@ -0,0 +1,71 @@
1
+ import { Quantity } from "./Quantity";
2
+ /**
3
+ * The statistics a sample of measurements is described with.
4
+ *
5
+ * The centre and the scatter are taken from the median rather than from
6
+ * the mean, because the departures a measurement over an edition looks
7
+ * for are in the sample it is taken over. A mean and a standard
8
+ * deviation would grow towards those departures until they no longer
9
+ * stood out; a median and a median absolute deviation leave them out of
10
+ * account.
11
+ */
12
+ /** The middle of a sample, the mean of the two middle values where it has an even number of them. */
13
+ export declare const medianOf: <U extends string>(values: readonly Quantity<U>[]) => Quantity<U> | undefined;
14
+ /** Where a sample sits and how far it scatters. */
15
+ export interface Spread<U extends string> {
16
+ /** How many measurements it was taken over. */
17
+ n: number;
18
+ /** The middle measurement. */
19
+ median: Quantity<U>;
20
+ /**
21
+ * The scatter about the median, as the median absolute deviation
22
+ * scaled to the standard deviation a normal sample of that scatter
23
+ * would have. It is zero where more than half the sample sits at
24
+ * one value, and no scatter can be read off such a sample.
25
+ */
26
+ sigma: Quantity<U>;
27
+ }
28
+ /** Where the sample sits and how far it scatters, or nothing for an empty sample. */
29
+ export declare const spreadOf: <U extends string>(values: readonly Quantity<U>[]) => Spread<U> | undefined;
30
+ /** How far the value lies from the centre of the spread, in units of its scatter. */
31
+ export declare const standardise: <U extends string>(value: Quantity<U>, spread: Spread<U>) => number;
32
+ /** The share of a normal sample lying below `z` standard deviations. */
33
+ export declare const normalBelow: (z: number) => number;
34
+ /** The share of a normal sample lying further than `z` standard deviations from its centre, on either side. */
35
+ export declare const normalBeyond: (z: number) => number;
36
+ /**
37
+ * How many standard deviations out the given share of a normal sample
38
+ * lies below, after Acklam's rational approximation, whose relative
39
+ * error stays below 1.15e-9. The inverse of `normalBelow`.
40
+ */
41
+ export declare const normalQuantile: (share: number) => number;
42
+ /**
43
+ * How much heavier the tails of a standardised sample are than a normal
44
+ * sample's. Zero for a normal shape, positive where more of the sample
45
+ * lies far out than the curve allows.
46
+ */
47
+ export declare const excessKurtosisOf: (standardised: readonly number[]) => number;
48
+ /** How much of a sample lies beyond a given distance from its centre, against how much would under a normal curve. */
49
+ export interface Tail {
50
+ /** The distance from the centre, in standard deviations. */
51
+ beyond: number;
52
+ /** How many of the sample lie further out than that. */
53
+ observed: number;
54
+ /** How many a normal sample of the same size would put there. */
55
+ expected: number;
56
+ }
57
+ /** What the sample puts beyond the given distance, against what a normal sample would. */
58
+ export declare const tailOf: (standardised: readonly number[], beyond: number) => Tail;
59
+ /** A sample counted into bins of one width. */
60
+ export interface Histogram<U extends string> {
61
+ /** The bounds of the bins, in order, one more of them than there are counts. */
62
+ edges: Quantity<U>[];
63
+ /** How many of the sample fall in each bin. */
64
+ counts: number[];
65
+ }
66
+ /**
67
+ * The sample counted into bins of the given width, laid out on
68
+ * multiples of that width so that two histograms of one width share
69
+ * their bounds. Nothing for an empty sample or a width of nothing.
70
+ */
71
+ export declare const histogramOf: <U extends string>(values: readonly Quantity<U>[], binWidth: Quantity<U>) => Histogram<U> | undefined;