Skip to main content

titrages

PyPI CI Licence MIT

La courbe de titrage d'un bécher quelconque par une burette quelconque — pH-métrie, conductimétrie, potentiométrie, complexométrie, précipitation —, avec la dilution et sans aucune approximation, et les nombres qu'on y lit : pH à un volume donné, équivalences, points d'inflexion, méthode des tangentes. Un paquet Python, et un paquet LaTeX qui calcule la même chose dans pgfplots.

Sommaire

Le but

Une courbe de titrage se calcule d'ordinaire par morceaux — avant, à, après l'équivalence —, chaque morceau par sa réaction prépondérante et sa formule approchée. Cela tient pour un acide faible par la soude ; cela ne tient plus pour un polyacide aux pKa voisins, un mélange, un titrant faible, un titrage en retour, ni pour l'EDTA sans tampon, dont le pH s'effondre en cours de route. Résoudre numériquement le polynôme en h ne vaut pas mieux : ses coefficients couvrent des dizaines d'ordres de grandeur, et le saut, là où tout se joue, est son point le plus mal conditionné.

titrages donne la courbe exacte, et les nombres qu'on y mesure, pour toute composition :

>>> from titrages import Titrage, solution
>>> t = Titrage(solution({"CH3COOH": 0.1}), solution({"NaOH": 0.1}), volume_becher=10)
>>> t.pH([0, 5, 10]).round(2).tolist()
[2.88, 4.76, 8.73]
>>> t.volumes_equivalents()
(10.0,)
>>> [(round(s.volume, 2), round(s.pH, 2)) for s in t.sauts(20)]
[(10.0, 8.73)]

Le paquet rend des nombres et des tableaux numpy : une courbe (volume, pH, pente), des sauts, des équivalences, une construction des tangentes. Le tracé est au choix du lecteur : matplotlib par titrages.trace, pgfplots par la liste de coordonnées de vers_pgfplots, ou directement par le paquet LaTeX.

Les moyens

  1. À pH imposé, tout est explicite. Les fractions de chaque forme se calculent, puis l'électroneutralité donne le volume versé comme une fonction directe du pH, V(pH) = -V0 R_bécher(pH) / R_burette(pH) : pas de polynôme à résoudre. C'est la méthode de Kalka (2021) et de Brière (2002), généralisée à un bécher et une burette quelconques ; la dilution y est exacte, chaque résidu gardant les concentrations de sa solution.
  2. Le sens inverse par dichotomie. Le pH à un volume donné annule une fonction croissante : une racine unique, trouvée à la précision de la machine, sans réglage ni point de départ.
  3. Des calculs qui ne perdent pas de chiffres. Les fractions se calculent en échelle logarithmique — l'EDTA à pH 14 ne déborde pas —, et la pente sans soustraire deux nombres voisins. Un point d'inflexion est un minimum de |dV/dpH|, affiné jusqu'à la précision de la machine : 10⁻¹⁰ unité de pH.
  4. La même idée partout. Le potentiel remplace le pH en rédox ; le ligand libre en complexométrie ; l'ion commun en précipitation, où l'on essaie chaque ensemble de solides et garde le seul cohérent. Quand le pH n'est pas tamponné, deux dichotomies imbriquées trouvent l'équilibre, dont l'unicité vient de la convexité des bilans.
  5. Chaque nombre est vérifié. Les tests confrontent le paquet à des calculs indépendants en précision arbitraire (mpmath), qui diluent à la main, et les trois implémentations — Python, Lua, pgfmath — entre elles.

Installation

pip install titrages
pip install "titrages[trace]"   # avec matplotlib, pour titrages.trace

Python 3.11 ou plus récent, et numpy 1.24 ou plus ; matplotlib 3.7 ou plus seulement pour les tracés. Le paquet est testé sous Python 3.11 à 3.14.

