Exploring crystal space#

Our goal is to generate, analyze, and categorize chemical compositions, making it easier to discover interesting and useful materials. This tutorial is based on a publication in Faraday Discussions.

1. Getting started#

In this tutorial, we’ll:

  • Generate binary chemical compositions using the SMACT filter.

  • Explore whether these compositions exist in the Materials Project database.

  • Categorize the compositions based on whether they pass the SMACT filter and whether they are found in the database.

The final phase will categorize the compositions into four distinct categories based on their properties. The categorization is based on whether a composition is allowed by the SMACT filter (smact_allowed) and whether it is present in the Materials Project database (mp). The categories are as follows:

smact_allowed

mp

label

yes

yes

standard

yes

no

missing

no

yes

interesting

no

no

unlikely

2. Generating compositions#

First, we’ll create binary chemical compositions using the SMACT filter. The SMACT filter is a smart tool that helps us select compositions based on important chemical rules, such as oxidation states and electronegativity.

To generate these compositions, we’ll use a function called generate_composition_with_smact. This function allows us to enumerate all possible binary compositions and filter them based on the SMACT rules.

Key parameters:#

  • num_elements: Number of elements in the composition (e.g., 2 for binary).

  • max_stoich: The maximum ratio of each element (e.g., 8 could mean up to 8 atoms of each element).

  • max_atomic_num: Maximum atomic number for the elements considered

  • num_processes: Number of processes to run in parallel to speed up calculations.

  • save_path: Where to save the generated compositions.

Open in Colab

# Install the required packages
try:
    import google.colab

    IN_COLAB = True
except:
    IN_COLAB = False

if IN_COLAB:
    !uv pip install smact[crystal_space] --quiet
from smact.utils.crystal_space.generate_composition_with_smact import (
    generate_composition_with_smact,
)
df_smact = generate_composition_with_smact(
    num_elements=2,
    max_stoich=8,
    max_atomic_num=103,
    num_processes=8,
    save_path="data/binary/df_binary_label.pkl",
    oxidation_states_set="smact14",
)
#1. Generating all possible combinations of elements...
Number of generated combinations: 5253
#2. Generating all possible stoichiometric combinations...
100%|██████████| 5253/5253 [00:05<00:00, 923.16it/s] 
Number of generated compounds: 336192
Number of generated compounds (unique): 225879
#3. Filtering compounds with SMACT...
100%|██████████| 4656/4656 [00:02<00:00, 1856.51it/s]
#4. Making data frame of results...
Number of compounds allowed by SMACT: 13464
Saved to data/binary/df_binary_label.pkl
df_smact
smact_allowed
Cr4C5 True
Bk3Bi8 False
Cf6F5 False
Hf5Pb4 False
CeHg2 False
... ...
Be4Xe3 False
NpSb5 False
Mn4Al False
Th6Ti7 False
H7Rn False

225879 rows × 1 columns

3. Download data from the Materials Project#

Next, we download data from the Materials Project api using the download_mp_data function. This function allows us to download data for a given number of elements and maximum stoichiometry. The data includes the chemical formula, energy, and other properties of the compounds.

download_mp_data function takes in the following parameters:

Key parameters:#

  • mp_api_key: your Materials Project API key

  • num_elements: Number of elements in the composition (e.g., 2 for binary).

  • max_stoich: The maximum ratio of each element (e.g., 8 could mean up to 8 atoms of each element).

  • save_dir: Where to save the downloaded data

mp_api_key = ""  # Add your Materials Project API key here
save_mp_dir = "data/binary/mp_data"
from smact.utils.crystal_space.download_compounds_with_mp_api import download_mp_data

# download data from MP for binary compounds
docs = download_mp_data(
    mp_api_key=mp_api_key,
    num_elements=2,
    max_stoich=8,
    save_dir=save_mp_dir,
)
  0%|          | 0/22 [00:00<?, ?it/s]
Downloading data for A2B3...
Retrieving SummaryDoc documents: 100%|██████████| 1190/1190 [00:01<00:00, 854.30it/s]
  5%|▍         | 1/22 [00:11<03:52, 11.07s/it]
Downloading data for A2B5...
Retrieving SummaryDoc documents: 100%|██████████| 308/308 [00:00<00:00, 1256659.18it/s]
  9%|▉         | 2/22 [00:14<02:15,  6.79s/it]
Downloading data for A2B7...
Retrieving SummaryDoc documents: 100%|██████████| 98/98 [00:00<00:00, 403377.62it/s]
 14%|█▎        | 3/22 [00:17<01:32,  4.84s/it]
Downloading data for A3B4...
Retrieving SummaryDoc documents: 100%|██████████| 616/616 [00:00<00:00, 2350947.46it/s]
 18%|█▊        | 4/22 [00:21<01:24,  4.71s/it]
Downloading data for A3B5...
Retrieving SummaryDoc documents: 100%|██████████| 486/486 [00:00<00:00, 54624.75it/s]
 23%|██▎       | 5/22 [00:27<01:23,  4.93s/it]
Downloading data for A3B7...
Retrieving SummaryDoc documents: 100%|██████████| 132/132 [00:00<00:00, 527786.59it/s]
 27%|██▋       | 6/22 [00:30<01:09,  4.33s/it]
Downloading data for A3B8...
Retrieving SummaryDoc documents: 100%|██████████| 93/93 [00:00<00:00, 302849.59it/s]
 32%|███▏      | 7/22 [00:32<00:54,  3.63s/it]
Downloading data for A4B5...
Retrieving SummaryDoc documents: 100%|██████████| 175/175 [00:00<00:00, 1073104.09it/s]
 36%|███▋      | 8/22 [00:35<00:49,  3.52s/it]
