driftlet 0.7.0 → 0.9.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 +11 -5
- package/docs/device.md +163 -3
- package/docs/kit.md +27 -3
- package/docs/numerics.md +16 -0
- package/llms.txt +14 -9
- package/package.json +1 -1
- package/src/checks.js +10 -9
- package/src/describe.js +14 -1
- package/src/device.js +153 -13
- package/src/grid.js +73 -0
- package/src/index.js +1 -0
- package/src/kit.js +1 -0
- package/src/polarization.js +96 -0
- package/src/solution.js +30 -4
- package/src/solver.js +325 -48
- package/src/stack.js +3 -2
- package/src/types.js +22 -4
- package/types/grid.d.ts +29 -0
- package/types/kit.d.ts +1 -0
- package/types/polarization.d.ts +31 -0
- package/types/solver.d.ts +26 -3
- package/types/stack.d.ts +1 -1
- package/types/types.d.ts +65 -4
package/src/stack.js
CHANGED
|
@@ -100,7 +100,7 @@ export function bath(c, reference, d = 0, { offset } = {}) {
|
|
|
100
100
|
return { ...drive(d, 'bath'), bath: { c: { ...c }, reference, ...(offset === undefined ? {} : { offset }) } };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const STACK_FIELDS = ['T', 'library', 'species', 'materials', 'stack', 'bulkReactions', 'ports', 'grid'];
|
|
103
|
+
const STACK_FIELDS = ['T', 'library', 'species', 'materials', 'stack', 'bulkReactions', 'ports', 'grid', 'geometry'];
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Build a plain device definition from a stack:
|
|
@@ -112,7 +112,7 @@ const STACK_FIELDS = ['T', 'library', 'species', 'materials', 'stack', 'bulkReac
|
|
|
112
112
|
* between them get the default face. Nested lists are flattened.
|
|
113
113
|
* - Layers may give doping as `donors` and `acceptors` (mol/m³) in place of `fixedCharge`.
|
|
114
114
|
* - `library`: pieces with species and materials, merged with `species` and `materials`.
|
|
115
|
-
* - `T`, `grid`, `ports` pass through.
|
|
115
|
+
* - `T`, `grid`, `ports`, `geometry` pass through.
|
|
116
116
|
* @param {object} def
|
|
117
117
|
* @returns {object} a device definition for `new Device`
|
|
118
118
|
*/
|
|
@@ -168,5 +168,6 @@ export function build(def) {
|
|
|
168
168
|
if (def.bulkReactions !== undefined) out.bulkReactions = def.bulkReactions;
|
|
169
169
|
if (def.ports !== undefined) out.ports = def.ports;
|
|
170
170
|
if (def.grid !== undefined) out.grid = def.grid;
|
|
171
|
+
if (def.geometry !== undefined) out.geometry = def.geometry;
|
|
171
172
|
return out;
|
|
172
173
|
}
|
package/src/types.js
CHANGED
|
@@ -154,9 +154,20 @@
|
|
|
154
154
|
* @property {Source} [V] held terminal voltage, V (default 0, if no I)
|
|
155
155
|
* @property {Source} [I] or: the current driven into the device, A/m² (e.g. 0 for a reference electrode)
|
|
156
156
|
* @property {number} [R] with V: a series resistance, Ω·m²
|
|
157
|
-
* @property {string} [terminal] species whose offset defaults to 0
|
|
157
|
+
* @property {string} [terminal] species whose offset defaults to 0; with reactions, the electrode's carrier
|
|
158
158
|
* @property {Record<string, 'blocked' | 'equilibrium' | { type: 'equilibrium', offset?: number, mu?: number }
|
|
159
|
-
* | { type: 'conductance', G: number, offset?: number } | { type: 'exchange', k: number, mu: number }>} species
|
|
159
|
+
* | { type: 'conductance', G: number, offset?: number } | { type: 'exchange', k: number, mu: number }>} [species]
|
|
160
|
+
* @property {{ equation: string, fixed?: Record<string, number>, k0: number, alpha: number, bare?: boolean }[]} [reactions] an
|
|
161
|
+
* electrode spread through the window: Butler–Volmer per area (k0 in mol/(m²·s)) between the region's
|
|
162
|
+
* species and the terminal species at the port's level
|
|
163
|
+
* @property {Record<string, { mu0: number, capacity: number, theta0?: number }>} [surface] with reactions: species
|
|
164
|
+
* on the electrode's sites, Langmuir (μ = μ° + RT ln(θ/θ₀)), capacity in mol of sites per m² of electrode,
|
|
165
|
+
* shared; a reaction with `bare: true` runs only on the bare fraction θ₀ = 1 − Σθ
|
|
166
|
+
* @property {{ C: number, zeroCharge?: number }} [capacitance] a capacitance spread through the window (F/m² of
|
|
167
|
+
* electrode): σ = C (V − zeroCharge − φ) on the port's side, aσ in each node's charge balance; a gate
|
|
168
|
+
* along a channel, a double layer
|
|
169
|
+
* @property {number | { x: number[], values: number[] }} [area] with reactions or a capacitance: electrode area per volume,
|
|
170
|
+
* m²/m³ (1/h for a film of thickness h), or a profile against the device's x
|
|
160
171
|
*/
|
|
161
172
|
|
|
162
173
|
/**
|
|
@@ -170,6 +181,10 @@
|
|
|
170
181
|
* @property {BulkReactionDef[]} [bulkReactions]
|
|
171
182
|
* @property {{ left?: ContactDef, right?: ContactDef }} [contacts]
|
|
172
183
|
* @property {PortDef[]} [ports] internal ports
|
|
184
|
+
* @property {'planar' | 'spherical' | 'cylindrical' | { type: 'planar' | 'spherical' | 'cylindrical', r0?: number }
|
|
185
|
+
* | { area: { x: number[], values: number[] } }} [geometry] the cross-section A(x): planar (A = 1 m², the
|
|
186
|
+
* default: currents read per m²), shells about a centre at r = r0 (m) left of x = 0 (cylindrical: per
|
|
187
|
+
* metre of length), or A in m² against x. Currents, fluxes, amounts and charges are then totals through A.
|
|
173
188
|
* @property {GridOptions} [grid]
|
|
174
189
|
*/
|
|
175
190
|
|
|
@@ -199,8 +214,11 @@
|
|
|
199
214
|
* then the ports by name): its voltage, and its current into the device (A/m²)
|
|
200
215
|
* @property {{ left: ContactResult, right: ContactResult }} contacts
|
|
201
216
|
* @property {{ left?: { V: number, D: number, charge: number }, right?: { V: number, D: number, charge: number } }} gates
|
|
202
|
-
* @property {{ name: string, V: number, flux: Record<string, number>, current: number
|
|
203
|
-
*
|
|
217
|
+
* @property {{ name: string, V: number, flux: Record<string, number>, current: number, x?: Float64Array,
|
|
218
|
+
* area?: Float64Array, rates?: Float64Array[], coverage?: Record<string, Float64Array>, bare?: Float64Array,
|
|
219
|
+
* sigma?: Float64Array, charge?: number }[]} ports what each internal port brings into the device;
|
|
220
|
+
* with reactions, also its window's nodes (x, m), area per volume there, and each reaction's rate per area
|
|
221
|
+
* at each node (mol/(m²·s), forward)
|
|
204
222
|
* @property {{ left: string, right: string, dipole: number, sheetCharge: number, D: number, N: Record<string, number>, rates: number[] }[]} interfaces
|
|
205
223
|
* @property {{ rate: Float64Array, regions: number[], total: number }[]} bulkReactions each bulk reaction's
|
|
206
224
|
* forward rate per node (mol/(m³·s), NaN where it doesn't run), and per region and in all (mol/(m²·s))
|
package/types/grid.d.ts
CHANGED
|
@@ -26,3 +26,32 @@ export function buildGrid(regions: {
|
|
|
26
26
|
regionEnd: Int32Array<ArrayBuffer>;
|
|
27
27
|
length: number;
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* Give a grid its cross-section: `area` (A at each node, m²), `segArea` (each segment's
|
|
31
|
+
* effective area, its length over ∫dx/A, so that a flux D·segArea·Δc/h is exact for steady
|
|
32
|
+
* diffusion between the nodes; A at its middle where A vanishes at an end, as at a sphere's
|
|
33
|
+
* centre) and `vol` (each node's box, ∫A dx). Planar (no geometry): A = 1, and nothing changes.
|
|
34
|
+
* @param {ReturnType<typeof buildGrid>} grid
|
|
35
|
+
* @param {{ type: 'planar' } | { type: 'spherical' | 'cylindrical', r0: number } | { type: 'profile', x: ArrayLike<number>, values: ArrayLike<number> }} geometry
|
|
36
|
+
*/
|
|
37
|
+
export function applyGeometry(grid: ReturnType<typeof buildGrid>, geometry: {
|
|
38
|
+
type: "planar";
|
|
39
|
+
} | {
|
|
40
|
+
type: "spherical" | "cylindrical";
|
|
41
|
+
r0: number;
|
|
42
|
+
} | {
|
|
43
|
+
type: "profile";
|
|
44
|
+
x: ArrayLike<number>;
|
|
45
|
+
values: ArrayLike<number>;
|
|
46
|
+
}): {
|
|
47
|
+
nNodes: number;
|
|
48
|
+
x: Float64Array<ArrayBuffer>;
|
|
49
|
+
nodeRegion: Int32Array<ArrayBuffer>;
|
|
50
|
+
segLength: Float64Array<ArrayBuffer>;
|
|
51
|
+
segRegion: Int32Array<ArrayBuffer>;
|
|
52
|
+
segFace: Int32Array<ArrayBuffer>;
|
|
53
|
+
vol: Float64Array<ArrayBuffer>;
|
|
54
|
+
regionStart: Int32Array<ArrayBuffer>;
|
|
55
|
+
regionEnd: Int32Array<ArrayBuffer>;
|
|
56
|
+
length: number;
|
|
57
|
+
};
|
package/types/kit.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export function vacuumZeroCharge(def: object, workFunction: number, inside: Vacu
|
|
|
36
36
|
export { live } from "./live.js";
|
|
37
37
|
export { recorder } from "./recorder.js";
|
|
38
38
|
export { check } from "./checks.js";
|
|
39
|
+
export { polarization } from "./polarization.js";
|
|
39
40
|
/**
|
|
40
41
|
* One material seen from vacuum: a level inside it (the anchor) and how far above that level,
|
|
41
42
|
* in electron energy, the vacuum level just outside lies (the offset, V). So
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each electrode reaction's rate at a face (a metal's) or at one spot of an electrode port, with
|
|
3
|
+
* the composition beside it as in `sol` and the metal's carrier at each level in `V` (in the
|
|
4
|
+
* terminals' convention, V = μ̄/(zF); against SHE, subtract φ for the usual tables' ions). The
|
|
5
|
+
* rates are the solver's own law, so at the electrode's actual level they are
|
|
6
|
+
* `sol.interfaces[f].rates` (or `sol.ports[k].rates` at that node).
|
|
7
|
+
* @param {import('./index.js').Device} device
|
|
8
|
+
* @param {object} sol a solution of it
|
|
9
|
+
* @param {{ face: number } | { port: string | number, x?: number }} where a face's index, or a port
|
|
10
|
+
* (name or index) and the position in its window (m, the device's x; default its first node)
|
|
11
|
+
* @param {number | ArrayLike<number>} V the metal's level(s), V
|
|
12
|
+
* @returns {{ V: Float64Array, current: Float64Array, reactions: { equation: string, level: number,
|
|
13
|
+
* rate: Float64Array, current: Float64Array }[] }} per reaction: its equilibrium level here (where
|
|
14
|
+
* its rate is zero), its rate (mol/(m²·s) of electrode, forward) and its current (A/m², anodic
|
|
15
|
+
* positive: into the solution); and their sum
|
|
16
|
+
*/
|
|
17
|
+
export function polarization(device: import("./index.js").Device, sol: object, where: {
|
|
18
|
+
face: number;
|
|
19
|
+
} | {
|
|
20
|
+
port: string | number;
|
|
21
|
+
x?: number;
|
|
22
|
+
}, V: number | ArrayLike<number>): {
|
|
23
|
+
V: Float64Array;
|
|
24
|
+
current: Float64Array;
|
|
25
|
+
reactions: {
|
|
26
|
+
equation: string;
|
|
27
|
+
level: number;
|
|
28
|
+
rate: Float64Array;
|
|
29
|
+
current: Float64Array;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
package/types/solver.d.ts
CHANGED
|
@@ -8,12 +8,19 @@ export class Solver {
|
|
|
8
8
|
n: any;
|
|
9
9
|
M: any;
|
|
10
10
|
nRx: any;
|
|
11
|
+
nSurf: any;
|
|
11
12
|
nNodes: any;
|
|
12
13
|
nFaces: number;
|
|
13
14
|
nB: any;
|
|
14
15
|
VT: number;
|
|
15
16
|
blockOfNode: Int32Array<any>;
|
|
16
17
|
blockOfFace: Int32Array<ArrayBuffer>;
|
|
18
|
+
surfPort: Int32Array<any>;
|
|
19
|
+
surfW: Int32Array<any>;
|
|
20
|
+
th: Float64Array<ArrayBuffer>;
|
|
21
|
+
thOld: Float64Array<ArrayBuffer>;
|
|
22
|
+
th0: Float64Array<any>;
|
|
23
|
+
th0Old: Float64Array<any>;
|
|
17
24
|
present: Uint8Array<ArrayBuffer>;
|
|
18
25
|
cRef: Float64Array<ArrayBuffer>;
|
|
19
26
|
mu0hat: Float64Array<ArrayBuffer>;
|
|
@@ -54,6 +61,10 @@ export class Solver {
|
|
|
54
61
|
left: number;
|
|
55
62
|
right: number;
|
|
56
63
|
};
|
|
64
|
+
portQ: Float64Array<any>;
|
|
65
|
+
portQStart: Float64Array<any>;
|
|
66
|
+
portQOld: Float64Array<any>;
|
|
67
|
+
portQEnd: any;
|
|
57
68
|
contactDOld: {
|
|
58
69
|
left: number;
|
|
59
70
|
right: number;
|
|
@@ -77,6 +88,8 @@ export class Solver {
|
|
|
77
88
|
history: any[];
|
|
78
89
|
dtNext: any;
|
|
79
90
|
rxs: any;
|
|
91
|
+
portArea: any;
|
|
92
|
+
portRates: any;
|
|
80
93
|
strictlyNeutral: any;
|
|
81
94
|
generationScale: number;
|
|
82
95
|
hasGeneration: any;
|
|
@@ -119,7 +132,7 @@ export class Solver {
|
|
|
119
132
|
redrive(): void;
|
|
120
133
|
deltaV: Float64Array<any>;
|
|
121
134
|
_refreshSources(): void;
|
|
122
|
-
/** η_i (μ̄/RT) of a port's outside level for species i. */
|
|
135
|
+
/** η_i (μ̄/RT) of a port's outside level for species i (its electrode's carrier: at V itself). */
|
|
123
136
|
portEta(port: any, i: any): number;
|
|
124
137
|
/** η_i/RT that a fixed contact link imposes (or NaN if the link isn't fixed). */
|
|
125
138
|
contactEta(side: any, i: any): number;
|
|
@@ -129,7 +142,9 @@ export class Solver {
|
|
|
129
142
|
* profile varies), or continued across the interface dipole if nothing there responds.
|
|
130
143
|
*/
|
|
131
144
|
initFromComposition(): void;
|
|
145
|
+
_portRate(port: any, rx: any, g: any): number;
|
|
132
146
|
_materialAt(mat: any, background: any, eta: any, mode: any, cFix: any, ph: any, zeta: any, cc: any): void;
|
|
147
|
+
_coverages(): void;
|
|
133
148
|
computeConcentrations(): void;
|
|
134
149
|
_work(md: any): any;
|
|
135
150
|
_nodeStatistics(g: any, b: any, phiHat: any): void;
|
|
@@ -165,11 +180,14 @@ export class Solver {
|
|
|
165
180
|
Cp: number;
|
|
166
181
|
Ca: number;
|
|
167
182
|
};
|
|
168
|
-
_faceReaction(rx: any, k: any, f: any, bf: any, bL: any, bR: any): void;
|
|
183
|
+
_faceReaction(rx: any, k: any, f: any, bf: any, bL: any, bR: any, Af: any): void;
|
|
169
184
|
_terminals(dt: any): void;
|
|
170
185
|
_circuit(k: any): void;
|
|
171
186
|
_captureRow(b: any, s: any, w: any, into: any): number;
|
|
172
|
-
_port(port: any, flux: any, k: any): void;
|
|
187
|
+
_port(port: any, flux: any, k: any, dt: any): void;
|
|
188
|
+
_portReaction(port: any, rx: any, area: any, rates: any, flux: any, k: any): void;
|
|
189
|
+
_portCapacitance(port: any, k: any, dt: any): void;
|
|
190
|
+
_surfaceStorage(port: any, dt: any): void;
|
|
173
191
|
_contact(side: any, dt: any): void;
|
|
174
192
|
_replaceRow(b: any, rs: any): void;
|
|
175
193
|
_equilibrate(): void;
|
|
@@ -253,6 +271,8 @@ export class Solver {
|
|
|
253
271
|
u: Float64Array<ArrayBuffer>;
|
|
254
272
|
uLo: Float64Array<ArrayBuffer>;
|
|
255
273
|
cOld: Float64Array<ArrayBuffer>;
|
|
274
|
+
thOld: Float64Array<ArrayBuffer>;
|
|
275
|
+
th0Old: Float64Array<ArrayBuffer>;
|
|
256
276
|
time: number;
|
|
257
277
|
lastDt: number;
|
|
258
278
|
atSteady: boolean;
|
|
@@ -265,6 +285,9 @@ export class Solver {
|
|
|
265
285
|
right: number;
|
|
266
286
|
};
|
|
267
287
|
contactDEnd: any;
|
|
288
|
+
portQStart: Float64Array<any>;
|
|
289
|
+
portQOld: Float64Array<any>;
|
|
290
|
+
portQEnd: any;
|
|
268
291
|
boundaryIntake: Float64Array<ArrayBuffer>;
|
|
269
292
|
history: any[];
|
|
270
293
|
termV: Float64Array<ArrayBuffer>;
|
package/types/stack.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export function bath(c: Record<string, number>, reference: string, d?: number, {
|
|
|
53
53
|
* between them get the default face. Nested lists are flattened.
|
|
54
54
|
* - Layers may give doping as `donors` and `acceptors` (mol/m³) in place of `fixedCharge`.
|
|
55
55
|
* - `library`: pieces with species and materials, merged with `species` and `materials`.
|
|
56
|
-
* - `T`, `grid`, `ports` pass through.
|
|
56
|
+
* - `T`, `grid`, `ports`, `geometry` pass through.
|
|
57
57
|
* @param {object} def
|
|
58
58
|
* @returns {object} a device definition for `new Device`
|
|
59
59
|
*/
|
package/types/types.d.ts
CHANGED
|
@@ -354,10 +354,10 @@ export type PortDef = {
|
|
|
354
354
|
*/
|
|
355
355
|
R?: number;
|
|
356
356
|
/**
|
|
357
|
-
* species whose offset defaults to 0
|
|
357
|
+
* species whose offset defaults to 0; with reactions, the electrode's carrier
|
|
358
358
|
*/
|
|
359
359
|
terminal?: string;
|
|
360
|
-
species
|
|
360
|
+
species?: Record<string, "blocked" | "equilibrium" | {
|
|
361
361
|
type: "equilibrium";
|
|
362
362
|
offset?: number;
|
|
363
363
|
mu?: number;
|
|
@@ -370,6 +370,45 @@ export type PortDef = {
|
|
|
370
370
|
k: number;
|
|
371
371
|
mu: number;
|
|
372
372
|
}>;
|
|
373
|
+
/**
|
|
374
|
+
* an
|
|
375
|
+
* electrode spread through the window: Butler–Volmer per area (k0 in mol/(m²·s)) between the region's
|
|
376
|
+
* species and the terminal species at the port's level
|
|
377
|
+
*/
|
|
378
|
+
reactions?: {
|
|
379
|
+
equation: string;
|
|
380
|
+
fixed?: Record<string, number>;
|
|
381
|
+
k0: number;
|
|
382
|
+
alpha: number;
|
|
383
|
+
bare?: boolean;
|
|
384
|
+
}[];
|
|
385
|
+
/**
|
|
386
|
+
* with reactions: species
|
|
387
|
+
* on the electrode's sites, Langmuir (μ = μ° + RT ln(θ/θ₀)), capacity in mol of sites per m² of electrode,
|
|
388
|
+
* shared; a reaction with `bare: true` runs only on the bare fraction θ₀ = 1 − Σθ
|
|
389
|
+
*/
|
|
390
|
+
surface?: Record<string, {
|
|
391
|
+
mu0: number;
|
|
392
|
+
capacity: number;
|
|
393
|
+
theta0?: number;
|
|
394
|
+
}>;
|
|
395
|
+
/**
|
|
396
|
+
* a capacitance spread through the window (F/m² of
|
|
397
|
+
* electrode): σ = C (V − zeroCharge − φ) on the port's side, aσ in each node's charge balance; a gate
|
|
398
|
+
* along a channel, a double layer
|
|
399
|
+
*/
|
|
400
|
+
capacitance?: {
|
|
401
|
+
C: number;
|
|
402
|
+
zeroCharge?: number;
|
|
403
|
+
};
|
|
404
|
+
/**
|
|
405
|
+
* with reactions or a capacitance: electrode area per volume,
|
|
406
|
+
* m²/m³ (1/h for a film of thickness h), or a profile against the device's x
|
|
407
|
+
*/
|
|
408
|
+
area?: number | {
|
|
409
|
+
x: number[];
|
|
410
|
+
values: number[];
|
|
411
|
+
};
|
|
373
412
|
};
|
|
374
413
|
/**
|
|
375
414
|
* A device definition: plain, serialisable data.
|
|
@@ -398,6 +437,20 @@ export type DeviceDefinition = {
|
|
|
398
437
|
* internal ports
|
|
399
438
|
*/
|
|
400
439
|
ports?: PortDef[];
|
|
440
|
+
/**
|
|
441
|
+
* the cross-section A(x): planar (A = 1 m², the
|
|
442
|
+
* default: currents read per m²), shells about a centre at r = r0 (m) left of x = 0 (cylindrical: per
|
|
443
|
+
* metre of length), or A in m² against x. Currents, fluxes, amounts and charges are then totals through A.
|
|
444
|
+
*/
|
|
445
|
+
geometry?: "planar" | "spherical" | "cylindrical" | {
|
|
446
|
+
type: "planar" | "spherical" | "cylindrical";
|
|
447
|
+
r0?: number;
|
|
448
|
+
} | {
|
|
449
|
+
area: {
|
|
450
|
+
x: number[];
|
|
451
|
+
values: number[];
|
|
452
|
+
};
|
|
453
|
+
};
|
|
401
454
|
grid?: GridOptions;
|
|
402
455
|
};
|
|
403
456
|
export type ContactResult = {
|
|
@@ -500,14 +553,22 @@ export type Solution = {
|
|
|
500
553
|
};
|
|
501
554
|
};
|
|
502
555
|
/**
|
|
503
|
-
* what each
|
|
504
|
-
*
|
|
556
|
+
* what each internal port brings into the device;
|
|
557
|
+
* with reactions, also its window's nodes (x, m), area per volume there, and each reaction's rate per area
|
|
558
|
+
* at each node (mol/(m²·s), forward)
|
|
505
559
|
*/
|
|
506
560
|
ports: {
|
|
507
561
|
name: string;
|
|
508
562
|
V: number;
|
|
509
563
|
flux: Record<string, number>;
|
|
510
564
|
current: number;
|
|
565
|
+
x?: Float64Array;
|
|
566
|
+
area?: Float64Array;
|
|
567
|
+
rates?: Float64Array[];
|
|
568
|
+
coverage?: Record<string, Float64Array>;
|
|
569
|
+
bare?: Float64Array;
|
|
570
|
+
sigma?: Float64Array;
|
|
571
|
+
charge?: number;
|
|
511
572
|
}[];
|
|
512
573
|
interfaces: {
|
|
513
574
|
left: string;
|