|
| 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