Skip to content

Commit 2db0e8e

Browse files
committed
docs update part 1
1 parent ff39bbd commit 2db0e8e

13 files changed

+432
-91
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ coverage.xml
4242
build/*
4343
dist/*
4444
sdist/*
45-
docs/*
45+
docs/_build/*
46+
docs/_static/*
47+
docs/api/*
4648
cover/*
4749
MANIFEST
4850

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- Significant documentation updates.
1213
- `Solution.components` is now automatically sorted in descending order of amount, for
1314
consistency with `anions`, `cations`, and `neutrals`.
1415

docs/amounts.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Solute Amounts
2+
3+
## See all components in the solution
4+
5+
You can inspect the solutes present in the solution via the `components` attribute. This comprises a dictionary of solute formula: moles, where 'moles' is the number of moles of that solute in the `Solution`. Note that the solvent (water) is present in `components`, too.
6+
`components` is reverse sorted, with the most predominant component (i.e., the solvent)
7+
listed first.
8+
9+
```python
10+
>>> from pyEQL import Solution
11+
>>> s = Solution({"Mg+2": "0.5 mol/L", "Cl-": "1.0 mol/L"})
12+
>>> s.components
13+
{'H2O(aq)': 54.83678280993063, 'Cl[-1]': 1.0, 'Mg[+2]': 0.5, 'H[+1]': 1e-07, 'OH[-1]': 1e-07}
14+
```
15+
16+
Similarly, you can use the properties `anions`, `cations`, `neutrals`, and `solvent` to
17+
retrieve subsets of `components`:
18+
19+
```python
20+
>>> s.anions
21+
{'Cl[-1]': 1.0, 'OH[-1]': 1e-07}
22+
>>> s.cations
23+
{'Mg[+2]': 0.5, 'H[+1]': 1e-07}
24+
>>> s.neutrals
25+
{'H2O(aq)': 54.83678280993063}
26+
>>> s.solvent
27+
'H2O(aq)'
28+
```
29+
30+
Like `components`, all of the above dicts are sorted in order of decreasing amount.
31+
32+
## Get the amount of a specific solute
33+
34+
To get the amount of a specific solute, use `get_amount()` and specify the units you want:
35+
36+
```python
37+
>>> from pyEQL import Solution
38+
>>> s = Solution({"Mg+2": "0.5 mol/L", "Cl-": "1.0 mol/L"})
39+
>>> s.get_amount('Mg[+2]', 'mol')
40+
<Quantity(0.5, 'mole')>
41+
```
42+
43+
`get_amount` is highly flexible with respect to the types of units it can interpret. You
44+
can request amounts in moles, mass, or equivalents (i.e., charge-weighted moles) per
45+
unit of mass or volume.
46+
47+
```python
48+
>>> s.get_amount('Mg[+2]', 'M')
49+
<Quantity(0.5, 'molar')>
50+
>>> s.get_amount('Mg[+2]', 'm')
51+
<Quantity(0.506124103, 'mole / kilogram')>
52+
>>> s.get_amount('Mg[+2]', 'eq/L')
53+
<Quantity(1.0, 'mole / liter')>
54+
>>> s.get_amount('Mg[+2]', 'ppm')
55+
<Quantity(12152.5, 'milligram / liter')>
56+
>>> s.get_amount('Mg[+2]', 'ppb')
57+
<Quantity(12152500.0, 'microgram / liter')>
58+
>>> s.get_amount('Mg[+2]', 'ppt')
59+
<Quantity(1.21525e+10, 'nanogram / liter')>
60+
```
61+
62+
:::{important}
63+
The unit `'ppt'` is ambiguous in the water community. To most researchers, it means
64+
"parts per trillion" or ng/L, while to many engineers and operators it means "parts
65+
per THOUSAND" or g/L. `pyEQL` interprets `ppt` as **parts per trillion**.
66+
:::
67+
68+
You can also request dimensionless concentrations as weight percent (`'%'`),
69+
mole fraction (`'fraction'`) or the total _number_
70+
of particles in the solution (`'count'`, useful for setting up simulation boxes).
71+
72+
```python
73+
>>> s.get_amount('Mg[+2]', '%')
74+
<Quantity(1.17358141, 'dimensionless')>
75+
>>> s.get_amount('Mg[+2]', 'fraction')
76+
<Quantity(0.00887519616, 'dimensionless')>
77+
>>> s.get_amount('Mg[+2]', 'count')
78+
<Quantity(3.01107038e+23, 'dimensionless')>
79+
```
80+
81+
## Total Element Concentrations
82+
83+
"Total" concentrations (i.e., concentrations of all species containing a particular
84+
element) are important for certain types of equilibrium calculations. These can
85+
be retrieved via `get_total_amount`. `get_total_amount` takes an element name as
86+
the first argument, and a unit as the second.
87+
88+
```python
89+
>>> from pyEQL import Solution
90+
>>> s = Solution({"Mg+2": "0.5 mol/L", "Cl-": "1.0 mol/L"})
91+
>>> s.equilibrate()
92+
>>> s.components
93+
{'H2O(aq)': 54.85346847938828, 'Cl[-1]': 0.9186683796593457, 'Mg[+2]': 0.41866839204646417, 'MgCl[+1]': 0.08133160795194606, 'OH[-1]': 1.4679440802358093e-07, 'H[+1]': 1.1833989847708719e-07, 'HCl(aq)': 1.2388705241250352e-08, 'MgOH[+1]': 3.9747494391744955e-13, 'O2(aq)': 7.027122927701743e-25, 'HClO(aq)': 1.5544872892067526e-27, 'ClO[-1]': 6.339364938003202e-28, 'H2(aq)': 5.792559717610837e-35, 'ClO2[-1]': 0.0, 'ClO3[-1]': 0.0, 'ClO4[-1]': 0.0, 'HClO2(aq)': 0.0}
94+
>>> s.get_total_amount('Mg', 'mol')
95+
<Quantity(0.5, 'mole')>
96+
```
97+
98+
## Elements present in a `Solution`
99+
100+
If you just want to know the elements present in the `Solution`, use `elements`. This
101+
returns a list of elements, sorted alphabetically.
102+
103+
```python
104+
>>> from pyEQL import Solution
105+
>>> s = Solution({"Mg+2": "0.5 mol/L", "Cl-": "1.0 mol/L"})
106+
>>> s.elements
107+
['Cl', 'H', 'Mg', 'O']
108+
```

docs/arithmetic.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Arithmetic Operations
2+
3+
## Addition and Subtraction
4+
5+
You can use the `+` operator to mix (combine) two solutions. The moles of each component
6+
in the two solutions will be added together, and the volume of the mixed solution will
7+
be _approximately_ equal to the sum of the two volumes, depending on the electrolyte
8+
modeling engine used.
9+
10+
```python
11+
>>> from pyEQL import Solution
12+
>>> s1 = Solution({"Na+": "0.5 mol/L", "Cl-": "0.5 mol/L"})
13+
>>> s2 = Solution({"Na+": "0.1 mol/L", "Cl-": "0.1 mol/L"})
14+
>>> s1+s2
15+
<pyEQL.solution.Solution object at 0x7f171aee3af0>
16+
>>> (s1+s2).get_amount('Na+', 'mol')
17+
<Quantity(0.6, 'mole')>
18+
>>> (s1+s2).volume
19+
<Quantity(1.99989659, 'liter')>
20+
```
21+
22+
:::{note}
23+
Both `Solution` involved in an addition operation must use the same [electrolyte
24+
modeling engine](engines.md).
25+
:::
26+
27+
Subtraction is not implemented and will raise a `NotImplementedError`.
28+
29+
```
30+
>>> s1-s2
31+
Traceback (most recent call last):
32+
File "<stdin>", line 1, in <module>
33+
File "/home/ryan/mambaforge/envs/pbx/code/pyEQL/src/pyEQL/solution.py", line 2481, in __sub__
34+
raise NotImplementedError("Subtraction of solutions is not implemented.")
35+
NotImplementedError: Subtraction of solutions is not implemented.
36+
```
37+
38+
## Multiplication and Division
39+
40+
The `*` and `/` operators scale the volume and all component amounts by a factor.
41+
42+
```python
43+
from pyEQL import Solution
44+
>>> s = Solution({"Na+": "0.2 mol/L", "Cl-": "0.2 mol/L"})
45+
>>> s.volume
46+
<Quantity(1, 'liter')>
47+
>>> s.get_amount('Cl-', 'mol')
48+
<Quantity(0.2, 'mole')>
49+
>>> s*=1.5
50+
<Quantity(1.5, 'liter')>
51+
s.get_amount('Cl-', 'mol')
52+
<Quantity(0.3, 'mole')>
53+
```
54+
55+
The modulo operator `//` is not implemented.

docs/chemistry.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
# Chemical Formulas
44

5-
pyEQL interprets the chemical formula of a substance to calculate its molecular
5+
`pyEQL` interprets the chemical formula of a substance to calculate its molecular
66
weight and formal charge. The formula is also used as a key to search the
7-
database for parameters (e.g. diffusion coefficient) that are used in subsequent
8-
calculations.
7+
[property database](database.md) for parameters (e.g. diffusion coefficient) that are
8+
used in subsequent calculations.
99

1010
## How to Enter Valid Chemical Formulas
1111

@@ -32,8 +32,7 @@ by `(aq)` to disambiguate them from solids.
3232

3333
:::{important}
3434
**When writing multivalent ion formulas, it is strongly recommended that you put the charge number AFTER the + or -
35-
sign** (e.g., type "Mg+2" NOT "Mg2+"). The latter formula is ambiguous - it could mean $`Mg_2^+`$ or $`Mg^{+2}`$ and
36-
it will be processed incorrectly into `Mg[+0.5]`
35+
sign** (e.g., type "Mg+2" NOT "Mg2+"). The latter formula is ambiguous - it could mean $Mg_2^+$ or $Mg^{+2}$ and it will be processed incorrectly into `Mg[+0.5]`
3736
:::
3837

3938
(manual-testing)=
@@ -56,11 +55,11 @@ When using the `Solution` class,
5655
- However, the `components` attribute is a special dictionary that automatically standardizes formulas when accessed. So, you can still enter the formula
5756
however you want. For example, the following all access or modify the same element in `components`:
5857

59-
```
60-
Solution.components.get('Na+')
61-
Solution.components["Na+1"]
62-
Solution.components.update("Na[+]": 2)
63-
Solution.components["Na[+1]"]
58+
```python
59+
>>> Solution.components.get('Na+')
60+
>>> Solution.components["Na+1"]
61+
>>> Solution.components.update("Na[+]": 2)
62+
>>> Solution.components["Na[+1]"]
6463
```
6564

6665
- Arguments to `Solution.get_property` can be entered in any format you prefer. When `pyEQL` queries the database, it will automatically standardize the formula.

docs/class_solution.md

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,10 @@
11
(class-solution)=
22

3-
# The Solution Class
3+
# `Solution` API Reference
44

5-
The `Solution` class defines a pythonic interface for **creating**, **modifying**, and **estimating properties** of
6-
electrolyte solutions. It is the core feature of `pyEQL` and the primary user-facing class.
5+
This page contains detailed information on each of the methods, attributes, and properties in `Solution`. Use the sidebar on the right for easier navigation.
76

8-
## Creating a solution
9-
10-
A `Solution` created with no arguments will default to pure water at pH=7, T=25 degrees Celsius, and 1 atm pressure.
11-
12-
```
13-
>>> from pyEQL import Solution
14-
>>> s1 = Solution()
15-
>>> s1.pH
16-
6.998877352386266
17-
```
18-
19-
Alternatively, you can use the `autogenerate()` function to easily create common solutions like seawater:
20-
21-
```
22-
>>> from pyEQL.functions import autogenerate
23-
>>> s2 = autogenerate('seawater')
24-
<pyEQL.solution.Solution object at 0x7f057de6b0a0>
25-
```
26-
27-
You can inspect the solutes present in the solution via the `components` attribute. This comprises a dictionary of solute formula: moles, where 'moles' is the number of moles of that solute in the `Solution`. Note that the solvent (water) is present in `components`, too.
28-
29-
```
30-
>>> s2.components
31-
{'H2O': 55.34455401423017,
32-
'H+': 7.943282347242822e-09,
33-
'OH-': 8.207436858780226e-06,
34-
'Na+': 0.46758273714962967,
35-
'Mg+2': 0.052661180523467986,
36-
'Ca+2': 0.010251594148212318,
37-
'K+': 0.010177468379526856,
38-
'Sr+2': 9.046483353663286e-05,
39-
'Cl-': 0.54425785619973,
40-
'SO4-2': 0.028151873448454025,
41-
'HCO3-': 0.001712651176926199,
42-
'Br-': 0.0008395244921424563,
43-
'CO3-2': 0.00023825904349479546,
44-
'B(OH)4': 0.0001005389715937341,
45-
'F-': 6.822478260456777e-05,
46-
'B(OH)3': 0.0003134669156396757,
47-
'CO2': 9.515218476861175e-06
48-
}
49-
```
50-
51-
To get the amount of a specific solute, use `get_amount()` and specify the units you want:
52-
53-
```
54-
>>> s2.get_amount('Na+', 'g/L')
55-
<Quantity(10.6636539, 'gram / liter')>
56-
```
57-
58-
Finally, you can manually create a solution with any list of solutes, temperature, pressure, etc. that you need:
59-
60-
```
61-
>>> from pyEQL import Solution
62-
>>> s1 = Solution(solutes={'Na+':'0.5 mol/kg', 'Cl-': '0.5 mol/kg'},
63-
pH=8,
64-
temperature = '20 degC',
65-
volume='8 L'
66-
)
67-
```
68-
69-
## Class reference
70-
71-
The remainder of this page contains detailed information on each of the methods, attributes, and properties in `Solution`. Use the sidebar on the right for easier navigation.
7+
## Solution
728

739
```{eval-rst}
7410
.. autoclass:: pyEQL.Solution

docs/creating.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Creating a `Solution`
2+
3+
The `Solution` class defines a pythonic interface for **creating**, **modifying**, and **estimating properties** of electrolyte solutions. It is the core feature of `pyEQL` and the primary user-facing class. There are several ways to create a `Solution`.
4+
5+
6+
## Empty solution
7+
8+
With no input arguments, you get an empty `Solution` at pH 7 and 1 atm pressure.
9+
10+
```python
11+
>>> from pyEQL import Solution
12+
>>> s = Solution()
13+
>>> print(s)
14+
Volume: 1.000 l
15+
Pressure: 1.000 atm
16+
Temperature: 298.150 K
17+
Components: ['H2O(aq)', 'H[+1]', 'OH[-1]']
18+
```
19+
20+
## Manual Creation
21+
22+
Typically, you will create a solution by specifying a list of solutes. Solutes are
23+
passed as a `dict` with amounts given **as strings** that include units (see [units](units.md)). Any unit that can be understood by `get_amount` is valid.
24+
25+
```python
26+
>>> from pyEQL import Solution
27+
>>> s = Solution({"Na+": "0.5 mol/L", "Cl-": "0.5 mol/L"})
28+
```
29+
30+
You can also specify conditions such as temperature, pressure, pH, and pE (redox potential).
31+
32+
33+
Finally, you can manually create a solution with any list of solutes, temperature, pressure, etc. that you need:
34+
35+
```python
36+
>>> from pyEQL import Solution
37+
>>> s1 = Solution(solutes={'Na+':'0.5 mol/kg', 'Cl-': '0.5 mol/kg'},
38+
pH=8,
39+
temperature = '20 degC',
40+
volume='8 L',
41+
pE = 4,
42+
)
43+
```
44+
45+
## Using a preset
46+
47+
Alternatively, you can use the `pyEQL.functions.autogenerate()` function to easily create common solutions like seawater:
48+
49+
```
50+
>>> from pyEQL.functions import autogenerate
51+
>>> s2 = autogenerate('seawater')
52+
<pyEQL.solution.Solution object at 0x7f057de6b0a0>
53+
```
54+
55+
## From a dictionary
56+
57+
If you have [converted a `Solution` to a `dict`](serialization.md#serialization-to-dict),
58+
you can re-instantiate it using the `Solution.from_dict()` class method.
59+
60+
## From a file
61+
62+
If you [save a `Solution` to a `.json` file](serialization.md#saving-to-a-json-file),
63+
you can [recreate it](serialization.md#loading-from-a-json-file) using
64+
[monty.serialization.loadfn](https://pythonhosted.org/monty/monty.html#module-monty.serialization)
65+
66+
```python
67+
>>> from monty.serialization import loadfn
68+
>>> s = loadfn('test.json')
69+
print(s)
70+
Volume: 1.000 l
71+
Pressure: 1.000 atm
72+
Temperature: 298.150 K
73+
Components: ['H2O(aq)', 'H[+1]', 'OH[-1]']
74+
```

docs/engines.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Electrolyte Modeling Engines
2+
3+
## Overview
4+
5+
## The `'native'` engine (Default)
6+
7+
:::{warning}
8+
experimental - speciation + activity model
9+
:::
10+
11+
## The ``phreeqc'` engine
12+
13+
The `phreeqc` engine uses [`phreeqpython`](https://github.com/Vitens/phreeqpython)
14+
for speciation, activity, and volume calculations.

0 commit comments

Comments
 (0)