Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Note on performance

In section 3-1, the use of the SOAP descriptor in GPR can be computationally intensive.

On high-performance computing (HPC) or GPU systems, the SOAP-based GPR runs significantly faster.

In this Colab environment, you can switch to a GPU by going to

Runtime → Change runtime type → Hardware accelerator → T4 GPU

try:
    import torch
    print("successfully imported torch")
    print(torch.__version__)
except ImportError:
    !! pip install torch torchvision torchaudio --user --index-url https://download.pytorch.org/whl/cpu
    print("completed installing torch")
successfully imported torch
2.6.0+cu124
try:
    import numpy as np
    print("successfully imported numpy")
    print(np.__version__)
except ImportError:
    !! pip install numpy --user --upgrade
    print("completed installing numpy")
successfully imported numpy
1.26.4
try:
    from matplotlib import pyplot as plt
    print("successfully imported matplotlib")
except ImportError:
    !! pip install matplotlib --user --upgrade
    print("completed installing matplotlib")
successfully imported matplotlib
!! pip install aenet-gpr --user --upgrade
print("completed installing aenet-gpr")

! pip show aenet-gpr
completed installing aenet-gpr
Name: aenet-gpr
Version: 2.6.5
Summary: Atomistic simulation tools based on Gaussian Processes Regression
Home-page: https://github.com/atomisticnet/aenet-gpr
Author: In Won Yeu
Author-email: iy2185@columbia.edu
License: MPL-2.0
Location: /data/home/iy2185/.local/lib/python3.12/site-packages
Requires: ase
Required-by: 
import os, sys, site
sys.path.append(site.USER_SITE)

import aenet_gpr
print(aenet_gpr.__version__)
2.6.5
from IPython.display import Image

try:
    import ase.io
    print("successfully imported ase")
except ImportError:
    !! pip install ase --user --upgrade
    print("completed installing ASE")

    import ase.io
    print("successfully imported ase")
successfully imported ase

1. Prepare training/test data

We are using train/test HSE06 data of Li/EC interface prepared in aenet-gpr/example/3_Li-EC/

import os.path

! rm -rf 3_Li-EC
! mkdir 3_Li-EC

if os.path.isfile("../example/3_Li-EC/train_set_tutorial.zip"):
    ! unzip -oq ../example/3_Li-EC/train_set_tutorial.zip -d ./3_Li-EC/
    print("number of train data:")
    ! find ./3_Li-EC/train_set_tutorial/ -type f | wc -l
else:
    ! wget https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train_set_tutorial.zip
    ! unzip -oq train_set_tutorial.zip -d ./3_Li-EC/
    ! rm train_set_tutorial.zip
    print("number of train data:")
    ! find ./3_Li-EC/train_set_tutorial/ -type f | wc -l
--2025-10-19 17:05:28--  https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train_set_tutorial.zip
Resolving github.com (github.com)... 140.82.112.3
Connecting to github.com (github.com)|140.82.112.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train_set_tutorial.zip [following]
--2025-10-19 17:05:28--  https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train_set_tutorial.zip
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 74855 (73K) [application/zip]
Saving to: ‘train_set_tutorial.zip’

train_set_tutorial. 100%[===================>]  73.10K  --.-KB/s    in 0.001s  

2025-10-19 17:05:28 (52.6 MB/s) - ‘train_set_tutorial.zip’ saved [74855/74855]

number of train data:
30
if os.path.isfile("../example/3_Li-EC/test_set_tutorial.zip"):
    ! unzip -oq ../example/3_Li-EC/test_set_tutorial.zip -d ./3_Li-EC/
    print("number of test data:")
    ! find ./3_Li-EC/test_set_tutorial/ -type f | wc -l
else:
    ! wget https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/test_set_tutorial.zip
    ! unzip -oq test_set_tutorial.zip -d ./3_Li-EC/
    ! rm test_set_tutorial.zip
    print("number of test data:")
    ! find ./3_Li-EC/test_set_tutorial/ -type f | wc -l
--2025-10-19 17:05:28--  https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/test_set_tutorial.zip
Resolving github.com (github.com)... 140.82.112.3
Connecting to github.com (github.com)|140.82.112.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/test_set_tutorial.zip [following]
--2025-10-19 17:05:29--  https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/test_set_tutorial.zip
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 149388 (146K) [application/zip]
Saving to: ‘test_set_tutorial.zip’

