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 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.0/src/index.js` (and `src/kit.js`, `src/plot.js`).
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\,(c_{\mathrm{eq}} - c)`$ with v in m/s and $`c_{\mathrm{eq}}`$ the end node's
483
- concentration in equilibrium with the outside level, i.e.
484
- $`v\,c\,(e^{(\bar\mu_{\mathrm{out}} - \bar\mu)/RT} - 1)`$, which keeps the flux's sign that of the
485
- level difference whatever the statistics. It lies between `'blocked'` (v → 0) and
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 a reaction with `bare: true` runs only on bare metal: its rate is multiplied by
670
- $`\theta_0`$, both ways, so equilibrium stays exact. Written for adsorption itself, that's Langmuir
671
- kinetics (on at a rate ∝ $`\theta_0`$, off ∝ $`\theta`$). A species' `theta0` is its starting
672
- coverage (not the bare fraction). Surface species carry no charge, so an adsorbed redox couple
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, bare: true }, // the film forms (in minutes)
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.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';
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.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';
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.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
- return { part, fixedA, k0: positive(rdef.k0, `${rpath}.k0 (mol/(m²·s))`), alpha: transferCoefficient(rdef.alpha, `${rpath}.alpha`), bare: rdef.bare === true };
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
 
@@ -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.bare ? bare : 1);
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 capacitive face to a conductor. (Else φ there is a free constant.)
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
- if (r0 > 0 && model.interfaces[r0 - 1].phi.type !== 'neutral' && materials[regions[r0 - 1].material].conductor) anchor = true;
389
- if (r1 < regions.length - 1 && model.interfaces[r1].phi.type !== 'neutral' && materials[regions[r1 + 1].material].conductor) anchor = true;
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 ? Math.exp(e * (u[o] + uLo[o] - port.surface[p.s].mu0 / RT)) : powr(c[g * n + p.i] / this.cRef[g * n + p.i], e);
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.bare) pref *= this.th0[g];
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 activity θ/θ₀ = e^ζ
3097
- pref *= Math.exp(e * (u[o] + uLo[o] - surf[p.s].mu0 / RT));
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.bare) {
3105
- // on bare metal only: the free fraction θ₀ = 1 − Σθ, with ∂ln θ₀/∂η_s = −θ_s
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; a reaction with `bare: true` runs only on the bare fraction θ₀ = 1 − Σθ
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; a reaction with `bare: true` runs only on the bare fraction θ₀ = 1 − Σθ
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;