Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update support functions #119

Merged
merged 1 commit into from
Mar 14, 2024
Merged
Changes from all commits
Commits
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
195 changes: 129 additions & 66 deletions calphy/phase_diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,35 @@
from scipy.spatial import ConvexHull
from scipy.interpolate import splrep, splev

colors = ['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928']
colors = ['#a6cee3','#1f78b4','#b2df8a',
'#33a02c','#fb9a99','#e31a1c',
'#fdbf6f','#ff7f00','#cab2d6',
'#6a3d9a','#ffff99','#b15928']

def get_free_energy_at(d, phase, comp, temp, threshold=1E-1):
"""
Extract free energy at given temperature
"""
tarr = np.array(d[phase]["%.2f"%comp]["temperature"])

def _get_temp_arg(tarr, temp, threshold=1E-1):
arg = np.argsort(np.abs(tarr-temp))[0]
th = np.abs(tarr-temp)[arg]
if th > threshold:
val = None
else:
val = d[phase]["%.2f"%comp]["free_energy"][arg]
return val

def calculate_configurational_entropy(x, correction=0):
"""
Calculate configurational entropy
"""
arg = None
return arg

def _get_fe_at_args(arr, args):
fes = [arr[count][x] for count, x in enumerate(args)]
return fes

def _calculate_configurational_entropy(x, correction=0):
if correction == 0:
s = np.array([(c*np.log(c) + (1-c)*np.log(1-c)) if 1 > c > 0 else 0 for c in x])
else:
arg = np.argsort(np.abs(x-correction))[0]
left_side = x[:arg+1]
right_side = x[arg:]
#print(len(left_side))
#print(left_side)
#print(len(right_side))
#print(right_side)

if len(left_side)>0:
left_side = left_side/left_side[-1]
s_left = np.array([(c*np.log(c) + (1-c)*np.log(1-c)) if 1 > c > 0 else 0 for c in left_side])

#correct to zero

if len(right_side)>0:
right_side = right_side - right_side[0]
right_side = right_side/right_side[-1]
Expand All @@ -56,81 +50,120 @@ def calculate_configurational_entropy(x, correction=0):
return s_left
else:
return np.concatenate((s_left, s_right[1:]))


return -s

#def get_free_energy_splines(composition, free_energy, k=3):
# """
# Create splines for free energy, and return them
# """
# return splrep(comp, fes, k=3)

def get_free_energy_fit(composition, free_energy, fit_order=5):
def _get_free_energy_fit(composition,
free_energy,
fit_order=5,
end_weight=3,
end_indices=4):
"""
Create splines for free energy, and return them
"""
weights = np.ones_like(free_energy)
weights[0:4] = 3
weights[-4:] = 3
weights[0:end_indices] = end_weight
weights[-end_indices:] = end_weight
fit = np.polyfit(composition, free_energy, fit_order, w=weights)
return fit


def get_phase_free_energy(data, phase, temp,
def get_phase_free_energy(df, phase, temp,
composition_interval=(0, 1),
ideal_configurational_entropy=False,
entropy_correction=0.0,
composition_grid=10000,
fit_order=5,
plot=False,
composition_interval=(0, 1),
composition_grid=10000,
composition_cutoff=None,
reset_value=1):
reset_value=1,
plot=False):
"""
Extract free energy for given phase
Get the free energy of a phase as a function of composition.

Parameters
----------
df: Pandas dataframe
Dataframe consisting of values from simulation. Should contain at least columns composition, phase, `free_energy` and `temperature`.
`energy_free` and `temperature` should be arrays of equal length, generally an output from reversible scaling calculation.

phase: str
phase for which calculation is to be done. Should be present in `df`.

temp: float
temperature at which the free energy curves are to be calculated.

composition_interval: tuple, optional
If provided, this composition interval is considered. Default (0, 1)

ideal_configuration_entropy: bool, optional\
If True, add the ideal configurational entropy. See Notes. Default False.

entropy_correction: float, optional.
The composition of the ordered phase. See Notes. Default None.

fit_order: int, optional
Order of the polynomial fit used for fitting free energy as a function of composition. Default 5.

composition_grid: int, optional
Number of composition points to be used for fitting. Default 10000.

composition_cutoff: float, optional
term for correcting incomplete data. If two consecutive composition values are separated by more than `composition_cutoff`,
it is reset to `reset_value`. Default None.

reset_value: float, optional
see above. Default 1.

plot: bool, optional
If True, plot the calculated free energy curves.

Returns
-------
result_dict: dict
contains keys: "phase", "temperature", "composition", "free_energy", and "entropy".

Notes
-----
To be added
"""
comporg = list(data[phase]["composition"])
fes = []
comp = []