La version de développement s'installe depuis GitHub (pip install git+https://github.com/antnardo/titrages). Chaque release porte aussi la roue titrages-<version>-py3-none-any.whl, pour un poste sans git, et le sdist, avec docs/, examples/ et tests/. Sans pip ni réseau, le dossier src/titrages/ posé à côté d'un script suffit : from titrages import Titrage, solution fonctionne depuis le dossier du script.

Le paquet LaTeX demande une distribution TeX avec pgfplots ; LuaLaTeX pour tout ce qui n'est pas une courbe acide-base, rédox ou un nombre simple (voir Le paquet LaTeX).

Une solution, un titrage

Une solution se décrit par ce qu'on y dissout, en mol/L ; les ions spectateurs se déduisent du sel :

>>> s = solution({"Na2CO3": 0.1})
>>> round(s.pH(), 2)
11.65
>>> round(s.concentrations()["HCO3-"], 4)
0.0045

Un mélange par un titrant faible, et un titrage en retour — la soude en excès a déjà transformé l'acide éthanoïque en éthanoate, que l'acide chlorhydrique titre après l'excès :

>>> t = Titrage(solution({"HCl": 0.05, "CH3COOH": 0.05}), solution({"NH3": 0.1}), volume_becher=20)
>>> [round(s.volume, 1) for s in t.sauts(30)]
[10.0, 20.0]
>>> Titrage(solution({"CH3COOH": 0.05, "NaOH": 0.1}), solution({"HCl": 0.1}), 10).volumes_equivalents()
(5.0, 10.0)

Les équivalences stœchiométriques et les points d'inflexion sont deux choses : l'acide phosphorique a trois équivalences et deux sauts. La méthode des tangentes part d'un point avant le saut et rend la construction complète :

>>> t = Titrage(solution({"CH3COOH": 0.1}), solution({"NaOH": 0.1}), volume_becher=10)
>>> tg = t.tangentes(7, 20)
>>> round(tg.volume, 2), round(tg.pH, 2)
(10.0, 8.65)

Deux règles valent partout : un float pour un scalaire, un tableau numpy pour un tableau (t.pH(5), t.pH([5, 10])) ; les volumes dans l'unité qu'on veut, la même partout.

Tracer

import matplotlib.pyplot as plt
from titrages import Titrage, indicateur, solution
from titrages.trace import tracer_sauts, tracer_tangentes, tracer_titrage, tracer_zone_virage

t = Titrage(solution({"H3PO4": 0.1}), solution({"NaOH": 0.1}), volume_becher=10)
ax = tracer_titrage(t, 35, lw=2)
tracer_sauts(t, 35, ax)
tracer_tangentes(t, 7, 35, ax)      # depuis 7 mL, avant le premier saut
tracer_zone_virage(indicateur("phénolphtaléine"), ax)
plt.show()

Le même tracé pour l'acide éthanoïque, par examples/01_acide_faible.py :

Courbe pH-métrique de l'acide éthanoïque par la soude : saut à 10 mL, méthode des tangentes et zone de virage de la phénolphtaléine

Chaque fonction de tracé dessine sur l'axe qu'on lui donne et le rend : elle se compose avec le reste d'un script. vers_pgfplots(t.courbe(20)) rend la même courbe en coordonnées pgfplots.

Conductimétrie et potentiométrie

La conductimétrie suit la loi de Kohlrausch, ions spectateurs compris (le Na⁺ de la soude), en S/m — l'unité que donnent λ° en mS·m²·mol⁻¹ et c en mol/L ; un conductimètre affiche des mS/cm, dix fois moins, et unite="mS/cm" le fait pour un tracé :

>>> t = Titrage(solution({"HCl": 0.01}), solution({"NaOH": 0.1}), volume_becher=100)
>>> round(t.conductivite(10), 4)
0.1149
>>> t.courbe_conductimetrique(20, 3, corrigee=True).conductivite.round(4).tolist()
[0.4261, 0.1264, 0.3751]

