driftlet 0.5.0 → 0.7.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 -7
- package/docs/device.md +61 -7
- package/docs/kit.md +147 -0
- package/llms.txt +100 -15
- package/package.json +1 -1
- package/src/checks.js +302 -0
- package/src/describe.js +18 -7
- package/src/device.js +31 -4
- package/src/devices.js +135 -0
- package/src/index.js +1 -1
- package/src/junction.js +164 -0
- package/src/kit.js +4 -0
- package/src/recorder.js +89 -0
- package/src/solution.js +18 -0
- package/src/solver.js +135 -7
- package/src/stack.js +1 -1
- 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 -3
- package/types/junction.d.ts +32 -0
- package/types/kit.d.ts +4 -0
- package/types/recorder.d.ts +27 -0
- package/types/solution.d.ts +1 -0
- package/types/solver.d.ts +8 -1
- package/types/stack.d.ts +1 -0
- 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
|
@@ -51,7 +51,7 @@ export class Device {
|
|
|
51
51
|
* (one array per probe, alongside `trace.t`): what a detector at x sees.
|
|
52
52
|
* @param {number} tEnd s
|
|
53
53
|
* @param {{ tol?: number, dt0?: number, dtMax?: number, budgetMs?: number, maxSteps?: number, method?: 'bdf2' | 'be',
|
|
54
|
-
* probes?: { x: number, species
|
|
54
|
+
* probes?: { x: number, species?: string, quantity?: 'c' | 'V' | 'phi', region?: string | number }[] }} [opts]
|
|
55
55
|
* @returns {Solution}
|
|
56
56
|
*/
|
|
57
57
|
advance(tEnd: number, opts?: {
|
|
@@ -63,8 +63,8 @@ export class Device {
|
|
|
63
63
|
method?: "bdf2" | "be";
|
|
64
64
|
probes?: {
|
|
65
65
|
x: number;
|
|
66
|
-
species
|
|
67
|
-
quantity?: "c" | "V";
|
|
66
|
+
species?: string;
|
|
67
|
+
quantity?: "c" | "V" | "phi";
|
|
68
68
|
region?: string | number;
|
|
69
69
|
}[];
|
|
70
70
|
}): Solution;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Henderson's liquid-junction potential, φ_R − φ_L (V): the junction taken to be the two solutions
|
|
3
|
+
* mixed in one proportion at every point (every ion's mixing fraction the same curve). Exact for a single binary salt, a close estimate otherwise (it's what
|
|
4
|
+
* JPCalc and most electrophysiology corrections use).
|
|
5
|
+
* @param {Record<string, number>} left mol/m³ by ion
|
|
6
|
+
* @param {Record<string, number>} right mol/m³ by ion
|
|
7
|
+
* @param {Record<string, { z: number, D: number }>} ions charge and diffusivity (m²/s), e.g. IONS
|
|
8
|
+
* @param {{ T?: number }} [opts] K (default 298.15)
|
|
9
|
+
*/
|
|
10
|
+
export function henderson(left: Record<string, number>, right: Record<string, number>, ions: Record<string, {
|
|
11
|
+
z: number;
|
|
12
|
+
D: number;
|
|
13
|
+
}>, { T }?: {
|
|
14
|
+
T?: number;
|
|
15
|
+
}): number;
|
|
16
|
+
/**
|
|
17
|
+
* Planck's liquid-junction potential, φ_R − φ_L (V): the steady state of constrained diffusion,
|
|
18
|
+
* a junction zone held between the two solutions, electroneutral, carrying no current. Solved by
|
|
19
|
+
* shooting on the Nernst–Planck equations across it (its length drops out). Exact for that
|
|
20
|
+
* junction; equal to Henderson's for a single binary salt.
|
|
21
|
+
* @param {Record<string, number>} left mol/m³ by ion
|
|
22
|
+
* @param {Record<string, number>} right mol/m³ by ion
|
|
23
|
+
* @param {Record<string, { z: number, D: number }>} ions charge and diffusivity (m²/s), e.g. IONS
|
|
24
|
+
* @param {{ T?: number, steps?: number }} [opts] K (default 298.15); RK4 steps across (default 4000)
|
|
25
|
+
*/
|
|
26
|
+
export function planck(left: Record<string, number>, right: Record<string, number>, ions: Record<string, {
|
|
27
|
+
z: number;
|
|
28
|
+
D: number;
|
|
29
|
+
}>, { T, steps }?: {
|
|
30
|
+
T?: number;
|
|
31
|
+
steps?: number;
|
|
32
|
+
}): number;
|
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,6 @@ 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 { henderson, planck } from "./junction.js";
|
|
65
|
+
export { perovskiteCell, PEROVSKITE_SCANS, hysteresis } from "./devices.js";
|
|
62
66
|
export { IONS, H2O, WATER_EPSR, SEMICONDUCTORS, METALS, aqueous, semiconductor, metal } from "./data.js";
|
|
@@ -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' | 'phi', 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" | "phi";
|
|
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>;
|
|
@@ -155,8 +156,11 @@ export class Solver {
|
|
|
155
156
|
_segmentMixing(s: any, bL: any, bR: any, Dm: any, h: any, mat: any): void;
|
|
156
157
|
mixWork: Float64Array<any>[];
|
|
157
158
|
_dc(g: any, k: any, d: any): void;
|
|
159
|
+
bulkRate(rx: any, g: any): number;
|
|
160
|
+
bulkOneWay(rx: any, g: any): number;
|
|
158
161
|
_srhRate(law: any, cn: any, cp: any, a: any): {
|
|
159
162
|
rate: number;
|
|
163
|
+
oneWay: number;
|
|
160
164
|
Cn: number;
|
|
161
165
|
Cp: number;
|
|
162
166
|
Ca: number;
|
|
@@ -243,12 +247,15 @@ export class Solver {
|
|
|
243
247
|
push: (values: any) => any;
|
|
244
248
|
};
|
|
245
249
|
_errorNorm(ref: any): number;
|
|
250
|
+
_warmFrom(src: any): void;
|
|
251
|
+
solvedV: any;
|
|
246
252
|
_snapshot(): {
|
|
247
253
|
u: Float64Array<ArrayBuffer>;
|
|
248
254
|
uLo: Float64Array<ArrayBuffer>;
|
|
249
255
|
cOld: Float64Array<ArrayBuffer>;
|
|
250
256
|
time: number;
|
|
251
257
|
lastDt: number;
|
|
258
|
+
atSteady: boolean;
|
|
252
259
|
contactDStart: {
|
|
253
260
|
left: number;
|
|
254
261
|
right: number;
|
|
@@ -309,7 +316,7 @@ export class Solver {
|
|
|
309
316
|
* The clock is not advanced, and open-system conservation bookkeeping restarts here.
|
|
310
317
|
*/
|
|
311
318
|
solveSteady(opts?: {}): any;
|
|
312
|
-
|
|
319
|
+
_steadyFromHere(opts: any): any;
|
|
313
320
|
_generationContinuation(opts: any, r: any): {
|
|
314
321
|
converged: boolean;
|
|
315
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/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
|
*/
|