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.
- package/README.md +106 -5
- package/lib/analysis/constraints.d.ts +7 -0
- package/lib/analysis/constraints.js +1 -0
- package/lib/analysis/ownPaper.d.ts +5 -1
- package/lib/analysis/ownPaper.js +5 -1
- package/lib/analysis/paper.d.ts +22 -4
- package/lib/analysis/paper.js +6 -0
- package/lib/analysis/reservations.d.ts +24 -1
- package/lib/analysis/reservations.js +15 -1
- package/lib/analysis/sigla.d.ts +6 -1
- package/lib/analysis/sigla.js +5 -1
- package/lib/analysis/stemma.d.ts +6 -1
- package/lib/analysis/stemma.js +6 -1
- package/lib/analysis/text.d.ts +16 -2
- package/lib/analysis/text.js +16 -2
- package/lib/analysis/witnesses.d.ts +20 -1
- package/lib/analysis/witnesses.js +4 -0
- package/lib/collation/Collation.d.ts +47 -8
- package/lib/collation/Collation.js +29 -5
- package/lib/collation/alignment.d.ts +16 -3
- package/lib/collation/alignment.js +15 -3
- package/lib/collation/scatter.d.ts +50 -9
- package/lib/collation/scatter.js +11 -1
- package/lib/collation/statistics.d.ts +39 -9
- package/lib/collation/statistics.js +27 -6
- package/lib/collation/substitution.d.ts +5 -0
- package/lib/collation/substitution.js +4 -0
- package/lib/emulation/Emulation.d.ts +16 -1
- package/lib/emulation/Emulation.js +10 -0
- package/lib/emulation/negotiation.d.ts +1 -0
- package/lib/emulation/negotiation.js +1 -0
- package/lib/filiation/adoption.d.ts +47 -0
- package/lib/filiation/adoption.js +190 -0
- package/lib/filiation/changes.d.ts +33 -0
- package/lib/filiation/changes.js +38 -0
- package/lib/filiation/index.d.ts +12 -0
- package/lib/filiation/index.js +12 -0
- package/lib/filiation/presence.d.ts +50 -0
- package/lib/filiation/presence.js +122 -0
- package/lib/filiation/proposal.d.ts +132 -0
- package/lib/filiation/proposal.js +376 -0
- package/lib/filiation/readings.d.ts +108 -0
- package/lib/filiation/readings.js +281 -0
- package/lib/filiation/search.d.ts +113 -0
- package/lib/filiation/search.js +268 -0
- package/lib/filiation/systems.d.ts +21 -0
- package/lib/filiation/systems.js +39 -0
- package/lib/filiation/trees.d.ts +20 -0
- package/lib/filiation/trees.js +69 -0
- package/lib/index.d.ts +34 -2
- package/lib/index.js +3 -1
- package/lib/io/asJsonLd.d.ts +10 -0
- package/lib/io/asJsonLd.js +10 -0
- package/lib/io/context.d.ts +16 -2
- package/lib/io/context.js +16 -2
- package/lib/io/importJsonLd.d.ts +1 -0
- package/lib/io/importJsonLd.js +1 -0
- package/lib/io/migrate.d.ts +8 -0
- package/lib/io/migrate.js +8 -0
- package/lib/lookup/lookup.d.ts +29 -5
- package/lib/lookup/lookup.js +29 -5
- package/lib/lookup/paths.d.ts +23 -3
- package/lib/lookup/paths.js +17 -3
- package/lib/model/Agent.d.ts +28 -2
- package/lib/model/Agent.js +15 -1
- package/lib/model/Assumption.d.ts +96 -7
- package/lib/model/Assumption.js +65 -7
- package/lib/model/ConditionState.d.ts +1 -0
- package/lib/model/Edit.d.ts +10 -0
- package/lib/model/Edit.js +8 -0
- package/lib/model/Edition.d.ts +11 -0
- package/lib/model/Edition.js +1 -0
- package/lib/model/Feature.d.ts +74 -7
- package/lib/model/Feature.js +41 -3
- package/lib/model/FeatureSource.d.ts +14 -1
- package/lib/model/FeatureSource.js +7 -1
- package/lib/model/Perforator.d.ts +10 -0
- package/lib/model/Perforator.js +1 -0
- package/lib/model/Quantity.d.ts +160 -13
- package/lib/model/Quantity.js +110 -6
- package/lib/model/RollCopy.d.ts +74 -12
- package/lib/model/RollCopy.js +53 -10
- package/lib/model/Symbol.d.ts +38 -1
- package/lib/model/Symbol.js +16 -0
- package/lib/model/TrackCalibration.d.ts +10 -2
- package/lib/model/TrackCalibration.js +9 -2
- package/lib/model/Version.d.ts +24 -4
- package/lib/model/Version.js +19 -4
- package/lib/model/notes.d.ts +15 -2
- package/lib/model/notes.js +9 -2
- package/lib/model/procedures.d.ts +5 -1
- package/lib/model/procedures.js +5 -1
- package/lib/model/vocabulary.d.ts +6 -1
- package/lib/model/vocabulary.js +6 -1
- package/lib/ops/beliefs.d.ts +23 -1
- package/lib/ops/beliefs.js +23 -1
- package/lib/ops/collation.d.ts +3 -0
- package/lib/ops/collation.js +3 -0
- package/lib/ops/commands.d.ts +17 -2
- package/lib/ops/commands.js +17 -2
- package/lib/ops/copies.d.ts +37 -6
- package/lib/ops/copies.js +37 -6
- package/lib/ops/draft.d.ts +2 -0
- package/lib/ops/draft.js +1 -0
- package/lib/ops/edits.d.ts +5 -1
- package/lib/ops/edits.js +5 -1
- package/lib/ops/features.d.ts +12 -1
- package/lib/ops/features.js +7 -0
- package/lib/ops/versions.d.ts +15 -3
- package/lib/ops/versions.js +15 -3
- package/lib/readers/phillipsEroll.d.ts +12 -1
- package/lib/readers/phillipsEroll.js +6 -1
- package/lib/readers/spencerBar.d.ts +12 -1
- package/lib/readers/spencerBar.js +7 -1
- package/lib/readers/stanfordAton.d.ts +15 -0
- package/lib/readers/stanfordAton.js +9 -0
- package/lib/schema.json +10 -0
- package/lib/shared/utils.d.ts +12 -0
- package/lib/spec/context.json +4 -0
- package/lib/systems/ReproducingSystem.d.ts +46 -3
- package/lib/systems/TrackerBar.d.ts +45 -4
- package/lib/systems/TrackerBar.js +20 -3
- package/lib/systems/index.d.ts +9 -2
- package/lib/systems/index.js +9 -2
- package/lib/systems/velocity.d.ts +1 -0
- package/lib/systems/velocity.js +1 -0
- package/lib/systems/welte.d.ts +18 -3
- package/lib/systems/welte.js +9 -2
- package/lib/systems/welteLicensee/bar.d.ts +1 -0
- package/lib/systems/welteLicensee/bar.js +1 -0
- package/lib/systems/welteLicensee/system.d.ts +18 -0
- package/lib/systems/welteLicensee/system.js +17 -0
- package/lib/systems/welteT100/bar.d.ts +8 -0
- package/lib/systems/welteT100/bar.js +4 -0
- package/lib/systems/welteT100/system.d.ts +46 -5
- package/lib/systems/welteT100/system.js +29 -3
- package/lib/systems/welteT98/bar.d.ts +6 -0
- package/lib/systems/welteT98/bar.js +2 -0
- package/lib/systems/welteT98/system.d.ts +58 -5
- package/lib/systems/welteT98/system.js +42 -3
- package/lib/validate.d.ts +16 -0
- package/lib/validate.js +15 -0
- package/package.json +2 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { leadsTo, systemAbove } from "./systems.js";
|
|
2
|
+
import { stemmataOf } from "./trees.js";
|
|
3
|
+
/**
|
|
4
|
+
* The costs the edition of WM 225 is reproduced with: a gain 10, a lost
|
|
5
|
+
* redundancy 0.25, a loss on a transfer 0.25, a deviation of a copy 1
|
|
6
|
+
* and a lost state 4. They were found by searching for values that give
|
|
7
|
+
* that stemma, so they say that it is consistent with such rules, not
|
|
8
|
+
* that the rules confirm it.
|
|
9
|
+
* @category Filiation
|
|
10
|
+
*/
|
|
11
|
+
export const defaultCosts = {
|
|
12
|
+
gain: 10,
|
|
13
|
+
redundantLoss: 0.25,
|
|
14
|
+
transferLoss: 0.25,
|
|
15
|
+
copyDeviation: 1,
|
|
16
|
+
lostState: 4
|
|
17
|
+
};
|
|
18
|
+
const INF = 1e12;
|
|
19
|
+
const lay = (tree, systems, names, transfers) => {
|
|
20
|
+
const copy = [];
|
|
21
|
+
const parent = [];
|
|
22
|
+
const system = [];
|
|
23
|
+
const below = [];
|
|
24
|
+
let lost = 0;
|
|
25
|
+
const visit = (node) => {
|
|
26
|
+
const visited = [];
|
|
27
|
+
for (const child of node.children) {
|
|
28
|
+
const done = visit(child);
|
|
29
|
+
if (!done)
|
|
30
|
+
return undefined;
|
|
31
|
+
visited.push(done);
|
|
32
|
+
}
|
|
33
|
+
const under = new Set(visited.flatMap(child => [...child.systems]));
|
|
34
|
+
let own;
|
|
35
|
+
if (node.copy !== undefined) {
|
|
36
|
+
own = systems[node.copy];
|
|
37
|
+
// A copy carrying a version above others is of a system that leads to theirs.
|
|
38
|
+
if ([...under].some(other => !leadsTo(transfers, own, other)))
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
own = systemAbove(transfers, under);
|
|
43
|
+
if (own === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
lost++;
|
|
46
|
+
}
|
|
47
|
+
const index = copy.length;
|
|
48
|
+
copy.push(node.copy ?? -1);
|
|
49
|
+
parent.push(-1);
|
|
50
|
+
system.push(own);
|
|
51
|
+
below.push(visited.reduce((mask, child) => mask | below[child.index], node.copy !== undefined ? 1 << node.copy : 0));
|
|
52
|
+
visited.forEach(child => { parent[child.index] = index; });
|
|
53
|
+
const label = node.copy !== undefined ? names[node.copy] : '*';
|
|
54
|
+
const notation = visited.length === 0 ? label : `${label}(${visited.map(child => child.notation).sort().join(',')})`;
|
|
55
|
+
return { index, systems: new Set([own, ...under]), notation };
|
|
56
|
+
};
|
|
57
|
+
const root = visit(tree);
|
|
58
|
+
if (!root)
|
|
59
|
+
return undefined;
|
|
60
|
+
const transfer = system.map((own, index) => parent[index] >= 0 && system[parent[index]] !== own ? 1 : 0);
|
|
61
|
+
return {
|
|
62
|
+
copy: Int16Array.from(copy),
|
|
63
|
+
parent: Int16Array.from(parent),
|
|
64
|
+
transfer: Uint8Array.from(transfer),
|
|
65
|
+
system,
|
|
66
|
+
below: Int32Array.from(below),
|
|
67
|
+
lost,
|
|
68
|
+
notation: root.notation
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const classesOf = (rows, copies, costs) => {
|
|
72
|
+
const counts = new Map();
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const key = `${row.presence.map(p => p[0]).join('')}${row.redundant ? 'r' : ''}`;
|
|
75
|
+
const known = counts.get(key);
|
|
76
|
+
if (known)
|
|
77
|
+
known.n++;
|
|
78
|
+
else
|
|
79
|
+
counts.set(key, { row, n: 1 });
|
|
80
|
+
}
|
|
81
|
+
const classes = [...counts.values()];
|
|
82
|
+
return {
|
|
83
|
+
weight: Float64Array.from(classes.map(({ n }) => n)),
|
|
84
|
+
loss: Float64Array.from(classes.map(({ row }) => row.redundant ? costs.redundantLoss : 1)),
|
|
85
|
+
forbidsAbsent: Array.from({ length: copies }, (_, c) => Uint8Array.from(classes.map(({ row }) => row.presence[c] === 'present' ? 1 : 0))),
|
|
86
|
+
forbidsPresent: Array.from({ length: copies }, (_, c) => Uint8Array.from(classes.map(({ row }) => row.presence[c] === 'absent' ? 1 : 0))),
|
|
87
|
+
total: rows.length
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The cost of every class at every node, children first: what the
|
|
92
|
+
* subtree below costs if the node lacks the reading (`absent`) and if
|
|
93
|
+
* it holds it (`present`).
|
|
94
|
+
*/
|
|
95
|
+
const fill = (tree, classes, costs, absent, present) => {
|
|
96
|
+
const k = classes.weight.length;
|
|
97
|
+
for (let v = 0; v < tree.copy.length; v++) {
|
|
98
|
+
const a = absent[v];
|
|
99
|
+
const p = present[v];
|
|
100
|
+
a.fill(0);
|
|
101
|
+
p.fill(0);
|
|
102
|
+
const copy = tree.copy[v];
|
|
103
|
+
if (copy >= 0) {
|
|
104
|
+
const inner = tree.below[v] !== 1 << copy;
|
|
105
|
+
const noAbsent = classes.forbidsAbsent[copy];
|
|
106
|
+
const noPresent = classes.forbidsPresent[copy];
|
|
107
|
+
for (let c = 0; c < k; c++) {
|
|
108
|
+
if (noAbsent[c])
|
|
109
|
+
a[c] = INF;
|
|
110
|
+
if (noPresent[c])
|
|
111
|
+
p[c] = inner ? costs.copyDeviation : INF;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (let v = 0; v < tree.copy.length; v++) {
|
|
116
|
+
const u = tree.parent[v];
|
|
117
|
+
if (u < 0)
|
|
118
|
+
continue;
|
|
119
|
+
const factor = tree.transfer[v] ? costs.transferLoss : 1;
|
|
120
|
+
const a = absent[v];
|
|
121
|
+
const p = present[v];
|
|
122
|
+
const pa = absent[u];
|
|
123
|
+
const pp = present[u];
|
|
124
|
+
for (let c = 0; c < k; c++) {
|
|
125
|
+
pa[c] += Math.min(a[c], p[c] + costs.gain);
|
|
126
|
+
pp[c] += Math.min(p[c], a[c] + classes.loss[c] * factor);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const scoreOf = (tree, classes, costs, absent, present) => {
|
|
131
|
+
fill(tree, classes, costs, absent, present);
|
|
132
|
+
const root = tree.copy.length - 1;
|
|
133
|
+
let sum = 0;
|
|
134
|
+
for (let c = 0; c < classes.weight.length; c++) {
|
|
135
|
+
sum += classes.weight[c] * Math.min(absent[root][c], present[root][c] + costs.gain);
|
|
136
|
+
}
|
|
137
|
+
return sum - costs.gain * classes.total + costs.lostState * tree.lost;
|
|
138
|
+
};
|
|
139
|
+
const before = (a, b) => a.cost < b.cost - 1e-9 || (Math.abs(a.cost - b.cost) <= 1e-9 && (a.lost < b.lost || (a.lost === b.lost && a.notation < b.notation)));
|
|
140
|
+
/**
|
|
141
|
+
* Scores every stemma of the copies and keeps the best, the runners-up
|
|
142
|
+
* and, for each grouping of the best, the best stemma without it. Ties
|
|
143
|
+
* go to fewer lost states, then to the notation, so that the outcome
|
|
144
|
+
* depends on nothing but the copies and the costs.
|
|
145
|
+
*/
|
|
146
|
+
export const searchStemmata = (copies, names, systems, rows, costs, transfers, keep) => {
|
|
147
|
+
const n = copies.length;
|
|
148
|
+
const classes = classesOf(rows, n, costs);
|
|
149
|
+
const pool = 2 * n;
|
|
150
|
+
const absent = Array.from({ length: pool }, () => new Float64Array(classes.weight.length));
|
|
151
|
+
const present = Array.from({ length: pool }, () => new Float64Array(classes.weight.length));
|
|
152
|
+
const masks = 1 << n;
|
|
153
|
+
const without = new Float64Array(masks).fill(Infinity);
|
|
154
|
+
const notAncestor = new Float64Array(n).fill(Infinity);
|
|
155
|
+
const top = [];
|
|
156
|
+
let scored = 0;
|
|
157
|
+
for (const tree of stemmataOf(n)) {
|
|
158
|
+
const laid = lay(tree, systems, names, transfers);
|
|
159
|
+
if (!laid)
|
|
160
|
+
continue;
|
|
161
|
+
scored++;
|
|
162
|
+
const cost = scoreOf(laid, classes, costs, absent, present);
|
|
163
|
+
const entry = { notation: laid.notation, cost, lost: laid.lost, tree: laid };
|
|
164
|
+
if (top.length <= keep || before(entry, top[top.length - 1])) {
|
|
165
|
+
top.push(entry);
|
|
166
|
+
top.sort((a, b) => before(a, b) ? -1 : before(b, a) ? 1 : 0);
|
|
167
|
+
if (top.length > keep + 1)
|
|
168
|
+
top.pop();
|
|
169
|
+
}
|
|
170
|
+
const groupings = new Set();
|
|
171
|
+
let ancestors = 0;
|
|
172
|
+
for (let v = 0; v < laid.copy.length; v++) {
|
|
173
|
+
if (laid.below[v] === (laid.copy[v] >= 0 ? 1 << laid.copy[v] : -1))
|
|
174
|
+
continue;
|
|
175
|
+
groupings.add(laid.below[v]);
|
|
176
|
+
if (laid.copy[v] >= 0)
|
|
177
|
+
ancestors |= 1 << laid.copy[v];
|
|
178
|
+
}
|
|
179
|
+
for (let mask = 1; mask < masks; mask++) {
|
|
180
|
+
if (!groupings.has(mask) && cost < without[mask])
|
|
181
|
+
without[mask] = cost;
|
|
182
|
+
}
|
|
183
|
+
for (let c = 0; c < n; c++) {
|
|
184
|
+
if (!(ancestors & (1 << c)) && cost < notAncestor[c])
|
|
185
|
+
notAncestor[c] = cost;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (top.length === 0)
|
|
189
|
+
throw new Error('No stemma of these copies is admissible');
|
|
190
|
+
const best = top[0];
|
|
191
|
+
const supports = [];
|
|
192
|
+
const named = (mask) => copies.filter((_, c) => mask & (1 << c));
|
|
193
|
+
const seen = new Set();
|
|
194
|
+
for (let v = 0; v < best.tree.copy.length; v++) {
|
|
195
|
+
const mask = best.tree.below[v];
|
|
196
|
+
const copy = best.tree.copy[v];
|
|
197
|
+
if (copy >= 0 && mask === 1 << copy)
|
|
198
|
+
continue;
|
|
199
|
+
if (mask === masks - 1 && copy < 0)
|
|
200
|
+
continue;
|
|
201
|
+
if (!seen.has(mask)) {
|
|
202
|
+
seen.add(mask);
|
|
203
|
+
supports.push({ copies: named(mask), margin: without[mask] - best.cost });
|
|
204
|
+
}
|
|
205
|
+
if (copy >= 0)
|
|
206
|
+
supports.push({ copies: named(mask), ancestor: copies[copy], margin: notAncestor[copy] - best.cost });
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
best: best.tree,
|
|
210
|
+
cost: best.cost,
|
|
211
|
+
scored,
|
|
212
|
+
alternatives: top.slice(1).map(({ notation, cost, lost }) => ({ notation, cost, lost })),
|
|
213
|
+
supports
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* What every node holds of one reading under the least costly account
|
|
218
|
+
* of it: children first, as `LaidOut` orders the nodes. A node may be
|
|
219
|
+
* held to a state (`fixed`), as an edit the editor wrote holds a
|
|
220
|
+
* version to what it inserts or deletes. Where two accounts cost the
|
|
221
|
+
* same, the one with the node lacking the reading is taken, so that a
|
|
222
|
+
* reading arises as late as the copies allow. Nothing where the fixed
|
|
223
|
+
* states cannot be met.
|
|
224
|
+
*/
|
|
225
|
+
export const statesOf = (tree, presence, redundant, costs, fixed) => {
|
|
226
|
+
const size = tree.copy.length;
|
|
227
|
+
const absent = new Float64Array(size);
|
|
228
|
+
const present = new Float64Array(size);
|
|
229
|
+
const loss = redundant ? costs.redundantLoss : 1;
|
|
230
|
+
for (let v = 0; v < size; v++) {
|
|
231
|
+
const copy = tree.copy[v];
|
|
232
|
+
if (copy >= 0) {
|
|
233
|
+
const inner = tree.below[v] !== 1 << copy;
|
|
234
|
+
if (presence[copy] === 'present')
|
|
235
|
+
absent[v] = INF;
|
|
236
|
+
if (presence[copy] === 'absent')
|
|
237
|
+
present[v] = inner ? costs.copyDeviation : INF;
|
|
238
|
+
}
|
|
239
|
+
const held = fixed?.get(v);
|
|
240
|
+
if (held === true)
|
|
241
|
+
absent[v] = INF;
|
|
242
|
+
if (held === false)
|
|
243
|
+
present[v] = INF;
|
|
244
|
+
}
|
|
245
|
+
for (let v = 0; v < size; v++) {
|
|
246
|
+
const u = tree.parent[v];
|
|
247
|
+
if (u < 0)
|
|
248
|
+
continue;
|
|
249
|
+
const factor = tree.transfer[v] ? costs.transferLoss : 1;
|
|
250
|
+
absent[u] += Math.min(absent[v], present[v] + costs.gain);
|
|
251
|
+
present[u] += Math.min(present[v], absent[v] + loss * factor);
|
|
252
|
+
}
|
|
253
|
+
const states = Array.from({ length: size }, () => false);
|
|
254
|
+
const root = size - 1;
|
|
255
|
+
const rootAbsent = absent[root];
|
|
256
|
+
const rootPresent = present[root] + costs.gain;
|
|
257
|
+
if (Math.min(rootAbsent, rootPresent) >= INF / 2)
|
|
258
|
+
return undefined;
|
|
259
|
+
states[root] = rootPresent < rootAbsent;
|
|
260
|
+
for (let v = size - 2; v >= 0; v--) {
|
|
261
|
+
const u = tree.parent[v];
|
|
262
|
+
const factor = tree.transfer[v] ? costs.transferLoss : 1;
|
|
263
|
+
const a = states[u] ? absent[v] + loss * factor : absent[v];
|
|
264
|
+
const p = states[u] ? present[v] : present[v] + costs.gain;
|
|
265
|
+
states[v] = p < a;
|
|
266
|
+
}
|
|
267
|
+
return states;
|
|
268
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TrackerBar } from "../systems/TrackerBar.js";
|
|
2
|
+
/**
|
|
3
|
+
* By system, the systems a roll of it was re-cut for, as the procedures
|
|
4
|
+
* of the vocabulary name them: a red roll was re-punched for the
|
|
5
|
+
* Licensee system and for the green one, and never the other way round.
|
|
6
|
+
* @category Filiation
|
|
7
|
+
*/
|
|
8
|
+
export declare const transfersOf: () => ReadonlyMap<string, ReadonlySet<string>>;
|
|
9
|
+
/** Whether a version of the one system can stand above a version of the other: the same system, or one re-cut from it. */
|
|
10
|
+
export declare const leadsTo: (transfers: ReadonlyMap<string, ReadonlySet<string>>, from: string, to: string) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The system of a lost version above versions of the given systems: the
|
|
13
|
+
* one they share, or else the one they were all re-cut from. A lost
|
|
14
|
+
* state above a red and a Licensee version is red, since a Licensee
|
|
15
|
+
* version is never re-cut into red. Where no system leads to all of
|
|
16
|
+
* them, nothing.
|
|
17
|
+
* @category Filiation
|
|
18
|
+
*/
|
|
19
|
+
export declare const systemAbove: (transfers: ReadonlyMap<string, ReadonlySet<string>>, systems: ReadonlySet<string>) => string | undefined;
|
|
20
|
+
/** The bar of a system the library knows, by its identifier. */
|
|
21
|
+
export declare const barNamed: (id: string) => TrackerBar | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Which reproducing system a version of the stemma is coded for, and which systems a roll was re-cut into. */
|
|
2
|
+
import { procedures } from "../model/procedures.js";
|
|
3
|
+
import { trackerBars } from "../systems/index.js";
|
|
4
|
+
const TRANSFER = /\/umstanzung\/(.+)-to-(.+)$/;
|
|
5
|
+
/**
|
|
6
|
+
* By system, the systems a roll of it was re-cut for, as the procedures
|
|
7
|
+
* of the vocabulary name them: a red roll was re-punched for the
|
|
8
|
+
* Licensee system and for the green one, and never the other way round.
|
|
9
|
+
* @category Filiation
|
|
10
|
+
*/
|
|
11
|
+
export const transfersOf = () => {
|
|
12
|
+
const transfers = new Map();
|
|
13
|
+
for (const procedure of procedures) {
|
|
14
|
+
const match = TRANSFER.exec(procedure.id ?? "");
|
|
15
|
+
if (!match)
|
|
16
|
+
continue;
|
|
17
|
+
const [from, to] = [match[1], match[2]];
|
|
18
|
+
transfers.set(from, new Set([...(transfers.get(from) ?? []), to]));
|
|
19
|
+
}
|
|
20
|
+
return transfers;
|
|
21
|
+
};
|
|
22
|
+
/** Whether a version of the one system can stand above a version of the other: the same system, or one re-cut from it. */
|
|
23
|
+
export const leadsTo = (transfers, from, to) => from === to || (transfers.get(from)?.has(to) ?? false);
|
|
24
|
+
/**
|
|
25
|
+
* The system of a lost version above versions of the given systems: the
|
|
26
|
+
* one they share, or else the one they were all re-cut from. A lost
|
|
27
|
+
* state above a red and a Licensee version is red, since a Licensee
|
|
28
|
+
* version is never re-cut into red. Where no system leads to all of
|
|
29
|
+
* them, nothing.
|
|
30
|
+
* @category Filiation
|
|
31
|
+
*/
|
|
32
|
+
export const systemAbove = (transfers, systems) => {
|
|
33
|
+
if (systems.size === 1)
|
|
34
|
+
return [...systems][0];
|
|
35
|
+
const candidates = new Set([...systems, ...transfers.keys()]);
|
|
36
|
+
return [...candidates].sort().find(candidate => [...systems].every(system => leadsTo(transfers, candidate, system)));
|
|
37
|
+
};
|
|
38
|
+
/** The bar of a system the library knows, by its identifier. */
|
|
39
|
+
export const barNamed = (id) => trackerBars.find(bar => bar.id === id);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every stemma a handful of copies can stand in: rooted trees whose
|
|
3
|
+
* nodes are versions, a copy carrying any of them, a lost one above
|
|
4
|
+
* any two or more.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A node of a stemma: the copy carrying it at first hand, by its number,
|
|
8
|
+
* or none for a lost state, and the nodes derived from it.
|
|
9
|
+
*/
|
|
10
|
+
export interface StemmaNode {
|
|
11
|
+
readonly copy?: number;
|
|
12
|
+
readonly children: readonly StemmaNode[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Every stemma of n copies: 3 for two copies, 91,984 for six. They are
|
|
16
|
+
* produced one by one, so that only the leaf trees, far fewer, are held.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stemmataOf(n: number): Generator<StemmaNode>;
|
|
19
|
+
/** How many stemmata the exhaustive search scores, at most. */
|
|
20
|
+
export declare const MAX_COPIES = 7;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every stemma a handful of copies can stand in: rooted trees whose
|
|
3
|
+
* nodes are versions, a copy carrying any of them, a lost one above
|
|
4
|
+
* any two or more.
|
|
5
|
+
*/
|
|
6
|
+
const leaf = (copy) => ({ copy, children: [] });
|
|
7
|
+
/** Every way to add the copy to the tree: as a further child of a lost node, or on an edge, above any subtree. */
|
|
8
|
+
function* insertions(tree, copy) {
|
|
9
|
+
yield { children: [tree, leaf(copy)] };
|
|
10
|
+
if (tree.children.length === 0)
|
|
11
|
+
return;
|
|
12
|
+
yield { ...tree, children: [...tree.children, leaf(copy)] };
|
|
13
|
+
for (let i = 0; i < tree.children.length; i++) {
|
|
14
|
+
for (const child of insertions(tree.children[i], copy)) {
|
|
15
|
+
yield { ...tree, children: [...tree.children.slice(0, i), child, ...tree.children.slice(i + 1)] };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The trees whose leaves are the copies 0 to n − 1 and whose inner nodes are all lost, each once. */
|
|
20
|
+
const leafTrees = (n) => {
|
|
21
|
+
if (n === 1)
|
|
22
|
+
return [leaf(0)];
|
|
23
|
+
let trees = [{ children: [leaf(0), leaf(1)] }];
|
|
24
|
+
for (let copy = 2; copy < n; copy++) {
|
|
25
|
+
trees = trees.flatMap(tree => [...insertions(tree, copy)]);
|
|
26
|
+
}
|
|
27
|
+
return trees;
|
|
28
|
+
};
|
|
29
|
+
const product = (lists) => lists.reduce((combos, list) => combos.flatMap(combo => list.map(item => [...combo, item])), [[]]);
|
|
30
|
+
/**
|
|
31
|
+
* The tree with as many of its copies moved up as it allows: at each
|
|
32
|
+
* lost node, at most one copy among its leaf children may come to carry
|
|
33
|
+
* it instead, making that copy's text the ancestor of the others'.
|
|
34
|
+
* Every stemma arises from exactly one leaf tree in exactly one way.
|
|
35
|
+
*/
|
|
36
|
+
function* promotions(tree) {
|
|
37
|
+
if (tree.children.length === 0) {
|
|
38
|
+
yield tree;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const options = tree.children.map(child => [...promotions(child)]);
|
|
42
|
+
const leaves = tree.children.flatMap((child, index) => child.children.length === 0 ? [index] : []);
|
|
43
|
+
for (const combo of product(options)) {
|
|
44
|
+
yield { children: combo };
|
|
45
|
+
for (const index of leaves) {
|
|
46
|
+
yield { copy: tree.children[index].copy, children: combo.filter((_, i) => i !== index) };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const cache = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Every stemma of n copies: 3 for two copies, 91,984 for six. They are
|
|
53
|
+
* produced one by one, so that only the leaf trees, far fewer, are held.
|
|
54
|
+
*/
|
|
55
|
+
export function* stemmataOf(n) {
|
|
56
|
+
let trees = cache.get(n);
|
|
57
|
+
if (!trees) {
|
|
58
|
+
trees = leafTrees(n);
|
|
59
|
+
cache.set(n, trees);
|
|
60
|
+
}
|
|
61
|
+
if (n === 1) {
|
|
62
|
+
yield trees[0];
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const tree of trees)
|
|
66
|
+
yield* promotions(tree);
|
|
67
|
+
}
|
|
68
|
+
/** How many stemmata the exhaustive search scores, at most. */
|
|
69
|
+
export const MAX_COPIES = 7;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Digital editions of piano rolls: reading an edition and writing it out
|
|
3
|
+
* as JSON-LD, finding what it holds, changing it through operations on
|
|
4
|
+
* an immer draft, reading copies from scans and analysis files,
|
|
5
|
+
* collating them into versions, and working out what the edition can
|
|
6
|
+
* and cannot vouch for. The reproducing systems that play a version are
|
|
7
|
+
* entry points of their own, so that the emulator they need is loaded
|
|
8
|
+
* only where it is wanted.
|
|
9
|
+
*
|
|
2
10
|
* The public interface of the library. Everything else under lib/ is its
|
|
3
11
|
* implementation and may change in any release: the in-place helpers the
|
|
4
12
|
* operations are built from, the date conversions of the serialisation,
|
|
5
|
-
* and the general utilities.
|
|
13
|
+
* and the general utilities. So may the exports tagged `@internal`,
|
|
14
|
+
* which the API reference leaves out.
|
|
15
|
+
*
|
|
16
|
+
* @module linked-rolls
|
|
17
|
+
*
|
|
18
|
+
* @categoryDescription Format types
|
|
19
|
+
* The types of an edition. They are documented field by field, with the
|
|
20
|
+
* ontology terms each field maps to, in the
|
|
21
|
+
* [format reference](https://pfefferniels.github.io/linked-rolls/); they
|
|
22
|
+
* are listed here so that the signatures can link to them.
|
|
23
|
+
*
|
|
24
|
+
* @categoryDescription Units
|
|
25
|
+
* A quantity is a plain number at runtime; its unit lives in its type, so
|
|
26
|
+
* that a length in millimetres cannot be passed where one in pixels is
|
|
27
|
+
* wanted. The constructors make a number into a quantity.
|
|
6
28
|
*/
|
|
7
29
|
export type { WithId, WithNote, WithType } from './shared/utils.js';
|
|
8
30
|
export * from './model/Quantity.js';
|
|
31
|
+
/**
|
|
32
|
+
* A number in a unit, the unit carried in the type alone: at runtime it
|
|
33
|
+
* is the plain number it was made from. A number becomes a quantity only
|
|
34
|
+
* through a constructor such as `mm` or `track`, and a quantity in one
|
|
35
|
+
* unit cannot stand in for one in another.
|
|
36
|
+
* @category Units
|
|
37
|
+
*/
|
|
38
|
+
export type { Quantity } from './model/Quantity.js';
|
|
9
39
|
export * from './model/Agent.js';
|
|
10
40
|
export * from './model/Assumption.js';
|
|
11
41
|
export * from './model/ConditionState.js';
|
|
@@ -41,10 +71,12 @@ export * from './emulation/negotiation.js';
|
|
|
41
71
|
export * from './collation/scatter.js';
|
|
42
72
|
export * from './ops/index.js';
|
|
43
73
|
export * from './emulation/Emulation.js';
|
|
44
|
-
|
|
74
|
+
/** @category Reading and writing */
|
|
75
|
+
export { validate, type ValidateEdition } from './validate.js';
|
|
45
76
|
export * from './analysis/constraints.js';
|
|
46
77
|
export * from './collation/substitution.js';
|
|
47
78
|
export * from './io/context.js';
|
|
79
|
+
export * from './filiation/index.js';
|
|
48
80
|
export * from './analysis/sigla.js';
|
|
49
81
|
export * from './model/notes.js';
|
|
50
82
|
export { asJsonLd } from './io/asJsonLd.js';
|
package/lib/index.js
CHANGED
|
@@ -34,10 +34,12 @@ export * from './emulation/negotiation.js';
|
|
|
34
34
|
export * from './collation/scatter.js';
|
|
35
35
|
export * from './ops/index.js';
|
|
36
36
|
export * from './emulation/Emulation.js';
|
|
37
|
-
|
|
37
|
+
/** @category Reading and writing */
|
|
38
|
+
export { validate } from './validate.js';
|
|
38
39
|
export * from './analysis/constraints.js';
|
|
39
40
|
export * from './collation/substitution.js';
|
|
40
41
|
export * from './io/context.js';
|
|
42
|
+
export * from './filiation/index.js';
|
|
41
43
|
export * from './analysis/sigla.js';
|
|
42
44
|
export * from './model/notes.js';
|
|
43
45
|
export { asJsonLd } from './io/asJsonLd.js';
|
package/lib/io/asJsonLd.d.ts
CHANGED
|
@@ -5,4 +5,14 @@ import { Edition } from "../model/Edition.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const derivedKeys: ReadonlySet<string>;
|
|
7
7
|
export declare const exportDate: (date: Date) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Writes the edition as a JSON-LD document in the terms of the Roll
|
|
10
|
+
* Edition Ontology. Each copy's features and tears go back to the
|
|
11
|
+
* copy's own places, as they were read, with the alignment beside them,
|
|
12
|
+
* and every version carries the context of its own reproducing system.
|
|
13
|
+
* A reference the edition holds possible, unlikely or false is quoted
|
|
14
|
+
* rather than stated, so that a reader of the RDF does not take it for
|
|
15
|
+
* a fact. The edition given is not changed.
|
|
16
|
+
* @category Reading and writing
|
|
17
|
+
*/
|
|
8
18
|
export declare const asJsonLd: (edition: Edition) => any;
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -160,6 +160,16 @@ const withDoubtedReferencesQuoted = (value, quoted) => {
|
|
|
160
160
|
const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurements.alignment)
|
|
161
161
|
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
162
162
|
: edition;
|
|
163
|
+
/**
|
|
164
|
+
* Writes the edition as a JSON-LD document in the terms of the Roll
|
|
165
|
+
* Edition Ontology. Each copy's features and tears go back to the
|
|
166
|
+
* copy's own places, as they were read, with the alignment beside them,
|
|
167
|
+
* and every version carries the context of its own reproducing system.
|
|
168
|
+
* A reference the edition holds possible, unlikely or false is quoted
|
|
169
|
+
* rather than stated, so that a reader of the RDF does not take it for
|
|
170
|
+
* a fact. The edition given is not changed.
|
|
171
|
+
* @category Reading and writing
|
|
172
|
+
*/
|
|
163
173
|
export const asJsonLd = (edition) => {
|
|
164
174
|
const quoted = [];
|
|
165
175
|
const node = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))), quoted);
|
package/lib/io/context.d.ts
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON-LD context of the format, which an exported edition names by
|
|
3
|
+
* its address, `https://w3id.org/reo/context.jsonld`: it maps the keys of
|
|
4
|
+
* the document onto the Roll Edition Ontology, CIDOC CRM, LRMoo and
|
|
5
|
+
* CRMinf.
|
|
6
|
+
* @category Reading and writing
|
|
7
|
+
*/
|
|
1
8
|
export { default as jsonLdContext } from '../spec/context.json';
|
|
2
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The context of the Welte-Mignon T-100, added to a version coded for it.
|
|
11
|
+
* @category Reading and writing
|
|
12
|
+
*/
|
|
3
13
|
export { default as welteT100JsonLdContext } from '../spec/welte-t100.context.json';
|
|
4
14
|
/**
|
|
5
15
|
* The context of the Welte-Mignon Licensee. Its expression terms are
|
|
6
16
|
* the T-100's, the two systems differing in the tracker scale rather
|
|
7
17
|
* than in the coding, so it reads them out of the T-100's vocabulary
|
|
8
18
|
* rather than minting a second set of IRIs for the same commands.
|
|
19
|
+
* @category Reading and writing
|
|
9
20
|
*/
|
|
10
21
|
export { default as welteLicenseeJsonLdContext } from '../spec/welte-licensee.context.json';
|
|
11
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The context of the Welte-Mignon T-98, added to an edition of a green roll.
|
|
24
|
+
* @category Reading and writing
|
|
25
|
+
*/
|
|
12
26
|
export { default as welteT98JsonLdContext } from '../spec/welte-t98.context.json';
|
package/lib/io/context.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON-LD context of the format, which an exported edition names by
|
|
3
|
+
* its address, `https://w3id.org/reo/context.jsonld`: it maps the keys of
|
|
4
|
+
* the document onto the Roll Edition Ontology, CIDOC CRM, LRMoo and
|
|
5
|
+
* CRMinf.
|
|
6
|
+
* @category Reading and writing
|
|
7
|
+
*/
|
|
1
8
|
export { default as jsonLdContext } from '../spec/context.json' with { type: 'json' };
|
|
2
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The context of the Welte-Mignon T-100, added to a version coded for it.
|
|
11
|
+
* @category Reading and writing
|
|
12
|
+
*/
|
|
3
13
|
export { default as welteT100JsonLdContext } from '../spec/welte-t100.context.json' with { type: 'json' };
|
|
4
14
|
/**
|
|
5
15
|
* The context of the Welte-Mignon Licensee. Its expression terms are
|
|
6
16
|
* the T-100's, the two systems differing in the tracker scale rather
|
|
7
17
|
* than in the coding, so it reads them out of the T-100's vocabulary
|
|
8
18
|
* rather than minting a second set of IRIs for the same commands.
|
|
19
|
+
* @category Reading and writing
|
|
9
20
|
*/
|
|
10
21
|
export { default as welteLicenseeJsonLdContext } from '../spec/welte-licensee.context.json' with { type: 'json' };
|
|
11
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The context of the Welte-Mignon T-98, added to an edition of a green roll.
|
|
24
|
+
* @category Reading and writing
|
|
25
|
+
*/
|
|
12
26
|
export { default as welteT98JsonLdContext } from '../spec/welte-t98.context.json' with { type: 'json' };
|
package/lib/io/importJsonLd.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type Json = any;
|
|
|
5
5
|
* Reads an edition. A document holds each copy's features and tears at
|
|
6
6
|
* the copy's own places, as they were read; the edition holds them on
|
|
7
7
|
* its axis, which is where the copy's alignment carries them.
|
|
8
|
+
* @category Reading and writing
|
|
8
9
|
*/
|
|
9
10
|
export declare const importJsonLd: (json: Json) => Edition;
|
|
10
11
|
export {};
|
package/lib/io/importJsonLd.js
CHANGED
|
@@ -133,6 +133,7 @@ const withQuotedStatementsInPlace = (edition) => {
|
|
|
133
133
|
* Reads an edition. A document holds each copy's features and tears at
|
|
134
134
|
* the copy's own places, as they were read; the edition holds them on
|
|
135
135
|
* its axis, which is where the copy's alignment carries them.
|
|
136
|
+
* @category Reading and writing
|
|
136
137
|
*/
|
|
137
138
|
export const importJsonLd = (json) => {
|
|
138
139
|
const { '@context': context, ...document } = withoutEmptyCreations(withPlainCopyIds(migrate(withQuotedStatementsInPlace(json))));
|
package/lib/io/migrate.d.ts
CHANGED
|
@@ -13,5 +13,13 @@
|
|
|
13
13
|
type Json = any;
|
|
14
14
|
/** A copy's id without the `copy/` that documents of earlier releases put before it. */
|
|
15
15
|
export declare const plainCopyId: (id: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Brings the JSON of an edition written by an earlier release of the
|
|
18
|
+
* format up to the current shape. A document that is current already
|
|
19
|
+
* passes through unchanged, and one may be migrated any number of
|
|
20
|
+
* times. `importJsonLd` migrates what it reads, so this is needed only
|
|
21
|
+
* to look at an old document as JSON.
|
|
22
|
+
* @category Reading and writing
|
|
23
|
+
*/
|
|
16
24
|
export declare const migrate: (edition: Json) => Json;
|
|
17
25
|
export {};
|
package/lib/io/migrate.js
CHANGED
|
@@ -677,4 +677,12 @@ const withReferenceCopy = (edition) => {
|
|
|
677
677
|
: edition;
|
|
678
678
|
};
|
|
679
679
|
const editionSteps = [withoutFormatVersion, withoutTempoAdjustment, withoutEditionType, withSystems, withEditors, withDerivationTolerance, withReferenceCopy];
|
|
680
|
+
/**
|
|
681
|
+
* Brings the JSON of an edition written by an earlier release of the
|
|
682
|
+
* format up to the current shape. A document that is current already
|
|
683
|
+
* passes through unchanged, and one may be migrated any number of
|
|
684
|
+
* times. `importJsonLd` migrates what it reads, so this is needed only
|
|
685
|
+
* to look at an old document as JSON.
|
|
686
|
+
* @category Reading and writing
|
|
687
|
+
*/
|
|
680
688
|
export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
|