linked-rolls 0.62.0 → 0.63.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 +14 -3
- package/lib/analysis/constraints.js +16 -10
- package/lib/analysis/ownPaper.d.ts +9 -14
- package/lib/analysis/ownPaper.js +13 -31
- package/lib/analysis/paper.d.ts +129 -0
- package/lib/analysis/paper.js +260 -0
- package/lib/collation/alignment.d.ts +46 -8
- package/lib/collation/alignment.js +137 -47
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/io/asJsonLd.js +6 -1
- package/lib/io/importJsonLd.d.ts +5 -0
- package/lib/io/importJsonLd.js +7 -0
- package/lib/io/migrate.js +94 -2
- package/lib/model/Assumption.d.ts +21 -9
- package/lib/model/Edition.d.ts +19 -0
- package/lib/model/Edition.js +9 -1
- package/lib/model/Quantity.d.ts +4 -1
- package/lib/model/Quantity.js +1 -0
- package/lib/model/RollCopy.d.ts +144 -48
- package/lib/model/RollCopy.js +9 -6
- package/lib/ops/copies.d.ts +47 -10
- package/lib/ops/copies.js +92 -26
- package/lib/ops/index.d.ts +1 -1
- package/lib/ops/index.js +1 -1
- package/lib/readers/stanfordAton.js +28 -1
- package/lib/schema.json +183 -24
- package/lib/spec/context.json +33 -1
- package/lib/systems/ReproducingSystem.d.ts +7 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,12 +22,15 @@ single undo step.
|
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
24
|
import { produce } from 'immer'
|
|
25
|
-
import { asJsonLd, connectVersions, createVersion, EditionView, importJsonLd, readFromStanfordAton } from 'linked-rolls'
|
|
25
|
+
import { alignCopy, asJsonLd, connectVersions, createVersion, EditionView, importJsonLd, readFromStanfordAton } from 'linked-rolls'
|
|
26
26
|
|
|
27
27
|
let edition = importJsonLd(json)
|
|
28
28
|
|
|
29
|
-
// Add a copy read from a SUPRA analysis file, with a version of its own
|
|
30
|
-
|
|
29
|
+
// Add a copy read from a SUPRA analysis file, with a version of its own,
|
|
30
|
+
// and align it with the reference copy, whose millimetres are the axis
|
|
31
|
+
const copy = readFromStanfordAton(aton)
|
|
32
|
+
edition = produce(edition, createVersion(copy))
|
|
33
|
+
edition = produce(edition, alignCopy(copy.id))
|
|
31
34
|
|
|
32
35
|
// Collate that version against another one
|
|
33
36
|
const view = new EditionView(edition)
|
|
@@ -36,6 +39,14 @@ edition = produce(edition, connectVersions(view, childId, parentId))
|
|
|
36
39
|
const document = asJsonLd(edition)
|
|
37
40
|
```
|
|
38
41
|
|
|
42
|
+
Within the library every place along the roll is a place on the edition's
|
|
43
|
+
axis, the millimetres of its reference copy. A document holds each copy's
|
|
44
|
+
features at the copy's own places, as they were read, together with the
|
|
45
|
+
alignment that carries them onto the axis; the import and the export map
|
|
46
|
+
between the two. What the alignments say about the paper of the copies,
|
|
47
|
+
how far each has stretched and how the papers of the systems relate, is
|
|
48
|
+
worked out by `paperOf`.
|
|
49
|
+
|
|
39
50
|
Documents written by earlier releases are brought up to date on import.
|
|
40
51
|
To check a document against the schema:
|
|
41
52
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { paperOfVersion } from "./ownPaper.js";
|
|
2
|
+
import { alignmentProblems } from "./paper.js";
|
|
2
3
|
import { idOf } from "../model/Assumption.js";
|
|
3
4
|
import { isCommand, pairsAmong, placementsOf } from "../model/Symbol.js";
|
|
4
5
|
import { keyOf } from "../systems/TrackerBar.js";
|
|
@@ -91,17 +92,22 @@ const carriersOffTheirMeaning = (view, version, commands) => {
|
|
|
91
92
|
.map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
|
|
92
93
|
};
|
|
93
94
|
/**
|
|
94
|
-
* Where
|
|
95
|
-
*
|
|
95
|
+
* Where a copy of the version's own system strays from the others in
|
|
96
|
+
* its paper by more than paper does.
|
|
96
97
|
*
|
|
97
|
-
*
|
|
98
|
-
* ran on, so a performance needs one
|
|
99
|
-
* is evidence about the copies,
|
|
100
|
-
*
|
|
98
|
+
* The paper of the system is what takes a place back to the paper the
|
|
99
|
+
* version's roll ran on, so a performance needs one figure. A copy far
|
|
100
|
+
* off it is evidence about the copies, perhaps one cut for another
|
|
101
|
+
* speed, and averaging it in would hide both that and the fact that the
|
|
102
|
+
* playback rests on a guess.
|
|
101
103
|
*/
|
|
102
|
-
const paperDisagreed = (view, version) =>
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
const paperDisagreed = (view, version) => {
|
|
105
|
+
const own = new Set(paperOfVersion(view, version)?.copies ?? []);
|
|
106
|
+
return alignmentProblems(view.edition)
|
|
107
|
+
.some(({ copy, problem }) => problem === 'paper-beyond-its-spread' && own.has(copy))
|
|
108
|
+
? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
|
|
109
|
+
: [];
|
|
110
|
+
};
|
|
105
111
|
/**
|
|
106
112
|
* A version's strikes that take nothing out of its text: each deleted
|
|
107
113
|
* symbol has to be one the parent hands down, or one the version put
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { EditionView } from "../view/EditionView.js";
|
|
2
2
|
import type { Version } from "../model/Version.js";
|
|
3
|
-
import {
|
|
4
|
-
/**
|
|
5
|
-
export declare const
|
|
6
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
7
|
-
export declare const speedScalesIn: (view: EditionView, version: Readonly<Version>) => number[];
|
|
3
|
+
import { PaperOfSystem } from "./paper.js";
|
|
4
|
+
/** What the alignments say about the paper of the version's own system, where they say anything. */
|
|
5
|
+
export declare const paperOfVersion: (view: EditionView, version: Readonly<Version>) => PaperOfSystem | undefined;
|
|
8
6
|
/**
|
|
9
7
|
* How a place on the edition's shared axis relates to the paper of
|
|
10
8
|
* this version: place × factor = millimetres of its own paper.
|
|
@@ -12,15 +10,12 @@ export declare const speedScalesIn: (view: EditionView, version: Readonly<Versio
|
|
|
12
10
|
* Copies cut for different systems are scaled onto one axis so that
|
|
13
11
|
* they can be collated at all, which leaves a version of another
|
|
14
12
|
* system carrying places in the axis copy's millimetres. A
|
|
15
|
-
* performance needs the paper the roll actually ran on,
|
|
16
|
-
*
|
|
13
|
+
* performance needs the paper the roll actually ran on, unstretched,
|
|
14
|
+
* and that is what the alignments of all the copies give together
|
|
15
|
+
* (`paperOf`): the ratio of the version's system to the reference
|
|
16
|
+
* copy's, over how far the reference copy's paper has stretched.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
21
|
-
* scale is put down to its own paper having stretched is left out as
|
|
22
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
23
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
24
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
18
|
+
* Nothing where no copy of the version's system measures its paper,
|
|
19
|
+
* which leaves the performance on the axis.
|
|
25
20
|
*/
|
|
26
21
|
export declare const toOwnPaperOf: (view: EditionView, version: Readonly<Version>) => number | undefined;
|
package/lib/analysis/ownPaper.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const carrying = new Set(view.snapshot(version.id)
|
|
8
|
-
.flatMap(symbol => idsOf(symbol.carriers))
|
|
9
|
-
.flatMap(id => {
|
|
10
|
-
const copy = view.copyOf(id);
|
|
11
|
-
return copy ? [copy.id] : [];
|
|
12
|
-
}));
|
|
13
|
-
return view.edition.copies.filter(copy => carrying.has(copy.id) && systemIdOf(copy.production?.system) === system);
|
|
1
|
+
import { defaultTrackerBar, trackerBarOf } from "../systems/index.js";
|
|
2
|
+
import { paperOf } from "./paper.js";
|
|
3
|
+
/** What the alignments say about the paper of the version's own system, where they say anything. */
|
|
4
|
+
export const paperOfVersion = (view, version) => {
|
|
5
|
+
const system = (trackerBarOf(version.system) ?? defaultTrackerBar).id;
|
|
6
|
+
return paperOf(view.edition)?.systems.find(paper => paper.system === system);
|
|
14
7
|
};
|
|
15
|
-
/** The scales of the version's own copies that are not their own paper stretch. */
|
|
16
|
-
export const speedScalesIn = (view, version) => [...new Set(copiesOwning(view, version)
|
|
17
|
-
.filter(copy => !copy.conditions.some(isPaperStretch))
|
|
18
|
-
.map(copy => copy.measurements.scale)
|
|
19
|
-
.filter((scale) => scale !== undefined && scale > 0))];
|
|
20
8
|
/**
|
|
21
9
|
* How a place on the edition's shared axis relates to the paper of
|
|
22
10
|
* this version: place × factor = millimetres of its own paper.
|
|
@@ -24,18 +12,12 @@ export const speedScalesIn = (view, version) => [...new Set(copiesOwning(view, v
|
|
|
24
12
|
* Copies cut for different systems are scaled onto one axis so that
|
|
25
13
|
* they can be collated at all, which leaves a version of another
|
|
26
14
|
* system carrying places in the axis copy's millimetres. A
|
|
27
|
-
* performance needs the paper the roll actually ran on,
|
|
28
|
-
*
|
|
15
|
+
* performance needs the paper the roll actually ran on, unstretched,
|
|
16
|
+
* and that is what the alignments of all the copies give together
|
|
17
|
+
* (`paperOf`): the ratio of the version's system to the reference
|
|
18
|
+
* copy's, over how far the reference copy's paper has stretched.
|
|
29
19
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* copies too and those say nothing about green paper. A copy whose
|
|
33
|
-
* scale is put down to its own paper having stretched is left out as
|
|
34
|
-
* well: that is a fact about the one exemplar, not about the speed
|
|
35
|
-
* the system's rolls were cut at. Where what remains disagrees,
|
|
36
|
-
* `constraintProblems` reports it rather than averaging it away.
|
|
20
|
+
* Nothing where no copy of the version's system measures its paper,
|
|
21
|
+
* which leaves the performance on the axis.
|
|
37
22
|
*/
|
|
38
|
-
export const toOwnPaperOf = (view, version) =>
|
|
39
|
-
const scales = speedScalesIn(view, version);
|
|
40
|
-
return scales.length === 1 ? 1 / scales[0] : undefined;
|
|
41
|
-
};
|
|
23
|
+
export const toOwnPaperOf = (view, version) => paperOfVersion(view, version)?.toOwnPaper;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Edition } from "../model/Edition.js";
|
|
2
|
+
import { Strain } from "../model/RollCopy.js";
|
|
3
|
+
import { Percent } from "../model/Quantity.js";
|
|
4
|
+
/**
|
|
5
|
+
* How far the paper of one copy is taken to have stretched or shrunk
|
|
6
|
+
* along the roll, as a standard deviation, where the edition has too few
|
|
7
|
+
* copies of one system to say: the three red copies of Welte 225 spread
|
|
8
|
+
* by 0.19 %. It includes the scanner's error along the roll, which the
|
|
9
|
+
* alignment cannot tell from the paper's.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PAPER_SPREAD: Percent;
|
|
12
|
+
/**
|
|
13
|
+
* How far a paper speed a label or the literature states is taken to be
|
|
14
|
+
* from the speed a copy was cut at: the literature gives the red roll's
|
|
15
|
+
* three metres a minute as 2.9 as well (Bärtsch 2020), which is 3.4 %.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SPEED_TOLERANCE = 0.035;
|
|
18
|
+
/** What the alignments say about one copy's paper. */
|
|
19
|
+
export interface PaperOfCopy {
|
|
20
|
+
copy: string;
|
|
21
|
+
/** The system the copy was cut for, by the id of its bar. */
|
|
22
|
+
system: string;
|
|
23
|
+
/** How far its paper has stretched along the roll since it was perforated, and how surely that is known. */
|
|
24
|
+
along: Strain;
|
|
25
|
+
/** Whether a strain measured on the copy went into it, rather than the spread of paper alone. */
|
|
26
|
+
measured: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** What the alignments say about the paper the copies of one system were cut on. */
|
|
29
|
+
export interface PaperOfSystem {
|
|
30
|
+
/** The system, by the id of its bar. */
|
|
31
|
+
system: string;
|
|
32
|
+
/**
|
|
33
|
+
* The length of this system's paper, unstretched, for a length of the
|
|
34
|
+
* reference system's: one for the reference copy's own system, about
|
|
35
|
+
* 0.77 for a Licensee or green re-cut of a red roll. It is the ratio
|
|
36
|
+
* of the paper speeds the two were cut for.
|
|
37
|
+
*/
|
|
38
|
+
ratio: number;
|
|
39
|
+
/** The standard uncertainty of the ratio. */
|
|
40
|
+
ratioError: number;
|
|
41
|
+
/**
|
|
42
|
+
* A place on the axis times this is millimetres of this system's
|
|
43
|
+
* paper, unstretched: what a performance of one of its versions runs
|
|
44
|
+
* on (`RollProperties.toOwnPaper`).
|
|
45
|
+
*/
|
|
46
|
+
toOwnPaper: number;
|
|
47
|
+
/** The standard uncertainty of `toOwnPaper`. */
|
|
48
|
+
toOwnPaperError: number;
|
|
49
|
+
/** The copies of the system it rests on. */
|
|
50
|
+
copies: string[];
|
|
51
|
+
}
|
|
52
|
+
/** What the alignments of an edition say about the paper of its copies, and how surely. */
|
|
53
|
+
export interface PaperReading {
|
|
54
|
+
/** The copy whose millimetres are the axis. */
|
|
55
|
+
reference: string;
|
|
56
|
+
/**
|
|
57
|
+
* How far one copy's paper is taken to stray from its system's, and
|
|
58
|
+
* how many degrees of freedom the copies give it: none where it is
|
|
59
|
+
* `PAPER_SPREAD` for want of copies.
|
|
60
|
+
*/
|
|
61
|
+
spread: {
|
|
62
|
+
value: Percent;
|
|
63
|
+
degreesOfFreedom: number;
|
|
64
|
+
};
|
|
65
|
+
copies: PaperOfCopy[];
|
|
66
|
+
systems: PaperOfSystem[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What the alignments of the edition say about the paper of its copies:
|
|
70
|
+
* how far each copy's paper has stretched along the roll, and how long
|
|
71
|
+
* the paper of each system is against the reference copy's.
|
|
72
|
+
*
|
|
73
|
+
* An alignment's scale is one number with three causes. A copy cut for
|
|
74
|
+
* another paper speed is shorter or longer by the ratio of the speeds,
|
|
75
|
+
* which the system's copies share; its paper has stretched or shrunk as
|
|
76
|
+
* it alone has, and so has the reference copy's. In true strains, the
|
|
77
|
+
* logarithms of the ratios of the lengths, the causes add:
|
|
78
|
+
*
|
|
79
|
+
* ln scale = strain_reference − strain_copy − ln ratio_system
|
|
80
|
+
*
|
|
81
|
+
* The alignments give the left side to a few parts in a hundred thousand.
|
|
82
|
+
* The right side has a strain for every copy and a ratio for every
|
|
83
|
+
* system, so it is found by least squares with each strain taken to lie
|
|
84
|
+
* about zero by the spread of paper (`PAPER_SPREAD`, or what the copies
|
|
85
|
+
* show where there are enough of one system), or about a strain
|
|
86
|
+
* measured on the copy by that measurement's uncertainty. Where a system
|
|
87
|
+
* has one copy only its ratio takes the whole scale and the copy's strain
|
|
88
|
+
* stays at zero, known no better than paper varies; where the copies of
|
|
89
|
+
* the reference system are several, the axis comes out at their mean
|
|
90
|
+
* paper and not at the reference copy's.
|
|
91
|
+
*
|
|
92
|
+
* Copies whose places are not places on the paper, such as a roll
|
|
93
|
+
* reader's, say nothing about the paper and are left out. Nothing is
|
|
94
|
+
* said where the edition has no reference copy.
|
|
95
|
+
*/
|
|
96
|
+
export declare const paperOf: (edition: Pick<Edition, 'copies' | 'referenceCopy'>) => PaperReading | undefined;
|
|
97
|
+
/** Where the alignments of the copies, or what they say about the paper, want looking into. */
|
|
98
|
+
export type AlignmentProblem = {
|
|
99
|
+
copy: string;
|
|
100
|
+
problem:
|
|
101
|
+
/** The copy has features but no alignment, and is not the reference copy. */
|
|
102
|
+
'not-aligned'
|
|
103
|
+
/** It was aligned against a copy that is not the reference copy now. */
|
|
104
|
+
| 'aligned-against-another-copy'
|
|
105
|
+
/**
|
|
106
|
+
* The speed stated for it and the reference copy's stated or
|
|
107
|
+
* nominal one give another ratio of the papers than the
|
|
108
|
+
* alignments, by more than a stated speed may be off.
|
|
109
|
+
*/
|
|
110
|
+
| 'speed-disagrees-with-paper'
|
|
111
|
+
/**
|
|
112
|
+
* Its paper strays from its system's by more than paper does:
|
|
113
|
+
* it may have been cut for another speed, or read wrongly.
|
|
114
|
+
*/
|
|
115
|
+
| 'paper-beyond-its-spread';
|
|
116
|
+
/**
|
|
117
|
+
* How far: for a speed, the logarithm of the ratio the speeds give
|
|
118
|
+
* over the one the alignments give; for a paper, the copy's strain in
|
|
119
|
+
* per cent.
|
|
120
|
+
*/
|
|
121
|
+
by?: number;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Where the alignments of the edition's copies, or what they say about
|
|
125
|
+
* the paper, want looking into. None of it is recomputed, so it is cheap
|
|
126
|
+
* to ask; whether an alignment would come out otherwise if found again
|
|
127
|
+
* is `alignmentFor`'s to say.
|
|
128
|
+
*/
|
|
129
|
+
export declare const alignmentProblems: (edition: Pick<Edition, 'copies' | 'referenceCopy'>) => AlignmentProblem[];
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { referenceCopyOf } from "../model/Edition.js";
|
|
2
|
+
import { barOf, featuresOf, paperStretchOf } from "../model/RollCopy.js";
|
|
3
|
+
import { certaintyOf, isAsserted } from "../model/Assumption.js";
|
|
4
|
+
import { inMetersPerMinute, percent } from "../model/Quantity.js";
|
|
5
|
+
/**
|
|
6
|
+
* How far the paper of one copy is taken to have stretched or shrunk
|
|
7
|
+
* along the roll, as a standard deviation, where the edition has too few
|
|
8
|
+
* copies of one system to say: the three red copies of Welte 225 spread
|
|
9
|
+
* by 0.19 %. It includes the scanner's error along the roll, which the
|
|
10
|
+
* alignment cannot tell from the paper's.
|
|
11
|
+
*/
|
|
12
|
+
export const PAPER_SPREAD = percent(0.2);
|
|
13
|
+
/**
|
|
14
|
+
* How far a paper speed a label or the literature states is taken to be
|
|
15
|
+
* from the speed a copy was cut at: the literature gives the red roll's
|
|
16
|
+
* three metres a minute as 2.9 as well (Bärtsch 2020), which is 3.4 %.
|
|
17
|
+
*/
|
|
18
|
+
export const SPEED_TOLERANCE = 0.035;
|
|
19
|
+
/** The kinds of source that give places on the paper, so that the length between two of them is the paper's. */
|
|
20
|
+
const measuringLength = new Set(['roll', 'scan', 'analysis']);
|
|
21
|
+
/**
|
|
22
|
+
* Whether the copy's places are places on its paper, so that its length
|
|
23
|
+
* between two notes is a length of paper: it is the reference copy or
|
|
24
|
+
* aligned with it, and was read off the roll or an image of it. A copy
|
|
25
|
+
* that names no source counts as read so. A roll reader measures time,
|
|
26
|
+
* and places worked out from it are only as long as the speed they were
|
|
27
|
+
* worked out with.
|
|
28
|
+
*/
|
|
29
|
+
const measuresPaper = (copy, reference) => (copy.id === reference.id || copy.measurements.alignment !== undefined)
|
|
30
|
+
&& featuresOf(copy).length > 0
|
|
31
|
+
&& (!copy.readFrom || measuringLength.has(copy.readFrom.kind));
|
|
32
|
+
/**
|
|
33
|
+
* A strain in per cent as the fit takes it: the logarithm of the ratio of
|
|
34
|
+
* the lengths, the true strain, which adds where the causes of a scale
|
|
35
|
+
* multiply. For the tenths of a per cent paper does it is the same.
|
|
36
|
+
*/
|
|
37
|
+
const logarithmicOf = (value) => Math.log1p(value / 100);
|
|
38
|
+
/** A true strain back in per cent of the length. */
|
|
39
|
+
const percentOf = (logarithmic) => percent(Math.expm1(logarithmic) * 100);
|
|
40
|
+
/** The strain measured on the copy along the roll, where one is and the edition holds it. */
|
|
41
|
+
const measuredAlong = (copy) => {
|
|
42
|
+
const stretch = paperStretchOf(copy);
|
|
43
|
+
return stretch && isAsserted(certaintyOf(stretch)) ? stretch.along : undefined;
|
|
44
|
+
};
|
|
45
|
+
/** How closely an alignment's scale is known, as a share of it, at no less than a part in a million. */
|
|
46
|
+
const scaleShareError = (copy) => {
|
|
47
|
+
const alignment = copy.measurements.alignment;
|
|
48
|
+
if (!alignment)
|
|
49
|
+
return 0;
|
|
50
|
+
const error = alignment.scaleError ?? 0;
|
|
51
|
+
return Math.max(Number.isFinite(error) ? error / alignment.scale : 0, 1e-6);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The spread of the copies' papers about their systems', as far as
|
|
55
|
+
* copies of one system can show it: each copy's length against the axis,
|
|
56
|
+
* `-ln scale`, differs from another's of its system by their strains
|
|
57
|
+
* alone, the ratio of the system being common to them.
|
|
58
|
+
*/
|
|
59
|
+
const spreadIn = (lengths) => {
|
|
60
|
+
const groups = [...lengths.values()];
|
|
61
|
+
const degreesOfFreedom = groups.reduce((total, group) => total + group.length - 1, 0);
|
|
62
|
+
if (degreesOfFreedom < 2)
|
|
63
|
+
return { value: logarithmicOf(PAPER_SPREAD), degreesOfFreedom: 0 };
|
|
64
|
+
const squares = groups.reduce((total, group) => {
|
|
65
|
+
const mean = group.reduce((sum, length) => sum + length, 0) / group.length;
|
|
66
|
+
return total + group.reduce((sum, length) => sum + (length - mean) ** 2, 0);
|
|
67
|
+
}, 0);
|
|
68
|
+
return { value: Math.sqrt(squares / degreesOfFreedom), degreesOfFreedom };
|
|
69
|
+
};
|
|
70
|
+
/** The inverse of a symmetric positive definite matrix, by Gauss and Jordan. */
|
|
71
|
+
const inverse = (matrix) => {
|
|
72
|
+
const n = matrix.length;
|
|
73
|
+
const work = matrix.map((row, i) => [...row, ...Array.from({ length: n }, (_, j) => (i === j ? 1 : 0))]);
|
|
74
|
+
for (let column = 0; column < n; column++) {
|
|
75
|
+
const pivot = work.reduce((best, row, i) => i >= column && Math.abs(row[column]) > Math.abs(work[best][column]) ? i : best, column);
|
|
76
|
+
[work[column], work[pivot]] = [work[pivot], work[column]];
|
|
77
|
+
const lead = work[column][column];
|
|
78
|
+
work[column] = work[column].map(value => value / lead);
|
|
79
|
+
work.forEach((row, i) => {
|
|
80
|
+
if (i === column)
|
|
81
|
+
return;
|
|
82
|
+
const factor = row[column];
|
|
83
|
+
work[i] = row.map((value, j) => value - factor * work[column][j]);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return work.map(row => row.slice(n));
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* What the alignments of the edition say about the paper of its copies:
|
|
90
|
+
* how far each copy's paper has stretched along the roll, and how long
|
|
91
|
+
* the paper of each system is against the reference copy's.
|
|
92
|
+
*
|
|
93
|
+
* An alignment's scale is one number with three causes. A copy cut for
|
|
94
|
+
* another paper speed is shorter or longer by the ratio of the speeds,
|
|
95
|
+
* which the system's copies share; its paper has stretched or shrunk as
|
|
96
|
+
* it alone has, and so has the reference copy's. In true strains, the
|
|
97
|
+
* logarithms of the ratios of the lengths, the causes add:
|
|
98
|
+
*
|
|
99
|
+
* ln scale = strain_reference − strain_copy − ln ratio_system
|
|
100
|
+
*
|
|
101
|
+
* The alignments give the left side to a few parts in a hundred thousand.
|
|
102
|
+
* The right side has a strain for every copy and a ratio for every
|
|
103
|
+
* system, so it is found by least squares with each strain taken to lie
|
|
104
|
+
* about zero by the spread of paper (`PAPER_SPREAD`, or what the copies
|
|
105
|
+
* show where there are enough of one system), or about a strain
|
|
106
|
+
* measured on the copy by that measurement's uncertainty. Where a system
|
|
107
|
+
* has one copy only its ratio takes the whole scale and the copy's strain
|
|
108
|
+
* stays at zero, known no better than paper varies; where the copies of
|
|
109
|
+
* the reference system are several, the axis comes out at their mean
|
|
110
|
+
* paper and not at the reference copy's.
|
|
111
|
+
*
|
|
112
|
+
* Copies whose places are not places on the paper, such as a roll
|
|
113
|
+
* reader's, say nothing about the paper and are left out. Nothing is
|
|
114
|
+
* said where the edition has no reference copy.
|
|
115
|
+
*/
|
|
116
|
+
export const paperOf = (edition) => {
|
|
117
|
+
const reference = referenceCopyOf(edition);
|
|
118
|
+
if (!reference)
|
|
119
|
+
return undefined;
|
|
120
|
+
const copies = edition.copies.filter(copy => measuresPaper(copy, reference));
|
|
121
|
+
if (!copies.some(copy => copy.id === reference.id))
|
|
122
|
+
return undefined;
|
|
123
|
+
const referenceSystem = barOf(reference).id;
|
|
124
|
+
const systemOf = (copy) => barOf(copy).id;
|
|
125
|
+
const systems = [...new Set(copies.map(systemOf))].filter(system => system !== referenceSystem);
|
|
126
|
+
const lengthOf = (copy) => copy.id === reference.id ? 0 : -Math.log(copy.measurements.alignment.scale);
|
|
127
|
+
const lengths = Map.groupBy(copies, systemOf);
|
|
128
|
+
const spread = spreadIn(new Map([...lengths].map(([system, group]) => [system, group.map(lengthOf)])));
|
|
129
|
+
// Unknowns: the strain of each copy, then the log ratio of each system but the reference's.
|
|
130
|
+
const strainAt = new Map(copies.map((copy, i) => [copy.id, i]));
|
|
131
|
+
const ratioAt = new Map(systems.map((system, i) => [system, copies.length + i]));
|
|
132
|
+
const referenceAt = strainAt.get(reference.id);
|
|
133
|
+
const aligned = copies
|
|
134
|
+
.filter(copy => copy.id !== reference.id)
|
|
135
|
+
.map(copy => {
|
|
136
|
+
const ratio = ratioAt.get(systemOf(copy));
|
|
137
|
+
return {
|
|
138
|
+
coefficients: new Map([
|
|
139
|
+
[referenceAt, 1],
|
|
140
|
+
[strainAt.get(copy.id), -1],
|
|
141
|
+
...(ratio !== undefined ? [[ratio, -1]] : [])
|
|
142
|
+
]),
|
|
143
|
+
value: Math.log(copy.measurements.alignment.scale),
|
|
144
|
+
variance: scaleShareError(copy) ** 2
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
const measured = new Map(copies.flatMap(copy => {
|
|
148
|
+
const strain = measuredAlong(copy);
|
|
149
|
+
return strain ? [[copy.id, strain]] : [];
|
|
150
|
+
}));
|
|
151
|
+
const priors = copies.map(copy => {
|
|
152
|
+
const strain = measured.get(copy.id);
|
|
153
|
+
const uncertainty = strain?.uncertainty !== undefined
|
|
154
|
+
? strain.uncertainty / (100 + strain.value)
|
|
155
|
+
: spread.value;
|
|
156
|
+
return {
|
|
157
|
+
coefficients: new Map([[strainAt.get(copy.id), 1]]),
|
|
158
|
+
value: strain ? logarithmicOf(strain.value) : 0,
|
|
159
|
+
variance: uncertainty ** 2
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
const size = copies.length + systems.length;
|
|
163
|
+
const normal = Array.from({ length: size }, () => Array.from({ length: size }, () => 0));
|
|
164
|
+
const right = Array.from({ length: size }, () => 0);
|
|
165
|
+
[...aligned, ...priors].forEach(({ coefficients, value, variance }) => {
|
|
166
|
+
const weight = 1 / variance;
|
|
167
|
+
coefficients.forEach((a, i) => {
|
|
168
|
+
right[i] += weight * a * value;
|
|
169
|
+
coefficients.forEach((b, j) => { normal[i][j] += weight * a * b; });
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
const covariance = inverse(normal);
|
|
173
|
+
const solution = covariance.map(row => row.reduce((total, value, j) => total + value * right[j], 0));
|
|
174
|
+
const strainReference = solution[referenceAt];
|
|
175
|
+
const varianceReference = covariance[referenceAt][referenceAt];
|
|
176
|
+
const systemReading = (system) => {
|
|
177
|
+
const at = ratioAt.get(system);
|
|
178
|
+
const logRatio = at === undefined ? 0 : solution[at];
|
|
179
|
+
const varianceRatio = at === undefined ? 0 : covariance[at][at];
|
|
180
|
+
const covarianceWithReference = at === undefined ? 0 : covariance[at][referenceAt];
|
|
181
|
+
const ratio = Math.exp(logRatio);
|
|
182
|
+
const toOwnPaper = Math.exp(logRatio - strainReference);
|
|
183
|
+
return {
|
|
184
|
+
system,
|
|
185
|
+
ratio,
|
|
186
|
+
ratioError: ratio * Math.sqrt(varianceRatio),
|
|
187
|
+
toOwnPaper,
|
|
188
|
+
toOwnPaperError: toOwnPaper * Math.sqrt(Math.max(0, varianceRatio + varianceReference - 2 * covarianceWithReference)),
|
|
189
|
+
copies: copies.filter(copy => systemOf(copy) === system).map(copy => copy.id)
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
reference: reference.id,
|
|
194
|
+
spread: { value: percentOf(spread.value), degreesOfFreedom: spread.degreesOfFreedom },
|
|
195
|
+
copies: copies.map(copy => {
|
|
196
|
+
const at = strainAt.get(copy.id);
|
|
197
|
+
return {
|
|
198
|
+
copy: copy.id,
|
|
199
|
+
system: systemOf(copy),
|
|
200
|
+
along: {
|
|
201
|
+
value: percentOf(solution[at]),
|
|
202
|
+
uncertainty: percent(Math.exp(solution[at]) * Math.sqrt(covariance[at][at]) * 100),
|
|
203
|
+
unit: 'percent'
|
|
204
|
+
},
|
|
205
|
+
measured: measured.has(copy.id)
|
|
206
|
+
};
|
|
207
|
+
}),
|
|
208
|
+
systems: [referenceSystem, ...systems].map(systemReading)
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
/** A paper speed the edition holds true or likely, in metres a minute. */
|
|
212
|
+
const statedSpeed = (copy) => {
|
|
213
|
+
const speed = copy.production?.speed;
|
|
214
|
+
return speed && isAsserted(certaintyOf(speed)) ? inMetersPerMinute(speed) : undefined;
|
|
215
|
+
};
|
|
216
|
+
/** The speed the reference copy was cut at: as stated for it, or as its system runs. */
|
|
217
|
+
const referenceSpeed = (reference) => {
|
|
218
|
+
const system = barOf(reference).paperSpeed;
|
|
219
|
+
return statedSpeed(reference) ?? (system && inMetersPerMinute(system));
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Where the alignments of the edition's copies, or what they say about
|
|
223
|
+
* the paper, want looking into. None of it is recomputed, so it is cheap
|
|
224
|
+
* to ask; whether an alignment would come out otherwise if found again
|
|
225
|
+
* is `alignmentFor`'s to say.
|
|
226
|
+
*/
|
|
227
|
+
export const alignmentProblems = (edition) => {
|
|
228
|
+
const reference = referenceCopyOf(edition);
|
|
229
|
+
if (!reference)
|
|
230
|
+
return [];
|
|
231
|
+
const unaligned = edition.copies
|
|
232
|
+
.filter(copy => copy.id !== reference.id && !copy.measurements.alignment && featuresOf(copy).length > 0)
|
|
233
|
+
.map(copy => ({ copy: copy.id, problem: 'not-aligned' }));
|
|
234
|
+
const elsewhere = edition.copies
|
|
235
|
+
.filter(copy => {
|
|
236
|
+
const against = copy.measurements.alignment?.against;
|
|
237
|
+
return against !== undefined && against !== reference.id;
|
|
238
|
+
})
|
|
239
|
+
.map(copy => ({ copy: copy.id, problem: 'aligned-against-another-copy' }));
|
|
240
|
+
const reading = paperOf(edition);
|
|
241
|
+
if (!reading)
|
|
242
|
+
return [...unaligned, ...elsewhere];
|
|
243
|
+
const beyond = reading.copies
|
|
244
|
+
.filter(paper => Math.abs(paper.along.value) > 3 * PAPER_SPREAD)
|
|
245
|
+
.map(paper => ({ copy: paper.copy, problem: 'paper-beyond-its-spread', by: paper.along.value }));
|
|
246
|
+
const speedOfReference = referenceSpeed(reference);
|
|
247
|
+
const speeds = speedOfReference === undefined ? [] : reading.copies.flatMap(paper => {
|
|
248
|
+
const copy = edition.copies.find(c => c.id === paper.copy);
|
|
249
|
+
const speed = copy.id === reference.id ? undefined : statedSpeed(copy);
|
|
250
|
+
const system = reading.systems.find(s => s.system === paper.system);
|
|
251
|
+
if (speed === undefined)
|
|
252
|
+
return [];
|
|
253
|
+
const by = Math.log((speed / speedOfReference) / system.ratio);
|
|
254
|
+
const allowed = SPEED_TOLERANCE + 3 * system.ratioError / system.ratio;
|
|
255
|
+
return Math.abs(by) > allowed
|
|
256
|
+
? [{ copy: copy.id, problem: 'speed-disagrees-with-paper', by }]
|
|
257
|
+
: [];
|
|
258
|
+
});
|
|
259
|
+
return [...unaligned, ...elsewhere, ...beyond, ...speeds];
|
|
260
|
+
};
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import { FeatureOrPatch } from "../model/Feature.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Alignment, RollCopy } from "../model/RollCopy.js";
|
|
3
3
|
import { TrackerBar } from "../systems/TrackerBar.js";
|
|
4
4
|
import { Millimeters } from "../model/Quantity.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
/** A place on the copy's own paper, on the edition's axis. */
|
|
6
|
+
export declare const toAxis: (alignment: Alignment | undefined) => (place: Millimeters) => Millimeters;
|
|
7
|
+
/** A place on the edition's axis, on the copy's own paper. */
|
|
8
|
+
export declare const fromAxis: (alignment: Alignment | undefined) => (place: Millimeters) => Millimeters;
|
|
9
|
+
/**
|
|
10
|
+
* Carries the copy's features and tears from its own places onto the
|
|
11
|
+
* axis, and records how, in place of any alignment it had: that one is
|
|
12
|
+
* taken off first.
|
|
13
|
+
*/
|
|
14
|
+
export declare const applyAlignment: (alignment: Alignment, copy: RollCopy) => void;
|
|
15
|
+
/** Takes the copy's features and tears back to its own places, as far as it was aligned. */
|
|
16
|
+
export declare const revertAlignment: (copy: RollCopy) => void;
|
|
17
|
+
/**
|
|
18
|
+
* The copy with its features and tears at its own places, as a document
|
|
19
|
+
* holds them, and its alignment kept to say how they go onto the axis.
|
|
20
|
+
* The copy given is left as it is. A copy that is not aligned is the
|
|
21
|
+
* very same copy.
|
|
22
|
+
*/
|
|
23
|
+
export declare const atOwnPlaces: (copy: RollCopy) => RollCopy;
|
|
24
|
+
/**
|
|
25
|
+
* Puts the features and tears of a copy read from a document, which
|
|
26
|
+
* holds them at the copy's own places, onto the axis. The copy given is
|
|
27
|
+
* changed.
|
|
28
|
+
*/
|
|
29
|
+
export declare const putOnAxis: (copy: RollCopy) => void;
|
|
30
|
+
/** The copy's features at its own places, as it was read, whatever alignment it has. */
|
|
31
|
+
export declare const ownFeaturesOf: (copy: RollCopy) => FeatureOrPatch[];
|
|
12
32
|
/**
|
|
13
33
|
* Takes the extension a pneumatic reader adds off the ends of the
|
|
14
34
|
* copy's chains of holes, and records how much was taken.
|
|
@@ -59,6 +79,16 @@ export declare const tooShortToShorten: (extension: Millimeters, copy: RollCopy,
|
|
|
59
79
|
export declare const shortenChains: (extension: Millimeters, copy: RollCopy, leaving?: ReadonlySet<string>) => void;
|
|
60
80
|
/** Puts the reader's extension back on the chains it was taken off, as far as one was taken off. */
|
|
61
81
|
export declare const revertShortening: (copy: RollCopy) => void;
|
|
82
|
+
/**
|
|
83
|
+
* The method `alignFeatures` follows, as an alignment records it. Its
|
|
84
|
+
* revision changes when the method does and at no other release, so
|
|
85
|
+
* that an alignment found by an earlier method can be told from one
|
|
86
|
+
* that would be found again as it stands.
|
|
87
|
+
*/
|
|
88
|
+
export declare const ALIGNMENT_METHOD: {
|
|
89
|
+
readonly software: 'linked-rolls alignFeatures';
|
|
90
|
+
readonly version: '1';
|
|
91
|
+
};
|
|
62
92
|
/**
|
|
63
93
|
* How a copy's places are carried onto another copy's:
|
|
64
94
|
* `x_other = (x + shift) · scale`.
|
|
@@ -71,6 +101,14 @@ export interface AlignmentResult {
|
|
|
71
101
|
matched: number;
|
|
72
102
|
/** How far the counterparts still lie apart, as a root mean square in the other copy's millimetres. */
|
|
73
103
|
residual: Millimeters;
|
|
104
|
+
/**
|
|
105
|
+
* The standard error of the scale, as for a line of least squares
|
|
106
|
+
* through the matches: the residual over the spread of the matched
|
|
107
|
+
* notes along the copy, and over the root of their number. The
|
|
108
|
+
* robust line is fitted otherwise, but on Welte 225 this comes
|
|
109
|
+
* within a few per cent of what resampling the matches gives.
|
|
110
|
+
*/
|
|
111
|
+
scaleError: number;
|
|
74
112
|
}
|
|
75
113
|
/**
|
|
76
114
|
* Finds the shift and scale that carry the places of `rollA` onto those
|