This commit is contained in:
2025-08-20 09:39:00 +08:00
parent 547f36a074
commit 397084995e

View File

@ -209,7 +209,7 @@ def reaction_table_page():
# 初始化数据 # 初始化数据
if 'reaction_data' not in st.session_state: if 'reaction_data' not in st.session_state:
st.session_state.reaction_data = pd.DataFrame([[None,None,None,None,None,None,None,None]],columns=[ df = pd.DataFrame([[None,None,None,None,None,None,None,None]],columns=[
"物质", "物质",
"分子量", "分子量",
"当量", "当量",
@ -218,15 +218,19 @@ def reaction_table_page():
"密度(g/mL)", "密度(g/mL)",
"体积(mL)", "体积(mL)",
"备注" "备注"
]) ],dtype="float")
df["物质"] = df["物质"].astype("string")
df["备注"] = df["备注"].astype("string")
st.session_state.reaction_data = df
st.write("### 反应物质表格") st.write("### 反应物质表格")
st.info("💡 当量为0时该物质不参与当量计算。修改任意数值时会自动重新计算相关参数。") use_on_change = st.checkbox("是否立即计算", value=True)
use_on_change = st.selectbox("是否立即刷新", options=["", ""], index=0) st.info(f"💡 当量为0时该物质不参与当量计算。"+("如果保证表格已有内容自洽,可选中不使用检查。" if not use_on_change else ""))
if not use_on_change:
st.button("计算",on_click=calc_reaction_data)
st.checkbox("不使用检查", value=False,key="no_check",)
def raise_NotImplementedError(info=None):
"""占位函数,避免未实现的回调错误"""
raise NotImplementedError("暂未实现延迟计算")
# 使用data_editor创建可编辑表格 # 使用data_editor创建可编辑表格
edited_data = st.data_editor( edited_data = st.data_editor(
st.session_state.reaction_data, st.session_state.reaction_data,
@ -274,9 +278,12 @@ def reaction_table_page():
"备注": st.column_config.TextColumn("备注", width="medium") "备注": st.column_config.TextColumn("备注", width="medium")
}, },
key="reaction_table", key="reaction_table",
on_change=recalculate_reaction_data if use_on_change == "" else raise_NotImplementedError on_change=recalculate_reaction_data if use_on_change else None
) )
if not use_on_change and isinstance(edited_data, pd.DataFrame):
st.session_state.static_reaction_data = edited_data
if st.session_state.get("reaction_table_refresh",0) == 2: if st.session_state.get("reaction_table_refresh",0) == 2:
st.warning("发生多个编辑,无法计算。") st.warning("发生多个编辑,无法计算。")
st.warning("由于计算失败,当前表格内容可能存在错误。") st.warning("由于计算失败,当前表格内容可能存在错误。")
@ -286,12 +293,88 @@ def reaction_table_page():
st.session_state.reaction_data = edited_data st.session_state.reaction_data = edited_data
st.session_state.reaction_table_refresh = 2 st.session_state.reaction_table_refresh = 2
st.rerun() st.rerun()
# print(st.session_state.reaction_data) # print(st.session_state.reaction_data)
# 仅当返回的是 DataFrame 时再回写;如果是变更字典则由回调处理 # 仅当返回的是 DataFrame 时再回写;如果是变更字典则由回调处理
# if isinstance(edited_data, pd.DataFrame): # if isinstance(edited_data, pd.DataFrame):
# print("Edited DataFrame:", edited_data) # print("Edited DataFrame:", edited_data)
# st.session_state.reaction_data = edited_data # st.session_state.reaction_data = edited_data
def calc_reaction_data():
try:
df: pd.DataFrame = st.session_state.get("static_reaction_data",None)
if not isinstance(df, pd.DataFrame):
st.error("反应数据格式不正确")
raise ValueError("reaction_data must be a DataFrame")
df.columns = ["name","mw","eq","mol","mass","rho","vol","note"]
# validate
if not st.session_state.get("no_check",False):
if any(df["mw"].notna() & df["mol"].notna() & df["mass"].notna()):
st.error("分子量、物质的量和质量不能同时存在")
raise ValueError
if any((df["mass"].notna() | (df["mw"].notna() & df["mol"].notna())) & df["vol"].notna() & df["rho"].notna()):
st.error("质量、体积和密度不能同时存在或可求")
raise ValueError
# mol -> mass
fil = df["mw"].notna() & df["mol"].notna()
df.loc[fil, "mass"] = df[fil]["mol"] * df[f"{'mw'}"] / 1000.0 # mmol -> mol再乘以 g/mol
# mass -> mol
fil = df["mw"].notna() & df["mass"].notna()
df.loc[fil, "mol"] = df[fil]["mass"] * 1000.0 / df[f"{'mw'}"] # g -> mol再除以 g/mol
# mass -> vol
fil = df["mass"].notna() & df["rho"].notna()
df.loc[fil, "vol"] = df[fil]["mass"] / df[fil]["rho"] # g -> mL再除以 g/mL
# vol -> mass
fil = df["vol"].notna() & df["rho"].notna()
df.loc[fil, "mass"] = df[fil]["vol"] * df[fil]["rho"] # mL -> g再乘以 g/mL
# mass -> mol
fil = df["mw"].notna() & df["mass"].notna()
df.loc[fil, "mol"] = df[fil]["mass"] * 1000.0 / df[f"{'mw'}"] # g -> mol再除以 g/mol
eql = df[(df["eq"] > 0) & (df["mol"] > 0)]
if not st.session_state.get("no_check",False):
if eql.size > 1:
st.error("对于当量存在物质,只允许一个物质设置用量、质量或体积")
raise ValueError
if eql.size == 0 and not df[(df["eq"] > 0)].empty:
st.error("设置了当量,但是均没有设置用量、质量或体积")
raise ValueError
if not eql.empty:
ref = eql.iloc[0]
base = ref["mol"]/ref["eq"]
eqfil = df["eq"] > 0
df.loc[eqfil, "mol"] = df[eqfil]["eq"] * base
fil = df["mw"].notna() & eqfil
df.loc[fil, "mass"] = df[fil]["mol"] * df[f"{'mw'}"] / 1000.0 # mmol -> mol再乘以 g/mol
fil = df["rho"].notna() & df["mass"].notna() & eqfil
df.loc[fil, "vol"] = df[fil]["mass"] / df[fil]["rho"] # g -> mL再除以 g/mL
df.columns = [
"物质",
"分子量",
"当量",
"用量(mmol)",
"质量(g)",
"密度(g/mL)",
"体积(mL)",
"备注"
]
st.session_state.reaction_data = df
except Exception as e:
st.error("计算过程中出错,表格可能有误")
raise e
print("calc_reaction_data error:", e)
return
def recalculate_reaction_data(): def recalculate_reaction_data():
"""根据最近一次编辑的行及当量,推算其他未编辑行的用量,并更新质量/体积。""" """根据最近一次编辑的行及当量,推算其他未编辑行的用量,并更新质量/体积。"""
try: try:
@ -382,17 +465,35 @@ def recalculate_reaction_data():
# 基准行的自洽计算(用量/质量/体积) # 基准行的自洽计算(用量/质量/体积)
brow = df.loc[basis_idx] brow = df.loc[basis_idx]
if "物质" in edited.keys() and pd.isna(brow["分子量"]) and "分子量" not in edited.keys():
try:
mass = molmass.Formula(edited["物质"]).mass
edited["分子量"] = mass
df.loc[basis_idx, "分子量"] = mass
except Exception as e:
pass
if "密度(g/mL)" in edited.keys(): if "密度(g/mL)" in edited.keys():
df.loc[basis_idx, "密度(g/mL)"] = edited["密度(g/mL)"] if _to_float(brow.get("体积(mL)")) is None and "质量(g)" in brow.keys():
if brow.get("体积(mL)") is None and "质量(g)" in brow.keys():
edited["质量(g)"] = _to_float(brow.get("质量(g)")) edited["质量(g)"] = _to_float(brow.get("质量(g)"))
elif brow.get("质量(g)") is None and "体积(mL)" in brow.keys(): elif _to_float(brow.get("质量(g)")) is None and "体积(mL)" in brow.keys():
edited["体积(mL)"] = _to_float(brow.get("体积(mL)")) edited["体积(mL)"] = _to_float(brow.get("体积(mL)"))
else: else:
print(brow)
st.error("当质量和体积同时存在时,修改密度为未定义行为。") st.error("当质量和体积同时存在时,修改密度为未定义行为。")
st.warning("由于计算失败,当前表格内容可能存在错误。") raise ValueError
return
if "分子量" in edited.keys():
if all(pd.notna(brow[["质量(g)","用量(mmol)"]])):
st.error("当质量和用量同时存在时,修改分子量为未定义行为。")
raise ValueError
if pd.notna(brow.get("质量(g)")):
edited["质量(g)"] = _to_float(brow.get("质量(g)"))
if pd.notna(brow.get("用量(mmol)")):
edited["用量(mmol)"] = _to_float(brow.get("用量(mmol)"))
b_mw = _to_float(brow.get("分子量")) b_mw = _to_float(brow.get("分子量"))
b_density = edited.get("密度(g/mL)", _to_float(brow.get("密度(g/mL)"))) b_density = edited.get("密度(g/mL)", _to_float(brow.get("密度(g/mL)")))
b_amount = edited.get("用量(mmol)", None) b_amount = edited.get("用量(mmol)", None)
@ -459,7 +560,7 @@ def recalculate_reaction_data():
st.session_state.reaction_data = df st.session_state.reaction_data = df
except Exception as e: except Exception as e:
# raise e # raise e
st.error("重新计算反应数据时出错,表格可能有误") st.warning("重新计算反应数据时出错,表格可能有误")
print("recalculate_reaction_data error:", e) print("recalculate_reaction_data error:", e)
def add_compound_to_reaction(compound:PubChemCompound): def add_compound_to_reaction(compound:PubChemCompound):