driftlet 0.16.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/docs/device.md +29 -11
- package/docs/kit.md +10 -1
- package/docs/numerics.md +68 -12
- package/docs/statistics.md +2 -1
- package/docs/templates.md +260 -0
- package/llms.txt +107 -299
- package/package.json +15 -3
- package/src/blockTridiagonal.js +73 -1
- package/src/channels.js +6 -4
- package/src/checks.js +78 -3
- package/src/data.js +5 -1
- package/src/device.js +1 -1
- package/src/devices.js +2 -0
- package/src/equation.js +2 -1
- package/src/errors.js +33 -0
- package/src/grid.js +12 -0
- package/src/index.js +21 -10
- package/src/junction.js +3 -1
- package/src/kit.js +1 -1
- package/src/live.js +2 -0
- package/src/particles.js +2 -0
- package/src/plot.js +4 -0
- package/src/polarization.js +2 -1
- package/src/recorder.js +2 -1
- package/src/solution.js +39 -5
- package/src/solver.js +248 -30
- package/src/sources.js +8 -1
- package/src/stack.js +14 -1
- package/src/statistics.js +1 -1
- package/src/traces.js +3 -0
- package/src/types.js +3 -1
- package/types/blockTridiagonal.d.ts +1 -0
- package/types/channels.d.ts +3 -3
- package/types/checks.d.ts +9 -3
- package/types/data.d.ts +3 -3
- package/types/devices.d.ts +1 -1
- package/types/equation.d.ts +1 -1
- package/types/errors.d.ts +8 -0
- package/types/index.d.ts +4 -7
- package/types/junction.d.ts +2 -2
- package/types/kit.d.ts +1 -1
- package/types/live.d.ts +1 -1
- package/types/particles.d.ts +1 -1
- package/types/plot.d.ts +3 -3
- package/types/polarization.d.ts +1 -1
- package/types/recorder.d.ts +1 -1
- package/types/solver.d.ts +8 -1
- package/types/sources.d.ts +7 -7
- package/types/traces.d.ts +1 -1
- package/types/types.d.ts +9 -1
package/llms.txt
CHANGED
|
@@ -12,12 +12,14 @@ Every code block below runs in the test suite and prints its closed-form check.
|
|
|
12
12
|
two sections, then go to the template nearest your device:
|
|
13
13
|
|
|
14
14
|
- semiconductors: a pn junction (with an I–V sweep), a live page with a slider, a solar cell, a
|
|
15
|
-
MOS capacitor, a
|
|
16
|
-
Transients);
|
|
15
|
+
MOS capacitor, a pulse drifting along a bar (under Transients);
|
|
17
16
|
- electrochemistry: a cell, an electrode in a stirred solution (a potential step, and a cyclic
|
|
18
|
-
voltammogram),
|
|
19
|
-
- physiology: a cell membrane (a resting potential)
|
|
20
|
-
|
|
17
|
+
voltammogram), a battery particle discharging;
|
|
18
|
+
- physiology: a cell membrane (a resting potential).
|
|
19
|
+
|
|
20
|
+
More in docs/templates.md: a Schottky diode, a thin-film transistor, corrosion under a drop (an
|
|
21
|
+
electrode spread through a film), a voltage clamp, an action potential, one travelling along an
|
|
22
|
+
axon.
|
|
21
23
|
|
|
22
24
|
The references are in docs/: device.md (the definition), kit.md (the helpers), conventions.md
|
|
23
25
|
(signs and levels), visualization.md, alignment.md (band offsets), statistics.md (non-ideal
|
|
@@ -28,9 +30,9 @@ materials, insertion hosts), data.md and reliability.md (what's validated, and t
|
|
|
28
30
|
From a CDN, pin the version you tested against:
|
|
29
31
|
|
|
30
32
|
```js nocheck
|
|
31
|
-
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
32
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
33
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
33
|
+
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/index.js';
|
|
34
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/kit.js';
|
|
35
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/plot.js';
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`. Every export is listed at the end.
|
|
@@ -58,11 +60,11 @@ From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`. Every export is listed at
|
|
|
58
60
|
- **What isn't defaulted**: an alignment between different materials, a φ law where a metal
|
|
59
61
|
meets a dielectric, and the amount of anything a contact doesn't feed (`c0`). The error names
|
|
60
62
|
the path and says what to give. (Kinetic details are defaulted: listed in docs/device.md.)
|
|
61
|
-
- **Check before trusting.** `sol.converged` must be true; read `sol.warnings`, where a
|
|
62
|
-
solve always says why. `describe(def)` (kit
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
a
|
|
63
|
+
- **Check before trusting, early.** `sol.converged` must be true; read `sol.warnings`, where a
|
|
64
|
+
failed solve always says why. Before comparing with theory, log `describe(def)` (kit: a summary
|
|
65
|
+
that flags unit slips) and `check(dev, sol).text` (kit): each species' ledger, conservation in
|
|
66
|
+
transients, a grid twice as fine (a FAIL means refine) and the species listed in reverse (a FAIL
|
|
67
|
+
means a solve converged somewhere that isn't steady: compare a long advance()).
|
|
66
68
|
|
|
67
69
|
## How a device is written
|
|
68
70
|
|
|
@@ -96,7 +98,9 @@ From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`. Every export is listed at
|
|
|
96
98
|
(`capacitance`, `gates`). A BJT's base contact is a port on a narrow window of the base,
|
|
97
99
|
`{ region: 'base', from, to, V, terminal: 'h+', species: { 'h+': 'equilibrium' } }`.
|
|
98
100
|
- **Drives.** A terminal is held at a voltage `V`, driven by a current `I` (open circuit is
|
|
99
|
-
`I: 0`), or behind a resistance `{ V, R }
|
|
101
|
+
`I: 0`), or behind a resistance `{ V, R }`; in `build()`, the same object goes where the
|
|
102
|
+
voltage would (`ohmic({ I: 0 }, ['e-'])`, `ohmic({ V: 1, R: 1e3 }, ['e-'])`), and `set()` may
|
|
103
|
+
switch one kind for another (`{ I: 0 }` drops a held `V`). Either can be a waveform: a table `{ t, values }`
|
|
100
104
|
(piecewise linear; a step is two points at one time), or the kit's `pulse({ start, width,
|
|
101
105
|
amplitude, rise, base })`, `square({ high, low, period, duty })`, `ramp({ from, to, duration,
|
|
102
106
|
start })` or `triangle({ from, to, period })` (out and back each period from t = 0: a cyclic
|
|
@@ -112,14 +116,17 @@ From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`. Every export is listed at
|
|
|
112
116
|
{ tauN, tauP } }` in place of `kf` (s), or at a face `{ equation: 'e-(left) + h+(right) = 0',
|
|
113
117
|
srh: { vn, vp } }` (m/s); with τ_N ≪ τ_p it gives n-type silicon's holes a lifetime τ_p at any
|
|
114
118
|
injection. Light is a reaction from a reservoir (`photon = e- + h+`, uniform), or absorbed as
|
|
115
|
-
it goes in, `photogeneration({ material, flux, alpha, mu: units.eV(3), from, to })
|
|
116
|
-
|
|
119
|
+
it goes in, `photogeneration({ material, flux, alpha, mu: units.eV(3), from, to })`, `from` and
|
|
120
|
+
`to` being x in the device (a port's window is instead m from its region's left end). To switch
|
|
121
|
+
it during a run, `set()` the device's whole `bulkReactions` list, recombination included: in
|
|
122
|
+
`set()`, a list replaces and an object merges (the state and the step size carry over).
|
|
117
123
|
- **Electrode kinetics.** A face reaction's `k0` (mol/(m²·s)) is its rate with every participant
|
|
118
124
|
at cRef: i₀ = nF·k0 there, and k0 = k°·cRef for a textbook k° in m/s (1 cm/s at 1 M is
|
|
119
125
|
`k0: 10`).
|
|
120
126
|
- **Grids** grade geometrically from `hmin` at each region's ends up to `hmax`, neighbouring cells
|
|
121
127
|
differing by at most `ratio` (1.2): put `hmin` below the shortest length that matters (a Debye
|
|
122
|
-
length, a depletion edge). `
|
|
128
|
+
length, a depletion edge). Left out, `grid` is a coarse default for a first look; `check()`
|
|
129
|
+
says whether a grid is fine enough.
|
|
123
130
|
|
|
124
131
|
`build()` is the usual way in. It writes a plain definition for `new Device()`, which some
|
|
125
132
|
templates below write by hand (where a port spans a region, or no stack fits):
|
|
@@ -202,9 +209,9 @@ a CDN needs a long virtual-time budget, 30 s.)
|
|
|
202
209
|
<span id="readout"></span>
|
|
203
210
|
<div id="figure"></div>
|
|
204
211
|
<script type="module">
|
|
205
|
-
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
206
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
207
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
212
|
+
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/index.js';
|
|
213
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/kit.js';
|
|
214
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.0/src/plot.js';
|
|
208
215
|
|
|
209
216
|
const def = build({
|
|
210
217
|
T: 300,
|
|
@@ -228,22 +235,17 @@ To draw in your own style, `traces(sol, opts)` (kit, the same options) gives the
|
|
|
228
235
|
see "Reading level diagrams" below.
|
|
229
236
|
|
|
230
237
|
For an explainer, `particles(sol, { weight, cap })` (kit) draws a solution as dots, each `weight`
|
|
231
|
-
mol/m² of a species, hopping at
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
`swarm.step(dt)` in the device's time (about h²/D per hop, h a cell), then draw `swarm.dots`
|
|
236
|
-
(`{ species, x }`) and `swarm.events` (`'unmade'` is a recombination, `'cross'` an ion through a
|
|
237
|
-
membrane between two seas); on a new steady solution, `swarm.update(sol, { resample: true })`.
|
|
238
|
-
An ion beside a cell membrane hops ~1e6 times per crossing, so a membrane page shows seas and
|
|
239
|
-
crossing events, not walking ions. The demos' page scripts do this for a diode's carriers (pn),
|
|
240
|
-
ions crossing a nerve membrane (axon, `cap: 0`) and Donnan exclusion (membrane).
|
|
238
|
+
mol/m² of a species, hopping at the solution's rates, so their density and net crossings agree
|
|
239
|
+
with it on average; say on the page that they sample the continuum solution. A cell over `cap`
|
|
240
|
+
dots is a sea, shaded by `expected(name)`. Each frame, `swarm.step(dt)`, then draw `swarm.dots`
|
|
241
|
+
and `swarm.events` (docs/kit.md; the pn, axon and membrane demos are worked examples).
|
|
241
242
|
|
|
242
243
|
## Template: a solar cell
|
|
243
244
|
|
|
244
245
|
`photon = e- + h+`, with the photons' μ well above the gap, generates uniformly at the rate `kf`.
|
|
245
246
|
Open circuit is `I: 0`, and the terminal's V is then V_oc. The photocurrent flows toward +x
|
|
246
|
-
(holes to the p side, on the right).
|
|
247
|
+
(holes to the p side, on the right), so the power the cell delivers is V·`sol.current`. Light
|
|
248
|
+
absorbed as it goes in, for a cell's thickness to matter, is `photogeneration()` (above).
|
|
247
249
|
|
|
248
250
|
```js
|
|
249
251
|
import { Device, units } from 'driftlet';
|
|
@@ -270,14 +272,10 @@ const oc = new Device(cell({ I: 0 })).solve(); // open circuit
|
|
|
270
272
|
console.log(`J_sc = ${sc.current.toFixed(1)} A/m², V_oc = ${oc.terminals.right.V.toFixed(3)} V`);
|
|
271
273
|
```
|
|
272
274
|
|
|
273
|
-
In an organic cell, light makes excitons
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
recombination; the face's `step` sets E_CT. With `alpha: 0` the splitting is a velocity
|
|
278
|
-
k0/cRef, a Robin condition on the exciton profile; with α > 0 it's k0/cRef · e^(α(E_X − E_CT)/RT).
|
|
279
|
-
The organic demo's page script is a worked example (J_sc against exciton diffusion theory, V_oc
|
|
280
|
-
against detailed balance).
|
|
275
|
+
In an organic cell, light makes excitons, a neutral species made by `photogeneration({ ...,
|
|
276
|
+
makes: 'X' })`, which split at the donor/acceptor face by a reaction, `X = h+(left) + e-(right)`
|
|
277
|
+
(e⁻ and h⁺ blocked there; the face's `step` sets E_CT). The organic demo's page script is a
|
|
278
|
+
worked example, checked against exciton diffusion and detailed balance.
|
|
281
279
|
|
|
282
280
|
For a perovskite cell, prefer the curated `perovskiteCell({ V, light, bulkSRH })`: IonMonger's
|
|
283
281
|
default cell (mobile iodide vacancies), with its J–V scans at seven rates in `PEROVSKITE_SCANS`
|
|
@@ -334,73 +332,6 @@ the same C at any frequency with the port; without it, only below the minority s
|
|
|
334
332
|
above. Draw it with
|
|
335
333
|
`bandDiagram(sol, { energy: true, phi: ['SiO2'] })`: across an insulator φ is the line to draw.
|
|
336
334
|
|
|
337
|
-
## Template: a Schottky diode
|
|
338
|
-
|
|
339
|
-
A barrier pinned at the metal (`phi: 'pinned'`, `zeroCharge` the barrier height for Si, whose
|
|
340
|
-
μ°_e⁻ is 0), crossed at the emission velocity: a `velocity` link, N_in = v (c_eq − c). With
|
|
341
|
-
`'equilibrium'` links (infinite velocity) it's diffusion theory instead, 20 times the current here.
|
|
342
|
-
|
|
343
|
-
```js
|
|
344
|
-
import { Device, units, FARADAY, GAS_CONSTANT } from 'driftlet';
|
|
345
|
-
import { build, layer, ohmic, semiconductor } from 'driftlet/kit';
|
|
346
|
-
|
|
347
|
-
const T = 300, VT = (GAS_CONSTANT * T) / FARADAY, Si = semiconductor('Si');
|
|
348
|
-
const phiB = 0.85, A = 1.12e6; // the barrier (V); Richardson's constant for n-Si, A/(m²K²)
|
|
349
|
-
const v = (A * T * T) / (FARADAY * Si.materials.Si.species['e-'].cRef); // Bethe's emission velocity, 2.2e4 m/s (cRef is N_c)
|
|
350
|
-
const schottky = (V) => ({
|
|
351
|
-
V,
|
|
352
|
-
terminal: 'e-',
|
|
353
|
-
species: { 'e-': { type: 'velocity', v }, 'h+': { type: 'velocity', v, offset: 0 } },
|
|
354
|
-
phi: 'pinned',
|
|
355
|
-
zeroCharge: phiB, // E_c − E_F at the metal
|
|
356
|
-
});
|
|
357
|
-
const dev = new Device(build({ T, library: [Si], stack: [schottky(0), layer('Si', units.um(2), { donors: units.perCm3(1e16) }), ohmic(0)], grid: { hmin: units.nm(1), hmax: units.nm(20) } }));
|
|
358
|
-
for (const V of [0.1, 0.2, 0.3]) {
|
|
359
|
-
dev.set({ contacts: { left: { V } } }); // the metal positive: forward
|
|
360
|
-
const J = dev.solve().current, te = A * T * T * Math.exp(-phiB / VT) * Math.expm1(V / VT);
|
|
361
|
-
console.log(`${V} V: J ${J.toExponential(3)} A/m², thermionic emission ${te.toExponential(3)}`); // about 4% below: the depletion region in series
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
The same `velocity` link at an ohmic contact is its surface recombination velocity.
|
|
366
|
-
|
|
367
|
-
## Template: a thin-film transistor (a capacitance along a channel)
|
|
368
|
-
|
|
369
|
-
A **port with a `capacitance`** holds charge σ = C (V − zeroCharge − φ) per area of electrode
|
|
370
|
-
along its window, balanced by the region's: a gate along a channel, a porous electrode's double
|
|
371
|
-
layer. Here x runs from source to drain with the gate spread along it: the gradual-channel
|
|
372
|
-
approximation, exact for a thin film until it pinches off.
|
|
373
|
-
|
|
374
|
-
```js
|
|
375
|
-
import { Device, GAS_CONSTANT, FARADAY } from 'driftlet';
|
|
376
|
-
|
|
377
|
-
const T = 300, VT = (GAS_CONSTANT * T) / FARADAY;
|
|
378
|
-
const t = 20e-9, L = 10e-6, W = 100e-6, C = 1e-3, mu = 1e-3; // film, length, width (m); F/m² of gate; m²/(V·s)
|
|
379
|
-
const tft = (Vg, Vd) =>
|
|
380
|
-
new Device({
|
|
381
|
-
T,
|
|
382
|
-
species: [{ name: 'e-', z: -1, cRef: 46 }], // an undoped film's electrons (N_c 2.8e19 cm⁻³)
|
|
383
|
-
materials: { film: { epsr: 0, species: { 'e-': { D: mu * VT, mu0: 0 } } } }, // ε = 0: F·n·t balances σ
|
|
384
|
-
regions: [{ name: 'channel', material: 'film', length: L }],
|
|
385
|
-
contacts: {
|
|
386
|
-
left: { V: 0, terminal: 'e-', species: { 'e-': 'equilibrium' }, phi: 'neutral' }, // source
|
|
387
|
-
right: { V: Vd, terminal: 'e-', species: { 'e-': 'equilibrium' }, phi: 'neutral' }, // drain
|
|
388
|
-
},
|
|
389
|
-
ports: [{ name: 'gate', region: 'channel', V: Vg, area: 1 / t, capacitance: { C, zeroCharge: 0.3 } }],
|
|
390
|
-
grid: { hmin: L / 2000, hmax: L / 100, ratio: 1.05 }, // fine at the drain, where it pinches off
|
|
391
|
-
});
|
|
392
|
-
// The output characteristic at V_G = 2 V: linear, then saturating past V_D ≈ V_G − 0.3 V.
|
|
393
|
-
for (const Vd of [0.1, 0.5, 1, 1.5, 2, 3]) {
|
|
394
|
-
const sol = tft(2, Vd).solve();
|
|
395
|
-
const I = -sol.current * t * W; // sol.current is per m² of the film's section
|
|
396
|
-
console.log(`V_D ${Vd.toFixed(1)} V: I_D ${(I * 1e6).toFixed(2)} µA, σ at the drain ${(sol.ports[0].sigma.at(-1) * 1e3).toFixed(3)} mC/m²`);
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
To sweep the gate on one device instead, patch the port: `dev.set({ ports: { gate: { V } } })`.
|
|
401
|
-
An organic electrochemical transistor is the same with a volumetric capacitance C*:
|
|
402
|
-
`area: 1 / t, capacitance: { C: Cstar * t }`.
|
|
403
|
-
|
|
404
335
|
## Template: an electrochemical cell
|
|
405
336
|
|
|
406
337
|
An electrode is a metal layer with the reaction at its face, behind a contact holding the
|
|
@@ -486,69 +417,62 @@ for (const t of [3e-3, 1e-2, 3e-2]) {
|
|
|
486
417
|
|
|
487
418
|
A cyclic voltammogram is this electrode driven by `triangle({ from, to, period })`, the period
|
|
488
419
|
being 2|to − from| over the scan rate: `solve()` it at `from` first, since the scan starts there
|
|
489
|
-
at t = 0, then `advance()
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
420
|
+
at t = 0, then `advance()` with a `dtMax` of a millivolt's worth of sweep, or the trace's points
|
|
421
|
+
fall a few mV apart and miss the peaks. For a diffusion peak (Randles–Ševčík), the stagnant layer
|
|
422
|
+
must be well past √(D·t_scan), a few hundred µm: the template's 20 µm gives a thin-layer wave.
|
|
423
|
+
Its potential against the bath's terminal is −`trace.voltage` (that is V_right − V_left), and
|
|
424
|
+
`trace.current` is anodic positive.
|
|
425
|
+
|
|
426
|
+
## Template: a battery particle discharging
|
|
427
|
+
|
|
428
|
+
A battery's insertion electrode is a material described by its OCV curve (docs/statistics.md):
|
|
429
|
+
here one particle of a cathode, a sphere, the single-particle model. Li⁺ is held at its surface
|
|
430
|
+
by the electrolyte, and the electrons come in through a port there (a current into the device,
|
|
431
|
+
so negative to discharge a cathode). With no steady state while a current flows, it's `advance()`d
|
|
432
|
+
from its `c0`, never `solve()`d. `D` is the dilute value: an ideal host's chemical diffusivity is
|
|
433
|
+
D/(1 − x), so it fills more easily the fuller it gets. A real OCV curve with a plateau needs a
|
|
434
|
+
little of the ideal slope (the error says how much).
|
|
499
435
|
|
|
500
436
|
```js
|
|
501
|
-
import { Device,
|
|
502
|
-
import { aqueous, polarization, H2O } from 'driftlet/kit';
|
|
503
|
-
|
|
504
|
-
// Salt water on iron: a drop 3 mm across. The iron dissolves and reduces O₂, which comes in from
|
|
505
|
-
// the air through the drop's top, fastest at the thin rim: the centre corrodes, the rim doesn't.
|
|
506
|
-
const T = 298.15, RT = GAS_CONSTANT * T, R = 3e-3, h0 = 1e-3, cO2 = 0.26, D = 2e-9;
|
|
507
|
-
const lib = aqueous(['Na+', 'Cl-', 'Fe2+', 'OH-', 'H+'], { epsr: 0 }); // strictly neutral water
|
|
508
|
-
lib.species.push({ name: 'O2', z: 0, cRef: 1000 }, { name: 'e-', z: -1 }); // e⁻: the iron's, in no material
|
|
509
|
-
lib.materials.water.species.O2 = { D, mu0: 16.4e3 };
|
|
510
|
-
const r = Array.from({ length: 61 }, (_, j) => (j / 60) * R);
|
|
511
|
-
const h = r.map((x) => Math.max(h0 * (1 - (x / R) * (x / R)), 0.02 * h0)); // the drop's height
|
|
512
|
-
const dev = new Device({
|
|
513
|
-
T,
|
|
514
|
-
species: lib.species,
|
|
515
|
-
materials: lib.materials,
|
|
516
|
-
geometry: { area: { x: r, values: r.map((x, j) => 2 * Math.PI * x * h[j]) } }, // ions squeeze through the thin rim
|
|
517
|
-
regions: [{ name: 'drop', material: 'water', length: R, c0: { 'Na+': 500, 'Cl-': 500 + 2e-6, 'Fe2+': 1e-6, 'OH-': 1e-4, 'H+': 1e-4, O2: cO2 } }],
|
|
518
|
-
contacts: { left: { phi: 'neutral' }, right: { phi: 'neutral' } }, // closed: the centre, and the rim
|
|
519
|
-
bulkReactions: [
|
|
520
|
-
{ equation: 'H+ + OH- = H2O', fixed: { H2O }, kf: { water: 1.4e8 } }, // H2O: water's μ, −237.129 kJ/mol
|
|
521
|
-
// O₂ from the air through the drop: a rate k_f (1 − c/c_sat), k_f = D c_sat/h² (not a port's
|
|
522
|
-
// exchange link, whose rate is linear in μ rather than c)
|
|
523
|
-
{ equation: 'Air = O2', fixed: { Air: 16.4e3 + RT * Math.log(cO2 / 1000) }, kf: { water: { x: r, values: h.map((y) => (D * cO2) / (y * y)) } } },
|
|
524
|
-
],
|
|
525
|
-
ports: [{
|
|
526
|
-
name: 'iron', region: 'drop', terminal: 'e-',
|
|
527
|
-
V: 0, // the only metal and the only path for current, so it passes none: held, it fixes the level φ is read against
|
|
528
|
-
area: { x: r, values: h.map((y) => 1 / y) }, // m² of iron per m³ of water
|
|
529
|
-
capacitance: { C: 0.2, zeroCharge: -0.4 }, // the double layer: the potential moves continuously
|
|
530
|
-
reactions: [
|
|
531
|
-
{ equation: 'Fe2+ + 2 e- = Fe(s)', fixed: { 'Fe(s)': 0 }, k0: 5e-6, alpha: 0.5 },
|
|
532
|
-
{ equation: 'O2 + 2 H2O + 4 e- = 4 OH-', fixed: { H2O }, k0: 3e-9, alpha: 0.125 },
|
|
533
|
-
],
|
|
534
|
-
}],
|
|
535
|
-
grid: { hmin: 5e-6, hmax: 60e-6, ratio: 1.2 },
|
|
536
|
-
});
|
|
537
|
-
const sol = dev.advance(600); // ten minutes
|
|
538
|
-
const iron = sol.ports[0], last = iron.x.length - 1;
|
|
539
|
-
// rates[q][j]: reaction q at node j of the window, forward, mol/(m²·s) of iron; anodic current +
|
|
540
|
-
const anodic = (j) => 2 * FARADAY * -iron.rates[0][j] - 4 * FARADAY * iron.rates[1][j];
|
|
541
|
-
console.log(`iron at ${(iron.V - sol.phi[0]).toFixed(3)} V against the water at the centre (SHE, with table μ°)`);
|
|
542
|
-
console.log(`net current on the iron: ${anodic(0).toFixed(2)} A/m² at the centre, ${anodic(last).toFixed(2)} A/m² at the rim`);
|
|
543
|
-
// The Evans diagram at the centre: each reaction's current with the iron's level moved, the water held.
|
|
544
|
-
const levels = [-0.55, -0.5, -0.45, -0.4].map((v) => v + sol.phi[0]);
|
|
545
|
-
const evans = polarization(dev, sol, { port: 'iron', x: 0 }, levels);
|
|
546
|
-
for (const rx of evans.reactions) console.log(`${rx.equation}: ${Array.from(rx.current, (i) => i.toFixed(2)).join(', ')} A/m²`);
|
|
547
|
-
```
|
|
437
|
+
import { Device, FARADAY, GAS_CONSTANT } from 'driftlet';
|
|
548
438
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
439
|
+
const T = 298.15, VT = (GAS_CONSTANT * T) / FARADAY;
|
|
440
|
+
const R = 5e-6, D = 1e-14, cMax = 50000, x0 = 0.1; // m, m²/s, mol/m³ of sites, starting fill
|
|
441
|
+
const xs = Array.from({ length: 99 }, (_, k) => (k + 1) / 100);
|
|
442
|
+
const particle = (I) =>
|
|
443
|
+
new Device({
|
|
444
|
+
T,
|
|
445
|
+
species: [{ name: 'Li+', z: 1 }, { name: 'e-', z: -1 }],
|
|
446
|
+
materials: {
|
|
447
|
+
host: {
|
|
448
|
+
epsr: 0,
|
|
449
|
+
species: { 'Li+': { D, mu0: 0, cRef: cMax }, 'e-': { D: 1e-4, mu0: 0, cRef: cMax } },
|
|
450
|
+
statistics: [{ type: 'insertion', species: ['Li+', 'e-'], cMax, ocv: { x: xs, E: xs.map((x) => 4 - VT * Math.log(x / (1 - x))), muRef: 0 } }],
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
geometry: { type: 'spherical', r0: 0 }, // x is the radius; currents are totals (A)
|
|
454
|
+
regions: [{ name: 'p', material: 'host', length: R, c0: { 'Li+': x0 * cMax, 'e-': x0 * cMax } }],
|
|
455
|
+
contacts: {
|
|
456
|
+
left: { phi: 'neutral' }, // the centre
|
|
457
|
+
right: { V: 0, terminal: 'Li+', species: { 'Li+': 'equilibrium' }, phi: 'bulk' }, // Li⁺ at the electrolyte's level
|
|
458
|
+
},
|
|
459
|
+
ports: [{ name: 'wire', region: 'p', from: 0.98 * R, to: R, I, terminal: 'e-', species: { 'e-': { type: 'conductance', G: 1e15 } } }],
|
|
460
|
+
grid: { hmin: R / 400, hmax: R / 40 },
|
|
461
|
+
});
|
|
462
|
+
const Q = FARADAY * cMax * (4 / 3) * Math.PI * R ** 3; // C for Δx = 1
|
|
463
|
+
const xEnd = 1 / (1 + Math.exp(-0.1 / VT)); // the OCV reaches a 3.9 V cutoff at x = 0.98
|
|
464
|
+
for (const C of [0.2, 20]) {
|
|
465
|
+
const I = -(C * (xEnd - x0) * Q) / 3600; // a C-rate: the whole window in 1/C hours
|
|
466
|
+
const dev = particle(I);
|
|
467
|
+
// Halfway: in u = −ln(1 − x), whose diffusivity is D however full, a constant flux q in makes a
|
|
468
|
+
// quasi-steady parabola, surface − centre = qR/(2D c_max), once t ≫ R²/(π²D) (250 s here).
|
|
469
|
+
const half = dev.advance(1800 / C), u = (c) => -Math.log(1 - c / cMax), q = -I / (FARADAY * 4 * Math.PI * R * R);
|
|
470
|
+
const du = u(half.c['Li+'].at(-1)) - u(half.c['Li+'][0]);
|
|
471
|
+
let sol = half;
|
|
472
|
+
while (sol.converged && sol.terminals.wire.V > 3.9) sol = dev.advance(sol.time + 36 / C); // on to the cutoff, in 1% steps
|
|
473
|
+
console.log(`${C}C: ${((100 * (-I * sol.time)) / Q / (xEnd - x0)).toFixed(0)}% of the capacity; halfway, Δu ${du.toFixed(4)}, qR/(2D c_max) ${((q * R) / (2 * D * cMax)).toFixed(4)}`);
|
|
474
|
+
} // 0.2C: 100%, 0.0205 against 0.0204; 20C: 91%, and halfway (90 s) is too soon for the parabola
|
|
475
|
+
```
|
|
552
476
|
|
|
553
477
|
## Template: a cell membrane
|
|
554
478
|
|
|
@@ -599,132 +523,10 @@ console.log(check(dev, sol).text); // per compartment: Na⁺ leaks into the cyto
|
|
|
599
523
|
Between two baths instead (the inside one `bath(inside, 'Cl-', { I: 0 })`: held composition, no
|
|
600
524
|
current), V_m is GHK. Without the pump, a closed cell runs down to Donnan equilibrium.
|
|
601
525
|
|
|
602
|
-
## Template: a voltage clamp
|
|
603
|
-
|
|
604
|
-
A face's `gates` (each a fraction open, dx/dt = α(V)(1 − x) − β(V)x with V = φ_right − φ_left)
|
|
605
|
-
scale its links (`gates: { m: 3, h: 1 }`). `hodgkinHuxley({ T, linear: true })` (kit) writes the
|
|
606
|
-
squid axon's as Hodgkin and Huxley had them, conductances g(V − E). Between two baths without a
|
|
607
|
-
reference species, V_right − V_left is V_m, so a held step is their voltage clamp.
|
|
608
|
-
|
|
609
|
-
```js
|
|
610
|
-
import { Device, FARADAY, GAS_CONSTANT } from 'driftlet';
|
|
611
|
-
import { build, layer, bath, aqueous, hodgkinHuxley } from 'driftlet/kit';
|
|
612
|
-
|
|
613
|
-
const T = 279.45, VT = (GAS_CONSTANT * T) / FARADAY; // HH's 6.3 °C
|
|
614
|
-
const out = { 'Na+': 440, 'K+': 20, 'Cl-': 450, 'A-': 10 }, inside = { 'Na+': 50, 'K+': 400, 'Cl-': 52, 'A-': 398 }; // squid, mM
|
|
615
|
-
const water = aqueous(['Na+', 'K+', 'Cl-'], { epsr: 0 });
|
|
616
|
-
water.species.push({ name: 'A-', z: -1, cRef: 1000 });
|
|
617
|
-
water.materials.water.species['A-'] = { D: 0.5e-9, mu0: 0 };
|
|
618
|
-
const hh = hodgkinHuxley({ T, linear: true });
|
|
619
|
-
const step = -0.01; // from −65 mV to −10 mV at 1 ms
|
|
620
|
-
const dev = new Device(
|
|
621
|
-
build({
|
|
622
|
-
T,
|
|
623
|
-
library: [water],
|
|
624
|
-
stack: [
|
|
625
|
-
bath(out), // the outside, on the left
|
|
626
|
-
layer('water', 1e-6, { c0: out }),
|
|
627
|
-
{ phi: { type: 'capacitive', C: 0.01 }, gates: hh.gates, species: { ...hh.species, 'A-': 'blocked' } },
|
|
628
|
-
layer('water', 1e-6, { c0: inside }),
|
|
629
|
-
bath(inside, { t: [0, 1e-3, 1e-3, 1], values: [-0.065, -0.065, step, step] }),
|
|
630
|
-
],
|
|
631
|
-
grid: { hmin: 20e-9, hmax: 0.2e-6 },
|
|
632
|
-
}),
|
|
633
|
-
);
|
|
634
|
-
dev.solve(); // the rest at −65 mV, the gates at their values there
|
|
635
|
-
const run = dev.advance(11e-3, { dtMax: 0.02e-3, probes: [{ interface: 0, species: 'Na+' }, { interface: 0, species: 'K+' }] });
|
|
636
|
-
const [na, k] = run.trace.probes.map((N) => N.map((x) => -FARADAY * x)); // outward currents, A/m²
|
|
637
|
-
const peak = na.indexOf(Math.min(...na)), EK = VT * Math.log(out['K+'] / inside['K+']);
|
|
638
|
-
console.log(`I_Na peaks ${((run.trace.t[peak] - 1e-3) * 1e3).toFixed(2)} ms into the step, ${(na[peak] * 0.1).toFixed(2)} mA/cm²`); // 0.73 ms, −1.48
|
|
639
|
-
console.log(`g_K after 10 ms: ${((k.at(-1) / (step - EK)) * 0.1).toFixed(2)} mS/cm²`); // 21.12: HH's 36 n∞⁴ is 21.5, which n nearly reaches by 10 ms
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
## Template: an action potential
|
|
643
|
-
|
|
644
|
-
Without `linear: true` (given `inside` and `outside`), `hodgkinHuxley` writes GHK permeabilities
|
|
645
|
-
matched to HH's conductances at −65 mV, which rectify as real channels do. A pump-less cell has no
|
|
646
|
-
steady rest (`solve()` finds Donnan), so start the transient from the concentrations given.
|
|
647
|
-
|
|
648
|
-
```js
|
|
649
|
-
import { Device } from 'driftlet';
|
|
650
|
-
import { build, layer, bath, aqueous, injector, pulse, hodgkinHuxley } from 'driftlet/kit';
|
|
651
|
-
|
|
652
|
-
const T = 279.45; // HH's 6.3 °C
|
|
653
|
-
const out = { 'Na+': 440, 'K+': 20, 'Cl-': 450, 'A-': 10 }, inside = { 'Na+': 50, 'K+': 400, 'Cl-': 52, 'A-': 398 }; // squid, mM
|
|
654
|
-
const water = aqueous(['Na+', 'K+', 'Cl-'], { epsr: 0 });
|
|
655
|
-
water.species.push({ name: 'A-', z: -1, cRef: 1000 });
|
|
656
|
-
water.materials.water.species['A-'] = { D: 0.5e-9, mu0: 0 };
|
|
657
|
-
const hh = hodgkinHuxley({ inside, outside: out, T }); // GHK channels; outside on the left
|
|
658
|
-
const axon = new Device(
|
|
659
|
-
build({
|
|
660
|
-
T,
|
|
661
|
-
library: [water],
|
|
662
|
-
stack: [
|
|
663
|
-
bath(out, 'Cl-'),
|
|
664
|
-
layer('water', 5e-6, { name: 'outside', c0: out }),
|
|
665
|
-
{ phi: { type: 'capacitive', C: 0.01 }, gates: hh.gates, species: { ...hh.species, 'A-': 'blocked' } },
|
|
666
|
-
layer('water', 20e-6, { name: 'cytoplasm', c0: inside }),
|
|
667
|
-
{ species: { 'Na+': 'blocked', 'K+': 'blocked', 'Cl-': 'blocked', 'A-': 'blocked' }, phi: 'neutral' },
|
|
668
|
-
],
|
|
669
|
-
// the stimulus: 10 µA/cm² of K⁺ for 1 ms at 20 ms, from a pipette in the cytoplasm
|
|
670
|
-
ports: [injector({ region: 'cytoplasm', from: 15e-6, to: 20e-6, species: 'K+', I: pulse({ start: 20e-3, width: 1e-3, amplitude: 0.1 }) })],
|
|
671
|
-
grid: { hmin: 50e-9, hmax: 1e-6 },
|
|
672
|
-
}),
|
|
673
|
-
);
|
|
674
|
-
let peak = -1;
|
|
675
|
-
for (let k = 1; k <= 300; k++) {
|
|
676
|
-
const sol = axon.advance(k * 1e-4); // 0.1 ms at a time
|
|
677
|
-
peak = Math.max(peak, sol.interfaces[0].V); // V_m, and sol.interfaces[0].gates = { m, h, n }
|
|
678
|
-
}
|
|
679
|
-
console.log(`peak ${(peak * 1e3).toFixed(1)} mV`); // +12.6: GHK's Na⁺ current is limited by the 50 mM inside (linear: true gives +37.9, HH's own)
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
## Template: an action potential travelling along an axon
|
|
683
|
-
|
|
684
|
-
Here x runs along the axon: the axoplasm is one region, and the membrane a port spanning it, a
|
|
685
|
-
capacitance and gated channels to an outside given as a `bath` (the port's V is then the
|
|
686
|
-
outside's φ). `hodgkinHuxley({ T, area })` writes HH's channels for it, `area` being the membrane
|
|
687
|
-
per volume, 2/a. Cable theory isn't put in; it comes out of the ions' drift. `ports[k].Vm[j]` and
|
|
688
|
-
`ports[k].gates[name][j]` give the membrane at each node. A myelinated axon is regions, each with
|
|
689
|
-
its own port (an internode's just a small capacitance). Nothing warns of a grid too coarse for
|
|
690
|
-
the spike's front: the speed comes out 3% low on 2 mm cells, 0.2% on 0.5 mm, for a = 238 µm
|
|
691
|
-
(scale h with √a). The stimulus is a current per volume, so a thinner axon needs it as 1/a.
|
|
692
|
-
|
|
693
|
-
```js
|
|
694
|
-
import { Device, FARADAY, GAS_CONSTANT } from 'driftlet';
|
|
695
|
-
import { hodgkinHuxley, injector, pulse } from 'driftlet/kit';
|
|
696
|
-
|
|
697
|
-
const T = 291.65, VT = (GAS_CONSTANT * T) / FARADAY, a = 238e-6; // 18.5 °C, a squid giant axon
|
|
698
|
-
const inside = { 'Na+': 50, 'K+': 400, 'Cl-': 52.5, 'A-': 397.5 }; // mM
|
|
699
|
-
// Outside levels that put the Nernst potentials at HH's +50, −77 and −54.4 mV (leak as Cl⁻).
|
|
700
|
-
const outside = { 'Na+': 50 * Math.exp(0.05 / VT), 'K+': 400 * Math.exp(-0.077 / VT), 'Cl-': 52.5 * Math.exp(0.054387 / VT) };
|
|
701
|
-
// One D for the small ions, giving HH's 35.4 Ω·cm.
|
|
702
|
-
const D = 1 / 0.354 / ((FARADAY * FARADAY) / (GAS_CONSTANT * T)) / (50 + 400 + 52.5);
|
|
703
|
-
const sp = (d) => ({ D: d, mu0: 0, cRef: 1000 });
|
|
704
|
-
const sealed = { species: { 'Na+': 'blocked', 'K+': 'blocked', 'Cl-': 'blocked', 'A-': 'blocked' }, phi: 'neutral' };
|
|
705
|
-
const hh = hodgkinHuxley({ T, area: 2 / a });
|
|
706
|
-
const axon = new Device({
|
|
707
|
-
T,
|
|
708
|
-
species: [{ name: 'Na+', z: 1 }, { name: 'K+', z: 1 }, { name: 'Cl-', z: -1 }, { name: 'A-', z: -1 }],
|
|
709
|
-
materials: { axoplasm: { epsr: 0, species: { 'Na+': sp(D), 'K+': sp(D), 'Cl-': sp(D), 'A-': sp(1e-12) } } },
|
|
710
|
-
regions: [{ name: 'axon', material: 'axoplasm', length: 0.06, c0: inside }],
|
|
711
|
-
contacts: { left: sealed, right: sealed },
|
|
712
|
-
ports: [
|
|
713
|
-
{ name: 'membrane', region: 'axon', V: 0, area: 2 / a, capacitance: { C: 0.01, zeroCharge: 0 }, bath: { c: outside }, gates: hh.gates, species: hh.species },
|
|
714
|
-
injector({ name: 'stim', region: 'axon', from: 0, to: 1e-3, species: 'K+', I: pulse({ start: 30e-3, width: 0.2e-3, amplitude: 30 }) }),
|
|
715
|
-
],
|
|
716
|
-
grid: { hmin: 2e-4, hmax: 2e-4 },
|
|
717
|
-
});
|
|
718
|
-
axon.advance(30e-3); // settle to rest (no pump: start from the concentrations, not solve())
|
|
719
|
-
const sol = axon.advance(34e-3, { dtMax: 5e-6, probes: [{ x: 0.02, quantity: 'phi' }, { x: 0.04, quantity: 'phi' }] });
|
|
720
|
-
const { t, probes } = sol.trace, at = (v) => t[v.findIndex((x) => x > 0)]; // (φ inside, the outside's held at 0)
|
|
721
|
-
console.log(`${(0.02 / (at(probes[1]) - at(probes[0]))).toFixed(1)} m/s`); // 18.8 here (finely resolved, 18.73; HH computed 18.8)
|
|
722
|
-
```
|
|
723
|
-
|
|
724
526
|
## Transients
|
|
725
527
|
|
|
726
528
|
`advance(t, opts)` steps adaptively from the device's present state (its `c0`, or the steady
|
|
727
|
-
state if you called `solve()`) to time t. It returns the solution there, with `done` and a `trace`
|
|
529
|
+
state if you called `solve()`) to time t, absolute (for a further dt, `advance(sol.time + dt)`). It returns the solution there, with `done` and a `trace`
|
|
728
530
|
of this call's accepted steps (`t`, `current`, `voltage`: V_right − V_left). It stops short at
|
|
729
531
|
`budgetMs` (an animation frame: call again) or, with a warning saying why, at `maxSteps` (100000)
|
|
730
532
|
or a step that won't converge. `probes`, read at accepted steps (give `dtMax` to resolve them):
|
|
@@ -812,7 +614,8 @@ What `bandDiagram()` and `traces()` draw, and how to explain it (docs/visualizat
|
|
|
812
614
|
reaction; `shifts` (marked ⌇) moves one species' lines to fit a shared axis.
|
|
813
615
|
- **Your own style.** `traces(sol, opts)` gives `{ x, series: [{ id, label, kind, role, slot, y }],
|
|
814
616
|
regions, faces, range }`: `kind` says how a line reads (`'level'`, `'standard'`, `'redox'`,
|
|
815
|
-
`'redox-standard'`, `'phi'`), `themeColor(role,
|
|
617
|
+
`'redox-standard'`, `'phi'`; an `id` is the kind and species, `V:e-`, `Vstd:h+`), `themeColor(role,
|
|
618
|
+
slot)` (plot) gives our colours,
|
|
816
619
|
`labelParts(label)` splits TeX-style subscripts, `NaN` breaks a line, and a face's doubled nodes
|
|
817
620
|
share an x (a vertical step).
|
|
818
621
|
|
|
@@ -828,17 +631,16 @@ What `bandDiagram()` and `traces()` draw, and how to explain it (docs/visualizat
|
|
|
828
631
|
|
|
829
632
|
## Common mistakes
|
|
830
633
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
`c0`, an unbalanced `c0` in a closed neutral region (add the counter-ion's share), `'2e-'` for
|
|
835
|
-
`'2 e-'`, a current drive of the wrong sign. `interfaces[f]` is the stack's f-th face.
|
|
634
|
+
Errors say what to give, and a misspelt option is an error that names the one meant. Beyond
|
|
635
|
+
those:
|
|
636
|
+
|
|
836
637
|
- **A gate as a contact on an oxide.** It fails as `'e-' is absent from the end material 'SiO2'`: make the
|
|
837
638
|
gate a metal layer with a capacitive face, as in the MOS template.
|
|
838
|
-
- **`ohmic()` on a metal.** Its default holds e⁻ and h⁺; on a metal write `ohmic(V, ['e-'])`.
|
|
839
639
|
- **Ions on both sides of a reacting face.** Label them, `'Li+(left) = Li+(right)'`, and block
|
|
840
640
|
them (`species: { 'Li+': 'blocked' }`) if they cross only by the reaction.
|
|
841
641
|
- **Unit slips.** D in cm²/s, μ° in eV, µF/cm², mS/cm², T in °C. `describe(def)` flags them.
|
|
642
|
+
- **A couple's μ° with the wrong sign** solves happily at −E° for +E°: μ°_red − μ°_ox = −F·E°.
|
|
643
|
+
Check the rest potential against E°, as the stirred-solution template does.
|
|
842
644
|
- **The library's data are for 25 °C** (its semiconductors for 300 K: give `T: 300`). At body
|
|
843
645
|
temperature the same μ° and D are a fair start; D rises about 2% per kelvin in water.
|
|
844
646
|
- **A steady solve of a closed cell where every ion can cross**: its steady state is complete
|
|
@@ -846,6 +648,10 @@ What `bandDiagram()` and `traces()` draw, and how to explain it (docs/visualizat
|
|
|
846
648
|
- **A sharp junction between very different solutions** (3 M KCl against 1 µM) can't start:
|
|
847
649
|
grade it over a few thin layers, as at a frit.
|
|
848
650
|
- **Changing `c0` with `set()`.** A carried state keeps what it holds: make a new `Device`.
|
|
651
|
+
- **A charged electrode face beside a strictly neutral solution** (`epsr: 0`, a capacitive face
|
|
652
|
+
with reactions): refining the grid there makes the kinetics worse, not better, since the edge
|
|
653
|
+
cell holds the double layer's charge. Read `sol.warnings`; a `zeroCharge` near the rest
|
|
654
|
+
potential avoids it.
|
|
849
655
|
- **An unresolved double layer.** If `sol.warnings` says so, refine the grid (`hmin` below the
|
|
850
656
|
Debye length) or use a neutral model (`epsr: 0`, `phi: 'neutral'`).
|
|
851
657
|
|
|
@@ -861,9 +667,11 @@ What `bandDiagram()` and `traces()` draw, and how to explain it (docs/visualizat
|
|
|
861
667
|
- sources: `pulse`, `square`, `triangle`, `ramp`, `injector`, `recombination`,
|
|
862
668
|
`photogeneration`;
|
|
863
669
|
- channels: `hodgkinHuxley`, `ghkCurrent`;
|
|
864
|
-
- checking: `describe`, `check`, `polarization` (an electrode's reactions at hypothetical
|
|
670
|
+
- checking: `describe`, `unitWarnings` (describe's unit slips alone), `check`, `polarization` (an electrode's reactions at hypothetical
|
|
865
671
|
metal levels, with the solution held: an Evans diagram), `henderson` and `planck`
|
|
866
672
|
(liquid-junction potentials);
|
|
867
|
-
- pages: `live`, `traces
|
|
673
|
+
- pages: `live`, `traces` (with `speciesRole` and `typeset` for its labels), `recorder`,
|
|
674
|
+
`particles`;
|
|
868
675
|
- curated devices: `perovskiteCell`, `PEROVSKITE_SCANS`, `hysteresis`.
|
|
869
|
-
- `driftlet/plot` (a quick look): `bandDiagram`, `levelChart`, `labelParts`, `themeColor
|
|
676
|
+
- `driftlet/plot` (a quick look): `bandDiagram`, `levelChart`, `labelParts`, `themeColor`,
|
|
677
|
+
`THEME`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driftlet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dependency-free 1D drift–diffusion–reaction (Poisson–Nernst–Planck) solver for semiconductors, electrochemistry and nerves, fast enough for live web demos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -42,8 +42,20 @@
|
|
|
42
42
|
"keywords": [
|
|
43
43
|
"drift-diffusion",
|
|
44
44
|
"poisson-nernst-planck",
|
|
45
|
-
"
|
|
45
|
+
"nernst-planck",
|
|
46
46
|
"semiconductor",
|
|
47
|
+
"pn-junction",
|
|
48
|
+
"band-diagram",
|
|
49
|
+
"solar-cell",
|
|
50
|
+
"device-simulation",
|
|
51
|
+
"electrochemistry",
|
|
52
|
+
"battery",
|
|
53
|
+
"impedance-spectroscopy",
|
|
54
|
+
"cyclic-voltammetry",
|
|
55
|
+
"ion-transport",
|
|
56
|
+
"membrane-potential",
|
|
57
|
+
"hodgkin-huxley",
|
|
58
|
+
"physics",
|
|
47
59
|
"simulation"
|
|
48
60
|
],
|
|
49
61
|
"homepage": "https://markblundeberg.github.io/driftlet/demos/",
|