Skip to content

Commit

Permalink
replace species with adsorbate to avoid confusion
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielYang59 committed Apr 2, 2024
1 parent c75e6c3 commit 27f8d61
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 37 deletions.
2 changes: 1 addition & 1 deletion cat_scaling/data/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
This object is oriented towards easier recording of stoichiometric number
for surface reactions (Unlike the Reaction class from pymatgen), as such
species names are treated as is (for example name like "*CO2" is allowed).
adsorbate names are treated as is (for example name like "*CO2" is allowed).
"""

from __future__ import annotations
Expand Down
4 changes: 2 additions & 2 deletions cat_scaling/relation/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def _convert_step(self, step: ReactionStep) -> np.ndarray:

# Add free species energy for adsorbed species to constant
# (intercept) term.
# NOTE: For species "*CO2", the energy for free "CO2"
# should be added (provided)
# NOTE: For adsorbate "*CO2", the energy for free "CO2"
# should be added
spec_arr[-1] += spec.energy

# Add correction term
Expand Down
57 changes: 26 additions & 31 deletions cat_scaling/relation/builder.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
# TODO: use consistent naming for "species" and "adsorbate" , where "adsorbate"
# is more specific and "species" is more general (which might be confusing
# together with "sample")


"""Build scaling relations (Relation) from adsorption energy (Eads).
The following would take CO2 reduction to CH4 reaction (CO2RR) as an example.
The traditional method:
As proposed in Peterson and Nørskov's work titled *Activity Descriptors for
CO2 Electroreduction to Methane on Transition-Metal Catalysts*, involves
grouping species involved in the CO2RR process into two categories:
grouping adsorbates involved in the CO2RR process into two categories:
C-centered (*COOH, *CO, *CHO, *CH2O) and O-centered (*OCH3, *O, *OH).
Within each group, a descriptor is nominated (in the original work, it was
*CO and *OH respectively). The adsorption energies of other species can
*CO and *OH respectively). The adsorption energies of other adsorbates can
then be approximated by their respective descriptor, effectively reducing
the dimensionality of the problem from 7D to 2D.
This reduction enables visualization and simplifies the analysis process.
The adaptive method:
As discussed in my MPhil work (at https://arxiv.org/abs/2402.03876),
recognizes that many species involved in the process include both oxygen
recognizes that many adsorbates involved in the process include both oxygen
and carbon. Therefore, it makes sense to include descriptors for both
elements in the relation. In that work, I discovered that this simple
approach can improve the coefficient of determination (R2) by
Expand Down Expand Up @@ -64,15 +59,15 @@ def __init__(
self.data = data

def _build_composite_descriptor(
self, spec_ratios: dict[str, float]
self, adsorbate_ratios: dict[str, float]
) -> np.ndarray:
"""Build a composite descriptor from child descriptors.
This method build a composite descriptor from a list of child
descriptors and their corresponding mixing ratios.
Parameters:
spec_ratios (dict[str, float]): Dict of species_name: ratio.
adsorbate_ratios (dict[str, float]): Dict of adsorbate_name: ratio.
Returns:
np.ndarray: The composite descriptor as a numpy array.
Expand All @@ -82,30 +77,30 @@ def _build_composite_descriptor(
if the lengths of names and ratios do not match.
"""

# Check arg: spec_ratios
if not isclose(sum(spec_ratios.values()), 1.0, abs_tol=1e-04):
# Check arg: adsorbate_ratios
if not isclose(sum(adsorbate_ratios.values()), 1.0, abs_tol=1e-04):
raise ValueError("Ratios should sum to 1.0.")

# Fetch child descriptors
child_descriptors = np.array(
[self.data.get_adsorbate(species) for species in spec_ratios]
[self.data.get_adsorbate(adsorbate) for adsorbate in adsorbate_ratios]
)

# Construct composite descriptor (from child descriptors)
return np.sum(
child_descriptors
* np.array(list(spec_ratios.values()))[:, np.newaxis],
* np.array(list(adsorbate_ratios.values()))[:, np.newaxis],
axis=0,
)

def _builder(
self, spec_name: str, ratios: dict[str, float]
self, adsorbate_name: str, ratios: dict[str, float]
) -> tuple[list[float], float, float]:
"""Core worker for building scaling relations.
Parameters:
spec_name (str): name of the target species to build.
ratios (dict[str, float]): Dict of species_name: ratio.
adsorbate_name (str): name of the target adsorbate to build.
ratios (dict[str, float]): Dict of adsorbate_name: ratio.
Returns:
coefs (list[float]): coefficients correspond to each descriptor
Expand Down Expand Up @@ -138,7 +133,7 @@ def _builder(
Multiple the coefficients with corresponding ratios,
and leave the intercept unchanged.
In a more "mathematical" manner:
In a more concise manner:
(where comp_des, A, N and target are arrays)
TODO: format this formula block (latex?)
1. The composite descriptor:
Expand All @@ -153,7 +148,7 @@ def _builder(

# Perform linear regression
_comp_des = composite_descriptor.reshape(-1, 1)
_target = self.data.get_adsorbate(spec_name).reshape(-1, 1)
_target = self.data.get_adsorbate(adsorbate_name).reshape(-1, 1)

reg = LinearRegression().fit(_comp_des, _target)

Expand All @@ -171,7 +166,7 @@ def _builder(
return coefs, intercept, metrics

def build_traditional(self, descriptors: Descriptors) -> EadsRelation:
"""Build scaling relations the traditional way, where each species is
"""Build scaling relations the traditional way, where each adsorbate is
approximated by a single descriptor within each group.
Returns:
Expand All @@ -189,8 +184,8 @@ def build_traditional(self, descriptors: Descriptors) -> EadsRelation:
metrics_dict = {}
ratios_dict = {}

for idx, (descriptor, species) in enumerate(groups.items()):
if species is None:
for idx, (descriptor, adsorbate) in enumerate(groups.items()):
if adsorbate is None:
raise ValueError(
"Group member for traditional builder cannot be None."
)
Expand All @@ -206,10 +201,10 @@ def build_traditional(self, descriptors: Descriptors) -> EadsRelation:
metrics_dict[descriptor] = 1.0
ratios_dict[descriptor] = ratios

# Build for each species
for spec_name in species:
# Build for each adsorbate
for adsorbate_name in adsorbate:
coefs, intercept, metrics = self._builder(
spec_name=spec_name,
adsorbate_name=adsorbate_name,
ratios=ratios,
)

Expand All @@ -221,10 +216,10 @@ def build_traditional(self, descriptors: Descriptors) -> EadsRelation:
]

# Collect results
coefficients_dict[spec_name] = _coefs
intercepts_dict[spec_name] = intercept
metrics_dict[spec_name] = metrics
ratios_dict[spec_name] = ratios
coefficients_dict[adsorbate_name] = _coefs
intercepts_dict[adsorbate_name] = intercept
metrics_dict[adsorbate_name] = metrics
ratios_dict[adsorbate_name] = ratios

return EadsRelation(
coefficients_dict, intercepts_dict, metrics_dict, ratios_dict
Expand Down Expand Up @@ -295,7 +290,7 @@ def build_adaptive(
}

_coefs, _intercept, metrics = self._builder(
spec_name=ads,
adsorbate_name=ads,
ratios=ratios,
)

Expand All @@ -310,7 +305,7 @@ def build_adaptive(
}

coefs, intercept, metrics = self._builder(
spec_name=ads,
adsorbate_name=ads,
ratios=opt_ratios,
)

Expand Down
4 changes: 2 additions & 2 deletions examples/traditional.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@
"\n",
"# (Optional but Recommended) Check metrics\n",
"print(\"Species R2 Score\")\n",
"for species, score in eads_relation.metrics.items():\n",
" print(f\"{species:<10} {score:.4f}\")"
"for adsorbate, score in eads_relation.metrics.items():\n",
" print(f\"{adsorbate:<10} {score:.4f}\")"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion tests/relation/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def test_builder(self):

# Test fitting *B with *A
coefs, intercept, metrics = builder._builder(
spec_name="*B", ratios={"*A": 1}
adsorbate_name="*B", ratios={"*A": 1}
)

# Check regression results
Expand Down

0 comments on commit 27f8d61

Please sign in to comment.