@钙钛矿的容忍因子计算使用的离子半径的反证
钙钛矿稳定性的问题非常重要,但理论上对其深层次的理解却进展缓慢。目前广泛采用的衡量钙钛矿稳定性的容忍因子(tolerance factor,简称t,参见图1)早在1926年由Goldschmidt提出。若是稳定的钙钛矿其容忍因子的范围一般为0.77~1.10之间。
钙钛矿的A位配位数为12,B位配位数为6,X配位数为6。但是笔者最近发现:由于配位数12的离子半径的数据不全,很多科研工作者都将A配位数6的离子半径充当计算时使用的半径。
本文通过收集已发表的文献钙钛矿的容忍因子(TF)来反证推断论文发表者在计算钙钛矿容忍因子时使用的配位数
1. ABX3为例(X为卤素)
1.1原文献ABX3的化学式与其对应的容忍因子TF
X可以为 F、Cl、Br、I
一共有36个样本(图中只是显示了部分)
1.2 当选择 A的配位数为12,B为6,X为6的离子半径
当选着 A的配位数为12,B为6,X为6时,笔者计算的结果与文献计算结果对比。
(若数据中缺乏配位数为12 的离子半径,则退而求其次选择配位数为8的离子半径,
若数据中缺乏配位数为8 的离子半径,选择配位数为6的离子半径,
因为选择配位数为6的离子半径的数据基本上都是有的,另外如果不这样做的话,代码会报错。)
结果1:rmse为0.0826,相关系数R为0.897,从图上可以看到我预测的结果会稍微偏大一些。
1.3 当选择 A的配位数为6,B为6,X为6的离子半径
当选着 A的配位数为6,B为6,X为6时,笔者计算的结果与文献计算结果对比。
结果2:rmse为0.026,相关系数R为0.936,从图上可以看到我预测的结果非常接近文献的结果。
结果发现:A配位数为6的结果的更好一些。
Talk is cheap. Show me the code
import pandas as pd
import numpy as np
import re
pd.options.display.max_rows = 1000
ChemicalFormula = pd.read_excel(r"化学式ABX3.xlsx")
# A_B_Series = ChemicalFormula["ChemicalFormula"].apply(lambda x : x.split("O")[0]).apply(lambda x :re.findall("[A-Z][a-z]?\d*\.?\d*",x))
# A_B_Series = ChemicalFormula["ChemicalFormula"].apply(lambda x : x[:-1]).apply(lambda x : re.split('F*?$',x)[0])\
# .apply(lambda x : re.split('Cl*?$',x)[0]).apply(lambda x : re.split('Br*?$',x)[0])\
# .apply(lambda x :re.findall("[A-Z][a-z]?\d*\.?\d*",x))
A_B_Series = ChemicalFormula["ChemicalFormula"].apply(lambda x :x[:-1]).apply(lambda x : re.split("[A-Z]?[a-z]?$",x)[0])\
.apply(lambda x :re.findall("[A-Z][a-z]?\d*\.?\d*",x))
import string
s = string.ascii_lowercase
def list_dict2(series_list):
list_2 = []
for sl in series_list :
if len(sl) <= 2:
b_dict = dict(zip(sl.split("0") ,["1"]))
list_2.append(b_dict)
else:
if sl[1] in s:
b_dict = {sl[:2]:sl[2:]}
list_2.append(b_dict)
else:
b_dict = {sl[:1]:sl[1:]}
list_2.append(b_dict)
return list_2
A_B_Series_trans = A_B_Series.map(list_dict2)
print(A_B_Series_trans.shape)
A_B_Series_trans.head(10)
# list_a = [{'Bi': '1'}, {'Ti': '0.15'}, {'Fe': '0.85'}]
# list_a = [{'Zn': '1'}, {'Ti': '1'}]
# list_a = [{'La': '0.75'}, {'Sr': '0.25'}, {'Fe': '1'}]
# list_a = [{'Ni': '0.7'}, {'Cu': '0.2'},{'Ca': '0.1'},{'La': '0.8'}, {'Sr': '0.2'} ]
# list_a = [{'La': '0.8'}, {'Sr': '0.2'}, {'Ni': '0.8'}, {'Cu': '0.2'}]
def transform2(list_a):
# list_a = [{'La': '0.75'}, {'Sr': '0.25'}, {'Fe': '1'}]
sum_ = 0
empty_dict = {}
res = []
for index,dict_ in enumerate(list_a):
value = round(float("".join(dict_.values())) ,10)
empty_dict.update(dict_)
sum_ += value
if round(sum_,10) == 1.0:
# print(index)
right = list_a[index+1 :]
empty_dict2 = {}
for right_dict in right:
empty_dict2.update(right_dict)
break
res.append(empty_dict)
res.append(empty_dict2)
return res
A_B_Series_transform2 = A_B_Series_trans.map(transform2)
print(A_B_Series_transform2.shape)
A_B_Series_transform2.head(10)
from mendeleev import get_table
df_ = get_table('elements')
# get_table('ionicradii')["atomic_number"] [get_table('ionicradii')["atomic_number"] == 14 ]
# get_table('ionicradii')[get_table('ionicradii')["atomic_number"] == 1]
df_["symbol"].values
IR = get_table('ionicradii')
Elements = df_[["atomic_number","symbol"]].merge(IR,on = "atomic_number",how = "left")
print(Elements.shape)
drop_index = [89,101,106,115,120,127,131,139,148]
# Elements[Elements["atomic_number"] == 24]
## Elements[Elements["atomic_number"] == 24][Elements[Elements["atomic_number"] == 24]["spin"] == "HS"].index
Elements = Elements.drop(drop_index)
Elements.reset_index(drop = True,inplace = True)
print(Elements.shape)
# Elements_ = Elements["symbol"].drop_duplicates().reset_index(drop = True)
# Elements_.to_excel("Elements.xlsx",index = False)
# list_ = [{'Sr': 1}, {'Ti': '.5', 'Ta': '.2', 'Cr': '.3'}]
def get_charge_list(list_):
charges_list = []
for length in range(2):
empty_list = []
list_dict = list_[length]
for key in list_dict.keys():
ele = Elements[Elements.symbol == key]
charge = tuple((set(ele["charge"])))
empty_list.append(charge)
# de_duplicated_charge :去重后的电子
de_duplicated_charge = set(empty_list[0])
for i in range(0,len(empty_list)):
de_duplicated_charge = de_duplicated_charge.intersection(set(empty_list[i]))
charges_list.append(list(de_duplicated_charge) )
return charges_list
A_B_Series_transform2_get_charge = A_B_Series_transform2.map(get_charge_list)
print(A_B_Series_transform2_get_charge.shape)
A_B_Series_transform2_get_charge.head()
A_B_Series_transform2_get_charge_select = A_B_Series_transform2_get_charge[A_B_Series_transform2_get_charge.apply(lambda x : not [] in x)]
print(A_B_Series_transform2_get_charge_select.shape)
A_B_Series_transform2_get_charge_select.head()
def get_charge_values(charges_list):
charge_values = []
for i in charges_list [0]:
for j in charges_list [1]:
if i + j == 3:
charge_values.extend([i,j])
return charge_values
get_charge = A_B_Series_transform2_get_charge_select.map(get_charge_values)
print(get_charge.shape)
get_charge.head()
get_charge_= get_charge[get_charge.apply(lambda x : x != [])].apply(lambda x : x[0:2])
element_charges_df = pd.concat([A_B_Series_transform2,get_charge_],axis=1,join='inner')
element_charges_df.columns = ["ChemicalFormula_dict","list_charges"]
print(element_charges_df.shape)
element_charges_df.reset_index(drop = True,inplace = True)
element_charges_df.head()
element_charges_df["ChemicalFormula_A"] = element_charges_df["ChemicalFormula_dict"].apply(lambda x : x[0])
element_charges_df["ChemicalFormula_B"] = element_charges_df["ChemicalFormula_dict"].apply(lambda x : x[1])
element_charges_df["charges_A"] = element_charges_df["list_charges"].apply(lambda x: x[0])
element_charges_df["charges_B"] = element_charges_df["list_charges"].apply(lambda x: x[1])
X = ChemicalFormula["ChemicalFormula"].apply(lambda x :x[:-1]).apply(lambda x : re.findall("[A-Z]?[a-z]?$",x)[0])
X.name = "X"
element_charges_df = pd.concat([ChemicalFormula,element_charges_df,X],axis=1)
ionic_radius_A = []
for index, dict_a in enumerate(element_charges_df["ChemicalFormula_A"]):
ionic_radius_rectify_sum_A = 0
for key in dict_a.keys():
key_ = "".join(key)
value = float(dict_a[key_])
charges_A = element_charges_df.loc[index,"charges_A"]
df_charge = Elements[Elements["symbol"] == key_]
df_charge_ = df_charge[df_charge["charge"] == charges_A]
# coordination = "".join(["XII" if "XII" in df_charge_["coordination"].values else "VI"])
# coordination = "".join(["XII" if "XII" in df_charge_["coordination"].values else "VIII" if "VIII" in df_charge_["coordination"].values else "VI"])
coordination = "VI"
ionic_radius = float(df_charge_[df_charge_["coordination"] == coordination]["ionic_radius"])
ionic_radius_rectify = ionic_radius* value
# print(ionic_radius_rectify)
ionic_radius_rectify_sum_A += ionic_radius_rectify
ionic_radius_rectify_sum_A = round(ionic_radius_rectify_sum_A,10)
ionic_radius_A.append(ionic_radius_rectify_sum_A)
ionic_radius_B = []
for index, dict_b in enumerate(element_charges_df["ChemicalFormula_B"]):
ionic_radius_rectify_sum_B = 0
for key in dict_b.keys():
key_ = "".join(key)
value = float(dict_b[key_])
charges_B = element_charges_df.loc[index,"charges_B"]
df_charge = Elements[Elements["symbol"] == key_]
df_charge_ = df_charge[df_charge["charge"] == charges_B]
# coordination = "".join(["XII" if "XII" in df_charge_["coordination"].values else "VI"])
# coordination = "".join(["VIII" if "VIII" in df_charge_["coordination"].values else "VI"])
# coordination = "".join(["XII" if "XII" in df_charge_["coordination"].values else "VIII" if "VIII" in df_charge_["coordination"].values else "VI"])
coordination = "VI"
ionic_radius = float(df_charge_[df_charge_["coordination"] == coordination]["ionic_radius"])
ionic_radius_rectify = ionic_radius* value
# print(ionic_radius_rectify)
ionic_radius_rectify_sum_B += ionic_radius_rectify
# print(ionic_radius_rectify_sum_B)
ionic_radius_rectify_sum_B = round(ionic_radius_rectify_sum_B,10)
ionic_radius_B.append(ionic_radius_rectify_sum_B)
element_charges_df = pd.concat([element_charges_df,
pd.Series(ionic_radius_A, name="ionic_radius_A"),
pd.Series(ionic_radius_B, name="ionic_radius_B") ],
axis=1)
# RClBrI = Elements[(Elements["symbol"] == "F") | (Elements["symbol"] == "Cl") | (Elements["symbol"] == "Br") | (Elements["symbol"] == "I")]
RClBrI = Elements[Elements["symbol"].apply(lambda x : x in ["F","Cl","Br","I"])]
RClBrI = RClBrI[(RClBrI["charge"] == -1) & (RClBrI["coordination"] == "VI")]
X_value = [round(float(RClBrI[RClBrI["symbol"] == x]["ionic_radius"].values),10) for x in ["F","Cl","Br","I"]]
X_name = [f"{i}" for i in ["F","Cl","Br","I"]]
transform_X = dict (zip(X_name,X_value))
element_charges_df["X_value"] = element_charges_df["X"].map(transform_X)
TF = ((element_charges_df["ionic_radius_A"] + element_charges_df["X_value"]) / \
(np.sqrt(2)*(element_charges_df["ionic_radius_B"]+element_charges_df["X_value"])))
u = (element_charges_df["ionic_radius_B"] / element_charges_df["X_value"])
element_charges_df_1 = pd.concat([element_charges_df,TF,u],axis=1)
element_charges_df_1.columns = list(element_charges_df) + ["TF_pred","u"]
def claer_trans_A(dict_a):
key = "".join(dict_a.keys())
return "XII" in Elements[Elements["symbol"] == key] ["coordination"].values
def claer_trans_B(dict_a):
key = "".join(dict_a.keys())
return "VI" in Elements[Elements["symbol"] == key] ["coordination"].values
element_charges_df_1 = element_charges_df_1[element_charges_df_1["ChemicalFormula_A"]\
.map(claer_trans_A) * element_charges_df_1["ChemicalFormula_B"] .map(claer_trans_B)]
element_charges_df_1.reset_index(drop = True,inplace = True)
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['font.family']='Arial'
df_tf = pd.concat([element_charges_df_1["TF"],element_charges_df_1["TF_pred"]],axis=1)
df_tf.columns = ["附录上的","我计算的",]
rmse = np.sqrt(mean_squared_error(df_tf["附录上的"],df_tf["我计算的"]))
print("rmse:",rmse)
print(df_tf.corr())
plt.figure(figsize = (10,6))
plt.style.use("seaborn-white")
plt.scatter(df_tf["附录上的"],df_tf["我计算的"],edgecolors=(0,0,0),s=35)
plt.plot([0.7,1.1],[0.7,1.1],'k--', lw=2)
plt.xlabel("true",fontsize = 20)
plt.ylabel('pred',fontsize = 20)
plt.show()
结论: 其实作者还以ABO3钙钛矿、A2BB1C6双钙钛矿为例
分别得出的结果与ABX3一致。