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/live.js
CHANGED
|
@@ -57,26 +57,31 @@ export class Session {
|
|
|
57
57
|
if (this.good && errors.every(Boolean)) return done(this.good.sol, { failed: true }); // nothing changed
|
|
58
58
|
const target = this.device.def;
|
|
59
59
|
try {
|
|
60
|
-
let sol = this.device.solve();
|
|
61
|
-
// Ramp from the last good state in more and more steps.
|
|
62
|
-
|
|
60
|
+
let sol = this.device.solve(), stuck = false;
|
|
61
|
+
// Ramp from the last good state in more and more steps. (Its in-between states can be
|
|
62
|
+
// invalid where the two ends aren't, a bath neutral at both but not between: then on to the
|
|
63
|
+
// fresh solve.)
|
|
64
|
+
ramping: for (let n = 2; !sol.converged && this.good && n <= this.maxRamp; n *= 2) {
|
|
63
65
|
ramp = n;
|
|
64
66
|
this.device._rollback(this.good.cp);
|
|
65
67
|
const from = this.good.cp.def;
|
|
66
68
|
for (let k = 1; k <= n; k++) {
|
|
67
|
-
|
|
69
|
+
try {
|
|
70
|
+
this.device.set(between(from, target, k / n));
|
|
71
|
+
} catch {
|
|
72
|
+
stuck = true; // (and sol, a converged step short of the target, isn't the answer)
|
|
73
|
+
break ramping;
|
|
74
|
+
}
|
|
68
75
|
sol = this.device.solve();
|
|
69
76
|
if (!sol.converged) break;
|
|
70
77
|
}
|
|
71
78
|
}
|
|
72
|
-
if (!sol.converged) {
|
|
79
|
+
if (stuck || !sol.converged) {
|
|
73
80
|
// Last, from scratch (with the solver's own continuations).
|
|
74
81
|
const fresh = new Device(target);
|
|
75
82
|
const cold = fresh.solve();
|
|
76
|
-
if (cold.converged)
|
|
77
|
-
|
|
78
|
-
sol = cold;
|
|
79
|
-
}
|
|
83
|
+
if (cold.converged) this.device = fresh;
|
|
84
|
+
if (cold.converged || stuck) sol = cold;
|
|
80
85
|
}
|
|
81
86
|
if (sol.converged) {
|
|
82
87
|
this.good = { cp: this.device._checkpoint(), sol };
|
package/src/particles.js
CHANGED
|
@@ -55,6 +55,8 @@ const NONE = 0, DOT = 1, OUTSIDE = 2; // nothing (closed); a cell of dots; a sea
|
|
|
55
55
|
*/
|
|
56
56
|
export function particles(sol, { species, dots = 200, weight, cap = Infinity, cells = 120, random = Math.random } = {}) {
|
|
57
57
|
checkOptions('particles', arguments[1], ['species', 'dots', 'weight', 'cap', 'cells', 'random']);
|
|
58
|
+
// (amounts per area, against fluxes that off a plane are per device: planar only)
|
|
59
|
+
if (sol.geometry !== undefined && sol.geometry !== 'planar') throw new RangeError(`particles: a planar device's solution, not a ${sol.geometry} one`);
|
|
58
60
|
const names = species ?? sol.species.map((s) => s.name).filter((nm) => sol.c[nm].some((v) => v > 0));
|
|
59
61
|
for (const nm of names) if (!sol.c[nm]) throw new RangeError(`particles: no species ${JSON.stringify(nm)} in the solution`);
|
|
60
62
|
if (!(dots > 0 && Number.isFinite(dots))) throw new RangeError(`particles: dots must be a positive number, got ${dots}`);
|
|
@@ -172,7 +174,8 @@ export function particles(sol, { species, dots = 200, weight, cap = Infinity, ce
|
|
|
172
174
|
}
|
|
173
175
|
[left[k], right[k]] = [side(k - 1), side(k)];
|
|
174
176
|
const hD = A.h / 2 / D[A.mid] + B.h / 2 / D[B.mid];
|
|
175
|
-
|
|
177
|
+
// (none of it where either cell is empty: an underflow to 0 makes both ψ infinite)
|
|
178
|
+
pair(k, J[A.b], N[k - 1] > 0 && N[k] > 0 ? (Math.sqrt((N[k - 1] / A.h) * (N[k] / B.h)) * shape(psi[k] - psi[k - 1])) / hD : 0);
|
|
176
179
|
} else if (A || B) {
|
|
177
180
|
// out of the cells: through a contact, or a face to a metal
|
|
178
181
|
const cl = A ?? B, g = A ? A.b : B.a;
|
package/src/plot.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { traces } from './traces.js';
|
|
16
16
|
import { powi } from './pow.js';
|
|
17
|
-
import { checkOptions } from './errors.js';
|
|
17
|
+
import { DeviceError, checkOptions } from './errors.js';
|
|
18
18
|
|
|
19
19
|
// Colours by role, light and dark (validated together for colour-vision deficiency; line weight,
|
|
20
20
|
// dash and the legend carry identity too). A role with more members than colours cycles.
|
|
@@ -23,16 +23,20 @@ export const THEME = Object.freeze({
|
|
|
23
23
|
dark: { electron: ['#5b95d6'], cation: ['#e8584f', '#d0772a', '#d65d95', '#b88a3e'], anion: ['#219c8f', '#2aa5bb', '#3aa874'], redox: ['#8b7ff0', '#a86bc9', '#7a9ae0'] },
|
|
24
24
|
});
|
|
25
25
|
const ROLES = ['electron', 'cation', 'anion', 'redox'];
|
|
26
|
+
const MUTED = { light: '#52514e', dark: '#c3c2b7' }; // (φ's, and lines of no species)
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* The colour of a trace's role and slot (a CSS colour), for drawing other charts in the same
|
|
29
30
|
* colours as the level diagrams.
|
|
30
|
-
* @param {'electron' | 'cation' | 'anion' | 'redox'} role
|
|
31
|
+
* @param {'electron' | 'cation' | 'anion' | 'redox' | 'phi'} role
|
|
31
32
|
* @param {number} slot
|
|
32
33
|
* @param {{ dark?: boolean }} [opts]
|
|
33
34
|
*/
|
|
34
35
|
export function themeColor(role, slot, { dark = false } = {}) {
|
|
35
36
|
checkOptions('themeColor', arguments[2], ['dark']);
|
|
37
|
+
if (role === 'phi') return MUTED[dark ? 'dark' : 'light'];
|
|
38
|
+
if (!ROLES.includes(role)) throw new DeviceError(`themeColor: no role '${role}' (${[...ROLES, 'phi'].join(', ')})`);
|
|
39
|
+
if (!(Number.isInteger(slot) && slot >= 0)) throw new DeviceError(`themeColor: slot must be a whole number ≥ 0, got ${slot}`);
|
|
36
40
|
const list = THEME[dark ? 'dark' : 'light'][role];
|
|
37
41
|
return list[slot % list.length];
|
|
38
42
|
}
|
|
@@ -118,8 +122,8 @@ function lengthUnit(L) {
|
|
|
118
122
|
|
|
119
123
|
const style = (id) => {
|
|
120
124
|
const vars = (mode) => ROLES.flatMap((role) => THEME[mode][role].map((c, k) => `--driftlet-${role}${role === 'electron' ? '' : `-${k + 1}`}:${c};`)).join('');
|
|
121
|
-
const light = `${vars('light')}--driftlet-ink:#0b0b0b;--driftlet-muted
|
|
122
|
-
const dark = `${vars('dark')}--driftlet-ink:#ffffff;--driftlet-muted
|
|
125
|
+
const light = `${vars('light')}--driftlet-ink:#0b0b0b;--driftlet-muted:${MUTED.light};--driftlet-grid:#e4e3df;--driftlet-band:#f1f0ec;--driftlet-face:#a8a7a1`;
|
|
126
|
+
const dark = `${vars('dark')}--driftlet-ink:#ffffff;--driftlet-muted:${MUTED.dark};--driftlet-grid:#3a3a37;--driftlet-band:#262624;--driftlet-face:#6b6a64`;
|
|
123
127
|
return (
|
|
124
128
|
`<style>` +
|
|
125
129
|
`:where(#${id}){${light}}` +
|
package/src/solution.js
CHANGED
|
@@ -27,6 +27,7 @@ export function makeSolution(solver, result = {}) {
|
|
|
27
27
|
V: {},
|
|
28
28
|
Vstd: {},
|
|
29
29
|
T: model.T,
|
|
30
|
+
geometry: model.geometry.type, // (planar, or amounts per device rather than per area)
|
|
30
31
|
time: solver.time,
|
|
31
32
|
steady: solver.atSteady,
|
|
32
33
|
converged: result.converged ?? true,
|