Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
78ab1cc
Merge github.com:materialsproject/mpmorph into dev
Jan 17, 2023
7b0642d
Merge branch 'bugfix/refactor-pv-extract' of github.com:materialsproj…
Jan 17, 2023
357e109
Init lammps feature
mcgalcode Jan 19, 2023
121fb65
add output
mcgalcode Jan 19, 2023
b325272
Add name to lammps maker
mcgalcode Jan 19, 2023
72bd5ae
Add dump files parameter
mcgalcode Jan 19, 2023
bd34ffa
fix data filename
mcgalcode Jan 19, 2023
3ee4b4b
try print
mcgalcode Jan 19, 2023
2f6032d
Add atom style to lammps data
mcgalcode Jan 19, 2023
7338ed4
Improve lammps runner inputs
mcgalcode Jan 20, 2023
e9ccfeb
fix lammps invocation
mcgalcode Jan 20, 2023
b112886
fix input format
mcgalcode Jan 20, 2023
161998d
Add template to package
mcgalcode Jan 20, 2023
c3744dd
Try vol workflow w lammps
mcgalcode Jan 20, 2023
28e6012
fix output ref
mcgalcode Jan 20, 2023
538320e
fix chem sys string
mcgalcode Jan 20, 2023
7406dde
get bins from path
mcgalcode Jan 20, 2023
e215b08
remove extraneous params
mcgalcode Jan 20, 2023
b7d0da0
add mpid and formula to output
mcgalcode Jan 21, 2023
5a91e22
use average of last 10% of data points
mcgalcode Jan 21, 2023
ba41df6
take 30%
mcgalcode Jan 21, 2023
7248d80
Save more data than we have been
mcgalcode Jan 21, 2023
9cf017c
fix flow name
mcgalcode Jan 21, 2023
d16f398
Update LAMMPS output
mcgalcode Jan 27, 2023
38843a5
fix typo"
mcgalcode Jan 27, 2023
3971037
include last step of trajectory
mcgalcode Jan 27, 2023
c70678e
add temperature sweeping lammps input template
Feb 8, 2023
fc12937
Merge pull request #29 from Tinaatucsd/feature/lammps
mcgalcode Feb 8, 2023
9e9af36
run black
Feb 10, 2023
fb2cbf7
Merge remote-tracking branch 'upstream/feature/lammps' into feature/l…
Feb 10, 2023
f68009e
remove the unfinished function
Feb 14, 2023
268601a
Add basic temp sweep code
mcgalcode Feb 22, 2023
0fc5ce9
Merge branch 'feature/lammps' of github.com:materialsproject/mpmorph …
mcgalcode Feb 22, 2023
a6a5c88
Merge pull request #16 from materialsproject/feature/lammps
Feb 22, 2023
8a10833
Merge branch 'feature/lammps' of https://github.com/Tinaatucsd/mpmorp…
Feb 22, 2023
deed20e
update the directory name to templates
Feb 22, 2023
e21aa33
Merge pull request #30 from Tinaatucsd/feature/lammps
Feb 22, 2023
cdc670c
fix the template path
Feb 22, 2023
173e4ea
Merge pull request #31 from Tinaatucsd/feature/lammps
Feb 22, 2023
a9b25e7
rename data.lammps to data.dump to match with template
Feb 22, 2023
883595f
Merge pull request #32 from Tinaatucsd/feature/lammps
Feb 22, 2023
a91d4db
update the lammps template file
Feb 22, 2023
f883cad
Merge pull request #33 from Tinaatucsd/feature/lammps
Feb 22, 2023
2cc3951
fix template file
Feb 22, 2023
c7755cd
fix path of template file
Feb 22, 2023
4b4060f
fix template path
Feb 22, 2023
737b9f0
dump ele str into trajectory.lammpstrj
Feb 23, 2023
5c40471
Merge pull request #34 from Tinaatucsd/feature/lammps
Feb 23, 2023
0ab76f0
add ensemble option for constant temperature run
Feb 25, 2023
41619a5
update ensemble option
Feb 25, 2023
b92aac4
minor fix ensemble
Feb 25, 2023
5ad2163
Merge pull request #35 from Tinaatucsd/feature/lammps
Feb 25, 2023
91ae044
Add ensemble and polyfit estimators
mcgalcode Feb 28, 2023
c2bada7
fix typo in lammps input template
mcgalcode Feb 28, 2023
758e56f
Merge branch 'feature/lammps' of github.com:materialsproject/mpmorph …
mcgalcode Feb 28, 2023
606f061
update the contant_temp template, enable NVT/NPT
Mar 1, 2023
d8c50ae
Merge branch 'materialsproject:feature/lammps' into feature/lammps
Mar 1, 2023
d84dbb0
reduce the redundancy
Mar 1, 2023
666ce24
Merge branch 'feature/lammps' of https://github.com/Tinaatucsd/mpmorp…
Mar 1, 2023
5972612
A few fixes
mcgalcode Mar 1, 2023
9b8f311
Add trisection method
mcgalcode Mar 2, 2023
710de91
Merge branch 'materialsproject:feature/lammps' into feature/lammps
Mar 2, 2023
eb8f7dd
Merge pull request #36 from Tinaatucsd/feature/lammps
Mar 4, 2023
4e064e1
Merge pull request #18 from materialsproject/feature/lammps
Mar 6, 2023
96ead3d
add box tilt large to lammps template
Mar 17, 2023
56f5215
Merge branch 'feature/lammps' of https://github.com/Tinaatucsd/mpmorp…
Mar 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mpmorph/analysis/diffusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def plot(self, title=None, annotate=True, el="", **kwargs):
self.y,
yerr=self.yerr.T,
label="Q[{}]: ".format(el) + tx + " K",
**kwargs
**kwargs,
)
plt.ylabel("ln(D cm$^2$/s)", fontsize=15)
plt.xlabel("1000/T K$^{-1}$", fontsize=15)
Expand Down
341 changes: 301 additions & 40 deletions src/mpmorph/analysis/melting_points.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,334 @@
import numpy as np
from scipy.stats import linregress
import matplotlib.pyplot as plt

