Skip to content

Commit 19f1b2c

Browse files
reimplemented fstar diagram
1 parent af3ace1 commit 19f1b2c

25 files changed

+4568
-0
lines changed

pymatgen/analysis/fstar/fstar.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
"""
2+
This module implements an f* coordinate and diagram generator.
3+
For information about f* diagrams, see the following publications:
4+
Yin, L. et al. Extending the limits of powder diffraction analysis: diffraction parameter space, occupancy defects, and
5+
atomic form factors. Rev. Sci. Instrum. 89, 093002 (2018). 10.1063/1.5044555
6+
Yin, L. et al.Thermodynamics of Antisite Defects in Layered NMC Cathodes: Systematic Insights from High-Precision Powder
7+
Diffraction Analyses Chem. Mater 2020 32 (3), 1002-1010. 10.1021/acs.chemmater.9b03646
8+
9+
"""
10+
11+
import os
12+
import numpy as np
13+
import pandas as pd
14+
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
15+
import plotly.express as px
16+
17+
18+
# Load in the form factors
19+
20+
with open(os.path.join(os.path.dirname(__file__),
21+
"xray_factors_2.csv")) as f:
22+
X_RAY_SCATTER_DF = pd.read_csv(f)
23+
# from https://it.iucr.org/Cb/ch6o1v0001/ table 6.1.1.4
24+
with open(os.path.join(os.path.dirname(__file__),
25+
"neutron_factors.csv")) as f:
26+
NEUTRON_SCATTER_DF = pd.read_csv(f)
27+
# from http://www.ccp14.ac.uk/ccp/web-mirrors/neutrons/n-scatter/n-lengths/LIST~1.HTM
28+
29+
30+
class FStarDiagram:
31+
"""
32+
Take a list of structure objects and/or cifs and use them to generate an f* phase diagram.
33+
"""
34+
35+
def __init__(self, structures, scattering_type='X-ray_simple', custom_scatter=None):
36+
"""
37+
Initialize the f* diagram generator with the list of structures and scattering type.
38+
39+
Args:
40+
structures(list): List of structure objects to use in the diagram. These must be symmetrized structure
41+
objects.
42+
scattering_type(str): Type of scattering to use in the f* calculation. Defaults to 'X-ray_simple'
43+
which uses the atomic number as the scattering factor. 'X-ray' and 'Neutron' are built in scattering
44+
types which use X-ray and neutron scattering factors, respectively. 'Custom' allows the user to
45+
supplement their own calculation with any set of scattering factors.
46+
custom_scatter(function): when using custom scattering set this equal to a global varialble that is equal
47+
to the custom scattering function.
48+
"""
49+
50+
self._structures = [SpacegroupAnalyzer(structure).get_symmetrized_structure() for structure in
51+
structures]
52+
self._scatter = scattering_type
53+
self._custscat = custom_scatter
54+
self._equiv_inds = [struct.equivalent_indices for struct in self._structures]
55+
self.site_labels = self.get_site_labels()
56+
self.coords = self.get_fstar_coords()
57+
self.plot = px.scatter_ternary(data_frame=self.coords, a=self.site_labels[0], b=self.site_labels[1],
58+
c=self.site_labels[2])
59+
print("The labels for this structure's unique sites are")
60+
print(self.site_labels)
61+
def edit_fstar_diagram(self, combine_list=False, plot_list=False, **kwargs):
62+
"""
63+
Edit the plot of the f* diagram using plotly express.
64+
65+
Args:
66+
combine_list(list): This is a list of lists which indicates what unique sites need to be combined to make
67+
the plot ternary.
68+
plot_list(list): This is a list that indicates what unique sites or combined sites to plot and what order to
69+
plot them in.
70+
kwargs: use this to add any other arguments from scatter_ternary .
71+
"""
72+
if combine_list:
73+
for combo in combine_list:
74+
self.coords[str(combo)] = sum([self.coords[site] for site in combo])
75+
if str(combo) not in self.site_labels:
76+
self.site_labels.append(str(combo))
77+
if plot_list:
78+
self.plot = px.scatter_ternary(data_frame=self.coords, a=plot_list[0], b=plot_list[1], c=plot_list[2],
79+
**kwargs)
80+
else:
81+
self.plot = px.scatter_ternary(data_frame=self.coords, a=self.site_labels[0], b=self.site_labels[1],
82+
c=self.site_labels[2], **kwargs)
83+
84+
def get_site_labels(self):
85+
"""
86+
Generates unique site labels based on composition, order, and symetry equivalence in the structure object.
87+
Ex:
88+
Structure Summary
89+
Lattice
90+
abc : 2.851 2.851 14.275
91+
angles : 90.0 90.0 119.99999999999999
92+
volume : 100.48498759501827
93+
A : 2.851 0.0 1.745734012184552e-16
94+
B : -1.4254999999999998 2.4690384261894347 1.745734012184552e-16
95+
C : 0.0 0.0 14.275
96+
PeriodicSite: Li:0.990, Ni:0.010 (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000]
97+
PeriodicSite: Li:0.990, Ni:0.010 (1.4255, 0.8230, 4.7583) [0.6667, 0.3333, 0.3333]
98+
PeriodicSite: Li:0.990, Ni:0.010 (-0.0000, 1.6460, 9.5167) [0.3333, 0.6667, 0.6667]
99+
PeriodicSite: Li:0.010, Mn:0.333, Co:0.333, Ni:0.323 (0.0000, 0.0000, 7.1375) [0.0000, 0.0000, 0.5000]
100+
PeriodicSite: Li:0.010, Mn:0.333, Co:0.333, Ni:0.323 (1.4255, 0.8230, 11.8958) [0.6667, 0.3333, 0.8333]
101+
PeriodicSite: Li:0.010, Mn:0.333, Co:0.333, Ni:0.323 (-0.0000, 1.6460, 2.3792) [0.3333, 0.6667, 0.1667]
102+
PeriodicSite: O (0.0000, 0.0000, 3.5688) [0.0000, 0.0000, 0.2500]
103+
PeriodicSite: O (0.0000, 0.0000, 10.7063) [0.0000, 0.0000, 0.7500]
104+
PeriodicSite: O (1.4255, 0.8230, 8.3270) [0.6667, 0.3333, 0.5833]
105+
PeriodicSite: O (1.4255, 0.8230, 1.1895) [0.6667, 0.3333, 0.0833]
106+
PeriodicSite: O (-0.0000, 1.6460, 13.0855) [0.3333, 0.6667, 0.9167]
107+
PeriodicSite: O (-0.0000, 1.6460, 5.9480) [0.3333, 0.6667, 0.4167]
108+
109+
results in
110+
labels - ['[0. 0. 0.]Li', '[0. 0. 0.5]Co', '[0. 0. 0.25]O']
111+
'[0. 0. 0.]Li' - PeriodicSite: Li:0.990, Ni:0.010 (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000]
112+
'[0. 0. 0.5]Co' - PeriodicSite: Li:0.010, Mn:0.333, Co:0.333, Ni:0.323
113+
(0.0000, 0.0000, 7.1375) [0.0000, 0.0000, 0.5000]
114+
'[0. 0. 0.25]O' - PeriodicSite: O (0.0000, 0.0000, 3.5688) [0.0000, 0.0000, 0.2500]
115+
"""
116+
117+
site_labels_fin = []
118+
for ind1, struct in enumerate(self._equiv_inds):
119+
site_labels = []
120+
for ind2, site in enumerate(struct):
121+
label = str(self._structures[ind1][site[0]].frac_coords) + \
122+
[str(sp) for sp, occ in self._structures[ind1][site[0]].species.items()][0]
123+
if label not in site_labels:
124+
site_labels.append(label)
125+
if len(site_labels) > len(site_labels_fin):
126+
site_labels_fin = site_labels
127+
return site_labels_fin
128+
129+
def get_fstar_coords(self):
130+
131+
"""
132+
Calculate the f* coordinates for the list of structures.
133+
"""
134+
135+
fstar_df_full = pd.DataFrame(columns=self.site_labels)
136+
137+
for ind1, struct in enumerate(self._equiv_inds):
138+
fstar_df = pd.DataFrame(columns=self.site_labels, data=[[0.0 for i in self.site_labels]])
139+
for ind2, site in enumerate(struct):
140+
occ_f_list = []
141+
mult = len(site)
142+
site_frac_coord = str(self._structures[ind1][site[0]].frac_coords)
143+
column = [label for label in self.site_labels if site_frac_coord in label]
144+
elements_and_occupancies = self._structures[ind1][site[0]].species.items()
145+
for sp, occ in elements_and_occupancies:
146+
if self._scatter == 'X-ray_simple':
147+
f_occ = sp.Z * occ
148+
if self._scatter == 'X-ray':
149+
for i, n in enumerate(X_RAY_SCATTER_DF['atom'].values):
150+
if hasattr(sp, "element"):
151+
if n == str(sp.element):
152+
f_occ = round(
153+
sum([X_RAY_SCATTER_DF.loc[i]['a1'], X_RAY_SCATTER_DF.loc[i]['a2'],
154+
X_RAY_SCATTER_DF.loc[i]['a3'],
155+
X_RAY_SCATTER_DF.loc[i]['a4'], X_RAY_SCATTER_DF.loc[i]['c']]), 0) * occ
156+
break
157+
else:
158+
continue
159+
else:
160+
if n == str(sp):
161+
f_occ = round(
162+
sum([X_RAY_SCATTER_DF.loc[i]['a1'], X_RAY_SCATTER_DF.loc[i]['a2'],
163+
X_RAY_SCATTER_DF.loc[i]['a3'],
164+
X_RAY_SCATTER_DF.loc[i]['a4'], X_RAY_SCATTER_DF.loc[i]['c']]), 0) * occ
165+
break
166+
else:
167+
continue
168+
169+
if self._scatter == 'Neutron':
170+
for i, n in enumerate(NEUTRON_SCATTER_DF['Isotope'].values):
171+
if hasattr(sp, "element"):
172+
if n == str(sp.element):
173+
f_occ = float(NEUTRON_SCATTER_DF.loc[i]['Coh b']) * occ
174+
break
175+
else:
176+
continue
177+
else:
178+
if n == str(sp):
179+
f_occ = float(NEUTRON_SCATTER_DF.loc[i]['Coh b']) * occ
180+
break
181+
else:
182+
continue
183+
if self._scatter == 'Custom':
184+
if hasattr(sp, "element"):
185+
f_occ = self._custscat(str(sp.element), occ, ind1, ind2)
186+
else:
187+
f_occ = self._custscat(str(sp), occ, ind1, ind2)
188+
occ_f_list.append(f_occ)
189+
190+
fstar = np.absolute(mult * sum(occ_f_list))
191+
fstar_df.loc[0][column[0]] = round(float(fstar), 4)
192+
tot = sum(sum(list(fstar_df.values)))
193+
fstar_df = pd.DataFrame(columns=self.site_labels, data=[fs / tot for fs in list(fstar_df.values)])
194+
fstar_df_full = pd.concat([fstar_df_full, fstar_df], ignore_index=True)
195+
return fstar_df_full

0 commit comments

Comments
 (0)