En potentiométrie, les électrons remplacent les protons. Les couples à plusieurs étapes, ceux qui échangent des protons (pris au pH du milieu, donné aux deux solutions) et les couples dimériques (I₂/I⁻, Cr₂O₇²⁻/Cr³⁺) s'y traitent ; le potentiel se lit contre une électrode de référence :

>>> from titrages import TitrageRedox, solution_redox
>>> r = TitrageRedox(solution_redox({"Fe2+": 0.1}), solution_redox({"KMnO4": 0.02}), 10, reference="ECS")
>>> r.volumes_equivalents(), round(r.E(10), 3)
((10.0,), 1.143)
>>> v = TitrageRedox(solution_redox({"V2+": 0.1}, pH=1), solution_redox({"KMnO4": 0.02}, pH=1), 10)
>>> v.volumes_equivalents()
(10.0, 20.0, 30.0)

Complexométrie et précipitation

Un métal titré par un ligand, à pH tamponné : la courbe pM = f(V), complexes successifs et métaux en compétition compris ; les équivalences se comptent comme celles d'un polyacide, une par étape de complexation.

>>> from titrages import SolutionComplexation, TitrageComplexometrique, complexe, ligand
>>> t = TitrageComplexometrique(
...     SolutionComplexation({"Ca2+": 0.01}), SolutionComplexation(ligand=0.01), 20,
...     [complexe("Ca2+", "EDTA")], ligand("EDTA"), pH=10,
... )
>>> round(t.pM(20), 2), [round(s.volume, 3) for s in t.sauts(40)], t.volumes_equivalents()
(6.28, [20.0], (20.0,))

Un titrage par précipitation suit l'ion commun, pAg = f(V). La courbe a des points anguleux là où un solide apparaît ou disparaît — des événements, distincts des sauts : dans la méthode de Mohr, le chromate d'argent apparaît juste après l'équivalence du chlorure.

>>> from titrages import SolutionPrecipitation, TitrageParPrecipitation, precipite
>>> m = TitrageParPrecipitation(
...     SolutionPrecipitation(partenaires={"Cl-": 0.05, "CrO4^2-": 0.005}),
...     SolutionPrecipitation(commun=0.05), 20,
...     [precipite("AgCl"), precipite("Ag2CrO4")], pH=7.5,
... )
>>> [round(v, 9) for v in m.volumes_equivalents()], [(e.partenaire, round(e.volume, 3)) for e in m.evenements(30)]
([20.0, 24.0], [('Cl-', 0.0), ('CrO4^2-', 20.012)])

Sans tampon : le pH calculé

Sans pH=, et dès que le pH compte — un ligand ou un partenaire qui a des acidités, un hydroxyde, une solution qui apporte de l'acide-base —, le pH se calcule à partir du contenu des solutions, et un UserWarning le rappelle : un pH oublié change la chimie. L'EDTA, pesé sous la forme Na₂H₂Y, libère ses protons en se fixant ; sans tampon, le pH tombe et le saut disparaît :

>>> import warnings
>>> with warnings.catch_warnings():
...     warnings.simplefilter("ignore")
...     s = TitrageComplexometrique(
...         SolutionComplexation({"Ca2+": 0.01}), SolutionComplexation(ligand=0.01, protons_ligand=2), 20,
...         [complexe("Ca2+", "EDTA")], ligand("EDTA"),
...     )
>>> round(float(s.pH_du_melange(20)), 2), s.sauts(40)
(3.54, ())

Un tampon se décrit alors comme une solution (un couple acide-base dans le bécher et la burette), et un hydroxyde comme un précipité de partenaire "OH-", que précipite une base forte.

La base de constantes

Les espèces se cherchent par ce qu'on écrit — "NH3", "ammoniac", "carbonate de sodium", "CO3^2-" ; casse, accents et écriture des charges ne comptent pas. La base porte 35 couples acide-base, 15 couples rédox, 9 indicateurs, les conductivités molaires ioniques, 13 ligands, 92 complexes et 56 précipités, dont 15 hydroxydes et oxydes. Les complexes et les précipités viennent des tables de D. Harvey, d'après Martell et Smith ; la source et les conditions de chaque valeur sont dans les fichiers de données et dans le champ note.

