SMACT 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {smact-3.1.0/SMACT.egg-info → smact-3.2.0}/PKG-INFO +5 -6
  2. {smact-3.1.0 → smact-3.2.0}/README.md +1 -2
  3. {smact-3.1.0 → smact-3.2.0/SMACT.egg-info}/PKG-INFO +5 -6
  4. {smact-3.1.0 → smact-3.2.0}/SMACT.egg-info/requires.txt +2 -2
  5. {smact-3.1.0 → smact-3.2.0}/pyproject.toml +4 -4
  6. {smact-3.1.0 → smact-3.2.0}/smact/data_loader.py +8 -8
  7. {smact-3.1.0 → smact-3.2.0}/smact/screening.py +42 -19
  8. {smact-3.1.0 → smact-3.2.0}/smact/tests/test_utils.py +27 -0
  9. {smact-3.1.0 → smact-3.2.0}/smact/utils/crystal_space/generate_composition_with_smact.py +114 -1
  10. {smact-3.1.0 → smact-3.2.0}/LICENSE +0 -0
  11. {smact-3.1.0 → smact-3.2.0}/MANIFEST.in +0 -0
  12. {smact-3.1.0 → smact-3.2.0}/SMACT.egg-info/SOURCES.txt +0 -0
  13. {smact-3.1.0 → smact-3.2.0}/SMACT.egg-info/dependency_links.txt +0 -0
  14. {smact-3.1.0 → smact-3.2.0}/SMACT.egg-info/top_level.txt +0 -0
  15. {smact-3.1.0 → smact-3.2.0}/setup.cfg +0 -0
  16. {smact-3.1.0 → smact-3.2.0}/smact/__init__.py +0 -0
  17. {smact-3.1.0 → smact-3.2.0}/smact/benchmarking/__init__.py +0 -0
  18. {smact-3.1.0 → smact-3.2.0}/smact/benchmarking/pymatgen_benchmark.py +0 -0
  19. {smact-3.1.0 → smact-3.2.0}/smact/benchmarking/smact_benchmark.py +0 -0
  20. {smact-3.1.0 → smact-3.2.0}/smact/benchmarking/utilities.py +0 -0
  21. {smact-3.1.0 → smact-3.2.0}/smact/builder.py +0 -0
  22. {smact-3.1.0 → smact-3.2.0}/smact/data/Covalent_radii.csv +0 -0
  23. {smact-3.1.0 → smact-3.2.0}/smact/data/SSE.csv +0 -0
  24. {smact-3.1.0 → smact-3.2.0}/smact/data/SSE_2015.csv +0 -0
  25. {smact-3.1.0 → smact-3.2.0}/smact/data/SSE_Pauling.csv +0 -0
  26. {smact-3.1.0 → smact-3.2.0}/smact/data/element_data.txt +0 -0
  27. {smact-3.1.0 → smact-3.2.0}/smact/data/element_valence_modified.csv +0 -0
  28. {smact-3.1.0 → smact-3.2.0}/smact/data/hhi.txt +0 -0
  29. {smact-3.1.0 → smact-3.2.0}/smact/data/ionic_radii.csv +0 -0
  30. {smact-3.1.0 → smact-3.2.0}/smact/data/magpie.csv +0 -0
  31. {smact-3.1.0 → smact-3.2.0}/smact/data/ordered_periodic.txt +0 -0
  32. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_state_probability_table.json +0 -0
  33. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states.txt +0 -0
  34. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_SP.txt +0 -0
  35. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_icsd.txt +0 -0
  36. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_icsd24_common.txt +0 -0
  37. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_icsd24_counts.json +0 -0
  38. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_icsd24_filtered.txt +0 -0
  39. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_icsd24_raw.txt +0 -0
  40. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_pmg.txt +0 -0
  41. {smact-3.1.0 → smact-3.2.0}/smact/data/oxidation_states_wiki.txt +0 -0
  42. {smact-3.1.0 → smact-3.2.0}/smact/data/shannon_radii.csv +0 -0
  43. {smact-3.1.0 → smact-3.2.0}/smact/data/shannon_radii_ML_extended.csv +0 -0
  44. {smact-3.1.0 → smact-3.2.0}/smact/data/solid_properties.txt +0 -0
  45. {smact-3.1.0 → smact-3.2.0}/smact/data/solid_properties.xlsx +0 -0
  46. {smact-3.1.0 → smact-3.2.0}/smact/data/species_rep/ion_embedding_M3GNet-MP-2023.11.1-oxi-Eform_cosine_similarity.json +0 -0
  47. {smact-3.1.0 → smact-3.2.0}/smact/data/species_rep/ion_embedding_M3GNet-MP-2023.11.1-oxi-band_gap_cosine_similarity.json +0 -0
  48. {smact-3.1.0 → smact-3.2.0}/smact/data/species_rep/skipspecies_20221028_319ion_dim200_cosine_similarity.json +0 -0
  49. {smact-3.1.0 → smact-3.2.0}/smact/distorter.py +0 -0
  50. {smact-3.1.0 → smact-3.2.0}/smact/dopant_prediction/__init__.py +0 -0
  51. {smact-3.1.0 → smact-3.2.0}/smact/dopant_prediction/doper.py +0 -0
  52. {smact-3.1.0 → smact-3.2.0}/smact/lattice.py +0 -0
  53. {smact-3.1.0 → smact-3.2.0}/smact/lattice_parameters.py +0 -0
  54. {smact-3.1.0 → smact-3.2.0}/smact/mainpage.py +0 -0
  55. {smact-3.1.0 → smact-3.2.0}/smact/metallicity.py +0 -0
  56. {smact-3.1.0 → smact-3.2.0}/smact/oxidation_states.py +0 -0
  57. {smact-3.1.0 → smact-3.2.0}/smact/properties.py +0 -0
  58. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/__init__.py +0 -0
  59. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/database.py +0 -0
  60. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/mutation.py +0 -0
  61. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/prediction.py +0 -0
  62. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/probability_models.py +0 -0
  63. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/structure.py +0 -0
  64. {smact-3.1.0 → smact-3.2.0}/smact/structure_prediction/utilities.py +0 -0
  65. {smact-3.1.0 → smact-3.2.0}/smact/tests/__init__.py +0 -0
  66. {smact-3.1.0 → smact-3.2.0}/smact/tests/test_core.py +0 -0
  67. {smact-3.1.0 → smact-3.2.0}/smact/tests/test_doper.py +0 -0
  68. {smact-3.1.0 → smact-3.2.0}/smact/tests/test_metallicity.py +0 -0
  69. {smact-3.1.0 → smact-3.2.0}/smact/tests/test_structure.py +0 -0
  70. {smact-3.1.0 → smact-3.2.0}/smact/utils/__init__.py +0 -0
  71. {smact-3.1.0 → smact-3.2.0}/smact/utils/band_gap_simple.py +0 -0
  72. {smact-3.1.0 → smact-3.2.0}/smact/utils/composition.py +0 -0
  73. {smact-3.1.0 → smact-3.2.0}/smact/utils/crystal_space/__init__.py +0 -0
  74. {smact-3.1.0 → smact-3.2.0}/smact/utils/crystal_space/download_compounds_with_mp_api.py +0 -0
  75. {smact-3.1.0 → smact-3.2.0}/smact/utils/crystal_space/plot_embedding.py +0 -0
  76. {smact-3.1.0 → smact-3.2.0}/smact/utils/oxidation.py +1 -1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SMACT
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Semiconducting Materials by Analogy and Chemical Theory
5
5
  Author-email: The SMACT Developers <a.walsh@imperial.ac.uk>
