Skip to content

Remove Ion.positive: the conductivity uses concentrations as they are - #23

Merged
jorgensd merged 2 commits into
mainfrom
dokken/proton-membrane
Nov 12, 2026
Merged

jorgensd merged 2 commits into
mainfrom
dokken/proton-membrane

Conversation

@jorgensd

Copy link
Copy Markdown
Member

What changed

  • Ion no longer has the positive flag. Electrolyte.concentration(name) has no positive argument and always returns the concentration with its sign: the field of a transported ion, or the electroneutrality expression for the balanced one.
  • Electrolyte.conductivity now uses the concentrations as they are. Before, it used the smooth positive part (c + sqrt(c^2 + floor^2)) / 2 of each flagged ion.
  • The kinetics are unchanged. ButlerVolmer and its subclasses still take the positive part of the reduced and oxidized ions, now by calling Electrolyte.positive_part explicitly. Before, the flag had no effect on them: they always passed positive=True. The Ion docstring wrongly claimed that the flag controlled the kinetics.
  • Electrolyte.current_density now states that it is the charge the ions carry, F sum_j z_j N_j.
  • Callers drop the positive=True and positive=False arguments: the 1D, 2D and 3D vanadium demos, the half-cell test fixtures, RedoxHalfCell.minimum_balanced_concentration, and the stoichiometry in kinetics.py. The 1D demo's text now says that the kinetics take the positive part.

Why

A concentration can undershoot zero, for example in Newton iterates, in Galerkin oscillations at advection-dominated fronts, or where an active ion runs out at high current.

  • The kinetics need a positive part. In k_a c_red - k_c c_ox, a negative concentration flips the sign of its term, so the reaction keeps consuming an ion that isn't there and drives it further negative. Nernst's ln(c_ox / c_red) becomes NaN.
  • The conductivity doesn't need one.
    • For c >= 0, the positive part differs from c only by about the floor (1e-14 mol/m^3).
    • Below zero, it only removes that ion's share of the conductivity, which stays positive anyway because the supporting ions dominate it.
    • It also broke consistency with Nernst-Planck transport. Migration uses the raw concentration, and -kappa grad(phi) - F sum_j z_j D_j grad(c_j) equals F sum_j z_j N_j only if kappa uses the same concentrations. With the flag, conduction did not carry the charge the ions moved wherever a flagged ion went negative.

Ions of higher valence

The positive part acts on the concentration, not the charge, so an ion's valence doesn't decide whether it needs one. Valence affects how much an undershoot costs:

  • Conductivity: each ion counts with z^2 D c. A negative V2+ or SO4 2- concentration lowers kappa four times as much as a monovalent ion would, and V3+ nine times as much. That is a reason to keep these concentrations exact, not to clip them.
  • Electroneutrality: the balanced SO4 2- is sum_j z_j c_j / 2 over the other ions, so an error delta in V3+ shifts sulfate by 3 delta / 2. RedoxHalfCell.minimum_balanced_concentration checks for this.
  • The balanced ion is never clipped. Taking a positive part of it would break electroneutrality. A negative minimum is reported by the diagnostic, not hidden.

Tests

  • New regression test, test_the_current_is_the_charge_the_ions_carry in tests/electrochemistry/test_electrolyte.py. It checks that the electrolyte current equals F sum_j z_j N_j over every ion, including sulfate from electroneutrality.
    • The setup is a porous region (Bruggeman) with advection, and a V2+ profile that undershoots zero over a third of the square.
    • The tolerance is relative to the ions' own charge fluxes, which largely cancel.
    • With the old conductivity the test fails: the squared error is 9e-8 against a tolerance of 1e-17.
  • test_positive_ions_take_the_positive_part is replaced by test_concentrations_keep_their_sign.
  • The plain-UFL conduction and transport comparison now uses raw concentrations in the conductivity, without the floor.

@jorgensd
jorgensd added this pull request to stack #25 November 12, 2026 09:58
@jorgensd
jorgensd force-pushed the dokken/proton-membrane branch from 135abc7 to e789910 Compare November 12, 2026 10:15
@jorgensd
jorgensd merged commit 0537f8f into main Nov 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant