From e4c6c85e9ac3d31bd21b45af74dd45f31c01713c Mon Sep 17 00:00:00 2001 From: Ryan Kingsbury Date: Thu, 7 Aug 2025 20:06:06 -0400 Subject: [PATCH 1/3] Docs: change to sphinx-immaterial theme --- CHANGELOG.md | 1 + docs/conf.py | 13 +++++-------- docs/contributing.md | 2 +- docs/requirements.txt | 18 +++++++++--------- src/pyEQL/solution.py | 18 ++++-------------- 5 files changed, 20 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d738e888..3028a663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/docs/conf.py b/docs/conf.py index 56945ec8..db6a5f47 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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", @@ -166,7 +167,7 @@ # 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 @@ -174,16 +175,12 @@ 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"]} diff --git a/docs/contributing.md b/docs/contributing.md index 85ed8580..d9646082 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -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 diff --git a/docs/requirements.txt b/docs/requirements.txt index 1d8b470e..a03ee2c8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -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 diff --git a/src/pyEQL/solution.py b/src/pyEQL/solution.py index c4d7ba24..85100003 100644 --- a/src/pyEQL/solution.py +++ b/src/pyEQL/solution.py @@ -509,9 +509,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. @@ -1253,10 +1250,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 @@ -1710,9 +1707,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 @@ -1757,10 +1751,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) From 5c6c18ff3b93e8faf35cd5ee7fca24576a757bfc Mon Sep 17 00:00:00 2001 From: Ryan Kingsbury Date: Thu, 7 Aug 2025 20:06:51 -0400 Subject: [PATCH 2/3] Solution: remove deprecated methods - list_salts() - list_solutes() - list_concentrations() - list_activities() --- src/pyEQL/solution.py | 95 ------------------------------------------- 1 file changed, 95 deletions(-) diff --git a/src/pyEQL/solution.py b/src/pyEQL/solution.py index 85100003..f22b6cdc 100644 --- a/src/pyEQL/solution.py +++ b/src/pyEQL/solution.py @@ -2685,98 +2685,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)) From d1bd10b18dfc1ea0cf3042a5d5bbff214c6a1cd9 Mon Sep 17 00:00:00 2001 From: Ryan Kingsbury Date: Thu, 7 Aug 2025 20:11:40 -0400 Subject: [PATCH 3/3] pre-commit run --all-files --- src/pyEQL/engines.py | 4 ++-- src/pyEQL/solution.py | 1 - tests/test_phreeqc.py | 2 +- tests/test_solution.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pyEQL/engines.py b/src/pyEQL/engines.py index bca84e93..a762db73 100644 --- a/src/pyEQL/engines.py +++ b/src/pyEQL/engines.py @@ -6,6 +6,7 @@ """ +import copy import logging import os import warnings @@ -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: @@ -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) diff --git a/src/pyEQL/solution.py b/src/pyEQL/solution.py index f22b6cdc..64b8b4fa 100644 --- a/src/pyEQL/solution.py +++ b/src/pyEQL/solution.py @@ -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 diff --git a/tests/test_phreeqc.py b/tests/test_phreeqc.py index 54eb521e..987a61b6 100644 --- a/tests/test_phreeqc.py +++ b/tests/test_phreeqc.py @@ -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}" diff --git a/tests/test_solution.py b/tests/test_solution.py index 0e90b25c..14fb845a 100644 --- a/tests/test_solution.py +++ b/tests/test_solution.py @@ -7,8 +7,8 @@ """ import copy -import platform import logging +import platform from importlib.resources import files import numpy as np