NuclearDataTools / NDplot.py
Wanlau
NDplot
a4ef31a
import copy
import json
import math
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#import matplotlib.colors as mcolors
from matplotlib.patches import Patch
## the synthesis methods: Mass Spectroscopy, Radioactive Decay, Light Particles, Fission, Fusion, Spallation, Projectile Fragmentation, and Transfer/Deep Inelastic Scattering
colors_synthesisMethods = {
"MS" : (0, 0, 0),
"RD" : (0, 255, 255),
"LP" : (255, 165, 0),
"FI" : (255, 255, 0),
"FU" : (255, 0, 0),
"SP" : (0, 0, 255),
"PF" : (0, 127, 0),
"UN" : (127, 0, 127)
}
names_synthesisMethods = {
"MS" : "Mass Spectroscopy",
"RD" : "Radioactive Decay",
"LP" : "Light Particles",
"FI" : "Fission",
"FU" : "Fusion",
"SP" : "Spallation",
"PF" : "Projectile Fragmentation",
"UN" : "Transfer/Deep Inelastic"
}
colors_halflife = {
"l100ns": (247, 189, 222),
"100ns" : (255, 198, 165),
"1us" : (255, 231, 198),
"10us" : (255, 255, 156),
"100us" : (255, 255, 16),
"1ms" : (231, 247, 132),
"10ms" : (214, 239, 57),
"100ms" : (173, 222, 99),
"1s" : (82, 181, 82),
"10s" : (99, 189, 181),
"100s" : (99, 198, 222),
"1ks" : (0, 165, 198),
"10ks" : (8, 154, 148),
"100ks" : (0, 132, 165),
"10Ms" : (49, 82, 165),
"1e10s" : (41, 0, 107),
"1e15s" : (0, 0, 0),
"ST" : (0, 0, 0),
"SU" : (255, 148, 115),
"UN" : (224, 224, 224)
}
legends_halflife = {
"l100ns": "<100ns",
"100ns" : "100ns ~ 1us",
"1us" : "1us ~ 10us",
"10us" : "10us ~ 100us",
"100us" : "100us ~ 1ms",
"1ms" : "1ms ~ 10ms",
"10ms" : "10ms ~ 100ms",
"100ms" : "100ms ~ 1s",
"1s" : "1s ~ 10s",
"10s" : "10s ~ 100s",
"100s" : "100s ~ 1ks",
"1ks" : "1ks ~ 10ks",
"10ks" : "10ks ~ 100ks",
"100ks" : "100ks ~ 10Ms",
"10Ms" : "10Ms ~ 1e10s",
"1e10s" : "1e10s ~ 1e15s",
"1e15s" : ">1e15s",
"ST" : "STABLE",
"SU" : "SpecialUnit",
"UN" : "UNKNOWN"
}
colors_DecayModes = {
"P" : (255, 148, 115),
"N" : (156, 123, 189),
"A" : (255, 255, 66),
"B-" : (231, 140, 198),
"EC+B+" : (99, 198, 222),
"EC" : (0, 132, 165),
"SF" : (82, 181, 82),
"STABLE": (0, 0, 0),
"UNKNOWN":(224, 224, 224)
}
legends_DecayModes = {
"P" : "Proton",
"N" : "Neutron",
"A" : "Alaph",
"B-" : "Beta-",
"EC+B+" : "Beta+ ElectronCapture",
"EC" : "ElectronCapture",
"SF" : "SpontaneousFission",
"STABLE": "STABLE",
"UNKNOWN":"UNKNOWN"
}
nuclides_data_path = "data/nndc_nudat_data_export.json"
data_synthesisMethods_path = "data/Nuclides_synthesisMethods.json"
ElementsList_path = "data/ElementsList.json"
data_NuclidesClassifiedHalflife_path = "data/NuclidesClassifiedHalflife.json"
data_NuclidesClassifiedDecayModes_path = "data/NuclidesClassifiedDecayModes.json"
## 使用matplotlib绘图
## 入参为核素分类模式、所绘制核素区域、显示信息、有无图例
## area部分待完善
def nucildesChartPlotPLT(plot_mode=0, area=((0,0),(118,177)), text_mode=0, have_legend=True):
## 确定绘制核素区域
z_min, n_min = area[0]
z_max, n_max = area[1]
area_ysize = z_max - z_min + 1
area_xsize = n_max - n_min + 1
## 计算坐标时使用的单位为像素,而matplotlib的方法使用的单位为英寸,执行前须进行单位转换
nbwidth = 40
nbheight = 40
dpi = 100
## 根据显示信息确定绘图大小
if text_mode == 0:
resize_ratio = 5
ticks_step = 10
fontsize_label = 75
fontsize_ticks = 50
elif text_mode == 1:
resize_ratio = 5
ticks_step = 10
fontsize_label = 75
fontsize_ticks = 50
elif text_mode == 2:
resize_ratio = 10
ticks_step = 10
fontsize_label = 150
fontsize_ticks = 100
elif text_mode == 3:
resize_ratio = 10
ticks_step = 10
fontsize_label = 150
fontsize_ticks = 100
else:
resize_ratio = 1
ticks_step = 10
fontsize_label = 15
fontsize_ticks = 10
## 确定绘制范围
## 据质子数、中子数取整十
## 对于默认的(118,177),绘制范围为(130, 200)
x_min = math.floor(n_min / 10) * 10
x_max = math.ceil(n_max / 10) * 10
y_min = math.floor(z_min / 10) * 10
y_max = math.ceil(z_max / 10) * 10
fig = plt.figure(figsize=(10*resize_ratio*(x_max-x_min)/200, 6*resize_ratio*(y_max-y_min)/130), dpi=dpi)
ax = plt.gca()
## 核素区域绘制,默认白色背景
bg_color = (255, 255, 255)
color_data = np.full((area_ysize, area_xsize, 3), bg_color)
dx = nbwidth * resize_ratio
dy = nbheight * resize_ratio
xposg = np.arange(0, area_xsize*dx + dx, dx)
yposg = np.arange(0, area_ysize*dy + dy, dy)
xgrid = np.tile(xposg.reshape(1, -1), (area_ysize + 1, 1))
ygrid = np.tile(yposg.reshape(-1, 1), (1, area_xsize + 1))
xgridI = xgrid / dpi
ygridI = ygrid / dpi
## 根据核素分类模式上色
color_data = nucildesChartPlotPLTColor(copy.deepcopy(color_data), plot_mode, z_min, n_min ,z_max, n_max)
## 停用imshow转用pcolormesh以便控制各网格大小以及绘制边框
#ax.imshow(color_data, origin="lower", extent=[n_min-0.5, n_max+0.5, z_min-0.5, z_max+0.5])
ax.pcolormesh(xgridI, ygridI, color_data.astype(np.uint8), edgecolors="white", linewidth=4*resize_ratio/dpi)
## 绘制坐标轴
ax.set_xlim(((x_min-5)*dx/dpi, (x_max+20)*dx/dpi))
ax.set_ylim(((y_min-5)*dy/dpi, (y_max+10)*dy/dpi))
ax.set_xlabel("Neutron number", fontsize=fontsize_label, font='Times New Roman')
ax.set_ylabel("Proton number" , fontsize=fontsize_label, font='Times New Roman')
## 刻度绘制,同样取整十 (ticks_step = 10)
xticks = np.arange(x_min, x_max + 20 + ticks_step, ticks_step)
yticks = np.arange(y_min, y_max + 10 + ticks_step, ticks_step)
xtickspos = (xticks * dx + np.ones(len(xticks)) * 0.5 * dx) / dpi
ytickspos = (yticks * dy + np.ones(len(yticks)) * 0.5 * dy) / dpi
ax.set_xticks(xtickspos, xticks, fontsize=fontsize_ticks)
ax.set_yticks(ytickspos, yticks, fontsize=fontsize_ticks)
## 图例添加
## 默认大小匹配全图绘制
if have_legend:
legend_handles = legendHandlesGet(plot_mode)
if len(legend_handles) < 13:
fontsize_legend = fontsize_ticks
else:
fontsize_legend = fontsize_ticks * 0.6
plt.legend(handles=legend_handles, loc="lower right", fontsize=fontsize_legend)
## 添加显示信息
if not text_mode == 0:
color_weight = np.array((0.299, 0.587, 0.114))
text_data = nucildesChartPlotPLTText(plot_mode, z_min, n_min ,z_max, n_max)
## 显示元素名称
if text_mode == 1:
for tdata in text_data:
## 根据方块颜色选择文本颜色(黑或白)
base_color = color_data[tdata["pos"][1]][tdata["pos"][0]]
if np.dot(color_weight, base_color) > 128:
text_color = "black"
else:
text_color = "white"
txposi = (tdata["pos"][0] + 0.5) * dx / dpi
typosi = (tdata["pos"][1] + 0.5) * dy / dpi
ax.text(txposi, typosi, tdata["text01"], ha="center", va="center", color=text_color, fontsize=6)
## 显示核素名称
elif text_mode == 2:
for tdata in text_data:
## 根据方块颜色选择文本颜色(黑或白)
base_color = color_data[tdata["pos"][1]][tdata["pos"][0]]
if np.dot(color_weight, base_color) > 128:
text_color = "black"
else:
text_color = "white"
txposi = (tdata["pos"][0] + 0.5) * dx / dpi
typosi = (tdata["pos"][1] + 0.5) * dy / dpi
ax.text(txposi, typosi, tdata["text02"], ha="center", va="center", color=text_color, fontsize=6)
## 显示详细信息
elif text_mode == 3:
for tdata in text_data:
## 根据方块颜色选择文本颜色(黑或白)
base_color = color_data[tdata["pos"][1]][tdata["pos"][0]]
if np.dot(color_weight, base_color) > 128:
text_color = "black"
else:
text_color = "white"
txposi = (tdata["pos"][0] + 0.5) * dx / dpi
typosi = (tdata["pos"][1] + 0.85) * dy / dpi
text = tdata["text02"] + "\n" + tdata["text03"]
ax.text(txposi, typosi, text, ha="center", va="top", ma="center", color=text_color, fontsize=2.4)
#fig.savefig("test.svg", format="svg")
#fig.savefig("test.png", format="png")
#plt.show()
return fig
def nucildesChartPlotPLTColor(color_data, mode, z_min, n_min ,z_max, n_max):
## 据半衰期上色
## 默认使用基态数据
## nndc上的nudat3绘制时若基态无数据则会使用激发态的数据,此处与其不同 比如:137Pm、154Lu、161Ta 等
if mode == 0:
## 自NDfilter.nuclidesClassifyHalflife()
with open(data_NuclidesClassifiedHalflife_path, "r", encoding="utf8") as file:
data = json.load(file)
for row in data:
if (row["z"] >= z_min and row["z"] <= z_max) and (row["n"] >= n_min and row["n"] <= n_max):
ypos = row["z"] - z_min
xpos = row["n"] - n_min
if row["type"] in colors_halflife:
color_data[ypos][xpos] = np.array(colors_halflife[row["type"]])
## 据衰变模式上色
elif mode == 1:
## 自NDfilter.nuclidesClassifyDecayMode()
with open(data_NuclidesClassifiedDecayModes_path, "r", encoding="utf8") as file:
data = json.load(file)
for row in data:
if (row["z"] >= z_min and row["z"] <= z_max) and (row["n"] >= n_min and row["n"] <= n_max):
ypos = row["z"] - z_min
xpos = row["n"] - n_min
if row["type"] in colors_DecayModes:
color_data[ypos][xpos] = np.array(colors_DecayModes[row["type"]])
elif row["type"] in ("2B-", "β⁻"):
color_data[ypos][xpos] = np.array(colors_DecayModes["B-"])
elif row["type"] in ("2P", "3P"):
color_data[ypos][xpos] = np.array(colors_DecayModes["P"])
elif row["type"] in ("2N"):
color_data[ypos][xpos] = np.array(colors_DecayModes["N"])
else:
color_data[ypos][xpos] = np.array(colors_DecayModes["UNKNOWN"])
## 据合成方法上色
elif mode == 2:
with open(data_synthesisMethods_path, "r", encoding="utf8") as file:
data = json.load(file)
for row in data:
if (row["z"] >= z_min and row["z"] <= z_max) and (row["n"] >= n_min and row["n"] <= n_max):
ypos = row["z"] - z_min
xpos = row["n"] - n_min
if row["type"] in colors_synthesisMethods:
color_data[ypos][xpos] = np.array(colors_synthesisMethods[row["type"]])
return color_data
def legendHandlesGet(plot_mode):
handles = []
if plot_mode == 0:
for hl_tag in colors_halflife:
if hl_tag == "ST":
pass
elif hl_tag == "1e15s":
handles.append(Patch(facecolor=np.array(colors_halflife[hl_tag])/255., label=">1e15s or Stable"))
else:
handles.append(Patch(facecolor=np.array(colors_halflife[hl_tag])/255., label=legends_halflife[hl_tag]))
elif plot_mode == 1:
for decay_mode in colors_DecayModes:
handles.append(Patch(facecolor=np.array(colors_DecayModes[decay_mode])/255., label=legends_DecayModes[decay_mode]))
elif plot_mode == 2:
for synthesis_method in colors_synthesisMethods:
handles.append(Patch(facecolor=np.array(colors_synthesisMethods[synthesis_method])/255., label=names_synthesisMethods[synthesis_method]))
return handles
def nucildesChartPlotPLTText(plot_mode, z_min, n_min ,z_max, n_max):
with open(ElementsList_path, "r", encoding="utf8") as file:
elements_list = json.load(file)
text_data = []
if plot_mode == 0 or plot_mode == 1:
with open(nuclides_data_path, "r", encoding="utf8") as file:
data = json.load(file)
## 填充半衰期及衰变模式信息
## 对于衰变模式多于三种的,只显示其前三种(仿nndc)
## 对于过长的数字,将会对其进行截断
for nom, ndata in data.items():
if (ndata["z"] >= z_min and ndata["z"] <= z_max) and (ndata["n"] >= n_min and ndata["n"] <= n_max):
ypos = ndata["z"] - z_min
xpos = ndata["n"] - n_min
text01 = elements_list[str(ndata["z"])]
text02 = str(ndata["z"]+ndata["n"]) + text01
hlt = ""
dmst = []
if len(ndata["levels"]) == 0:
hlt = ""
dmst = []
else:
if not "halflife" in ndata["levels"][0]:
hlt = ""
elif not "value" in ndata["levels"][0]["halflife"]:
hlt = ""
elif ndata["levels"][0]["halflife"]["value"] == "STABLE":
hlt = "STABLE"
else:
hlv = ndata["levels"][0]["halflife"]["value"]
if hlv > 1e4:
hlt = f"{hlv:.2e}"
else:
hlt = str(hlv)
if ndata["levels"][0]["halflife"]["unit"] == "m":
hlt = hlt + " min"
else:
hlt = hlt + " " + ndata["levels"][0]["halflife"]["unit"]
if not "decayModes" in ndata["levels"][0]:
dmst = []
else:
decay_modes = ndata["levels"][0]["decayModes"]["observed"] + ndata["levels"][0]["decayModes"]["predicted"]
if len(decay_modes) == 0:
dmst = []
else:
## 据分支比排序
dms1 = []
dms2 = []
dmsd = {}
dmsd1 = []
for decay_mode in decay_modes:
if not "value" in decay_mode:
dms2.append(decay_mode)
else:
dmsd[decay_mode["mode"]] = decay_mode
dmsd1.append((decay_mode["value"], decay_mode["mode"]))
dmsdf = pd.DataFrame(dmsd1, columns=["value", "mode"])
dmsdf.sort_values(by="value", ascending=False, inplace=True)
for mode in dmsdf["mode"]:
dms1.append(dmsd[mode])
dms = dms1 + dms2
###
for decay_mode in dms:
text = decay_mode["mode"]
if not "value" in decay_mode:
text = text + " ?"
else:
dmv = decay_mode["value"]
dmt = str(dmv)
if len(dmt) > 7:
if re.search(rf"([eE])", dmt) == None:
if dmv > 1e-3:
dmt = dmt[:7]
else:
match00 = re.fullmatch(rf"(0+)(\.)(0+)([1-9]+)", dmt)
if not match00 == None:
ne = match00.group(4)
if len(ne) < 3:
dmt = f"{dmv:e}"
else:
dmt = f"{dmv:.2e}"
if decay_mode["uncertainty"]["type"] == "limit":
if decay_mode["uncertainty"]["limitType"] == "lower":
if decay_mode["uncertainty"]["isInclusive"] == True:
text = text + " ≥ " + dmt + "%"
else:
text = text + " > " + dmt + "%"
elif decay_mode["uncertainty"]["limitType"] == "upper":
if decay_mode["uncertainty"]["isInclusive"] == True:
text = text + " ≤ " + dmt + "%"
else:
text = text + " < " + dmt + "%"
else:
text = text + " = " + dmt + "%"
dmst.append(text)
text03 = hlt + "\n"
if not len(dmst) > 0:
pass
elif len(dmst) <= 3:
for dmt in dmst:
text03 = text03 + "\n" + dmt
else:
for idx in range(0, 3):
text03 = text03 + "\n" + dmst[idx]
text_data.append({"pos":(xpos, ypos), "text01":text01, "text02":text02, "text03":text03})
## 填充合成方法信息
elif plot_mode == 2:
with open(data_synthesisMethods_path, "r", encoding="utf8") as file:
data = json.load(file)
for row in data:
if (row["z"] >= z_min and row["z"] <= z_max) and (row["n"] >= n_min and row["n"] <= n_max):
ypos = row["z"] - z_min
xpos = row["n"] - n_min
text01 = elements_list[str(row["z"])]
text02 = str(row["z"]+row["n"]) + text01
text03 = row["type"]
text_data.append({"pos":(xpos, ypos), "text01":text01, "text02":text02, "text03":text03})
return text_data
## test