En ligne de commande

titrages pH CH3COOH=0.1 CH3COO-=0.05
titrages sigma HCl=0.01                                   # en S/m
titrages E Fe2+=0.1 Fe3+=0.01
titrages titrage H3PO4=0.1 --burette NaOH=0.1 --volume 10
titrages redox V2+=0.1 --burette KMnO4=0.02 --reference ECS
titrages couples phosph
titrages indicateurs

Le paquet LaTeX

Le dossier latex/ contient titrages.sty et titrages.lua, à poser ensemble dans un dossier que TeX trouve (par exemple TEXMFHOME/tex/latex/titrages/) ou à côté du document ; chaque release porte aussi titrages-ctan.zip, avec la documentation compilée.

\usepackage{titrages}
\pgfplotsset{compat=1.18}
\titragedefinir{ethanoique}{
  volume = 10,
  becher = { espece = { pKa = 4.76, C = 0.1 } },
  burette = { base forte = 0.1 },
}
\begin{tikzpicture}
\begin{axis}[xlabel={$V$ (mL)}, ylabel=pH]
  \addplottitrage[thick]{ethanoique}
  \addplottitragetangentes[red, dashed]{ethanoique}{7}
\end{axis}
\end{tikzpicture}
Le pH à l'équivalence vaut \num{\titragepH{ethanoique}{10}}.

Sous LuaLaTeX, titrages.lua calcule tout : courbes pH, σ, E, pM et pP, dérivée, sauts, équivalences, tangentes, événements, pH calculé, et des nombres développables (\titragepH, \titragepM, \titragesaut…). Sous pdfLaTeX, un moteur de secours trace les courbes acide-base et rédox par pgfmath (quatre à cinq chiffres, invisible au tracé) et donne par l3fp le pH, le potentiel, la conductivité ou le volume à un point donné, plus lentement ; ni équivalences, ni sauts, ni tangentes, ni couples dimériques, ni complexométrie, ni précipitation. Le détail est dans latex/README.md et dans la documentation titrages-doc.pdf.

La documentation

  • docs/DOC.md : l'API complète, la méthode et ses formules, les modules.
  • examples/ : dix-sept scripts, décrits avec leurs courbes dans la galerie examples/README.md.
  • titrages-doc.pdf, la documentation du paquet LaTeX (syntaxe, clés, messages, index), jointe à chaque release.
  • docs/technique/ : le document technique, titrages-technique.tex — tous les calculs, les choix algorithmiques, les démonstrations d'unicité, et les écarts mesurés face aux calculs indépendants (mpmath), que verifications.py recalcule.
  • CHANGELOG.md.

Pour partir d'une masse pesée, le paquet pychimie donne la masse molaire ; titrages n'en dépend pas, le pont tient dans le script :

from pychimie import masse_molaire
from titrages import Titrage, solution

C = 0.384 / masse_molaire("NaHCO3") / 0.100      # 384 mg dans 100 mL, en mol/L
t = Titrage(solution({"NaHCO3": C}), solution({"HCl": 0.1}), volume_becher=50)

Antériorité

pHcalc et chempy calculent l'équilibre d'une composition donnée, chempy avec complexation et précipitation ; ni l'un ni l'autre n'a d'objet titrage, de dilution exacte, de sauts, de tangentes, de conductimétrie ni de rédox. Les logiciels de spéciation (PHREEQC, Visual MINTEQ) résolvent n'importe quel mélange, sans courbe de titrage ni points d'inflexion. Côté LaTeX, phtitration trace huit configurations de titrage par un titrant fort, sans mélange, sans nombres, ni conductimétrie ni rédox ; titrages part du même calcul paramétrique exact et le généralise à un bécher et une burette quelconques.

