# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
"""
pyiron_atomistics based implementation of the coexistence method. Currently this functionality is primarly used as part
of the melting point simulation protocol which is available at:
https://github.com/pyiron/pyiron_meltingpoint
"""
import json
import operator
import os
import random
import matplotlib.pylab as plt
import numpy as np
from sklearn.neighbors import KernelDensity
__author__ = "Lifang Zhu, Jan Janssen"
__copyright__ = (
"Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH "
"- Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "development"
__date__ = "Apr 24, 2020"
[docs]
def freeze_one_half(basis):
"""
Split the structure into two parts along the z-axis and then freeze the position of the atoms
of the upper part (z>0.5) by setting selective dynamics to False.
Args:
basis (pyiron_atomistics.structure.atoms.Atoms): Atomistic structure object
Returns:
pyiron_atomistics.structure.atoms.Atoms: Atomistic structure object with half of the atoms fixed
"""
basis.add_tag(selective_dynamics=[True, True, True])
_, _, z = basis.scaled_pos_xyz()
for selector, ind in zip(z < 0.5, range(len(basis))):
if selector:
basis.selective_dynamics[ind] = [True, True, True]
else:
basis.selective_dynamics[ind] = [False, False, False]
return basis
[docs]
def remove_selective_dynamics(basis):
"""
If the selective dyanmics tag is set, allow all atoms to move by setting selective dynamics to True
Args:
basis (pyiron_atomistics.structure.atoms.Atoms): Atomistic structure object
Returns:
Atoms: Atomistic structure object with selective dynamics set to True
"""
if "selective_dynamics" in basis.arrays.keys():
for ind in range(len(basis)):
basis.selective_dynamics[ind] = [True, True, True]
return basis
[docs]
def set_server(job, project_parameter):
"""
Set the potential, queue and cpu_cores defined in the project_parameter dictionary to the job object.
Args:
job (GenericJob): Job object
project_parameter (dict): Dictionary with the keys potential and cpu_cores and optionally queue
Returns:
GenericJob: Updated job object
"""
job.potential = project_parameter["potential"]
if "queue" in project_parameter.keys():
job.server.queue = project_parameter["queue"]
job.server.cores = project_parameter["cpu_cores"]
return job
[docs]
def create_job_template(job_name, structure, project_parameter):
"""
Create a job template using the project_parameter dictionary. The dictionary has to contain the following keys:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
job_name (str): Name of the job object
structure (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
project_parameter (dict): Dictionary with the project parameters
Returns:
GenericJob: New job object
"""
pr = project_parameter["project"]
job = pr.create_job(project_parameter["job_type"], job_name)
job.structure = structure
return set_server(job=job, project_parameter=project_parameter)
[docs]
def fix_iso(job):
"""
Add couple xyz to the fix_ensemble inside LAMMPS
Args:
job (LAMMPS): Lammps job object
Returns:
LAMMPS: Return updated job object
"""
job.input.control["fix___ensemble"] = (
job.input.control["fix___ensemble"] + " couple xyz"
)
return job
[docs]
def fix_z_dir(job):
"""
Rather than fixing all directions only fix the z-direction during an NPT simulation
Args:
job (LAMMPS): Lammps job object
Returns:
LAMMPS: Return updated job object
"""
job.input.control["fix___ensemble"] = job.input.control["fix___ensemble"].replace(
"x 0.0 0.0 1.0 y 0.0 0.0 1.0 z 0.0 0.0 1.0", "z 0.0 0.0 1.0"
)
return job
[docs]
def half_velocity(job, temperature):
"""
Rather than setting twice the kinetic energy at the beginning of a molecular dynamics simulation reduce the
velocity to half the initial velocity. This is required to continue MD claculation.
Args:
job (LAMMPS): Lammps job object
temperature (float): Temperature of the molecular dynamics calculation in K
Returns:
LAMMPS: Return updated job object
"""
job.input.control["velocity"] = job.input.control["velocity"].replace(
str(temperature * 2), str(temperature)
)
return job
[docs]
def minimize_pos(structure, project_parameter, max_iter=1000):
"""
Minimize the positions in a given structure using the job type defined in the project_parameters, which
contains the following keys:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
structure (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
project_parameter (dict): Dictionary wtih the project parameters
max_iter (int): Maximum number of steps during minimization
Returns:
job object used to execute the calculation
"""
ham_minimize_pos = create_job_template(
job_name="minimize_pos",
structure=structure,
project_parameter=project_parameter,
)
ham_minimize_pos.calc_minimize(
max_iter=max_iter, e_tol=1.0e-9, f_tol=1.0e-8, n_print=max_iter
)
ham_minimize_pos.run()
ham_minimize_pos.project.wait_for_job(
ham_minimize_pos, interval_in_s=100, max_iterations=100000
)
return ham_minimize_pos
[docs]
def minimize_vol(structure, project_parameter, max_iter=1000):
"""
Minimize the volume for a given structure using the job type defined in the project_parameters, which
contains the following keys:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
structure (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
project_parameter (dict): Dictionary with the project parameters
max_iter (int): Maximum number of steps during minimization
Returns:
job object used to execute the calculation
"""
ham_minimize_vol = create_job_template(
job_name="minimize_vol",
structure=structure,
project_parameter=project_parameter,
)
ham_minimize_vol.calc_minimize(
max_iter=max_iter, e_tol=1.0e-9, f_tol=1.0e-8, pressure=0.0, n_print=max_iter
)
ham_minimize_vol.input.control["fix___ensemble"] += " vmax 0.001"
ham_minimize_vol.run()
ham_minimize_vol.project.wait_for_job(
ham_minimize_vol, interval_in_s=100, max_iterations=100000
)
return ham_minimize_vol
[docs]
def next_calc(structure, temperature, project_parameter, run_time_steps=10000):
"""
Calculate NPT ensemble at a given temperature using the job defined in the project parameters:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
structure (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
temperature (float): Temperature of the Molecular dynamics calculation
project_parameter (dict): Dictionary with the project parameters
run_time_steps (int): Number of Molecular dynamics steps
Returns:
Final Atomistic Structure object
"""
ham_temp = create_job_template(
job_name="temp_heating_" + str(temperature).replace(".", "_"),
structure=structure,
project_parameter=project_parameter,
)
ham_temp.calc_md(
temperature=temperature,
temperature_damping_timescale=100.0,
pressure=0.0,
pressure_damping_timescale=1000.0,
n_print=run_time_steps,
n_ionic_steps=run_time_steps,
seed=project_parameter["seed"],
)
ham_temp = fix_iso(job=ham_temp)
ham_temp = half_velocity(job=ham_temp, temperature=temperature)
ham_temp.run()
ham_temp.project.wait_for_job(ham_temp, interval_in_s=100, max_iterations=100000)
return ham_temp.get_structure()
[docs]
def npt_solid(temperature, basis, project_parameter, timestep=1.0):
"""
Calculate NPT ensemble at a given temperature using the job defined in the project parameters:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
temperature (float): Temperature of the Molecular dynamics calculation
basis (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
project_parameter (dict): Dictionary with the project parameters
timestep (float): Molecular dynamics time step
Returns:
job object used to execute the calculation
"""
ham_npt_solid = create_job_template(
job_name="ham_npt_solid_" + str(temperature).replace(".", "_"),
structure=basis,
project_parameter=project_parameter,
)
ham_npt_solid.calc_md(
temperature=temperature,
temperature_damping_timescale=100.0,
time_step=timestep,
pressure=0.0,
pressure_damping_timescale=1000.0,
n_print=project_parameter["run_time_steps"],
n_ionic_steps=project_parameter["run_time_steps"],
seed=project_parameter["seed"],
)
ham_npt_solid = half_velocity(job=ham_npt_solid, temperature=temperature)
ham_npt_solid = fix_iso(job=ham_npt_solid)
ham_npt_solid.run()
ham_npt_solid.project.wait_for_job(
ham_npt_solid, interval_in_s=100, max_iterations=100000
)
return ham_npt_solid
[docs]
def setup_liquid_job(job_name, basis, temperature, project_parameter, timestep=1.0):
"""
Calculate NPT ensemble at a given temperature while freezing the position of the atoms
of the upper part (z>0.5) amd the using the job defined in the project parameters:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
job_name (str): Job name for the liquid calculation
basis (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
temperature (float): Temperature of the Molecular dynamics calculation
project_parameter (dict): Dictionary with the project parameters
timestep (float): Molecular dynamics time step
Returns:
job object used to execute the calculation
"""
ham_npt_liquid_high = create_job_template(
job_name=job_name,
structure=freeze_one_half(basis),
project_parameter=project_parameter,
)
ham_npt_liquid_high.calc_md(
temperature=temperature,
temperature_damping_timescale=100.0,
time_step=timestep,
pressure=[0.0, 0.0, 0.0],
pressure_damping_timescale=1000.0,
n_print=project_parameter["run_time_steps"],
n_ionic_steps=project_parameter["run_time_steps"],
seed=project_parameter["seed"],
)
ham_npt_liquid_high = half_velocity(
job=ham_npt_liquid_high, temperature=temperature
)
ham_npt_liquid_high = fix_z_dir(job=ham_npt_liquid_high)
ham_npt_liquid_high.run()
ham_npt_liquid_high.project.wait_for_job(
ham_npt_liquid_high, interval_in_s=100, max_iterations=100000
)
return ham_npt_liquid_high
[docs]
def npt_liquid(
temperature_solid, temperature_liquid, basis, project_parameter, timestep=1.0
):
"""
Calculate NPT ensemble at a given temperature while initally freezing the position of the atoms
of the upper part (z>0.5) and afterwards calculating the full sample at a lower temperature.
These steps are used to construct the solid liquid interface as part of the coexistence approach.
For the calculations the job object is defined in the project parameters:
- job_type: Type of Simulation code to be used
- project: Project object used to create the job
- potential: Interatomic Potential
- queue (optional): HPC Job queue to be used
Args:
temperature_solid (flaot): Temperature to simulate the whole structure
temperature_liquid (float): Temperature to simulate the upper half of the structure
basis (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
project_parameter (dict): Dictionary with the project parameters
timestep (float): Molecular dynamics time step
Returns:
job object used to execute the calculation
"""
ham_npt_liquid_high = setup_liquid_job(
job_name="ham_npt_liquid_high_" + str(temperature_liquid).replace(".", "_"),
basis=basis,
temperature=temperature_liquid,
project_parameter=project_parameter,
timestep=timestep,
)
ham_npt_liquid_low = setup_liquid_job(
job_name="ham_npt_liquid_low_" + str(temperature_solid).replace(".", "_"),
basis=ham_npt_liquid_high.get_structure(iteration_step=-1),
temperature=temperature_solid,
project_parameter=project_parameter,
timestep=timestep,
)
return ham_npt_liquid_low
[docs]
def check_diamond(structure):
"""
Utility function to check if the structure is fcc, bcc, hcp or diamond
Args:
structure (pyiron_atomistics.structure.atoms.Atoms): Atomistic Structure object to check
Returns:
bool: true if diamond else false
"""
cna_dict = structure.analyse.pyscal_cna_adaptive(
mode="total", ovito_compatibility=True
)
dia_dict = structure.analyse.pyscal_diamond_structure(
mode="total", ovito_compatibility=True
)
return (
cna_dict["CommonNeighborAnalysis.counts.OTHER"]
> dia_dict["IdentifyDiamond.counts.OTHER"]
)
[docs]
def analyse_structure(structure, mode="total", diamond=False):
"""
Use either common neighbor analysis or the diamond structure detector
Args:
structure (pyiron_atomistics.structure.atoms.Atoms): The structure to analyze.
mode ("total"/"numeric"/"str"): Controls the style and level
of detail of the output.
- total : return number of atoms belonging to each structure
- numeric : return a per atom list of numbers- 0 for unknown,
1 fcc, 2 hcp, 3 bcc and 4 icosa
- str : return a per atom string of sructures
diamond (bool): Flag to either use the diamond structure detector or
the common neighbor analysis.
Returns:
(depends on `mode`)
"""
if not diamond:
return structure.analyse.pyscal_cna_adaptive(
mode=mode, ovito_compatibility=True
)
else:
return structure.analyse.pyscal_diamond_structure(
mode=mode, ovito_compatibility=True
)
[docs]
def next_step_funct(
number_of_atoms,
key_max,
structure_left,
structure_right,
temperature_left,
temperature_right,
distribution_initial_half,
structure_after_minimization,
run_time_steps,
project_parameter,
):
"""
Args:
number_of_atoms:
key_max:
structure_left:
structure_right:
temperature_left:
temperature_right:
distribution_initial_half:
structure_after_minimization:
run_time_steps:
project_parameter:
Returns:
"""
structure_left_dict = analyse_structure(
structure=structure_left,
mode="total",
diamond=project_parameter["crystalstructure"].lower() == "diamond",
)
structure_right_dict = analyse_structure(
structure=structure_right,
mode="total",
diamond=project_parameter["crystalstructure"].lower() == "diamond",
)
temperature_diff = temperature_right - temperature_left
if (
structure_left_dict[key_max] / number_of_atoms > distribution_initial_half
and structure_right_dict[key_max] / number_of_atoms > distribution_initial_half
):
structure_left = structure_right.copy()
temperature_left = temperature_right
temperature_right += temperature_diff
structure_right = next_calc(
structure=structure_after_minimization,
temperature=temperature_right,
project_parameter=project_parameter,
run_time_steps=run_time_steps,
)
elif (
structure_left_dict[key_max] / number_of_atoms
> distribution_initial_half
> structure_right_dict[key_max] / number_of_atoms
):
temperature_diff /= 2
temperature_left += temperature_diff
structure_left = next_calc(
structure=structure_after_minimization,
temperature=temperature_left,
project_parameter=project_parameter,
run_time_steps=run_time_steps,
)
elif (
structure_left_dict[key_max] / number_of_atoms < distribution_initial_half
and structure_right_dict[key_max] / number_of_atoms < distribution_initial_half
):
temperature_diff /= 2
temperature_right = temperature_left
temperature_left -= temperature_diff
structure_right = structure_left.copy()
structure_left = next_calc(
structure=structure_after_minimization,
temperature=temperature_left,
project_parameter=project_parameter,
run_time_steps=run_time_steps,
)
else:
raise ValueError("We should never reach this point!")
return structure_left, structure_right, temperature_left, temperature_right
[docs]
def round_temperature_next(temperature_next):
"""
Round temperature to the last two dicits
Args:
temperature_next (float): Temperature
Returns:
float: rounded temperature
"""
return np.round(temperature_next, 2)
[docs]
def strain_circle(
basis_relative,
temperature_next,
nve_run_time_steps,
project_parameter,
timestep=1.0,
strain_result_lst=None,
pressure_result_lst=None,
center=None,
fit_range=0.02,
):
"""
Args:
basis_relative:
temperature_next:
nve_run_time_steps:
project_parameter:
timestep:
strain_result_lst:
pressure_result_lst:
center:
fit_range:
Returns:
"""
strain_lst, pressure_lst, temperature_lst, pressure_std_lst, temperature_std_lst = (
[],
[],
[],
[],
[],
)
ovito_dict_lst, ham_nvt_lst, ham_nve_lst = [], [], []
strain_value_lst = get_strain_lst(
fit_range=fit_range,
points=project_parameter["points"],
strain_result_lst=strain_result_lst,
pressure_result_lst=pressure_result_lst,
center=center,
)
temperature_next = round_temperature_next(temperature_next)
for strain in strain_value_lst:
job_name = get_nve_job_name(
temperature_next=temperature_next,
strain=strain,
steps_lst=project_parameter["nve_run_time_steps_lst"],
nve_run_time_steps=nve_run_time_steps,
)
ham_nve = project_parameter["project"].load(job_name)
if ham_nve is None:
basis_strain = basis_relative.copy()
cell = basis_strain.cell.copy()
cell[2, 2] *= strain
basis_strain.set_cell(cell=cell, scale_atoms=True)
ham_nvt = create_job_template(
job_name=job_name.replace("nve", "nvt"),
structure=basis_strain,
project_parameter=project_parameter,
)
ham_nvt.calc_md(
temperature=temperature_next,
time_step=timestep,
temperature_damping_timescale=100.0,
n_print=project_parameter["nvt_run_time_steps"],
n_ionic_steps=project_parameter["nvt_run_time_steps"],
seed=project_parameter["seed"],
)
ham_nvt.input.control["fix___ensemble"] += " drag 1"
ham_nvt = half_velocity(job=ham_nvt, temperature=temperature_next)
ham_nvt.write_restart_file()
ham_nvt.run()
ham_nvt_lst.append(ham_nvt)
for ham_nvt in ham_nvt_lst:
ham_nvt.project.wait_for_job(ham_nvt, interval_in_s=100, max_iterations=100000)
ham_nve = ham_nvt.restart()
ham_nve.job_name = ham_nvt.job_name.replace("nvt", "nve")
ham_nve.calc_md(
n_ionic_steps=nve_run_time_steps,
time_step=timestep,
n_print=nve_run_time_steps / 100,
seed=project_parameter["seed"],
)
ham_nve = set_server(job=ham_nve, project_parameter=project_parameter)
ham_nve.input.control["dump___1"] = ham_nve.input.control["dump___1"].replace(
"${dumptime}", str(nve_run_time_steps)
)
ham_nve.run()
ham_nve_lst.append(ham_nve)
for ham_nve in ham_nve_lst:
ham_nve.project.wait_for_job(ham_nve, interval_in_s=100, max_iterations=100000)
for strain in strain_value_lst:
job_name = get_nve_job_name(
temperature_next=temperature_next,
strain=strain,
steps_lst=project_parameter["nve_run_time_steps_lst"],
nve_run_time_steps=nve_run_time_steps,
)
ham_nve = project_parameter["project"].load(job_name)
press, temperature, press_std, temperature_std, ovito_dict = [
np.mean(get_press(ham=ham_nve, step=-20)),
np.mean(ham_nve["output/generic/temperature"][-20:]),
np.std(get_press(ham=ham_nve, step=-20)),
np.std(ham_nve["output/generic/temperature"][-20:]),
analyse_structure(
structure=ham_nve.get_structure(iteration_step=-1),
mode="total",
diamond=project_parameter["crystalstructure"].lower() == "diamond",
),
]
strain_lst.append(strain)
pressure_lst.append(press)
temperature_lst.append(temperature)
pressure_std_lst.append(press_std)
temperature_std_lst.append(temperature_std)
ovito_dict_lst.append(ovito_dict)
return (
strain_lst,
pressure_lst,
temperature_lst,
pressure_std_lst,
temperature_std_lst,
ovito_dict_lst,
)
[docs]
def analyse_minimized_structure(ham):
"""
Args:
ham (GenericJob):
Returns:
"""
final_structure = ham.get_structure(iteration_step=-1)
diamond_flag = check_diamond(structure=final_structure)
final_structure_dict = analyse_structure(
structure=final_structure, mode="total", diamond=diamond_flag
)
key_max = max(final_structure_dict.items(), key=operator.itemgetter(1))[0]
number_of_atoms = len(final_structure)
distribution_initial = final_structure_dict[key_max] / number_of_atoms
distribution_initial_half = distribution_initial / 2
return (
final_structure,
key_max,
number_of_atoms,
distribution_initial_half,
final_structure_dict,
)
[docs]
def get_press(ham, step=20):
"""
Args:
ham:
step:
Returns:
"""
return np.mean(ham["output/generic/pressures"][step:, :, :].diagonal(0, 2), axis=1)
[docs]
def get_center_point(strain_result_lst=None, pressure_result_lst=None, center=None):
"""
Args:
strain_result_lst:
pressure_result_lst:
center:
Returns:
"""
if (
strain_result_lst is not None
and len(strain_result_lst) != 0
and pressure_result_lst is not None
and len(pressure_result_lst) != 0
):
center_point = np.round(
np.roots(np.polyfit(strain_result_lst, pressure_result_lst, 1))[0], 2
)
elif center is not None:
center_point = center
else:
center_point = 1.0
return center_point
[docs]
def get_strain_lst(
fit_range=0.02,
points=21,
strain_result_lst=None,
pressure_result_lst=None,
center=None,
):
"""
Args:
fit_range:
points:
strain_result_lst:
pressure_result_lst:
center:
Returns:
"""
center_point = get_center_point(
strain_result_lst=strain_result_lst,
pressure_result_lst=pressure_result_lst,
center=center,
)
return [
np.round(s, 3)
for s in np.linspace(center_point - fit_range, center_point + fit_range, points)
]
[docs]
def get_nve_job_name(temperature_next, strain, steps_lst, nve_run_time_steps):
"""
Args:
temperature_next:
strain:
steps_lst:
nve_run_time_steps:
Returns:
"""
temperature_next = round_temperature_next(temperature_next)
temp_str = str(temperature_next).replace(".", "_")
strain_str = str(strain).replace(".", "_")
steps_str = str(steps_lst.index(nve_run_time_steps))
return "ham_nve_" + strain_str + "_" + temp_str + "_" + steps_str
[docs]
def plot_solid_liquid_ratio(
temperature_next, strain_lst, nve_run_time_steps, project_parameter, debug_plot=True
):
"""
Args:
temperature_next:
strain_lst:
nve_run_time_steps:
project_parameter:
debug_plot:
Returns:
"""
cna_str = project_parameter["crystalstructure"].upper()
ratio_lst = []
for strain in strain_lst:
job_name = get_nve_job_name(
temperature_next=temperature_next,
strain=strain,
steps_lst=project_parameter["nve_run_time_steps_lst"],
nve_run_time_steps=nve_run_time_steps,
)
ham_nve = project_parameter["project"].load(job_name)
struct = ham_nve.get_structure().center_coordinates_in_unit_cell()
cna = analyse_structure(
structure=struct,
mode="str",
diamond=project_parameter["crystalstructure"].lower() == "diamond",
)
if not project_parameter["crystalstructure"].lower() == "diamond":
bcc_count = sum(cna == "BCC")
fcc_count = sum(cna == "FCC")
hcp_count = sum(cna == "HCP")
cond = (
(cna_str == "BCC" and bcc_count > fcc_count and bcc_count > hcp_count)
or (
cna_str == "FCC" and fcc_count > bcc_count and fcc_count > hcp_count
)
or (
cna_str == "HCP" and hcp_count > bcc_count and hcp_count > fcc_count
)
)
else:
cna_str = "Cubic diamond"
cond = sum(cna == cna_str) > 0.05 * len(struct)
if cond:
# plt.figure(figsize=(16,12))
bandwidth = (struct.get_volume() / len(struct)) ** (1.0 / 3.0)
kde = KernelDensity(kernel="gaussian", bandwidth=bandwidth).fit(
struct.positions[:, 2][cna == cna_str].reshape(-1, 1)
)
z_range = np.linspace(
struct.positions[:, 2].min(), struct.positions[:, 2].max(), 1000
)
sample = kde.score_samples(z_range.reshape(-1, 1))
gaussian_funct = np.exp(sample) / np.exp(sample).max()
z_range_above_limit = z_range[np.where(gaussian_funct > 0.1)]
z_range_below_limit = z_range[np.where(gaussian_funct < 0.1)]
if len(z_range_above_limit) != 0:
ratio_above = (
np.max(z_range_above_limit) - np.min(z_range_above_limit)
) / (np.max(z_range) - np.min(z_range))
else:
ratio_above = 1.0
if len(z_range_below_limit) != 0:
ratio_below = 1 - (
np.max(z_range_below_limit) - np.min(z_range_below_limit)
) / (np.max(z_range) - np.min(z_range))
else:
ratio_below = 0.0
if ratio_below == 0.0:
ratio = ratio_above
elif ratio_above == 1.0:
ratio = ratio_below
else:
ratio = np.min([ratio_below, ratio_above])
ratio_lst.append(ratio)
else:
z_range = None
gaussian_funct = None
z_range_above_limit = None
ratio = None
ratio_lst.append(0.0)
if debug_plot:
plt.title("strain: " + str(strain))
plt.xlabel("position z")
plt.ylabel("position x")
plt.plot(struct.positions[:, 2], struct.positions[:, 0], "o", label="all")
if not project_parameter["crystalstructure"].lower() == "diamond":
plt.plot(
struct.positions[:, 2][cna == "BCC"],
struct.positions[:, 0][cna == "BCC"],
"x",
label="BCC",
)
plt.plot(
struct.positions[:, 2][cna == "FCC"],
struct.positions[:, 0][cna == "FCC"],
"x",
label="FCC",
)
plt.plot(
struct.positions[:, 2][cna == "HCP"],
struct.positions[:, 0][cna == "HCP"],
"x",
label="HCP",
)
else:
plt.plot(
struct.positions[:, 2][cna == "Cubic diamond"],
struct.positions[:, 0][cna == "Cubic diamond"],
"x",
label="Cubic diamond",
)
plt.plot(
struct.positions[:, 2][cna == "Cubic diamond (1st neighbor)"],
struct.positions[:, 0][cna == "Cubic diamond (1st neighbor)"],
"x",
label="Cubic diamond (1st neighbor)",
)
plt.plot(
struct.positions[:, 2][cna == "Cubic diamond (2nd neighbor)"],
struct.positions[:, 0][cna == "Cubic diamond (2nd neighbor)"],
"x",
label="Cubic diamond (2nd neighbor)",
)
plt.plot(
struct.positions[:, 2][cna == "Hexagonal diamond"],
struct.positions[:, 0][cna == "Hexagonal diamond"],
"x",
label="Hexagonal diamond",
)
plt.plot(
struct.positions[:, 2][cna == "Hexagonal diamond (1st neighbor)"],
struct.positions[:, 0][cna == "Hexagonal diamond (1st neighbor)"],
"x",
label="Hexagonal diamond (1st neighbor)",
)
plt.plot(
struct.positions[:, 2][cna == "Hexagonal diamond (2nd neighbor)"],
struct.positions[:, 0][cna == "Hexagonal diamond (2nd neighbor)"],
"x",
label="Hexagonal diamond (2nd neighbor)",
)
cna_str_lst = struct.positions[:, 2][cna == cna_str]
if len(cna_str_lst) != 0:
plt.axvline(cna_str_lst.max(), color="red")
plt.axvline(cna_str_lst.min(), color="red")
plt.legend()
plt.show()
plt.xlabel("Position in z")
plt.ylabel("kernel density score")
plt.title("strain: " + str(strain))
if z_range is not None:
plt.plot(z_range, gaussian_funct, label=cna_str)
plt.axvline(
np.min(z_range_above_limit),
color="black",
linestyle="--",
label="ratio: " + str(ratio),
)
plt.axvline(np.max(z_range_above_limit), color="black", linestyle="--")
plt.axhline(0.1, color="red")
plt.legend()
plt.show()
return ratio_lst
[docs]
def ratio_selection(
strain_lst,
ratio_lst,
pressure_lst,
temperature_lst,
ratio_boundary,
debug_plot=True,
):
"""
Args:
strain_lst:
ratio_lst:
pressure_lst:
temperature_lst:
ratio_boundary:
debug_plot:
Returns:
"""
if debug_plot:
plt.plot(strain_lst, ratio_lst)
plt.axhline(0.5 + ratio_boundary, color="red", linestyle="--")
plt.axhline(0.5, color="black", linestyle="--")
plt.axhline(0.5 - ratio_boundary, color="red", linestyle="--")
plt.xlabel("Strain")
plt.ylabel("ratio solid vs. liquid")
rat_lst, rat_col_lst = [], []
for rat in ratio_lst:
if (0.5 - ratio_boundary) < rat < (0.5 + ratio_boundary):
rat_lst.append(rat)
elif len(rat_lst) != 0:
rat_col_lst.append(rat_lst)
rat_lst = []
if len(rat_lst) != 0:
rat_col_lst.append(rat_lst)
if len(rat_col_lst) != 0:
rat_max_ind = np.argmax([len(lst) for lst in rat_col_lst])
ratio_ind = [r in rat_col_lst[rat_max_ind] for r in ratio_lst]
strain_value_lst = np.array(strain_lst)[ratio_ind]
ratio_value_lst = np.array(ratio_lst)[ratio_ind]
pressure_value_lst = np.array(pressure_lst)[ratio_ind]
temperature_value_lst = np.array(temperature_lst)[ratio_ind]
if debug_plot:
plt.axvline(np.min(strain_value_lst), color="blue", linestyle="--")
plt.axvline(np.max(strain_value_lst), color="blue", linestyle="--")
plt.show()
if np.mean(ratio_value_lst) > 0.5:
return (
strain_value_lst,
ratio_value_lst,
pressure_value_lst,
temperature_value_lst,
1,
)
else:
return (
strain_value_lst,
ratio_value_lst,
pressure_value_lst,
temperature_value_lst,
-1,
)
else:
if np.mean(ratio_lst) > 0.5:
return [], [], [], [], 1
else:
return [], [], [], [], -1
[docs]
def plot_equilibration(
temperature_next, strain_lst, nve_run_time_steps, project_parameter, debug_plot=True
):
"""
Args:
temperature_next:
strain_lst:
nve_run_time_steps:
project_parameter:
debug_plot:
Returns:
"""
if debug_plot:
for strain in strain_lst:
job_name = get_nve_job_name(
temperature_next=temperature_next,
strain=strain,
steps_lst=project_parameter["nve_run_time_steps_lst"],
nve_run_time_steps=nve_run_time_steps,
)
ham_nve = project_parameter["project"].load(job_name)
plt.plot(
ham_nve["output/generic/temperature"], label="strain: " + str(strain)
)
plt.axhline(
np.mean(ham_nve["output/generic/temperature"][-20:]),
linestyle="--",
color="red",
)
plt.axvline(
range(len(ham_nve["output/generic/temperature"]))[-20],
linestyle="--",
color="black",
)
plt.legend()
plt.xlabel("timestep")
plt.ylabel("Temperature K")
plt.legend()
plt.show()
[docs]
def plot_melting_point_prediction(
strain_value_lst,
pressure_value_lst,
temperature_value_lst,
boundary_value=0.25,
debug_plot=True,
):
"""
Args:
strain_value_lst:
pressure_value_lst:
temperature_value_lst:
boundary_value:
debug_plot:
Returns:
"""
fit_press = np.poly1d(np.polyfit(strain_value_lst, pressure_value_lst, 1))
fit_temp = np.poly1d(np.polyfit(strain_value_lst, temperature_value_lst, 1))
fit_temp_from_press = np.poly1d(
np.polyfit(pressure_value_lst, temperature_value_lst, 1)
)
fit_combined = np.poly1d(
np.polyfit(fit_press(strain_value_lst), fit_temp(strain_value_lst), 1)
)
if debug_plot:
plt.plot(strain_value_lst, pressure_value_lst, "o", label="pressure (strain)")
plt.plot(strain_value_lst, fit_press(strain_value_lst), label="fit")
plt.xlabel("Strain")
plt.ylabel("Pressure GPa")
plt.legend()
plt.show()
plt.plot(
strain_value_lst, temperature_value_lst, "o", label="temperature (strain)"
)
plt.plot(strain_value_lst, fit_temp(strain_value_lst), label="fit")
plt.xlabel("Strain")
plt.ylabel("Temperature K")
plt.legend()
plt.show()
plt.plot(
pressure_value_lst,
temperature_value_lst,
"o",
label="temperature (pressure)",
)
plt.plot(
pressure_value_lst,
fit_temp_from_press(pressure_value_lst),
label="fit direct",
)
plt.plot(
fit_press(strain_value_lst),
fit_temp(strain_value_lst),
label="combined fits",
)
plt.xlabel("Pressure GPa")
plt.ylabel("Temperature K")
plt.legend()
plt.show()
print(fit_temp_from_press(0.0), fit_combined(0.0))
temperature_mean = (
np.min(temperature_value_lst)
+ (np.max(temperature_value_lst) - np.min(temperature_value_lst)) * 1 / 2
)
temperature_left = np.min(temperature_value_lst) + (
np.max(temperature_value_lst) - np.min(temperature_value_lst)
) * (1 / 2 - boundary_value)
temperature_right = np.min(temperature_value_lst) + (
np.max(temperature_value_lst) - np.min(temperature_value_lst)
) * (1 / 2 + boundary_value)
temperature_next = fit_temp_from_press(0.0)
return temperature_next, temperature_mean, temperature_left, temperature_right
[docs]
def calc_temp_iteration(
basis,
temperature_next,
project_parameter,
timestep,
nve_run_time_steps,
fit_range,
center,
debug_plot=True,
):
"""
Args:
basis:
temperature_next:
project_parameter:
timestep:
nve_run_time_steps:
fit_range:
center:
debug_plot:
Returns:
"""
temperature_next = round_temperature_next(temperature_next)
ham_npt_solid = npt_solid(
temperature=temperature_next,
basis=basis,
project_parameter=project_parameter,
timestep=timestep,
)
ham_npt_liquid_low = npt_liquid(
temperature_solid=temperature_next,
temperature_liquid=temperature_next + 1000,
basis=ham_npt_solid.get_structure(),
project_parameter=project_parameter,
timestep=timestep,
)
basis = ham_npt_liquid_low.get_structure()
basis_no_selective = remove_selective_dynamics(basis)
basis_relative = basis_no_selective.copy()
strain_lst, pressure_lst, temperature_lst, _, _, _ = strain_circle(
basis_relative=basis_relative,
temperature_next=temperature_next,
nve_run_time_steps=nve_run_time_steps,
project_parameter=project_parameter,
timestep=timestep,
strain_result_lst=None,
pressure_result_lst=None,
center=center,
fit_range=fit_range,
)
ratio_lst = plot_solid_liquid_ratio(
temperature_next=temperature_next,
strain_lst=strain_lst,
nve_run_time_steps=nve_run_time_steps,
project_parameter=project_parameter,
debug_plot=debug_plot,
)
(
strain_value_lst,
_,
pressure_value_lst,
temperature_value_lst,
sl_flag,
) = ratio_selection(
strain_lst=strain_lst,
ratio_lst=ratio_lst,
pressure_lst=pressure_lst,
temperature_lst=temperature_lst,
ratio_boundary=project_parameter["ratio_boundary"],
debug_plot=debug_plot,
)
if len(strain_value_lst) > 2:
plot_equilibration(
temperature_next=temperature_next,
strain_lst=strain_lst,
nve_run_time_steps=nve_run_time_steps,
project_parameter=project_parameter,
debug_plot=debug_plot,
)
ind = check_for_holes(
temperature_next=temperature_next,
strain_value_lst=strain_value_lst,
nve_run_time_steps=nve_run_time_steps,
project_parameter=project_parameter,
)
strain_value_lst = np.array(strain_value_lst)[ind].tolist()
pressure_value_lst = np.array(pressure_value_lst)[ind].tolist()
temperature_value_lst = np.array(temperature_value_lst)[ind].tolist()
(
temperature_next,
temperature_mean,
temperature_left,
temperature_right,
) = plot_melting_point_prediction(
strain_value_lst=strain_value_lst,
pressure_value_lst=pressure_value_lst,
temperature_value_lst=temperature_value_lst,
boundary_value=project_parameter["boundary_value"],
debug_plot=True,
)
else:
if sl_flag < 0:
temperature_next, temperature_mean, temperature_left, temperature_right = (
temperature_next * 0.90,
0.0,
0.0,
0.0,
)
else:
temperature_next, temperature_mean, temperature_left, temperature_right = (
temperature_next * 1.10,
0.0,
0.0,
0.0,
)
return (
temperature_next,
temperature_mean,
temperature_left,
temperature_right,
strain_value_lst,
pressure_value_lst,
)
[docs]
def get_initial_melting_temperature_guess(
project_parameter, ham_minimize_vol, temperature_next=None
):
"""
Args:
project_parameter:
ham_minimize_vol:
temperature_next:
Returns:
"""
(
structure_after_minimization,
key_max,
number_of_atoms,
distribution_initial_half,
_,
) = analyse_minimized_structure(ham_minimize_vol)
temperature_left = project_parameter["temperature_left"]
temperature_right = project_parameter["temperature_right"]
if temperature_next is None:
structure_left = structure_after_minimization
structure_right = next_calc(
structure=structure_after_minimization,
temperature=temperature_right,
project_parameter=project_parameter,
run_time_steps=project_parameter["strain_run_time_steps"],
)
temperature_step = temperature_right - temperature_left
while temperature_step > 10:
(
structure_left,
structure_right,
temperature_left,
temperature_right,
) = next_step_funct(
number_of_atoms=number_of_atoms,
key_max=key_max,
structure_left=structure_left,
structure_right=structure_right,
temperature_left=temperature_left,
temperature_right=temperature_right,
distribution_initial_half=distribution_initial_half,
structure_after_minimization=structure_after_minimization,
run_time_steps=project_parameter["strain_run_time_steps"],
project_parameter=project_parameter,
)
temperature_step = temperature_right - temperature_left
temperature_next = int(round(temperature_left))
return temperature_next, structure_left
else:
return temperature_next, ham_minimize_vol.get_structure()
[docs]
def validate_convergence(
pr,
temperature_left,
temperature_next,
temperature_right,
enable_iteration,
timestep_iter,
timestep_lst,
timestep,
fit_range_iter,
fit_range_lst,
fit_range,
nve_run_time_steps_iter,
nve_run_time_steps_lst,
nve_run_time_steps,
strain_result_lst,
pressure_result_lst,
step_count,
step_dict,
boundary_value,
ratio_boundary,
convergence_goal,
output_file="melting.json",
):
"""
Args:
pr:
temperature_left:
temperature_next:
temperature_right:
enable_iteration:
timestep_iter:
timestep_lst:
timestep:
fit_range_iter:
fit_range_lst:
fit_range:
nve_run_time_steps_iter:
nve_run_time_steps_lst:
nve_run_time_steps:
strain_result_lst:
pressure_result_lst:
step_count:
step_dict:
boundary_value:
ratio_boundary:
convergence_goal:
output_file:
Returns:
"""
if temperature_left < temperature_next < temperature_right and enable_iteration:
timestep = next(timestep_iter)
fit_range = next(fit_range_iter)
nve_run_time_steps = next(nve_run_time_steps_iter)
if (
timestep == timestep_lst[-1]
and fit_range == fit_range_lst[-1]
and nve_run_time_steps == nve_run_time_steps_lst[-1]
):
enable_iteration = False
center = np.abs(
get_center_point(
strain_result_lst=strain_result_lst, pressure_result_lst=pressure_result_lst
)
)
step_count += 1
if step_count not in step_dict.keys():
step_dict[step_count] = {
"timestep": timestep,
"fit_range": fit_range,
"nve_run_time_steps": nve_run_time_steps,
"boundary_value": boundary_value,
"ratio_boundary": ratio_boundary,
"temperature_next": temperature_next,
"center": center,
}
with open(output_file, "w") as f:
json.dump(step_dict, f)
else:
timestep = step_dict[step_count]["timestep"]
fit_range = step_dict[step_count]["fit_range"]
nve_run_time_steps = step_dict[step_count]["nve_run_time_steps"]
boundary_value = step_dict[step_count]["boundary_value"]
ratio_boundary = step_dict[step_count]["ratio_boundary"]
temperature_next = step_dict[step_count]["temperature_next"]
center = step_dict[step_count]["center"]
if (
np.abs(
step_dict[step_count]["temperature_next"]
- step_dict[step_count - 1]["temperature_next"]
)
<= convergence_goal
):
convergence_goal_achieved = True
else:
convergence_goal_achieved = False
return (
convergence_goal_achieved,
enable_iteration,
step_count,
step_dict,
timestep,
fit_range,
nve_run_time_steps,
boundary_value,
ratio_boundary,
temperature_next,
center,
)
[docs]
def initialise_iterators(project_parameter):
"""
Args:
project_parameter:
Returns:
"""
return (
iter(project_parameter["timestep_lst"]),
iter(project_parameter["fit_range_lst"]),
iter(project_parameter["nve_run_time_steps_lst"]),
)
[docs]
def get_voronoi_volume(
temperature_next, strain_lst, nve_run_time_steps, project_parameter
):
"""
Args:
temperature_next:
strain_lst:
nve_run_time_steps:
project_parameter:
Returns:
"""
max_lst, mean_lst = [], []
for strain in strain_lst:
job_name = get_nve_job_name(
temperature_next=temperature_next,
strain=strain,
steps_lst=project_parameter["nve_run_time_steps_lst"],
nve_run_time_steps=nve_run_time_steps,
)
ham_nve = project_parameter["project"].load(job_name)
structure_voronoi_lst = ham_nve.get_structure().analyse.pyscal_voronoi_volume()
max_lst.append(np.max(structure_voronoi_lst))
mean_lst.append(np.mean(structure_voronoi_lst))
return max_lst, mean_lst
[docs]
def check_for_holes(
temperature_next,
strain_value_lst,
nve_run_time_steps,
project_parameter,
debug_plot=True,
):
"""
Args:
temperature_next:
strain_value_lst:
nve_run_time_steps:
project_parameter:
debug_plot:
Returns:
"""
max_lst, mean_lst = get_voronoi_volume(
temperature_next=temperature_next,
strain_lst=strain_value_lst,
nve_run_time_steps=nve_run_time_steps,
project_parameter=project_parameter,
)
if debug_plot:
plt.plot(strain_value_lst, mean_lst, label="mean")
plt.plot(strain_value_lst, max_lst, label="max")
plt.axhline(np.mean(mean_lst) * 2, color="black", linestyle="--")
plt.legend()
plt.xlabel("Strain")
plt.ylabel("Voronoi Volume")
plt.show()
return np.array(max_lst) < np.mean(mean_lst) * 2
[docs]
def generate_structure(project_parameter):
"""
Args:
project_parameter:
Returns:
"""
if "lattice_constant" in project_parameter.keys():
basis = project_parameter["project"].create_structure(
project_parameter["element"],
project_parameter["crystalstructure"].lower(),
project_parameter["lattice_constant"],
)
else:
basis = project_parameter["project"].create_ase_bulk(
project_parameter["element"],
project_parameter["crystalstructure"].lower(),
cubic=True,
)
basis_lst = [basis.repeat([i, i, i]) for i in range(5, 30)]
basis = basis_lst[
np.argmin(
[
np.abs(len(b) - project_parameter["number_of_atoms"] / 2)
for b in basis_lst
]
)
]
return basis
[docs]
def generate_random_seed(project_parameter):
"""
Generate random seed for project parameters
Args:
project_parameter (dict):
Returns:
dict: The project parameters dictionary including the key 'seed'
"""
if "seed" not in project_parameter.keys():
project_parameter["seed"] = random.randint(0, 99999)
return project_parameter