driftlet 0.13.0 → 0.16.2
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 +1 -1
- package/docs/alignment.md +6 -3
- package/docs/data.md +3 -1
- package/docs/device.md +97 -33
- package/docs/kit.md +95 -8
- package/docs/logo-dark.svg +3 -2
- package/docs/logo.svg +3 -2
- package/docs/numerics.md +193 -114
- package/docs/reliability.md +16 -10
- package/docs/statistics.md +28 -4
- package/llms.txt +325 -335
- package/package.json +1 -1
- package/src/blockTridiagonal.js +12 -6
- package/src/checks.js +16 -5
- package/src/describe.js +46 -5
- package/src/device.js +88 -10
- package/src/index.js +19 -7
- package/src/kit.js +1 -0
- package/src/particles.js +407 -0
- package/src/solution.js +171 -10
- package/src/solver.js +797 -262
- package/src/sources.js +10 -2
- package/src/stack.js +16 -6
- package/src/statistics.js +7 -3
- package/types/checks.d.ts +2 -1
- package/types/kit.d.ts +1 -0
- package/types/particles.d.ts +39 -0
- package/types/solution.d.ts +1 -0
- package/types/solver.d.ts +47 -29
- package/types/stack.d.ts +6 -4
package/src/sources.js
CHANGED
|
@@ -127,13 +127,21 @@ export function photogeneration({ material, flux, alpha, mu, from = 0, to, makes
|
|
|
127
127
|
// nothing is left), then `to`.
|
|
128
128
|
const depth = alpha * (to - from), reach = Math.min(depth, 50), n = Math.ceil(reach / 0.05), step = reach / n;
|
|
129
129
|
const taus = Array.from({ length: n + 1 }, (_, k) => k * step);
|
|
130
|
-
if (depth > 50) taus.push(depth);
|
|
130
|
+
if (depth > 50 * (1 + 1e-9)) taus.push(depth); // (not a round-off's worth past the last point)
|
|
131
131
|
// A straight line between two points of e^{−τ} encloses (Δτ/2)·coth(Δτ/2) times too much: each
|
|
132
132
|
// value is scaled back by that, so the total is the exponential's own (to ~Δτ⁴).
|
|
133
133
|
const fix = (step / 2) / Math.tanh(step / 2);
|
|
134
|
+
// Nothing outside [from, to]: a profile holds its end values beyond its table, so it drops to
|
|
135
|
+
// zero a hair beyond each end (the light enters a layer of a material that's also elsewhere).
|
|
136
|
+
const hair = 1e-10 * (to - from);
|
|
134
137
|
return {
|
|
135
138
|
equation: `photon = ${makes}`,
|
|
136
139
|
fixed: { photon: mu },
|
|
137
|
-
kf: {
|
|
140
|
+
kf: {
|
|
141
|
+
[material]: {
|
|
142
|
+
x: [from - hair, ...taus.map((t) => from + t / alpha), to + hair],
|
|
143
|
+
values: [0, ...taus.map((t) => (flux * alpha * Math.exp(-t)) / fix), 0],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
138
146
|
};
|
|
139
147
|
}
|
package/src/stack.js
CHANGED
|
@@ -89,17 +89,27 @@ export function ohmic(d = 0, carriers = ['e-', 'h+']) {
|
|
|
89
89
|
/**
|
|
90
90
|
* A bath: a neutral composition `c` (mol/m³) held at the contact. With a `reference`, the charged
|
|
91
91
|
* species a reference electrode would sense (Cl⁻ for Ag/AgCl), its levels are referred to that
|
|
92
|
-
* species, which is the terminal. Without one, `bath(c, drive)`, the terminal voltage is the
|
|
92
|
+
* species, which is the terminal. Without one, `bath(c, drive, opts)`, the terminal voltage is the
|
|
93
93
|
* bath's φ: an ideal salt bridge, the convention of membrane potentials.
|
|
94
94
|
* @param {Record<string, number>} c
|
|
95
95
|
* @param {string | number | object} [reference] the reference species, or the drive
|
|
96
96
|
* @param {number | object} [drive]
|
|
97
|
-
* @param {{ offset?: number }} [opts] where the reference species
|
|
98
|
-
* voltage, V_ref = V + offset (V)
|
|
97
|
+
* @param {{ offset?: number, material?: string }} [opts] `offset`: where the reference species
|
|
98
|
+
* sits relative to the terminal voltage, V_ref = V + offset (V). `material`: the bath's own
|
|
99
|
+
* material (a solution against an ion exchanger), if not the end layer's
|
|
99
100
|
*/
|
|
100
|
-
export function bath(c, reference, d = 0,
|
|
101
|
-
if (typeof reference !== 'string')
|
|
102
|
-
|
|
101
|
+
export function bath(c, reference, d = 0, opts = {}) {
|
|
102
|
+
if (typeof reference !== 'string') [d, opts] = [reference ?? 0, isObject(d) ? d : {}];
|
|
103
|
+
const { offset, material } = opts;
|
|
104
|
+
return {
|
|
105
|
+
...drive(d, 'bath'),
|
|
106
|
+
bath: {
|
|
107
|
+
c: { ...c },
|
|
108
|
+
...(typeof reference === 'string' ? { reference } : {}),
|
|
109
|
+
...(offset === undefined ? {} : { offset }),
|
|
110
|
+
...(material === undefined ? {} : { material }),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
const STACK_FIELDS = ['T', 'library', 'species', 'materials', 'stack', 'bulkReactions', 'ports', 'grid', 'geometry'];
|
package/src/statistics.js
CHANGED
|
@@ -23,16 +23,20 @@ const MODEL_TYPES = ['ideal', 'fermi-dirac', 'lattice', 'redlich-kister', 'debye
|
|
|
23
23
|
|
|
24
24
|
const logistic = (y) => (y >= 0 ? 1 / (1 + Math.exp(-y)) : Math.exp(y) / (1 + Math.exp(y)));
|
|
25
25
|
|
|
26
|
-
// Safeguarded Newton for an increasing function: f(y) = target on [lo, hi].
|
|
26
|
+
// Safeguarded Newton for an increasing function: f(y) = target on [lo, hi]. A Newton step that
|
|
27
|
+
// leaves the bracket, or that wouldn't halve the last step (Newton zigzagging across an
|
|
28
|
+
// inflection, its bracket shrinking by a little each time: a steep tabulated OCV's ran out of
|
|
29
|
+
// iterations 0.2 thermal units from its root), bisects instead.
|
|
27
30
|
function solveIncreasing(f, target, lo, hi, guess) {
|
|
28
|
-
let y = Math.min(hi, Math.max(lo, guess));
|
|
31
|
+
let y = Math.min(hi, Math.max(lo, guess)), last = Infinity;
|
|
29
32
|
for (let it = 0; it < 200; it++) {
|
|
30
33
|
const { v, d } = f(y);
|
|
31
34
|
const r = v - target;
|
|
32
35
|
if (r > 0) hi = y;
|
|
33
36
|
else lo = y;
|
|
34
37
|
let next = y - r / d;
|
|
35
|
-
if (!(next > lo && next < hi)) next = 0.5 * (lo + hi);
|
|
38
|
+
if (!(next > lo && next < hi) || Math.abs(next - y) > 0.5 * last) next = 0.5 * (lo + hi);
|
|
39
|
+
last = Math.abs(next - y);
|
|
36
40
|
if (Math.abs(next - y) <= 1e-15 * Math.max(1, Math.abs(y)) || hi - lo <= 1e-15 * Math.max(1, Math.abs(y))) return next;
|
|
37
41
|
y = next;
|
|
38
42
|
}
|
package/types/checks.d.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* zero; under light it's J = F(G − R), and it says where every carrier went.
|
|
19
19
|
* - **conservation** (a transient): every closed, unreacting stretch keeps what it held plus
|
|
20
20
|
* what came in.
|
|
21
|
-
* - **grid** (
|
|
21
|
+
* - **grid** (with `refine`, the default; in a transient, `ok: null` and a reminder, since the
|
|
22
|
+
* answer depends on the whole run): in a steady state, the device solved again on a grid
|
|
22
23
|
* twice as fine everywhere, from this solution interpolated onto it. The current, each floating terminal's voltage and each
|
|
23
24
|
* region's charge must agree to `tol` (relative, default 1e-2, a plot's accuracy; a benchmark
|
|
24
25
|
* wants less). The discretisation is second order, so the change estimates this grid's error
|
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 { particles } from "./particles.js";
|
|
39
40
|
export { polarization } from "./polarization.js";
|
|
40
41
|
/**
|
|
41
42
|
* One material seen from vacuum: a level inside it (the anchor) and how far above that level,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A swarm of dots sampling a solution's concentrations and fluxes. Amounts are per area (a planar
|
|
3
|
+
* device's). `step(dt)` moves them on in the device's time, at the rates of the solution last
|
|
4
|
+
* given; `update(sol)` gives a new one.
|
|
5
|
+
* @param {object} sol a solution (from solve(), advance(), live(), …)
|
|
6
|
+
* @param {object} [opts]
|
|
7
|
+
* @param {string[]} [opts.species] which species (default: every species with a concentration)
|
|
8
|
+
* @param {number} [opts.dots] about how many dots per species (default 200), for the default weight
|
|
9
|
+
* @param {number|object} [opts.weight] amount per dot, mol/m²: one number for every species (so
|
|
10
|
+
* their dots compare), or { name: weight }; default, each species' total over `dots`
|
|
11
|
+
* @param {number} [opts.cap] the most dots a cell of the asked-for width holds on average before
|
|
12
|
+
* it's a sea (a density: a wider cell holds more; default ∞)
|
|
13
|
+
* @param {number} [opts.cells] about how many cells across the device (default 120)
|
|
14
|
+
* @param {() => number} [opts.random] a uniform random source in [0, 1) (default Math.random)
|
|
15
|
+
*/
|
|
16
|
+
export function particles(sol: object, { species, dots, weight, cap, cells, random }?: {
|
|
17
|
+
species?: string[];
|
|
18
|
+
dots?: number;
|
|
19
|
+
weight?: number | object;
|
|
20
|
+
cap?: number;
|
|
21
|
+
cells?: number;
|
|
22
|
+
random?: () => number;
|
|
23
|
+
}): {
|
|
24
|
+
cells: any[];
|
|
25
|
+
species: any;
|
|
26
|
+
weight: {};
|
|
27
|
+
sea: {};
|
|
28
|
+
dots: any[];
|
|
29
|
+
events: any[];
|
|
30
|
+
crossed: {};
|
|
31
|
+
time: number;
|
|
32
|
+
update: (next: any, { resample }?: {
|
|
33
|
+
resample?: boolean;
|
|
34
|
+
}) => /*elided*/ any;
|
|
35
|
+
step: (dt: any, { maxHops }?: {
|
|
36
|
+
maxHops?: number;
|
|
37
|
+
}) => /*elided*/ any;
|
|
38
|
+
expected: (nm: any) => Float64Array<ArrayBuffer>;
|
|
39
|
+
};
|
package/types/solution.d.ts
CHANGED
package/types/solver.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export class Solver {
|
|
|
84
84
|
left: number;
|
|
85
85
|
right: number;
|
|
86
86
|
};
|
|
87
|
+
lawForm: Map<any, any>;
|
|
87
88
|
terms: any;
|
|
88
89
|
termV: Float64Array<any>;
|
|
89
90
|
termVPrev: Float64Array<any>;
|
|
@@ -92,6 +93,9 @@ export class Solver {
|
|
|
92
93
|
sourceOverride: Map<any, any>;
|
|
93
94
|
steady: boolean;
|
|
94
95
|
floating: any;
|
|
96
|
+
drivenKey: any;
|
|
97
|
+
lawRefs: Map<any, any>;
|
|
98
|
+
chargeRefs: Map<any, any>;
|
|
95
99
|
termB: any;
|
|
96
100
|
termC: any;
|
|
97
101
|
termDI: Float64Array<any>;
|
|
@@ -134,24 +138,36 @@ export class Solver {
|
|
|
134
138
|
stretchOf: Int32Array<ArrayBuffer>;
|
|
135
139
|
moieties: Float64Array<any>[];
|
|
136
140
|
flatStretches: any[];
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
_moves(st: any): boolean;
|
|
142
|
+
_still(st: any): boolean;
|
|
143
|
+
_feeds(k: any, st: any): any;
|
|
144
|
+
_laws(): void;
|
|
139
145
|
constraints: any[];
|
|
140
146
|
localConstraints: any[];
|
|
141
147
|
islands: any[];
|
|
142
148
|
pinnedIslands: any[];
|
|
143
149
|
islandsOn: boolean;
|
|
144
150
|
pins: any[];
|
|
145
|
-
|
|
151
|
+
timeLaws: any[];
|
|
152
|
+
carriers: any;
|
|
153
|
+
_captureLaws(): void;
|
|
146
154
|
_captureLocal(): void;
|
|
147
155
|
_applyIslands(): any[];
|
|
148
156
|
islandRow: Float64Array<any>;
|
|
149
157
|
_rowInto(o: any, add: any): void;
|
|
150
158
|
linRow: Float64Array<any>;
|
|
151
159
|
_applyLocalConstraints(): void;
|
|
152
|
-
_conservedMoieties(): Float64Array<any>[];
|
|
160
|
+
_conservedMoieties(drives?: boolean, alsoCurrent?: any[]): Float64Array<any>[];
|
|
153
161
|
_directSteady(): boolean;
|
|
154
162
|
_applyConstraints(): void;
|
|
163
|
+
_lawAmount(cs: any): {
|
|
164
|
+
amount: number;
|
|
165
|
+
reference: number;
|
|
166
|
+
dV: number;
|
|
167
|
+
gross: number;
|
|
168
|
+
};
|
|
169
|
+
_lawStart(cs: any): number;
|
|
170
|
+
_circuitLaws(dt: any): void;
|
|
155
171
|
_solveBordered(rhs: any, delta: any, deltaV: any, pins: any): void;
|
|
156
172
|
dWork: Float64Array<any>;
|
|
157
173
|
borderCols: any[];
|
|
@@ -209,9 +225,11 @@ export class Solver {
|
|
|
209
225
|
mixWork: Float64Array<any>[];
|
|
210
226
|
_dc(g: any, k: any, d: any): void;
|
|
211
227
|
bulkRate(rx: any, g: any): number;
|
|
228
|
+
bulkForward(rx: any, g: any): any;
|
|
212
229
|
bulkOneWay(rx: any, g: any): number;
|
|
213
230
|
_srhRate(law: any, cn: any, cp: any, a: any): {
|
|
214
231
|
rate: number;
|
|
232
|
+
forward: number;
|
|
215
233
|
oneWay: number;
|
|
216
234
|
Cn: number;
|
|
217
235
|
Cp: number;
|
|
@@ -231,8 +249,13 @@ export class Solver {
|
|
|
231
249
|
_portCapacitance(port: any, k: any, dt: any): void;
|
|
232
250
|
_surfaceStorage(port: any, dt: any): void;
|
|
233
251
|
_contact(side: any, dt: any): void;
|
|
252
|
+
_flatLevel(st: any): {
|
|
253
|
+
k: number;
|
|
254
|
+
hi: number;
|
|
255
|
+
lo: number;
|
|
256
|
+
};
|
|
257
|
+
_snapFlat(): void;
|
|
234
258
|
_flatRows(): void;
|
|
235
|
-
_levelRows(): void;
|
|
236
259
|
_replaceRow(b: any, rs: any): void;
|
|
237
260
|
_equilibrate(): void;
|
|
238
261
|
_assembleDifference(dt: any): {
|
|
@@ -263,6 +286,7 @@ export class Solver {
|
|
|
263
286
|
digits: any;
|
|
264
287
|
x: number;
|
|
265
288
|
where: string;
|
|
289
|
+
nodes: any[];
|
|
266
290
|
};
|
|
267
291
|
/**
|
|
268
292
|
* Advance by dt, splitting the interval into halves (recursively) wherever Newton fails.
|
|
@@ -286,6 +310,8 @@ export class Solver {
|
|
|
286
310
|
};
|
|
287
311
|
contactDEnd: any;
|
|
288
312
|
sourceBefore: boolean;
|
|
313
|
+
solvedV: any;
|
|
314
|
+
stepped: boolean;
|
|
289
315
|
/**
|
|
290
316
|
* Adaptive time stepping to tEnd: variable-step BDF2 (or backward Euler), with the local error
|
|
291
317
|
* estimated against an explicit predictor through the previous states, and controlled to
|
|
@@ -320,7 +346,6 @@ export class Solver {
|
|
|
320
346
|
};
|
|
321
347
|
_errorNorm(ref: any): number;
|
|
322
348
|
_warmFrom(src: any): void;
|
|
323
|
-
solvedV: any;
|
|
324
349
|
_snapshot(): {
|
|
325
350
|
u: Float64Array<ArrayBuffer>;
|
|
326
351
|
uLo: Float64Array<ArrayBuffer>;
|
|
@@ -387,6 +412,8 @@ export class Solver {
|
|
|
387
412
|
g: any[];
|
|
388
413
|
d: any[];
|
|
389
414
|
}[];
|
|
415
|
+
segmentFluxes(): Float64Array<ArrayBuffer>[];
|
|
416
|
+
faceOneWay(f: any): any;
|
|
390
417
|
_smallSignalProfiles(xr: any, xi: any): {
|
|
391
418
|
phi: {
|
|
392
419
|
re: Float64Array<any>;
|
|
@@ -400,13 +427,12 @@ export class Solver {
|
|
|
400
427
|
slowestTime(): number;
|
|
401
428
|
fastestTime(): number;
|
|
402
429
|
/**
|
|
403
|
-
* Steady state
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* If Newton fails, dt ramps up from a small value (pseudo-transient continuation) instead.
|
|
430
|
+
* Steady state: where a transient from the present state ends, found as the solution of the
|
|
431
|
+
* steady equations (dt = ∞, no storage term), with no slow modes to wait out, however slow the
|
|
432
|
+
* physics (e.g. exponentially scarce minority carriers). What the storage term held is held by
|
|
433
|
+
* the steady system's own rows instead (see _laws): each conserved amount in place of a balance
|
|
434
|
+
* row that says nothing new, levels that carry nothing held flat, islands' summed balances.
|
|
435
|
+
* Where Newton from here fails: continuation along the drives, then the pseudo-transient ramp.
|
|
410
436
|
* The clock is not advanced, and open-system conservation bookkeeping restarts here.
|
|
411
437
|
*/
|
|
412
438
|
solveSteady(opts?: {}): any;
|
|
@@ -416,9 +442,17 @@ export class Solver {
|
|
|
416
442
|
low: any;
|
|
417
443
|
high: any;
|
|
418
444
|
};
|
|
445
|
+
charging: {
|
|
446
|
+
terminal: any;
|
|
447
|
+
current: any;
|
|
448
|
+
what: any[];
|
|
449
|
+
};
|
|
450
|
+
steadyAt: number;
|
|
419
451
|
_solveSteadyAll(opts: any): any;
|
|
420
452
|
_steadyFromHere(opts: any): any;
|
|
421
453
|
dimmer: boolean;
|
|
454
|
+
_bound(k: any): boolean;
|
|
455
|
+
_chargeOnly(k: any): boolean;
|
|
422
456
|
_floatingContinuation(opts: any, r: any): any;
|
|
423
457
|
_generationContinuation(opts: any, r: any): {
|
|
424
458
|
converged: boolean;
|
|
@@ -427,22 +461,6 @@ export class Solver {
|
|
|
427
461
|
history: any;
|
|
428
462
|
};
|
|
429
463
|
_continuation(opts: any, k: any, level: any, target: any, r: any, warm: any, hold: any): any;
|
|
430
|
-
_solveSteady(opts?: {}): {
|
|
431
|
-
converged: boolean;
|
|
432
|
-
steps: number;
|
|
433
|
-
iterations: number;
|
|
434
|
-
history: ({
|
|
435
|
-
dt: number;
|
|
436
|
-
converged: boolean;
|
|
437
|
-
iterations: number;
|
|
438
|
-
maxStep?: undefined;
|
|
439
|
-
} | {
|
|
440
|
-
dt: number;
|
|
441
|
-
converged: boolean;
|
|
442
|
-
iterations: number;
|
|
443
|
-
maxStep: number;
|
|
444
|
-
})[];
|
|
445
|
-
};
|
|
446
464
|
_steadySteps({ maxSteps, tol }?: {
|
|
447
465
|
maxSteps?: number;
|
|
448
466
|
tol?: number;
|
package/types/stack.d.ts
CHANGED
|
@@ -33,16 +33,18 @@ export function ohmic(d?: number, carriers?: string[]): any;
|
|
|
33
33
|
/**
|
|
34
34
|
* A bath: a neutral composition `c` (mol/m³) held at the contact. With a `reference`, the charged
|
|
35
35
|
* species a reference electrode would sense (Cl⁻ for Ag/AgCl), its levels are referred to that
|
|
36
|
-
* species, which is the terminal. Without one, `bath(c, drive)`, the terminal voltage is the
|
|
36
|
+
* species, which is the terminal. Without one, `bath(c, drive, opts)`, the terminal voltage is the
|
|
37
37
|
* bath's φ: an ideal salt bridge, the convention of membrane potentials.
|
|
38
38
|
* @param {Record<string, number>} c
|
|
39
39
|
* @param {string | number | object} [reference] the reference species, or the drive
|
|
40
40
|
* @param {number | object} [drive]
|
|
41
|
-
* @param {{ offset?: number }} [opts] where the reference species
|
|
42
|
-
* voltage, V_ref = V + offset (V)
|
|
41
|
+
* @param {{ offset?: number, material?: string }} [opts] `offset`: where the reference species
|
|
42
|
+
* sits relative to the terminal voltage, V_ref = V + offset (V). `material`: the bath's own
|
|
43
|
+
* material (a solution against an ion exchanger), if not the end layer's
|
|
43
44
|
*/
|
|
44
|
-
export function bath(c: Record<string, number>, reference?: string | number | object, d?: number,
|
|
45
|
+
export function bath(c: Record<string, number>, reference?: string | number | object, d?: number, opts?: {
|
|
45
46
|
offset?: number;
|
|
47
|
+
material?: string;
|
|
46
48
|
}): any;
|
|
47
49
|
/**
|
|
48
50
|
* Build a plain device definition from a stack:
|