linked-rolls 0.70.0 → 0.72.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.
Files changed (143) hide show
  1. package/README.md +106 -5
  2. package/lib/analysis/constraints.d.ts +7 -0
  3. package/lib/analysis/constraints.js +1 -0
  4. package/lib/analysis/ownPaper.d.ts +5 -1
  5. package/lib/analysis/ownPaper.js +5 -1
  6. package/lib/analysis/paper.d.ts +22 -4
  7. package/lib/analysis/paper.js +6 -0
  8. package/lib/analysis/reservations.d.ts +24 -1
  9. package/lib/analysis/reservations.js +15 -1
  10. package/lib/analysis/sigla.d.ts +6 -1
  11. package/lib/analysis/sigla.js +5 -1
  12. package/lib/analysis/stemma.d.ts +6 -1
  13. package/lib/analysis/stemma.js +6 -1
  14. package/lib/analysis/text.d.ts +16 -2
  15. package/lib/analysis/text.js +16 -2
  16. package/lib/analysis/witnesses.d.ts +20 -1
  17. package/lib/analysis/witnesses.js +4 -0
  18. package/lib/collation/Collation.d.ts +47 -8
  19. package/lib/collation/Collation.js +29 -5
  20. package/lib/collation/alignment.d.ts +16 -3
  21. package/lib/collation/alignment.js +15 -3
  22. package/lib/collation/scatter.d.ts +50 -9
  23. package/lib/collation/scatter.js +11 -1
  24. package/lib/collation/statistics.d.ts +39 -9
  25. package/lib/collation/statistics.js +27 -6
  26. package/lib/collation/substitution.d.ts +5 -0
  27. package/lib/collation/substitution.js +4 -0
  28. package/lib/emulation/Emulation.d.ts +16 -1
  29. package/lib/emulation/Emulation.js +10 -0
  30. package/lib/emulation/negotiation.d.ts +1 -0
  31. package/lib/emulation/negotiation.js +1 -0
  32. package/lib/filiation/adoption.d.ts +47 -0
  33. package/lib/filiation/adoption.js +190 -0
  34. package/lib/filiation/changes.d.ts +33 -0
  35. package/lib/filiation/changes.js +38 -0
  36. package/lib/filiation/index.d.ts +12 -0
  37. package/lib/filiation/index.js +12 -0
  38. package/lib/filiation/presence.d.ts +50 -0
  39. package/lib/filiation/presence.js +122 -0
  40. package/lib/filiation/proposal.d.ts +132 -0
  41. package/lib/filiation/proposal.js +376 -0
  42. package/lib/filiation/readings.d.ts +108 -0
  43. package/lib/filiation/readings.js +281 -0
  44. package/lib/filiation/search.d.ts +113 -0
  45. package/lib/filiation/search.js +268 -0
  46. package/lib/filiation/systems.d.ts +21 -0
  47. package/lib/filiation/systems.js +39 -0
  48. package/lib/filiation/trees.d.ts +20 -0
  49. package/lib/filiation/trees.js +69 -0
  50. package/lib/index.d.ts +34 -2
  51. package/lib/index.js +3 -1
  52. package/lib/io/asJsonLd.d.ts +10 -0
  53. package/lib/io/asJsonLd.js +10 -0
  54. package/lib/io/context.d.ts +16 -2
  55. package/lib/io/context.js +16 -2
  56. package/lib/io/importJsonLd.d.ts +1 -0
  57. package/lib/io/importJsonLd.js +1 -0
  58. package/lib/io/migrate.d.ts +8 -0
  59. package/lib/io/migrate.js +8 -0
  60. package/lib/lookup/lookup.d.ts +29 -5
  61. package/lib/lookup/lookup.js +29 -5
  62. package/lib/lookup/paths.d.ts +23 -3
  63. package/lib/lookup/paths.js +17 -3
  64. package/lib/model/Agent.d.ts +28 -2
  65. package/lib/model/Agent.js +15 -1
  66. package/lib/model/Assumption.d.ts +96 -7
  67. package/lib/model/Assumption.js +65 -7
  68. package/lib/model/ConditionState.d.ts +1 -0
  69. package/lib/model/Edit.d.ts +10 -0
  70. package/lib/model/Edit.js +8 -0
  71. package/lib/model/Edition.d.ts +11 -0
  72. package/lib/model/Edition.js +1 -0
  73. package/lib/model/Feature.d.ts +74 -7
  74. package/lib/model/Feature.js +41 -3
  75. package/lib/model/FeatureSource.d.ts +14 -1
  76. package/lib/model/FeatureSource.js +7 -1
  77. package/lib/model/Perforator.d.ts +10 -0
  78. package/lib/model/Perforator.js +1 -0
  79. package/lib/model/Quantity.d.ts +160 -13
  80. package/lib/model/Quantity.js +110 -6
  81. package/lib/model/RollCopy.d.ts +74 -12
  82. package/lib/model/RollCopy.js +53 -10
  83. package/lib/model/Symbol.d.ts +38 -1
  84. package/lib/model/Symbol.js +16 -0
  85. package/lib/model/TrackCalibration.d.ts +10 -2
  86. package/lib/model/TrackCalibration.js +9 -2
  87. package/lib/model/Version.d.ts +24 -4
  88. package/lib/model/Version.js +19 -4
  89. package/lib/model/notes.d.ts +15 -2
  90. package/lib/model/notes.js +9 -2
  91. package/lib/model/procedures.d.ts +5 -1
  92. package/lib/model/procedures.js +5 -1
  93. package/lib/model/vocabulary.d.ts +6 -1
  94. package/lib/model/vocabulary.js +6 -1
  95. package/lib/ops/beliefs.d.ts +23 -1
  96. package/lib/ops/beliefs.js +23 -1
  97. package/lib/ops/collation.d.ts +3 -0
  98. package/lib/ops/collation.js +3 -0
  99. package/lib/ops/commands.d.ts +17 -2
  100. package/lib/ops/commands.js +17 -2
  101. package/lib/ops/copies.d.ts +37 -6
  102. package/lib/ops/copies.js +37 -6
  103. package/lib/ops/draft.d.ts +2 -0
  104. package/lib/ops/draft.js +1 -0
  105. package/lib/ops/edits.d.ts +5 -1
  106. package/lib/ops/edits.js +5 -1
  107. package/lib/ops/features.d.ts +12 -1
  108. package/lib/ops/features.js +7 -0
  109. package/lib/ops/versions.d.ts +15 -3
  110. package/lib/ops/versions.js +15 -3
  111. package/lib/readers/phillipsEroll.d.ts +12 -1
  112. package/lib/readers/phillipsEroll.js +6 -1
  113. package/lib/readers/spencerBar.d.ts +12 -1
  114. package/lib/readers/spencerBar.js +7 -1
  115. package/lib/readers/stanfordAton.d.ts +15 -0
  116. package/lib/readers/stanfordAton.js +9 -0
  117. package/lib/schema.json +10 -0
  118. package/lib/shared/utils.d.ts +12 -0
  119. package/lib/spec/context.json +4 -0
  120. package/lib/systems/ReproducingSystem.d.ts +46 -3
  121. package/lib/systems/TrackerBar.d.ts +45 -4
  122. package/lib/systems/TrackerBar.js +20 -3
  123. package/lib/systems/index.d.ts +9 -2
  124. package/lib/systems/index.js +9 -2
  125. package/lib/systems/velocity.d.ts +1 -0
  126. package/lib/systems/velocity.js +1 -0
  127. package/lib/systems/welte.d.ts +18 -3
  128. package/lib/systems/welte.js +9 -2
  129. package/lib/systems/welteLicensee/bar.d.ts +1 -0
  130. package/lib/systems/welteLicensee/bar.js +1 -0
  131. package/lib/systems/welteLicensee/system.d.ts +18 -0
  132. package/lib/systems/welteLicensee/system.js +17 -0
  133. package/lib/systems/welteT100/bar.d.ts +8 -0
  134. package/lib/systems/welteT100/bar.js +4 -0
  135. package/lib/systems/welteT100/system.d.ts +46 -5
  136. package/lib/systems/welteT100/system.js +29 -3
  137. package/lib/systems/welteT98/bar.d.ts +6 -0
  138. package/lib/systems/welteT98/bar.js +2 -0
  139. package/lib/systems/welteT98/system.d.ts +58 -5
  140. package/lib/systems/welteT98/system.js +42 -3
  141. package/lib/validate.d.ts +16 -0
  142. package/lib/validate.js +15 -0
  143. package/package.json +2 -1
