driftlet 0.9.0 → 0.11.4
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 +47 -37
- package/docs/device.md +142 -96
- package/docs/kit.md +5 -0
- package/docs/numerics.md +197 -26
- package/docs/reliability.md +122 -0
- package/llms.txt +111 -7
- package/package.json +3 -1
- package/src/blockTridiagonal.js +17 -4
- package/src/checks.js +44 -33
- package/src/describe.js +15 -9
- package/src/device.js +16 -5
- package/src/difference.js +142 -0
- package/src/index.js +39 -10
- package/src/polarization.js +51 -16
- package/src/solution.js +29 -11
- package/src/solver.js +1115 -165
- package/src/types.js +4 -0
- package/types/blockTridiagonal.d.ts +9 -1
- package/types/checks.d.ts +3 -3
- package/types/difference.d.ts +30 -0
- package/types/index.d.ts +3 -1
- package/types/live.d.ts +1 -0
- package/types/polarization.d.ts +8 -2
- package/types/solver.d.ts +65 -12
- package/types/types.d.ts +6 -0
package/README.md
CHANGED
|
@@ -13,35 +13,24 @@ to be the engine of live, thermodynamically honest web demos of charge transport
|
|
|
13
13
|
semiconductors, electrochemical cells, membranes and solid ionic conductors. It handles any mix
|
|
14
14
|
of charged and neutral species, Poisson electrostatics (or strict neutrality), bulk and
|
|
15
15
|
interfacial reactions, heterointerfaces, metals and external circuits, in steady state, in time
|
|
16
|
-
and as small-signal impedance.
|
|
17
|
-
|
|
16
|
+
and as small-signal impedance. It's small enough to run in a web page: no dependencies,
|
|
17
|
+
millisecond solves.
|
|
18
18
|
|
|
19
19
|
[Live demos](https://markblundeberg.github.io/driftlet/demos/) · [npm](https://www.npmjs.com/package/driftlet) ·
|
|
20
20
|
[reading level diagrams](docs/visualization.md) · [device reference](docs/device.md) ·
|
|
21
21
|
[agent guide](llms.txt)
|
|
22
22
|
|
|
23
|
+
**Built to be relied on.** A drift–diffusion solver that handles a textbook pn junction is a
|
|
24
|
+
weekend's work; one that keeps working on the devices people actually build is not. driftlet is
|
|
25
|
+
tested two ways: every physics feature against an analytic result or an independent code, and
|
|
26
|
+
thousands of randomly generated devices (junctions, MOS capacitors, solar cells, electrolyte
|
|
27
|
+
cells, electrodes) put through cold solves, sweeps, transients and impedance, every result
|
|
28
|
+
checked. A solve that fails reports it, with what went wrong where it can tell.
|
|
29
|
+
[How far to trust it](docs/reliability.md) has the details, the numbers and the known limits.
|
|
30
|
+
|
|
23
31
|
**Status: 0.x.** Released on npm and used by the demos, but the API may still change between
|
|
24
32
|
minor versions.
|
|
25
33
|
|
|
26
|
-
## Is it for your problem?
|
|
27
|
-
|
|
28
|
-
The same equations go by different names in different fields. If your problem is one-dimensional
|
|
29
|
-
(planar, or radial: spheres and cylinders, or any cross-section varying along x), it's probably here:
|
|
30
|
-
|
|
31
|
-
| If you work on | you may call it | start from | checked against |
|
|
32
|
-
|---|---|---|---|
|
|
33
|
-
| Semiconductor devices | drift–diffusion, van Roosbroeck, quasi-Fermi levels, Scharfetter–Gummel; pn, Schottky, MOS, heterojunctions; thin-film transistors (gradual channel) | [first example](#a-semiconductor-junction), [pn](https://markblundeberg.github.io/driftlet/demos/pn.html) and [MOS](https://markblundeberg.github.io/driftlet/demos/mos.html) demos, [`contacts`](test/contacts.test.js), [`metal`](test/metal.test.js) tests | exact built-in potential, Shockley J–V, depletion charge, MOS C–V |
|
|
34
|
-
| Solar cells | photogeneration, radiative and SRH recombination | [solar](https://markblundeberg.github.io/driftlet/demos/solar.html) [organic (excitons)](https://markblundeberg.github.io/driftlet/demos/organic.html) and [perovskite (mobile ions)](https://markblundeberg.github.io/driftlet/demos/perovskite.html) demos, [`ionmonger`](test/ionmonger.test.js), [`reactions`](test/reactions.test.js) and [`generation`](test/generation.test.js) tests: generation is a reaction from a photon reservoir (uniform, or Beer–Lambert with `photogeneration()`), SRH as a rate law, in the bulk and at faces | `J_sc = qG(L_n + L_p + W)`; J_sc under Beer–Lambert against collection theory; Shockley J–V in the dark |
|
|
35
|
-
| Electrochemistry, corrosion | Nernst–Planck, concentration polarization, limiting current, Butler–Volmer, Warburg, cyclic voltammetry, salt bridges and liquid junctions, mixed potentials, electrodes under a film | [second example](#an-electrochemical-cell), [cyclic voltammetry](https://markblundeberg.github.io/driftlet/demos/redox.html), [Daniell cell](https://markblundeberg.github.io/driftlet/demos/daniell.html), [saturation](https://markblundeberg.github.io/driftlet/demos/saturation.html), [impedance](https://markblundeberg.github.io/driftlet/demos/impedance.html) and [liquid-junction](https://markblundeberg.github.io/driftlet/demos/junction.html) demos, [`circuit`](test/circuit.test.js), [`kinetics`](test/kinetics.test.js), [`impedance`](test/impedance.test.js), [`junction`](test/junction.test.js), [`ports`](test/ports.test.js) tests | `i_lim·tanh(V/4V_T)`, Butler–Volmer closed form, finite-length Warburg; junction potentials against Henderson, Planck, JPCalc and LJPcalc; Wagner–Traud |
|
|
36
|
-
| Batteries, intercalation | OCV, insertion hosts, chemical diffusion | [insertion demo](https://markblundeberg.github.io/driftlet/demos/insertion.html), [`statistics`](test/statistics.test.js) test | composition vs OCV, π²D/4L² relaxation |
|
|
37
|
-
| Double layers, colloids | Poisson–Boltzmann, Gouy–Chapman–Stern, Debye screening, crowding (Bikerman) | [double-layer demo](https://markblundeberg.github.io/driftlet/demos/double-layer.html), [`equilibrium`](test/equilibrium.test.js) test | Gouy–Chapman charge and profile, Kilic–Bazant–Ajdari |
|
|
38
|
-
| Membranes, desalination | Donnan, ion exchange, liquid junctions, water dissociation | [membrane demo](https://markblundeberg.github.io/driftlet/demos/membrane.html), [`equilibrium`](test/equilibrium.test.js), [`neutral`](test/neutral.test.js) tests | Donnan partition, Planck EMF |
|
|
39
|
-
| Solid-state ionics | mixed ionic–electronic conduction, defect chemistry, mobile ions | [`statistics`](test/statistics.test.js), [`reactions`](test/reactions.test.js) tests | mass action from standard potentials |
|
|
40
|
-
| Biophysics, nanofluidics | charged nanochannels and pores, resting potentials, Goldman–Hodgkin–Katz, Nernst, Donnan, pumps and leaks | [charged-nanochannel](https://markblundeberg.github.io/driftlet/demos/channel.html), [resting-potential](https://markblundeberg.github.io/driftlet/demos/cell.html) and [liquid-junction](https://markblundeberg.github.io/driftlet/demos/junction.html) (a patch pipette's) demos, [`pore`](test/pore.test.js) test; a membrane as a face: a capacitor with ion permeabilities, and the Na⁺/K⁺ pump as a reaction on it ([`membrane`](test/membrane.test.js) test) | GHK potential and current–voltage curve; a resolved lipid layer; Mullins–Noda; the pump's static head; Donnan |
|
|
41
|
-
|
|
42
|
-
The tests are worked setups, each with its analytic check, so they double as recipes. The
|
|
43
|
-
[validation table](#validation) lists them all.
|
|
44
|
-
|
|
45
34
|
## Install
|
|
46
35
|
|
|
47
36
|
```sh
|
|
@@ -55,7 +44,7 @@ import { bandDiagram } from 'driftlet/plot'; // a level diagram as an SVG str
|
|
|
55
44
|
```
|
|
56
45
|
|
|
57
46
|
Or straight from a CDN in a page, pinned to a version:
|
|
58
|
-
`https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
47
|
+
`https://cdn.jsdelivr.net/npm/driftlet@0.11.4/src/index.js` (and `src/kit.js`, `src/plot.js`).
|
|
59
48
|
TypeScript declarations ship in the package. Writing a demo with an LLM agent? Point it at
|
|
60
49
|
[`llms.txt`](llms.txt).
|
|
61
50
|
|
|
@@ -143,9 +132,9 @@ them locally, serve the repository root (e.g. `python3 -m http.server`) and open
|
|
|
143
132
|
| | | |
|
|
144
133
|
|---|---|---|
|
|
145
134
|
| [](https://markblundeberg.github.io/driftlet/demos/pn.html) | [](https://markblundeberg.github.io/driftlet/demos/solar.html) | [](https://markblundeberg.github.io/driftlet/demos/mos.html) |
|
|
146
|
-
| **pn junction**: quasi-Fermi levels under bias; I–V against Shockley | **Solar cell**: J<sub>sc</sub> and V<sub>oc</sub> against collection theory | **MOS capacitor**: band bending; C–V
|
|
135
|
+
| **pn junction**: quasi-Fermi levels under bias; I–V against Shockley | **Solar cell**: J<sub>sc</sub> and V<sub>oc</sub> against collection theory | **MOS capacitor**: band bending; C–V at any frequency, low to high |
|
|
147
136
|
| [](https://markblundeberg.github.io/driftlet/demos/redox.html) | [](https://markblundeberg.github.io/driftlet/demos/daniell.html) | [](https://markblundeberg.github.io/driftlet/demos/saturation.html) |
|
|
148
|
-
| **Cyclic voltammetry**: a Fermi level against a redox level | **Daniell cell**: a real salt bridge, leaking, ion by ion | **Saturation**: Ag \| AgNO₃ \| Ag
|
|
137
|
+
| **Cyclic voltammetry**: a Fermi level against a redox level | **Daniell cell**: a real salt bridge, leaking, ion by ion | **Saturation**: Ag \| AgNO₃ \| Ag up to its limiting current, and a thin-film transistor pinching off, against the charge-sheet model |
|
|
149
138
|
| [](https://markblundeberg.github.io/driftlet/demos/impedance.html) | [](https://markblundeberg.github.io/driftlet/demos/membrane.html) | [](https://markblundeberg.github.io/driftlet/demos/double-layer.html) |
|
|
150
139
|
| **Impedance**: the Warburg arc, and the cell's small-signal response inside | **Ion-exchange membrane**: Donnan steps, ion by ion, against TMS theory | **Double layer**: dilute vs crowded ions, against closed forms |
|
|
151
140
|
| [](https://markblundeberg.github.io/driftlet/demos/insertion.html) | [](https://markblundeberg.github.io/driftlet/demos/organic.html) | [](https://markblundeberg.github.io/driftlet/demos/perovskite.html) |
|
|
@@ -153,6 +142,25 @@ them locally, serve the repository root (e.g. `python3 -m http.server`) and open
|
|
|
153
142
|
| [](https://markblundeberg.github.io/driftlet/demos/channel.html) | [](https://markblundeberg.github.io/driftlet/demos/cell.html) | [](https://markblundeberg.github.io/driftlet/demos/junction.html) |
|
|
154
143
|
| **Charged nanochannel**: selectivity, overlapping Donnan layers and rectification, against Teorell–Meyer–Sievers | **Resting potential**: leaks and the Na⁺/K⁺ pump, against Mullins–Noda; the run-down to Donnan | **Liquid junctions**: a patch pipette's against Henderson, Planck and LJPcalc; a charged frit |
|
|
155
144
|
|
|
145
|
+
## Is it for your problem?
|
|
146
|
+
|
|
147
|
+
The same equations go by different names in different fields. If your problem is one-dimensional
|
|
148
|
+
(planar, or radial: spheres and cylinders, or any cross-section varying along x), it's probably here:
|
|
149
|
+
|
|
150
|
+
| If you work on | you may call it | start from | checked against |
|
|
151
|
+
|---|---|---|---|
|
|
152
|
+
| Semiconductor devices | drift–diffusion, van Roosbroeck, quasi-Fermi levels, Scharfetter–Gummel; pn, Schottky, MOS, heterojunctions; thin-film transistors (gradual channel, in the [saturation demo](https://markblundeberg.github.io/driftlet/demos/saturation.html#transistor)) | [first example](#a-semiconductor-junction), [pn](https://markblundeberg.github.io/driftlet/demos/pn.html) and [MOS](https://markblundeberg.github.io/driftlet/demos/mos.html) demos, [`contacts`](test/contacts.test.js), [`metal`](test/metal.test.js) tests | exact built-in potential, Shockley J–V, depletion charge, MOS C–V |
|
|
153
|
+
| Solar cells | photogeneration, radiative and SRH recombination | [solar](https://markblundeberg.github.io/driftlet/demos/solar.html) [organic (excitons)](https://markblundeberg.github.io/driftlet/demos/organic.html) and [perovskite (mobile ions)](https://markblundeberg.github.io/driftlet/demos/perovskite.html) demos, [`ionmonger`](test/ionmonger.test.js), [`reactions`](test/reactions.test.js) and [`generation`](test/generation.test.js) tests: generation is a reaction from a photon reservoir (uniform, or Beer–Lambert with `photogeneration()`), SRH as a rate law, in the bulk and at faces | `J_sc = qG(L_n + L_p + W)`; J_sc under Beer–Lambert against collection theory; Shockley J–V in the dark |
|
|
154
|
+
| Electrochemistry, corrosion | Nernst–Planck, concentration polarization, limiting current, Butler–Volmer, Warburg, cyclic voltammetry, salt bridges and liquid junctions, mixed potentials, electrodes under a film | [second example](#an-electrochemical-cell), [cyclic voltammetry](https://markblundeberg.github.io/driftlet/demos/redox.html), [Daniell cell](https://markblundeberg.github.io/driftlet/demos/daniell.html), [saturation](https://markblundeberg.github.io/driftlet/demos/saturation.html), [impedance](https://markblundeberg.github.io/driftlet/demos/impedance.html) and [liquid-junction](https://markblundeberg.github.io/driftlet/demos/junction.html) demos, [`circuit`](test/circuit.test.js), [`kinetics`](test/kinetics.test.js), [`impedance`](test/impedance.test.js), [`junction`](test/junction.test.js), [`ports`](test/ports.test.js) tests | `i_lim·tanh(V/4V_T)`, Butler–Volmer closed form, finite-length Warburg; junction potentials against Henderson, Planck, JPCalc and LJPcalc; Wagner–Traud |
|
|
155
|
+
| Batteries, intercalation | OCV, insertion hosts, chemical diffusion | [insertion demo](https://markblundeberg.github.io/driftlet/demos/insertion.html), [`statistics`](test/statistics.test.js) test | composition vs OCV, π²D/4L² relaxation |
|
|
156
|
+
| Double layers, colloids | Poisson–Boltzmann, Gouy–Chapman–Stern, Debye screening, crowding (Bikerman) | [double-layer demo](https://markblundeberg.github.io/driftlet/demos/double-layer.html), [`equilibrium`](test/equilibrium.test.js) test | Gouy–Chapman charge and profile, Kilic–Bazant–Ajdari |
|
|
157
|
+
| Membranes, desalination | Donnan, ion exchange, liquid junctions, water dissociation | [membrane demo](https://markblundeberg.github.io/driftlet/demos/membrane.html), [`equilibrium`](test/equilibrium.test.js), [`neutral`](test/neutral.test.js) tests | Donnan partition, Planck EMF |
|
|
158
|
+
| Solid-state ionics | mixed ionic–electronic conduction, defect chemistry, mobile ions | [`statistics`](test/statistics.test.js), [`reactions`](test/reactions.test.js) tests | mass action from standard potentials |
|
|
159
|
+
| Biophysics, nanofluidics | charged nanochannels and pores, resting potentials, Goldman–Hodgkin–Katz, Nernst, Donnan, pumps and leaks | [charged-nanochannel](https://markblundeberg.github.io/driftlet/demos/channel.html), [resting-potential](https://markblundeberg.github.io/driftlet/demos/cell.html) and [liquid-junction](https://markblundeberg.github.io/driftlet/demos/junction.html) (a patch pipette's) demos, [`pore`](test/pore.test.js) test; a membrane as a face: a capacitor with ion permeabilities, and the Na⁺/K⁺ pump as a reaction on it ([`membrane`](test/membrane.test.js) test) | GHK potential and current–voltage curve; a resolved lipid layer; Mullins–Noda; the pump's static head; Donnan |
|
|
160
|
+
|
|
161
|
+
The tests are worked setups, each with its analytic check, so they double as recipes. The
|
|
162
|
+
[validation table](#validation) lists them all.
|
|
163
|
+
|
|
156
164
|
## How to think about it
|
|
157
165
|
|
|
158
166
|
driftlet insists on thermodynamically honest concepts ([conventions](docs/conventions.md)):
|
|
@@ -260,12 +268,12 @@ Every physics feature is tested against analytic results (`npm test`, node's bui
|
|
|
260
268
|
| Time integration | BE first order, BDF2 second order; adaptive error control | ratios 2, 4 |
|
|
261
269
|
| Advection and eddy mixing | exact convection–diffusion profile; `D + D_mix`; junction EMF unchanged by mixing | 1e-12; 1e-12; 1e-5 |
|
|
262
270
|
| Metal regions | ohmic; Schottky face and MOS gate equal their contact forms; Ag \| AgNO₃ \| Ag with metal electrodes; bipolar electrode at V/2 | 1e-12; 1e-9; 1e-9; 1e-9 |
|
|
263
|
-
| Internal ports | transmission line σV tanh(L/λ)/λ (O(h²)); held level; MOS low-frequency C–V with a grounded channel | 1e-4; 1e-9; 2e-3 |
|
|
271
|
+
| Internal ports | transmission line σV tanh(L/λ)/λ (O(h²)); held level; MOS low-frequency C–V with a grounded channel, and without one the same equilibrium, and a gate step filling the inversion layer to it | 1e-4; 1e-9; 2e-3; 1e-6; 1e-3 |
|
|
264
272
|
| Cross-sections (spherical, cylindrical, any A(x)) | steady diffusion to a sphere and a cylinder (exact on any grid); Cottrell with the spherical term; uptake by a sphere filling from its surface; Debye–Hückel around a charged sphere, potential and charge | 1e-10; 3e-4; 2e-4; 1e-3, 3e-4 |
|
|
265
273
|
| A capacitance through a port | a thin-film transistor against the charge-sheet model (below threshold to saturation, second order); de Levie impedance; gate impedance against dQ/dV | 5e-4; 1e-3; 1e-6 |
|
|
266
274
|
| Electrode surfaces (coverage) | Langmuir isotherm against potential; the charge to fill a surface; the active–passive curve, blocking exactly (1 − θ) | 1e-9; 1e-3; 1e-9 |
|
|
267
275
|
| Electrodes spread through a port | transmission line with the reaction's linear kinetics as the conductance (O(h²)), rates along it; Wagner–Traud mixed potential of two Butler–Volmer couples, floating | 1e-4, 2e-3; 1e-6 V |
|
|
268
|
-
| Impedance | Macdonald blocking-electrode spectrum, 100 Hz–1 GHz; finite-length Warburg (Ag \| AgNO₃ \| Ag); DC limit = differential resistance | 3e-4; 3e-5; 1e-6 |
|
|
276
|
+
| Impedance | Macdonald blocking-electrode spectrum, 100 Hz–1 GHz; finite-length Warburg (Ag \| AgNO₃ \| Ag); DC limit = differential resistance; a p⁺n⁺ junction without recombination, Re Y → dI/dV and C = dQ/dV; a redox electrode against a bath at its open circuit, Re Y → dI/dV | 3e-4; 3e-5; 1e-6; 1e-4, 1e-6; 1e-4 |
|
|
269
277
|
| Conservation | per step, and against time-integrated contact fluxes | 1e-11 relative |
|
|
270
278
|
| Strictly neutral limit (ε = 0) | Planck EMF; polarization with no overlimiting; Donnan at neutral faces | 1e-5; 1e-4; 1e-9 |
|
|
271
279
|
| Fermi–Dirac statistics | `𝓕_{±1/2}` vs quadrature; degenerate bulk; accumulation charge via `𝓕_{3/2}` | 1e-13; 1e-12; 1e-3 |
|
|
@@ -284,17 +292,17 @@ checked in CI; times are from a desktop Ryzen 7600X in Node 22:
|
|
|
284
292
|
| Task | Time | Factorisations |
|
|
285
293
|
|---|---|---|
|
|
286
294
|
| Linear solve, 300 nodes × 7 unknowns (factor + solve) | 0.4 ms | 1 |
|
|
287
|
-
| pn diode (264 nodes): cold equilibrium |
|
|
288
|
-
| pn diode: I–V sweep 0 → 0.6 V, 31 points |
|
|
289
|
-
| pn diode: warm jump from +0.4 V to −1 V |
|
|
290
|
-
| pn diode: adaptive transient, 0 → 0.5 V, 100 ns |
|
|
291
|
-
| pn diode: impedance at 20 frequencies |
|
|
292
|
-
| MOS with a metal gate: C–V sweep, 26 points |
|
|
293
|
-
| n-Si \| KCl (4 species, 3 unknowns per node): Mott–Schottky sweep, 31 points |
|
|
294
|
-
| n-Si \| KCl: impedance at 20 frequencies |
|
|
295
|
+
| pn diode (264 nodes): cold equilibrium | 3 ms | 9 |
|
|
296
|
+
| pn diode: I–V sweep 0 → 0.6 V, 31 points | 29 ms | 137 |
|
|
297
|
+
| pn diode: warm jump from +0.4 V to −1 V | 8 ms | 51 |
|
|
298
|
+
| pn diode: adaptive transient, 0 → 0.5 V, 100 ns | 165 ms | 926 |
|
|
299
|
+
| pn diode: impedance at 20 frequencies | 14 ms | 21 |
|
|
300
|
+
| MOS with a metal gate: C–V sweep, 26 points | 9 ms | 126 |
|
|
301
|
+
| n-Si \| KCl (4 species, 3 unknowns per node): Mott–Schottky sweep, 31 points | 36 ms | 156 |
|
|
302
|
+
| n-Si \| KCl: impedance at 20 frequencies | 16 ms | 21 |
|
|
295
303
|
| Ag \| AgNO₃ \| Ag with double layers: sweep 0 → 0.1 V, 21 points | 12 ms | 81 |
|
|
296
|
-
| Ag \| AgNO₃ \| Ag, neutral: adaptive transient over 1 s |
|
|
297
|
-
| Silver electrodes with a bipolar plate: sweep 0 → 1 V, 11 points |
|
|
304
|
+
| Ag \| AgNO₃ \| Ag, neutral: adaptive transient over 1 s | 24 ms | 186 |
|
|
305
|
+
| Silver electrodes with a bipolar plate: sweep 0 → 1 V, 11 points | 25 ms | 54 |
|
|
298
306
|
|
|
299
307
|
The library has no dependencies and does no DOM access, so it runs in a Web Worker. (A device
|
|
300
308
|
definition is plain data and can be posted to a worker. Devices using custom-function
|
|
@@ -307,12 +315,14 @@ No dependencies. Tests use node's built-in runner (Node ≥ 22):
|
|
|
307
315
|
```sh
|
|
308
316
|
npm test # the validation suite
|
|
309
317
|
npm run bench # typical workloads against bench/baseline.json (--save to update it)
|
|
318
|
+
npm run stress # random devices, every result checked (--n 500 for more; see docs/reliability.md)
|
|
319
|
+
npm run hard # the corpus of once-hard cases against bench/hard.json
|
|
310
320
|
npm run types # TypeScript declarations from the JSDoc, into types/ (fetches TypeScript via npx)
|
|
311
321
|
npm run smoke # every demo in headless Chrome, every control to its extremes (CHROME=… to choose)
|
|
312
322
|
npm run shots # retake the demos' screenshots, each caught at a set state (tools/shots.js)
|
|
313
323
|
```
|
|
314
324
|
|
|
315
|
-
Design notes and plans: [numerics](docs/numerics.md), [conventions](docs/conventions.md),
|
|
325
|
+
Design notes and plans: [reliability](docs/reliability.md), [numerics](docs/numerics.md), [conventions](docs/conventions.md),
|
|
316
326
|
[statistics](docs/statistics.md), [the kit](docs/kit.md), [reading level diagrams](docs/visualization.md),
|
|
317
327
|
[data](docs/data.md), [roadmap](ROADMAP.md).
|
|
318
328
|
|
package/docs/device.md
CHANGED
|
@@ -421,65 +421,74 @@ with the contact behind it holding the conductor's electrons (see
|
|
|
421
421
|
## Internal ports
|
|
422
422
|
|
|
423
423
|
A port is an outside phase with known levels, like a contact's, attached to a window of nodes
|
|
424
|
-
inside one region instead of at an end
|
|
425
|
-
|
|
426
|
-
reference electrode
|
|
424
|
+
inside one region instead of at an end: the 1D stand-in for whatever feeds, drains or charges the
|
|
425
|
+
region sideways. It's a [terminal](#terminals) like a contact (held at `V`, behind `R`, or driven
|
|
426
|
+
by `I`; a reference electrode is a port at `I: 0`), and in its window it does any of these, in
|
|
427
|
+
any combination:
|
|
428
|
+
|
|
429
|
+
| Field | What it adds | For |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| `species` | [levels held or exchanged](#levels-held-or-exchanged), per volume | source and drain grounding a channel, salt injected mid-solution, O₂ from the air |
|
|
432
|
+
| `reactions`, `area` | an [electrode spread through the window](#electrodes-spread-through-a-window) | a thin film on a metal, a crevice, a porous electrode |
|
|
433
|
+
| `surface` | [species on that electrode's sites](#an-electrodes-surface-adsorbates-and-passive-films) | adsorbates, a passive film |
|
|
434
|
+
| `capacitance`, `area` | [charge held across a capacitance](#a-capacitance-through-a-window) | a gate along a channel, a double layer |
|
|
427
435
|
|
|
428
436
|
```js nocheck
|
|
429
437
|
ports: [{
|
|
430
438
|
name: 'channel',
|
|
431
439
|
region: 'Si', // name or index
|
|
432
440
|
from: 495e-9, to: 500e-9, // window, m from the region's left end (default: the whole region)
|
|
433
|
-
V: 0, //
|
|
434
|
-
terminal: 'e-',
|
|
435
|
-
species: {
|
|
436
|
-
'e-': 'equilibrium', // μ̄ held at V_i = V + offset throughout the window
|
|
437
|
-
'Na+': { type: 'conductance', G: 1e9, offset: 0.2 }, // source G (V_out − V_i)/(zF) per volume, G in S/m³
|
|
438
|
-
O2: { type: 'exchange', k: 1e-3, mu: -2e3 }, // neutral: source k (μ_out − μ)/RT per volume, k in mol/(m³·s)
|
|
439
|
-
},
|
|
441
|
+
V: 0, // or I, or V and R (see terminals)
|
|
442
|
+
terminal: 'e-', // the species whose level V is
|
|
443
|
+
species: { 'e-': 'equilibrium' },
|
|
440
444
|
}]
|
|
441
445
|
```
|
|
442
446
|
|
|
443
|
-
Links and offsets are those of contacts, per volume instead of per area: 0 by default only for
|
|
444
|
-
the terminal species, and an absolute `mu` for neutral species. A port is a terminal like a
|
|
445
|
-
contact: held at a voltage, behind a resistance, or driven by a current (a reference electrode
|
|
446
|
-
is a port at `I: 0`). Each solution reports `ports[k]`, with `{ name, V, flux, current }`: what
|
|
447
|
-
the port brings into the device.
|
|
448
|
-
|
|
449
|
-
A held (`'equilibrium'`) level leaves the device's two end nodes to their contacts. A port that
|
|
450
|
-
exchanges only neutral species carries no current, so its voltage means nothing: give it no drive.
|
|
451
|
-
|
|
452
|
-
An exchange link's source is linear in $`\mu_{out} - \mu`$, so it grows without bound as the
|
|
453
|
-
species runs out ($`\ln c`$). A supply limited by diffusion through a film above the window, such as O₂
|
|
454
|
-
reaching a thin layer of water from the air, is linear in $`c`$ instead: $`k(c_{sat} - c)`$. For that,
|
|
455
|
-
use a [bulk reaction](#bulk-reactions) with the outside phase as a fixed participant, e.g.
|
|
456
|
-
`{ equation: 'Air = O2', fixed: { Air: muSat }, kf: { water: profile } }`. Its rate is
|
|
457
|
-
$`k_f (1 - c/c_{sat})`$, and a profile gives $`k_f`$ its spatial variation (a film of thickness $`h(x)`$:
|
|
458
|
-
$`D c_{sat}/h^2`$).
|
|
459
|
-
|
|
460
|
-
A port driven by a current (`I`, or a waveform) floats to whatever voltage delivers it: with a
|
|
461
|
-
conductance `G` per volume over a window of width $`w`$, it sits about $`I/(G w)`$ above the
|
|
462
|
-
window's level for that species. So `G` decides how the current is shared across the window, not
|
|
463
|
-
how much enters. A small `G`, with the port volts above the window, spreads it evenly, like a
|
|
464
|
-
current source; a large one pins the window near the port's level, and the current enters
|
|
465
|
-
wherever the species is drawn away fastest. At `I: 0` the port still ties that species' level
|
|
466
|
-
together across its window (a large `G` shorts the window for it), so keep such windows narrow.
|
|
467
|
-
|
|
468
447
|
On a [conductor region](#materials), a port is a wire to the whole conductor: it takes no window,
|
|
469
448
|
only the conductor's carrier, and a conductance link's `G` is per area (S/m², a resistance
|
|
470
449
|
$`R \cdot A`$ to the port's voltage as $`G = 1/(R \cdot A)`$). That's how a floating electrode is tied
|
|
471
450
|
to ground through a resistor.
|
|
472
451
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
452
|
+
### Levels held or exchanged
|
|
453
|
+
|
|
454
|
+
```js nocheck
|
|
455
|
+
species: {
|
|
456
|
+
'e-': 'equilibrium', // μ̄ held at V_i = V + offset throughout the window
|
|
457
|
+
'Na+': { type: 'conductance', G: 1e9, offset: 0.2 }, // source G (V_out − V_i)/(zF) per volume, G in S/m³
|
|
458
|
+
O2: { type: 'exchange', k: 1e-3, mu: -2e3 }, // neutral: source k (μ_out − μ)/RT per volume, k in mol/(m³·s)
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Links and offsets are those of contacts, per volume instead of per area: 0 by default only for
|
|
463
|
+
the terminal species, and an absolute `mu` for neutral species. A held (`'equilibrium'`) level
|
|
464
|
+
leaves the device's two end nodes to their contacts. A port that exchanges only neutral species
|
|
465
|
+
carries no current, so its voltage means nothing: give it no drive.
|
|
466
|
+
|
|
467
|
+
A port driven by a current floats to whatever voltage delivers it: with a conductance `G` per
|
|
468
|
+
volume over a window of width $`w`$, about $`I/(G w)`$ above the window's level for that species. So
|
|
469
|
+
`G` decides how the current is shared across the window, not how much enters. A small `G` spreads
|
|
470
|
+
it evenly, like a current source; a large one pins the window near the port's level, and the
|
|
471
|
+
current enters wherever the species is drawn away fastest. At `I: 0` the port still ties that
|
|
472
|
+
species' level together across its window (a large `G` shorts the window for it), so keep such
|
|
473
|
+
windows narrow.
|
|
474
|
+
|
|
475
|
+
An exchange link's source is linear in $`\mu_{out} - \mu`$, so it grows without bound as the
|
|
476
|
+
species runs out ($`\ln c`$). A supply limited by diffusion through a film above the window, such
|
|
477
|
+
as O₂ reaching a thin layer of water from the air, is linear in $`c`$ instead: $`k(c_{sat} - c)`$. For
|
|
478
|
+
that, use a [bulk reaction](#bulk-reactions) with the outside phase as a fixed participant,
|
|
479
|
+
`{ equation: 'Air = O2', fixed: { Air: muSat }, kf: { water: profile } }`, whose rate is
|
|
480
|
+
$`k_f (1 - c/c_{sat})`$; a profile gives $`k_f`$ its spatial variation ($`D c_{sat}/h^2`$ under a film
|
|
481
|
+
of thickness $`h(x)`$).
|
|
482
|
+
|
|
483
|
+
A held level can also anchor what nothing else holds. In a 1D MOS capacitor without generation,
|
|
484
|
+
inversion electrons can only arrive by minority-carrier diffusion from the back contact (weeks),
|
|
485
|
+
so the inversion layer's Fermi level is undetermined to within round-off and no steady state can
|
|
486
|
+
be computed. A port holding the electrons beside the oxide, as source and drain would, anchors it,
|
|
487
|
+
and the device shows the low-frequency C–V.
|
|
479
488
|
|
|
480
489
|
### Electrodes spread through a window
|
|
481
490
|
|
|
482
|
-
A port can
|
|
491
|
+
A port can hold an electrode: a metal whose carrier sits at the port's level, reacting all
|
|
483
492
|
through the window rather than at one face. That's the floor under a thin film of electrolyte, the
|
|
484
493
|
walls of a crevice or a pit, or the matrix of a porous electrode, wherever the metal conducts well
|
|
485
494
|
enough to be one level. Give the port its `reactions` (Butler–Volmer, as at a face), the carrier as
|
|
@@ -499,28 +508,26 @@ ports: [{
|
|
|
499
508
|
|
|
500
509
|
Each node's rate per area is the face's law, with the region's species at that node and the
|
|
501
510
|
carrier at activity 1 and level $`V`$. Times the area per volume, it makes and consumes the
|
|
502
|
-
region's species there, and the electrons it takes or gives are the port's current.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
electrode, forward), so reaction first, then node. They're typed arrays (`Float64Array`), so
|
|
510
|
-
`Array.from` them before mapping to anything but numbers. Per volume of the window it's
|
|
511
|
+
region's species there, and the electrons it takes or gives are the port's current. Held at a
|
|
512
|
+
voltage, the port is a potentiostat. At `I: 0` (or with nothing else to carry current), the
|
|
513
|
+
reactions settle at the mixed potential, the corrosion potential where anodic and cathodic
|
|
514
|
+
currents cancel over the window, while each node can be a net anode or a net cathode; a floating
|
|
515
|
+
one starts there, for the start's composition (with a double layer, it starts uncharged and
|
|
516
|
+
charges toward it). An electrode that's the device's only path for current is at `I: 0` whatever
|
|
517
|
+
its drive: hold it at `V: 0`, which also fixes the level the water's φ is read against. Per volume of the window a rate is
|
|
511
518
|
`rates[q][j] * area[j]`, and per area of a film's floor (thickness h, `area` 1/h) the rate itself.
|
|
512
519
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
anything that reaches the film from above (O₂ from the air, see the exchange link above). Where
|
|
517
|
-
the film's thickness varies, give the device its [cross-section](#geometry) too, so that ions
|
|
520
|
+
In 1D, a film on a metal is a slice along the metal: the film's thickness enters through `area`,
|
|
521
|
+
and through a bulk reaction for anything that reaches the film from above (O₂ from the air, see
|
|
522
|
+
above). Where the thickness varies, give the device its [cross-section](#geometry) too, so that ions
|
|
518
523
|
travelling along the film squeeze through where it thins: for a drop on a metal, seen from its
|
|
519
|
-
centre, $`A = 2\pi r\,h(r)`$ with `area` $`1/h(r)`$.
|
|
524
|
+
centre, $`A = 2\pi r\,h(r)`$ with `area` $`1/h(r)`$. The region must be strictly neutral
|
|
525
|
+
($`\varepsilon = 0`$), as in porous-electrode theory: each spot's double layer is below the grid, so
|
|
526
|
+
the reactions can't leave the solution charged (give the port a
|
|
527
|
+
[capacitance](#a-capacitance-through-a-window) for the double layer's charge).
|
|
520
528
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
solution charged.
|
|
529
|
+
Validated against the transmission line (linear kinetics along a bar, test/ports.test.js) and the
|
|
530
|
+
Wagner–Traud mixed potential of two Butler–Volmer couples.
|
|
524
531
|
|
|
525
532
|
### An electrode's surface: adsorbates and passive films
|
|
526
533
|
|
|
@@ -538,7 +545,7 @@ ports: [{
|
|
|
538
545
|
surface: { 'Fe(OH)2': { mu0: -490e3, capacity: 2e-5, theta0: 1e-6 } }, // theta0: the start
|
|
539
546
|
reactions: [
|
|
540
547
|
{ equation: 'Fe2+ + 2 e- = Fe(s)', fixed: { 'Fe(s)': 0 }, k0: 5e-6, alpha: 0.5, bare: true }, // dissolves where bare
|
|
541
|
-
{ equation: 'Fe(s) + 2 OH- = Fe(OH)2 + 2 e-', fixed: { 'Fe(s)': 0 }, k0: 1e-
|
|
548
|
+
{ equation: 'Fe(s) + 2 OH- = Fe(OH)2 + 2 e-', fixed: { 'Fe(s)': 0 }, k0: 1e-4, alpha: 0.5, bare: true }, // the film forms (in minutes)
|
|
542
549
|
{ equation: 'O2 + 2 H2O + 4 e- = 4 OH-', fixed: { H2O: -237.13e3 }, k0: 3e-9, alpha: 0.125 }, // on film or not
|
|
543
550
|
],
|
|
544
551
|
}]
|
|
@@ -546,35 +553,39 @@ ports: [{
|
|
|
546
553
|
|
|
547
554
|
A film forms where the potential and the pH favour it, through its own thermodynamics, and blocks
|
|
548
555
|
what needs bare metal: the dissolution current climbs with potential, peaks, and falls as the film
|
|
549
|
-
covers the metal, the active–passive curve. Each node's surface stores
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
556
|
+
covers the metal, the active–passive curve. Each node's surface stores $`\Gamma\,d\theta/dt`$ per area
|
|
557
|
+
of electrode, so filling it passes a current, and what passes through a surface is conserved with
|
|
558
|
+
it. It's a monolayer picture: a real passive oxide is nanometres thick, grows, and breaks down
|
|
559
|
+
under chloride, none of which a coverage has.
|
|
560
|
+
|
|
561
|
+
Cautions:
|
|
562
|
+
- **Keep a film's stability within reason.** Blocking the bare fraction to 1e-3–1e-6 is already
|
|
563
|
+
a passive metal; far below that ($`\theta_0 \lesssim 10^{-10}`$) the coverage's η barely affects
|
|
564
|
+
anything and Newton struggles with it (a solution warns).
|
|
565
|
+
- **A film's μ° is a fitted number.** A monolayer given a bulk hydroxide's ΔG°f passivates iron far
|
|
566
|
+
into neutral water, so choose it to put the passivation where the metal's chemistry does.
|
|
567
|
+
- **Without a double layer, passivation can jump.** With no capacitance the potential follows the
|
|
568
|
+
kinetics instantly, and when the last active patch covers over, the potential where no current
|
|
569
|
+
flows can vanish from the active branch: a discontinuity, where a transient stops. Give the port
|
|
570
|
+
a [capacitance](#a-capacitance-through-a-window) and it's a quick, continuous swing, as on a real
|
|
571
|
+
electrode.
|
|
563
572
|
|
|
564
573
|
Validated in test/coverage.test.js: the Langmuir isotherm against the electrode's potential (to
|
|
565
|
-
1e-9),
|
|
566
|
-
active–passive curve with blocking exactly $`(1-\theta)`$ times Butler–Volmer
|
|
574
|
+
1e-9), filling against its exponential and its charge $`F\,\Gamma\,\Delta\theta`$ per area of electrode,
|
|
575
|
+
the active–passive curve with blocking exactly $`(1-\theta)`$ times Butler–Volmer, and a steady state
|
|
576
|
+
through a surface intermediate where the transient ends.
|
|
567
577
|
|
|
568
|
-
### A capacitance through a window
|
|
578
|
+
### A capacitance through a window
|
|
569
579
|
|
|
570
580
|
A port can hold charge across a capacitance spread through its window: per area of electrode,
|
|
571
581
|
$`\sigma = C\,(V - \mathtt{zeroCharge} - \phi)`$ on the port's side (C in F/m²), with `area` per volume
|
|
572
582
|
as for reactions, and the window holds the opposite. Each node's charge balance gains $`a\sigma`$:
|
|
573
|
-
Gauss's law averaged across the section,
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
583
|
+
Gauss's law averaged across the section, $`-\partial_x(\varepsilon\,\partial_x\phi) = \rho + a\sigma`$,
|
|
584
|
+
and at $`\varepsilon = 0`$ neutrality $`\rho + a\sigma = 0`$, the mobile species rearranging to supply
|
|
585
|
+
it (counter-ions in, co-ions out). The port passes the charging current, $`d(\int a\sigma\,dV)/dt`$,
|
|
586
|
+
none in a steady state. Capacitances on overlapping windows add (a channel's top and bottom
|
|
587
|
+
gates). A capacitance alone, driven by a current, only charges: it starts uncharged, `advance()`
|
|
588
|
+
follows it, and `solve()` refuses (there's no steady state, as for a gate contact driven by one).
|
|
578
589
|
|
|
579
590
|
```js nocheck
|
|
580
591
|
// a thin-film transistor: the gate along a 10 nm channel through 1 mF/m² (35 nm of SiO₂)
|
|
@@ -583,15 +594,23 @@ ports: [{ name: 'gate', region: 'channel', V: 1.5, area: 1 / 10e-9, capacitance:
|
|
|
583
594
|
ports: [{ name: 'iron', region: 'film', terminal: 'e-', V: 0, area: 1e4, capacitance: { C: 0.2, zeroCharge: -0.4 }, reactions: [/* … */] }]
|
|
584
595
|
```
|
|
585
596
|
|
|
597
|
+
A volumetric capacitance $`C^*`$ (F/m³, an organic electrochemical transistor's) goes in per
|
|
598
|
+
area of a nominal electrode: `area: 1 / t, capacitance: { C: Cstar * t }` for a film t thick
|
|
599
|
+
(`area: 1, C: Cstar` is the same physics, but reads to `describe()` like a unit slip). To read the
|
|
600
|
+
gate's voltage as the film's effective one, put the undisturbed film at φ = 0 (its carrier's
|
|
601
|
+
`cRef` at its doping, `mu0: 0`) and leave `zeroCharge` at 0. The gate charges the carriers
|
|
602
|
+
through their own chemical capacitance too, in series, so $`dQ/dV_G`$ comes out below $`C^*`$
|
|
603
|
+
(about 12% for holes at 1e20 cm⁻³ against 40 F/cm³).
|
|
604
|
+
|
|
586
605
|
That's the gradual-channel approximation of a field-effect transistor, exact for a thin film
|
|
587
606
|
with no body (a TFT, an organic or oxide transistor) while the channel is long against the gate
|
|
588
607
|
dielectric, and failing where it pinches off (where the 2D field takes over). It's also the
|
|
589
|
-
Bernards model of an organic electrochemical transistor (a volumetric capacitance
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
a blocked spectator's flat level does in an electrolyte,
|
|
594
|
-
carriers run out at the drain, in both.
|
|
608
|
+
Bernards model of an organic electrochemical transistor (a volumetric capacitance; the
|
|
609
|
+
Bernards–Malliaras current is the charge-sheet formula below without its $`V_T`$ term, so they
|
|
610
|
+
part near pinch-off, where driftlet keeps the diffusion that formula drops), cable theory
|
|
611
|
+
for an axon's membrane, and a porous electrode's double layer. The gate's flat level pins the
|
|
612
|
+
channel's standard level through C, as a blocked spectator's flat level does in an electrolyte,
|
|
613
|
+
and the current saturates as the carriers run out at the drain, in both.
|
|
595
614
|
|
|
596
615
|
Validated in test/capacitance.test.js: a thin-film transistor's current against the
|
|
597
616
|
charge-sheet model, $`I/W = (\mu/L)[(\sigma_s^2 - \sigma_d^2)/(2C) + V_T(\sigma_s - \sigma_d)]`$, from
|
|
@@ -599,6 +618,23 @@ below threshold through saturation (second order in the grid); a porous electrod
|
|
|
599
618
|
impedance $`\sqrt{r/y}\coth(L\sqrt{ry})`$, the double layer in series with the ions' chemical
|
|
600
619
|
capacitance; and the gate's low-frequency impedance against $`dQ/dV_G`$ from steady states.
|
|
601
620
|
|
|
621
|
+
### What a solution reports
|
|
622
|
+
|
|
623
|
+
Each solution's `ports[k]`, as the port has them:
|
|
624
|
+
|
|
625
|
+
| Field | Contents |
|
|
626
|
+
|---|---|
|
|
627
|
+
| `name`, `V` | the port's name and voltage |
|
|
628
|
+
| `flux[name]`, `current` | what it brings into the device: each species (mol/(m²·s)) and the current (A/m², the charging current included), totals through a [cross-section](#geometry) |
|
|
629
|
+
| `x[j]`, `area[j]` | with reactions or a capacitance: the window's node j (m) and the electrode's area per volume there (m²/m³) |
|
|
630
|
+
| `rates[q][j]` | reaction q's forward rate at node j, mol/(m²·s) of electrode (reaction first, then node) |
|
|
631
|
+
| `coverage[name][j]`, `bare[j]` | with a surface: each species' coverage θ, and the bare fraction θ₀ to full precision |
|
|
632
|
+
| `sigma[j]`, `charge` | with a capacitance: σ per area of electrode (C/m²), and the total the port's side holds |
|
|
633
|
+
|
|
634
|
+
The arrays are typed (`Float64Array`), so `Array.from` them before mapping to anything but
|
|
635
|
+
numbers. The kit's [`polarization()`](kit.md#polarization-curves-an-evans-diagram) gives each reaction's rate at a spot of the
|
|
636
|
+
electrode with its level moved, an Evans diagram.
|
|
637
|
+
|
|
602
638
|
## Terminals
|
|
603
639
|
|
|
604
640
|
The two contacts and every port are the device's terminals, named `left`, `right` and by each
|
|
@@ -610,7 +646,7 @@ contacts: {
|
|
|
610
646
|
right: { I: -2, ... }, // driven by a current into the device, A/m² (I: 0 is open circuit)
|
|
611
647
|
},
|
|
612
648
|
ports: [{ name: 'ref', I: 0, ... }, // a reference electrode: no current, its voltage read off
|
|
613
|
-
{ name: 'wire', V: 0, R: 1e-3, ... }], // a source V behind a series resistance R (Ω·m
|
|
649
|
+
{ name: 'wire', V: 0, R: 1e-3, ... }], // a source V behind a series resistance R (Ω·m², or Ω with a geometry)
|
|
614
650
|
```
|
|
615
651
|
|
|
616
652
|
- **`V`**: the terminal's voltage, the shift of its outside phase's ladder, as the voltage of
|
|
@@ -620,7 +656,9 @@ ports: [{ name: 'ref', I: 0, ... }, // a reference electrode: no current, its v
|
|
|
620
656
|
the voltage floats and is solved for.
|
|
621
657
|
- **`V` and `R`**: a source behind a resistance, $`I = (V - V_{\mathrm{terminal}})/R`$.
|
|
622
658
|
|
|
623
|
-
At least one terminal must be held at a voltage, or the device's overall level floats
|
|
659
|
+
At least one terminal must be held at a voltage, or the device's overall level floats, and if
|
|
660
|
+
one is driven by a current, a held one must pass current (a closed end held at a voltage fixes
|
|
661
|
+
nothing). In
|
|
624
662
|
steady state the terminal currents sum to zero. A contact can be driven by a current only if
|
|
625
663
|
something passes it (a linked species, or a gate's displacement).
|
|
626
664
|
|
|
@@ -675,7 +713,8 @@ its middle, and nothing passes the end. Only an end may have $`A = 0`$. Flow (`v
|
|
|
675
713
|
planar devices.
|
|
676
714
|
|
|
677
715
|
With a cross-section, the solution's currents, contact and port fluxes, terminal currents and
|
|
678
|
-
`charge` are totals (A, mol/s; per metre for cylinders), and so are `I` drives
|
|
716
|
+
`charge` are totals (A, mol/s; per metre for cylinders), and so are `I` drives, a series `R`
|
|
717
|
+
and the impedance (Ω, not Ω·m²). Face laws,
|
|
679
718
|
rates, `interfaces[f].N` and `D`, and concentrations stay per area or per volume. Validated against
|
|
680
719
|
steady diffusion to a sphere and a cylinder (exact), Cottrell's transient with the spherical term,
|
|
681
720
|
uptake by a sphere filling from its surface, and Debye–Hückel screening around a charged sphere
|
|
@@ -687,6 +726,7 @@ uptake by a sphere filling from its surface, and Debye–Hückel screening aroun
|
|
|
687
726
|
const dev = new Device(def);
|
|
688
727
|
const sol = dev.solve(); // steady state (or equilibrium)
|
|
689
728
|
dev.set({ contacts: { right: { V: 0.3 } } }); // deep-merged change; the state is kept as a warm start
|
|
729
|
+
dev.set({ ports: { gate: { V: 0.2 } } }); // ports patched by name (an array replaces them all)
|
|
690
730
|
const sol2 = dev.solve();
|
|
691
731
|
const tr = dev.step(1e-6); // one backward-Euler step (auto-subdivided if needed)
|
|
692
732
|
const tr2 = dev.step(1e-6, { method: 'bdf2' }); // second-order BDF2 once a previous step exists
|
|
@@ -701,17 +741,23 @@ const now = dev.solution(); // snapshot of the current state
|
|
|
701
741
|
amounts (blocked species, reactive moieties) are kept exactly. If a direct solve fails at a
|
|
702
742
|
bias (a cold start far from equilibrium), it solves with both terminals level and ramps the
|
|
703
743
|
right terminal's voltage to its target; with generation reactions (below), it ramps their
|
|
704
|
-
rates up from nearly nothing.
|
|
744
|
+
rates up from nearly nothing. A terminal driven by a current (open circuit, say) is held at a
|
|
745
|
+
voltage instead, the voltage marched until the current crosses its target, and floated from
|
|
746
|
+
there. If 20 V either way brings no crossing (a current beyond the limiting one), the solve
|
|
747
|
+
fails with a warning giving the range of currents the held voltages passed.
|
|
705
748
|
- `step(dt, { method })` advances the transient by dt seconds, halving internally where Newton
|
|
706
749
|
needs it. `method` is `'be'` (backward Euler, the default) or `'bdf2'`.
|
|
707
750
|
- `advance(tEnd, opts)` integrates adaptively to `tEnd` with variable-step BDF2, controlling
|
|
708
751
|
the local error per step to `tol` (default 1e-3) in thermal units of every potential ($`\phi`$ and
|
|
709
|
-
each $`\bar\mu/RT`$): roughly 0.1% in concentrations.
|
|
752
|
+
each $`\bar\mu/RT`$): roughly 0.1% in concentrations per step. That's local: over a long decay
|
|
753
|
+
the errors add up, and a small current at the end of one can be tens of percent off, so tighten
|
|
754
|
+
`tol` (1e-5, say) before fitting a time constant to a tail. It lands exactly on `tEnd`. Options: `tol`,
|
|
710
755
|
`dt0` (first step), `dtMax`, `budgetMs` (return after this much wall time, with
|
|
711
756
|
`done: false`), `maxSteps`, `method`. The step size carries over between calls, so an
|
|
712
757
|
animation can call `advance(tNext, { budgetMs })` once per frame. The solution adds `done`,
|
|
713
758
|
`rejected`, and a `trace` of terminal current and voltage after every accepted step (the
|
|
714
|
-
|
|
759
|
+
current is the right contact's, toward +x, as `sol.current`; a port's is in `sol.terminals`
|
|
760
|
+
after each call; the voltage is the right contact's minus the left's: what a meter between the terminals reads,
|
|
715
761
|
which at a bath is its reference species' level, not $`\phi`$). With
|
|
716
762
|
`probes: [{ x, species, quantity, region }]` the trace also reads inside the device,
|
|
717
763
|
as `trace.probes[k]` beside `trace.t`: a species' concentration (`quantity: 'c'`, the default,
|
|
@@ -774,7 +820,7 @@ console.log(`${run.steps} steps; I(0.01 s) ≈ ${run.trace.current[run.trace.t.f
|
|
|
774
820
|
| `terminals[name]` | `{ V, current }` for each terminal (contacts and ports), current into the device |
|
|
775
821
|
| `contacts.left/right` | `{ V, flux: {name}, D, current }` at each contact |
|
|
776
822
|
| `gates.left/right` | charge on a gate or Stern plate, where the contact is capacitive |
|
|
777
|
-
| `ports[k]` | `{ name, V, flux: {name}, current }`: what each internal port brings into the device |
|
|
823
|
+
| `ports[k]` | `{ name, V, flux: {name}, current }`: what each internal port brings into the device; an electrode's rates, coverages and charge [as well](#what-a-solution-reports) |
|
|
778
824
|
| `interfaces[f]` | `{ left, right, dipole, sheetCharge, D, N: {name}, rates }`: the names of the regions the face joins, what crosses it by its links, and each face reaction's rate (mol/(m²·s)) |
|
|
779
825
|
| `bulkReactions[k]` | `{ rate, regions, total }`: each bulk reaction's forward rate at every node (mol/(m³·s), `NaN` where it doesn't run), and integrated over each region and the device (mol/(m²·s)), as the balances count it: a charge-balance check is J = F(generation − recombination), which the kit's `check()` does for every species |
|
|
780
826
|
| `charge` | total charge in the device, C/m² (C through a cross-section) |
|
package/docs/kit.md
CHANGED
|
@@ -133,6 +133,11 @@ the SHE level (`sol.phi` with the usual tables' ions).
|
|
|
133
133
|
|
|
134
134
|
The example above ends with the left electrode's curve.
|
|
135
135
|
|
|
136
|
+
An electrode port's surface is held too by default. With `{ surface: 'equilibrium' }` (a fifth
|
|
137
|
+
argument) its coverages are re-equilibrated at each V instead, each species by the reaction that
|
|
138
|
+
makes it from the solution: the steady-state curve, where a passive film's active–passive peak
|
|
139
|
+
shows (`curve.bare` gives the bare fraction along it).
|
|
140
|
+
|
|
136
141
|
Concentrations are held, so these are the curves of that instant (for a mixed potential, the
|
|
137
142
|
kinetic picture). Away from the actual level the composition beside the electrode would change,
|
|
138
143
|
which is the transport the full solve accounts for.
|