driftlet 0.10.0 → 0.12.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 +50 -36
- package/docs/device.md +129 -7
- package/docs/kit.md +12 -0
- package/docs/numerics.md +188 -22
- package/docs/reliability.md +122 -0
- package/llms.txt +55 -8
- package/package.json +2 -1
- package/src/blockTridiagonal.js +17 -4
- package/src/channels.js +73 -0
- package/src/checks.js +17 -10
- package/src/describe.js +4 -2
- package/src/device.js +55 -11
- package/src/difference.js +142 -0
- package/src/index.js +22 -6
- package/src/kit.js +1 -0
- package/src/recorder.js +1 -1
- package/src/solution.js +20 -3
- package/src/solver.js +1166 -146
- package/types/blockTridiagonal.d.ts +9 -1
- package/types/channels.d.ts +39 -0
- package/types/difference.d.ts +30 -0
- package/types/index.d.ts +2 -1
- package/types/kit.d.ts +1 -0
- package/types/recorder.d.ts +7 -3
- package/types/solver.d.ts +77 -11
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, 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 |
|
|
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.12.0/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,7 +132,7 @@ 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
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) |
|
|
@@ -152,6 +141,27 @@ them locally, serve the repository root (e.g. `python3 -m http.server`) and open
|
|
|
152
141
|
| **Intercalation host**: cycling between cutoffs against the OCV | **Organic solar cell**: excitons diffusing to a donor/acceptor interface, against theory | **Perovskite hysteresis**: mobile ions and scan rate, against IonMonger |
|
|
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 |
|
|
144
|
+
| [](https://markblundeberg.github.io/driftlet/demos/axon.html) | [](https://markblundeberg.github.io/driftlet/demos/propagation.html) | [](https://markblundeberg.github.io/driftlet/demos/myelin.html) |
|
|
145
|
+
| **Action potential**: Hodgkin and Huxley's gated channels, each ion's driving force through the spike, against the HH equations | **Propagation**: a spike travelling along an axon at HH's speed, out of the ions' drift; which ions carry it | **Myelin**: saltatory conduction, node to node, against a compartmental cable; a race with a bare axon |
|
|
146
|
+
|
|
147
|
+
## Is it for your problem?
|
|
148
|
+
|
|
149
|
+
The same equations go by different names in different fields. If your problem is one-dimensional
|
|
150
|
+
(planar, or radial: spheres and cylinders, or any cross-section varying along x), it's probably here:
|
|
151
|
+
|
|
152
|
+
| If you work on | you may call it | start from | checked against |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| 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 |
|
|
155
|
+
| 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 |
|
|
156
|
+
| 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 |
|
|
157
|
+
| 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 |
|
|
158
|
+
| 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 |
|
|
159
|
+
| 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 |
|
|
160
|
+
| 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 |
|
|
161
|
+
| 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), [action-potential](https://markblundeberg.github.io/driftlet/demos/axon.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); voltage-gated channels with Hodgkin–Huxley kinetics, action potentials, and their propagation along an axon ([`gates`](test/gates.test.js), [`cable`](test/cable.test.js) tests) | GHK potential and current–voltage curve; a resolved lipid layer; Mullins–Noda; the pump's static head; Donnan; HH gating under voltage clamp; the space-clamped HH equations |
|
|
162
|
+
|
|
163
|
+
The tests are worked setups, each with its analytic check, so they double as recipes. The
|
|
164
|
+
[validation table](#validation) lists them all.
|
|
155
165
|
|
|
156
166
|
## How to think about it
|
|
157
167
|
|
|
@@ -247,6 +257,8 @@ Every physics feature is tested against analytic results (`npm test`, node's bui
|
|
|
247
257
|
| Schottky barrier (metal region \| n-Si) | surface density from the alignment; depletion charge | 5e-3; 2% |
|
|
248
258
|
| Charged pore or nanochannel (resolved Donnan layers at each mouth) | conductance against salt, down to its plateau, as Teorell–Meyer–Sievers in a long pore (short ones short by end layers ∝ 1/L); the zero-current voltage across a salt gradient; a symmetric pore's I–V odd, rectification with charge on one side or bipolar | 1%; 5e-3 to 1e-4; 1e-9 |
|
|
249
259
|
| Cell membrane (a capacitive face with permeabilities) | GHK resting potential (neutral or resolved solutions) and current–voltage curve; the same as a resolved 5 nm lipid layer; charging at I/C; with the Na⁺/K⁺ pump in a closed cell, Mullins–Noda, the static head 3Δμ̄_Na − 2Δμ̄_K = ΔG_ATP, and Donnan with the pump off | 1e-5 V; 3e-3; 2e-5 V; 1%; 1e-9 |
|
|
260
|
+
| Voltage-gated channels (Hodgkin–Huxley gates on a membrane face) | each gate at α/(α + β) under a voltage clamp, and relaxing at α + β after a step; a squid axon's action potential against the space-clamped HH equations with GHK currents; its impedance at rest (inductive, resonant) against the linearised equations | 1e-12; 2e-4; 1 mV; 5e-4 |
|
|
261
|
+
| A propagating action potential (a squid axon along x: gated membrane port, ions drifting in the axoplasm) | rest at HH's −65 mV and gate values; conduction velocity against the cable equation solved directly, and HH's 18.8 m/s; a myelinated axon's saltatory conduction, node by node, against a compartmental cable | 0.01 mV, 1e-4; 2e-3; 1%; 1 µs of ~25 per node |
|
|
250
262
|
| Liquid junctions in mixtures | Henderson's formula against JPCalc's published values; driftlet's steady junction against Planck by shooting and the stationary Nernst–Planck codes LJPcalc and JLJP, a patch pipette's K-gluconate among them; a free-diffusion junction constant in time, between the two | 0.015 mV; 2e-4 and 0.02 mV; 5e-3 mV |
|
|
251
263
|
| Liquid junction, open circuit | cell EMF 2t₊(RT/F) ln(c₁/c₂); Planck diffusion potential | 1e-4 |
|
|
252
264
|
| Salt diffusion mode from a `c0` profile | c̄ + a·sin(πx/2L) decaying at π²D/4L², D = 2D₊D₋/(D₊ + D₋) | 2e-5 |
|
|
@@ -260,12 +272,12 @@ Every physics feature is tested against analytic results (`npm test`, node's bui
|
|
|
260
272
|
| Time integration | BE first order, BDF2 second order; adaptive error control | ratios 2, 4 |
|
|
261
273
|
| Advection and eddy mixing | exact convection–diffusion profile; `D + D_mix`; junction EMF unchanged by mixing | 1e-12; 1e-12; 1e-5 |
|
|
262
274
|
| 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 |
|
|
275
|
+
| 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
276
|
| 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
277
|
| 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
278
|
| 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
279
|
| 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 |
|
|
280
|
+
| 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
281
|
| Conservation | per step, and against time-integrated contact fluxes | 1e-11 relative |
|
|
270
282
|
| Strictly neutral limit (ε = 0) | Planck EMF; polarization with no overlimiting; Donnan at neutral faces | 1e-5; 1e-4; 1e-9 |
|
|
271
283
|
| Fermi–Dirac statistics | `𝓕_{±1/2}` vs quadrature; degenerate bulk; accumulation charge via `𝓕_{3/2}` | 1e-13; 1e-12; 1e-3 |
|
|
@@ -284,17 +296,17 @@ checked in CI; times are from a desktop Ryzen 7600X in Node 22:
|
|
|
284
296
|
| Task | Time | Factorisations |
|
|
285
297
|
|---|---|---|
|
|
286
298
|
| 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 |
|
|
299
|
+
| pn diode (264 nodes): cold equilibrium | 3 ms | 9 |
|
|
300
|
+
| pn diode: I–V sweep 0 → 0.6 V, 31 points | 29 ms | 137 |
|
|
301
|
+
| pn diode: warm jump from +0.4 V to −1 V | 8 ms | 51 |
|
|
302
|
+
| pn diode: adaptive transient, 0 → 0.5 V, 100 ns | 165 ms | 926 |
|
|
303
|
+
| pn diode: impedance at 20 frequencies | 14 ms | 21 |
|
|
304
|
+
| MOS with a metal gate: C–V sweep, 26 points | 9 ms | 126 |
|
|
305
|
+
| n-Si \| KCl (4 species, 3 unknowns per node): Mott–Schottky sweep, 31 points | 36 ms | 156 |
|
|
306
|
+
| n-Si \| KCl: impedance at 20 frequencies | 16 ms | 21 |
|
|
295
307
|
| 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 |
|
|
308
|
+
| Ag \| AgNO₃ \| Ag, neutral: adaptive transient over 1 s | 24 ms | 186 |
|
|
309
|
+
| Silver electrodes with a bipolar plate: sweep 0 → 1 V, 11 points | 25 ms | 54 |
|
|
298
310
|
|
|
299
311
|
The library has no dependencies and does no DOM access, so it runs in a Web Worker. (A device
|
|
300
312
|
definition is plain data and can be posted to a worker. Devices using custom-function
|
|
@@ -307,12 +319,14 @@ No dependencies. Tests use node's built-in runner (Node ≥ 22):
|
|
|
307
319
|
```sh
|
|
308
320
|
npm test # the validation suite
|
|
309
321
|
npm run bench # typical workloads against bench/baseline.json (--save to update it)
|
|
322
|
+
npm run stress # random devices, every result checked (--n 500 for more; see docs/reliability.md)
|
|
323
|
+
npm run hard # the corpus of once-hard cases against bench/hard.json
|
|
310
324
|
npm run types # TypeScript declarations from the JSDoc, into types/ (fetches TypeScript via npx)
|
|
311
325
|
npm run smoke # every demo in headless Chrome, every control to its extremes (CHROME=… to choose)
|
|
312
326
|
npm run shots # retake the demos' screenshots, each caught at a set state (tools/shots.js)
|
|
313
327
|
```
|
|
314
328
|
|
|
315
|
-
Design notes and plans: [numerics](docs/numerics.md), [conventions](docs/conventions.md),
|
|
329
|
+
Design notes and plans: [reliability](docs/reliability.md), [numerics](docs/numerics.md), [conventions](docs/conventions.md),
|
|
316
330
|
[statistics](docs/statistics.md), [the kit](docs/kit.md), [reading level diagrams](docs/visualization.md),
|
|
317
331
|
[data](docs/data.md), [roadmap](ROADMAP.md).
|
|
318
332
|
|
package/docs/device.md
CHANGED
|
@@ -277,6 +277,68 @@ read from `sol.phi` (or a `'phi'` probe in a transient). A bath contact's termin
|
|
|
277
277
|
its reference species' level instead, what an electrode reversible to that species would read,
|
|
278
278
|
so between two different solutions it differs from $`\Delta\phi`$ by that species' Nernst term.
|
|
279
279
|
|
|
280
|
+
### Voltage-gated channels
|
|
281
|
+
|
|
282
|
+
A permeability can be gated, as Hodgkin and Huxley's Na⁺ and K⁺ channels are: the face lists its
|
|
283
|
+
`gates`, each a fraction $`x`$ in [0, 1] with first-order kinetics in the voltage across the face,
|
|
284
|
+
$`V = \phi_{\mathrm{right}} - \phi_{\mathrm{left}}`$ at its two edge nodes,
|
|
285
|
+
|
|
286
|
+
```math
|
|
287
|
+
\frac{dx}{dt} = \alpha(V)\,(1 - x) - \beta(V)\,x ,
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
and a link names the gates that scale its $`P`$ (or a conductance link's $`G`$), with their exponents:
|
|
291
|
+
$`P\,m^3 h`$ for `{ m: 3, h: 1 }`. (A membrane along a region instead, an axon's, is a
|
|
292
|
+
[port](#a-membrane-through-a-window-gated-channels).)
|
|
293
|
+
Each of $`\alpha`$ and $`\beta`$ is one of NeuroML's three standard forms, with `rate` in 1/s and `midpoint`
|
|
294
|
+
and `scale` in volts:
|
|
295
|
+
|
|
296
|
+
| `type` | rate |
|
|
297
|
+
| --- | --- |
|
|
298
|
+
| `'exp'` | $`\mathtt{rate}\; e^{(V - \mathtt{midpoint})/\mathtt{scale}}`$ |
|
|
299
|
+
| `'sigmoid'` | $`\mathtt{rate} / (1 + e^{(\mathtt{midpoint} - V)/\mathtt{scale}})`$ |
|
|
300
|
+
| `'expLinear'` | $`\mathtt{rate}\; y/(1 - e^{-y})`$, $`y = (V - \mathtt{midpoint})/\mathtt{scale}`$ |
|
|
301
|
+
|
|
302
|
+
With the outside on the left, $`V`$ is the membrane potential $`\phi_{\mathrm{in}} - \phi_{\mathrm{out}}`$. Hodgkin and
|
|
303
|
+
Huxley's squid axon (at their 6.3 °C):
|
|
304
|
+
|
|
305
|
+
```js nocheck
|
|
306
|
+
{
|
|
307
|
+
phi: { type: 'capacitive', C: 0.01 },
|
|
308
|
+
gates: {
|
|
309
|
+
m: { alpha: { type: 'expLinear', rate: 1000, midpoint: -0.040, scale: 0.010 }, beta: { type: 'exp', rate: 4000, midpoint: -0.065, scale: -0.018 } },
|
|
310
|
+
h: { alpha: { type: 'exp', rate: 70, midpoint: -0.065, scale: -0.020 }, beta: { type: 'sigmoid', rate: 1000, midpoint: -0.035, scale: 0.010 } },
|
|
311
|
+
n: { alpha: { type: 'expLinear', rate: 100, midpoint: -0.055, scale: 0.010 }, beta: { type: 'exp', rate: 125, midpoint: -0.065, scale: -0.080 } },
|
|
312
|
+
},
|
|
313
|
+
species: {
|
|
314
|
+
'Na+': { type: 'permeability', P: 1.16e-6, gates: { m: 3, h: 1 } },
|
|
315
|
+
'K+': { type: 'permeability', P: 1.33e-6, gates: { n: 4 } },
|
|
316
|
+
'Cl-': { type: 'permeability', P: 6.4e-9 }, // the leak
|
|
317
|
+
'A-': 'blocked',
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The gates scale permeabilities and nothing else: they hold no charge (the small gating current
|
|
323
|
+
of the channels' voltage sensors is left out, as Hodgkin and Huxley left it out), and each ion
|
|
324
|
+
still crosses by electrodiffusion down its own $`\bar\mu`$, so the dissipation stays positive whatever
|
|
325
|
+
they do. In steady state each gate is at $`\alpha/(\alpha + \beta)`$; each solution reports them as
|
|
326
|
+
`interfaces[f].gates` (`{ m, h, n }`) with the voltage they follow, `interfaces[f].V`.
|
|
327
|
+
|
|
328
|
+
Two things differ from the textbook equations. The currents are GHK's, which rectify: a channel's
|
|
329
|
+
conductance depends on the voltage, so a permeability matches a conductance $`g`$ at one voltage
|
|
330
|
+
only (above, the chord conductances at rest, −65 mV, for Hodgkin and Huxley's 120, 36 and 0.3
|
|
331
|
+
mS/cm²; matched at the reversal potentials instead, the rest depolarises itself and the axon
|
|
332
|
+
fires on its own). And a cell without a pump has no resting steady state: `solve()` finds its
|
|
333
|
+
Donnan equilibrium, every permeant ion level across the membrane, which the real cell reaches
|
|
334
|
+
only over hours. Start a transient from the concentrations given (it settles to rest within
|
|
335
|
+
milliseconds), or keep the gradients with the Na⁺/K⁺ pump on the same face. Started from the
|
|
336
|
+
same state, a squid axon's action potential follows the space-clamped Hodgkin–Huxley equations
|
|
337
|
+
with GHK currents to within 1 mV; and in small signals, with the membrane held at rest between
|
|
338
|
+
two baths, `impedance()` gives the gates' inductance that Cole measured (the impedance inductive
|
|
339
|
+
below ~50 Hz and resonant near 60 Hz), the linearised equations' to 5e-4
|
|
340
|
+
([`gates`](../test/gates.test.js) test).
|
|
341
|
+
|
|
280
342
|
### Faces next to a conductor
|
|
281
343
|
|
|
282
344
|
A conductor has no $`\phi`$, so a face beside it can't take a `dipole` or `step` alignment. Its $`\phi`$
|
|
@@ -432,6 +494,7 @@ any combination:
|
|
|
432
494
|
| `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
495
|
| `surface` | [species on that electrode's sites](#an-electrodes-surface-adsorbates-and-passive-films) | adsorbates, a passive film |
|
|
434
496
|
| `capacitance`, `area` | [charge held across a capacitance](#a-capacitance-through-a-window) | a gate along a channel, a double layer |
|
|
497
|
+
| `gates` | [voltage-gated channels](#a-membrane-through-a-window-gated-channels) on its conductance links | an axon's membrane along it |
|
|
435
498
|
|
|
436
499
|
```js nocheck
|
|
437
500
|
ports: [{
|
|
@@ -594,10 +657,20 @@ ports: [{ name: 'gate', region: 'channel', V: 1.5, area: 1 / 10e-9, capacitance:
|
|
|
594
657
|
ports: [{ name: 'iron', region: 'film', terminal: 'e-', V: 0, area: 1e4, capacitance: { C: 0.2, zeroCharge: -0.4 }, reactions: [/* … */] }]
|
|
595
658
|
```
|
|
596
659
|
|
|
660
|
+
A volumetric capacitance $`C^*`$ (F/m³, an organic electrochemical transistor's) goes in per
|
|
661
|
+
area of a nominal electrode: `area: 1 / t, capacitance: { C: Cstar * t }` for a film t thick
|
|
662
|
+
(`area: 1, C: Cstar` is the same physics, but reads to `describe()` like a unit slip). To read the
|
|
663
|
+
gate's voltage as the film's effective one, put the undisturbed film at φ = 0 (its carrier's
|
|
664
|
+
`cRef` at its doping, `mu0: 0`) and leave `zeroCharge` at 0. The gate charges the carriers
|
|
665
|
+
through their own chemical capacitance too, in series, so $`dQ/dV_G`$ comes out below $`C^*`$
|
|
666
|
+
(about 12% for holes at 1e20 cm⁻³ against 40 F/cm³).
|
|
667
|
+
|
|
597
668
|
That's the gradual-channel approximation of a field-effect transistor, exact for a thin film
|
|
598
669
|
with no body (a TFT, an organic or oxide transistor) while the channel is long against the gate
|
|
599
670
|
dielectric, and failing where it pinches off (where the 2D field takes over). It's also the
|
|
600
|
-
Bernards model of an organic electrochemical transistor (a volumetric capacitance
|
|
671
|
+
Bernards model of an organic electrochemical transistor (a volumetric capacitance; the
|
|
672
|
+
Bernards–Malliaras current is the charge-sheet formula below without its $`V_T`$ term, so they
|
|
673
|
+
part near pinch-off, where driftlet keeps the diffusion that formula drops), cable theory
|
|
601
674
|
for an axon's membrane, and a porous electrode's double layer. The gate's flat level pins the
|
|
602
675
|
channel's standard level through C, as a blocked spectator's flat level does in an electrolyte,
|
|
603
676
|
and the current saturates as the carriers run out at the drain, in both.
|
|
@@ -608,6 +681,44 @@ below threshold through saturation (second order in the grid); a porous electrod
|
|
|
608
681
|
impedance $`\sqrt{r/y}\coth(L\sqrt{ry})`$, the double layer in series with the ions' chemical
|
|
609
682
|
capacitance; and the gate's low-frequency impedance against $`dQ/dV_G`$ from steady states.
|
|
610
683
|
|
|
684
|
+
### A membrane through a window: gated channels
|
|
685
|
+
|
|
686
|
+
A port with a capacitance can carry [gates](#voltage-gated-channels), as a face can: at each node of
|
|
687
|
+
its window, each gate is a fraction open with Hodgkin–Huxley kinetics in the voltage across the
|
|
688
|
+
port's capacitance there, $`V_m = \phi - (V - \mathtt{zeroCharge})`$, and its conductance links name
|
|
689
|
+
the gates that scale them (`G × m³h`). That's a membrane all along a region, between it and an
|
|
690
|
+
outside held by the port: an axon's, with x running along the axon. Per volume of axoplasm, a
|
|
691
|
+
cylinder of radius a has $`2/a`$ of membrane (`area`); held at `V: 0` with `zeroCharge: 0`, the port's
|
|
692
|
+
V is the outside's $`\phi`$, and a charged species' `offset` is its level there against that,
|
|
693
|
+
$`(\mu^\circ + RT\ln(c_{out}/c_{\mathrm{ref}}))/(zF)`$. A conductance link's current is then
|
|
694
|
+
$`G\,(V_{i,out} - V_i)`$, which is $`g\,a_m (E_i - V_m)`$: Hodgkin and Huxley's linear channel, with $`E_i`$ the
|
|
695
|
+
Nernst potential at the inside's concentration there.
|
|
696
|
+
|
|
697
|
+
```js nocheck
|
|
698
|
+
ports: [{
|
|
699
|
+
name: 'membrane', region: 'axon', V: 0, terminal: 'K+', area: 2 / 238e-6, // a squid giant axon
|
|
700
|
+
capacitance: { C: 0.01, zeroCharge: 0 },
|
|
701
|
+
gates: hodgkinHuxley({ /* … */ T }).gates,
|
|
702
|
+
species: {
|
|
703
|
+
'Na+': { type: 'conductance', G: 1200 * 2 / 238e-6, offset: offsetNa, gates: { m: 3, h: 1 } },
|
|
704
|
+
'K+': { type: 'conductance', G: 360 * 2 / 238e-6, offset: offsetK, gates: { n: 4 } },
|
|
705
|
+
'Cl-': { type: 'conductance', G: 3 * 2 / 238e-6, offset: offsetCl }, // the leak
|
|
706
|
+
},
|
|
707
|
+
}]
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
With the axoplasm strictly neutral and the outside held (no resistance outside), the ions'
|
|
711
|
+
drift along x and the membrane's charging are cable theory, without its being put in: a squid
|
|
712
|
+
axon's action potential propagates at Hodgkin and Huxley's speed, 18.73 m/s for their 18.5 °C
|
|
713
|
+
axon (they computed 18.8 by hand), the cable equation's own to 2e-3
|
|
714
|
+
([`cable`](../test/cable.test.js) test). A myelinated axon is regions of one axoplasm, each node
|
|
715
|
+
of Ranvier and each internode its own, with a port each: the nodes' gated, the internodes' a small
|
|
716
|
+
capacitance (myelin) and nothing else. The grid refines at every node's faces, and the spike jumps
|
|
717
|
+
node to node, arriving at each within 1 µs of a compartmental cable's prediction (the same test).
|
|
718
|
+
Each solution's `ports[k]` reports the gates at each node of the window, `gates[name][j]`,
|
|
719
|
+
and the voltage they follow, `Vm[j]`. A gated window can't overlap another port's gated or
|
|
720
|
+
surfaced one.
|
|
721
|
+
|
|
611
722
|
### What a solution reports
|
|
612
723
|
|
|
613
724
|
Each solution's `ports[k]`, as the port has them:
|
|
@@ -620,6 +731,7 @@ Each solution's `ports[k]`, as the port has them:
|
|
|
620
731
|
| `rates[q][j]` | reaction q's forward rate at node j, mol/(m²·s) of electrode (reaction first, then node) |
|
|
621
732
|
| `coverage[name][j]`, `bare[j]` | with a surface: each species' coverage θ, and the bare fraction θ₀ to full precision |
|
|
622
733
|
| `sigma[j]`, `charge` | with a capacitance: σ per area of electrode (C/m²), and the total the port's side holds |
|
|
734
|
+
| `gates[name][j]`, `Vm[j]` | with gates: each gate's fraction open, and the voltage across the capacitance that they follow (V) |
|
|
623
735
|
|
|
624
736
|
The arrays are typed (`Float64Array`), so `Array.from` them before mapping to anything but
|
|
625
737
|
numbers. The kit's [`polarization()`](kit.md#polarization-curves-an-evans-diagram) gives each reaction's rate at a spot of the
|
|
@@ -636,7 +748,7 @@ contacts: {
|
|
|
636
748
|
right: { I: -2, ... }, // driven by a current into the device, A/m² (I: 0 is open circuit)
|
|
637
749
|
},
|
|
638
750
|
ports: [{ name: 'ref', I: 0, ... }, // a reference electrode: no current, its voltage read off
|
|
639
|
-
{ name: 'wire', V: 0, R: 1e-3, ... }], // a source V behind a series resistance R (Ω·m
|
|
751
|
+
{ name: 'wire', V: 0, R: 1e-3, ... }], // a source V behind a series resistance R (Ω·m², or Ω with a geometry)
|
|
640
752
|
```
|
|
641
753
|
|
|
642
754
|
- **`V`**: the terminal's voltage, the shift of its outside phase's ladder, as the voltage of
|
|
@@ -703,7 +815,8 @@ its middle, and nothing passes the end. Only an end may have $`A = 0`$. Flow (`v
|
|
|
703
815
|
planar devices.
|
|
704
816
|
|
|
705
817
|
With a cross-section, the solution's currents, contact and port fluxes, terminal currents and
|
|
706
|
-
`charge` are totals (A, mol/s; per metre for cylinders), and so are `I` drives
|
|
818
|
+
`charge` are totals (A, mol/s; per metre for cylinders), and so are `I` drives, a series `R`
|
|
819
|
+
and the impedance (Ω, not Ω·m²). Face laws,
|
|
707
820
|
rates, `interfaces[f].N` and `D`, and concentrations stay per area or per volume. Validated against
|
|
708
821
|
steady diffusion to a sphere and a cylinder (exact), Cottrell's transient with the spherical term,
|
|
709
822
|
uptake by a sphere filling from its surface, and Debye–Hückel screening around a charged sphere
|
|
@@ -715,6 +828,7 @@ uptake by a sphere filling from its surface, and Debye–Hückel screening aroun
|
|
|
715
828
|
const dev = new Device(def);
|
|
716
829
|
const sol = dev.solve(); // steady state (or equilibrium)
|
|
717
830
|
dev.set({ contacts: { right: { V: 0.3 } } }); // deep-merged change; the state is kept as a warm start
|
|
831
|
+
dev.set({ ports: { gate: { V: 0.2 } } }); // ports patched by name (an array replaces them all)
|
|
718
832
|
const sol2 = dev.solve();
|
|
719
833
|
const tr = dev.step(1e-6); // one backward-Euler step (auto-subdivided if needed)
|
|
720
834
|
const tr2 = dev.step(1e-6, { method: 'bdf2' }); // second-order BDF2 once a previous step exists
|
|
@@ -729,22 +843,30 @@ const now = dev.solution(); // snapshot of the current state
|
|
|
729
843
|
amounts (blocked species, reactive moieties) are kept exactly. If a direct solve fails at a
|
|
730
844
|
bias (a cold start far from equilibrium), it solves with both terminals level and ramps the
|
|
731
845
|
right terminal's voltage to its target; with generation reactions (below), it ramps their
|
|
732
|
-
rates up from nearly nothing.
|
|
846
|
+
rates up from nearly nothing. A terminal driven by a current (open circuit, say) is held at a
|
|
847
|
+
voltage instead, the voltage marched until the current crosses its target, and floated from
|
|
848
|
+
there. If 20 V either way brings no crossing (a current beyond the limiting one), the solve
|
|
849
|
+
fails with a warning giving the range of currents the held voltages passed.
|
|
733
850
|
- `step(dt, { method })` advances the transient by dt seconds, halving internally where Newton
|
|
734
851
|
needs it. `method` is `'be'` (backward Euler, the default) or `'bdf2'`.
|
|
735
852
|
- `advance(tEnd, opts)` integrates adaptively to `tEnd` with variable-step BDF2, controlling
|
|
736
853
|
the local error per step to `tol` (default 1e-3) in thermal units of every potential ($`\phi`$ and
|
|
737
|
-
each $`\bar\mu/RT`$): roughly 0.1% in concentrations.
|
|
854
|
+
each $`\bar\mu/RT`$): roughly 0.1% in concentrations per step. That's local: over a long decay
|
|
855
|
+
the errors add up, and a small current at the end of one can be tens of percent off, so tighten
|
|
856
|
+
`tol` (1e-5, say) before fitting a time constant to a tail. It lands exactly on `tEnd`. Options: `tol`,
|
|
738
857
|
`dt0` (first step), `dtMax`, `budgetMs` (return after this much wall time, with
|
|
739
858
|
`done: false`), `maxSteps`, `method`. The step size carries over between calls, so an
|
|
740
859
|
animation can call `advance(tNext, { budgetMs })` once per frame. The solution adds `done`,
|
|
741
860
|
`rejected`, and a `trace` of terminal current and voltage after every accepted step (the
|
|
742
|
-
|
|
861
|
+
current is the right contact's, toward +x, as `sol.current`; a port's is in `sol.terminals`
|
|
862
|
+
after each call; the voltage is the right contact's minus the left's: what a meter between the terminals reads,
|
|
743
863
|
which at a bath is its reference species' level, not $`\phi`$). With
|
|
744
864
|
`probes: [{ x, species, quantity, region }]` the trace also reads inside the device,
|
|
745
865
|
as `trace.probes[k]` beside `trace.t`: a species' concentration (`quantity: 'c'`, the default,
|
|
746
866
|
mol/m³) or species voltage (`'V'`), or $`\phi`$ itself (`'phi'`, no species), linearly between the
|
|
747
|
-
nodes around `x` (at an interface, `region` picks the side). That's what a detector at `x` sees.
|
|
867
|
+
nodes around `x` (at an interface, `region` picks the side). That's what a detector at `x` sees.
|
|
868
|
+
At a face, `{ interface: f, species }` reads a species' flux through it (mol/(m²·s), toward +x,
|
|
869
|
+
as `interfaces[f].N`), and `{ interface: f, gate }` a gate's fraction open. Probes read at accepted steps, which grow
|
|
748
870
|
as a transient slows, so give `dtMax` to resolve a signal in time.
|
|
749
871
|
- `impedance(frequencies, { terminal, profiles })` solves the steady state, then linearises about
|
|
750
872
|
it: $`Z(f) = \delta V/\delta I`$ in Ω·m² at one terminal (`'right'` by default), with I into the
|
package/docs/kit.md
CHANGED
|
@@ -142,6 +142,18 @@ Concentrations are held, so these are the curves of that instant (for a mixed po
|
|
|
142
142
|
kinetic picture). Away from the actual level the composition beside the electrode would change,
|
|
143
143
|
which is the transport the full solve accounts for.
|
|
144
144
|
|
|
145
|
+
## Channels
|
|
146
|
+
|
|
147
|
+
**`hodgkinHuxley({ inside, outside, T, g, leak, leakZ, at, names })`**: Hodgkin and Huxley's
|
|
148
|
+
squid-axon channels for a [membrane face](device.md#voltage-gated-channels) with the outside on its
|
|
149
|
+
left: `{ gates, species }`, the face's `gates` (m, h and n, their 1952 rates at 6.3 °C scaled to
|
|
150
|
+
`T` by Q₁₀ = 3) and links for Na⁺ (P·m³h), K⁺ (P·n⁴) and the leak (one ion, `leak`, of charge
|
|
151
|
+
`leakZ`: Cl⁻ by default). The currents are GHK's, which rectify, so each permeability is the one
|
|
152
|
+
whose chord conductance at `at` (−65 mV) is HH's `g` (120, 36 and 0.3 mS/cm²) at the
|
|
153
|
+
concentrations given. Spread it into the face beside its $`\phi`$ law, adding the other species'
|
|
154
|
+
links: `{ phi: { type: 'capacitive', C: 0.01 }, gates: hh.gates, species: { ...hh.species, 'A-': 'blocked' } }`.
|
|
155
|
+
`ghkCurrent(z, inside, outside, V, T)` is the GHK current (outward, A/m²) at unit permeability.
|
|
156
|
+
|
|
145
157
|
## Sources
|
|
146
158
|
|
|
147
159
|
Helpers that write sources as plain data:
|