from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics import mean_squared_error
import numpy as np
import matplotlib.pyplot as plt
import math
from abc import ABC, abstractmethod
import numpy.polynomial.polynomial as poly
from numpy.polynomial import Polynomial as P

class MeltingPointAnalyzer():
class AbstractMeltingPointEstimator(ABC):

def split_dset(self, pts, split_idx):
return pts[0:split_idx], pts[split_idx:]
def plot(self, ts, vs, plot_title=None):
fig, axs = self._plot_ts_vs(ts, vs)
Tm = self.estimate(ts, vs)
axs.plot([Tm, Tm], [min(vs), max(vs)], color="r")

def get_split_fit(self, xs, ys, split_idx):
leftx, rightx = self.split_dset(xs, split_idx)
lefty, righty = self.split_dset(ys, split_idx)
if plot_title is None:
axs.set_title("Volume vs Temperature by Polynomial Fit")
else:
axs.set_title(plot_title)

leftfit = linregress(leftx, lefty)
lefterr = leftfit.stderr
return fig, axs

@abstractmethod
def estimate(self, temps, vols):
pass

def _plot_ts_vs(self, ts, vs):
fig, axs = plt.subplots()
axs.scatter(ts, vs)
axs.set_xlabel("Temperature (K)")
axs.set_ylabel("Volume (A^3)")
return fig, axs

class MeltingPointEnsembleEstimator(AbstractMeltingPointEstimator):

def __init__(self, estimators):
self._estimators = estimators

def estimate(self, temps, vols):
tm_estimates = [e.estimate(temps, vols) for e in self._estimators]
return np.mean(tm_estimates)

def plot(self, ts, vs, plot_title = None):
_, axs = self._plot_ts_vs(ts, vs)
tm = self.estimate(ts, vs)
axs.plot([tm, tm], [np.min(vs), np.max(vs)])

