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
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda"
}
144 changes: 135 additions & 9 deletions pysipfenn/misc/conveniences.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,143 @@
from importlib.resources import files
import ast
import inspect
import json
from importlib.resources import files
from importlib import import_module
import pkgutil

def _find_pymatgen_class(class_name: str):
"""Locate a class anywhere in pymatgen, robust to module reorganization."""
import pymatgen
for _, modname, _ in pkgutil.walk_packages(pymatgen.__path__, prefix="pymatgen."):
try:
mod = import_module(modname)
except Exception:
continue
obj = getattr(mod, class_name, None)
if isinstance(obj, type) and obj.__module__.startswith("pymatgen"):
return obj
return None
Comment on lines +8 to +19

def patchCovalentRadiiForExoticElements() -> None:
"""
"""
patchRadii = {
"Bk": 1.68,
"Cf": 1.68,
"Es": 1.65,
"Fm": 1.67,
"Md": 1.73,
"No": 1.76,
"Lr": 1.61,
"Rf": 1.57,
"Db": 1.49,
"Sg": 1.43,
"Bh": 1.41,
"Hs": 1.34,
"Mt": 1.29,
"Ds": 1.28,
"Rg": 1.21,
"Cn": 1.22,
"Nh": 1.36,
"Fl": 1.43,
"Mc": 1.62,
"Lv": 1.75,
"Ts": 1.65,
"Og": 1.57,
}


CovalentRadius = _find_pymatgen_class("CovalentRadius")
if CovalentRadius is None:
raise RuntimeError(
"Could not locate `CovalentRadius` class in pymatgen; "
"pymatgen's layout may have changed and this patch needs updating."
)
source_file = inspect.getsourcefile(CovalentRadius)
with open(source_file, "r") as f:
src = f.read()

dict_node = None
for cls in ast.walk(ast.parse(src)):
if not (isinstance(cls, ast.ClassDef) and cls.name == "CovalentRadius"):
continue
for stmt in cls.body:
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
target, value = stmt.target.id, stmt.value
elif (isinstance(stmt, ast.Assign)
and len(stmt.targets) == 1
and isinstance(stmt.targets[0], ast.Name)):
target, value = stmt.targets[0].id, stmt.value
else:
continue
if target == "radius" and isinstance(value, ast.Dict):
dict_node = value
break
break

if dict_node is None:
raise RuntimeError(
f"Could not locate `CovalentRadius.radius` dict in {source_file}; "
"pymatgen's layout may have changed and this patch needs updating."
)

existing = ast.literal_eval(dict_node)
# Skip writing if the file is already up to date with our patch values.
if any(existing.get(el) is None for el in patchRadii):
merged = {**patchRadii, **existing}

# Match pymatgen's existing indentation by reading it from the source
# rather than hardcoding spaces, so the patch survives style changes.
src_lines = src.splitlines(keepends=True)
first_key = dict_node.keys[0]
entry_indent = src_lines[first_key.lineno - 1][:first_key.col_offset]
close_indent = src_lines[dict_node.end_lineno - 1][:dict_node.end_col_offset - 1]

new_literal = "{\n" + "".join(
f'{entry_indent}"{el}": {v},\n' for el, v in merged.items()
) + close_indent + "}"

# Convert (line, col) bounds to byte offsets and splice.
line_starts = [0]
for line in src.splitlines(keepends=True):
line_starts.append(line_starts[-1] + len(line))
start = line_starts[dict_node.lineno - 1] + dict_node.col_offset
end = line_starts[dict_node.end_lineno - 1] + dict_node.end_col_offset

src = src[:start] + new_literal + src[end:]
with open(source_file, "w") as f:
f.write(src)

def patchPymatgenForExoticElements(
x: bool = True,
iupacOrder: bool = True
iupacOrder: bool = True,
radii: bool = True,
) -> None:
"""Patches pymatgen's ``core/periodic_table.json`` with (selectable) electronegativities and IUPAC ordering values
needed to correctly handle some exotic chemical elements. The IUPAC rules are followed exactly per Table VI in the
same reference. The electronegativity values are `not` Pauling ones but based on Oganov 2021 and are meant to be
"""
Patch pymatgen's installed element data for elements whose properties are
missing or incomplete in the default pymatgen data files.

This function directly edits files inside the installed pymatgen package:

1. Patches pymatgen's ``core/periodic_table.json`` with (selectable) electronegativities and IUPAC ordering values
needed to correctly handle some exotic chemical elements. The IUPAC rules are followed exactly per Table VI in the
same reference. The electronegativity values are `not` Pauling ones but based on Oganov 2021 and are meant to be
used primarily for providing trend information for ML model deployment (has to be included in training).

2. CovalentRadius.radius
Adds missing covalent radii for elements Bk through Og using `ast` to locate the dictionary definition in
pymatgen's source code, merge in the missing values, and write the updated literal back to disk. Radii reference
values from Pekka Pyykkö, The Journal of Physical Chemistry A 2015 119 (11), 2326-2337,
DOI: 10.1021/jp5065819

Comment on lines +116 to +132
Args:
x: Patch electronegativities.
iupacOrder: Patch IUPAC ordering of elements in chemical formulas so that they can be handled at all.
radii: Patch ``CovalentRadius.radius`` with covalent radii for elements past Cm.

Returns:
None. The ``core/periodic_table.json`` file in local install of ``pymatgen`` is patched. Reinstall or upgrade
of ``pymatgen`` reverses the changes.
None. The ``core/periodic_table.json`` files and the python file containing the ``CovalentRadius`` in the
local install of ``pymatgen`` are patched. Reinstall or upgrade ``pymatgen`` to reverse the changes.
"""

patchIUPAC = {
Expand Down Expand Up @@ -66,7 +187,12 @@ def patchPymatgenForExoticElements(
if x:
for el in patchX:
pt[el]["X"] = patchX[el]
if iupacOrder:
if iupacOrder:
for el in patchIUPAC:
pt[el]["IUPAC ordering"] = patchIUPAC[el]
json.dump(pt, f)
json.dump(pt, f)

# Patch covalent radii on disk.
# We locate the dict with `ast` and splice a merged literal back in.
if radii:
patchCovalentRadiiForExoticElements()
Loading
Loading