test_set_tutorial.z 100%[===================>] 145.89K  --.-KB/s    in 0.003s  

2025-10-19 17:05:29 (41.8 MB/s) - ‘test_set_tutorial.zip’ saved [149388/149388]

number of test data:
60
atoms = ase.io.read("./3_Li-EC/test_set_tutorial/file_0000.xsf")
ase.io.write('Li-EC.png', atoms)
Image("Li-EC.png")
<IPython.core.display.Image object>

2-1. Train–Test with default Cartesian fingerprint

! rm -f train.in

if os.path.isfile("../example/3_Li-EC/train.in"):
    ! cp ../example/3_Li-EC/train.in .
else:
    ! wget https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train.in
--2025-10-19 17:05:30--  https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train.in
Resolving github.com (github.com)... 140.82.112.3
Connecting to github.com (github.com)|140.82.112.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train.in [following]
--2025-10-19 17:05:30--  https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train.in
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 587 [text/plain]
Saving to: ‘train.in’

train.in            100%[===================>]     587  --.-KB/s    in 0s      

2025-10-19 17:05:30 (10.8 MB/s) - ‘train.in’ saved [587/587]

! cat train.in
# File path
Train_file ./3_Li-EC/train_set_tutorial/file_*.xsf
Test_file ./3_Li-EC/test_set_tutorial/file_*.xsf

# Train model save (default: False)
# Train_model_save True

# File format (default: xsf)
File_format xsf

# Descriptor (default: cartesian coordinates)
Descriptor cart

# Kernel parameter
# scale 0.4
# weight 1.0

# Data process (default: batch, 25)
data_process batch
batch_size 25

# Flags for xsf file writing (default: False)
Train_write False
Test_write False
# Additional_write True

# Data augmentation parameter (default: 0.055, 25)
# Disp_length 0.05
# Num_copy 3
! python -m aenet_gpr train.in
======================================================================
aenet-GPR: surrogate GPR for GPR-ANN indirect force training
======================================================================

2025-10-19 17:05:34.

Developed by In Won Yeu

This program performs three main steps:
1. Train: Generates a GPR model using the provided structure, energy, and force data.
2. Test: Uses the generated GPR model to predict values for the test set structures.
3. Augmentation: Performs data augmentation in xsf file format, compatible with aenet-(PyTorch),
supporting a GPR-ANN training in conjunction with aenet-(PyTorch).

Each of these steps is executed once the input file (train.in) contains the keywords:
Train_file [train file path]
Test_file [test file path]
Additional_write [True]


======================================================================
Train
======================================================================

Read reference training data

Time needed for reading data:            0.077833 s
Maximum CPU memory used:             0.411930 GB
Maximum GPU memory used:             0.000000 GB
Energy data size: (30,)  # (N_data, )
Force data size: (30, 46, 3)  # (N_data, N_atom, 3)
----------------------------------------------------------------------
Candidate scales: tensor([0.2000, 0.4000, 0.8000])
Updated scale: 0.800000011920929
Updated weight: 1.1156722220085324
----------------------------------------------------------------------
Model train