def plot_all_estimates(self, ts, vs):
_, axs = self._plot_ts_vs(ts, vs)
for e in self._estimators:
tm = e.estimate(ts, vs)
axs.plot([tm, tm], [np.min(vs), np.max(vs)], label=e.name)

avg = self.estimate(ts, vs)
axs.plot([avg , avg ], [np.min(vs), np.max(vs)], label="Mean Estimate")
axs.set_title("Ensemble Tm Estimates")
axs.legend()



class MeltingPointClusterEstimator(AbstractMeltingPointEstimator):

name: str = "Clustering"

def plot_clusters(self, ts, vs, plot_title=None):
points = np.array(list(zip(ts, vs)))
cluster1, cluster2 = self._get_clusters(points)
fig, axs = plt.subplots()
axs.scatter(*cluster1)
axs.scatter(*cluster2)
axs.set_xlabel("Temperature (K)")
axs.set_ylabel("Volume (A^3)")
Tm = self.estimate(ts, vs)
axs.plot([Tm, Tm], [min(vs), max(vs)], color="r")

if plot_title is None:
axs.title("Volume vs Temperature by Clustering")
else:
axs.title(plot_title)

rightfit = linregress(rightx, righty)
righterr = rightfit.stderr
return fig, axs

def estimate(self, temps, vols):
points = np.array(list(zip(temps, vols)))
cluster1, cluster2 = self._get_clusters(points)
if min(cluster1[0]) < min(cluster2[0]):
solid_range = cluster1[0]
liquid_range = cluster2[0]
else:
solid_range = cluster2[0]
liquid_range = cluster1[0]

return np.mean([max(solid_range), min(liquid_range)])

def _get_clusters(self, points):
clustering = AgglomerativeClustering(n_clusters=2).fit(points)
cluster1 = points[np.argwhere(clustering.labels_ == 1).squeeze()].T
cluster2 = points[np.argwhere(clustering.labels_ == 0).squeeze()].T
return cluster1, cluster2

class MeltingPointTrisectionEstimator(AbstractMeltingPointEstimator):

def _get_fit_error_total(self, xs, ys):
slope, intercept, r_value, p_value, std_err = linregress(xs, ys)
y_pred = intercept + slope * np.array(xs)
err = np.sum(np.abs(y_pred - ys))
return slope, intercept, err

def _unzip_pts(self, pts):
ts = [pt[0] for pt in pts]
vs = [pt[1] for pt in pts]
return ts, vs

def _plot_pts(self, pts):
ts, vs = self._unzip_pts(pts)
plt.scatter(ts,vs, color='grey')


def _split_pts(self, pts, x1, x2):
set1 = [pt for pt in pts if pt[0] < x1]
set2 = [pt for pt in pts if pt[0] > x1 and pt[0] < x2]
set3 = [pt for pt in pts if pt[0] > x2]
return set1, set2, set3

def _plot_split(self, pts, x1, x2):
set1, set2, set3 = self._plit_pts(pts, x1, x2)
self._plot_pts(set1)
self._plot_pts(set2)
self._plot_pts(set3)

def _get_linear_ys(self, m, b, xs):
return [m * x + b for x in xs]

def _plot_fit_line(self, m, b, xs):
fit_ys = self._get_linear_ys(m, b, xs)
plt.plot(xs, fit_ys)

def _plot_fits(self, pts, x1, x2):
set1, set2, set3 = self._split_pts(pts, x1, x2)
for dset in [set1, set2, set3]:
xs, ys = self._unzip_pts(dset)
m, b, err = self._get_fit_error_total(xs, ys)
self._plot_pts(dset)
self._plot_fit_line(m, b, xs)


def _find_best_trisection(self, points, min_x = None, max_x = None, min_window_size = 100, step_size = 50):
lowest_err = math.inf