Downloading data for A4B7...
Retrieving SummaryDoc documents: 100%|██████████| 151/151 [00:00<00:00, 642332.56it/s]
 41%|████      | 9/22 [00:39<00:45,  3.48s/it]
Downloading data for A5B6...
Retrieving SummaryDoc documents: 100%|██████████| 136/136 [00:00<00:00, 363559.81it/s]
 45%|████▌     | 10/22 [00:41<00:37,  3.12s/it]
Downloading data for A5B7...
Retrieving SummaryDoc documents: 100%|██████████| 31/31 [00:00<00:00, 70626.52it/s]
 50%|█████     | 11/22 [00:43<00:30,  2.81s/it]
Downloading data for A5B8...
Retrieving SummaryDoc documents: 100%|██████████| 71/71 [00:00<00:00, 300803.62it/s]
 55%|█████▍    | 12/22 [00:45<00:26,  2.60s/it]
Downloading data for A6B7...
Retrieving SummaryDoc documents: 100%|██████████| 43/43 [00:00<00:00, 282688.20it/s]
 59%|█████▉    | 13/22 [00:47<00:22,  2.45s/it]
Downloading data for A7B8...
Retrieving SummaryDoc documents: 100%|██████████| 30/30 [00:00<00:00, 89813.79it/s]
 64%|██████▎   | 14/22 [00:50<00:18,  2.37s/it]
Downloading data for AB...
Retrieving SummaryDoc documents: 100%|██████████| 3634/3634 [00:04<00:00, 895.90it/s]
 68%|██████▊   | 15/22 [01:03<00:39,  5.64s/it]
Downloading data for AB2...
Retrieving SummaryDoc documents: 100%|██████████| 4753/4753 [00:07<00:00, 644.81it/s]
 73%|███████▎  | 16/22 [01:21<00:55,  9.30s/it]
Downloading data for AB3...
Retrieving SummaryDoc documents: 100%|██████████| 5628/5628 [00:05<00:00, 1088.41it/s]
 77%|███████▋  | 17/22 [01:43<01:05, 13.12s/it]
Downloading data for AB4...
Retrieving SummaryDoc documents: 100%|██████████| 686/686 [00:00<00:00, 1829175.17it/s]
 82%|████████▏ | 18/22 [01:48<00:43, 10.78s/it]
Downloading data for AB5...
Retrieving SummaryDoc documents: 100%|██████████| 544/544 [00:00<00:00, 1890390.54it/s]
 86%|████████▋ | 19/22 [01:52<00:26,  8.74s/it]
Downloading data for AB6...
Retrieving SummaryDoc documents: 100%|██████████| 221/221 [00:00<00:00, 795657.67it/s]
 91%|█████████ | 20/22 [01:55<00:14,  7.04s/it]
Downloading data for AB7...
Retrieving SummaryDoc documents: 100%|██████████| 85/85 [00:00<00:00, 303676.18it/s]
 95%|█████████▌| 21/22 [01:57<00:05,  5.54s/it]
Downloading data for AB8...
Retrieving SummaryDoc documents: 100%|██████████| 37/37 [00:00<00:00, 125863.14it/s]
100%|██████████| 22/22 [01:59<00:00,  5.42s/it]

4. Categorise compositions#

Finally, we categorize the compositions into four labels: standard, missing, interesting, and unlikely.

from pathlib import Path
import pandas as pd
mp_data = {p.stem: True for p in Path(save_mp_dir).glob("*.json")}
df_mp = pd.DataFrame.from_dict(mp_data, orient="index", columns=["mp"])
# make category dataframe
df_category = df_smact.join(df_mp, how="left").fillna(False)

# make label for each category
dict_label = {
    (True, True): "standard",
    (True, False): "missing",
    (False, True): "interesting",
    (False, False): "unlikely",
}
df_category["label"] = df_category.apply(lambda x: dict_label[(x["smact_allowed"], x["mp"])], axis=1)

# count number of each label
print(df_category["label"].value_counts())

# save dataframe
df_category.to_pickle("data/binary/df_binary_category.pkl")

# show df_category
df_category.head()
/var/folders/gb/3q75byln3gz8710dqhxnnyr80000gp/T/ipykernel_38537/2875216502.py:4: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
  df_category = df_smact.join(df_mp, how="left").fillna(False)
smact_allowed mp
Cr4C5 True False
Bk3Bi8 False False
Cf6F5 False False
Hf5Pb4 False False
CeHg2 False True
... ... ...
Be4Xe3 False False
NpSb5 False False
Mn4Al False False
Th6Ti7 False False
H7Rn False False

225879 rows × 2 columns

label
unlikely       205910
missing          9789
interesting      6505
standard         3675
Name: count, dtype: int64
smact_allowed mp label
Cr4C5 True False missing
Bk3Bi8 False False unlikely
Cf6F5 False False unlikely
Hf5Pb4 False False unlikely
CeHg2 False True interesting
... ... ... ...
Be4Xe3 False False unlikely
NpSb5 False False unlikely
Mn4Al False False unlikely
Th6Ti7 False False unlikely
H7Rn False False unlikely

225879 rows × 3 columns

label
unlikely       205910
missing          9789
interesting      6505
standard         3675
Name: count, dtype: int64
smact_allowed mp label
Cr4C5 True False missing
Bk3Bi8 False False unlikely
Cf6F5 False False unlikely
Hf5Pb4 False False unlikely
CeHg2 False True interesting

Next steps#

move to crystal_space_visualisation.ipynb to visualize the data and explore the chemical space.