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.
@@ -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 change to the terminals' drives alone (V, I,
17
- * R) is applied in place, cheaply. Otherwise the device is rebuilt, keeping the current state as
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. Options are those of `traces()` and `levelChart()`.
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
+ };
@@ -10,6 +10,7 @@ export function makeSolution(solver: any, result?: {}): {
10
10
  V: {};
11
11
  Vstd: {};
12
12
  time: any;
13
+ steady: any;
13
14
  converged: any;
14
15
  iterations: any;
15
16
  steps: any;
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
- solvedV: any;
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
- * @param {{ species?: string[], standard?: boolean, phi?: boolean | string[],
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
  */