combined_err = math.sqrt(lefterr ** 2 + righterr ** 2)
combined_err = lefterr + righterr
return leftfit.slope, leftfit.intercept, rightfit.slope, rightfit.intercept, combined_err
xs, ys = self._unzip_pts(points)
if min_x is None:
min_x = math.floor(np.min(xs))

if max_x is None:
max_x = math.ceil(np.max(xs))

for pt1 in range(min_x + min_window_size, max_x - 2 * min_window_size, step_size):
for pt2 in range(pt1 + min_window_size, max_x - min_window_size, step_size):
set1, set2, set3 = self._split_pts(points, pt1, pt2)
errs_total = 0
errs = []
try:
for dset in [set1, set2, set3]:
xs, ys = self._unzip_pts(dset)
m, b, err = self._get_fit_error_total(xs, ys)
errs.append(err)
errs_total += err ** 2

errs_total = math.sqrt(errs_total)

if errs_total < lowest_err:
lowest_err = errs_total
best_pt1 = pt1
best_pt2 = pt2
except:
print("problem encountered")

def assess_splits(self, xs, ys):
return best_pt1, best_pt2

def _estimate_melting_pt(self, points):
pt1, pt2 = self._find_best_trisection(points, step_size=100)
# print(f'Coarse guess: {pt1, pt2}')
pt1, pt2 = self._find_best_trisection(points, pt1 - 150, pt2 + 150, step_size = 5)
# print(f'Fine guess: {pt1, pt2}')
return pt1, pt2, (pt2 - pt1) / 2 + pt1

def estimate(self, temps, vols):
pts = list(zip(temps, vols))
pt1, pt2, tm = self._estimate_melting_pt(pts)
return tm

def plot(self, ts, vs, plot_title=None):
pts = list(zip(ts, vs))
pt1, pt2, tm = self._estimate_melting_pt(pts)
self._plot_fits(pts, pt1, pt2)


class MeltingPointBisectionEstimator(AbstractMeltingPointEstimator):

def plot_best_split(self, temps, vols):
split_idx = self.get_best_split(temps, vols)
fig, axs = self._plot_split(temps, vols, split_idx)
Tm = self.estimate(temps, vols)
axs.plot([Tm, Tm], [min(vols), max(vols)], color="r")
return fig, axs

def estimate(self, temps, vols):
best_split_idx = self.get_best_split(temps, vols)
return np.mean([temps[best_split_idx], temps[best_split_idx - 1]])

def _split_dset(self, pts, split_idx):
return pts[0:split_idx], pts[split_idx:]

def _assess_splits(self, xs, ys):
dset_size = len(xs)
buffer = max(round(dset_size / 10), 3)

pt_idxs = list(range(buffer + 1, len(xs) - buffer - 1))
errs = []
for idx in pt_idxs:
_, _, _, _, total_err = self.get_split_fit(xs, ys, idx)
_, _, _, _, total_err = self._get_split_fit(xs, ys, idx)
errs.append(total_err)

return list(zip(pt_idxs, errs))

def get_linear_ys(self, m, b, xs):
def _get_linear_ys(self, m, b, xs):
return [m * x + b for x in xs]

def plot_split(self, xs, ys, split_idx):
m1, b1, m2, b2, _ = self.get_split_fit(xs, ys, split_idx)
leftxs, rightxs = self.split_dset(xs, split_idx)
leftys, rightys = self.split_dset(ys, split_idx)
def _plot_split(self, xs, ys, split_idx):
m1, b1, m2, b2, _ = self._get_split_fit(xs, ys, split_idx)
leftxs, rightxs = self._split_dset(xs, split_idx)
leftys, rightys = self._split_dset(ys, split_idx)

left_fit_ys = self.get_linear_ys(m1, b1, leftxs)
fig, axs = plt.subplots()

plt.scatter(leftxs, leftys)
plt.plot(leftxs, left_fit_ys)
plt.title("Volume vs Temperature (w/ best fits)")
plt.xlabel("Temperature (K)")
plt.ylabel("Equil. Volume (cubic Angstroms)")
left_fit_ys = self._get_linear_ys(m1, b1, leftxs)
axs.scatter(leftxs, leftys)
axs.plot(leftxs, left_fit_ys)

