driftlet 0.9.0 → 0.10.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 +3 -3
- package/docs/device.md +118 -90
- package/docs/kit.md +5 -0
- package/docs/numerics.md +17 -5
- package/llms.txt +106 -7
- package/package.json +2 -1
- package/src/checks.js +27 -23
- package/src/describe.js +15 -9
- package/src/device.js +16 -5
- package/src/index.js +17 -4
- package/src/polarization.js +51 -16
- package/src/solution.js +21 -9
- package/src/solver.js +151 -40
- package/src/types.js +4 -0
- package/types/checks.d.ts +3 -3
- package/types/index.d.ts +1 -0
- package/types/live.d.ts +1 -0
- package/types/polarization.d.ts +8 -2
- package/types/solver.d.ts +16 -4
- package/types/types.d.ts +6 -0
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ The same equations go by different names in different fields. If your problem is
|
|
|
30
30
|
|
|
31
31
|
| If you work on | you may call it | start from | checked against |
|
|
32
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 |
|
|
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
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
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
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 |
|
|
@@ -55,7 +55,7 @@ import { bandDiagram } from 'driftlet/plot'; // a level diagram as an SVG str
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Or straight from a CDN in a page, pinned to a version:
|
|
58
|
-
`https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
58
|
+
`https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/index.js` (and `src/kit.js`, `src/plot.js`).
|
|
59
59
|
TypeScript declarations ship in the package. Writing a demo with an LLM agent? Point it at
|
|
60
60
|
[`llms.txt`](llms.txt).
|
|
61
61
|
|
|
@@ -145,7 +145,7 @@ them locally, serve the repository root (e.g. `python3 -m http.server`) and open
|
|
|
145
145
|
| [](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
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 against ideal theory |
|
|
147
147
|
| [](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
|
|
148
|
+
| **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
149
|
| [](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
150
|
| **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
151
|
| [](https://markblundeberg.github.io/driftlet/demos/insertion.html) | [](https://markblundeberg.github.io/driftlet/demos/organic.html) | [](https://markblundeberg.github.io/driftlet/demos/perovskite.html) |
|
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₂)
|
|
@@ -587,11 +598,9 @@ That's the gradual-channel approximation of a field-effect transistor, exact for
|
|
|
587
598
|
with no body (a TFT, an organic or oxide transistor) while the channel is long against the gate
|
|
588
599
|
dielectric, and failing where it pinches off (where the 2D field takes over). It's also the
|
|
589
600
|
Bernards model of an organic electrochemical transistor (a volumetric capacitance), cable theory
|
|
590
|
-
for an axon's membrane, and a porous electrode's double layer
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
a blocked spectator's flat level does in an electrolyte, and the current saturates as the
|
|
594
|
-
carriers run out at the drain, in both.
|
|
601
|
+
for an axon's membrane, and a porous electrode's double layer. The gate's flat level pins the
|
|
602
|
+
channel's standard level through C, as a blocked spectator's flat level does in an electrolyte,
|
|
603
|
+
and the current saturates as the carriers run out at the drain, in both.
|
|
595
604
|
|
|
596
605
|
Validated in test/capacitance.test.js: a thin-film transistor's current against the
|
|
597
606
|
charge-sheet model, $`I/W = (\mu/L)[(\sigma_s^2 - \sigma_d^2)/(2C) + V_T(\sigma_s - \sigma_d)]`$, from
|
|
@@ -599,6 +608,23 @@ below threshold through saturation (second order in the grid); a porous electrod
|
|
|
599
608
|
impedance $`\sqrt{r/y}\coth(L\sqrt{ry})`$, the double layer in series with the ions' chemical
|
|
600
609
|
capacitance; and the gate's low-frequency impedance against $`dQ/dV_G`$ from steady states.
|
|
601
610
|
|
|
611
|
+
### What a solution reports
|
|
612
|
+
|
|
613
|
+
Each solution's `ports[k]`, as the port has them:
|
|
614
|
+
|
|
615
|
+
| Field | Contents |
|
|
616
|
+
|---|---|
|
|
617
|
+
| `name`, `V` | the port's name and voltage |
|
|
618
|
+
| `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) |
|
|
619
|
+
| `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³) |
|
|
620
|
+
| `rates[q][j]` | reaction q's forward rate at node j, mol/(m²·s) of electrode (reaction first, then node) |
|
|
621
|
+
| `coverage[name][j]`, `bare[j]` | with a surface: each species' coverage θ, and the bare fraction θ₀ to full precision |
|
|
622
|
+
| `sigma[j]`, `charge` | with a capacitance: σ per area of electrode (C/m²), and the total the port's side holds |
|
|
623
|
+
|
|
624
|
+
The arrays are typed (`Float64Array`), so `Array.from` them before mapping to anything but
|
|
625
|
+
numbers. The kit's [`polarization()`](kit.md#polarization-curves-an-evans-diagram) gives each reaction's rate at a spot of the
|
|
626
|
+
electrode with its level moved, an Evans diagram.
|
|
627
|
+
|
|
602
628
|
## Terminals
|
|
603
629
|
|
|
604
630
|
The two contacts and every port are the device's terminals, named `left`, `right` and by each
|
|
@@ -620,7 +646,9 @@ ports: [{ name: 'ref', I: 0, ... }, // a reference electrode: no current, its v
|
|
|
620
646
|
the voltage floats and is solved for.
|
|
621
647
|
- **`V` and `R`**: a source behind a resistance, $`I = (V - V_{\mathrm{terminal}})/R`$.
|
|
622
648
|
|
|
623
|
-
At least one terminal must be held at a voltage, or the device's overall level floats
|
|
649
|
+
At least one terminal must be held at a voltage, or the device's overall level floats, and if
|
|
650
|
+
one is driven by a current, a held one must pass current (a closed end held at a voltage fixes
|
|
651
|
+
nothing). In
|
|
624
652
|
steady state the terminal currents sum to zero. A contact can be driven by a current only if
|
|
625
653
|
something passes it (a linked species, or a gate's displacement).
|
|
626
654
|
|
|
@@ -774,7 +802,7 @@ console.log(`${run.steps} steps; I(0.01 s) ≈ ${run.trace.current[run.trace.t.f
|
|
|
774
802
|
| `terminals[name]` | `{ V, current }` for each terminal (contacts and ports), current into the device |
|
|
775
803
|
| `contacts.left/right` | `{ V, flux: {name}, D, current }` at each contact |
|
|
776
804
|
| `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 |
|
|
805
|
+
| `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
806
|
| `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
807
|
| `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
808
|
| `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.
|
package/docs/numerics.md
CHANGED
|
@@ -358,7 +358,9 @@ reaction) and that nothing outside feeds ($`w = 0`$ on stretches reached by a co
|
|
|
358
358
|
the null space of that stoichiometry. A stretch in none of them is fed. So Ag⁺ between silver
|
|
359
359
|
electrodes is fed (Ag⁺ + e⁻ ⇌ Ag(s), the electrons fed by the contacts), and the steady equations
|
|
360
360
|
are solved directly; Fe³⁺ and Fe²⁺ between platinum electrodes aren't, since Fe³⁺ + e⁻ ⇌ Fe²⁺
|
|
361
|
-
conserves the iron whatever the electrons do, so their total is kept by huge steps (below).
|
|
361
|
+
conserves the iron whatever the electrons do, so their total is kept by huge steps (below). An
|
|
362
|
+
electrode surface's species are columns too, holding $`\Gamma \sum v\,a\,\theta`$, so species fed only
|
|
363
|
+
through one (A⁺ + e⁻ = S, S + e⁻ = B⁻) conserve their total with what the surface holds. A
|
|
362
364
|
floating conductor starts uncharged, with its carrier's level in equilibrium with the first
|
|
363
365
|
reaction on its left face that takes it.
|
|
364
366
|
|
|
@@ -366,7 +368,8 @@ reaction on its left face that takes it.
|
|
|
366
368
|
|
|
367
369
|
A port adds a source per volume to the balance rows of the nodes in its window, after every
|
|
368
370
|
other term at those nodes and before the contacts. A held (`'equilibrium'`) level replaces the
|
|
369
|
-
balance row with a Dirichlet row
|
|
371
|
+
balance row with a Dirichlet row, after every port's other terms (another port's electrode can
|
|
372
|
+
react in the same window). The port's flux is then that row's residual, read just
|
|
370
373
|
before replacement, exactly as at a contact. Conductance and exchange links are linear in
|
|
371
374
|
$`(\bar\mu_{\mathrm{out}} - \bar\mu)`$. Everything stays on the node's own block. A contact's flux
|
|
372
375
|
readout at an end node already includes any port source there, so contact and port fluxes always
|
|
@@ -478,8 +481,15 @@ test checks the compact Jacobian column by column on devices that cover every as
|
|
|
478
481
|
$`C_c = h^3(1+\omega)^2/(\omega(1+2\omega))`$ and $`C_p = h(h+h_1)(h+h_1+h_2)`$; linear after
|
|
479
482
|
backward Euler, scaled by $`h/(2h + h_1)`$. The first step, with no history, is checked by step
|
|
480
483
|
doubling.
|
|
484
|
+
- The state a first step starts from needn't satisfy the algebraic equations ($`\hat\phi`$ in a
|
|
485
|
+
strictly neutral material, an interface's unknowns), after a start or a jump; they jump in
|
|
486
|
+
the first instant. So the history starts from the first step's half step, not from there,
|
|
487
|
+
and the second step is backward Euler: a predictor through the starting state saw the jump
|
|
488
|
+
as error that shrank only as fast as the step, and a junction from a sharp boundary at a
|
|
489
|
+
tight tolerance stepped down into round-off.
|
|
481
490
|
- The error is measured in thermal units over every state potential ($`\hat\phi`$ where defined,
|
|
482
|
-
each present $`\eta`$,
|
|
491
|
+
each present $`\eta`$, an electrode surface's coverages as their $`\eta`$; a floating terminal's
|
|
492
|
+
voltage follows from these). That includes species far below their
|
|
483
493
|
largest concentration, such as minority carriers ahead of a diffusion front. Weighting
|
|
484
494
|
them down (a mixed relative/absolute tolerance,
|
|
485
495
|
$`\delta\eta \cdot c/(c + 10^{-10} c_{\mathrm{max}})`$) took a third fewer steps on a pn turn-on,
|
|
@@ -627,8 +637,10 @@ was, so storage and neutrality don't see it. Only the fluxes do. But in $`(\hat\
|
|
|
627
637
|
and neutrality see it as pairs of huge entries that cancel, and round-off in that cancellation, of
|
|
628
638
|
order storage/flux ~ $`h^2/(D \cdot dt)`$ times the range of concentrations, swamps the fluxes that
|
|
629
639
|
fix it: a trace ion beside 3 M KCl lost about ten digits, and Newton stalled. So at each node that
|
|
630
|
-
stays neutral (interior nodes,
|
|
631
|
-
|
|
640
|
+
stays neutral (interior nodes, edges at `neutral` faces, and a device's end where its contact is
|
|
641
|
+
closed to every ion and leaves $`\phi`$ alone; not the edge of a capacitive or pinned face, which
|
|
642
|
+
holds the face's charge, nor an end whose contact holds a level), assembled without storage and
|
|
643
|
+
neutrality:
|
|
632
644
|
- rows: the balance of the most abundant charged species (by $`z^2 c`$) becomes
|
|
633
645
|
$`\sum (z_i/z_k) \times`$ each balance, which without storage is current continuity;
|
|
634
646
|
- columns: the unknowns become $`\hat\phi'`$ and $`\eta'_i = \eta_i - z_i \hat\phi`$ (the update is
|
package/llms.txt
CHANGED
|
@@ -14,9 +14,9 @@ docs/conventions.md (signs and levels) and docs/data.md (the data library).
|
|
|
14
14
|
From a CDN, pin the version:
|
|
15
15
|
|
|
16
16
|
```js nocheck
|
|
17
|
-
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
18
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
19
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
17
|
+
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/index.js';
|
|
18
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/kit.js';
|
|
19
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/plot.js';
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`.
|
|
@@ -34,7 +34,9 @@ From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`.
|
|
|
34
34
|
and h⁺). In voltage units the conduction band sits below the valence band. `NaN` marks where a
|
|
35
35
|
species is absent.
|
|
36
36
|
- **Current**: `sol.current` is positive toward +x through the device. `sol.terminals[name]` gives
|
|
37
|
-
each terminal's `{ V, current }`, current into the device.
|
|
37
|
+
each terminal's `{ V, current }`, current into the device. Currents, fluxes and amounts are per
|
|
38
|
+
m² of a planar device; with a `geometry` (spherical, cylindrical or any cross-section A(x)) they
|
|
39
|
+
are totals through it.
|
|
38
40
|
- **Nothing physical is defaulted.** Every interface between different materials needs an
|
|
39
41
|
alignment (`dipole` or `step`), or `phi: 'neutral'` for a macroscopic model. Every metal face
|
|
40
42
|
next to a dielectric needs its φ law. Every species that doesn't reach a contact needs its
|
|
@@ -247,6 +249,103 @@ const dev = new Device(electrode(rest.terminals.left.V + 0.2));
|
|
|
247
249
|
console.log(`anodic current ${dev.solve().terminals.left.current.toFixed(2)} A/m²`);
|
|
248
250
|
```
|
|
249
251
|
|
|
252
|
+
## Template: corrosion under a drop (an electrode spread through a film)
|
|
253
|
+
|
|
254
|
+
A **port with reactions** is an electrode spread through a window of a region: a metal under a
|
|
255
|
+
thin film of water, a crevice's walls, a porous electrode. Its `terminal` is the metal's carrier,
|
|
256
|
+
`area` the electrode's area per volume of the window (1/h under a film h thick), and each
|
|
257
|
+
reaction is Butler–Volmer per area, as at a face. The region must be strictly neutral (ε = 0); a
|
|
258
|
+
`capacitance` adds the double layer. A **`geometry`** gives the device a cross-section A(x): here
|
|
259
|
+
x is the radius of a drop seen from its centre, A = 2πr·h(r), and every current and amount becomes
|
|
260
|
+
a total through it (A, mol), not per m².
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
import { Device, GAS_CONSTANT, FARADAY } from 'driftlet';
|
|
264
|
+
import { aqueous, polarization } from 'driftlet/kit';
|
|
265
|
+
|
|
266
|
+
// Salt water on iron: a drop 3 mm across. The iron dissolves and reduces O₂, which comes in from
|
|
267
|
+
// the air through the drop's top, fastest at the thin rim: the centre corrodes, the rim doesn't.
|
|
268
|
+
const T = 298.15, RT = GAS_CONSTANT * T, R = 3e-3, h0 = 1e-3, cO2 = 0.26, D = 2e-9;
|
|
269
|
+
const lib = aqueous(['Na+', 'Cl-', 'Fe2+', 'OH-', 'H+'], { epsr: 0 }); // strictly neutral water
|
|
270
|
+
lib.species.push({ name: 'O2', z: 0, cRef: 1000 }, { name: 'e-', z: -1 }); // e⁻: the iron's, in no material
|
|
271
|
+
lib.materials.water.species.O2 = { D, mu0: 16.4e3 };
|
|
272
|
+
const r = Array.from({ length: 61 }, (_, j) => (j / 60) * R);
|
|
273
|
+
const h = r.map((x) => Math.max(h0 * (1 - (x / R) * (x / R)), 0.02 * h0)); // the drop's height
|
|
274
|
+
const dev = new Device({
|
|
275
|
+
T,
|
|
276
|
+
species: lib.species,
|
|
277
|
+
materials: lib.materials,
|
|
278
|
+
geometry: { area: { x: r, values: r.map((x, j) => 2 * Math.PI * x * h[j]) } }, // ions squeeze through the thin rim
|
|
279
|
+
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 } }],
|
|
280
|
+
contacts: { left: { phi: 'neutral' }, right: { phi: 'neutral' } }, // closed: the centre, and the rim
|
|
281
|
+
bulkReactions: [
|
|
282
|
+
{ equation: 'H+ + OH- = H2O', fixed: { H2O: -237.129e3 }, kf: { water: 1.4e8 } },
|
|
283
|
+
// 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
|
|
284
|
+
// exchange link, whose rate is linear in μ rather than c)
|
|
285
|
+
{ equation: 'Air = O2', fixed: { Air: 16.4e3 + RT * Math.log(cO2 / 1000) }, kf: { water: { x: r, values: h.map((y) => (D * cO2) / (y * y)) } } },
|
|
286
|
+
],
|
|
287
|
+
ports: [{
|
|
288
|
+
name: 'iron', region: 'drop', terminal: 'e-',
|
|
289
|
+
V: 0, // the only metal and the only path for current, so it passes none: held, it fixes the level φ is read against
|
|
290
|
+
area: { x: r, values: h.map((y) => 1 / y) }, // m² of iron per m³ of water
|
|
291
|
+
capacitance: { C: 0.2, zeroCharge: -0.4 }, // the double layer: the potential moves continuously
|
|
292
|
+
reactions: [
|
|
293
|
+
{ equation: 'Fe2+ + 2 e- = Fe(s)', fixed: { 'Fe(s)': 0 }, k0: 5e-6, alpha: 0.5 },
|
|
294
|
+
{ equation: 'O2 + 2 H2O + 4 e- = 4 OH-', fixed: { H2O: -237.129e3 }, k0: 3e-9, alpha: 0.125 },
|
|
295
|
+
],
|
|
296
|
+
}],
|
|
297
|
+
grid: { hmin: 5e-6, hmax: 60e-6, ratio: 1.2 },
|
|
298
|
+
});
|
|
299
|
+
const sol = dev.advance(600); // ten minutes
|
|
300
|
+
const iron = sol.ports[0], last = iron.x.length - 1;
|
|
301
|
+
// rates[q][j]: reaction q at node j of the window, forward, mol/(m²·s) of iron; anodic current +
|
|
302
|
+
const anodic = (j) => 2 * FARADAY * -iron.rates[0][j] - 4 * FARADAY * iron.rates[1][j];
|
|
303
|
+
console.log(`iron at ${(iron.V - sol.phi[0]).toFixed(3)} V against the water at the centre (SHE, with table μ°)`);
|
|
304
|
+
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`);
|
|
305
|
+
// The Evans diagram at the centre: each reaction's current with the iron's level moved, the water held.
|
|
306
|
+
const levels = [-0.55, -0.5, -0.45, -0.4].map((v) => v + sol.phi[0]);
|
|
307
|
+
const evans = polarization(dev, sol, { port: 'iron', x: 0 }, levels);
|
|
308
|
+
for (const rx of evans.reactions) console.log(`${rx.equation}: ${Array.from(rx.current, (i) => i.toFixed(2)).join(', ')} A/m²`);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
On an electrode's surface, species can take its sites (`surface: { name: { mu0, capacity } }`,
|
|
312
|
+
Langmuir coverages), and a reaction with `bare: true` runs only on bare metal: a passive film
|
|
313
|
+
(docs/device.md, "An electrode's surface").
|
|
314
|
+
|
|
315
|
+
## Template: a thin-film transistor (a capacitance along a channel)
|
|
316
|
+
|
|
317
|
+
A **port with a `capacitance`** holds charge σ = C (V − zeroCharge − φ) per area of electrode
|
|
318
|
+
along its window, balanced by the region (Gauss's law, or neutrality at ε = 0): a gate along a
|
|
319
|
+
channel, a porous electrode's double layer. In 1D a field-effect transistor is its channel, x from
|
|
320
|
+
source to drain, with the gate spread along it: the gradual-channel approximation, exact for a thin
|
|
321
|
+
film until it pinches off.
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { Device, GAS_CONSTANT, FARADAY } from 'driftlet';
|
|
325
|
+
|
|
326
|
+
const T = 300, VT = (GAS_CONSTANT * T) / FARADAY;
|
|
327
|
+
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)
|
|
328
|
+
const tft = (Vg, Vd) =>
|
|
329
|
+
new Device({
|
|
330
|
+
T,
|
|
331
|
+
species: [{ name: 'e-', z: -1, cRef: 46 }], // an undoped film's electrons (N_c 2.8e19 cm⁻³)
|
|
332
|
+
materials: { film: { epsr: 0, species: { 'e-': { D: mu * VT, mu0: 0 } } } }, // ε = 0: F·n·t balances σ
|
|
333
|
+
regions: [{ name: 'channel', material: 'film', length: L }],
|
|
334
|
+
contacts: {
|
|
335
|
+
left: { V: 0, terminal: 'e-', species: { 'e-': 'equilibrium' }, phi: 'neutral' }, // source
|
|
336
|
+
right: { V: Vd, terminal: 'e-', species: { 'e-': 'equilibrium' }, phi: 'neutral' }, // drain
|
|
337
|
+
},
|
|
338
|
+
ports: [{ name: 'gate', region: 'channel', V: Vg, area: 1 / t, capacitance: { C, zeroCharge: 0.3 } }],
|
|
339
|
+
grid: { hmin: L / 2000, hmax: L / 100, ratio: 1.05 }, // fine at the drain, where it pinches off
|
|
340
|
+
});
|
|
341
|
+
// The output characteristic at V_G = 2 V: linear, then saturating past V_D ≈ V_G − 0.3 V.
|
|
342
|
+
for (const Vd of [0.1, 0.5, 1, 1.5, 2, 3]) {
|
|
343
|
+
const sol = tft(2, Vd).solve();
|
|
344
|
+
const I = -sol.current * t * W; // sol.current is per m² of the film's section
|
|
345
|
+
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²`);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
250
349
|
## Template: a transient, with a pulse injected sideways
|
|
251
350
|
|
|
252
351
|
A **port** feeds a window of nodes inside a region from the side (a point contact, an injector,
|
|
@@ -319,9 +418,9 @@ solve), as long as it settles rather than ping-pongs.
|
|
|
319
418
|
<span id="readout"></span>
|
|
320
419
|
<div id="figure"></div>
|
|
321
420
|
<script type="module">
|
|
322
|
-
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
323
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
324
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.
|
|
421
|
+
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/index.js';
|
|
422
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/kit.js';
|
|
423
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@0.10.0/src/plot.js';
|
|
325
424
|
|
|
326
425
|
const def = build({
|
|
327
426
|
T: 300,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driftlet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Small, dependency-free 1D drift–diffusion–reaction (Poisson–Nernst–Planck) solver for interactive web demos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "node --test",
|
|
31
31
|
"bench": "node bench/bench.js",
|
|
32
|
+
"hard": "node bench/hard.js",
|
|
32
33
|
"smoke": "node tools/smoke.js",
|
|
33
34
|
"shots": "node tools/shots.js",
|
|
34
35
|
"prepublishOnly": "npm test && npm run types",
|