driftlet 1.0.0 → 1.0.1
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 +1 -1
- package/docs/device.md +15 -9
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/src/device.js +9 -3
- package/src/polarization.js +2 -2
- package/src/solver.js +48 -14
- package/src/types.js +2 -1
- package/types/solver.d.ts +2 -0
- package/types/types.d.ts +2 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ import { bandDiagram } from 'driftlet/plot'; // a level diagram as an SVG str
|
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Or straight from a CDN in a page, pinned to a version:
|
|
59
|
-
`https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
59
|
+
`https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/index.js` (and `src/kit.js`, `src/plot.js`).
|
|
60
60
|
TypeScript declarations ship in the package. Writing a demo with an LLM agent? Point it at
|
|
61
61
|
[`llms.txt`](llms.txt).
|
|
62
62
|
|
package/docs/device.md
CHANGED
|
@@ -479,10 +479,10 @@ contacts: {
|
|
|
479
479
|
- `{ type: 'exchange', k, mu }` (neutral species):
|
|
480
480
|
$`N_{\mathrm{in}} = k \cdot (\mu_{\mathrm{out}} - \mu)/RT`$, k in mol/(m²·s).
|
|
481
481
|
- `{ type: 'velocity', v, offset }` (or `mu`, for a neutral species): a surface velocity,
|
|
482
|
-
$`N_{\mathrm{in}} = v\,(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
level difference
|
|
482
|
+
$`N_{\mathrm{in}} = v\,c\,(e^{(\bar\mu_{\mathrm{out}} - \bar\mu)/RT} - 1)`$ with v in m/s: for dilute
|
|
483
|
+
species that's $`v\,(c_{\mathrm{eq}} - c)`$, $`c_{\mathrm{eq}}`$ the end node's concentration in
|
|
484
|
+
equilibrium with the outside level, and with any statistics it keeps the flux's sign that of
|
|
485
|
+
the level difference (though it isn't then $`v\,(c_{\mathrm{eq}} - c)`$, which saturates). It lies between `'blocked'` (v → 0) and
|
|
486
486
|
`'equilibrium'` (v → ∞): a contact's surface recombination velocity, or thermionic emission
|
|
487
487
|
over a Schottky barrier (below).
|
|
488
488
|
- `'blocked'`: no flux.
|
|
@@ -666,10 +666,16 @@ An electrode port can carry species on its surface, sharing its sites: an adsorb
|
|
|
666
666
|
or a passive film taken as a coverage. Each has a coverage $`\theta`$ at every node of the window,
|
|
667
667
|
Langmuir statistics, $`\mu = \mu^\circ + RT\ln(\theta/\theta_0)`$ with $`\theta_0 = 1 - \sum\theta`$ the
|
|
668
668
|
bare fraction, and `capacity` $`\Gamma`$, mol of sites per m² of electrode. Reactions name them like
|
|
669
|
-
species, and
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
669
|
+
species, and their kinetics are Langmuir: in the Butler–Volmer prefactor each surface species
|
|
670
|
+
counts at its coverage $`\theta`$ (as a solution species does at $`c/c_{\mathrm{ref}}`$), and a reaction
|
|
671
|
+
that fills $`\Delta`$ sites going forward holds $`m = \max(\Delta, 0)`$ bare ones in its transition
|
|
672
|
+
state, so its forward rate goes as $`\theta_0^{\,m}`$ and its backward as $`\theta_0^{\,m-\Delta}`$.
|
|
673
|
+
Adsorption goes on at a rate ∝ $`\theta_0`$ and off ∝ $`\theta`$, an adsorbed couple converts at a
|
|
674
|
+
rate ∝ its coverage, and $`2\mathrm{H_{ads}} \to \mathrm{H_2}`$ goes as $`\theta_H^2`$ and back as
|
|
675
|
+
$`\theta_0^2`$. A reaction with `bare: true` runs only on bare metal, needing a bare site even
|
|
676
|
+
where it fills none ($`m \ge 1`$): a metal dissolving, blocked by a film. Equilibrium is exact
|
|
677
|
+
throughout, from the affinity. A species' `theta0` is its starting coverage (not the bare
|
|
678
|
+
fraction). Surface species carry no charge, so an adsorbed redox couple
|
|
673
679
|
balances its electrons through the solution: `'Oads + e- = Rads + Cl-'`, its coverages at
|
|
674
680
|
$`\theta_O/\theta_R = e^{(E - E^\circ)/V_T}`$ against the chloride's level.
|
|
675
681
|
|
|
@@ -679,7 +685,7 @@ ports: [{
|
|
|
679
685
|
surface: { 'Fe(OH)2': { mu0: -490e3, capacity: 2e-5, theta0: 1e-6 } }, // theta0: the start
|
|
680
686
|
reactions: [
|
|
681
687
|
{ equation: 'Fe2+ + 2 e- = Fe(s)', fixed: { 'Fe(s)': 0 }, k0: 5e-6, alpha: 0.5, bare: true }, // dissolves where bare
|
|
682
|
-
{ equation: 'Fe(s) + 2 OH- = Fe(OH)2 + 2 e-', fixed: { 'Fe(s)': 0 }, k0: 1e-4, alpha: 0.5
|
|
688
|
+
{ equation: 'Fe(s) + 2 OH- = Fe(OH)2 + 2 e-', fixed: { 'Fe(s)': 0 }, k0: 1e-4, alpha: 0.5 }, // the film forms (in minutes), on bare sites
|
|
683
689
|
{ equation: 'O2 + 2 H2O + 4 e- = 4 OH-', fixed: { H2O: -237.13e3 }, k0: 3e-9, alpha: 0.125 }, // on film or not
|
|
684
690
|
],
|
|
685
691
|
}]
|
package/llms.txt
CHANGED
|
@@ -30,9 +30,9 @@ materials, insertion hosts), data.md and reliability.md (what's validated, and t
|
|
|
30
30
|
From a CDN, pin the version you tested against:
|
|
31
31
|
|
|
32
32
|
```js nocheck
|
|
33
|
-
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
34
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
35
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
33
|
+
import { Device, units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/index.js';
|
|
34
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/kit.js';
|
|
35
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/plot.js';
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
From npm: `driftlet`, `driftlet/kit`, `driftlet/plot`. Every export is listed at the end.
|
|
@@ -209,9 +209,9 @@ a CDN needs a long virtual-time budget, 30 s.)
|
|
|
209
209
|
<span id="readout"></span>
|
|
210
210
|
<div id="figure"></div>
|
|
211
211
|
<script type="module">
|
|
212
|
-
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
213
|
-
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
214
|
-
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.
|
|
212
|
+
import { units } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/index.js';
|
|
213
|
+
import { build, layer, ohmic, semiconductor, live } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/kit.js';
|
|
214
|
+
import { bandDiagram } from 'https://cdn.jsdelivr.net/npm/driftlet@1.0.1/src/plot.js';
|
|
215
215
|
|
|
216
216
|
const def = build({
|
|
217
217
|
T: 300,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "driftlet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
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": {
|
package/src/device.js
CHANGED
|
@@ -355,8 +355,8 @@ export function normalizeDevice(def) {
|
|
|
355
355
|
// A contact that passes nothing can't be driven by a current.
|
|
356
356
|
for (const side of ['left', 'right']) {
|
|
357
357
|
const ct = contacts[side];
|
|
358
|
-
const passes = ct.species.some((l) => l.type !== 'blocked') || ct.phi.type === 'capacitive' || ct.phi.type === 'pinned';
|
|
359
|
-
need(passes || ct.drive.kind === 'V', `contacts.${side}.I: this contact passes no current (no linked species, no gate)`);
|
|
358
|
+
const passes = ct.species.some((l, i) => l.type !== 'blocked' && species[i].z !== 0) || ct.phi.type === 'capacitive' || ct.phi.type === 'pinned';
|
|
359
|
+
need(passes || ct.drive.kind === 'V', `contacts.${side}.I: this contact passes no current (no linked charged species, no gate)`);
|
|
360
360
|
ct.passes = passes;
|
|
361
361
|
}
|
|
362
362
|
checkLevel(terminals.map((t) => t.drive), terminals.map((t) => (t.kind === 'port' ? ports[t.index] : contacts[t.side])));
|
|
@@ -889,7 +889,13 @@ function portReactions(pdef, path, mat, terminal, species, speciesIndex, RT, sur
|
|
|
889
889
|
need(part.some((p) => p.side === 0 || p.side === 2), `${rpath}: no species of the port's region or its surface takes part`);
|
|
890
890
|
need(charge === 0, `${rpath}: charge is not balanced (Σ ν z = ${charge})`);
|
|
891
891
|
need(rdef.bare === undefined || typeof rdef.bare === 'boolean', `${rpath}.bare must be true or false`);
|
|
892
|
-
|
|
892
|
+
const alpha = transferCoefficient(rdef.alpha, `${rpath}.alpha`), bare = rdef.bare === true;
|
|
893
|
+
// Langmuir sites: the reaction fills Δ of them going forward, and its transition state holds
|
|
894
|
+
// m bare ones (those it fills, or one if it runs only on bare metal), so the forward rate
|
|
895
|
+
// goes as θ₀^m and the backward as θ₀^(m−Δ): the prefactor's θ₀^(m(1−α)+(m−Δ)α), with each
|
|
896
|
+
// surface species at its coverage θ.
|
|
897
|
+
const fills = part.reduce((t, p) => t + (p.side === 2 ? p.nu : 0), 0), m = Math.max(fills, 0, bare ? 1 : 0);
|
|
898
|
+
return { part, fixedA, k0: positive(rdef.k0, `${rpath}.k0 (mol/(m²·s))`), alpha, bare, sites: m * (1 - alpha) + (m - fills) * alpha };
|
|
893
899
|
});
|
|
894
900
|
}
|
|
895
901
|
|
package/src/polarization.js
CHANGED
|
@@ -115,10 +115,10 @@ export function polarization(device, sol, where, V, opts = {}) {
|
|
|
115
115
|
const out = parts.map(({ rx, k, base, s, q, pref, surf }) => {
|
|
116
116
|
const rate = Float64Array.from(Vs, (v, j) => {
|
|
117
117
|
const { lnA, bare } = at[j];
|
|
118
|
-
let a = base + s * v, p = pref * (rx.
|
|
118
|
+
let a = base + s * v, p = pref * (rx.sites ? powr(bare, rx.sites) : 1);
|
|
119
119
|
for (const u of surf) {
|
|
120
120
|
a -= u.nu * lnA[u.s];
|
|
121
|
-
p *= Math.exp(u.e * lnA[u.s]);
|
|
121
|
+
p *= Math.exp(u.e * lnA[u.s]) * powr(bare, u.e); // its coverage θ = θ₀ a
|
|
122
122
|
}
|
|
123
123
|
return rx.vmax ? p * -Math.expm1(-a) : p * (Math.exp(rx.alpha * a) - Math.exp(-(1 - rx.alpha) * a));
|
|
124
124
|
});
|
package/src/solver.js
CHANGED
|
@@ -373,7 +373,8 @@ export class Solver {
|
|
|
373
373
|
// φ is undefined in a conductor or an insertion host, and in a charge-free region (no
|
|
374
374
|
// charged species) unless its electrostatic cluster, the regions joined to it by faces that
|
|
375
375
|
// aren't neutral, reaches something that fixes φ: a region with charged species, a gate or
|
|
376
|
-
// pinned contact, or a
|
|
376
|
+
// pinned contact, or a face that isn't neutral to a conductor or to a strictly neutral region
|
|
377
|
+
// with ions. (Else φ there is a free constant.)
|
|
377
378
|
this.phiUndefined = new Uint8Array(nNodes);
|
|
378
379
|
const charged = (mat) => species.some((sp, i) => mat.present[i] && sp.z !== 0);
|
|
379
380
|
const pins = (ct) => ct.phi.type === 'capacitive' || ct.phi.type === 'pinned';
|
|
@@ -385,8 +386,11 @@ export class Solver {
|
|
|
385
386
|
while (r1 + 1 < regions.length && model.interfaces[r1].phi.type !== 'neutral' && !free(r1 + 1)) r1++;
|
|
386
387
|
let anchor = (r0 === 0 && pins(contacts.left)) || (r1 === regions.length - 1 && pins(contacts.right));
|
|
387
388
|
for (let r = r0; r <= r1; r++) if (charged(materials[regions[r].material])) anchor = true;
|
|
388
|
-
|
|
389
|
-
|
|
389
|
+
// A neighbour across a face that isn't neutral: a conductor (a gate), or a strictly neutral
|
|
390
|
+
// region whose ions fix its φ.
|
|
391
|
+
const fixes = (r) => materials[regions[r].material].conductor || (!materials[regions[r].material].phiFree && charged(materials[regions[r].material]));
|
|
392
|
+
if (r0 > 0 && model.interfaces[r0 - 1].phi.type !== 'neutral' && fixes(r0 - 1)) anchor = true;
|
|
393
|
+
if (r1 < regions.length - 1 && model.interfaces[r1].phi.type !== 'neutral' && fixes(r1 + 1)) anchor = true;
|
|
390
394
|
for (let r = r0; r <= r1; r++) anchored[r] = anchor ? 1 : 2;
|
|
391
395
|
r0 = r1;
|
|
392
396
|
}
|
|
@@ -585,6 +589,16 @@ export class Solver {
|
|
|
585
589
|
const mat = materials[regions[grid.nodeRegion[g]].material], law = contacts[k === 0 ? 'left' : 'right'].phi.type;
|
|
586
590
|
if (!mat.conductor && mat.epsr === 0 && (law === 'bulk' || law === 'neutral') && !this.chargeNode[g] && !this.phiUndefined[g] && this.nodeIdeal[g]) this.lateStorage[g] = 1;
|
|
587
591
|
});
|
|
592
|
+
// An end node's species held by a port (its contact blocks it, so the level is the port's):
|
|
593
|
+
// its row is the port's, storage and all, and the contact passes none of it.
|
|
594
|
+
this.portHeldEnd = new Uint8Array(2 * n);
|
|
595
|
+
model.ports.forEach((port) => {
|
|
596
|
+
[0, nNodes - 1].forEach((g, k) => {
|
|
597
|
+
if (!port.nodes.includes(g)) return;
|
|
598
|
+
const ct = contacts[k === 0 ? 'left' : 'right'];
|
|
599
|
+
for (let i = 0; i < n; i++) if (port.species[i].type === 'equilibrium' && ct.species[i].type === 'blocked') this.portHeldEnd[k * n + i] = 1;
|
|
600
|
+
});
|
|
601
|
+
});
|
|
588
602
|
this.combining = false;
|
|
589
603
|
this.bookkeeping = regions.map((_, r) => {
|
|
590
604
|
const g0 = grid.regionStart[r], g1 = grid.regionEnd[r], nodes = [], segs = [];
|
|
@@ -1895,6 +1909,14 @@ export class Solver {
|
|
|
1895
1909
|
// toward their mixed potential.
|
|
1896
1910
|
for (const k of this.floating) {
|
|
1897
1911
|
const t = this.terms[k], port = t.kind === 'port' ? model.ports[t.index] : null;
|
|
1912
|
+
const gate = t.kind === 'contact' ? model.contacts[t.side].phi : null;
|
|
1913
|
+
if (gate?.type === 'capacitive') {
|
|
1914
|
+
// A gate contact likewise: at its zero-charge level from the φ beside it (a pinned
|
|
1915
|
+
// one's charge is the field's, already zero against a neutral start).
|
|
1916
|
+
const g = t.side === 'left' ? 0 : this.nNodes - 1;
|
|
1917
|
+
if (!this.phiUndefined[g]) this.termV[k] = gate.zeroCharge + this.VT * u[this.blockOfNode[g] * M];
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1898
1920
|
if (!port?.capacitance) continue;
|
|
1899
1921
|
let w = 0, sum = 0;
|
|
1900
1922
|
port.nodes.forEach((g, j) => {
|
|
@@ -1968,9 +1990,9 @@ export class Solver {
|
|
|
1968
1990
|
}
|
|
1969
1991
|
const o = b * M + (p.side === 2 ? 1 + n + p.s : 1 + p.i), e = p.nu < 0 ? -p.nu * (1 - rx.alpha) : p.nu * rx.alpha;
|
|
1970
1992
|
a -= p.nu * (u[o] + uLo[o]);
|
|
1971
|
-
pref *= p.side === 2 ?
|
|
1993
|
+
pref *= p.side === 2 ? powr(th[g * nSurf + p.s], e) : powr(c[g * n + p.i] / this.cRef[g * n + p.i], e);
|
|
1972
1994
|
}
|
|
1973
|
-
if (rx.
|
|
1995
|
+
if (rx.sites) pref *= powr(this.th0[g], rx.sites);
|
|
1974
1996
|
return pref * bvFactor(a, rx.alpha).g;
|
|
1975
1997
|
}
|
|
1976
1998
|
|
|
@@ -2193,6 +2215,11 @@ export class Solver {
|
|
|
2193
2215
|
if (f >= 0) this._j(b, r, this.blockOfFace[f], 0, (this.sheetSign[g] * this.model.grid.area[g]) / (z[im] * FARADAY * dt));
|
|
2194
2216
|
}
|
|
2195
2217
|
|
|
2218
|
+
// Whether a port holds species i at node g, a device end node (see portHeldEnd).
|
|
2219
|
+
_portHeldAt(g, i) {
|
|
2220
|
+
return g === 0 ? this.portHeldEnd[i] === 1 : g === this.nNodes - 1 && this.portHeldEnd[this.n + i] === 1;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2196
2223
|
// Dilute nodes g0…g1 (ideal statistics, c = c_ref e^ζ): storage of each species and the space
|
|
2197
2224
|
// charge in the Gauss row, written straight into the diagonal blocks by local index. A
|
|
2198
2225
|
// dielectric is the case with no species.
|
|
@@ -2212,7 +2239,7 @@ export class Solver {
|
|
|
2212
2239
|
for (let i = 0; i < n; i++) {
|
|
2213
2240
|
const k = g * n + i;
|
|
2214
2241
|
if (!present[k]) continue;
|
|
2215
|
-
const ck = c[k], l = loc[lb + 1 + i], stores = !(late && z[i] !== 0);
|
|
2242
|
+
const ck = c[k], l = loc[lb + 1 + i], stores = !(late && z[i] !== 0 && !this._portHeldAt(g, i));
|
|
2216
2243
|
if (stores) res[R[lb + 1 + i]] += (v * (ck - cOld[k])) / dt;
|
|
2217
2244
|
q += F * z[i] * ck;
|
|
2218
2245
|
dq += F * z[i] * z[i] * ck;
|
|
@@ -3093,18 +3120,19 @@ export class Solver {
|
|
|
3093
3120
|
aLo -= p.nu * uLo[o];
|
|
3094
3121
|
const e = p.nu < 0 ? -p.nu * (1 - al) : p.nu * al;
|
|
3095
3122
|
if (p.side === 2) {
|
|
3096
|
-
// a surface species at
|
|
3097
|
-
pref *=
|
|
3123
|
+
// a surface species at its coverage θ = θ₀e^ζ, with ∂ln θ_s/∂η_x = δ_sx − θ_x
|
|
3124
|
+
pref *= powr(th[g * nSurf + p.s], e);
|
|
3098
3125
|
d[1 + n + p.s] += e;
|
|
3126
|
+
for (let x = 0; x < surf.length; x++) d[1 + n + x] -= e * th[g * nSurf + x];
|
|
3099
3127
|
} else {
|
|
3100
3128
|
pref *= powr(c[g * n + p.i] / this.cRef[g * n + p.i], e);
|
|
3101
3129
|
this._dlnc(g, p.i, e, d);
|
|
3102
3130
|
}
|
|
3103
3131
|
}
|
|
3104
|
-
if (rx.
|
|
3105
|
-
//
|
|
3106
|
-
for (let x = 0; x < surf.length; x++) d[1 + n + x] -= th[g * nSurf + x];
|
|
3107
|
-
pref *= this.th0[g];
|
|
3132
|
+
if (rx.sites) {
|
|
3133
|
+
// the bare sites its transition state holds: θ₀ = 1 − Σθ, with ∂ln θ₀/∂η_s = −θ_s
|
|
3134
|
+
for (let x = 0; x < surf.length; x++) d[1 + n + x] -= rx.sites * th[g * nSurf + x];
|
|
3135
|
+
pref *= powr(this.th0[g], rx.sites);
|
|
3108
3136
|
}
|
|
3109
3137
|
const { g: bv, gp } = bvFactor(aHi + aLo, al);
|
|
3110
3138
|
const rate = pref * bv;
|
|
@@ -3236,8 +3264,9 @@ export class Solver {
|
|
|
3236
3264
|
// passes, so their flux is zero.)
|
|
3237
3265
|
const closedIons = this.combining && dyn && this.chargeNode[g] === 1;
|
|
3238
3266
|
let I = 0;
|
|
3267
|
+
const held = this.portHeldEnd.subarray(k * n, (k + 1) * n);
|
|
3239
3268
|
for (let i = 0; i < n; i++) {
|
|
3240
|
-
const active = this.loc[b * M + 1 + i] >= 0 && !(closedIons && z[i] !== 0);
|
|
3269
|
+
const active = this.loc[b * M + 1 + i] >= 0 && !(closedIons && z[i] !== 0) && !held[i];
|
|
3241
3270
|
const nIn = active ? res[R[b * M + 1 + i]] : 0;
|
|
3242
3271
|
flux[i] = sgn * nIn; // (toward +x)
|
|
3243
3272
|
if (active && z[i] !== 0) {
|
|
@@ -3250,7 +3279,7 @@ export class Solver {
|
|
|
3250
3279
|
if (this.combining && dyn && this.lateStorage[g] === 1) {
|
|
3251
3280
|
const v = model.grid.vol[g], { cOld } = this;
|
|
3252
3281
|
for (let i = 0; i < n; i++) {
|
|
3253
|
-
if (z[i] === 0 || this.loc[b * M + 1 + i] < 0) continue;
|
|
3282
|
+
if (z[i] === 0 || this.loc[b * M + 1 + i] < 0 || held[i]) continue;
|
|
3254
3283
|
const ci = c[g * n + i], st = (v * (ci - cOld[g * n + i])) / dt;
|
|
3255
3284
|
res[R[b * M + 1 + i]] += st;
|
|
3256
3285
|
flux[i] += sgn * st;
|
|
@@ -4866,6 +4895,11 @@ export class Solver {
|
|
|
4866
4895
|
}
|
|
4867
4896
|
mu[name].re[g] = RT * xr[b * M + 1 + i];
|
|
4868
4897
|
mu[name].im[g] = RT * xi[b * M + 1 + i];
|
|
4898
|
+
if (this.nodeConductor[g] >= 0) {
|
|
4899
|
+
// a metal has a Fermi level but no concentration (as a solution reports it)
|
|
4900
|
+
c[name].re[g] = c[name].im[g] = NaN;
|
|
4901
|
+
continue;
|
|
4902
|
+
}
|
|
4869
4903
|
// δc_i = Σ_j K_ij (δη_j − z_j δφ̂)
|
|
4870
4904
|
let sr = 0, si = 0;
|
|
4871
4905
|
for (let j = 0; j < n; j++) {
|
package/src/types.js
CHANGED
|
@@ -163,7 +163,8 @@
|
|
|
163
163
|
* species and the terminal species at the port's level
|
|
164
164
|
* @property {Record<string, { mu0: number, capacity: number, theta0?: number }>} [surface] with reactions: species
|
|
165
165
|
* on the electrode's sites, Langmuir (μ = μ° + RT ln(θ/θ₀)), capacity in mol of sites per m² of electrode,
|
|
166
|
-
* shared;
|
|
166
|
+
* shared; reactions run on Langmuir sites (what fills sites needs bare ones), and one with `bare: true`
|
|
167
|
+
* needs a bare site even if it fills none (θ₀ = 1 − Σθ)
|
|
167
168
|
* @property {{ C: number, zeroCharge?: number }} [capacitance] a capacitance spread through the window (F/m² of
|
|
168
169
|
* electrode): σ = C (V − zeroCharge − φ) on the port's side, aσ in each node's charge balance; a gate
|
|
169
170
|
* along a channel, a double layer
|
package/types/solver.d.ts
CHANGED
|
@@ -115,6 +115,7 @@ export class Solver {
|
|
|
115
115
|
chargeNode: Uint8Array<any>;
|
|
116
116
|
chargeNodes: Int32Array<ArrayBuffer>;
|
|
117
117
|
lateStorage: Uint8Array<any>;
|
|
118
|
+
portHeldEnd: Uint8Array<ArrayBuffer>;
|
|
118
119
|
combining: boolean;
|
|
119
120
|
bookkeeping: any;
|
|
120
121
|
bookkeepingFaces: number[];
|
|
@@ -209,6 +210,7 @@ export class Solver {
|
|
|
209
210
|
dtNow: any;
|
|
210
211
|
transformed: any;
|
|
211
212
|
_nodeConductor(g: any, dt: any): void;
|
|
213
|
+
_portHeldAt(g: any, i: any): boolean;
|
|
212
214
|
_nodesDilute(g0: any, g1: any, dt: any): void;
|
|
213
215
|
_nodeConcentrated(g: any, dt: any): void;
|
|
214
216
|
_storage(g: any, i: any, dt: any, Kz?: number): void;
|
package/types/types.d.ts
CHANGED
|
@@ -390,7 +390,8 @@ export type PortDef = {
|
|
|
390
390
|
/**
|
|
391
391
|
* with reactions: species
|
|
392
392
|
* on the electrode's sites, Langmuir (μ = μ° + RT ln(θ/θ₀)), capacity in mol of sites per m² of electrode,
|
|
393
|
-
* shared;
|
|
393
|
+
* shared; reactions run on Langmuir sites (what fills sites needs bare ones), and one with `bare: true`
|
|
394
|
+
* needs a bare site even if it fills none (θ₀ = 1 − Σθ)
|
|
394
395
|
*/
|
|
395
396
|
surface?: Record<string, {
|
|
396
397
|
mu0: number;
|