Limites

  • Les solutions sont idéales : l'activité est la concentration, les conductivités molaires sont les valeurs limites, qui surestiment σ dès quelques mmol/L. La température n'intervient pas, mais on peut changer les valeurs de pKe, de la pente de Nernst et de toutes les autres constantes ; les valeurs par défaut sont celles de 25 °C.
  • Les couples rédox qui échangent des protons sont pris à pH fixé ; un métal ou un gaz (Ag, H₂) n'est pas un couple de solution.
  • Un seul ligand par titrage : un tampon ammoniacal qui complexerait aussi le métal, ou un indicateur métallochrome, en serait un second. Complexation et précipitation ne se combinent pas dans un même titrage.
  • Les constantes de la base sont celles des tables à 25 °C ; elles diffèrent d'une table à l'autre, et la base le signale quand l'écart est notable. Les λ° de dix-sept ions, non tabulés, sont approchés par un ion voisin, nommé ; ceux de l'EDTA manquent.

Développement

git clone https://github.com/antnardo/titrages
cd titrages && uv sync
uv run pytest                    # tout, moteur Lua et paquet LaTeX compris quand TeX est là
uv run pytest -m "not latex"     # sans compiler le paquet LaTeX
uv run ruff check src tests && uv run ruff format --check src tests && uv run mypy
cd latex/tests && texlua test_titrages.lua

Les tests marqués lua comparent le moteur Lua au moteur Python par texlua ; ceux marqués latex compilent les documents de latex/tests/ par lualatex et pdflatex et comparent leurs nombres au calcul Python. Les deux groupes sont sautés quand TeX manque.

Sources

  • H. Kalka, Polyprotic Acids and Beyond — An Algebraic Approach, Chemistry 2021, 3, 454-508, doi:10.3390/chemistry3020034.
  • T. Brière, Une méthode simple de résolution des problèmes de pH et de titrages acido-basiques sans approximations, BUP 841, 2002.
  • Y. Cortial, Courbe de dosage acidobasique et méthode des tangentes, BUP 734, 1991.
  • W. M. Haynes (dir.), CRC Handbook of Chemistry and Physics, 97e éd., CRC Press, 2016 : constantes d'acidité, potentiels standard, conductivités molaires ioniques.
  • D. Harvey, Analytical Chemistry 2.1, LibreTexts, § 16.10 à 16.12, d'après A. E. Martell et R. M. Smith, Critical Stability Constants, Plenum, 1976 : constantes de formation et produits de solubilité.

Licence

Le code — le paquet Python (src/), le paquet LaTeX (latex/), les tests et les scripts — est sous licence MIT (LICENSE). Les exemples (examples/), contenu scientifique et pédagogique plutôt qu'algorithmes, sont sous licence CC BY-NC-SA 4.0 (LICENSE-CC-BY-NC-SA-4.0).

Metadata

Release files for titrages 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for titrages 0.2.0
File Size Uploaded Provenance
titrages-0.2.0.tar.gz 669.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for titrages 0.2.0
File Interpreter ABI Platform
titrages-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 766.6 kB

Release files / titrages-0.2.0.tar.gz

Download URL titrages-0.2.0.tar.gz
Size 669.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0cd6f02a46dc3cf5f17e61b6aacbebcc3c014486e1b915dc2ef61b86c6596e28
BLAKE2b-256 checksum
How to use checksums
d11349c866a6c7ea9b916dad7ed864cb6aca6b555d0ff945ebc917d29ba0b097
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / titrages-0.2.0-py3-none-any.whl

Download URL titrages-0.2.0-py3-none-any.whl
Size 96.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7d8e6ca8d16831a90c0fdc37a8b1b371a7f40840311c013d553aef4ba3e18b20
BLAKE2b-256 checksum
How to use checksums
a05c873e01126d0783495e1707a0dd4e9d1dd9fef3b62e2904d6af557260128b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for titrages 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for titrages-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page