efta 1.0.0__tar.gz

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.
Files changed (44) hide show
  1. efta-1.0.0/CHANGELOG.md +22 -0
  2. efta-1.0.0/LICENSE +21 -0
  3. efta-1.0.0/MANIFEST.in +6 -0
  4. efta-1.0.0/PKG-INFO +404 -0
  5. efta-1.0.0/README.md +367 -0
  6. efta-1.0.0/efta/__init__.py +223 -0
  7. efta-1.0.0/efta/balance.py +431 -0
  8. efta-1.0.0/efta/errors.py +169 -0
  9. efta-1.0.0/efta/mixture.py +271 -0
  10. efta-1.0.0/efta/model/__init__.py +34 -0
  11. efta-1.0.0/efta/model/distribution.py +387 -0
  12. efta-1.0.0/efta/model/fitting.py +1278 -0
  13. efta-1.0.0/efta/model/freaction.py +789 -0
  14. efta-1.0.0/efta/model/ga.py +314 -0
  15. efta-1.0.0/efta/model/mass_action.py +204 -0
  16. efta-1.0.0/efta/model/suggest.py +536 -0
  17. efta-1.0.0/efta/periodic_table.py +412 -0
  18. efta-1.0.0/efta/plotting.py +719 -0
  19. efta-1.0.0/efta/py.typed +0 -0
  20. efta-1.0.0/efta/reaction.py +725 -0
  21. efta-1.0.0/efta/reactions.py +922 -0
  22. efta-1.0.0/efta/solution.py +1119 -0
  23. efta-1.0.0/efta/solventextraction/__init__.py +107 -0
  24. efta-1.0.0/efta/solventextraction/multistage.py +1245 -0
  25. efta-1.0.0/efta/solventextraction/sx.py +583 -0
  26. efta-1.0.0/efta/solventextraction/units.py +194 -0
  27. efta-1.0.0/efta/solver/__init__.py +78 -0
  28. efta-1.0.0/efta/solver/_shared.py +692 -0
  29. efta-1.0.0/efta/solver/dispatch.py +464 -0
  30. efta-1.0.0/efta/solver/find.py +255 -0
  31. efta-1.0.0/efta/solver/method_a.py +149 -0
  32. efta-1.0.0/efta/solver/method_b.py +203 -0
  33. efta-1.0.0/efta/solver/method_de.py +175 -0
  34. efta-1.0.0/efta/solver/method_l.py +257 -0
  35. efta-1.0.0/efta/species.py +838 -0
  36. efta-1.0.0/efta/styling.py +505 -0
  37. efta-1.0.0/efta/system.py +550 -0
  38. efta-1.0.0/efta.egg-info/PKG-INFO +404 -0
  39. efta-1.0.0/efta.egg-info/SOURCES.txt +42 -0
  40. efta-1.0.0/efta.egg-info/dependency_links.txt +1 -0
  41. efta-1.0.0/efta.egg-info/requires.txt +12 -0
  42. efta-1.0.0/efta.egg-info/top_level.txt +1 -0
  43. efta-1.0.0/pyproject.toml +68 -0
  44. efta-1.0.0/setup.cfg +4 -0
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to efta are documented here.
4
+
5
+ ## [1.0.0] — 2026-04-06
6
+
7
+ ### Added
8
+ - Initial PyPI release.
9
+ - `reaction` and `reactions` classes for defining and solving coupled chemical equilibria.
10
+ - Four numerical solver methods: Method L (Newton in log-space), Method A (extent-of-reaction), Method B (fsolve variants), Method DE (differential evolution).
11
+ - `solution` and `mixture` classes for managing equilibrium results.
12
+ - Solvent extraction module (`efta.solventextraction`) with single-stage `sx` and multistage topologies (`countercurrent`, `crosscurrent`, `strip_countercurrent`, `strip_crosscurrent`).
13
+ - Activity coefficient support via `reaction.set_gamma()` and `reactions.set_gamma()`.
14
+ - Precipitation/Ksp reactions (`ksp=True`).
15
+ - Parameter fitting (`model`, `analyze`, `montecarlo`) with bootstrap uncertainty.
16
+ - `freaction` / `freactions` for parameterised reactions with `$(xN)` placeholders.
17
+ - Full periodic table (`periodic_table`) with IUPAC 2021 atomic masses.
18
+ - Plotting utilities: concentration sweeps, speciation fraction diagrams, stage profiles.
19
+ - `PlotStyle` singleton (`efta.style`) for consistent plot customisation.
20
+ - Styling helpers: `coloring()`, `randomize_color()`, built-in palettes including `'colorblind'`, `'ocean'`, `'earth'`.
21
+ - Complete error hierarchy (`EftaError`, `ConvergenceError`, `ConvergenceWarning`, …).
22
+ - `py.typed` marker — efta is fully typed.
efta-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 efta contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
efta-1.0.0/MANIFEST.in ADDED
@@ -0,0 +1,6 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include efta/py.typed
5
+ recursive-exclude * __pycache__
6
+ recursive-exclude * *.py[co]
efta-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,404 @@
1
+ Metadata-Version: 2.4
2
+ Name: efta
3
+ Version: 1.0.0
4
+ Summary: Equilibrium Formulation and Thermodynamic Analysis — a Python library for solving chemical equilibrium problems
5
+ Author: efta contributors
6
+ License-Expression: MIT
7
+ Project-URL: Source Code, https://github.com/arsyadmdz/efta
8
+ Project-URL: Bug Tracker, https://github.com/arsyadmdz/efta/issues
9
+ Project-URL: Documentation, https://efta.readthedocs.io
10
+ Keywords: chemistry,equilibrium,thermodynamics,speciation,solvent extraction,chemical engineering,hydrometallurgy
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: scipy>=1.10
28
+ Requires-Dist: matplotlib>=3.6
29
+ Provides-Extra: fitting
30
+ Requires-Dist: scipy>=1.10; extra == "fitting"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: ruff; extra == "dev"
35
+ Requires-Dist: mypy; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ <p align="center">
39
+ <img src="https://raw.githubusercontent.com/arsyadmdz/efta/main/docs/_static/logo.png" width="200" alt="efta logo">
40
+ </p>
41
+
42
+ <h1 align="center">efta — Equilibrium Formulation and Thermodynamic Analysis</h1>
43
+
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/efta/"><img src="https://badge.fury.io/py/efta.svg" alt="PyPI version"></a>
46
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+"></a>
47
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
48
+ <a href="https://efta.readthedocs.io"><img src="https://readthedocs.org/projects/efta/badge/?version=latest" alt="Documentation"></a>
49
+ </p>
50
+
51
+ **efta** is a Python library for solving chemical equilibrium problems — from simple acid-base dissociation to complex multi-phase solvent extraction systems.
52
+
53
+ ---
54
+
55
+ ## Features
56
+
57
+ - **Flexible reaction input** — define reactions as strings (`'Fe[3+] + 3OH[-] = Fe(OH)3(s)'`), dicts, or coefficient–name pairs
58
+ - **Multi-reaction systems** — solve coupled equilibria simultaneously using four built-in numerical methods
59
+ - **Speciation and precipitation** — handles both aqueous speciation (Ka, Kb, Kf) and solubility products (Ksp)
60
+ - **Solvent extraction** — single-stage and multistage counter-current / cross-current extraction circuits
61
+ - **Activity coefficients** — plug in Davies, Debye-Hückel, or any custom gamma function
62
+ - **Parameter fitting** — fit equilibrium constants to measured concentration data, with Monte Carlo uncertainty analysis
63
+ - **Plotting** — concentration profiles, speciation fraction diagrams, and extraction stage profiles via matplotlib
64
+
65
+ ---
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ pip install efta
71
+ ```
72
+
73
+ Requires Python 3.10+ and NumPy, SciPy, matplotlib (installed automatically).
74
+
75
+ ---
76
+
77
+ ## Quick Start
78
+
79
+ ### Acid-Base Equilibrium
80
+
81
+ ```python
82
+ from efta import reaction, reactions
83
+
84
+ # Define individual equilibrium reactions with their equilibrium constants
85
+ acetic_acid = reaction('CH3COOH = CH3COO[-] + H[+]', 1.8e-5) # Ka of acetic acid
86
+ water_autoion = reaction('H2O = H[+] + OH[-]', 1e-14) # Kw of water
87
+
88
+ # Combine reactions into a system and solve
89
+ sys = reactions(acetic_acid, water_autoion)
90
+
91
+ # Provide initial concentrations (mol/L) for all species
92
+ c_eq = sys.equilibrium({
93
+ 'CH3COOH': 0.1, # 0.1 M acetic acid
94
+ 'CH3COO[-]': 0.0,
95
+ 'H[+]': 1e-7, # neutral pH starting guess
96
+ 'OH[-]': 1e-7,
97
+ 'H2O': 1.0,
98
+ })
99
+
100
+ import numpy as np
101
+ print(f"pH = {-np.log10(c_eq['H[+]']):.2f}") # → pH ≈ 2.87
102
+ ```
103
+
104
+ ### Precipitation / Solubility
105
+
106
+ ```python
107
+ from efta import reaction, reactions
108
+
109
+ # ksp=True tells efta this is a solubility product reaction
110
+ calcite = reaction('CaCO3(s) = Ca[2+] + CO3[2-]', 3.36e-9, ksp=True)
111
+
112
+ sys = reactions(calcite)
113
+ c_eq = sys.equilibrium({'CaCO3(s)': 1.0, 'Ca[2+]': 0.0, 'CO3[2-]': 0.0})
114
+
115
+ print(f"[Ca²⁺] = {c_eq['Ca[2+]']:.2e} mol/L")
116
+ ```
117
+
118
+ ### Concentration Sweeps and Plotting
119
+
120
+ ```python
121
+ import numpy as np
122
+
123
+ # Sweep initial [CH3COOH] from 1e-4 to 1.0 M on a log scale
124
+ fig, ax = sys.plot(
125
+ {'CH3COOH': [1e-4, 1.0], 'H[+]': 1e-7, 'OH[-]': 1e-7, 'H2O': 1.0},
126
+ sweep='CH3COOH',
127
+ logx=True,
128
+ logy=True,
129
+ n_points=60,
130
+ )
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Species Notation
136
+
137
+ efta uses a compact notation for chemical species:
138
+
139
+ | Notation | Meaning | Example |
140
+ |---|---|---|
141
+ | `Fe[3+]` | Fe with charge 3+ | ferric iron |
142
+ | `OH[-]` | hydroxide | |
143
+ | `Fe(OH)3(s)` | solid phase | ferric hydroxide precipitate |
144
+ | `H2A2(org)` | organic phase | di-2-ethylhexylphosphoric acid |
145
+ | `e[-]` | electron | for redox reactions |
146
+ | `$(1/3)` | fractional coefficient | `$(1/3)Fe[3+]` |
147
+
148
+ ---
149
+
150
+ ## Reaction Construction
151
+
152
+ Four equivalent ways to define the same reaction:
153
+
154
+ ```python
155
+ from efta import reaction
156
+
157
+ # 1. String (most readable)
158
+ r = reaction('Fe[3+] + 3OH[-] = Fe(OH)3(s)', 1e3)
159
+
160
+ # 2. Stoichiometry dict (negative = reactant, positive = product)
161
+ r = reaction({'Fe[3+]': -1, 'OH[-]': -3, 'Fe(OH)3(s)': 1}, 1e3)
162
+
163
+ # 3. Separate reactant and product dicts
164
+ r = reaction({'Fe[3+]': 1, 'OH[-]': 3}, {'Fe(OH)3(s)': 1}, 1e3)
165
+
166
+ # 4. (coefficient, name) pairs — last argument is K
167
+ r = reaction((-1, 'Fe[3+]'), (-3, 'OH[-]'), (1, 'Fe(OH)3(s)'), 1e3)
168
+ ```
169
+
170
+ ### Combining Reactions
171
+
172
+ Reactions can be added and scaled. K values update automatically:
173
+
174
+ ```python
175
+ # Adding two reactions combines their stoichiometries; K values multiply
176
+ r_combined = r1 + r2
177
+
178
+ # Scaling multiplies all coefficients; K is raised to that power
179
+ r_half = r1 / 2 # divide all coefficients by 2 → K becomes √K
180
+ r_rev = r1 * -1 # reverse the reaction → K becomes 1/K
181
+ ```
182
+
183
+ ---
184
+
185
+ ## The `reactions` System
186
+
187
+ A `reactions` object holds multiple coupled reactions and provides the solver interface:
188
+
189
+ ```python
190
+ from efta import reaction, reactions
191
+
192
+ sys = reactions(r1, r2, r3)
193
+
194
+ # --- Solve for equilibrium concentrations ---
195
+ c_eq = sys.equilibrium({'Fe[3+]': 0.01, 'OH[-]': 1e-7, ...})
196
+
197
+ # --- Inspect which species are in the system ---
198
+ print(sys.species) # frozenset of all species names
199
+ print(sys.aqueous_species) # aqueous species only
200
+ print(sys.organic_species) # organic-phase species only
201
+
202
+ # --- Plot a concentration sweep ---
203
+ fig, ax = sys.plot({'Fe[3+]': [1e-5, 0.1], ...}, sweep='Fe[3+]', logx=True)
204
+
205
+ # --- Inverse solve: find initial [X] that gives a target equilibrium ---
206
+ c_target = sys.find(
207
+ unknown='NaOH',
208
+ c0={'NaOH': 0.0, 'H[+]': 1e-7, ...},
209
+ target={'H[+]': 1e-8}, # target pH 8
210
+ )
211
+ ```
212
+
213
+ ---
214
+
215
+ ## The `solution` Class
216
+
217
+ A `solution` pairs a concentration dict with a volume, and provides convenient access methods:
218
+
219
+ ```python
220
+ from efta import solution
221
+
222
+ sol = solution({'H[+]': 1e-4, 'OH[-]': 1e-10, 'H2O': 1.0}, volume=0.5)
223
+
224
+ sol['H[+]'] # 1e-4 — species concentration in mol/L
225
+ sol.pH # 4.0 — convenience property
226
+ sol.ionic_strength # mol/L
227
+ sol.moles('H[+]') # mol = concentration × volume
228
+ sol.mass('H2O') # g = moles × molar mass
229
+
230
+ sol.aqueous # dict of aqueous-phase species only
231
+ sol.organic # dict of organic-phase species only
232
+ sol.solid # dict of solid-phase species only
233
+
234
+ sol_2L = sol(2.0) # clone with 2 L volume — moles are preserved
235
+ mixed = sol1 + sol2 # mix two solutions (moles add, volumes add)
236
+ ```
237
+
238
+ ### Creating a `solution` directly from `reactions`
239
+
240
+ ```python
241
+ sol = sys.solution({'CH3COOH': 0.1, 'H2O': 1.0}, volume=1.0)
242
+ # Returns a solution object at equilibrium
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Activity Coefficients (Non-Ideal Systems)
248
+
249
+ Register a gamma function for any species in a reaction. The solver calls it at each iteration and adjusts K accordingly:
250
+
251
+ ```python
252
+ import math
253
+
254
+ # Davies equation activity coefficient (depends on ionic strength I)
255
+ def davies(I):
256
+ sqI = math.sqrt(I)
257
+ return 10 ** (-0.509 * 3**2 * (sqI / (1 + sqI) - 0.3 * I))
258
+
259
+ # 'I' is a special token — efta computes ionic strength and passes it to davies()
260
+ rxn.set_gamma('Fe[3+]', (davies, 'I'))
261
+
262
+ # Gamma depending on another species' concentration
263
+ rxn.set_gamma('Fe[3+]', (lambda c_cl: 1 - 0.1 * c_cl, 'Cl[-]'))
264
+
265
+ # Constant gamma (no dependencies)
266
+ rxn.set_gamma('Fe[3+]', (lambda: 0.5,))
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Solvent Extraction
272
+
273
+ efta includes a full solvent extraction module for modelling liquid–liquid extraction processes.
274
+
275
+ ```python
276
+ from efta import reaction, solution
277
+ from efta.solventextraction import (
278
+ sx, countercurrent, distribution_coef, separation_factor, splitter
279
+ )
280
+
281
+ # Extraction reaction: metal transfers from aqueous to organic phase
282
+ rxn = reaction('LaCl[2+] + 3H2A2(org) = LaClA2(HA)4(org) + 2H[+]', 10.6)
283
+
284
+ feed = solution({'LaCl[2+]': 0.003, 'H[+]': 0.3}, volume=1.0) # aqueous feed
285
+ org = solution({'H2A2(org)': 0.25}, volume=1.0) # organic phase
286
+
287
+ # Single-stage extraction
288
+ stage = sx(rxn, feed, org)
289
+ stage.run()
290
+ extract, raffinate = stage.outlets[0], stage.outlets[1]
291
+
292
+ D = stage.distribution_coef('La') # D = [La]_org / [La]_aq
293
+ beta = stage.separation_factor('La', 'Ce') # β = D(La) / D(Ce)
294
+
295
+ # 5-stage counter-current extraction circuit
296
+ ms = countercurrent(rxn, stages=5, feed=feed, organic=org)
297
+ ms.run() # solve all stages at equilibrium
298
+ ms.run(efficiency=0.85) # with stage efficiency < 1
299
+
300
+ raffinate = ms.outlets[5] # aqueous exit after stage 5
301
+ extract = ms.outlets[1] # organic exit after stage 1
302
+
303
+ # Plot concentration profile across stages
304
+ ms.plot(['La', 'Ce'], phase='aq')
305
+
306
+ # Reflux design with a flow splitter
307
+ split = splitter(1, 2) # splits flow: 1/3 reflux, 2/3 forward
308
+ reflux, forward = split(extract)
309
+ ```
310
+
311
+ Available multistage topologies:
312
+
313
+ | Function | Description |
314
+ |---|---|
315
+ | `countercurrent` | Aqueous feeds stage 1→n, organic feeds stage n→1 |
316
+ | `crosscurrent` | Aqueous feeds stage 1→n, fresh organic at every stage |
317
+ | `strip_countercurrent` | Organic 1→n, aqueous n→1 (stripping mode) |
318
+ | `strip_crosscurrent` | Organic 1→n, fresh aqueous at every stage |
319
+
320
+ ---
321
+
322
+ ## Parameter Fitting
323
+
324
+ Fit unknown equilibrium constants to experimental data:
325
+
326
+ ```python
327
+ from efta import freaction, freactions, model, analyze
328
+
329
+ # $(x1) is a free parameter — efta will optimise it
330
+ r_fit = freaction('Fe[3+] + 3OH[-] = Fe(OH)3(s)', '$(x1)', ksp=True)
331
+
332
+ # Experimental data: list of {species: measured_concentration} dicts
333
+ data = [
334
+ {'Fe[3+]': 1e-5, 'OH[-]': 1e-3},
335
+ {'Fe[3+]': 2e-5, 'OH[-]': 5e-4},
336
+ # ...
337
+ ]
338
+
339
+ best_fit = model(r_fit, data)
340
+ print(f"Best log K = {best_fit.logK:.2f}")
341
+
342
+ # Bootstrap uncertainty analysis
343
+ result = analyze(r_fit, data, n_bootstrap=200)
344
+ print(f"log K = {result.logK_mean:.2f} ± {result.logK_std:.2f}")
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Module Reference
350
+
351
+ | Module | Description |
352
+ |---|---|
353
+ | `efta.reaction` | `reaction` class — single equilibrium reaction |
354
+ | `efta.reactions` | `reactions` class — coupled system and solver |
355
+ | `efta.species` | Species name parsing: `species()`, `formula()`, `charge()`, `components()` |
356
+ | `efta.solution` | `solution` class — composition + volume |
357
+ | `efta.mixture` | `mixture` class — ordered collection of solutions |
358
+ | `efta.balance` | Cluster detection and conservation-law analysis |
359
+ | `efta.system` | System assembly, activity coefficients, extent↔concentration |
360
+ | `efta.solver` | Numerical solvers (Method L, A, B, DE) |
361
+ | `efta.model` | Parameter fitting, Monte Carlo analysis |
362
+ | `efta.plotting` | Concentration plots, speciation diagrams, `style` singleton |
363
+ | `efta.styling` | Runtime palette and font-size helpers |
364
+ | `efta.periodic_table` | Atomic masses and element lookup |
365
+ | `efta.solventextraction.sx` | Single-stage extraction |
366
+ | `efta.solventextraction.multistage` | Multistage extraction circuits |
367
+ | `efta.solventextraction.units` | `splitter` flow-splitter unit |
368
+
369
+ ---
370
+
371
+ ## Error Handling
372
+
373
+ All efta exceptions inherit from `EftaError`, so you can catch the entire family with a single clause:
374
+
375
+ ```python
376
+ from efta import EftaError, ConvergenceError, ConvergenceWarning
377
+ import warnings
378
+
379
+ # Turn convergence warnings into hard errors (useful during debugging)
380
+ warnings.filterwarnings('error', category=ConvergenceWarning)
381
+
382
+ try:
383
+ c_eq = sys.equilibrium(c0)
384
+ except ConvergenceError as e:
385
+ print(f"Solver failed — best residual: {e.residual:.2e}")
386
+ except EftaError as e:
387
+ print(f"efta error: {e}")
388
+ ```
389
+
390
+ | Exception | Raised when |
391
+ |---|---|
392
+ | `SpeciesError` | Species name cannot be parsed |
393
+ | `ReactionError` | Reaction is malformed (bad K, empty side, …) |
394
+ | `BalanceError` | Automatic balancing fails |
395
+ | `InputError` | Invalid argument passed to a function |
396
+ | `ConcentrationError` | Negative or missing initial concentration |
397
+ | `ConvergenceError` | All solver methods fail to converge |
398
+ | `ConvergenceWarning` | Solver returns a result but residual exceeds tolerance |
399
+
400
+ ---
401
+
402
+ ## License
403
+
404
+ MIT — see [LICENSE](LICENSE) for details.