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.
@@ -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: string, quantity?: 'c' | 'V', region?: string | number }[] }} [opts]
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: string;
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
+ };
@@ -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>;
@@ -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
- solvedV: any;
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
  */