driftlet 0.4.0 → 0.6.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 +8 -4
- package/docs/device.md +33 -3
- package/docs/kit.md +123 -0
- package/docs/numerics.md +6 -2
- package/docs/visualization.md +9 -0
- package/llms.txt +46 -11
- package/package.json +1 -1
- package/src/checks.js +268 -0
- package/src/describe.js +11 -6
- package/src/device.js +50 -3
- package/src/devices.js +135 -0
- package/src/index.js +11 -2
- package/src/kit.js +3 -0
- package/src/plot.js +3 -2
- package/src/recorder.js +89 -0
- package/src/solution.js +19 -1
- package/src/solver.js +209 -12
- package/src/stack.js +1 -1
- package/src/traces.js +23 -10
- package/src/types.js +4 -1
- package/types/checks.d.ts +59 -0
- package/types/devices.d.ts +77 -0
- package/types/index.d.ts +3 -2
- package/types/kit.d.ts +3 -0
- package/types/plot.d.ts +2 -1
- package/types/recorder.d.ts +27 -0
- package/types/solution.d.ts +1 -0
- package/types/solver.d.ts +18 -1
- package/types/stack.d.ts +1 -0
- package/types/traces.d.ts +7 -2
- package/types/types.d.ts +15 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IonMonger's default planar perovskite solar cell: TiO₂ (100 nm) | MAPbI₃ (400 nm) with mobile
|
|
3
|
+
* iodide vacancies over an equal immobile background | spiro-OMeTAD (200 nm), at 298 K, lit
|
|
4
|
+
* through the TiO₂. Species e⁻, h⁺ and the vacancies 'V+'. Recombination is SRH at both faces
|
|
5
|
+
* (IonMonger's interface law is driftlet's face SRH, with n₁ from the TiO₂'s electron density)
|
|
6
|
+
* and, with `bulkSRH`, SRH through midgap traps in the perovskite (τₙ = 3 ns, τₚ = 300 ns).
|
|
7
|
+
* Light is 1.4e21 photons/(m²·s) at α = 1.3e7 /m (IonMonger's one sun), times `light`. The left
|
|
8
|
+
* contact is the TiO₂'s, at 0 V; the right is the spiro's, at `V`, so photocurrent is positive.
|
|
9
|
+
*
|
|
10
|
+
* `PEROVSKITE_SCANS` holds IonMonger's own results for this cell, scan by scan.
|
|
11
|
+
* @param {{ V?: import('./types.js').Source | { V?: import('./types.js').Source, I?: import('./types.js').Source, R?: number }, light?: number, bulkSRH?: boolean }} [opts]
|
|
12
|
+
* @returns {import('./types.js').DeviceDefinition}
|
|
13
|
+
*/
|
|
14
|
+
export function perovskiteCell({ V, light, bulkSRH }?: {
|
|
15
|
+
V?: import("./types.js").Source | {
|
|
16
|
+
V?: import("./types.js").Source;
|
|
17
|
+
I?: import("./types.js").Source;
|
|
18
|
+
R?: number;
|
|
19
|
+
};
|
|
20
|
+
light?: number;
|
|
21
|
+
bulkSRH?: boolean;
|
|
22
|
+
}): import("./types.js").DeviceDefinition;
|
|
23
|
+
/**
|
|
24
|
+
* A J–V loop's figures of merit, from a transient's trace (`{ t, voltage, current }`, as a
|
|
25
|
+
* recorder or advance() gives it): the loop splits at time `turn` into the sweep before (`rev`)
|
|
26
|
+
* and after (`fwd`). Each sweep's maximum power V·I (W/m², the largest among the trace's points)
|
|
27
|
+
* and its open-circuit voltage (where I changes sign, interpolated linearly); and the
|
|
28
|
+
* hysteresis index (Pmax,rev − Pmax,fwd)/Pmax,rev. Power out is positive, so a cell whose
|
|
29
|
+
* current runs the other way should have it negated first.
|
|
30
|
+
* @param {{ t: ArrayLike<number>, voltage: ArrayLike<number>, current: ArrayLike<number> }} trace
|
|
31
|
+
* @param {number} turn s
|
|
32
|
+
*/
|
|
33
|
+
export function hysteresis(trace: {
|
|
34
|
+
t: ArrayLike<number>;
|
|
35
|
+
voltage: ArrayLike<number>;
|
|
36
|
+
current: ArrayLike<number>;
|
|
37
|
+
}, turn: number): {
|
|
38
|
+
hi: number;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* IonMonger's J–V scans of `perovskiteCell()` (IonMonger 2.0, commit 1ae4f9e, run in GNU Octave):
|
|
42
|
+
* steady state at 1.2 V under light, then 1.2 → 0 → 1.2 V at `rate` (V/s). `full` is the cell
|
|
43
|
+
* as given (bulk SRH on); `noBulkSRH` is `perovskiteCell({ bulkSRH: false })`. Per scan: J0, the
|
|
44
|
+
* current at 0 V (A/m²); each sweep's maximum power Pmax (W/m², the largest V·J among IonMonger's
|
|
45
|
+
* 201 output points per loop) and Voc (V, interpolated); and the hysteresis index
|
|
46
|
+
* hi = (Pmax,rev − Pmax,fwd)/Pmax,rev. `hysteresis()` reads the same from a run's trace.
|
|
47
|
+
* driftlet reproduces these to 0.15 % in hi, 0.5 W/m² in Pmax and 2 mV in Voc (its test suite).
|
|
48
|
+
*/
|
|
49
|
+
export const PEROVSKITE_SCANS: Readonly<{
|
|
50
|
+
source: "IonMonger 2.0 (github.com/PerovskiteSCModelling/IonMonger, commit 1ae4f9e), default parameters";
|
|
51
|
+
full: {
|
|
52
|
+
rate: number;
|
|
53
|
+
J0: number;
|
|
54
|
+
rev: {
|
|
55
|
+
Pmax: number;
|
|
56
|
+
Voc: number;
|
|
57
|
+
};
|
|
58
|
+
fwd: {
|
|
59
|
+
Pmax: number;
|
|
60
|
+
Voc: number;
|
|
61
|
+
};
|
|
62
|
+
hi: number;
|
|
63
|
+
}[];
|
|
64
|
+
noBulkSRH: {
|
|
65
|
+
rate: number;
|
|
66
|
+
J0: number;
|
|
67
|
+
rev: {
|
|
68
|
+
Pmax: number;
|
|
69
|
+
Voc: number;
|
|
70
|
+
};
|
|
71
|
+
fwd: {
|
|
72
|
+
Pmax: number;
|
|
73
|
+
Voc: number;
|
|
74
|
+
};
|
|
75
|
+
hi: number;
|
|
76
|
+
}[];
|
|
77
|
+
}>;
|
package/types/index.d.ts
CHANGED
|
@@ -13,8 +13,9 @@ export class Device {
|
|
|
13
13
|
get solver(): any;
|
|
14
14
|
get grid(): any;
|
|
15
15
|
/**
|
|
16
|
-
* Change part of the definition (deep-merged). A
|
|
17
|
-
*
|
|
16
|
+
* Change part of the definition (deep-merged). A contact given V or I drops the other, so
|
|
17
|
+
* `{ contacts: { right: { I: 0 } } }` switches it to open circuit. A change to the terminals'
|
|
18
|
+
* drives alone (V, I, R) is applied in place, cheaply. Otherwise the device is rebuilt, keeping the current state as
|
|
18
19
|
* the warm start when the grid and species are unchanged (else restarting from the regions' c0).
|
|
19
20
|
* @param {object} patch a partial device definition, merged into the current one
|
|
20
21
|
* @returns {this}
|
package/types/kit.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export function vacuumDipole(def: object, left: VacuumSide, right: VacuumSide):
|
|
|
34
34
|
*/
|
|
35
35
|
export function vacuumZeroCharge(def: object, workFunction: number, inside: VacuumSide): number;
|
|
36
36
|
export { live } from "./live.js";
|
|
37
|
+
export { recorder } from "./recorder.js";
|
|
38
|
+
export { check } from "./checks.js";
|
|
37
39
|
/**
|
|
38
40
|
* One material seen from vacuum: a level inside it (the anchor) and how far above that level,
|
|
39
41
|
* in electron energy, the vacuum level just outside lies (the offset, V). So
|
|
@@ -59,4 +61,5 @@ export { parseEquation, stoichiometry, half, SHE, level } from "./equation.js";
|
|
|
59
61
|
export { traces, speciesRole, typeset } from "./traces.js";
|
|
60
62
|
export { pulse, square, triangle, ramp, injector, recombination, photogeneration } from "./sources.js";
|
|
61
63
|
export { describe, unitWarnings } from "./describe.js";
|
|
64
|
+
export { perovskiteCell, PEROVSKITE_SCANS, hysteresis } from "./devices.js";
|
|
62
65
|
export { IONS, H2O, WATER_EPSR, SEMICONDUCTORS, METALS, aqueous, semiconductor, metal } from "./data.js";
|
package/types/plot.d.ts
CHANGED
|
@@ -37,7 +37,8 @@ export function levelChart(tr: ReturnType<typeof traces>, { width, height, ylabe
|
|
|
37
37
|
}): string;
|
|
38
38
|
/**
|
|
39
39
|
* A solution's level diagram as an SVG string: each charged species' voltage (thick) and
|
|
40
|
-
* standard level (thin), regions as bands
|
|
40
|
+
* standard level (thin), regions as bands; or with `energy: true`, the familiar energy-up band
|
|
41
|
+
* diagram of electrons and holes. Options are those of `traces()` and `levelChart()`.
|
|
41
42
|
* @param {import('./types.js').Solution} sol
|
|
42
43
|
* @param {Parameters<typeof traces>[1] & Parameters<typeof levelChart>[1]} [opts]
|
|
43
44
|
* @returns {string}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record a device's transient. Frames are whole solutions: at the start, then at each of
|
|
3
|
+
* `times` (or every `every` seconds from the start), which `advance()` lands on exactly; with
|
|
4
|
+
* neither, at the end of each `advance()` call. The trace (`t`, `current`, `voltage`, and
|
|
5
|
+
* `probes` if given) runs from the start through every accepted step of every call.
|
|
6
|
+
* @param {import('./index.js').Device} device
|
|
7
|
+
* @param {{ every?: number, times?: number[], probes?: { x: number, species: string, quantity?: 'c' | 'V', region?: string | number }[] }} [opts]
|
|
8
|
+
*/
|
|
9
|
+
export function recorder(device: import("./index.js").Device, opts?: {
|
|
10
|
+
every?: number;
|
|
11
|
+
times?: number[];
|
|
12
|
+
probes?: {
|
|
13
|
+
x: number;
|
|
14
|
+
species: string;
|
|
15
|
+
quantity?: "c" | "V";
|
|
16
|
+
region?: string | number;
|
|
17
|
+
}[];
|
|
18
|
+
}): {
|
|
19
|
+
advance: (tEnd: number, aopts?: object) => import("./types.js").Solution;
|
|
20
|
+
frame: (t: number) => import("./types.js").Solution;
|
|
21
|
+
frames: import("./types.js").Solution[];
|
|
22
|
+
trace: {
|
|
23
|
+
t: number[];
|
|
24
|
+
current: number[];
|
|
25
|
+
voltage: number[];
|
|
26
|
+
};
|
|
27
|
+
};
|
package/types/solution.d.ts
CHANGED
package/types/solver.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export class Solver {
|
|
|
44
44
|
jR: Float64Array<any>;
|
|
45
45
|
time: number;
|
|
46
46
|
lastDt: number;
|
|
47
|
+
atSteady: boolean;
|
|
47
48
|
contactFlux: {
|
|
48
49
|
left: Float64Array<any>;
|
|
49
50
|
right: Float64Array<any>;
|
|
@@ -103,7 +104,10 @@ export class Solver {
|
|
|
103
104
|
stretchOf: Int32Array<ArrayBuffer>;
|
|
104
105
|
moieties: Float64Array<ArrayBuffer>[];
|
|
105
106
|
constraints: any[];
|
|
107
|
+
localConstraints: any[];
|
|
106
108
|
constrained: boolean;
|
|
109
|
+
_captureLocal(): void;
|
|
110
|
+
_applyLocalConstraints(): void;
|
|
107
111
|
_conservedMoieties(): Float64Array<ArrayBuffer>[];
|
|
108
112
|
_directSteady(): boolean;
|
|
109
113
|
_applyConstraints(): void;
|
|
@@ -152,6 +156,15 @@ export class Solver {
|
|
|
152
156
|
_segmentMixing(s: any, bL: any, bR: any, Dm: any, h: any, mat: any): void;
|
|
153
157
|
mixWork: Float64Array<any>[];
|
|
154
158
|
_dc(g: any, k: any, d: any): void;
|
|
159
|
+
bulkRate(rx: any, g: any): number;
|
|
160
|
+
bulkOneWay(rx: any, g: any): number;
|
|
161
|
+
_srhRate(law: any, cn: any, cp: any, a: any): {
|
|
162
|
+
rate: number;
|
|
163
|
+
oneWay: number;
|
|
164
|
+
Cn: number;
|
|
165
|
+
Cp: number;
|
|
166
|
+
Ca: number;
|
|
167
|
+
};
|
|
155
168
|
_faceReaction(rx: any, k: any, f: any, bf: any, bL: any, bR: any): void;
|
|
156
169
|
_terminals(dt: any): void;
|
|
157
170
|
_circuit(k: any): void;
|
|
@@ -234,12 +247,15 @@ export class Solver {
|
|
|
234
247
|
push: (values: any) => any;
|
|
235
248
|
};
|
|
236
249
|
_errorNorm(ref: any): number;
|
|
250
|
+
_warmFrom(src: any): void;
|
|
251
|
+
solvedV: any;
|
|
237
252
|
_snapshot(): {
|
|
238
253
|
u: Float64Array<ArrayBuffer>;
|
|
239
254
|
uLo: Float64Array<ArrayBuffer>;
|
|
240
255
|
cOld: Float64Array<ArrayBuffer>;
|
|
241
256
|
time: number;
|
|
242
257
|
lastDt: number;
|
|
258
|
+
atSteady: boolean;
|
|
243
259
|
contactDStart: {
|
|
244
260
|
left: number;
|
|
245
261
|
right: number;
|
|
@@ -288,6 +304,7 @@ export class Solver {
|
|
|
288
304
|
_assembleBookkeeping(dt: any): void;
|
|
289
305
|
_accumulateBoundaryIntake(dt: any, cN: any): void;
|
|
290
306
|
slowestTime(): number;
|
|
307
|
+
fastestTime(): number;
|
|
291
308
|
/**
|
|
292
309
|
* Steady state.
|
|
293
310
|
* - If every species stretch is fed by a contact, nothing is conserved on its own, so the
|
|
@@ -299,7 +316,7 @@ export class Solver {
|
|
|
299
316
|
* The clock is not advanced, and open-system conservation bookkeeping restarts here.
|
|
300
317
|
*/
|
|
301
318
|
solveSteady(opts?: {}): any;
|
|
302
|
-
|
|
319
|
+
_steadyFromHere(opts: any): any;
|
|
303
320
|
_generationContinuation(opts: any, r: any): {
|
|
304
321
|
converged: boolean;
|
|
305
322
|
steps: any;
|
package/types/stack.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export function combine(...parts: {
|
|
|
19
19
|
* @param {object} [more]
|
|
20
20
|
*/
|
|
21
21
|
export function layer(material: string, length: number, more?: object): any;
|
|
22
|
+
export function drive(d: any, path: any): any;
|
|
22
23
|
/**
|
|
23
24
|
* An ohmic contact: each carrier in equilibrium with a metal at the terminal voltage, the
|
|
24
25
|
* first being the terminal species, and every other sharing its voltage (offset 0, as holes and
|
package/types/traces.d.ts
CHANGED
|
@@ -40,7 +40,11 @@ export function typeset(name: string, z: number): string;
|
|
|
40
40
|
* its standard level V°_i (`standard`, the band edge for e⁻ and h⁺), optionally φ and
|
|
41
41
|
* half-reactions' levels. Doubled interface nodes share an x, so steps draw as vertical lines.
|
|
42
42
|
* @param {import('./types.js').Solution} sol
|
|
43
|
-
*
|
|
43
|
+
* With `energy: true` it's the familiar band diagram instead, energy up: electrons and holes as
|
|
44
|
+
* electron energies in eV (E = −V: E_c and E_v from the standard levels, E_Fn and E_Fp, or E_F
|
|
45
|
+
* where only electrons are present, from the levels), with any redox levels and φ (as −qφ) turned
|
|
46
|
+
* over too. Only electrons and holes have bands; ions are refused (draw them as species voltages).
|
|
47
|
+
* @param {{ species?: string[], standard?: boolean, phi?: boolean | string[], energy?: boolean,
|
|
44
48
|
* levels?: { half: { equation: string, fixed?: Record<string, number> }, label?: string, standard?: boolean }[],
|
|
45
49
|
* labels?: Record<string, string>, shifts?: Record<string, number> }} [opts]
|
|
46
50
|
* species to show (default: every charged one), whether to show standard levels (default true)
|
|
@@ -51,10 +55,11 @@ export function typeset(name: string, z: number): string;
|
|
|
51
55
|
* @returns {{ x: Float64Array, series: Trace[], regions: { name: string, material: string, x0: number, x1: number }[],
|
|
52
56
|
* faces: number[], range: [number, number] }}
|
|
53
57
|
*/
|
|
54
|
-
export function traces(sol: import("./types.js").Solution, { species, standard, phi, levels, labels, shifts }?: {
|
|
58
|
+
export function traces(sol: import("./types.js").Solution, { species, standard, phi, energy, levels, labels, shifts }?: {
|
|
55
59
|
species?: string[];
|
|
56
60
|
standard?: boolean;
|
|
57
61
|
phi?: boolean | string[];
|
|
62
|
+
energy?: boolean;
|
|
58
63
|
levels?: {
|
|
59
64
|
half: {
|
|
60
65
|
equation: string;
|
package/types/types.d.ts
CHANGED
|
@@ -510,12 +510,23 @@ export type Solution = {
|
|
|
510
510
|
current: number;
|
|
511
511
|
}[];
|
|
512
512
|
interfaces: {
|
|
513
|
+
left: string;
|
|
514
|
+
right: string;
|
|
513
515
|
dipole: number;
|
|
514
516
|
sheetCharge: number;
|
|
515
517
|
D: number;
|
|
516
518
|
N: Record<string, number>;
|
|
517
519
|
rates: number[];
|
|
518
520
|
}[];
|
|
521
|
+
/**
|
|
522
|
+
* each bulk reaction's
|
|
523
|
+
* forward rate per node (mol/(m³·s), NaN where it doesn't run), and per region and in all (mol/(m²·s))
|
|
524
|
+
*/
|
|
525
|
+
bulkReactions: {
|
|
526
|
+
rate: Float64Array;
|
|
527
|
+
regions: number[];
|
|
528
|
+
total: number;
|
|
529
|
+
}[];
|
|
519
530
|
/**
|
|
520
531
|
* total charge in the device, C/m²
|
|
521
532
|
*/
|
|
@@ -538,6 +549,10 @@ export type Solution = {
|
|
|
538
549
|
* s
|
|
539
550
|
*/
|
|
540
551
|
time: number;
|
|
552
|
+
/**
|
|
553
|
+
* whether this is a converged steady state (solve()), not a transient's
|
|
554
|
+
*/
|
|
555
|
+
steady: boolean;
|
|
541
556
|
/**
|
|
542
557
|
* advance(): whether tEnd was reached (false if the frame budget ran out)
|
|
543
558
|
*/
|