pychnosz.geochemistry.redox API documentation
Description: Redox reactions and Eh-pH diagram calculations for CHNOSZ …

# Module `pychnosz.geochemistry.redox`

Redox reactions and Eh-pH diagram calculations for CHNOSZ.

This module implements redox equilibria calculations, Eh-pH diagrams, and electron activity (pe) calculations for environmental geochemistry.

## Functions

`def eh(pe_value: float, T: float = 298.15) ‑> float`
  Expand source code

      def eh(pe_value: float, T: float = 298.15) -> float:
          """
          Convert pe to Eh.

          Parameters
          ----------
          pe_value : float
              Electron activity
          T : float, default 298.15
              Temperature in Kelvin

          Returns
          -------
          float
              Eh in Volts
          """

          return _redox_calculator.eh_from_pe(pe_value, T)

  Convert pe to Eh.

  ## Parameters

  `pe_value` :`float`
    Electron activity
  `T` :`float`, default `298.15`
    Temperature in Kelvin

  ## Returns

  `float`
    Eh in Volts
`def eh_ph(element: str, **kwargs) ‑> Dict[str, Any]`
  Expand source code

      def eh_ph(element: str, **kwargs) -> Dict[str, Any]:
          """
          Create Eh-pH diagram for an element.

          Parameters
          ----------
          element : str
              Element symbol
          **kwargs
              Additional parameters for eh_ph_diagram()

          Returns
          -------
          dict
              Eh-pH diagram data
          """

          return _redox_calculator.eh_ph_diagram(element, **kwargs)

  Create Eh-pH diagram for an element.

  ## Parameters

  `element` :`str`
    Element symbol
  `**kwargs`
    Additional parameters for eh_ph_diagram()

  ## Returns

  `dict`
    Eh-pH diagram data
`def logfO2(pe_value: float, pH: float = 7.0, **kwargs) ‑> float`
  Expand source code

      def logfO2(pe_value: float, pH: float = 7.0, **kwargs) -> float:
          """
          Calculate log oxygen fugacity.

          Parameters
          ----------
          pe_value : float
              Electron activity
          pH : float, default 7.0
              Solution pH
          **kwargs
              Additional parameters

          Returns
          -------
          float
              log fO2
          """

          return _redox_calculator.oxygen_fugacity(pe_value, pH, **kwargs)

  Calculate log oxygen fugacity.

  ## Parameters

  `pe_value` :`float`
    Electron activity
  `pH` :`float`, default `7.0`
    Solution pH
  `**kwargs`
    Additional parameters

  ## Returns

  `float`
    log fO2
`def pe(redox_couple: str | Dict[str, float],
concentrations: Dict[str, float],
**kwargs) ‑> float`
  Expand source code

      def pe(redox_couple: Union[str, Dict[str, float]],
            concentrations: Dict[str, float], **kwargs) -> float:
          """
          Calculate pe for a redox couple.

          Parameters
          ----------
          redox_couple : str or dict
              Redox couple or reaction
          concentrations : dict
              Species concentrations
          **kwargs
              Additional parameters

          Returns
          -------
          float
              pe value
          """

          return _redox_calculator.pe_calculation(redox_couple, concentrations, **kwargs)

  Calculate pe for a redox couple.

  ## Parameters

  `redox_couple` :`str` or `dict`
    Redox couple or reaction
  `concentrations` :`dict`
    Species concentrations
  `**kwargs`
    Additional parameters

  ## Returns

  `float`
    pe value

## Classes