6
6
  Maintainer-email: "Anthony O. Onwuli" <anthony.onwuli16@imperial.ac.uk>
@@ -81,11 +81,11 @@ Requires-Dist: pandarallel>=1.6.5; extra == "optional"
81
81
  Requires-Dist: matplotlib-venn[shapely]>=1.1.2; extra == "optional"
82
82
  Provides-Extra: strict
83
83
  Requires-Dist: pydantic==2.9.2; extra == "strict"
84
- Requires-Dist: mp-api==0.45.3; extra == "strict"
84
+ Requires-Dist: mp-api==0.45.5; extra == "strict"
85
85
  Requires-Dist: pymatviz==0.15.1; extra == "strict"
86
- Requires-Dist: mp-api==0.45.3; extra == "strict"
86
+ Requires-Dist: mp-api==0.45.5; extra == "strict"
87
87
  Requires-Dist: seaborn==0.13.2; extra == "strict"
88
- Requires-Dist: pymatgen==2025.3.10; extra == "strict"
88
+ Requires-Dist: pymatgen==2025.4.24; extra == "strict"
89
89
  Requires-Dist: matminer==0.9.3; extra == "strict"
90
90
  Requires-Dist: umap-learn==0.5.7; extra == "strict"
91
91
  Requires-Dist: kaleido==0.2.1; extra == "strict"
