Notebook 6 - Fitting disorder linewidth parameters

This short notebooks shows an example of how to fit L and R in a given material

[1]:
import sys

import h5py
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from ase.io import read
from scipy.interpolate import interp1d

from tqdm import tqdm

from smooth_disorder.structural import obtain_density, THzToCm, THz, Angstrom
from smooth_disorder.vis.interactive import *

from smooth_disorder.disorder_linewidth import lorentzian_numpy, lorentzian_torch
from smooth_disorder.disorder_linewidth import prepare_fitting_inputs
from smooth_disorder.disorder_linewidth import evaluate_linewidth_and_model_prediction
from smooth_disorder.disorder_linewidth import PDCModel

import torch
[2]:
CRYSTAL_POSCAR    = "./1_graphite/POSCAR"
DISORDERED_POSCAR = "./2_irg_t2/irg_t2_14009.vasp"

WORK_DIR = "./dl_tutorial"

CRYSTAL_VEL_SAVE     = f"{WORK_DIR}/crystal_vdos_group_vel"
DISORDERED_VDOS_SAVE = f"{WORK_DIR}/disordered_vdos"
SHIFTED_SAVE         = f"{WORK_DIR}/reduced_density_crystal_vdos_group_vel"
[3]:
# read the input data + setup torch arrays

(density_crystal,
density_disordered,
freq_disordered,
vdos_disordered,
interp_shifted_freq_crystal,
interp_shifted_vdos_crystal,
interp_shifted_speed_crystal) = prepare_fitting_inputs(
    CRYSTAL_POSCAR,
    DISORDERED_POSCAR,
    DISORDERED_VDOS_SAVE,
    SHIFTED_SAVE,
)

# X and Y are the normalised crystal and disordered VDOS used in the loss
X = torch.from_numpy(interp_shifted_vdos_crystal / density_crystal)
Y = torch.from_numpy(vdos_disordered / density_disordered)
[4]:
# Initial parameter values — intentionally away from the optimum
L0, R0 = 3.3, 5.54

# instantiate the model for fitting the linewidths and setup LBFGS search
model = PDCModel(
    L0, R0,
    density_crystal, density_disordered,
    freq_disordered,
    interp_shifted_freq_crystal,
    interp_shifted_vdos_crystal,
    interp_shifted_speed_crystal,
)

optim = torch.optim.LBFGS(
    model.parameters(),
    lr=1.0,
    max_iter=50,
    line_search_fn="strong_wolfe",
)

loss_fn = torch.nn.MSELoss()

losses, model_parameters_history = {}, {}
[5]:
# MAIN ITERATION LOOP

def closure():
    optim.zero_grad()
    preds = model(X)
    loss = loss_fn(preds, Y)
    loss.backward()
    print(loss, model.model_params.detach().cpu().numpy().copy())
    return loss

loss = optim.step(closure)
tensor(0.0057, dtype=torch.float64, grad_fn=<MseLossBackward0>) [3.3  5.54]
tensor(0.0057, dtype=torch.float64, grad_fn=<MseLossBackward0>) [3.29906297 5.54084807]
tensor(0.0057, dtype=torch.float64, grad_fn=<MseLossBackward0>) [3.29062973 5.54848067]
tensor(0.0055, dtype=torch.float64, grad_fn=<MseLossBackward0>) [3.20629726 5.62480669]
tensor(0.0043, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.36297261 6.38806689]
tensor(0.5871, dtype=torch.float64, grad_fn=<MseLossBackward0>) [-2.72415394  8.06097748]
tensor(0.0043, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.35731212 6.38992835]
tensor(0.0039, dtype=torch.float64, grad_fn=<MseLossBackward0>) [1.84916552 6.55703326]
tensor(0.0039, dtype=torch.float64, grad_fn=<MseLossBackward0>) [1.50392337 6.85466844]
tensor(0.0038, dtype=torch.float64, grad_fn=<MseLossBackward0>) [1.65907103 6.72091478]
tensor(0.0038, dtype=torch.float64, grad_fn=<MseLossBackward0>) [1.70118642 6.97404115]
tensor(0.0035, dtype=torch.float64, grad_fn=<MseLossBackward0>) [1.94422296 8.89727369]
tensor(0.0035, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.01215958 9.34020213]
tensor(0.0035, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.03352871 9.45896675]
tensor(0.0035, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.03621991 9.46528819]
tensor(0.0035, dtype=torch.float64, grad_fn=<MseLossBackward0>) [2.03709562 9.46466436]

Hence final estimated values for L and R are L = 20.37 A and R = 9.46e-6 THz cm nm^3