@@ -0,0 +1,108 @@
1
+ import { Edition } from "../model/Edition.js";
2
+ import { HorizontalSpan } from "../model/Feature.js";
3
+ import { RollCopy } from "../model/RollCopy.js";
4
+ import { Millimeters } from "../model/Quantity.js";
5
+ import { TrackerBar, TrackMeaning } from "../systems/TrackerBar.js";
6
+ import { CollationTolerance } from "../collation/Collation.js";
7
+ /**
8
+ * One copy's reading of one feature: what the copy's bar reads off it,
9
+ * and where.
10
+ * @category Filiation
11
+ */
12
+ export interface CopyReading {
13
+ /** The feature and what it is read as, since a feature across two positions reads as two commands. */
14
+ id: string;
15
+ copy: string;
16
+ feature: string;
17
+ meaning: TrackMeaning;
18
+ key: string;
19
+ place: HorizontalSpan;
20
+ }
21
+ /**
22
+ * A reading the copies share: the readings of one meaning that lie at
23
+ * about the same place, at most one of each copy.
24
+ * @category Filiation
25
+ */
26
+ export interface CollatedReading {
27
+ /**
28
+ * A name for the reading, the same for the same meaning on the same
29
+ * features, so that collating again gives it again.
30
+ */
31
+ id: string;
32
+ meaning: TrackMeaning;
33
+ key: string;
34
+ /** The features carrying it, each on another copy. */
35
+ carriers: string[];
36
+ /** The copies carrying it, in the order the collation was given them. */
37
+ copies: string[];
38
+ /** Where it lies: the median of its carriers at either end. */
39
+ place: HorizontalSpan;
40
+ }
41
+ /**
42
+ * How far two copies may put one reading apart, and where that window
43
+ * is centred: the systematic difference between the two and the scatter
44
+ * about it, as `scatter.ts` measures a derivation's.
45
+ * @category Filiation
46
+ */
47
+ export interface PairWindow extends CollationTolerance {
48
+ /** How many readings the window was measured on; none where it fell back on the default. */
49
+ n: number;
50
+ }
51
+ /**
52
+ * A command one copy holds where another latches, as a transfer into a
53
+ * system with held commands spells it: the readings of the one standing
54
+ * for the readings of the other.
55
+ * @category Filiation
56
+ */
57
+ export interface Recoding {
58
+ /** The collated readings of the latched pair. */
59
+ replaced: string[];
60
+ /** The collated readings of the held command. */
61
+ by: string[];
62
+ }
63
+ /**
64
+ * The collation of a set of copies.
65
+ * @category Filiation
66
+ */
67
+ export interface CopyCollation {
68
+ copies: string[];
69
+ readings: CollatedReading[];
70
+ /** By pair of copies (`a|b|group`, the pair in the order the copies were given), the window used. */
71
+ windows: Record<string, PairWindow>;
72
+ recodings: Recoding[];
73
+ /** By copy, where its text begins and ends. */
74
+ extent: Record<string, {
75
+ from: Millimeters;
76
+ to: Millimeters;
77
+ }>;
78
+ }
79
+ /**
80
+ * What the copy's own punching put on the paper and its bar reads as
81
+ * text, in order of onset.
82
+ *
83
+ * A later alteration of the copy is not part of what was punched from
84
+ * the version, so only the features of the production count. Where the
85
+ * bar finds the roll's end, reading stops, and the end itself is not
86
+ * read. A command no other scale has a word for (the motor, the rewind,
87
+ * the electric cut-off) runs the instrument rather than the music and
88
+ * is no reading either.
89
+ * @category Filiation
90
+ */
91
+ export declare const readingsOfCopy: (copy: Readonly<RollCopy>) => CopyReading[];
92
+ /**
93
+ * Collates the copies all at once. Every reading is compared with every
94
+ * reading of the same meaning on the other copies, and two are taken to
95
+ * be one where they lie within the window of their pair of copies at
96
+ * both ends. The readings are then joined nearest pair first, and never
97
+ * so that one copy would read a joined reading twice.
98
+ *
99
+ * Collating one copy after another against what was collated before, as
100
+ * connecting versions does, depends on the order: six copies collated in
101
+ * their 720 orders give hundreds of different results. Compared all with
102
+ * all, the result is the same whichever order the copies are given in,
103
+ * and only the copies themselves decide it.
104
+ * @category Filiation
105
+ */
106
+ export declare const collateCopies: (edition: Pick<Edition, 'copies'>, copyIds: readonly string[]) => CopyCollation;
107
+ /** Whether the bar has a word for the meaning at all. */
108
+ export declare const reads: (bar: TrackerBar, meaning: TrackMeaning) => boolean;
@@ -0,0 +1,281 @@
1
+ /**
2
+ * The readings of a set of copies, collated all at once: every copy
3
+ * against every other, so that what counts as one reading does not
4
+ * depend on the order the copies are taken in.
5
+ */
6
+ import { v5 } from "uuid";
7
+ import { asSymbols, barOf } from "../model/RollCopy.js";
8
+ import { idOf } from "../model/Assumption.js";
9
+ import { mm } from "../model/Quantity.js";
10
+ import { keyOf } from "../systems/TrackerBar.js";
11
+ import { medianOf, spreadOf } from "../collation/statistics.js";
12
+ import { departureThreshold } from "../collation/scatter.js";
13
+ import { substitutionsBetween } from "../collation/substitution.js";
14
+ import { transfersOf } from "./systems.js";
15
+ /** The namespace the names of collated readings are minted in, so that the same carriers always give the same name. */
16
+ const READINGS = '0b7a3f5e-5d0c-4a59-9d55-4b2b6c1f6a70';
17
+ /**
18
+ * How far apart the first estimate takes two readings to be the same:
19
+ * far beyond any scatter, near enough to stay off the next command of
20
+ * the same kind.
21
+ */
22
+ const PROVISIONAL = mm(8);
23
+ /** The window between a pair too seldom read together for a scatter to be read off. */
24
+ const FALLBACK = { n: 0, toleranceStart: mm(5), toleranceEnd: mm(5) };
25
+ /** Below this many shared readings no scatter is read off a pair. */
26
+ const FEWEST = 10;
27
+ /** Two readings further apart than this are never compared. */
28
+ const REACH = mm(40);
29
+ /** How far a held command may lie from the latched pair it stands for. */
30
+ const RECODING_TOLERANCE = { toleranceStart: mm(5), toleranceEnd: mm(5) };
31
+ const groupOf = (meaning) => meaning.type;
32
+ /**
33
+ * What the copy's own punching put on the paper and its bar reads as
34
+ * text, in order of onset.
35
+ *
36
+ * A later alteration of the copy is not part of what was punched from
37
+ * the version, so only the features of the production count. Where the
38
+ * bar finds the roll's end, reading stops, and the end itself is not
39
+ * read. A command no other scale has a word for (the motor, the rewind,
40
+ * the electric cut-off) runs the instrument rather than the music and
41
+ * is no reading either.
42
+ * @category Filiation
43
+ */
44
+ export const readingsOfCopy = (copy) => {
45
+ const bar = barOf(copy);
46
+ const chains = (copy.production?.produced ?? []).filter((feature) => feature.type === 'HoleChain');
47
+ const end = bar.endsAt(chains);
48
+ const place = new Map(chains.map(chain => [chain.id, chain.horizontal]));
49
+ return asSymbols(chains, bar)
50
+ .filter(symbol => symbol.type !== 'expression' || bar.operationOf(symbol.expressionType) !== undefined)
51
+ .flatMap((symbol) => {
52
+ if (symbol.type === 'text')
53
+ return [];
54
+ const feature = idOf(symbol.carriers[0]);
55
+ const horizontal = place.get(feature);
56
+ if (end !== undefined && horizontal.from >= end.at)
57
+ return [];
58
+ const meaning = symbol.type === 'note'
59
+ ? { type: 'note', pitch: symbol.pitch }
60
+ : { type: 'expression', expressionType: symbol.expressionType, scope: symbol.scope };
61
+ const key = keyOf(meaning);
62
+ return [{ id: `${feature}|${key}`, copy: copy.id, feature, meaning, key, place: { ...horizontal } }];
63
+ })
64
+ .sort((a, b) => a.place.from - b.place.from || compare(a.id, b.id));
65
+ };
66
+ const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
67
+ const groupBy = (items, key) => {
68
+ const groups = new Map();
69
+ for (const item of items) {
70
+ const k = key(item);
71
+ const group = groups.get(k);
72
+ if (group)
73
+ group.push(item);
74
+ else
75
+ groups.set(k, [item]);
76
+ }
77
+ return groups;
78
+ };
79
+ const pairKey = (a, b, group) => `${a}|${b}|${group}`;
80
+ /** The reading of the other copy nearest in onset, within the provisional window, ties to the smaller id. */
81
+ const nearestOf = (reading, others) => {
82
+ let best;
83
+ let distance = Infinity;
84
+ for (const other of others) {
85
+ const d = Math.abs(other.place.from - reading.place.from);
86
+ if (d < distance || (d === distance && best && other.id < best.id)) {
87
+ best = other;
88
+ distance = d;
89
+ }
90
+ }
91
+ return distance <= PROVISIONAL ? best : undefined;
92
+ };
93
+ /**
94
+ * The window of every pair of copies, read off the readings they share
95
+ * at first sight: for each meaning, the mutually nearest onsets within a
96
+ * wide provisional window. Their displacements, the later copy less the
97
+ * earlier, give the centre (the median) and the scatter about it (the
98
+ * median absolute deviation as a standard deviation), and the window is
99
+ * the scatter times the threshold beyond which fewer than one reading of
100
+ * a sample of that size would fall by chance.
101
+ */
102
+ const windowsOf = (copies, byKey) => {
103
+ const samples = new Map();
104
+ for (const readings of byKey.values()) {
105
+ const group = groupOf(readings[0].meaning);
106
+ const byCopy = groupBy(readings, reading => reading.copy);
107
+ for (let i = 0; i < copies.length; i++) {
108
+ for (let j = i + 1; j < copies.length; j++) {
109
+ const one = byCopy.get(copies[i]) ?? [];
110
+ const other = byCopy.get(copies[j]) ?? [];
111
+ for (const a of one) {
112
+ const b = nearestOf(a, other);
113
+ if (!b || nearestOf(b, one) !== a)
114
+ continue;
115
+ const key = pairKey(copies[i], copies[j], group);
116
+ const sample = samples.get(key) ?? { from: [], to: [] };
117
+ sample.from.push(mm(b.place.from - a.place.from));
118
+ sample.to.push(mm(b.place.to - a.place.to));
119
+ samples.set(key, sample);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ const windows = {};
125
+ for (const [key, sample] of samples) {
126
+ const start = spreadOf(sample.from);
127
+ const end = spreadOf(sample.to);
128
+ if (!start || !end || sample.from.length < FEWEST || start.sigma === 0 || end.sigma === 0)
129
+ continue;
130
+ const k = departureThreshold(sample.from.length);
131
+ windows[key] = {
132
+ n: sample.from.length,
133
+ offsetStart: start.median,
134
+ offsetEnd: end.median,
135
+ toleranceStart: mm(k * start.sigma),
136
+ toleranceEnd: mm(k * end.sigma)
137
+ };
138
+ }
139
+ return windows;
140
+ };
141
+ /** How far apart two readings lie, in units of the window of their copies; above one they are not the same. */
142
+ const distanceIn = (windows, order, x, y) => {
143
+ const [a, b] = order.get(x.copy) < order.get(y.copy) ? [x, y] : [y, x];
144
+ const window = windows[pairKey(a.copy, b.copy, groupOf(a.meaning))] ?? FALLBACK;
145
+ const start = Math.abs(b.place.from - a.place.from - (window.offsetStart ?? 0)) / window.toleranceStart;
146
+ const end = Math.abs(b.place.to - a.place.to - (window.offsetEnd ?? 0)) / window.toleranceEnd;
147
+ return Math.max(start, end);
148
+ };
149
+ /** The readings in clusters: nearest pairs first, never two readings of one copy in one cluster. */
150
+ const clustered = (readings, edges) => {
151
+ const parent = new Map(readings.map(reading => [reading.id, reading.id]));
152
+ const members = new Map(readings.map(reading => [reading.id, [reading]]));
153
+ const find = (id) => {
154
+ let root = id;
155
+ while (parent.get(root) !== root)
156
+ root = parent.get(root);
157
+ parent.set(id, root);
158
+ return root;
159
+ };
160
+ for (const { x, y } of edges) {
161
+ const a = find(x.id);
162
+ const b = find(y.id);
163
+ if (a === b)
164
+ continue;
165
+ const one = members.get(a);
166
+ const other = members.get(b);
167
+ const copies = new Set(one.map(reading => reading.copy));
168
+ if (other.some(reading => copies.has(reading.copy)))
169
+ continue;
170
+ const [root, child] = a < b ? [a, b] : [b, a];
171
+ parent.set(child, root);
172
+ members.set(root, [...members.get(root), ...members.get(child)]);
173
+ members.delete(child);
174
+ }
175
+ return [...members.values()];
176
+ };
177
+ const readingId = (key, carriers) => `symbol_${v5(`${key}|${[...carriers].sort().join(',')}`, READINGS)}`;
178
+ const asCollated = (members, order) => {
179
+ const sorted = [...members].sort((a, b) => order.get(a.copy) - order.get(b.copy));
180
+ const carriers = sorted.map(reading => reading.feature);
181
+ return {
182
+ id: readingId(sorted[0].key, carriers),
183
+ meaning: sorted[0].meaning,
184
+ key: sorted[0].key,
185
+ carriers,
186
+ copies: sorted.map(reading => reading.copy),
187
+ place: {
188
+ unit: 'mm',
189
+ from: medianOf(sorted.map(reading => reading.place.from)),
190
+ to: medianOf(sorted.map(reading => reading.place.to))
191
+ }
192
+ };
193
+ };
194
+ /**
195
+ * Where the copies of a system re-cut from another hold what the other
196
+ * latches: for each such pair of copies, the held commands that stand
197
+ * for a latched pair, as `substitutionsBetween` finds them. Each is
198
+ * stated once, however many copies of the older system it was found
199
+ * against.
200
+ */
201
+ const recodingsOf = (edition, copies, readingsOf, collatedOf) => {
202
+ const systemOf = new Map(copies.map(id => [id, barOf(edition.copies.find(copy => copy.id === id)).id]));
203
+ const transfers = transfersOf();
204
+ const asExpression = (reading) => ({ type: 'expression', id: reading.id, carriers: [], ...reading.meaning });
205
+ const placeOf = new Map([...readingsOf.values()].flat().map(reading => [reading.id, reading.place]));
206
+ const locate = (symbol) => placeOf.get(symbol.id);
207
+ const found = new Map();
208
+ for (const newer of copies) {
209
+ for (const older of copies) {
210
+ if (!transfers.get(systemOf.get(older))?.has(systemOf.get(newer)))
211
+ continue;
212
+ const own = readingsOf.get(newer).filter(reading => reading.meaning.type === 'expression').map(asExpression);
213
+ const inherited = readingsOf.get(older).filter(reading => reading.meaning.type === 'expression').map(asExpression);
214
+ for (const substitution of substitutionsBetween(own, inherited, locate, RECODING_TOLERANCE)) {
215
+ const by = [...new Set(substitution.by.map(symbol => collatedOf.get(symbol.id)))].sort();
216
+ const replaced = [...new Set(substitution.replaced.map(symbol => collatedOf.get(symbol.id)))].sort();
217
+ const key = `${by.join(',')}>${replaced.join(',')}`;
218
+ if (!found.has(key))
219
+ found.set(key, { replaced, by });
220
+ }
221
+ }
222
+ }
223
+ return [...found.values()].sort((a, b) => compare(a.by[0], b.by[0]));
224
+ };
225
+ /**
226
+ * Collates the copies all at once. Every reading is compared with every
227
+ * reading of the same meaning on the other copies, and two are taken to
228
+ * be one where they lie within the window of their pair of copies at
229
+ * both ends. The readings are then joined nearest pair first, and never
230
+ * so that one copy would read a joined reading twice.
231
+ *
232
+ * Collating one copy after another against what was collated before, as
233
+ * connecting versions does, depends on the order: six copies collated in
234
+ * their 720 orders give hundreds of different results. Compared all with
235
+ * all, the result is the same whichever order the copies are given in,
236
+ * and only the copies themselves decide it.
237
+ * @category Filiation
238
+ */
239
+ export const collateCopies = (edition, copyIds) => {
240
+ const copies = copyIds.filter(id => edition.copies.some(copy => copy.id === id));
241
+ const order = new Map(copies.map((id, index) => [id, index]));
242
+ const readingsOf = new Map(copies.map(id => [id, readingsOfCopy(edition.copies.find(copy => copy.id === id))]));
243
+ const all = [...readingsOf.values()].flat();
244
+ const byKey = groupBy(all, reading => reading.key);
245
+ const windows = windowsOf(copies, byKey);
246
+ const edges = [];
247
+ for (const readings of byKey.values()) {
248
+ const sorted = [...readings].sort((a, b) => a.place.from - b.place.from || compare(a.id, b.id));
249
+ for (let i = 0; i < sorted.length; i++) {
250
+ for (let j = i + 1; j < sorted.length && sorted[j].place.from - sorted[i].place.from <= REACH; j++) {
251
+ const x = sorted[i];
252
+ const y = sorted[j];
253
+ if (x.copy === y.copy)
254
+ continue;
255
+ const d = distanceIn(windows, order, x, y);
256
+ if (d > 1 + 1e-9)
257
+ continue;
258
+ const [p, q] = x.id < y.id ? [x, y] : [y, x];
259
+ edges.push({ x: p, y: q, d });
260
+ }
261
+ }
262
+ }
263
+ edges.sort((a, b) => a.d - b.d || compare(a.x.id, b.x.id) || compare(a.y.id, b.y.id));
264
+ const readings = clustered(all, edges)
265
+ .map(members => asCollated(members, order))
266
+ .sort((a, b) => a.place.from - b.place.from || compare(a.key, b.key) || compare(a.id, b.id));
267
+ const collatedOf = new Map();
268
+ for (const reading of readings) {
269
+ reading.carriers.forEach(feature => collatedOf.set(`${feature}|${reading.key}`, reading.id));
270
+ }
271
+ const extent = Object.fromEntries(copies.map(id => {
272
+ const own = readingsOf.get(id);
273
+ return [id, {
274
+ from: mm(Math.min(...own.map(reading => reading.place.from))),
275
+ to: mm(Math.max(...own.map(reading => reading.place.from)))
276
+ }];
277
+ }));
278
+ return { copies, readings, windows, recodings: recodingsOf(edition, copies, readingsOf, collatedOf), extent };
279
+ };
280
+ /** Whether the bar has a word for the meaning at all. */
281
+ export const reads = (bar, meaning) => bar.positionOf(meaning) !== undefined;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The stemma that explains the presence of the readings at the least
3
+ * cost, searched among all of them, and what each node of it holds.
4
+ */
5
+ import { Presence } from "./presence.js";
6
+ /**
7
+ * What the search counts against a stemma. A reading arising costs
8
+ * `gain`, and a reading the base text holds arose too. A reading lost
9
+ * costs 1, or less where the editorial principles say a loss is weak
10
+ * evidence.
11
+ * @category Filiation
12
+ */
13
+ export interface Costs {
14
+ /**
15
+ * A reading arising. Ten times a loss, since an addition at exactly
16
+ * the same place is very unlikely to be made twice (shared addition),
17
+ * while a perforation is easily struck.
18
+ */
19
+ gain: number;
20
+ /**
21
+ * A redundant reading lost, for the share of a loss: the removal of
22
+ * redundancies is expected and happens in two branches independently
23
+ * (independent cleanup).
24
+ */
25
+ redundantLoss: number;
26
+ /**
27
+ * A reading lost on the way into another system, for the share of a
28
+ * loss: a re-cut was an editorial pass of its own, and what it lacks
29
+ * says little about where it was cut from.
30
+ */
31
+ transferLoss: number;
32
+ /**
33
+ * The copy carrying a version lacking one of its readings, since a
34
+ * copy may fail to show what its version holds. Where it is
35
+ * `Infinity`, a copy carries its version exactly.
36
+ */
37
+ copyDeviation: number;
38
+ /** A lost state, so that one is posited only where shared additions require it. */
39
+ lostState: number;
40
+ }
41
+ /**
42
+ * The costs the edition of WM 225 is reproduced with: a gain 10, a lost
43
+ * redundancy 0.25, a loss on a transfer 0.25, a deviation of a copy 1
44
+ * and a lost state 4. They were found by searching for values that give
45
+ * that stemma, so they say that it is consistent with such rules, not
46
+ * that the rules confirm it.
47
+ * @category Filiation
48
+ */
49
+ export declare const defaultCosts: Costs;
50
+ /**
51
+ * A stemma laid out for counting: its nodes children first, the root
52
+ * last, each with the copy it carries, its parent, its system, whether
53
+ * it was re-cut from its parent, and the copies at and below it.
54
+ */
55
+ export interface LaidOut {
56
+ copy: Int16Array;
57
+ parent: Int16Array;
58
+ transfer: Uint8Array;
59
+ system: string[];
60
+ below: Int32Array;
61
+ lost: number;
62
+ notation: string;
63
+ }
64
+ /**
65
+ * A stemma the search scored, in a notation naming the copies: a lost
66
+ * state as `*`, the versions derived from a node in brackets after it.
67
+ * @category Filiation
68
+ */
69
+ export interface Scored {
70
+ notation: string;
71
+ cost: number;
72
+ lost: number;
73
+ }
74
+ /**
75
+ * How much more the best stemma without one of the best one's groupings
76
+ * costs: the margin by which the copies decide it.
77
+ * @category Filiation
78
+ */
79
+ export interface Support {
80
+ /** The copies the grouping holds, as the copies of a node and those below it. */
81
+ copies: string[];
82
+ /** Whether it is the grouping of a copy carrying a version above others, rather than of the copies below a node. */
83
+ ancestor?: string;
84
+ margin: number;
85
+ }
86
+ /** The outcome of the search. */
87
+ export interface Search {
88
+ best: LaidOut;
89
+ cost: number;
90
+ scored: number;
91
+ alternatives: Scored[];
92
+ supports: Support[];
93
+ }
94
+ /**
95
+ * Scores every stemma of the copies and keeps the best, the runners-up
96
+ * and, for each grouping of the best, the best stemma without it. Ties
97
+ * go to fewer lost states, then to the notation, so that the outcome
98
+ * depends on nothing but the copies and the costs.
99
+ */
100
+ export declare const searchStemmata: (copies: readonly string[], names: readonly string[], systems: readonly string[], rows: readonly {
101
+ presence: readonly Presence[];
102
+ redundant: boolean;
103
+ }[], costs: Costs, transfers: ReadonlyMap<string, ReadonlySet<string>>, keep: number) => Search;
104
+ /**
105
+ * What every node holds of one reading under the least costly account
106
+ * of it: children first, as `LaidOut` orders the nodes. A node may be
107
+ * held to a state (`fixed`), as an edit the editor wrote holds a
108
+ * version to what it inserts or deletes. Where two accounts cost the
109
+ * same, the one with the node lacking the reading is taken, so that a
110
+ * reading arises as late as the copies allow. Nothing where the fixed
111
+ * states cannot be met.
112
+ */
113
+ export declare const statesOf: (tree: LaidOut, presence: readonly Presence[], redundant: boolean, costs: Costs, fixed?: ReadonlyMap<number, boolean>) => boolean[] | undefined;