for c in comporg:
if (composition_interval[0] <= c <= composition_interval[1]):
f = get_free_energy_at(data, phase, float(c), temp)
if f is not None:
fes.append(f)
comp.append(c)
df_phase = df.loc[df['phase']==phase]
df_phase = df_phase.sort_values(by="composition")
df_phase = df_phase[(df_phase['composition'] >= composition_interval[0]) & (df_phase['composition'] <= composition_interval[1])]

fes = np.array(fes)
comp = np.array(comp)
composition = df_phase['composition'].values
args = df_phase["temperature"].apply(_get_temp_arg, args=(temp,))
fes = _get_fe_at_args(df["free_energy"], args)

#filter out None values
composition = np.array([composition[count] for count, x in enumerate(fes) if x is not None])
fes = np.array([x for x in fes if x is not None])

if (len(fes)==0) or (fes is None):
warnings.warn("Some temperatures could not be found!")
else:
else:
if ideal_configurational_entropy:
entropy_term = kb*temp*calculate_configurational_entropy(comp, correction=entropy_correction)
entropy_term = kb*temp*_calculate_configurational_entropy(composition,
correction=entropy_correction)
fes = fes - entropy_term
else:
entropy_term = []

fe_fit = get_free_energy_fit(comp, fes, fit_order=fit_order)
fe_fit = _get_free_energy_fit(composition, fes, fit_order=fit_order)
compfine = np.linspace(np.min(comp), np.max(comp), composition_grid)

fe = np.polyval(fe_fit, compfine)

#fix missing values; assign +0.01 to all values which are not within vicinity
#now fit on the comp grid again
fe = np.polyval(fe_fit, compfine)

if composition_cutoff is not None:
#so we go along composition, see if there are points with no adjacent comp values, ignore them
distances = [np.min(np.abs(c-comp)) for c in compfine]
distances = [np.min(np.abs(c-composition)) for c in compfine]
filters = [x for x in range(len(distances)) if distances[x] > composition_cutoff]
fe[filters] = reset_value

if plot:
plt.scatter(comp, fes, s=4, label=f'{phase}-calc.', color=colors[np.random.randint(len(colors))])
plt.plot(compfine, fe, label=f'{phase}-fit', color=colors[np.random.randint(len(colors))])
plt.scatter(comp, fes, s=4, label=f'{phase}-calc.', color="#e57373")
plt.plot(compfine, fe, label=f'{phase}-fit', color="#b71c1c")
plt.xlabel("x")
plt.ylabel("F (eV/atom)")
plt.legend()
#plt.ylim(top=0.0)

return {"phase":phase, "temperature": temp, "composition": compfine,
"free_energy": fe, "entropy": entropy_term}
return None
Expand All @@ -139,6 +172,9 @@ def get_phase_free_energy(data, phase, temp,
def get_free_energy_mixing(dict_list, threshold=1E-3):
"""
Input is a list of dictionaries

Get free energy of mixing by subtracting end member values.
End members are chosen automatically.
"""
#we have to get min_comp from all possible values
min_comp = np.min([np.min(d["composition"]) for d in dict_list])
Expand Down Expand Up @@ -236,13 +272,17 @@ def get_tangent_type(dict_list, tangent, energy):
return phase_str


def get_common_tangents(dict_list, peak_cutoff=0.01, plot=False,
def get_common_tangents(dict_list,
peak_cutoff=0.01,
plot=False,
remove_self_tangents_for=[],
color_dict=None):
"""
Get common tangent constructions using convex hull method
"""
points = np.vstack([np.column_stack((d["composition"], d["free_energy_mix"])) for d in dict_list])
points = np.vstack([np.column_stack((d["composition"],
d["free_energy_mix"])) for d in dict_list])

if color_dict is None:
color_dict = create_color_list(dict_list)

Expand Down Expand Up @@ -283,5 +323,28 @@ def get_common_tangents(dict_list, peak_cutoff=0.01, plot=False,
for t, e in zip(tangents, energies):
plt.plot(t, e, color="black", ls="dashed")
plt.ylim(top=0.0)
return np.array(tangents), np.array(energies), np.array(tangent_colors), color_dict
return np.array(tangents),
np.array(energies),
np.array(tangent_colors),
color_dict


def plot_phase_diagram(tangents, temperature,
colors,
edgecolor="#37474f",
linewidth=1,
linestyle='-'):

fig, ax = plt.subplots(edgecolor=edgecolor)

for count, x in enumerate(tangents):
for c, a in enumerate(x):
ax.plot(np.array(a),
[temperature[count], temperature[count]],
linestyle,
lw=linewidth,
c=colors[count][c],
)
return fig


Loading