driftlet 1.2.3 → 1.3.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/src/solution.js CHANGED
@@ -239,7 +239,11 @@ export function makeSolution(solver, result = {}) {
239
239
  );
240
240
  }
241
241
  });
242
- const cond = solver.conditioning, un = solver.unreached, ch = solver.charging;
242
+ const cond = solver.conditioning, un = solver.unreached, ch = solver.charging, lost = result.unsolved ? solver.drifted : null; // (a failed steady solve's: see Device.solve)
243
+ // A failed solve with a reaction that a steady state needs at equilibrium, left far from it (see
244
+ // stuckReactions): one too slow for the steady solve's time steps, which reach ~1e12 of the
245
+ // slowest diffusion time (Y = Z at kf 1e-10/s failed with nothing said).
246
+ const slow = result.unsolved && !ch && !un && !lost ? solver.stuckReactions() : [];
243
247
  if (result.converged === false && ch) {
244
248
  const A = model.geometry.type === 'planar' ? 'A/m²' : 'A';
245
249
  sol.warnings.push(
@@ -261,6 +265,19 @@ export function makeSolution(solver, result = {}) {
261
265
  ? ' It passes almost nothing steadily at any voltage, so it may only store what comes in (a host filling, a capacitor charging), which has no steady state at a current: advance() in time instead.'
262
266
  : ''),
263
267
  );
268
+ } else if (lost) {
269
+ sol.warnings.push(
270
+ `the solve did not converge: the amount of ${lost.join(' + ')}, which no row of the steady system keeps (it runs through a species ` +
271
+ 'that moves in part of the device only), is kept by time steps alone, and steps long enough for the slowest species lost it to round-off. ' +
272
+ 'advance() in time instead, or give the still species a small D where it is still.',
273
+ );
274
+ } else if (slow.length > 0) {
275
+ for (const { where, affinity } of slow) {
276
+ sol.warnings.push(
277
+ `the solve did not converge, likely because ${where} is out of equilibrium by ${(affinity * solver.VT * 1e3).toPrecision(3)} mV of affinity, yet nothing else makes or uses what it changes, so a steady state needs it at equilibrium: ` +
278
+ 'it is too slow for the steady solve to wait out. Raise its rate constant (the steady state does not depend on it), or advance() in time.',
279
+ );
280
+ }
264
281
  } else if (result.converged === false && cond && cond.digits > 12) {
265
282
  const digits = Number.isFinite(cond.digits) ? `lost ${cond.digits.toFixed(0)} of its ~16 digits` : 'was exactly singular';
266
283
  // An ion there so scarce beside the others (a minority swept out of a junction by a large
@@ -287,7 +304,14 @@ export function makeSolution(solver, result = {}) {
287
304
  : '')),
288
305
  );
289
306
  }
290
- if (result.converged === false && !result.stopped && !un && !ch && !(cond && cond.digits > 12)) {
307
+ if (result.converged === false && !result.stopped && !un && !ch && solver.violent?.length > 0) {
308
+ // A cold start far from where a floating double layer's neighbours balance (an electrode's
309
+ // reactions with its starting coverages, an ion link): Newton has to cross that gap at once.
310
+ sol.warnings.push(
311
+ `the solve did not converge from a start far from equilibrium: ${solver.violent.join('; ')}, its double layer uncharged. ` +
312
+ 'Hold that terminal at a voltage first, solve(), then let it float (set its I: 0): it keeps the state the held one gave it.',
313
+ );
314
+ } else if (result.converged === false && !result.stopped && !un && !ch && !lost && slow.length === 0 && !(cond && cond.digits > 12)) {
291
315
  sol.warnings.push(
292
316
  'the solve did not converge, and nothing specific showed why. ' +
293
317
  'Check the definition with describe() (from driftlet/kit); approach this state in smaller steps (a sweep, or advance() in time); or refine the grid.',