right_fit_ys = self.get_linear_ys(m2, b2, rightxs)

plt.scatter(rightxs, rightys)
plt.plot(rightxs, right_fit_ys)

right_fit_ys = self._get_linear_ys(m2, b2, rightxs)
axs.scatter(rightxs, rightys)
axs.plot(rightxs, right_fit_ys)

axs.title("Volume vs Temperature (w/ best fits by Slope Method")
axs.xlabel("Temperature (K)")
axs.ylabel("Equil. Volume (cubic Angstroms)")
return fig, axs

def get_best_split(self, xs, ys):
split_errs = self.assess_splits(xs, ys)
split_errs = self._assess_splits(xs, ys)
errs = [pt[1] for pt in split_errs]
idxs = [pt[0] for pt in split_errs]
best_split_idx = idxs[np.argmin(errs)]
return best_split_idx

def plot_vol_vs_temp(self, temps, vols):
split_idx = self.get_best_split(temps, vols)
self.plot_split(temps, vols, split_idx)

def estimate_melting_temp(self, temps, vols):
best_split_idx = self.get_best_split(temps, vols)
return temps[best_split_idx]

class MeltingPointSlopeRMSEEstimator(MeltingPointBisectionEstimator):

name: str = "RMSE Bisection"

def _get_fit_error(self, xs, ys):
slope, intercept, r_value, p_value, std_err = linregress(xs, ys)
y_pred = intercept + slope * np.array(xs)
err = mean_squared_error(y_true=ys, y_pred=y_pred, squared=False)
return slope, intercept, err

def _get_split_fit(self, xs, ys, split_idx):
leftx, rightx = self._split_dset(xs, split_idx)
lefty, righty = self._split_dset(ys, split_idx)

lslope, lintercept, r_value, p_value, std_err = linregress(leftx, lefty)
left_y_pred = lintercept + lslope * np.array(leftx)
lefterr = mean_squared_error(y_true=lefty, y_pred=left_y_pred, squared=False)

rslope, rintercept, r_value, p_value, std_err = linregress(rightx, righty)
right_y_pred = rintercept + rslope * np.array(rightx)
righterr = mean_squared_error(y_true=righty, y_pred=right_y_pred, squared=False)

combined_err = math.sqrt(lefterr**2 + righterr**2)
combined_err = lefterr + righterr
return lslope, lintercept, rslope, rintercept, combined_err


class MeltingPointSlopeStdErrEstimator(MeltingPointBisectionEstimator):

name: str = "StdErr Bisection"

def _get_split_fit(self, xs, ys, split_idx):
leftx, rightx = self._split_dset(xs, split_idx)
lefty, righty = self._split_dset(ys, split_idx)

leftfit = linregress(leftx, lefty)
lefterr = leftfit.stderr

rightfit = linregress(rightx, righty)
righterr = rightfit.stderr

combined_err = math.sqrt(lefterr**2 + righterr**2)
combined_err = lefterr + righterr
return (
leftfit.slope,
leftfit.intercept,
rightfit.slope,
rightfit.intercept,
combined_err,
)

class MeltingPointPolyFitEstimator(AbstractMeltingPointEstimator):

name: str = "Polynomial Fit"

def plot_fit(self, ts, vs, plot_title=None):
fig, axs = super().plot(ts, vs, plot_title=plot_title)
p = self._polyfit(ts, vs)
fit_ts = p(vs)
axs.plot(fit_ts, vs, color='r')
return fig, axs

def estimate(self, temps, vols):
p = self._polyfit(temps, vols)
second = p.deriv(2)
melting_v = second.roots()[0]
melting_t = p(melting_v)
return melting_t

def _polyfit(self, temps, vols):
coefs = poly.polyfit(vols, temps, 3)
p = P(coefs)
return p
Loading