Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support `numpy>2.0`
- Bump `pint` to `0.24.4` for `numpy` `v2.0` compatibility and to mitigate CI issues (#239, @SuixiongTay, @rkingsbury)
- CI: add `python` `v3.13` to post-merge unit tests
- Docs: `sphinx-material` theme migrated to `sphinx-immaterial` (#2xX, @ugognw, @rkingsbury)
- Docs: `tox -e docs` command configured to fail on warning (#255, ugognw)
- Docs: ReadTheDocs built with Python 3.11 (#255, ugognw)
- Use `importlib` to locate test files (#241, @SuixiongTay)
Expand Down
13 changes: 5 additions & 8 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx_immaterial",
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
Expand Down Expand Up @@ -166,24 +167,20 @@

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "sphinx_material"
html_theme = "sphinx_immaterial"

# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
# "sidebar_width": "300px",
# "page_width": "1200px",
"base_url": "https://pyeql.readthedocs.io/en/latest/",
"site_url": "https://pyeql.readthedocs.io/en/latest/",
"repo_url": "https://github.com/KingsburyLab/pyEQL/",
"repo_name": "pyEQL",
# 'logo_icon': 'e798',
"html_minify": True,
"css_minify": True,
"nav_title": "pyEQL: a python interface for water chemistry",
"color_primary": "blue",
"color_accent": "light-blue",
"globaltoc_depth": 2,
"toc_title": "pyEQL: a python interface for water chemistry",
"palette": { "primary": "blue", "accent": "light-blue" },
"globaltoc_collapse": True,
}
html_sidebars = {"**": ["logo-text.html", "globaltoc.html", "localtoc.html", "searchbox.html"]}
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Please abide by the following guidelines when contributing code to `pyEQL`:

## Documentation

Improvements to the documentation are most welcome! Our documentation system uses `sphinx` with the [Materials for Sphinx](https://bashtage.github.io/sphinx-material/) theme. To edit the documentation locally, run `tox -e autodocs` from the repository root directory. This will serve the documents to `http://localhost:8000/` so you can view them in your web browser. When you make changes to the files in the `docs/` directory, the documentation will automatically rebuild and update in your browser (you might have to refresh the page to see changes).
Improvements to the documentation are most welcome! Our documentation system uses `sphinx` with the [Sphinx-Immaterial](https://jbms.github.io/sphinx-immaterial/) theme. To edit the documentation locally, run `tox -e autodocs` from the repository root directory. This will serve the documents to `http://localhost:8000/` so you can view them in your web browser. When you make changes to the files in the `docs/` directory, the documentation will automatically rebuild and update in your browser (you might have to refresh the page to see changes).

## Changelog

Expand Down
18 changes: 9 additions & 9 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Requirements file for ReadTheDocs, check .readthedocs.yml.
# To build the module reference correctly, make sure every external package
# under `install_requires` in `setup.cfg` is also listed here!
pint>=0.19
numpy
scipy
pymatgen>=2024.9.10
iapws
monty
maggma
phreeqpython
pint>=0.24.4
numpy>1.26
scipy>=1.12
pymatgen>=2025.1.9
iapws>=1.5.3
monty>=2024.12.10
maggma>=0.71.4
phreeqpython>=1.5.2
sphinx>=3.2.1
nbsphinx
sphinx-rtd-theme
ipython>=9.3.0
myst-parser[linkify]
git+https://github.com/bashtage/sphinx-material.git # Material theme
sphinx-immaterial # Material theme
4 changes: 2 additions & 2 deletions src/pyEQL/engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

"""

import copy
import logging
import os
import warnings
Expand Down Expand Up @@ -215,7 +216,7 @@ def _setup_ppsol(self, solution: "solution.Solution") -> None:
if bare_el in SPECIAL_ELEMENTS:
# PHREEQC will ignore float-formatted oxi states. Need to make sure we are
# passing, e.g. 'C(4)' and not 'C(4.0)'
key = f'{bare_el}({int(float(el.split("(")[-1].split(")")[0]))})'
key = f"{bare_el}({int(float(el.split('(')[-1].split(')')[0]))})"
elif bare_el in ["H", "O"]:
continue
else:
Expand Down Expand Up @@ -702,7 +703,6 @@ def equilibrate(self, solution: "solution.Solution") -> None:
def __deepcopy__(self, memo) -> "NativeEOS":
# custom deepcopy required because the PhreeqPython instance used by the Native and Phreeqc engines
# is not pickle-able.
import copy

cls = self.__class__
result = cls.__new__(cls)
Expand Down
114 changes: 4 additions & 110 deletions src/pyEQL/solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import numpy as np
from maggma.stores import JSONStore, Store
from monty.dev import deprecated
from monty.json import MontyDecoder, MSONable
from monty.serialization import dumpfn, loadfn
from pint import DimensionalityError, Quantity
Expand Down Expand Up @@ -509,9 +508,6 @@ def dielectric_constant(self) -> Quantity:
r"""
Returns the dielectric constant of the solution.

Args:
None

Returns:
Quantity: the dielectric constant of the solution, dimensionless.

Expand Down Expand Up @@ -1253,10 +1249,10 @@ def add_solute(self, formula: str, amount: str):
"""Primary method for adding substances to a pyEQL solution.

Args:
formula (str): Chemical formula for the solute. Charged species must contain a + or - and
(for polyvalent solutes) a number representing the net charge (e.g. 'SO4-2').
amount (str): The amount of substance in the specified unit system. The string should contain
both a quantity and a pint-compatible representation of a ureg. e.g. '5 mol/kg' or '0.1 g/L'.
formula (str): Chemical formula for the solute. Charged species must contain a+ or - and
(for polyvalent solutes) a number representing the net charge (e.g. 'SO4-2').
amount (str): The amount of substance in the specified unit system. The string should
contain both a quantity and a pint-compatible representation of a ureg. e.g. '5 mol/kg' or '0.1 g/L'.
"""
# if units are given on a per-volume basis,
# iteratively solve for the amount of solute that will preserve the
Expand Down Expand Up @@ -1710,9 +1706,6 @@ def get_activity_coefficient(
Args:
solute: The solute for which to retrieve the activity coefficient
scale: The activity coefficient concentration scale
verbose: If True, pyEQL will print a message indicating the parent salt
that is being used for activity calculations. This option is
useful when modeling multicomponent solutions. False by default.

Returns:
Quantity: the activity coefficient as a dimensionless pint Quantity
Expand Down Expand Up @@ -1757,10 +1750,6 @@ def get_activity(
The concentration scale for the returned activity.
Valid options are "molal", "molar", and "rational" (i.e., mole fraction).
By default, the molal scale activity is returned.
verbose:
If True, pyEQL will print a message indicating the parent salt
that is being used for activity calculations. This option is
useful when modeling multicomponent solutions. False by default.

Returns:
The thermodynamic activity of the solute in question (dimensionless Quantity)
Expand Down Expand Up @@ -2695,98 +2684,3 @@ def __str__(self) -> str:
l6 = f"Solvent: {self.solvent}"
l7 = f"Components: {self.components.keys():}"
return f"{l1}\n{l2}\n{l3}\n{l4}\n{l5}\n{l6}\n{l7}"

"""
Legacy methods to be deprecated in a future release.
"""

@deprecated(
message="list_salts() is deprecated and will be removed in the next release! Use Solution.get_salt_dict() instead.)"
)
def list_salts(self, unit="mol/kg", decimals=4): # pragma: no cover
for k, v in self.get_salt_dict().items():
print(k + "\t {:0.{decimals}f}".format(v, decimals=decimals))

@deprecated(
message="list_solutes() is deprecated and will be removed in the next release! Use Solution.components.keys() instead.)"
)
def list_solutes(self): # pragma: no cover
"""List all the solutes in the solution."""
return list(self.components.keys())

@deprecated(
message="list_concentrations() is deprecated and will be removed in the next release! Use Solution.print() instead.)"
)
def list_concentrations(self, unit="mol/kg", decimals=4, type="all"): # pragma: no cover
"""
List the concentration of each species in a solution.

Parameters
----------
unit: str
String representing the desired concentration ureg.
decimals: int
The number of decimal places to display. Defaults to 4.
type : str
The type of component to be sorted. Defaults to 'all' for all
solutes. Other valid arguments are 'cations' and 'anions' which
return lists of cations and anions, respectively.

Returns:
-------
dict
Dictionary containing a list of the species in solution paired with their amount in the specified units
:meta private:
"""
result_list = []
# populate a list with component names

if type == "all":
print("Component Concentrations:\n")
print("========================\n")
for item in self.components:
amount = self.get_amount(item, unit)
result_list.append([item, amount])
print(item + ":" + "\t {0:0.{decimals}f~}".format(amount, decimals=decimals))
elif type == "cations":
print("Cation Concentrations:\n")
print("========================\n")
for item in self.components:
if self.components[item].charge > 0:
amount = self.get_amount(item, unit)
result_list.append([item, amount])
print(item + ":" + "\t {0:0.{decimals}f~}".format(amount, decimals=decimals))
elif type == "anions":
print("Anion Concentrations:\n")
print("========================\n")
for item in self.components:
if self.components[item].charge < 0:
amount = self.get_amount(item, unit)
result_list.append([item, amount])
print(item + ":" + "\t {0:0.{decimals}f~}".format(amount, decimals=decimals))

return result_list

@deprecated(
message="list_activities() is deprecated and will be removed in the next release! Use Solution.print() instead.)"
)
def list_activities(self, decimals=4): # pragma: no cover
"""
List the activity of each species in a solution.

Parameters
----------
decimals: int
The number of decimal places to display. Defaults to 4.

Returns:
-------
dict
Dictionary containing a list of the species in solution paired with their activity

:meta private:
"""
print("Component Activities:\n")
print("=====================\n")
for i in self.components:
print(i + ":" + "\t {0.magnitude:0.{decimals}f}".format(self.get_activity(i), decimals=decimals))
2 changes: 1 addition & 1 deletion tests/test_phreeqc.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def test_conductivity(s1):

# MgCl2
for conc, cond in zip([0.001, 0.05, 0.1], [124.15, 114.49, 97.05], strict=False):
s1 = Solution({"Mg+2": f"{conc} mol/L", "Cl-": f"{2*conc} mol/L"})
s1 = Solution({"Mg+2": f"{conc} mol/L", "Cl-": f"{2 * conc} mol/L"})
assert np.isclose(
s1.conductivity.to("S/m").magnitude, 2 * conc * cond / 10, atol=1
), f"Conductivity test failed for MgCl2 at {conc} mol/L. Result = {s1.conductivity.to('S/m').magnitude}"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"""

import copy
import platform
import logging
import platform
from importlib.resources import files

import numpy as np
Expand Down
Loading