driftlet 1.1.1 → 1.2.2
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 +2 -2
- package/docs/conventions.md +2 -2
- package/docs/data.md +4 -1
- package/docs/device.md +6 -5
- package/docs/internals.md +86 -0
- package/docs/numerics.md +24 -5
- package/docs/reliability.md +34 -0
- package/docs/statistics.md +1 -0
- package/llms.txt +6 -6
- package/package.json +2 -1
- package/src/channels.js +12 -7
- package/src/checks.js +6 -4
- package/src/data.js +1 -1
- package/src/describe.js +1 -1
- package/src/device.js +3 -4
- package/src/devices.js +9 -1
- package/src/equation.js +14 -3
- package/src/index.js +6 -3
- package/src/junction.js +9 -3
- package/src/kit.js +1 -1
- package/src/lawRows.js +562 -0
- package/src/laws.js +693 -0
- package/src/live.js +14 -9
- package/src/particles.js +4 -1
- package/src/plot.js +8 -4
- package/src/solution.js +1 -0
- package/src/solver.js +82 -902
- package/src/stack.js +5 -1
- package/src/statistics.js +31 -10
- package/src/traces.js +10 -7
- package/src/types.js +96 -29
- package/types/index.d.ts +14 -5
- package/types/kit.d.ts +1 -1
- package/types/lawRows.d.ts +5 -0
- package/types/laws.d.ts +86 -0
- package/types/plot.d.ts +2 -2
- package/types/solution.d.ts +1 -0
- package/types/solver.d.ts +80 -39
- package/types/types.d.ts +179 -26
package/src/stack.js
CHANGED
|
@@ -33,6 +33,9 @@ export function combine(...parts) {
|
|
|
33
33
|
const species = [], materials = {};
|
|
34
34
|
parts.forEach((part, k) => {
|
|
35
35
|
if (!isObject(part)) fail(`combine: part ${k} must be { species, materials }`);
|
|
36
|
+
// (trap()'s whole result, given for its library, dropped the traps without a word)
|
|
37
|
+
if (part.library !== undefined) fail(`combine: part ${k} is { library, … }, as trap() returns: give its library here (trap(…).library), and its bulkReactions and c0 to the device`);
|
|
38
|
+
fields(part, `combine: part ${k}`, ['species', 'materials']);
|
|
36
39
|
for (const sp of part.species ?? []) {
|
|
37
40
|
const prev = species.find((s) => s.name === sp.name);
|
|
38
41
|
if (!prev) species.push({ ...sp });
|
|
@@ -100,6 +103,7 @@ export function ohmic(d = 0, carriers = ['e-', 'h+']) {
|
|
|
100
103
|
*/
|
|
101
104
|
export function bath(c, reference, d = 0, opts = {}) {
|
|
102
105
|
if (typeof reference !== 'string') [d, opts] = [reference ?? 0, isObject(d) ? d : {}];
|
|
106
|
+
checkOptions('bath', opts, ['offset', 'material']);
|
|
103
107
|
const { offset, material } = opts;
|
|
104
108
|
return {
|
|
105
109
|
...drive(d, 'bath'),
|
|
@@ -161,7 +165,7 @@ export function build(def) {
|
|
|
161
165
|
if (regions.length === 0) fail('build.stack has no layers');
|
|
162
166
|
// A contact holding a carrier its layer lacks: most often ohmic()'s default on a metal.
|
|
163
167
|
for (const [k, end] of [[0, regions[0]], [stack.length - 1, regions[regions.length - 1]]]) {
|
|
164
|
-
const held = isObject(stack[k].species) ? Object.keys(stack[k].species).filter((s) => stack[k].species[s] !== 'blocked') : [];
|
|
168
|
+
const held = isObject(stack[k].species) ? Object.keys(stack[k].species).filter((s) => stack[k].species[s] !== 'blocked' && stack[k].species[s]?.type !== 'blocked') : [];
|
|
165
169
|
const mat = materials[end.material];
|
|
166
170
|
if (!isObject(mat)) continue;
|
|
167
171
|
const has = (s) => (isObject(mat.conductor) ? mat.conductor.species === s : isObject(mat.species) && mat.species[s] !== undefined);
|
package/src/statistics.js
CHANGED
|
@@ -286,7 +286,7 @@ function debyeHuckelModel(idx, cRef, z, epsr, a, RT) {
|
|
|
286
286
|
idx,
|
|
287
287
|
evaluate(zeta, c, K) {
|
|
288
288
|
for (let b = 0; b < k; b++) y[b] = zeta[b] + Math.log(cRef[b]);
|
|
289
|
-
let st;
|
|
289
|
+
let st, mx = Infinity;
|
|
290
290
|
for (let it = 0; it < 100; it++) {
|
|
291
291
|
for (let b = 0; b < k; b++) cc[b] = Math.exp(y[b]);
|
|
292
292
|
st = state(cc);
|
|
@@ -297,7 +297,7 @@ function debyeHuckelModel(idx, cRef, z, epsr, a, RT) {
|
|
|
297
297
|
R[b] = y[b] + lng[b] - zeta[b] - Math.log(cRef[b]);
|
|
298
298
|
dot += s[b] * cc[b] * R[b];
|
|
299
299
|
}
|
|
300
|
-
|
|
300
|
+
mx = 0;
|
|
301
301
|
for (let b = 0; b < k; b++) {
|
|
302
302
|
let dy = R[b] + (st.A * s[b] * dot) / st.denom;
|
|
303
303
|
if (dy > 1) dy = 1;
|
|
@@ -307,6 +307,8 @@ function debyeHuckelModel(idx, cRef, z, epsr, a, RT) {
|
|
|
307
307
|
}
|
|
308
308
|
if (mx < 1e-14) break;
|
|
309
309
|
}
|
|
310
|
+
// (a stall at round-off, near the convexity limit, is converged; anything more isn't)
|
|
311
|
+
if (!(mx < 1e-10)) throw new SolverError(`debye-huckel: the concentrations at these levels did not converge (a step of ${mx.toExponential(2)} in ln c after 100 iterations)`);
|
|
310
312
|
for (let b = 0; b < k; b++) c[b] = Math.exp(y[b]);
|
|
311
313
|
st = state(c);
|
|
312
314
|
const f = st.A / st.denom;
|
|
@@ -315,18 +317,37 @@ function debyeHuckelModel(idx, cRef, z, epsr, a, RT) {
|
|
|
315
317
|
}
|
|
316
318
|
},
|
|
317
319
|
invert(zeta, fixed, target) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
// The free species by Newton in y = ln c, as evaluate() does, the fixed held at their targets:
|
|
321
|
+
// J = I − A s_F (s∘c)_Fᵀ over the free ones alone. (A fixed-point iteration contracted by
|
|
322
|
+
// 1 − denom, and near the convexity limit stopped short without a word.)
|
|
323
|
+
for (let b = 0; b < k; b++) {
|
|
324
|
+
y[b] = fixed[b] ? Math.log(target[b]) : zeta[b] + Math.log(cRef[b]);
|
|
325
|
+
cc[b] = Math.exp(y[b]);
|
|
326
|
+
}
|
|
327
|
+
let mx = 0;
|
|
328
|
+
for (let it = 0; it < 100; it++) {
|
|
329
|
+
const st = state(cc);
|
|
330
|
+
let dot = 0, s2cF = 0;
|
|
322
331
|
for (let b = 0; b < k; b++) {
|
|
323
332
|
if (fixed[b]) continue;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
333
|
+
lng[b] = y[b] + lng[b] - zeta[b] - Math.log(cRef[b]); // (the residual, in lng's place)
|
|
334
|
+
dot += s[b] * cc[b] * lng[b];
|
|
335
|
+
s2cF += s[b] * s[b] * cc[b];
|
|
327
336
|
}
|
|
328
|
-
|
|
337
|
+
const dF = 1 - st.A * s2cF; // (≥ denom > 0)
|
|
338
|
+
mx = 0;
|
|
339
|
+
for (let b = 0; b < k; b++) {
|
|
340
|
+
if (fixed[b]) continue;
|
|
341
|
+
let dy = lng[b] + (st.A * s[b] * dot) / dF;
|
|
342
|
+
if (dy > 1) dy = 1;
|
|
343
|
+
else if (dy < -1) dy = -1;
|
|
344
|
+
y[b] -= dy;
|
|
345
|
+
cc[b] = Math.exp(y[b]);
|
|
346
|
+
mx = Math.max(mx, Math.abs(dy));
|
|
347
|
+
}
|
|
348
|
+
if (mx < 1e-14) break;
|
|
329
349
|
}
|
|
350
|
+
if (!(mx < 1e-10)) throw new SolverError(`debye-huckel: the composition given did not converge to its levels (a step of ${mx.toExponential(2)} in ln c after 100 iterations)`);
|
|
330
351
|
state(cc);
|
|
331
352
|
for (let b = 0; b < k; b++) if (fixed[b]) zeta[b] = Math.log(target[b] / cRef[b]) + lng[b];
|
|
332
353
|
},
|
package/src/traces.js
CHANGED
|
@@ -37,6 +37,8 @@ const finite = (a) => {
|
|
|
37
37
|
*/
|
|
38
38
|
export function speciesRole(sol, name) {
|
|
39
39
|
const z = Object.fromEntries(sol.species.map((sp) => [sp.name, sp.z]));
|
|
40
|
+
if (!(name in z)) throw new DeviceError(`speciesRole: no species '${name}' in the solution`);
|
|
41
|
+
if (z[name] === 0) throw new DeviceError(`speciesRole: '${name}' is neutral, with no role in level diagrams (draw it muted, or in a colour of your own)`);
|
|
40
42
|
const role = (n) => (n === 'e-' ? 'electron' : z[n] > 0 ? 'cation' : 'anion');
|
|
41
43
|
const charged = sol.species.filter((sp) => sp.z !== 0).map((sp) => sp.name);
|
|
42
44
|
return { role: role(name), slot: charged.filter((n) => role(n) === role(name)).indexOf(name) };
|
|
@@ -88,7 +90,7 @@ export function traces(sol, { species, standard = true, phi = false, energy = fa
|
|
|
88
90
|
const shown = species ?? names.filter((name) => finite(sol.V[name]) && (!energy || carriers.includes(name)));
|
|
89
91
|
if (energy) {
|
|
90
92
|
for (const name of shown) {
|
|
91
|
-
if (!carriers.includes(name)) throw new
|
|
93
|
+
if (!carriers.includes(name)) throw new DeviceError(`traces: '${name}' has no band to draw as an energy; draw it as a species voltage (energy: false)`);
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
const sign = energy ? -1 : 1, unit = energy ? 'eV' : 'V';
|
|
@@ -98,22 +100,23 @@ export function traces(sol, { species, standard = true, phi = false, energy = fa
|
|
|
98
100
|
const pretty = (name) => (name in zOf ? typeset(name, zOf[name]) : name);
|
|
99
101
|
const series = [];
|
|
100
102
|
for (const name of shown) {
|
|
101
|
-
if (!names.includes(name)) throw new
|
|
103
|
+
if (!names.includes(name)) throw new DeviceError(`traces: no charged species '${name}' in the solution`);
|
|
102
104
|
const shift = shifts[name] ?? 0;
|
|
103
|
-
const at = (y) => (shift || energy ?
|
|
105
|
+
const at = (y) => (shift || energy ? Float64Array.from(y, (v) => sign * v + shift) : y);
|
|
104
106
|
const common = { species: name, role: role(name), slot: slotOf(name), ...(shift ? { shift } : {}) };
|
|
105
107
|
if (finite(sol.V[name])) series.push({ id: `V:${name}`, label: energy ? energyLabels[`V:${name}`] : `V_{${pretty(name)}}`, kind: 'level', ...common, y: at(sol.V[name]) });
|
|
106
108
|
if (standard && finite(sol.Vstd[name])) series.push({ id: `Vstd:${name}`, label: energy ? energyLabels[`Vstd:${name}`] : `V°_{${pretty(name)}}`, kind: 'standard', ...common, y: at(sol.Vstd[name]) });
|
|
107
109
|
}
|
|
108
110
|
if (Array.isArray(phi)) {
|
|
109
111
|
const known = sol.regions.flatMap((r) => [r.name, r.material]);
|
|
110
|
-
for (const name of phi) if (!known.includes(name)) throw new
|
|
112
|
+
for (const name of phi) if (!known.includes(name)) throw new DeviceError(`traces: no region or material '${name}' to draw φ in (${[...new Set(known)].join(', ')})`);
|
|
111
113
|
const inside = sol.regions.map((r) => phi.includes(r.name) || phi.includes(r.material));
|
|
112
|
-
series.push({ id: 'phi', label: energy ? '−qφ' : 'φ', kind: 'phi', role: 'phi', slot: 0, y:
|
|
113
|
-
} else if (phi) series.push({ id: 'phi', label: energy ? '−qφ' : 'φ', kind: 'phi', role: 'phi', slot: 0, y: energy ?
|
|
114
|
+
series.push({ id: 'phi', label: energy ? '−qφ' : 'φ', kind: 'phi', role: 'phi', slot: 0, y: Float64Array.from(sol.phi, (v, g) => (inside[sol.region[g]] ? sign * v : NaN)) });
|
|
115
|
+
} else if (phi) series.push({ id: 'phi', label: energy ? '−qφ' : 'φ', kind: 'phi', role: 'phi', slot: 0, y: energy ? Float64Array.from(sol.phi, (v) => -v) : sol.phi });
|
|
116
|
+
for (const lv of levels) if (typeof lv?.half?.equation !== 'string') throw new DeviceError('traces: each of levels is { half, label, standard }, its half a half() (as { half: half(\'Fe3+ + e- = Fe2+\') })');
|
|
114
117
|
const couples = [...new Set(levels.map((lv) => lv.half.equation))];
|
|
115
118
|
levels.forEach((lv, k) => {
|
|
116
|
-
const y = energy ?
|
|
119
|
+
const y = energy ? Float64Array.from(level(sol, lv.half, { standard: lv.standard }), (v) => -v) : level(sol, lv.half, { standard: lv.standard });
|
|
117
120
|
series.push({
|
|
118
121
|
id: `level:${k}`,
|
|
119
122
|
label: lv.label ?? `${lv.standard ? 'V° ' : ''}${lv.half.equation.split(' ').map(pretty).join(' ')}`,
|
package/src/types.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* Statistics model: how the listed species' concentrations follow from their reduced
|
|
20
20
|
* potentials ζ = (μ̄ − zFφ − μ°)/RT. Species not in any model are ideal, c = c_ref e^ζ.
|
|
21
21
|
* @typedef {(
|
|
22
|
+
* { type: 'ideal', species: string[] } |
|
|
22
23
|
* { type: 'fermi-dirac', species: string[], order?: 0.5 | 0 } |
|
|
23
24
|
* { type: 'lattice', species: string[], cMax: number } |
|
|
24
25
|
* { type: 'redlich-kister', species: [string], cMax: number, A?: number[] } |
|
|
@@ -73,19 +74,45 @@
|
|
|
73
74
|
*/
|
|
74
75
|
|
|
75
76
|
/** @typedef {'pinned' | 'neutral' | { type: 'capacitive', C: number }} InterfacePhiLaw */
|
|
76
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* A species' law across a face. conductance: J = G (V_L − V_R), G in S/m², charged species;
|
|
79
|
+
* permeability: Goldman–Hodgkin–Katz electrodiffusion, P in m/s, ideal statistics on both sides,
|
|
80
|
+
* not beside a conductor. gates: the face's gates that scale G or P, with their exponents
|
|
81
|
+
* ({ m: 3, h: 1 } for m³h; not beside a conductor).
|
|
82
|
+
* @typedef {'equilibrium' | 'blocked' | { type: 'equilibrium' | 'blocked' }
|
|
83
|
+
* | { type: 'conductance', G: number, gates?: Record<string, number> }
|
|
84
|
+
* | { type: 'permeability', P: number, gates?: Record<string, number> }} InterfaceSpeciesLink
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One of a gate's rates (1/s) in the voltage V it follows, in NeuroML's standard forms (midpoint
|
|
89
|
+
* and scale in V, scale ≠ 0): 'exp', rate·e^((V − midpoint)/scale); 'sigmoid',
|
|
90
|
+
* rate/(1 + e^((midpoint − V)/scale)); 'expLinear', rate·y/(1 − e^(−y)), y = (V − midpoint)/scale.
|
|
91
|
+
* @typedef {{ type: 'exp' | 'sigmoid' | 'expLinear', rate: number, midpoint: number, scale: number }} GateRate
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* A Hodgkin–Huxley gate: a fraction open x in [0, 1], dx/dt = α(V)(1 − x) − β(V)x.
|
|
95
|
+
* @typedef {{ alpha: GateRate, beta: GateRate }} GateDef
|
|
96
|
+
*/
|
|
77
97
|
|
|
78
98
|
/**
|
|
79
99
|
* A reaction at a face: participants on each side with signed stoichiometric coefficients
|
|
80
|
-
* (ν < 0 consumed by the forward reaction)
|
|
100
|
+
* (ν < 0 consumed by the forward reaction). Butler–Volmer kinetics (k0, alpha), or instead SRH
|
|
101
|
+
* (srh) or saturating (vmax and K), neither with a conductor's carrier.
|
|
81
102
|
* @typedef {object} FaceReactionDef
|
|
82
103
|
* @property {string} [equation] the reaction as an equation, e.g. 'Ag+ + e- = Ag(s)', each
|
|
83
104
|
* participant on the side that holds it ('Li+(left)' where both do); or give left and right
|
|
84
105
|
* @property {Record<string, number>} [left] participants on the left side (species present there, or fixed neutrals)
|
|
85
106
|
* @property {Record<string, number>} [right] participants on the right side
|
|
86
107
|
* @property {Record<string, number>} [fixed] μ (J/mol) of fixed-activity participants that aren't species
|
|
87
|
-
* @property {number} k0 standard rate constant, mol/(m²·s)
|
|
88
|
-
* @property {number} [alpha] transfer coefficient, 0…1 (default 0.5)
|
|
108
|
+
* @property {number} [k0] Butler–Volmer: standard rate constant, mol/(m²·s) (required unless srh or vmax)
|
|
109
|
+
* @property {number} [alpha] with k0: transfer coefficient, 0…1 (default 0.5)
|
|
110
|
+
* @property {{ vn: number, vp: number, n1?: number }} [srh] instead: trap-assisted recombination, for a
|
|
111
|
+
* reaction consuming one negative and one positive species and making none; capture velocities
|
|
112
|
+
* vn, vp in m/s, n1 in mol/m³ (default n_i, a midgap trap)
|
|
113
|
+
* @property {number} [vmax] instead: saturating kinetics, the largest rate, mol/(m²·s)
|
|
114
|
+
* @property {Record<string, number>} [K] with vmax: a half-saturation concentration (mol/m³) for
|
|
115
|
+
* every species the forward reaction consumes
|
|
89
116
|
*/
|
|
90
117
|
/**
|
|
91
118
|
* One face between consecutive regions. For different materials under a 'pinned' or
|
|
@@ -94,11 +121,15 @@
|
|
|
94
121
|
* @property {{ species: string, value: number }} [step] standard-level step right − left, J/mol
|
|
95
122
|
* @property {number} [dipole] φ jump right − left, V
|
|
96
123
|
* @property {number} [zeroCharge] beside a conductor region: V_F − φ_edge at zero charge, V
|
|
97
|
-
* @property {InterfacePhiLaw} [phi] electrostatic law (default 'pinned'; 'neutral' between ε = 0 materials
|
|
124
|
+
* @property {InterfacePhiLaw} [phi] electrostatic law (default 'pinned'; 'neutral' between ε = 0 materials;
|
|
125
|
+
* beside a conductor 'neutral' or capacitive, required next to an ε > 0 material)
|
|
98
126
|
* @property {number} [sheetCharge] C/m²
|
|
99
127
|
* @property {Record<string, InterfaceSpeciesLink>} [species] per-species laws (default equilibrium
|
|
100
|
-
* where present on both sides and
|
|
128
|
+
* where present on both sides and mobile on one, blocked otherwise; a species reacting at the
|
|
129
|
+
* face and present on both sides has no default)
|
|
101
130
|
* @property {FaceReactionDef[]} [reactions] reactions at the face
|
|
131
|
+
* @property {Record<string, GateDef>} [gates] voltage-gated channels: gates following
|
|
132
|
+
* V = φ_right − φ_left, which its links name (not beside a conductor)
|
|
102
133
|
*/
|
|
103
134
|
|
|
104
135
|
/**
|
|
@@ -106,14 +137,18 @@
|
|
|
106
137
|
* @property {Record<string, number>} [nu] signed stoichiometric coefficients (ν < 0 consumed)
|
|
107
138
|
* @property {string} [equation] the reaction as an equation, e.g. 'e- + h+ = 0' (instead of nu)
|
|
108
139
|
* @property {Record<string, number>} [fixed] μ (J/mol) of fixed-activity participants that aren't species
|
|
109
|
-
* @property {Record<string, number | Profile>} kf forward rate constant per material
|
|
110
|
-
* profile of it against the device's x (an absorption profile, for photogeneration)
|
|
140
|
+
* @property {Record<string, number | Profile>} [kf] mass action: forward rate constant per material
|
|
141
|
+
* name, or a profile of it against the device's x (an absorption profile, for photogeneration);
|
|
142
|
+
* give kf or srh
|
|
143
|
+
* @property {Record<string, { tauN: number, tauP: number, n1?: number }>} [srh] instead: SRH
|
|
144
|
+
* (trap-assisted) recombination per material name, for a reaction consuming one negative and
|
|
145
|
+
* one positive species and making none; lifetimes in s, n1 in mol/m³ (default n_i, a midgap trap)
|
|
111
146
|
*/
|
|
112
147
|
|
|
113
148
|
/**
|
|
114
149
|
* Contacts use the same laws as internal faces; the outside is a phase with known levels
|
|
115
150
|
* V_i = V + offset_i (a rigid ladder that the circuit slides by V).
|
|
116
|
-
* @typedef {'blocked' | 'equilibrium'
|
|
151
|
+
* @typedef {'blocked' | 'equilibrium' | { type: 'blocked' }
|
|
117
152
|
* | { type: 'equilibrium', offset?: number, mu?: number }
|
|
118
153
|
* | { type: 'conductance', G: number, offset?: number }
|
|
119
154
|
* | { type: 'exchange', k: number, mu: number }
|
|
@@ -133,9 +168,11 @@
|
|
|
133
168
|
* @property {number} [R] with V: a series resistance, Ω·m² (a source behind a resistor)
|
|
134
169
|
* @property {string} [terminal] species whose voltage V is
|
|
135
170
|
* @property {Record<string, ContactSpeciesLink>} [species] per-species links (default blocked)
|
|
136
|
-
* @property {{ c: Record<string, number>, reference
|
|
137
|
-
* composition held at the contact, anchored through a
|
|
138
|
-
*
|
|
171
|
+
* @property {{ c: Record<string, number>, reference?: string, offset?: number, material?: string }} [bath]
|
|
172
|
+
* instead of species: a neutral composition c (mol/m³) held at the contact, anchored through a
|
|
173
|
+
* charged reference species (offset: its V_i − V, V); with no reference, V is the bath's φ. It's
|
|
174
|
+
* the end material at that composition, or the material it names (a solution beside a charged region)
|
|
175
|
+
* @property {ContactPhiLink} [phi] required when any species connects (default 'neutral'; 'bulk' with a bath)
|
|
139
176
|
* @property {number} [zeroCharge] for a capacitive or pinned law: V − φ_edge at zero charge, V
|
|
140
177
|
*/
|
|
141
178
|
|
|
@@ -158,20 +195,27 @@
|
|
|
158
195
|
* @property {Source} [I] or: the current driven into the device, A/m² (e.g. 0 for a reference electrode)
|
|
159
196
|
* @property {number} [R] with V: a series resistance, Ω·m²
|
|
160
197
|
* @property {string} [terminal] species whose offset defaults to 0; with reactions, the electrode's carrier
|
|
161
|
-
* @property {Record<string, 'blocked' | 'equilibrium' | { type: 'equilibrium', offset?: number, mu?: number }
|
|
162
|
-
* | { type: 'conductance', G: number, offset?: number
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
198
|
+
* @property {Record<string, 'blocked' | 'equilibrium' | { type: 'blocked' } | { type: 'equilibrium', offset?: number, mu?: number }
|
|
199
|
+
* | { type: 'conductance', G: number, offset?: number, gates?: Record<string, number> }
|
|
200
|
+
* | { type: 'exchange', k: number, mu: number }>} [species] per-species links, per volume (default
|
|
201
|
+
* blocked): G in S/m³ (S/m² on a conductor), k in mol/(m³·s); a conductance's gates name the
|
|
202
|
+
* port's gates that scale G, with their exponents
|
|
203
|
+
* @property {{ c: Record<string, number> }} [bath] the outside's composition (mol/m³): V is then its φ,
|
|
204
|
+
* and each linked species' level there follows from it (unless its link gives an offset or mu)
|
|
205
|
+
* @property {{ equation: string, fixed?: Record<string, number>, k0: number, alpha?: number, bare?: boolean }[]} [reactions] an
|
|
206
|
+
* electrode spread through the window: Butler–Volmer per area (k0 in mol/(m²·s), alpha default 0.5)
|
|
207
|
+
* between the region's species and the terminal species at the port's level
|
|
166
208
|
* @property {Record<string, { mu0: number, capacity: number, theta0?: number }>} [surface] with reactions: species
|
|
167
209
|
* on the electrode's sites, Langmuir (μ = μ° + RT ln(θ/θ₀)), capacity in mol of sites per m² of electrode,
|
|
168
|
-
* shared; reactions run on Langmuir sites (what fills
|
|
169
|
-
* needs a bare site even if it fills none (θ₀ = 1 − Σθ)
|
|
210
|
+
* shared, theta0 the starting coverage (default 1e-6); reactions run on Langmuir sites (what fills
|
|
211
|
+
* sites needs bare ones), and one with `bare: true` needs a bare site even if it fills none (θ₀ = 1 − Σθ)
|
|
170
212
|
* @property {{ C: number, zeroCharge?: number }} [capacitance] a capacitance spread through the window (F/m² of
|
|
171
213
|
* electrode): σ = C (V − zeroCharge − φ) on the port's side, aσ in each node's charge balance; a gate
|
|
172
214
|
* along a channel, a double layer
|
|
173
215
|
* @property {number | { x: number[], values: number[] }} [area] with reactions or a capacitance: electrode area per volume,
|
|
174
216
|
* m²/m³ (1/h for a film of thickness h), or a profile against the device's x
|
|
217
|
+
* @property {Record<string, GateDef>} [gates] with a capacitance: voltage-gated channels at each node of
|
|
218
|
+
* the window, following the voltage across it, φ − (V − zeroCharge), which its conductance links name
|
|
175
219
|
*/
|
|
176
220
|
|
|
177
221
|
/**
|
|
@@ -198,6 +242,7 @@
|
|
|
198
242
|
* @property {Record<string, number>} flux particle flux toward +x through this contact, mol/(m²·s)
|
|
199
243
|
* @property {number} D displacement toward +x at this contact, C/m²
|
|
200
244
|
* @property {number} current total current toward +x, A/m²
|
|
245
|
+
* @property {Record<string, string>} links how each species that crosses it does (its link type)
|
|
201
246
|
*/
|
|
202
247
|
|
|
203
248
|
/**
|
|
@@ -216,6 +261,11 @@
|
|
|
216
261
|
* @property {Record<string, Float64Array>} muStd standard levels μ° + zFφ, J/mol
|
|
217
262
|
* @property {Record<string, Float64Array>} V species voltages μ̄/(zF), V (charged species)
|
|
218
263
|
* @property {Record<string, Float64Array>} Vstd standard levels as voltages, V
|
|
264
|
+
* @property {Record<string, Float64Array>} flux each species' flux toward +x across each segment, between
|
|
265
|
+
* nodes g and g + 1 (a face's across its pair of nodes), mol/(m²·s): the fluxes the balances add up
|
|
266
|
+
* @property {Record<string, Float64Array>} D diffusivity at each node, m²/s (NaN where absent, or in a conductor)
|
|
267
|
+
* @property {number} T temperature, K
|
|
268
|
+
* @property {'planar' | 'cylindrical' | 'spherical' | 'profile'} geometry the device's (amounts and rates per area only if planar)
|
|
219
269
|
* @property {number} current current toward +x through the device, A/m²
|
|
220
270
|
* @property {number} terminalVoltage V_right − V_left, V
|
|
221
271
|
* @property {Record<string, { V: number, current: number }>} terminals each terminal (left, right,
|
|
@@ -225,31 +275,48 @@
|
|
|
225
275
|
* Stern plate at a capacitive contact
|
|
226
276
|
* @property {{ name: string, V: number, flux: Record<string, number>, current: number, x?: Float64Array,
|
|
227
277
|
* area?: Float64Array, rates?: Float64Array[], coverage?: Record<string, Float64Array>, bare?: Float64Array,
|
|
228
|
-
* sigma?: Float64Array, charge?: number }[]} ports
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
278
|
+
* sigma?: Float64Array, charge?: number, gates?: Record<string, Float64Array>, Vm?: Float64Array }[]} ports
|
|
279
|
+
* what each internal port brings into the device (current: the charging current included); with
|
|
280
|
+
* reactions or a capacitance, also its window's nodes (x, m) and area per volume there (m²/m³); with
|
|
281
|
+
* reactions, each reaction's net rate per area at each node (mol/(m²·s), forward); with a surface, each
|
|
282
|
+
* species' coverage and the bare fraction; with a capacitance, σ per area (C/m²) and the charge its
|
|
283
|
+
* side holds; with gates, each gate's fraction open at each node and the voltage they follow (V)
|
|
284
|
+
* @property {{ left: string, right: string, dipole: number, sheetCharge: number, D: number, N: Record<string, number>,
|
|
285
|
+
* rates: number[], links: Record<string, string>, oneWay?: Record<string, [number, number]>,
|
|
286
|
+
* gates?: Record<string, number>, V?: number }[]} interfaces each face: the regions it joins, its dipole
|
|
287
|
+
* (V) and sheet charge (C/m²), its displacement D (C/m²), each species' flux N across it and each face
|
|
288
|
+
* reaction's net rate (mol/(m²·s), toward +x and forward), how each species that crosses it does (its
|
|
289
|
+
* link type); for those crossing by permeability, the one-way fluxes [toward +x, toward −x] whose
|
|
290
|
+
* difference is N; with gates, each one's fraction open and the voltage they follow, φ_right − φ_left (V)
|
|
291
|
+
* @property {{ rate: Float64Array, forward: Float64Array, nu: Record<string, number>, regions: number[], total: number }[]} bulkReactions
|
|
292
|
+
* each bulk reaction's net rate per node (mol/(m³·s), forward minus backward, NaN where it doesn't
|
|
293
|
+
* run), its one-way forward rate (the backward is forward − rate), what one forward reaction makes
|
|
294
|
+
* of each species (ν, negative for what it consumes), and the net rate per region and in all (mol/(m²·s))
|
|
234
295
|
* @property {number} charge total charge in the device, C/m²
|
|
235
296
|
* @property {{ species: string, regions: number[], spectator: boolean, connected: boolean, reactive: boolean,
|
|
236
297
|
* amount: number, reference: number, intake: number, drift: number }[]} conservation
|
|
237
298
|
* @property {string[]} warnings
|
|
238
299
|
* @property {boolean} converged
|
|
239
|
-
* @property {number} iterations
|
|
300
|
+
* @property {number} iterations Newton iterations
|
|
301
|
+
* @property {number} [steps] solve(): the steps it took to the steady state; advance(): accepted steps
|
|
302
|
+
* @property {number} [substeps] step(): how many pieces the interval was split into
|
|
303
|
+
* @property {(number | object)[]} [history] solver bookkeeping: solve(), a record of its steps and
|
|
304
|
+
* stages; step(), Newton's largest update per iteration (thermal units)
|
|
240
305
|
* @property {number} time s
|
|
241
306
|
* @property {boolean} steady whether this is a converged steady state (solve()), not a transient's
|
|
242
307
|
* @property {boolean} [done] advance(): whether tEnd was reached (false if the frame budget ran out)
|
|
243
308
|
* @property {number} [rejected] advance(): steps rejected by error control or Newton failure
|
|
244
309
|
* @property {{ t: number[], current: number[], voltage: number[], probes?: number[][] }} [trace] advance():
|
|
245
|
-
*
|
|
246
|
-
*
|
|
310
|
+
* the right contact's current (A/m², toward +x) and V_right − V_left after each accepted step, and
|
|
311
|
+
* each probe's reading: inside, interpolated between the nodes around its x (mol/m³ for quantity
|
|
312
|
+
* 'c', V for 'V' or 'phi'); at a face, a species' flux through it (mol/(m²·s), toward +x) or a
|
|
313
|
+
* gate's fraction open
|
|
247
314
|
*/
|
|
248
315
|
|
|
249
316
|
/**
|
|
250
317
|
* @typedef {object} ImpedanceResult
|
|
251
318
|
* @property {Float64Array} f frequencies, Hz
|
|
252
|
-
* @property {{ re: Float64Array, im: Float64Array }} Z impedance
|
|
319
|
+
* @property {{ re: Float64Array, im: Float64Array }} Z impedance δV/δI at the terminal, I into the device, Ω·m²
|
|
253
320
|
* @property {{ phi: ComplexProfile, mu: Record<string, ComplexProfile>, c: Record<string, ComplexProfile> }[]} [profiles]
|
|
254
321
|
* per frequency, per unit excitation (volt, or A/m² in current mode)
|
|
255
322
|
* @property {string[]} warnings the steady state's warnings, which hold for its impedance too
|
package/types/index.d.ts
CHANGED
|
@@ -48,11 +48,14 @@ export class Device {
|
|
|
48
48
|
* Adaptive transient to time tEnd (s): variable-step BDF2 with local error control. With
|
|
49
49
|
* `budgetMs`, it returns after that much wall time even if tEnd isn't reached (`done` says
|
|
50
50
|
* which), so an animation can call it once per frame; the step size carries over. `probes`
|
|
51
|
-
* read a species at points inside the device after every accepted step, into
|
|
52
|
-
* (one array per probe, alongside `trace.t`): what a detector at x sees.
|
|
51
|
+
* read a species (or φ) at points inside the device after every accepted step, into
|
|
52
|
+
* `trace.probes` (one array per probe, alongside `trace.t`): what a detector at x sees. At a
|
|
53
|
+
* face, `{ interface, species }` reads a species' flux through it (mol/(m²·s), toward +x), and
|
|
54
|
+
* `{ interface, gate }` a gate's fraction open.
|
|
53
55
|
* @param {number} tEnd s
|
|
54
56
|
* @param {{ tol?: number, dt0?: number, dtMax?: number, budgetMs?: number, maxSteps?: number, method?: 'bdf2' | 'be',
|
|
55
|
-
* probes?: { x: number, species?: string, quantity?: 'c' | 'V' | 'phi', region?: string | number }
|
|
57
|
+
* probes?: ({ x: number, species?: string, quantity?: 'c' | 'V' | 'phi', region?: string | number }
|
|
58
|
+
* | { interface: number, species: string } | { interface: number, gate: string })[] }} [opts]
|
|
56
59
|
* @returns {Solution}
|
|
57
60
|
*/
|
|
58
61
|
advance(tEnd: number, opts?: {
|
|
@@ -62,12 +65,18 @@ export class Device {
|
|
|
62
65
|
budgetMs?: number;
|
|
63
66
|
maxSteps?: number;
|
|
64
67
|
method?: "bdf2" | "be";
|
|
65
|
-
probes?: {
|
|
68
|
+
probes?: ({
|
|
66
69
|
x: number;
|
|
67
70
|
species?: string;
|
|
68
71
|
quantity?: "c" | "V" | "phi";
|
|
69
72
|
region?: string | number;
|
|
70
|
-
}
|
|
73
|
+
} | {
|
|
74
|
+
interface: number;
|
|
75
|
+
species: string;
|
|
76
|
+
} | {
|
|
77
|
+
interface: number;
|
|
78
|
+
gate: string;
|
|
79
|
+
})[];
|
|
71
80
|
}): Solution;
|
|
72
81
|
/**
|
|
73
82
|
* Small-signal impedance Z(f) about the steady state (solved first), at one terminal: its
|
package/types/kit.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function vacuumLevel(def: object, side: VacuumSide, path?: string): numbe
|
|
|
25
25
|
export function vacuumDipole(def: object, left: VacuumSide, right: VacuumSide): number;
|
|
26
26
|
/**
|
|
27
27
|
* The vacuum-level (Schottky–Mott-type) estimate of a `zeroCharge` where a conductor meets a
|
|
28
|
-
* material: at a conductor region's face, or a contact's capacitive or
|
|
28
|
+
* material: at a conductor region's face, or a contact's capacitive or pinned law. The conductor's
|
|
29
29
|
* vacuum level sits its work function W beyond its Fermi level, so
|
|
30
30
|
* zeroCharge = V_F − φ_edge at zero charge = W + (V_vac − φ)_inside.
|
|
31
31
|
* @param {object} def device definition
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function applyLaws(s: any, dt: any): void;
|
|
2
|
+
export function captureLaws(s: any): void;
|
|
3
|
+
export function captureLocal(s: any): void;
|
|
4
|
+
export function solveBordered(s: any, rhs: any, delta: any, deltaV: any, pins: any): void;
|
|
5
|
+
export function snapFlat(s: any): void;
|
package/types/laws.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export function moves({ model }: {
|
|
2
|
+
model: any;
|
|
3
|
+
}, st: any): boolean;
|
|
4
|
+
export function still({ model }: {
|
|
5
|
+
model: any;
|
|
6
|
+
}, st: any): boolean;
|
|
7
|
+
export function carrierZ({ model, z }: {
|
|
8
|
+
model: any;
|
|
9
|
+
z: any;
|
|
10
|
+
}, k: any): any;
|
|
11
|
+
export function contactCharges({ model, nNodes, phiUndefined }: {
|
|
12
|
+
model: any;
|
|
13
|
+
nNodes: any;
|
|
14
|
+
phiUndefined: any;
|
|
15
|
+
}, k: any): boolean;
|
|
16
|
+
export function feeds(k: any, st: any): any;
|
|
17
|
+
export function noCurrent(t: any): boolean;
|
|
18
|
+
export function chargeOnly({ model, terms }: {
|
|
19
|
+
model: any;
|
|
20
|
+
terms: any;
|
|
21
|
+
}, k: any): boolean;
|
|
22
|
+
export function conservedMoieties(s: any, { drives, alsoCurrent, skip }?: {
|
|
23
|
+
drives?: boolean;
|
|
24
|
+
alsoCurrent?: any[];
|
|
25
|
+
skip?: any;
|
|
26
|
+
}): Float64Array<any>[];
|
|
27
|
+
export function findLaws(s: any): {
|
|
28
|
+
constraints: any[];
|
|
29
|
+
affinities: {
|
|
30
|
+
rx: any;
|
|
31
|
+
f: any;
|
|
32
|
+
row: any;
|
|
33
|
+
idx: Int32Array<any>;
|
|
34
|
+
w: Float64Array<any>;
|
|
35
|
+
len: number;
|
|
36
|
+
res: number;
|
|
37
|
+
}[];
|
|
38
|
+
localConstraints: {
|
|
39
|
+
parts: ({
|
|
40
|
+
i: any;
|
|
41
|
+
w: number;
|
|
42
|
+
q?: undefined;
|
|
43
|
+
k?: undefined;
|
|
44
|
+
} | {
|
|
45
|
+
q: any;
|
|
46
|
+
k: any;
|
|
47
|
+
w: number;
|
|
48
|
+
i?: undefined;
|
|
49
|
+
})[];
|
|
50
|
+
row: any;
|
|
51
|
+
nodes: any;
|
|
52
|
+
reference: Float64Array<any>;
|
|
53
|
+
}[];
|
|
54
|
+
islands: {
|
|
55
|
+
stretch: any;
|
|
56
|
+
row: any;
|
|
57
|
+
flux: any[];
|
|
58
|
+
weight: number[];
|
|
59
|
+
outside: any[];
|
|
60
|
+
idx: Int32Array<ArrayBuffer>;
|
|
61
|
+
w: Float64Array<ArrayBuffer>;
|
|
62
|
+
len: number;
|
|
63
|
+
res: number;
|
|
64
|
+
}[];
|
|
65
|
+
timeLaws: {
|
|
66
|
+
parts: {
|
|
67
|
+
stretch: number;
|
|
68
|
+
w: number;
|
|
69
|
+
}[];
|
|
70
|
+
surface: {
|
|
71
|
+
col: number;
|
|
72
|
+
w: number;
|
|
73
|
+
}[];
|
|
74
|
+
carriers: any;
|
|
75
|
+
terminals: any[];
|
|
76
|
+
circuit: any;
|
|
77
|
+
lambda: number;
|
|
78
|
+
idx: Int32Array<ArrayBuffer>;
|
|
79
|
+
w: Float64Array<ArrayBuffer>;
|
|
80
|
+
len: number;
|
|
81
|
+
}[];
|
|
82
|
+
contactLaw: boolean[];
|
|
83
|
+
carriers: any;
|
|
84
|
+
conserved: Set<any>;
|
|
85
|
+
unkept: Float64Array<any>[];
|
|
86
|
+
};
|
package/types/plot.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The colour of a trace's role and slot (a CSS colour), for drawing other charts in the same
|
|
3
3
|
* colours as the level diagrams.
|
|
4
|
-
* @param {'electron' | 'cation' | 'anion' | 'redox'} role
|
|
4
|
+
* @param {'electron' | 'cation' | 'anion' | 'redox' | 'phi'} role
|
|
5
5
|
* @param {number} slot
|
|
6
6
|
* @param {{ dark?: boolean }} [opts]
|
|
7
7
|
*/
|
|
8
|
-
export function themeColor(role: "electron" | "cation" | "anion" | "redox", slot: number, { dark }?: {
|
|
8
|
+
export function themeColor(role: "electron" | "cation" | "anion" | "redox" | "phi", slot: number, { dark }?: {
|
|
9
9
|
dark?: boolean;
|
|
10
10
|
}, ...args: any[]): string;
|
|
11
11
|
/**
|