Training parameters
{'num_data': 30, 'calculator': {'kerneltype': 'sqexp', 'scale': tensor(0.8000), 'weight': tensor(1.1157, dtype=torch.float64), 'noise': tensor(1.0000e-06, dtype=torch.float64), 'noisefactor': tensor(0.5000, dtype=torch.float64), 'prior': tensor(-16685.0192, dtype=torch.float64)}, 'fix_ind': None, 'pbc': True, 'species': ['Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'C', 'C', 'O', 'H', 'H', 'O', 'H', 'H', 'C', 'O'], 'num_atom': 46}

Time needed for training:            3.383716 s
Maximum CPU memory used:             0.997082 GB
Maximum GPU memory used:             0.000000 GB
----------------------------------------------------------------------
======================================================================
Test
======================================================================
----------------------------------------------------------------------
Model evaluation for test set
Time needed for test evaluation:            4.836310 s
Maximum CPU memory used:             1.618618 GB
Maximum GPU memory used:             0.000000 GB
----------------------------------------------------------------------
GPR energy MAE (eV): 0.1636123145555151
GPR force MAE (eV/Ang): 0.5956040013558996

Saving test target to [energy_test_reference.npy] and [force_test_reference.npy]
Saving GPR prediction to [energy_test_gpr.npy], [force_test_gpr.npy], [unc_e_test_gpr.npy], and [unc_f_test_gpr.npy]


2-2. Visualize the results

energy_test_gpr = np.load("./energy_test_gpr.npy")
energy_test_reference = np.load("./energy_test_reference.npy")

force_test_gpr = np.load("./force_test_gpr.npy")
force_test_reference = np.load("./force_test_reference.npy")

unc_e_test_gpr = np.load("./unc_e_test_gpr.npy")
unc_f_test_gpr = np.load("./unc_f_test_gpr.npy")
diff_per_atom = (energy_test_reference - energy_test_gpr) / 46

print(diff_per_atom.min(), diff_per_atom.max())
-0.007817935342324705 0.012918689117372118
fig = plt.figure(figsize=(7, 6))
ax2 = fig.subplots()

font_x = {'size': 20, 'color': 'black'}
font_y = {'size': 20, 'color': 'black'}
font_tick = {'size': 16, 'color': 'black'}


# The second plot
counts, bins = np.histogram(diff_per_atom, bins=30, range=(-0.005, 0.05))

ax2.hist(bins[:-1], bins, weights=counts, facecolor='dimgray', alpha=1.0, edgecolor='black')

ax2.set_xlabel("DFT $-$ GPR (eV/atom)", fontdict=font_x)
ax2.set_xlim([-0.03, 0.03])
x_labels = [round(label, 2) for label in ax2.get_xticks()]
ax2.set_xticks(x_labels)
ax2.set_xlim([-0.03, 0.03])
ax2.set_xticklabels(x_labels, fontdict=font_tick)

ax2.set_ylabel("Number of structures", fontdict=font_y)
ax2.set_ylim([0, 25])
y_labels = [int(label) for label in ax2.get_yticks()]
ax2.set_yticks(y_labels)
ax2.set_ylim([0, 25])
ax2.set_yticklabels(y_labels, fontdict=font_tick)

[x.set_linewidth(1.5) for x in ax2.spines.values()]
ax2.tick_params(bottom=False, top=True, left=True, right=True)
ax2.tick_params(labelbottom=True, labeltop=False, labelleft=True, labelright=False)
ax2.tick_params(direction='in', length=8, width=1.5)

ax2.grid(True, which='both', linestyle=':', linewidth=0.7, alpha=0.7)

fig.tight_layout()
plt.show()
<Figure size 700x600 with 1 Axes>
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

# --- Force error vs uncertainty ---
force_error = np.abs(force_test_gpr - force_test_reference).reshape(-1)
unc_f = unc_f_test_gpr.reshape(-1)

r_force, _ = pearsonr(force_error, unc_f)

plt.figure(figsize=(6, 5))
plt.scatter(unc_f, force_error, alpha=0.6, edgecolors='k')
plt.xlabel("Force uncertainty (eV/Å)")
plt.ylabel("Force absolute error (eV/Å)")
plt.title(f"Force Error vs Uncertainty\nPearson r = {r_force:.3f}")
plt.grid(True)
# plt.xlim(0.0, 1.0)
# plt.ylim(0, 2.0)
plt.tight_layout()
plt.show()
<Figure size 600x500 with 1 Axes>

3-1. Train–Test–Augment with SOAP fingerprint

! rm -f train_soap.in

if os.path.isfile("../example/3_Li-EC/train_soap.in"):
    ! cp ../example/3_Li-EC/train_soap.in .
else:
    ! wget https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train_soap.in
--2025-10-19 17:07:20--  https://github.com/atomisticnet/aenet-gpr/raw/refs/heads/main/example/3_Li-EC/train_soap.in
Resolving github.com (github.com)... 140.82.112.3
Connecting to github.com (github.com)|140.82.112.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train_soap.in [following]
--2025-10-19 17:07:20--  https://raw.githubusercontent.com/atomisticnet/aenet-gpr/refs/heads/main/example/3_Li-EC/train_soap.in
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.109.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1077 (1.1K) [text/plain]
Saving to: ‘train_soap.in’

train_soap.in       100%[===================>]   1.05K  --.-KB/s    in 0s      

2025-10-19 17:07:20 (18.1 MB/s) - ‘train_soap.in’ saved [1077/1077]

! cat train_soap.in
# File path
Train_file ./3_Li-EC/train_set_tutorial/file_*.xsf
Test_file ./3_Li-EC/test_set_tutorial/file_*.xsf

# File format (default: xsf)
File_format xsf

# Descriptor (default: cartesian coordinates)
Descriptor soap

# Soap parameters
soap_r_cut 5.0
soap_n_max 6
soap_l_max 4
soap_centers [[2.20113706670393, 2.328998192856251, 6.952547732109352], [2.20113706670393, 2.328998192856251, 11.895790642109352], [2.20113706670393, 6.760484232856251, 6.952547732109352], [2.20113706670393, 6.760484232856251, 11.895790642109352], [6.63924050670393, 2.328998192856251, 6.952547732109352], [6.63924050670393, 2.328998192856251, 11.895790642109352], [6.63924050670393, 6.760484232856251, 6.952547732109352], [6.63924050670393, 6.760484232856251, 11.895790642109352]]
soap_n_jobs 32

# Kernel parameter
# scale 0.4
# weight 1.0

# Data process (default: batch, 25)
data_process batch
batch_size 10

# Flags for xsf file writing (default: False)
Train_write False
Test_write False
Additional_write True

# Data augmentation parameter (default: 0.055, 25)
Disp_length 0.05
Num_copy 2
! rm -rf additional_xsf/
! python -m aenet_gpr train_soap.in
======================================================================
aenet-GPR: surrogate GPR for GPR-ANN indirect force training
======================================================================

2025-10-19 17:07:24.

Developed by In Won Yeu

This program performs three main steps:
1. Train: Generates a GPR model using the provided structure, energy, and force data.
2. Test: Uses the generated GPR model to predict values for the test set structures.
3. Augmentation: Performs data augmentation in xsf file format, compatible with aenet-(PyTorch),
supporting a GPR-ANN training in conjunction with aenet-(PyTorch).

Each of these steps is executed once the input file (train.in) contains the keywords:
Train_file [train file path]
Test_file [test file path]
Additional_write [True]


======================================================================
Train
======================================================================

Read reference training data

Time needed for reading data:            0.076803 s
Maximum CPU memory used:             0.411224 GB
Maximum GPU memory used:             0.000000 GB
Energy data size: (30,)  # (N_data, )
Force data size: (30, 46, 3)  # (N_data, N_atom, 3)
----------------------------------------------------------------------
Candidate scales: tensor([0.2000, 0.4000, 0.8000])
Updated scale: 0.800000011920929
Updated weight: 0.7879021148110678
----------------------------------------------------------------------
Model train

Training parameters
{'num_data': 30, 'calculator': {'kerneltype': 'sqexp', 'scale': tensor(0.8000), 'weight': tensor(0.7879, dtype=torch.float64), 'noise': tensor(1.0000e-06, dtype=torch.float64), 'noisefactor': tensor(0.5000, dtype=torch.float64), 'prior': tensor(-16684.1882, dtype=torch.float64)}, 'fix_ind': None, 'pbc': True, 'species': ['Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'C', 'C', 'O', 'H', 'H', 'O', 'H', 'H', 'C', 'O'], 'num_atom': 46}

Time needed for training:          135.652588 s
Maximum CPU memory used:             3.810555 GB
Maximum GPU memory used:             0.000000 GB
----------------------------------------------------------------------
======================================================================
Test
======================================================================
----------------------------------------------------------------------
Model evaluation for test set
Time needed for test evaluation:           89.152006 s
Maximum CPU memory used:             3.903370 GB
Maximum GPU memory used:             0.000000 GB
----------------------------------------------------------------------
GPR energy MAE (eV): 0.1501698888436901
GPR force MAE (eV/Ang): 0.3222749835025399

Saving test target to [energy_test_reference.npy] and [force_test_reference.npy]
Saving GPR prediction to [energy_test_gpr.npy], [force_test_gpr.npy], [unc_e_test_gpr.npy], and [unc_f_test_gpr.npy]


======================================================================
Augmentation
======================================================================
----------------------------------------------------------------------
Additional structure generation

Time needed for additional generation:            0.012513 s
Maximum CPU memory used:             3.903370 GB
Maximum GPU memory used:             0.000000 GB
Displacement length (Ang): 0.05
Multiple: 2
N_additional = N_reference (30) * Multiple (2) = 60
----------------------------------------------------------------------
----------------------------------------------------------------------
Model evaluation for additional set

Test parameters
{'num_data': 60, 'calculator': {'kerneltype': 'sqexp', 'scale': tensor(0.8000), 'weight': tensor(0.7879, dtype=torch.float64), 'noise': tensor(1.0000e-06, dtype=torch.float64), 'noisefactor': tensor(0.5000, dtype=torch.float64), 'prior': tensor(-16684.1882, dtype=torch.float64)}, 'fix_ind': None, 'pbc': True, 'species': ['Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'Li', 'C', 'C', 'O', 'H', 'H', 'O', 'H', 'H', 'C', 'O'], 'num_atom': 46}

Time needed for additional evaluation:           88.011260 s
Maximum CPU memory used:             3.940384 GB
Maximum GPU memory used:             0.000000 GB
----------------------------------------------------------------------

Saving GPR prediction to [energy_additional_gpr.npy], [force_additional_gpr.npy], [unc_e_additional_gpr.npy], and [unc_f_additional_gpr.npy]
======================================================================
Writing additional xsf files to additional_xsf

Time needed for writing xsf files:            0.460515 s
Maximum CPU memory used:             3.940384 GB
Maximum GPU memory used:             0.000000 GB
======================================================================

3-2. Visualize the test results

energy_test_gpr = np.load("./energy_test_gpr.npy")
energy_test_reference = np.load("./energy_test_reference.npy")

force_test_gpr = np.load("./force_test_gpr.npy")
force_test_reference = np.load("./force_test_reference.npy")

unc_e_test_gpr = np.load("./unc_e_test_gpr.npy")
unc_f_test_gpr = np.load("./unc_f_test_gpr.npy")
diff_per_atom = (energy_test_reference - energy_test_gpr) / 46

print(diff_per_atom.min(), diff_per_atom.max())
-0.0027989477913133273 0.012551811322776737
fig = plt.figure(figsize=(7, 6))
ax2 = fig.subplots()

font_x = {'size': 20, 'color': 'black'}
font_y = {'size': 20, 'color': 'black'}
font_tick = {'size': 16, 'color': 'black'}


# The second plot
counts, bins = np.histogram(diff_per_atom, bins=30, range=(-0.005, 0.05))

ax2.hist(bins[:-1], bins, weights=counts, facecolor='dimgray', alpha=1.0, edgecolor='black')

ax2.set_xlabel("DFT $-$ GPR (eV/atom)", fontdict=font_x)
ax2.set_xlim([-0.03, 0.03])
x_labels = [round(label, 2) for label in ax2.get_xticks()]
ax2.set_xticks(x_labels)
ax2.set_xlim([-0.03, 0.03])
ax2.set_xticklabels(x_labels, fontdict=font_tick)

ax2.set_ylabel("Number of structures", fontdict=font_y)
ax2.set_ylim([0, 25])
y_labels = [int(label) for label in ax2.get_yticks()]
ax2.set_yticks(y_labels)
ax2.set_ylim([0, 25])
ax2.set_yticklabels(y_labels, fontdict=font_tick)

[x.set_linewidth(1.5) for x in ax2.spines.values()]
ax2.tick_params(bottom=False, top=True, left=True, right=True)
ax2.tick_params(labelbottom=True, labeltop=False, labelleft=True, labelright=False)
ax2.tick_params(direction='in', length=8, width=1.5)

ax2.grid(True, which='both', linestyle=':', linewidth=0.7, alpha=0.7)

fig.tight_layout()
plt.show()
<Figure size 700x600 with 1 Axes>
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

# --- Force error vs uncertainty ---
force_error = np.abs(force_test_gpr - force_test_reference).reshape(-1)
unc_f = unc_f_test_gpr.reshape(-1)

r_force, _ = pearsonr(force_error, unc_f)

plt.figure(figsize=(6, 5))
plt.scatter(unc_f, force_error, alpha=0.6, edgecolors='k')
plt.xlabel("Force uncertainty (eV/Å)")
plt.ylabel("Force absolute error (eV/Å)")
plt.title(f"Force Error vs Uncertainty\nPearson r = {r_force:.3f}")
plt.grid(True)
# plt.xlim(0.0, 1.0)
# plt.ylim(0, 2.0)
plt.tight_layout()
plt.show()
<Figure size 600x500 with 1 Axes>