`class RedoxCalculator`
  Expand source code

      class RedoxCalculator:
          """
          Redox equilibria calculator for geochemical systems.

          This class handles redox reactions, pe-pH diagrams, and
          electron activity calculations.
          """

          def __init__(self):
              """Initialize the redox calculator."""
              self.equilibrium_solver = EquilibriumSolver()

              # Standard electrode potentials (V) at 25°C
              self.standard_potentials = {
                  'O2/H2O': 1.229,
                  'H+/H2': 0.000,
                  'Fe+3/Fe+2': 0.771,
                  'NO3-/NO2-': 0.835,
                  'SO4-2/HS-': -0.217,
                  'CO2/CH4': -0.244,
                  'N2/NH4+': -0.277,
                  'Fe+2/Fe': -0.447,
                  'S/HS-': -0.065,
              }

              # Common redox couples and their reactions
              self.redox_reactions = {
                  # Oxygen reduction
                  'O2_H2O': {'O2': 1, 'H+': 4, 'e-': 4, 'H2O': -2},
                  'H2O_H2': {'H2O': 2, 'e-': 2, 'H2': -1, 'OH-': -2},

                  # Iron redox
                  'Fe3_Fe2': {'Fe+3': 1, 'e-': 1, 'Fe+2': -1},
                  'Fe2_Fe': {'Fe+2': 1, 'e-': 2, 'Fe': -1},
                  'Fe2O3_Fe2': {'Fe2O3': 1, 'H+': 6, 'e-': 2, 'Fe+2': -2, 'H2O': -3},

                  # Nitrogen redox
                  'NO3_NO2': {'NO3-': 1, 'H+': 2, 'e-': 2, 'NO2-': -1, 'H2O': -1},
                  'NO2_NH4': {'NO2-': 1, 'H+': 8, 'e-': 6, 'NH4+': -1, 'H2O': -2},

                  # Sulfur redox
                  'SO4_HS': {'SO4-2': 1, 'H+': 9, 'e-': 8, 'HS-': -1, 'H2O': -4},
                  'S_HS': {'S': 1, 'H+': 1, 'e-': 2, 'HS-': -1},

                  # Carbon redox
                  'CO2_CH4': {'CO2': 1, 'H+': 8, 'e-': 8, 'CH4': -1, 'H2O': -2},
                  'HCO3_CH4': {'HCO3-': 1, 'H+': 9, 'e-': 8, 'CH4': -1, 'H2O': -3}
              }

          def eh_ph_diagram(self, element: str,
                           pH_range: Tuple[float, float] = (0, 14),
                           pe_range: Tuple[float, float] = (-10, 15),
                           T: float = 298.15, P: float = 1.0,
                           total_concentration: float = 1e-6,
                           resolution: int = 100) -> Dict[str, Any]:
              """
              Create Eh-pH (pe-pH) diagram for an element.

              Parameters
              ----------
              element : str
                  Element symbol (e.g., 'Fe', 'S', 'N', 'C')
              pH_range : tuple, default (0, 14)
                  pH range for diagram
              pe_range : tuple, default (-10, 15)
                  pe (electron activity) range
              T : float, default 298.15
                  Temperature in Kelvin
              P : float, default 1.0
                  Pressure in bar
              total_concentration : float, default 1e-6
                  Total element concentration (molal)
              resolution : int, default 100
                  Grid resolution

              Returns
              -------
              dict
                  Eh-pH diagram data
              """

              pH_grid = np.linspace(pH_range[0], pH_range[1], resolution)
              pe_grid = np.linspace(pe_range[0], pe_range[1], resolution)
              pH_mesh, pe_mesh = np.meshgrid(pH_grid, pe_grid)

              # Get species for this element
              element_species = self._get_element_species(element)

              if not element_species:
                  raise ValueError(f"No species found for element {element}")

              # Calculate predominance at each point
              predominant = np.zeros_like(pH_mesh, dtype=int)
              activities = {species: np.zeros_like(pH_mesh) for species in element_species}

              for i in range(len(pe_grid)):
                  for j in range(len(pH_grid)):
                      pH, pe = pH_mesh[i, j], pe_mesh[i, j]

                      # Calculate speciation at this point
                      spec_result = self._calculate_redox_speciation(
                          element_species, pH, pe, T, P, total_concentration)

                      # Find predominant species
                      max_activity = -np.inf
                      max_idx = 0

                      for k, species in enumerate(element_species):
                          activity = spec_result.get(species, 1e-20)
                          activities[species][i, j] = activity

                          if np.log10(activity) > max_activity:
                              max_activity = np.log10(activity)
                              max_idx = k

                      predominant[i, j] = max_idx

              # Add water stability limits
              water_limits = self._water_stability_lines(pH_grid, T, P)

              return {
                  'pH': pH_grid,
                  'pe': pe_grid,
                  'pH_mesh': pH_mesh,
                  'pe_mesh': pe_mesh,
                  'predominant': predominant,
                  'activities': activities,
                  'species_names': element_species,
                  'water_limits': water_limits,
                  'element': element,
                  'T': T,
                  'P': P,
                  'total_concentration': total_concentration
              }

          def pe_calculation(self, redox_couple: Union[str, Dict[str, float]],
                            concentrations: Dict[str, float],
                            pH: float = 7.0, T: float = 298.15, P: float = 1.0) -> float:
              """
              Calculate pe (electron activity) for a redox couple.

              Parameters
              ----------
              redox_couple : str or dict
                  Redox couple name or reaction dictionary
              concentrations : dict
                  Species concentrations {species: concentration}
              pH : float, default 7.0
                  Solution pH
              T : float, default 298.15
                  Temperature in Kelvin
              P : float, default 1.0
                  Pressure in bar

              Returns
              -------
              float
                  pe value
              """

              if isinstance(redox_couple, str):
                  if redox_couple not in self.redox_reactions:
                      raise ValueError(f"Unknown redox couple: {redox_couple}")
                  reaction = self.redox_reactions[redox_couple]
              else:
                  reaction = redox_couple

              # Calculate equilibrium constant
              try:
                  species_names = [sp for sp in reaction.keys() if sp != 'e-']
                  coefficients = [reaction[sp] for sp in species_names]

                  result = subcrt(species_names, coefficients, T=T, P=P, show=False)
                  if result.out is not None and 'logK' in result.out.columns:
                      logK = result.out['logK'].iloc[0]
                  else:
                      logK = 0.0
              except Exception as e:
                  warnings.warn(f"Could not calculate logK: {e}")
                  logK = 0.0

              # Apply Nernst equation
              n_electrons = abs(reaction.get('e-', 1))  # Number of electrons

              # Calculate activity quotient
              log_Q = 0.0
              for species, coeff in reaction.items():
                  if species == 'e-':
                      continue
                  elif species == 'H+':
                      activity = 10**(-pH)
                  elif species in concentrations:
                      activity = concentrations[species]
                      # Apply activity coefficients if needed
                      gamma = self._get_activity_coefficient(species, concentrations, T)
                      activity *= gamma
                  else:
                      activity = 1.0  # Default for species not specified

                  if activity > 0:
                      log_Q += coeff * np.log10(activity)

              # Nernst equation: pe = pe° + (1/n) * log(Q)
              # where pe° = logK/n for the half-reaction
              pe = logK / n_electrons + log_Q / n_electrons

              return pe

          def eh_from_pe(self, pe: float, T: float = 298.15) -> float:
              """
              Convert pe to Eh (redox potential).

              Parameters
              ----------
              pe : float
                  Electron activity (pe)
              T : float, default 298.15
                  Temperature in Kelvin

              Returns
              -------
              float
                  Redox potential (Eh) in Volts
              """

              # Eh = (RT/F) * ln(10) * pe
              # where R = 8.314 J/(mol·K), F = 96485 C/mol
              RT_F = 8.314 * T / 96485
              Eh = RT_F * 2.302585 * pe  # ln(10) = 2.302585

              return Eh

          def pe_from_eh(self, eh: float, T: float = 298.15) -> float:
              """
              Convert Eh (redox potential) to pe.

              Parameters
              ----------
              eh : float
                  Redox potential (Eh) in Volts
              T : float, default 298.15
                  Temperature in Kelvin

              Returns
              -------
              float
                  Electron activity (pe)
              """

              # pe = F * Eh / (RT * ln(10))
              RT_F = 8.314 * T / 96485
              pe = eh / (RT_F * 2.302585)

              return pe

          def oxygen_fugacity(self, pe: float, pH: float = 7.0,
                             T: float = 298.15, P: float = 1.0) -> float:
              """
              Calculate oxygen fugacity from pe and pH.

              Parameters
              ----------
              pe : float
                  Electron activity
              pH : float, default 7.0
                  Solution pH
              T : float, default 298.15
                  Temperature in Kelvin
              P : float, default 1.0
                  Pressure in bar

              Returns
              -------
              float
                  log fO2 (log oxygen fugacity)
              """

              # O2 + 4H+ + 4e- = 2H2O
              # At equilibrium: log fO2 = 4*pe + 4*pH - logK

              try:
                  # Calculate logK for oxygen-water reaction
                  result = subcrt(['O2', 'H+', 'H2O'], [1, 4, -2], T=T, P=P, show=False)
                  if result.out is not None and 'logK' in result.out.columns:
                      logK = result.out['logK'].iloc[0]
                  else:
                      logK = 83.1  # Approximate value at 25°C
              except:
                  logK = 83.1

              log_fO2 = 4 * pe + 4 * pH - logK

              return log_fO2

          def _get_element_species(self, element: str) -> List[str]:
              """Get list of species containing the specified element."""

              # Simplified species lists for common elements
              element_species = {
                  'Fe': ['Fe+2', 'Fe+3', 'Fe2O3', 'FeOH+', 'Fe(OH)2', 'Fe(OH)3'],
                  'S': ['SO4-2', 'SO3-2', 'S2O3-2', 'HS-', 'S0', 'S-2'],
                  'N': ['NO3-', 'NO2-', 'NH4+', 'NH3', 'N2O', 'N2'],
                  'C': ['CO2', 'HCO3-', 'CO3-2', 'CH4', 'HCOOH', 'CH3COO-'],
                  'Mn': ['Mn+2', 'MnO4-', 'MnO2', 'Mn+3', 'MnOH+'],
                  'As': ['AsO4-3', 'AsO3-3', 'H3AsO4', 'H3AsO3', 'As0']
              }

              return element_species.get(element, [f'{element}+2', f'{element}+3'])

          def _calculate_redox_speciation(self, species: List[str], pH: float, pe: float,
                                        T: float, P: float, total_conc: float) -> Dict[str, float]:
              """Calculate speciation at given pH and pe."""

              # Simplified speciation calculation
              # Full implementation would solve equilibrium system

              activities = {}

              for sp in species:
                  # Simplified pe-pH dependence
                  if '+' in sp:  # Cations (more stable at low pH, high pe)
                      charge = self._extract_charge(sp)
                      log_activity = -6 + charge * (14 - pH) / 14 + pe / 10
                  elif '-' in sp:  # Anions (more stable at high pH, high pe)
                      charge = abs(self._extract_charge(sp))
                      log_activity = -6 + charge * pH / 14 + pe / 10
                  else:  # Neutral (less pH/pe dependence)
                      log_activity = -6 + (pe - pH) / 20

                  activities[sp] = 10**log_activity

              # Normalize to total concentration
              total_calculated = sum(activities.values())
              if total_calculated > 0:
                  factor = total_conc / total_calculated
                  activities = {sp: act * factor for sp, act in activities.items()}

              return activities

          def _extract_charge(self, species: str) -> int:
              """Extract charge from species name."""

              if '+' in species:
                  parts = species.split('+')
                  if len(parts) > 1 and parts[-1].isdigit():
                      return int(parts[-1])
                  else:
                      return 1
              elif '-' in species:
                  parts = species.split('-')
                  if len(parts) > 1 and parts[-1].isdigit():
                      return -int(parts[-1])
                  else:
                      return -1
              else:
                  return 0

          def _water_stability_lines(self, pH_values: np.ndarray,
                                    T: float, P: float) -> Dict[str, np.ndarray]:
              """Calculate water stability limits (H2O/H2 and O2/H2O)."""

              # Upper limit: O2/H2O
              # O2 + 4H+ + 4e- = 2H2O
              # pe = 20.75 - pH (at 25°C, 1 atm O2)
              pe_upper = 20.75 - pH_values

              # Lower limit: H2O/H2
              # 2H2O + 2e- = H2 + 2OH-
              # pe = -pH (at 25°C, 1 atm H2)
              pe_lower = -pH_values

              # Temperature corrections (simplified)
              if T != 298.15:
                  dT = T - 298.15
                  pe_upper += dT * 0.001  # Small temperature dependence
                  pe_lower -= dT * 0.001

              return {
                  'upper': pe_upper,  # O2/H2O line
                  'lower': pe_lower   # H2O/H2 line
              }

          def _get_activity_coefficient(self, species: str, composition: Dict[str, float],
                                       T: float) -> float:
              """Get activity coefficient for species."""

              # Simplified - would use proper activity models
              charge = abs(self._extract_charge(species))

              if charge == 0:
                  return 1.0
              else:
                  # Simple ionic strength correction
                  I = 0.001  # Assume low ionic strength
                  return 10**(-0.509 * charge**2 * np.sqrt(I))

  Redox equilibria calculator for geochemical systems.

  This class handles redox reactions, pe-pH diagrams, and electron activity calculations.

  Initialize the redox calculator.

  ### Methods

  `def eh_from_pe(self, pe: float, T: float = 298.15) ‑> float`
    Expand source code

        def eh_from_pe(self, pe: float, T: float = 298.15) -> float:
            """
            Convert pe to Eh (redox potential).

            Parameters
            ----------
            pe : float
                Electron activity (pe)
            T : float, default 298.15
                Temperature in Kelvin

            Returns
            -------
            float
                Redox potential (Eh) in Volts
            """

            # Eh = (RT/F) * ln(10) * pe
            # where R = 8.314 J/(mol·K), F = 96485 C/mol
            RT_F = 8.314 * T / 96485
            Eh = RT_F * 2.302585 * pe  # ln(10) = 2.302585

            return Eh

    Convert pe to Eh (redox potential).

    ## Parameters

    `pe` :`float`
      Electron activity (pe)
    `T` :`float`, default `298.15`
      Temperature in Kelvin

    ## Returns

    `float`
      Redox potential (Eh) in Volts
  `def eh_ph_diagram(self,
  element: str,
  pH_range: Tuple[float, float] = (0, 14),
  pe_range: Tuple[float, float] = (-10, 15),
  T: float = 298.15,
  P: float = 1.0,
  total_concentration: float = 1e-06,
  resolution: int = 100) ‑> Dict[str, Any]`
    Expand source code

        def eh_ph_diagram(self, element: str,
                         pH_range: Tuple[float, float] = (0, 14),
                         pe_range: Tuple[float, float] = (-10, 15),
                         T: float = 298.15, P: float = 1.0,
                         total_concentration: float = 1e-6,
                         resolution: int = 100) -> Dict[str, Any]:
            """
            Create Eh-pH (pe-pH) diagram for an element.

            Parameters
            ----------
            element : str
                Element symbol (e.g., 'Fe', 'S', 'N', 'C')
            pH_range : tuple, default (0, 14)
                pH range for diagram
            pe_range : tuple, default (-10, 15)
                pe (electron activity) range
            T : float, default 298.15
                Temperature in Kelvin
            P : float, default 1.0
                Pressure in bar
            total_concentration : float, default 1e-6
                Total element concentration (molal)
            resolution : int, default 100
                Grid resolution

            Returns
            -------
            dict
                Eh-pH diagram data
            """

            pH_grid = np.linspace(pH_range[0], pH_range[1], resolution)
            pe_grid = np.linspace(pe_range[0], pe_range[1], resolution)
            pH_mesh, pe_mesh = np.meshgrid(pH_grid, pe_grid)

            # Get species for this element
            element_species = self._get_element_species(element)

            if not element_species:
                raise ValueError(f"No species found for element {element}")

            # Calculate predominance at each point
            predominant = np.zeros_like(pH_mesh, dtype=int)
            activities = {species: np.zeros_like(pH_mesh) for species in element_species}

            for i in range(len(pe_grid)):
                for j in range(len(pH_grid)):
                    pH, pe = pH_mesh[i, j], pe_mesh[i, j]

                    # Calculate speciation at this point
                    spec_result = self._calculate_redox_speciation(
                        element_species, pH, pe, T, P, total_concentration)

                    # Find predominant species
                    max_activity = -np.inf
                    max_idx = 0

                    for k, species in enumerate(element_species):
                        activity = spec_result.get(species, 1e-20)
                        activities[species][i, j] = activity

                        if np.log10(activity) > max_activity:
                            max_activity = np.log10(activity)
                            max_idx = k

                    predominant[i, j] = max_idx

            # Add water stability limits
            water_limits = self._water_stability_lines(pH_grid, T, P)

            return {
                'pH': pH_grid,
                'pe': pe_grid,
                'pH_mesh': pH_mesh,
                'pe_mesh': pe_mesh,
                'predominant': predominant,
                'activities': activities,
                'species_names': element_species,
                'water_limits': water_limits,
                'element': element,
                'T': T,
                'P': P,
                'total_concentration': total_concentration
            }

    Create Eh-pH (pe-pH) diagram for an element.

    ## Parameters

    `element` :`str`
      Element symbol (e.g., 'Fe', 'S', 'N', 'C')
    `pH_range` :`tuple`, default `(0, 14)`
      pH range for diagram
    `pe_range` :`tuple`, default `(-10, 15)`
      pe (electron activity) range
    `T` :`float`, default `298.15`
      Temperature in Kelvin
    `P` :`float`, default `1.0`
      Pressure in bar
    `total_concentration` :`float`, default `1e-6`
      Total element concentration (molal)
    `resolution` :`int`, default `100`
      Grid resolution

    ## Returns

    `dict`
      Eh-pH diagram data
  `def oxygen_fugacity(self, pe: float, pH: float = 7.0, T: float = 298.15, P: float = 1.0) ‑> float`
    Expand source code

        def oxygen_fugacity(self, pe: float, pH: float = 7.0,
                           T: float = 298.15, P: float = 1.0) -> float:
            """
            Calculate oxygen fugacity from pe and pH.

            Parameters
            ----------
            pe : float
                Electron activity
            pH : float, default 7.0
                Solution pH
            T : float, default 298.15
                Temperature in Kelvin
            P : float, default 1.0
                Pressure in bar

            Returns
            -------
            float
                log fO2 (log oxygen fugacity)
            """

            # O2 + 4H+ + 4e- = 2H2O
            # At equilibrium: log fO2 = 4*pe + 4*pH - logK

            try:
                # Calculate logK for oxygen-water reaction
                result = subcrt(['O2', 'H+', 'H2O'], [1, 4, -2], T=T, P=P, show=False)
                if result.out is not None and 'logK' in result.out.columns:
                    logK = result.out['logK'].iloc[0]
                else:
                    logK = 83.1  # Approximate value at 25°C
            except:
                logK = 83.1

            log_fO2 = 4 * pe + 4 * pH - logK

            return log_fO2

    Calculate oxygen fugacity from pe and pH.

    ## Parameters

    `pe` :`float`
      Electron activity
    `pH` :`float`, default `7.0`
      Solution pH
    `T` :`float`, default `298.15`
      Temperature in Kelvin
    `P` :`float`, default `1.0`
      Pressure in bar

    ## Returns

    `float`
      log fO2 (log oxygen fugacity)
  `def pe_calculation(self,
  redox_couple: str | Dict[str, float],
  concentrations: Dict[str, float],
  pH: float = 7.0,
  T: float = 298.15,
  P: float = 1.0) ‑> float`
    Expand source code

        def pe_calculation(self, redox_couple: Union[str, Dict[str, float]],
                          concentrations: Dict[str, float],
                          pH: float = 7.0, T: float = 298.15, P: float = 1.0) -> float:
            """
            Calculate pe (electron activity) for a redox couple.

            Parameters
            ----------
            redox_couple : str or dict
                Redox couple name or reaction dictionary
            concentrations : dict
                Species concentrations {species: concentration}
            pH : float, default 7.0
                Solution pH
            T : float, default 298.15
                Temperature in Kelvin
            P : float, default 1.0
                Pressure in bar

            Returns
            -------
            float
                pe value
            """

            if isinstance(redox_couple, str):
                if redox_couple not in self.redox_reactions:
                    raise ValueError(f"Unknown redox couple: {redox_couple}")
                reaction = self.redox_reactions[redox_couple]
            else:
                reaction = redox_couple

            # Calculate equilibrium constant
            try:
                species_names = [sp for sp in reaction.keys() if sp != 'e-']
                coefficients = [reaction[sp] for sp in species_names]

                result = subcrt(species_names, coefficients, T=T, P=P, show=False)
                if result.out is not None and 'logK' in result.out.columns:
                    logK = result.out['logK'].iloc[0]
                else:
                    logK = 0.0
            except Exception as e:
                warnings.warn(f"Could not calculate logK: {e}")
                logK = 0.0

            # Apply Nernst equation
            n_electrons = abs(reaction.get('e-', 1))  # Number of electrons

            # Calculate activity quotient
            log_Q = 0.0
            for species, coeff in reaction.items():
                if species == 'e-':
                    continue
                elif species == 'H+':
                    activity = 10**(-pH)
                elif species in concentrations:
                    activity = concentrations[species]
                    # Apply activity coefficients if needed
                    gamma = self._get_activity_coefficient(species, concentrations, T)
                    activity *= gamma
                else:
                    activity = 1.0  # Default for species not specified

                if activity > 0:
                    log_Q += coeff * np.log10(activity)

            # Nernst equation: pe = pe° + (1/n) * log(Q)
            # where pe° = logK/n for the half-reaction
            pe = logK / n_electrons + log_Q / n_electrons

            return pe

    Calculate pe (electron activity) for a redox couple.

    ## Parameters

    `redox_couple` :`str` or `dict`
      Redox couple name or reaction dictionary
    `concentrations` :`dict`
      Species concentrations {species: concentration}
    `pH` :`float`, default `7.0`
      Solution pH
    `T` :`float`, default `298.15`
      Temperature in Kelvin
    `P` :`float`, default `1.0`
      Pressure in bar

    ## Returns

    `float`
      pe value
  `def pe_from_eh(self, eh: float, T: float = 298.15) ‑> float`
    Expand source code

        def pe_from_eh(self, eh: float, T: float = 298.15) -> float:
            """
            Convert Eh (redox potential) to pe.

            Parameters
            ----------
            eh : float
                Redox potential (Eh) in Volts
            T : float, default 298.15
                Temperature in Kelvin

            Returns
            -------
            float
                Electron activity (pe)
            """

            # pe = F * Eh / (RT * ln(10))
            RT_F = 8.314 * T / 96485
            pe = eh / (RT_F * 2.302585)

            return pe

    Convert Eh (redox potential) to pe.

    ## Parameters

    `eh` :`float`
      Redox potential (Eh) in Volts
    `T` :`float`, default `298.15`
      Temperature in Kelvin

    ## Returns

    `float`
      Electron activity (pe)