@@ -107,7 +107,7 @@ Dynamic: license-file
107
107
  ![dependencies](https://img.shields.io/librariesio/release/pypi/smact)
108
108
  [![CI Status](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml/badge.svg)](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml)
109
109
  [![codecov](https://codecov.io/gh/WMD-group/SMACT/branch/master/graph/badge.svg?token=UtgVxjoYNP)](https://codecov.io/gh/WMD-group/SMACT)
110
- ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact)
110
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/WMD-group/SMACT)
111
111
 
112
112
  # SMACT
113
113
 
@@ -141,7 +141,6 @@ Use cases are available in our [examples](https://smact.readthedocs.io/en/latest
141
141
  - Element compositions can be screened through based on the heuristic filters of charge neutrality and electronegativity order. This is handled using the [screening module](https://smact.readthedocs.io/en/latest/smact.screening.html) and [this publication](<https://www.cell.com/chem/fulltext/S2451-9294(16)30155-3>) describes the underlying theory. An example procedure is [outlined in the docs](https://smact.readthedocs.io/en/latest/examples/filter.html).
142
142
 
143
143
  - Further filters can be applied to generated lists of compositions in order to screen for particular properties. These properties are either intrinsic properties of elements or are calculated for compositions using the [properties module](https://smact.readthedocs.io/en/latest/smact.properties.html). For example:
144
-
145
144
  - An application is shown in [this publication](https://pubs.rsc.org/en/content/articlehtml/2018/sc/c7sc03961a), in which 160,000 chemical compositions are screened based on optical band gap calculated using the [solid-state energy scale](https://www.sciencedirect.com/science/article/pii/S0022459615300888).
146
145
  - The [oxidation_states module](https://smact.readthedocs.io/en/latest/smact.oxidation_states.html) can be used to filter out compositions containing metals in unlikely oxidation states according to [a data-driven model](https://pubs.rsc.org/en/content/articlelanding/2018/fd/c8fd00032h#!divAbstract).
147
146
 
@@ -10,7 +10,7 @@
10
10
  ![dependencies](https://img.shields.io/librariesio/release/pypi/smact)
11
11
  [![CI Status](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml/badge.svg)](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml)
12
12
  [![codecov](https://codecov.io/gh/WMD-group/SMACT/branch/master/graph/badge.svg?token=UtgVxjoYNP)](https://codecov.io/gh/WMD-group/SMACT)
13
- ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact)
13
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/WMD-group/SMACT)
14
14
 
15
15
  # SMACT
16
16
 
@@ -44,7 +44,6 @@ Use cases are available in our [examples](https://smact.readthedocs.io/en/latest
44
44
  - Element compositions can be screened through based on the heuristic filters of charge neutrality and electronegativity order. This is handled using the [screening module](https://smact.readthedocs.io/en/latest/smact.screening.html) and [this publication](<https://www.cell.com/chem/fulltext/S2451-9294(16)30155-3>) describes the underlying theory. An example procedure is [outlined in the docs](https://smact.readthedocs.io/en/latest/examples/filter.html).
45
45
 
46
46
  - Further filters can be applied to generated lists of compositions in order to screen for particular properties. These properties are either intrinsic properties of elements or are calculated for compositions using the [properties module](https://smact.readthedocs.io/en/latest/smact.properties.html). For example:
47
-
48
47
  - An application is shown in [this publication](https://pubs.rsc.org/en/content/articlehtml/2018/sc/c7sc03961a), in which 160,000 chemical compositions are screened based on optical band gap calculated using the [solid-state energy scale](https://www.sciencedirect.com/science/article/pii/S0022459615300888).
49
48
  - The [oxidation_states module](https://smact.readthedocs.io/en/latest/smact.oxidation_states.html) can be used to filter out compositions containing metals in unlikely oxidation states according to [a data-driven model](https://pubs.rsc.org/en/content/articlelanding/2018/fd/c8fd00032h#!divAbstract).
50
49
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SMACT
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Semiconducting Materials by Analogy and Chemical Theory
5
5
  Author-email: The SMACT Developers <a.walsh@imperial.ac.uk>
6
6
  Maintainer-email: "Anthony O. Onwuli" <anthony.onwuli16@imperial.ac.uk>
@@ -81,11 +81,11 @@ Requires-Dist: pandarallel>=1.6.5; extra == "optional"
81
81
  Requires-Dist: matplotlib-venn[shapely]>=1.1.2; extra == "optional"
82
82
  Provides-Extra: strict
83
83
  Requires-Dist: pydantic==2.9.2; extra == "strict"
84
- Requires-Dist: mp-api==0.45.3; extra == "strict"
84
+ Requires-Dist: mp-api==0.45.5; extra == "strict"
85
85
  Requires-Dist: pymatviz==0.15.1; extra == "strict"
86
- Requires-Dist: mp-api==0.45.3; extra == "strict"
86
+ Requires-Dist: mp-api==0.45.5; extra == "strict"
87
87
  Requires-Dist: seaborn==0.13.2; extra == "strict"
88
- Requires-Dist: pymatgen==2025.3.10; extra == "strict"
88
+ Requires-Dist: pymatgen==2025.4.24; extra == "strict"
89
89
  Requires-Dist: matminer==0.9.3; extra == "strict"
90
90
  Requires-Dist: umap-learn==0.5.7; extra == "strict"
91
91
  Requires-Dist: kaleido==0.2.1; extra == "strict"
@@ -107,7 +107,7 @@ Dynamic: license-file
107
107
  ![dependencies](https://img.shields.io/librariesio/release/pypi/smact)
108
108
  [![CI Status](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml/badge.svg)](https://github.com/WMD-group/SMACT/actions/workflows/ci.yml)
109
109
  [![codecov](https://codecov.io/gh/WMD-group/SMACT/branch/master/graph/badge.svg?token=UtgVxjoYNP)](https://codecov.io/gh/WMD-group/SMACT)
110
- ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact)
110
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/smact) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/WMD-group/SMACT)
111
111
 
112
112
  # SMACT
113
113
 
@@ -141,7 +141,6 @@ Use cases are available in our [examples](https://smact.readthedocs.io/en/latest
141
141
  - Element compositions can be screened through based on the heuristic filters of charge neutrality and electronegativity order. This is handled using the [screening module](https://smact.readthedocs.io/en/latest/smact.screening.html) and [this publication](<https://www.cell.com/chem/fulltext/S2451-9294(16)30155-3>) describes the underlying theory. An example procedure is [outlined in the docs](https://smact.readthedocs.io/en/latest/examples/filter.html).
142
142
 
143
143
  - Further filters can be applied to generated lists of compositions in order to screen for particular properties. These properties are either intrinsic properties of elements or are calculated for compositions using the [properties module](https://smact.readthedocs.io/en/latest/smact.properties.html). For example:
144
-
145
144
  - An application is shown in [this publication](https://pubs.rsc.org/en/content/articlehtml/2018/sc/c7sc03961a), in which 160,000 chemical compositions are screened based on optical band gap calculated using the [solid-state energy scale](https://www.sciencedirect.com/science/article/pii/S0022459615300888).
146
145
  - The [oxidation_states module](https://smact.readthedocs.io/en/latest/smact.oxidation_states.html) can be used to filter out compositions containing metals in unlikely oxidation states according to [a data-driven model](https://pubs.rsc.org/en/content/articlelanding/2018/fd/c8fd00032h#!divAbstract).
147
146
 
@@ -59,10 +59,10 @@ matplotlib-venn[shapely]>=1.1.2
59
59
 
60
60
  [strict]
61
61
  pydantic==2.9.2
62
- mp-api==0.45.3
62
+ mp-api==0.45.5
63
63
  pymatviz==0.15.1
64
64
  seaborn==0.13.2
65
- pymatgen==2025.3.10
65
+ pymatgen==2025.4.24
66
66
  matminer==0.9.3
67
67
  umap-learn==0.5.7
68
68
  kaleido==0.2.1
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "SMACT"
9
- version = "3.1.0"
9
+ version = "3.2.0"
10
10
  description = "Semiconducting Materials by Analogy and Chemical Theory"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -108,11 +108,11 @@ optional = [
108
108
  ]
109
109
  strict = [
110
110
  "pydantic==2.9.2",
111
- "mp-api==0.45.3",
111
+ "mp-api==0.45.5",
112
112
  "pymatviz==0.15.1",
113
- "mp-api==0.45.3",
113
+ "mp-api==0.45.5",
114
114
  "seaborn==0.13.2",
115
- "pymatgen==2025.3.10",
115
+ "pymatgen==2025.4.24",
116
116
  "matminer==0.9.3",
117
117
  "umap-learn==0.5.7",
118
118
  "kaleido==0.2.1",
@@ -42,11 +42,11 @@ def set_warnings(enable=True):
42
42
 
43
43
 
44
44
  def _get_data_rows(filename):
45
- """Generator for datafile entries by row."""
45
+ """Generator for datafile entries by row for custom oxidation states lists. Skips rows with no oxidation states for performance."""
46
46
  with open(filename) as file:
47
47
  for line in file:
48
48
  line = line.strip()
49
- if line[0] != "#":
49
+ if line[0] != "#" and any(char.isdigit() for char in line):
50
50
  yield line.split()
51
51
 
52
52
 
@@ -58,7 +58,6 @@ def float_or_None(x):
58
58
  return None
59
59
 
60
60
 
61
- # Loader and cache for the element oxidation-state data.
62
61
  _el_ox_states = None
63
62
 
64
63
 
@@ -88,7 +87,6 @@ def lookup_element_oxidation_states(symbol, copy=True):
88
87
 
89
88
  if _el_ox_states is None:
90
89
  _el_ox_states = {}
91
-
92
90
  for items in _get_data_rows(os.path.join(data_directory, "oxidation_states.txt")):
93
91
  _el_ox_states[items[0]] = [int(oxidationState) for oxidationState in items[1:]]
94
92
 
@@ -259,7 +257,7 @@ def lookup_element_oxidation_states_custom(symbol, filepath, copy=True):
259
257
 
260
258
  Args:
261
259
  ----
262
- symbol (str) : the atomic symbol of the element to look up.
260
+ symbol (str) : the atomic symbol of the element to look up. "all" can be used to return the list (copy=True) or dict (copy=False) for all oxidation states.
263
261
  filepath (str) : the path to the text file containing the
264
262
  oxidation states data.
265
263
  copy (Optional(bool)): if True (default), return a copy of the
@@ -279,19 +277,21 @@ def lookup_element_oxidation_states_custom(symbol, filepath, copy=True):
279
277
 
280
278
  if _el_ox_states_custom is None:
281
279
  _el_ox_states_custom = {}
282
-
283
280
  for items in _get_data_rows(filepath):
284
281
  _el_ox_states_custom[items[0]] = [int(oxidationState) for oxidationState in items[1:]]
285
-
286
282
  if symbol in _el_ox_states_custom:
287
283
  if copy:
288
284
  # _el_ox_states_custom stores lists -> if copy is set, make an implicit
289
285
  # deep copy. The elements of the lists are integers, which are
290
286
  # "value types" in Python.
291
-
292
287
  return list(_el_ox_states_custom[symbol])
293
288
  else:
294
289
  return _el_ox_states_custom[symbol]
290
+ elif symbol == "all":
291
+ if copy:
292
+ return list(_el_ox_states_custom)
293
+ else:
294
+ return _el_ox_states_custom
295
295
  else:
296
296
  if _print_warnings:
297
297
  print(f"WARNING: Oxidation states for element {symbol} not found.")
@@ -344,8 +344,8 @@ def smact_filter(
344
344
  Returns:
345
345
  -------
346
346
  allowed_comps (list): Allowed compositions for that chemical system
347
- in the form [(elements), (oxidation states), (ratios)] if species_unique=True
348
- or in the form [(elements), (ratios)] if species_unique=False.
347
+ in the form [(elements), (oxidation states), (ratios)] if species_unique=True and tuple=False
348
+ or in the form [(elements), (ratios)] if species_unique=False and tuple=False.
349
349
 
350
350
  Example usage:
351
351
  >>> from smact.screening import smact_filter
@@ -371,8 +371,6 @@ def smact_filter(
371
371
 
372
372
 
373
373
  """
374
- compositions = []
375
-
376
374
  # Get symbols and electronegativities
377
375
  symbols = tuple(e.symbol for e in els)
378
376
  electronegs = [e.pauling_eneg for e in els]
@@ -385,32 +383,31 @@ def smact_filter(
385
383
  "pymatgen_sp": [e.oxidation_states_sp for e in els],
386
384
  "wiki": [e.oxidation_states_wiki for e in els],
387
385
  }
386
+
388
387
  if oxidation_states_set in oxi_set:
389
388
  ox_combos = oxi_set[oxidation_states_set]
389
+ if oxidation_states_set == "wiki":
390
+ warnings.warn(
391
+ "This set of oxidation states is sourced from Wikipedia. The results from using this set could be questionable and should not be used unless you know what you are doing and have inspected the oxidation states.",
392
+ stacklevel=2,
393
+ )
390
394
  elif os.path.exists(oxidation_states_set):
391
- ox_combos = [oxi_custom(e.symbol, oxidation_states_set) for e in els]
395
+ ox_combos = (oxi_custom(e.symbol, oxidation_states_set) for e in els)
392
396
  else:
393
397
  raise (
394
398
  Exception(
395
399
  f'{oxidation_states_set} is not valid. Enter either "smact14", "icsd", "pymatgen","wiki" or a filepath to a textfile of oxidation states.'
396
400
  )
397
401
  )
398
- if oxidation_states_set == "wiki":
399
- warnings.warn(
400
- "This set of oxidation states is sourced from Wikipedia. The results from using this set could be questionable and should not be used unless you know what you are doing and have inspected the oxidation states.",
401
- stacklevel=2,
402
- )
403
402
 
403
+ compositions = []
404
404
  for ox_states in itertools.product(*ox_combos):
405
405
  # Test for charge balance
406
406
  cn_e, cn_r = neutral_ratios(ox_states, stoichs=stoichs, threshold=threshold)
407
407
  # Electronegativity test
408
- if cn_e:
409
- electroneg_OK = pauling_test(ox_states, electronegs)
410
- if electroneg_OK:
411
- for ratio in cn_r:
412
- compositions.append((symbols, ox_states, ratio))
413
-
408
+ if cn_e and pauling_test(ox_states, electronegs):
409
+ for ratio in cn_r:
410
+ compositions.append((symbols, ox_states, ratio))
414
411
  # Return list depending on whether we are interested in unique species combinations
415
412
  # or just unique element combinations.
416
413
  if species_unique:
@@ -429,9 +426,12 @@ def smact_validity(
429
426
  composition: pymatgen.core.Composition | str,
430
427
  use_pauling_test: bool = True,
431
428
  include_alloys: bool = True,
432
- oxidation_states_set: str = "icsd24",
433
429
  check_metallicity: bool = False,
434
430
  metallicity_threshold: float = 0.7,
431
+ oxidation_states_set: str | None = None,
432
+ include_zero: bool = False,
433
+ consensus: int = 3,
434
+ commonality: str = "medium",
435
435
  ) -> bool:
436
436
  """
437
437
  Check if a composition is valid according to SMACT rules:
@@ -444,14 +444,24 @@ def smact_validity(
444
444
  composition (Composition or str): Composition to check.
445
445
  use_pauling_test (bool): Whether to apply the Pauling EN test.
446
446
  include_alloys (bool): Consider pure metals valid automatically.
447
- oxidation_states_set (str): Which set of oxidation states to use.
448
447
  check_metallicity (bool): If True, consider high metallicity valid.
449
448
  metallicity_threshold (float): Score threshold for metallicity validity.
449
+ oxidation_states_set (str): Which set of oxidation states to use. If specified it overrides the making of the oxidation states set.
450
+ include_zero (bool): Include oxidation state of zero in the filtered list. Default is False.
451
+ consensus (int): Minimum number of occurrences in literature for an ion to be considered valid. Default is 3.
452
+ commonality (str): Excludes species below a certain proportion of appearances in literature with respect to the total number of reports of a given element (after the consensus threshold has been applied). "low" includes all species, "medium" excludes rare species below 10% occurrence, and "high" excludes non-majority species below 50% occurrence. "main" selects the species with the highest occurrence for a given element. Users may also specify their own threshold (float or int). Default is "medium".
450
453
 
451
454
  Returns:
452
455
  bool: True if the composition is valid, False otherwise.
453
456
  """
454
457
  from smact import _gcd_recursive, metals, neutral_ratios
458
+ from smact.utils.oxidation import ICSD24OxStatesFilter
459
+
460
+ if oxidation_states_set is not None and any([include_zero, consensus != 3, commonality != "medium"]):
461
+ warnings.warn(
462
+ "Parameters include_zero, consensus, and commonality are only used when oxidation_states_set is None",
463
+ stacklevel=2,
464
+ )
455
465
 
456
466
  if isinstance(composition, str):
457
467
  composition = Composition(composition)
@@ -484,7 +494,20 @@ def smact_validity(
484
494
  electronegs = [e.pauling_eneg for e in smact_elems]
485
495
 
486
496
  # Get oxidation states data
487
- if oxidation_states_set == "smact14":
497
+ if oxidation_states_set is None:
498
+ ox_filter = ICSD24OxStatesFilter()
499
+ filtered_df = ox_filter.filter(consensus=consensus, include_zero=include_zero, commonality=commonality)
500
+ oxidation_dict = {
501
+ row["element"]: [int(x) for x in row["oxidation_state"].split()] for _, row in filtered_df.iterrows()
502
+ }
503
+ ox_combos = []
504
+ for el in smact_elems:
505
+ ox_el = oxidation_dict.get(el.symbol, None)
506
+ if ox_el is not None:
507
+ ox_combos.append(ox_el)
508
+ else:
509
+ return False
510
+ elif oxidation_states_set == "smact14":
488
511
  ox_combos = [el.oxidation_states_smact14 for el in smact_elems]
489
512
  elif oxidation_states_set == "icsd16":
490
513
  ox_combos = [el.oxidation_states_icsd16 for el in smact_elems]
@@ -133,6 +133,33 @@ class TestCrystalSpace(unittest.TestCase):
133
133
  # Clean up
134
134
  shutil.rmtree("data")
135
135
 
136
+ def test_generate_composition_with_smact_custom(self):
137
+ save_dir = "data/binary/df_binary_label.pkl"
138
+ oxidation_states_sets = ["smact14", "icsd24"]
139
+ oxidation_states_sets_dict = {
140
+ "smact14": {"smact_allowed": 388},
141
+ "icsd24": {"smact_allowed": 342},
142
+ }
143
+ for ox_states in oxidation_states_sets:
144
+ with self.subTest(ox_states=ox_states):
145
+ smact_df = generate_composition_with_smact.generate_composition_with_smact_custom(
146
+ num_elements=2,
147
+ max_stoich=3,
148
+ max_atomic_num=20,
149
+ save_path=save_dir,
150
+ oxidation_states_set=ox_states,
151
+ )
152
+ self.assertIsInstance(smact_df, pd.DataFrame)
153
+ self.assertTrue(len(smact_df) == 1330)
154
+ self.assertTrue(
155
+ smact_df["smact_allowed"].sum() == oxidation_states_sets_dict[ox_states]["smact_allowed"]
156
+ )
157
+ # Check if the data was saved to disk
158
+ self.assertTrue(os.path.exists(save_dir))
159
+
160
+ # Clean up
161
+ shutil.rmtree("data")
162
+
136
163
  @pytest.mark.skipif(
137
164
  (
138
165
  sys.platform == "win32"
@@ -13,6 +13,7 @@ from pymatgen.core import Composition
13
13
  from tqdm import tqdm
14
14
 
15
15
  from smact import Element, ordered_elements
16
+ from smact.data_loader import lookup_element_oxidation_states_custom
16
17
  from smact.screening import smact_filter
17
18
 
18
19
  warnings.simplefilter(action="ignore", category=UserWarning)
@@ -56,7 +57,7 @@ def generate_composition_with_smact(
56
57
  max_atomic_num (int): the maximum atomic number. Defaults to 103.
57
58
  num_processes (int): the number of processes to use. Defaults to None.
58
59
  save_path (str): the path to save the results. Defaults to None.
59
- oxidation_states_set (str): the oxidation states set to use. Options are "smact14", "icsd16", "icsd24", "pymatgen_sp" or a filepath to a custom oxidation states list. For reproducing the Faraday Discussions results, use "smact14".
60
+ oxidation_states_set (str): the oxidation states set to use. Options are "smact14", "icsd16", "icsd24", "pymatgen_sp". For reproducing the Faraday Discussions results, use "smact14". For custom oxidation states lists check generate_composition_with_smact_custom below.
60
61
 
61
62
  Returns:
62
63
  df (pd.DataFrame): A DataFrame of SMACT-generated compositions with boolean smact_allowed column.
@@ -142,3 +143,115 @@ def generate_composition_with_smact(
142
143
  print(f"Saved to {save_path}")
143
144
 
144
145
  return df
146
+
147
+
148
+ def generate_composition_with_smact_custom(
149
+ num_elements: int = 2,
150
+ max_stoich: int = 8,
151
+ max_atomic_num: int = 103,
152
+ num_processes: int | None = None,
153
+ save_path: str | None = None,
154
+ oxidation_states_set: str | None = None,
155
+ ) -> pd.DataFrame:
156
+ """
157
+ Generate all possible compositions of a given number of elements and
158
+ filter them with SMACT.
159
+
160
+ Args:
161
+ num_elements (int): the number of elements in a compound. Defaults to 2.
162
+ max_stoich (int): the maximum stoichiometric coefficient. Defaults to 8.
163
+ max_atomic_num (int): the maximum atomic number. Defaults to 103.
164
+ num_processes (int): the number of processes to use. Defaults to None.
165
+ save_path (str): the path to save the results. Defaults to None.
166
+ oxidation_states_set (str): the path to the oxidation states file. Defaults to None.
167
+
168
+ Returns:
169
+ df (pd.DataFrame): A DataFrame of SMACT-generated compositions with boolean smact_allowed column.
170
+
171
+ """
172
+ # 1. generate all possible combinations of elements
173
+ print("#1. Generating all possible combinations of elements...")
174
+
175
+ elements = [Element(element) for element in ordered_elements(1, max_atomic_num)]
176
+ combinations = list(itertools.combinations(elements, num_elements))
177
+ print(f"Number of generated combinations: {len(combinations)}")
178
+
179
+ # 2. generate all possible stoichiometric combinations
180
+ print("#2. Generating all possible stoichiometric combinations...")
181
+
182
+ pool = multiprocessing.Pool(processes=(multiprocessing.cpu_count() if num_processes is None else num_processes))
183
+ compounds = list(
184
+ tqdm(
185
+ pool.imap_unordered(
186
+ partial(
187
+ convert_formula,
188
+ num_elements=num_elements,
189
+ max_stoich=max_stoich,
190
+ ),
191
+ combinations,
192
+ ),
193
+ total=len(combinations),
194
+ )
195
+ )
196
+
197
+ pool.close()
198
+ pool.join()
199
+ # Flatten the list of lists into a single list
200
+ compounds = [item for sublist in compounds for item in sublist]
201
+
202
+ print(f"Number of generated compounds: {len(compounds)}")
203
+ compounds = list(set(compounds))
204
+ print(f"Number of generated compounds (unique): {len(compounds)}")
205
+
206
+ # 3. filter compounds with smact
207
+ print("#3. Filtering compounds with SMACT...")
208
+
209
+ ox_states_custom = lookup_element_oxidation_states_custom("all", oxidation_states_set, copy=False)
210
+ elements_pauling = [
211
+ Element(element)
212
+ for element in ordered_elements(1, max_atomic_num)
213
+ if element in ox_states_custom
214
+ and Element(element).pauling_eneg is not None
215
+ and Element(element).pauling_eneg >= Element("Fr").pauling_eneg
216
+ ]
217
+ compounds_pauling = list(itertools.combinations(elements_pauling, num_elements))
218
+
219
+ pool = multiprocessing.Pool(processes=(multiprocessing.cpu_count() if num_processes is None else num_processes))
220
+ results = list(
221
+ tqdm(
222
+ pool.imap_unordered(
223
+ partial(
224
+ smact_filter,
225
+ threshold=max_stoich,
226
+ oxidation_states_set=oxidation_states_set,
227
+ ),
228
+ compounds_pauling,
229
+ ),
230
+ total=len(compounds_pauling),
231
+ )
232
+ )
233
+ pool.close()
234
+ pool.join()
235
+
236
+ # 4. make data frame of results
237
+ print("#4. Making data frame of results...")
238
+ # make dataframework with index is compound and columns are boolean smact results
239
+ smact_allowed = []
240
+
241
+ for result in results:
242
+ for res in result:
243
+ symbols_stoich = zip(res[0], res[2], strict=False)
244
+ composition_dict = dict(symbols_stoich)
245
+ smact_allowed.append(Composition(composition_dict).reduced_formula)
246
+ smact_allowed = list(set(smact_allowed))
247
+ print(f"Number of compounds allowed by SMACT: {len(smact_allowed)}")
248
+
249
+ df = pd.DataFrame(data=False, index=compounds, columns=["smact_allowed"])
250
+ df.loc[smact_allowed, "smact_allowed"] = True
251
+
252
+ if save_path is not None:
253
+ Path(save_path).parent.mkdir(parents=True, exist_ok=True)
254
+ df.to_pickle(save_path)
255
+ print(f"Saved to {save_path}")
256
+
257
+ return df
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -136,6 +136,7 @@ class ICSD24OxStatesFilter:
136
136
  species_occurrences_df = self.ox_states_df[self.ox_states_df["oxidation_state"] != 0].reset_index(drop=True)
137
137
  else:
138
138
  species_occurrences_df = self.ox_states_df
139
+
139
140
  species_occurrences_df = species_occurrences_df[
140
141
  (species_occurrences_df.results_count >= consensus)
141
142
  ].reset_index(drop=True)
@@ -185,7 +186,6 @@ class ICSD24OxStatesFilter:
185
186
  final_summary.append(f"{element} {oxidation_states}".strip())
186
187
  else:
187
188
  final_summary.append(element)
188
-
189
189
  # Write the filtered oxidation states list to a txt file
190
190
  if not filename.endswith(".txt"):
191
191
  filename += ".txt"