From 813062b5308e5de3b08ef967e1a5968066ed8422 Mon Sep 17 00:00:00 2001
From: XFShii <148210896+XFShii@users.noreply.github.com>
Date: Wed, 26 Jun 2024 16:24:42 +0800
Subject: [PATCH 1/5] Update common_prop.py
add DecohesionEnergy
---
apex/core/common_prop.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/apex/core/common_prop.py b/apex/core/common_prop.py
index dc179dd..d54f981 100644
--- a/apex/core/common_prop.py
+++ b/apex/core/common_prop.py
@@ -11,6 +11,7 @@
from apex.core.property.Surface import Surface
from apex.core.property.Vacancy import Vacancy
from apex.core.property.Phonon import Phonon
+from apex.core.property.DecohesionEnergy import DecohesionEnergy
from apex.core.lib.utils import create_path
from apex.core.lib.util import collect_task
from apex.core.lib.dispatcher import make_submission
@@ -39,6 +40,8 @@ def make_property_instance(parameters, inter_param):
return Gamma(parameters, inter_param)
elif prop_type == "phonon":
return Phonon(parameters, inter_param)
+ elif prop_type == "DecohesionEnergy":
+ return DecohesionEnergy(parameters, inter_param)
else:
raise RuntimeError(f"unknown APEX type {prop_type}")
From eb54fa6371da617f4ea74fdc66e75778b01f69b5 Mon Sep 17 00:00:00 2001
From: XFShii <148210896+XFShii@users.noreply.github.com>
Date: Wed, 26 Jun 2024 16:27:22 +0800
Subject: [PATCH 2/5] Add files via upload
---
apex/core/property/DecohesionEnergy.py | 300 +++++++++++++++++++++++++
1 file changed, 300 insertions(+)
create mode 100644 apex/core/property/DecohesionEnergy.py
diff --git a/apex/core/property/DecohesionEnergy.py b/apex/core/property/DecohesionEnergy.py
new file mode 100644
index 0000000..7950763
--- /dev/null
+++ b/apex/core/property/DecohesionEnergy.py
@@ -0,0 +1,300 @@
+import glob
+import json
+import logging
+import os
+import re
+import dpdata
+import numpy as np
+from monty.serialization import dumpfn, loadfn
+from pymatgen.core.structure import Structure
+from pymatgen.core.surface import SlabGenerator
+
+from apex.core.calculator.lib import abacus_utils
+from apex.core.calculator.lib import vasp_utils
+from apex.core.property.Property import Property
+from apex.core.refine import make_refine
+from apex.core.reproduce import make_repro, post_repro
+from dflow.python import upload_packages
+upload_packages.append(__file__)
+
+class DecohesionEnergy(Property):
+ def __init__(self, parameter, inter_param=None):
+ '''
+ core parameter for make_confs[POSCAR]:
+ min_slab_size max_vacuum_size vacuum_size_step
+ miller_index
+ '''
+ parameter["reproduce"] = parameter.get("reproduce", False)
+ self.reprod = parameter["reproduce"]
+ if not self.reprod:
+ if not ("init_from_suffix" in parameter and "output_suffix" in parameter):
+ self.min_slab_size = parameter["min_slab_size"]
+ parameter["pert_xz"] = parameter.get("pert_xz", 0.01)
+ self.pert_xz = parameter["pert_xz"]
+ parameter["max_vacuum_size"] = parameter.get("max_vacuum_size", 15)
+ self.max_vacuum_size = parameter["max_vacuum_size"]
+ parameter["vacuum_size_step"]=parameter.get("vacuum_size_step", 1)
+ self.vacuum_size_step = parameter["vacuum_size_step"]
+ self.miller_index = tuple(parameter["miller_index"])
+ parameter["cal_type"] = parameter.get("cal_type", "relaxation")
+ default_cal_setting = {
+ "relax_pos": False,
+ "relax_shape": False,
+ "relax_vol": False,
+ }
+ else:
+ parameter["cal_type"] = "static"
+ self.cal_type = parameter["cal_type"]
+ default_cal_setting = {
+ "relax_pos": False,
+ "relax_shape": False,
+ "relax_vol": False,
+ }
+ parameter["init_from_suffix"] = parameter.get("init_from_suffix", "00")
+ self.init_from_suffix = parameter["init_from_suffix"]
+ self.cal_type = parameter["cal_type"]
+ parameter["cal_setting"] = parameter.get("cal_setting", default_cal_setting)
+ for key in default_cal_setting:
+ parameter["cal_setting"].setdefault(key, default_cal_setting[key])
+ self.cal_setting = parameter["cal_setting"]
+ self.parameter = parameter
+ self.inter_param = inter_param if inter_param != None else {"type": "vasp"}
+
+ def make_confs(self, path_to_work, path_to_equi, refine=False):
+ path_to_work = os.path.abspath(path_to_work)
+ if os.path.exists(path_to_work):
+ logging.warning("%s already exists" % path_to_work)
+ else:
+ os.makedirs(path_to_work)
+ path_to_equi = os.path.abspath(path_to_equi)
+
+ task_list = []
+ cwd = os.getcwd()
+
+ if self.reprod:
+ print("surface reproduce starts")
+ if "init_data_path" not in self.parameter:
+ raise RuntimeError("please provide the initial data path to reproduce")
+ init_data_path = os.path.abspath(self.parameter["init_data_path"])
+ task_list = make_repro(
+ self.inter_param,
+ init_data_path,
+ self.init_from_suffix,
+ path_to_work,
+ self.parameter.get("reprod_last_frame", True),
+ )
+
+ else:
+ if refine:
+ logging.info("surface refine starts")
+ task_list = make_refine(
+ self.parameter["init_from_suffix"],
+ self.parameter["output_suffix"],
+ path_to_work,
+ )
+ # record miller
+ init_from_path = re.sub(
+ self.parameter["output_suffix"][::-1],
+ self.parameter["init_from_suffix"][::-1],
+ path_to_work[::-1],
+ count=1,
+ )[::-1]
+ task_list_basename = list(map(os.path.basename, task_list))
+
+ for ii in task_list_basename:
+ init_from_task = os.path.join(init_from_path, ii)
+ output_task = os.path.join(path_to_work, ii)
+ os.chdir(output_task)
+ if os.path.isfile("decohesion_energy.json"):
+ os.remove("decohesion_energy.json")
+ if os.path.islink("decohesion_energy.json"):
+ os.remove("decohesion_energy.json")
+ os.symlink(
+ os.path.relpath(os.path.join(init_from_task, "decohesion_energy.json")),
+ "decohesion_energy.json",)
+ else:
+ if self.inter_param["type"] == "abacus":
+ CONTCAR = abacus_utils.final_stru(path_to_equi)
+ POSCAR = "STRU"
+ else:
+ # refine = false && reproduce = false && self.inter_param["type"]== "vasp"
+ CONTCAR = "CONTCAR"
+ POSCAR = "POSCAR"
+ equi_contcar = os.path.join(path_to_equi, CONTCAR)
+ if not os.path.exists(equi_contcar):
+ raise RuntimeError("please do relaxation first")
+
+ if self.inter_param["type"] == "abacus":
+ stru = dpdata.System(equi_contcar, fmt="stru")
+ stru.to("contcar", "CONTCAR.tmp")
+ ptypes = vasp_utils.get_poscar_types("CONTCAR.tmp")
+ ss = Structure.from_file("CONTCAR.tmp")
+ os.remove("CONTCAR.tmp")
+ else:
+ ptypes = vasp_utils.get_poscar_types(equi_contcar)
+ # element type 读取 vasp 第五行
+ ss = Structure.from_file(equi_contcar)
+
+ # gen POSCAR of Slab
+ all_slabs = []
+ vacuum = []
+ num = 0
+ while self.vacuum_size_step * num <= self.max_vacuum_size:
+ vacuum_size = self.vacuum_size_step * num
+ slabs = self.__gen_slab_pmg(ss, self.miller_index, self.min_slab_size, vacuum_size)
+ num = num + 1
+ all_slabs.append(slabs)
+ vacuum.append(vacuum_size)
+
+ os.chdir(path_to_work)
+ if os.path.exists(POSCAR):
+ os.remove(
+ POSCAR)
+ os.symlink(os.path.relpath(equi_contcar), POSCAR)
+ for ii in range(len(all_slabs)):
+ output_task = os.path.join(path_to_work, "task.%06d" % ii)
+ os.makedirs(output_task, exist_ok=True)
+ os.chdir(output_task)
+ for jj in [
+ "INCAR",
+ "POTCAR",
+ "POSCAR",
+ "conf.lmp",
+ "in.lammps",
+ "STRU",
+ ]:
+ if os.path.exists(jj):
+ os.remove(jj)
+ task_list.append(output_task)
+
+ logging.info(
+ "# %03d generate " % ii,
+ output_task,
+ " \t %d atoms" % len(all_slabs[ii].sites),
+ )
+
+ all_slabs[ii].to("POSCAR.tmp", "POSCAR")
+ vasp_utils.regulate_poscar("POSCAR.tmp", "POSCAR")
+ vasp_utils.sort_poscar("POSCAR", "POSCAR", ptypes)
+ vasp_utils.perturb_xz("POSCAR", "POSCAR", self.pert_xz)
+ if self.inter_param["type"] == "abacus":
+ abacus_utils.poscar2stru("POSCAR", self.inter_param, "STRU")
+ os.remove("POSCAR")
+ decohesion_energy = {"miller_index": self.miller_index, "vacuum_size":vacuum[ii]}
+ dumpfn(decohesion_energy, "decohesion_energy.json", indent=4)
+ os.chdir(cwd)
+ return task_list
+
+ def post_process(self, task_list):
+ pass
+
+ def task_type(self):
+ return self.parameter["type"]
+
+ def task_param(self):
+ return self.parameter
+
+ def _compute_lower(self, output_file, all_tasks, all_res) -> [dict, str]:
+ output_file = os.path.abspath(output_file)
+ res_data = {}
+ ptr_data = os.path.dirname(output_file) + "\n"
+ if not self.reprod:
+ '''
+ equi_path = os.path.abspath(
+ os.path.join(
+ os.path.dirname(output_file), "../relaxation/relax_task"
+ )
+ )
+ equi_result = loadfn(os.path.join(equi_path, "result.json"))
+ equi_epa = equi_result["energies"][-1] / np.sum(
+ equi_result["atom_numbs"]
+ )
+ '''
+ vacuum_size_step = loadfn(os.path.join(os.path.dirname(output_file), "param.json"))["vacuum_size_step"]
+ ptr_data += ("Miller Index: " + str(loadfn(os.path.join(os.path.dirname(output_file), "param.json"))["miller_index"]) + "\n")
+ ptr_data += "Vacuum_size(e-10 m):\tDecohesion_E(J/m^2) Decohesion_S(Pa)\n"
+ pre_task_result = loadfn(os.path.join(all_tasks[0],"result_task.json"))
+ pre_evac = 0
+ equi_evac = pre_task_result["energies"][-1]
+ for ii in all_tasks:
+ task_result = loadfn(os.path.join(ii, "result_task.json"))
+ AA = np.linalg.norm(
+ np.cross(task_result["cells"][0][0], task_result["cells"][0][1])
+ )
+ structure_dir = os.path.basename(ii)
+ Cf = 1.60217657e-16 / 1e-20 * 0.001
+ evac = (task_result["energies"][-1] - equi_evac) / AA * Cf
+ vacuum_size = loadfn(os.path.join(ii, "decohesion_energy.json"))["vacuum_size"]
+ stress = (evac - pre_evac) / vacuum_size_step * 1e10
+
+ ptr_data += "%-30s % 7.3f %10.3e \n" % (
+ str(vacuum_size) + "-" + structure_dir + ":",
+ evac,
+ stress,
+ )
+ res_data[str(vacuum_size) + "_" + structure_dir] = [
+ evac,
+ stress,
+ vacuum_size,
+ ]
+ pre_evac = evac
+
+ else:
+ if "init_data_path" not in self.parameter:
+ raise RuntimeError("please provide the initial data path to reproduce")
+ init_data_path = os.path.abspath(self.parameter["init_data_path"])
+ res_data, ptr_data = post_repro(
+ init_data_path,
+ self.parameter["init_from_suffix"],
+ all_tasks,
+ ptr_data,
+ self.parameter.get("reprod_last_frame", True),
+ )
+
+ with open(output_file, "w") as fp:
+ json.dump(res_data, fp, indent=4)
+
+ return res_data, ptr_data
+
+ def __gen_slab_pmg(self, structure: Structure,
+ plane_miller, slab_size, vacuum_size) -> Structure:
+
+ # Generate slab via Pymatgen
+ slabGen = SlabGenerator(structure, miller_index=plane_miller,
+ min_slab_size=slab_size, min_vacuum_size=0,
+ center_slab=True, in_unit_planes=False,
+ lll_reduce=True, reorient_lattice=False,
+ primitive=False)
+ slabs_pmg = slabGen.get_slabs(ftol=0.001)
+ slab = [s for s in slabs_pmg if s.miller_index == plane_miller][0]
+ # If a transform matrix is passed, reorient the slab
+ order = zip(slab.frac_coords, slab.species)
+ c_order = sorted(order, key=lambda x: x[0][2])
+ sorted_frac_coords = []
+ sorted_species = []
+ for (frac_coord, species) in c_order:
+ sorted_frac_coords.append(frac_coord)
+ sorted_species.append(species)
+ # add vacuum layer to the slab with height unit of angstrom
+ a, b, c = slab.lattice.matrix
+ slab_height = slab.lattice.matrix[2][2]
+ if slab_height >= 0:
+ self.is_flip = False
+ elong_scale = 1 + (vacuum_size / slab_height)
+ else:
+ self.is_flip = True
+ elong_scale = 1 + (-vacuum_size / slab_height)
+ new_lattice = [a, b, elong_scale * c]
+ new_frac_coords = []
+ for ii in range(len(sorted_frac_coords)):
+ coord = sorted_frac_coords[ii].copy()
+ coord[2] = coord[2] / elong_scale
+ new_frac_coords.append(coord)
+ # 建立 slab 结构
+
+ slab_new = Structure(lattice=np.matrix(new_lattice),
+ coords=new_frac_coords, species=sorted_species)
+
+ return slab_new
+
+
From b9804c6efb7f7e7687277c27ccd2c5f2785f9d15 Mon Sep 17 00:00:00 2001
From: XFShii <148210896+XFShii@users.noreply.github.com>
Date: Wed, 26 Jun 2024 16:29:28 +0800
Subject: [PATCH 3/5] Delete apex/reporter directory
---
apex/reporter/DashReportApp.py | 355 -----------------
apex/reporter/__init__.py | 0
apex/reporter/property_report.py | 621 -----------------------------
apex/reporter/relaxation_report.py | 76 ----
4 files changed, 1052 deletions(-)
delete mode 100644 apex/reporter/DashReportApp.py
delete mode 100644 apex/reporter/__init__.py
delete mode 100644 apex/reporter/property_report.py
delete mode 100644 apex/reporter/relaxation_report.py
diff --git a/apex/reporter/DashReportApp.py b/apex/reporter/DashReportApp.py
deleted file mode 100644
index 76fa746..0000000
--- a/apex/reporter/DashReportApp.py
+++ /dev/null
@@ -1,355 +0,0 @@
-import dash
-from dash import dcc, html, State
-from dash.dependencies import Input, Output
-import dash_bootstrap_components as dbc
-import plotly.graph_objects as go
-import webbrowser
-from threading import Timer
-from .relaxation_report import RelaxationReport
-from .property_report import *
-
-
-NO_GRAPH_LIST = ['relaxation']
-UI_FRONTSIZE = 18
-PLOT_FRONTSIZE = 18
-LINE_SIZE = 2
-MARKER_SIZE = 6
-REF_LINE_SIZE = 4
-REF_MARKER_SIZE = 9
-
-
-def return_prop_class(prop_type: str):
- if prop_type == 'eos':
- return EOSReport
- elif prop_type == 'elastic':
- return ElasticReport
- elif prop_type == 'surface':
- return SurfaceReport
- elif prop_type == 'interstitial':
- return InterstitialReport
- elif prop_type == 'vacancy':
- return VacancyReport
- elif prop_type == 'gamma':
- return GammaReport
- elif prop_type == 'phonon':
- return PhononReport
-
-
-def return_prop_type(prop: str):
- try:
- prop_type = prop.split('_')[0]
- except AttributeError:
- return None
- return prop_type
-
-
-def generate_test_datasets():
- datasets = {
- '/Users/zhuoyuan/labspace/ti-mo_test/Ti_test/DP_test': {
- 'confs/std-hcp': {
- 'result': {
- 'eos_00': {
- "14.743452666313036": -7.6612955,
- "15.610714587860862": -7.7632485,
- "16.477976509408688": -7.817405,
- "17.345238430956513": -7.8335905,
- "18.21250035250434": -7.8194775,
- "19.079762274052165": -7.7812295,
- }
- }
- }
- }
- }
- return datasets
-
-
-class DashReportApp:
- def __init__(self, datasets):
- dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
- self.datasets = datasets
- self.all_dimensions = set()
- self.all_datasets = set()
- self.app = dash.Dash(
- __name__,
- suppress_callback_exceptions=True,
- external_stylesheets=[dbc.themes.MATERIA, dbc_css]
- )
- #load_figure_template("materia")
- self.app.layout = self.generate_layout()
-
- """define callbacks"""
- self.app.callback(
- Output('graph', 'style'),
- [Input('props-dropdown', 'value'),
- Input('confs-radio', 'value')]
- )(self.update_graph_visibility)
-
- self.app.callback(
- Output('graph', 'figure'),
- [Input('props-dropdown', 'value'),
- Input('confs-radio', 'value')]
- )(self.update_graph)
-
- self.app.callback(
- Output('table', 'children'),
- [Input('props-dropdown', 'value'),
- Input('confs-radio', 'value')]
- )(self.update_table)
-
- self.app.callback(
- Output('props-dropdown', 'options'),
- [Input('confs-radio', 'value')]
- )(self.update_dropdown_options)
-
- @staticmethod
- def plotly_color_cycle():
- # https://plotly.com/python/discrete-color/
- colors = [
- '#636EFA', # blue
- '#EF553B', # red
- '#00CC96', # green
- '#AB63FA', # purple
- '#FFA15A', # orange
- '#19D3F3', # cyan
- '#FF6692', # pink
- '#B6E880', # lime
- '#FF97FF', # magenta
- '#FECB52', # yellow
- ]
- while True:
- for color in colors:
- yield color
-
- def generate_layout(self):
- for w in self.datasets.values():
- self.all_dimensions.update(w.keys())
- for dimension in w.values():
- self.all_datasets.update(dimension.keys())
-
- # find the first default combination of configuration and property exist
- default_dimension = None
- default_dataset = None
- for w_key, w in self.datasets.items():
- if not w:
- continue
- for d_key, d in w.items():
- if d:
- default_dimension = d_key
- default_dataset = next(iter(d.keys()))
- break
- if default_dataset:
- break
-
- layout = html.Div(
- [
- html.H1("APEX Results Visualization Report", style={'textAlign': 'center'}),
- html.Label('Configuration:', style={'font-weight': 'bold', "fontSize": UI_FRONTSIZE}),
- dcc.RadioItems(
- id='confs-radio',
- options=[{'label': name, 'value': name} for name in self.all_dimensions],
- value=default_dimension,
- style={"fontSize": UI_FRONTSIZE}
- ),
- html.Br(),
- html.Label('Property:', style={'font-weight': 'bold', "fontSize": UI_FRONTSIZE}),
- dcc.Dropdown(
- id='props-dropdown',
- options=[{'label': name, 'value': name} for name in self.all_datasets],
- value=default_dataset,
- style={"fontSize": UI_FRONTSIZE}
- ),
- html.Br(),
- dcc.Graph(id='graph', style={'display': 'block', "fontSize": UI_FRONTSIZE},
- className='graph-container'),
- html.Div(id='table')
- ],
- style={'margin': '0 auto', 'maxWidth': '900px'}
- )
- return layout
-
- def update_graph_visibility(self, selected_prop, selected_confs):
- prop_type = return_prop_type(selected_prop)
- valid_count = 0
- if prop_type not in NO_GRAPH_LIST:
- for w_dimension, dataset in self.datasets.items():
- try:
- _ = dataset[selected_confs][selected_prop]
- except KeyError:
- pass
- else:
- valid_count += 1
- if prop_type in NO_GRAPH_LIST or valid_count == 0:
- return {'display': 'none'}
- else:
- return {'display': 'block'}
-
- def update_dropdown_options(self, selected_confs):
- all_datasets = set()
- for w in self.datasets.values():
- if selected_confs in w:
- all_datasets.update(w[selected_confs].keys())
-
- return [{'label': name, 'value': name} for name in all_datasets]
-
- def update_graph(self, selected_prop, selected_confs):
- fig = go.Figure()
- prop_type = return_prop_type(selected_prop)
- color_generator = self.plotly_color_cycle()
- if prop_type not in NO_GRAPH_LIST:
- for w_dimension, dataset in self.datasets.items():
- try:
- data = dataset[selected_confs][selected_prop]['result']
- except KeyError:
- pass
- else:
- propCls = return_prop_class(prop_type)
- # trace_name = f"{w_dimension} - {selected_confs} - {selected_prop}"
- trace_name = w_dimension
- traces, layout = propCls.plotly_graph(
- data, trace_name,
- color=next(color_generator)
- )
- # set color and width of reference lines
- if prop_type != 'vacancy':
- for trace in iter(traces):
- if trace_name.split('/')[-1] in ['DFT', 'REF']:
- trace.update({'line': {'color': 'black', 'width': REF_LINE_SIZE},
- 'marker': {'color': 'black', 'size': REF_MARKER_SIZE}})
- else:
- trace.update({'line': {'width': LINE_SIZE}}, marker={'size': MARKER_SIZE})
- fig.add_traces(traces)
- fig.layout = layout
- fig.update_layout(
- font=dict(
- family="Arial, sans-serif",
- size=PLOT_FRONTSIZE,
- color="Black"
- ),
- plot_bgcolor='rgba(0, 0, 0, 0)',
- #plot_bgcolor='rgba(229, 229, 229, 100)',
- #paper_bgcolor='rgba(0, 0, 0, 0)',
- xaxis_title=dict(font=dict(size=PLOT_FRONTSIZE)),
- yaxis_title=dict(font=dict(size=PLOT_FRONTSIZE)),
- xaxis=dict(
- mirror=True,
- ticks='inside',
- tickwidth=2,
- showline=True,
- linewidth=2,
- linecolor='black',
- gridcolor='lightgrey',
- zerolinecolor='lightgrey',
- zerolinewidth=0.2
- ),
- yaxis=dict(
- mirror=True,
- ticks='inside',
- tickwidth=2,
- showline=True,
- linewidth=2,
- linecolor='black',
- gridcolor='lightgrey',
- zerolinecolor='lightgrey',
- zerolinewidth=0.2
- ),
- polar=dict(
- bgcolor='rgba(0, 0, 0, 0)',
- radialaxis=dict(
- visible=True,
- autorange=True,
- ticks='inside',
- tickwidth=2,
- showline=True,
- linewidth=2,
- linecolor='black',
- gridcolor='lightgrey',
- ),
- angularaxis=dict(
- visible=True,
- ticks='inside',
- tickwidth=2,
- showline=True,
- linewidth=2,
- linecolor='black',
- gridcolor='lightgrey',
- ),
- ),
- autotypenumbers='convert types'
- )
- return fig
-
- def update_table(self, selected_prop, selected_confs):
- table_index = 0
- tables = []
- prop_type = return_prop_type(selected_prop)
- if prop_type == 'relaxation':
- for w_dimension, dataset in self.datasets.items():
- table_title = html.H3(f"{w_dimension} - {selected_prop}")
- clip_id = f"clip-{table_index}"
- clipboard = dcc.Clipboard(id=clip_id, style={"fontSize": UI_FRONTSIZE})
- table = RelaxationReport.dash_table(dataset)
- table.id = f"table-{table_index}"
- tables.append(html.Div([table_title, clipboard, table],
- style={'width': '100%', 'display': 'inline-block'}))
- table_index += 1
- else:
- for w_dimension, dataset in self.datasets.items():
- try:
- data = dataset[selected_confs][selected_prop]['result']
- except KeyError:
- pass
- else:
- propCls = return_prop_class(prop_type)
- table_title = html.H3(
- f"{w_dimension} - {selected_confs} - {selected_prop}",
- style={"fontSize": UI_FRONTSIZE}
- )
- table, df = propCls.dash_table(data)
- table.id = f"table-{table_index}"
- # add strips to table
- table.style_data_conditional = [
- {'if': {'row_index': 'odd'},
- 'backgroundColor': 'rgb(248, 248, 248)'}
- ]
- # add clipboards
- clip_id = f"clip-{table_index}"
- clipboard = dcc.Clipboard(id=clip_id, style={"fontSize": UI_FRONTSIZE})
- tables.append(
- html.Div([table_title, clipboard, table],
- style={'width': '50%', 'display': 'inline-block'})
- )
- table_index += 1
-
- self._generate_dynamic_callbacks(table_index)
-
- return html.Div(
- tables, style={'display': 'flex', 'flex-wrap': 'wrap'}
- )
-
- @staticmethod
- def csv_copy(_, data):
- dff = pd.DataFrame(data)
- return dff.to_csv(index=False) # do not include row names
-
- def _generate_dynamic_callbacks(self, count):
- for index in range(count):
- self.app.callback(Output(f'clip-{index}', 'content'),
- [Input(f'clip-{index}', 'n_clicks'),
- State(f'table-{index}', 'data')])(self.csv_copy)
-
- def run(self, **kwargs):
- Timer(1.2, self.open_webpage).start()
- print('Dash server running... (See the report at http://127.0.0.1:8050/)')
- print('NOTE: If two Dash pages are automatically opened in your browser, you can close the first one.')
- print('NOTE: If the clipboard buttons do not function well, try to reload the page one time.')
- print('NOTE: Do not over-refresh the page as duplicate errors may occur. '
- 'If did, stop the server and re-execute the apex report command.')
- self.app.run(**kwargs)
-
- @staticmethod
- def open_webpage():
- webbrowser.open('http://127.0.0.1:8050/')
-
-
-if __name__ == "__main__":
- DashReportApp(datasets=generate_test_datasets()).run()
diff --git a/apex/reporter/__init__.py b/apex/reporter/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/apex/reporter/property_report.py b/apex/reporter/property_report.py
deleted file mode 100644
index a64a066..0000000
--- a/apex/reporter/property_report.py
+++ /dev/null
@@ -1,621 +0,0 @@
-import numpy as np
-from abc import ABC, abstractmethod
-import plotly.graph_objs as go
-from dash import dash_table
-import pandas as pd
-
-from apex.core.lib.utils import round_format, round_2d_format
-
-TABLE_WIDTH = '50%'
-TABLE_MIN_WIDTH = '95%'
-
-
-def random_color():
- r = np.random.randint(50, 200)
- g = np.random.randint(50, 200)
- b = np.random.randint(50, 200)
- return f'rgb({r}, {g}, {b})'
-
-
-class PropertyReport(ABC):
- @staticmethod
- @abstractmethod
- def plotly_graph(res_data: dict, name: str):
- """
- Plot plotly graph.
-
- Parameters
- ----------
- res_data : dict
- The dict storing the result of the props
- Returns:
- -------
- list[plotly.graph_objs]
- The list of plotly graph object
- plotly.graph_objs.layout
- the layout
- """
- pass
-
- @staticmethod
- @abstractmethod
- def dash_table(res_data: dict, decimal: int) -> [dash_table.DataTable, pd.DataFrame]:
- """
- Make Dash table.
-
- Parameters
- ----------
- res_data : dict
- The dict storing the result of the props
- Returns:
- -------
- dash_table.DataTable
- The dash table object
- pd.DataFrame
- """
- pass
-
-
-class EOSReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- vpa = []
- epa = []
- for k, v in res_data.items():
- vpa.append(k)
- epa.append(v)
- df = pd.DataFrame({
- "VpA(A^3)": vpa,
- "EpA(eV)": epa
- })
- trace = go.Scatter(
- name=name,
- x=df['VpA(A^3)'],
- y=df['EpA(eV)'],
- mode='lines+markers'
- )
- layout = go.Layout(
- title='Energy of State',
- xaxis=dict(
- title_text="VpA (A3)",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- ),
- yaxis=dict(
- title_text="EpA (eV)",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- )
- )
-
- return [trace], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- vpa = []
- epa = []
- for k, v in res_data.items():
- vpa.append(float(k))
- epa.append(float(v))
- df = pd.DataFrame({
- "VpA(A^3)": round_format(vpa, decimal),
- "EpA(eV)": round_format(epa, decimal)
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
-
-class ElasticReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- elastic_tensor = res_data['elastic_tensor']
- c11 = elastic_tensor[0][0]
- c12 = elastic_tensor[0][1]
- c13 = elastic_tensor[0][2]
- c22 = elastic_tensor[1][1]
- c23 = elastic_tensor[1][2]
- c33 = elastic_tensor[2][2]
- c44 = elastic_tensor[3][3]
- c55 = elastic_tensor[4][4]
- c66 = elastic_tensor[5][5]
- BV = res_data['BV']
- GV = res_data['GV']
- EV = res_data['EV']
- uV = res_data['uV']
-
- polar = go.Scatterpolar(
- name=name,
- r=[c11, c12, c13, c22, c23, c33,
- c44, c55, c66, BV, GV, EV, uV],
- theta=['C11', 'C12', 'C13', 'C22', 'C23', 'C33',
- 'C44', 'C55', 'C66', 'BV', 'GV', 'EV', 'uV'],
- fill='none'
- )
-
- layout = go.Layout(
- showlegend=True,
- title='Elastic Property'
- )
-
- return [polar], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- ph = '-'
- et = res_data['elastic_tensor']
- BV = res_data['BV']
- GV = res_data['GV']
- EV = res_data['EV']
- uV = res_data['uV']
- null_t = [' '] * 6
- BV_t = ['BV', BV, ph, ph, ph, ph]
- GV_t = ['GV', GV, ph, ph, ph, ph]
- EV_t = ['EV', EV, ph, ph, ph, ph]
- uV_t = ['uV', uV, ph, ph, ph, ph]
- table_tensor = [et[0], et[1], et[2], et[3], et[4], et[5],
- null_t, BV_t, GV_t, EV_t, uV_t]
-
- # round numbers in table
- rounded_tensor = round_2d_format(table_tensor, decimal)
-
- df = pd.DataFrame(
- rounded_tensor,
- columns=['Col 1', 'Col 2', 'Col 3', 'Col 4', 'Col 5', 'Col 6'],
- )
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left', 'width': '150px'}
- )
-
- return table, df
-
-
-class SurfaceReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- miller = []
- surf_e = []
- epa = []
- epa_equi = []
- for k, v in res_data.items():
- miller.append(k.split('_')[0])
- surf_e.append(float(v[0]))
- epa.append(float(v[1]))
- epa_equi.append(float(v[2]))
-
- # enclose polar plot
- surf_e.append(surf_e[0])
- miller.append(miller[0])
- polar = go.Scatterpolar(
- name=name,
- r=surf_e,
- theta=miller,
- fill='none'
- )
-
- layout = go.Layout(
- polar=dict(
- radialaxis=dict(
- visible=True,
- autorange=True
- )
- ),
- showlegend=True,
- title='Surface Forming Energy'
- )
-
- return [polar], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- miller = []
- surf_e = []
- epa = []
- epa_equi = []
- for k, v in res_data.items():
- miller.append(k.split('_')[0])
- surf_e.append(float(v[0]))
- epa.append(float(v[1]))
- epa_equi.append(float(v[2]))
- df = pd.DataFrame({
- "Miller Index": miller,
- "E_surface (J/m^2)": round_format(surf_e, decimal),
- "EpA (eV)": round_format(epa, decimal),
- "EpA_equi (eV)": round_format(epa_equi, decimal),
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
-
-class InterstitialReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- inter_struct = []
- inter_form_e = []
- struct_e = []
- equi_e = []
- for k, v in res_data.items():
- inter_struct.append(k.split('_')[1])
- inter_form_e.append(float(v[0]))
- struct_e.append(float(v[1]))
- equi_e.append(float(v[2]))
-
- # enclose polar plot
- inter_struct.append(inter_struct[0])
- inter_form_e.append(inter_form_e[0])
-
- polar = go.Scatterpolar(
- name=name,
- r=inter_form_e,
- theta=inter_struct,
- fill='none'
- )
-
- layout = go.Layout(
- polar=dict(
- radialaxis=dict(
- visible=True,
- autorange=True
- )
- ),
- showlegend=True,
- title='Interstitial Forming Energy'
- )
-
- return [polar], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- inter_struct = []
- inter_form_e = []
- struct_e = []
- equi_e = []
- for k, v in res_data.items():
- inter_struct.append(k.split('_')[1])
- inter_form_e.append(float(v[0]))
- struct_e.append(float(v[1]))
- equi_e.append(float(v[2]))
- df = pd.DataFrame({
- "Initial configuration ": inter_struct,
- "E_form (eV)": round_format(inter_form_e, decimal),
- "E_defect (eV)": round_format(struct_e, decimal),
- "E_equi (eV)": round_format(equi_e, decimal),
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
-
-class VacancyReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- v = list(res_data.values())[0]
- vac_form_e = float(v[0])
- struct_e = float(v[1])
- equi_e = float(v[2])
-
- bar = go.Bar(
- name=name,
- # x=[vac_form_e, struct_e, equi_e],
- # y=['E_form (eV)', 'E_defect (eV)', 'E_equi (eV)'],
- x=[vac_form_e],
- y=['E_form'],
- orientation='h'
- )
-
- layout = go.Layout(
- title='Vacancy Forming Energy',
- xaxis=dict(
- title_text="Vacancy Forming Energy (eV)",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- ),
- yaxis=dict(
- title_text="",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- ),
- ),
- showlegend=True
- )
-
- return [bar], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- vac_form_e = []
- struct_e = []
- equi_e = []
- for k, v in res_data.items():
- vac_form_e.append(float(v[0]))
- struct_e.append(float(v[1]))
- equi_e.append(float(v[2]))
- df = pd.DataFrame({
- "E_form (eV)": round_format(vac_form_e, decimal),
- "E_defect (eV)": round_format(struct_e, decimal),
- "E_equi (eV)": round_format(equi_e, decimal),
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
-
-class GammaReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- displ = []
- displ_length = []
- fault_en = []
- struct_en = []
- equi_en = []
- for k, v in res_data.items():
- displ.append(k)
- displ_length.append(v[0])
- fault_en.append(v[1])
- struct_en.append((v[2]))
- equi_en.append(v[3])
- df = pd.DataFrame({
- "displacement": displ,
- "displace_length": displ_length,
- "fault_en": fault_en
- })
- trace = go.Scatter(
- name=name,
- x=df['displacement'],
- # x=df['displace_length'],
- y=df['fault_en'],
- mode='lines+markers'
- )
- layout = go.Layout(
- title='Stacking Fault Energy (Gamma Line)',
- xaxis=dict(
- title_text="Slip Fraction",
- # title_text="Displace_Length (Å)",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- ),
- yaxis=dict(
- title_text='Fault Energy (J/m2)',
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- )
- )
-
- return [trace], layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- displ = []
- displ_length = []
- fault_en = []
- struct_en = []
- equi_en = []
- for k, v in res_data.items():
- displ.append(float(k))
- displ_length.append(v[0])
- fault_en.append(v[1])
- struct_en.append((v[2]))
- equi_en.append(v[3])
- df = pd.DataFrame({
- "Slip_frac": round_format(displ, decimal),
- "Slip_Length (Å)": round_format(displ_length, decimal),
- "E_Fault (J/m^2)": round_format(fault_en, decimal),
- "E_Slab (eV)": round_format(struct_en, decimal),
- "E_Equilib (eV)": round_format(equi_en, decimal)
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
-
-class PhononReport(PropertyReport):
- @staticmethod
- def plotly_graph(res_data: dict, name: str, **kwargs):
- bands = res_data['band']
-
- band_path_list = []
- for seg in bands[0]:
- seg_list = [k for k in seg.keys()]
- band_path_list.extend(seg_list)
- band_list = []
- for band in bands:
- seg_result_list = []
- for seg in band:
- seg_result = [v for v in seg.values()]
- seg_result_list.extend(seg_result)
- band_list.append(seg_result_list)
- pd_dict = {"Band Path": band_path_list}
- for ii in range(len(band_list)):
- pd_dict['Band %02d' % (ii + 1)] = band_list[ii]
- df = pd.DataFrame(pd_dict)
- traces = []
-
-
- for ii in range(len(band_list)):
- trace = go.Scatter(
- x=df['Band Path'],
- y=df['Band %02d' % (ii + 1)],
- name='Band %02d' % (ii + 1),
- legendgroup=name,
- legendgrouptitle_text=name,
- mode='lines',
- line=dict(color=kwargs["color"], width=1.5)
- )
- traces.append(trace)
-
- segment_value_list = res_data['segment']
- band_path_info = res_data['band_path']
- segment_value_iter = iter(segment_value_list)
-
- x_label_list = []
- connect_seg = False
- pre_k = None
- for seg in band_path_info:
- for point in seg:
- k = list(point.keys())[0]
- if connect_seg:
- new_k = f'{pre_k}/{k}'
- x_label_list[-1][0] = new_k
- connect_seg = False
- else:
- x_label_list.append([k, float(next(segment_value_iter))])
- pre_k = k
- connect_seg = True
-
- # label special points
- x_label_values_list = [x[1] for x in x_label_list]
- annotations = []
- shapes = []
-
- for x_label in x_label_list:
- # add label
- annotations.append(go.layout.Annotation(
- x=x_label[1],
- y=1.08,
- xref="x",
- yref="paper",
- text=x_label[0], # label text
- showarrow=False,
- yshift=0, # label position
- xanchor='center'
- ))
-
- # add special vertical line
- '''
- shapes.append({
- 'type': 'line',
- 'x0': x_label[1],
- 'y0': 0,
- 'x1': x_label[1],
- 'y1': 1,
- 'xref': 'x',
- 'yref': 'paper',
- 'line': {
- 'color': 'grey',
- 'width': 1,
- 'dash': 'dot',
- },
- })
- '''
-
- layout = go.Layout(
- title='Phonon Spectra',
- annotations=annotations,
- shapes=shapes,
- autotypenumbers='convert types',
- xaxis=dict(
- tickmode='array',
- tickvals=x_label_values_list,
- ticktext=[f'{float(val):.3f}' for val in x_label_values_list],
- title_text="Band Path",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- ),
- yaxis=dict(
- title_text="Frequency (THz)",
- title_font=dict(
- size=18,
- color="#7f7f7f"
- )
- )
- )
-
- return traces, layout
-
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- bands = res_data['band']
- band_path_list = []
- for seg in bands[0]:
- seg_list = [float(k) for k in seg.keys()]
- band_path_list.extend(seg_list)
- band_path_list.append(' ')
- band_path_list.pop()
-
- band_list = []
- for band in bands:
- seg_result_list = []
- for seg in band:
- seg_result = [v for v in seg.values()]
- seg_result_list.extend(seg_result)
- seg_result_list.append(' ')
- seg_result_list.pop()
- band_list.append(round_format(seg_result_list, decimal))
-
- pd_dict = {"Band Path": round_format(band_path_list, decimal)}
- for ii in range(len(band_list)):
- pd_dict['Band %02d' % (ii + 1)] = band_list[ii]
-
- df = pd.DataFrame(pd_dict)
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left'}
- )
-
- return table, df
-
diff --git a/apex/reporter/relaxation_report.py b/apex/reporter/relaxation_report.py
deleted file mode 100644
index 6e6147a..0000000
--- a/apex/reporter/relaxation_report.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import logging
-import json
-import numpy as np
-import plotly.graph_objs as go
-from dash import dash_table
-import pandas as pd
-from monty.json import MontyEncoder
-
-from apex.core.lib.utils import round_format, round_2d_format
-
-TABLE_WIDTH = '100%'
-TABLE_MIN_WIDTH = '100%'
-
-
-class RelaxationReport:
- @staticmethod
- def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
- conf_list = []
- equi_en = []
- cell_vec_a = []
- cell_vec_b = []
- cell_vec_c = []
- space_group_symbol = []
- space_group_number = []
- point_group_symbol = []
- crystal_system = []
- lattice_type = []
- for conf, dataset in res_data.items():
- try:
- class_data = dataset['relaxation']['result']
- struct_info = dataset['relaxation']['structure_info']
- except KeyError:
- pass
- else:
- data = json.dumps(class_data, cls=MontyEncoder, indent=4)
- data = json.loads(data)
- conf_list.append(conf)
- equi_en.append(data["data"]["energies"]["data"][-1])
- vec_a_length = np.linalg.norm(data["data"]["cells"]["data"][-1][0])
- vec_b_length = np.linalg.norm(data["data"]["cells"]["data"][-1][1])
- vec_c_length = np.linalg.norm(data["data"]["cells"]["data"][-1][2])
- cell_vec_a.append(vec_a_length)
- cell_vec_b.append(vec_b_length)
- cell_vec_c.append(vec_c_length)
- space_group_symbol.append(struct_info["space_group_symbol"])
- space_group_number.append(struct_info["space_group_number"])
- point_group_symbol.append(struct_info["point_group_symbol"])
- crystal_system.append(struct_info["crystal_system"])
- lattice_type.append(struct_info["lattice_type"])
-
- # round numbers in table
- # rounded_tensor = round_2d_format(data, decimal)
-
- df = pd.DataFrame({
- "Conf": conf_list,
- "Equi E (eV)": equi_en,
- "Cell Vector length a (Å)": cell_vec_a,
- "Cell Vector length b (Å)": cell_vec_b,
- "Cell Vector length c (Å)": cell_vec_c,
- "Space Group Symbol": space_group_symbol,
- "Space Group Number": space_group_number,
- "Point Group Symbol": point_group_symbol,
- "Crystal System": crystal_system,
- "Lattice Type": lattice_type,
- })
-
- table = dash_table.DataTable(
- data=df.to_dict('records'),
- columns=[{'name': i, 'id': i} for i in df.columns],
- style_table={'width': TABLE_WIDTH,
- 'minWidth': TABLE_MIN_WIDTH,
- 'overflowX': 'auto'},
- style_cell={'textAlign': 'left', 'width': '150px'}
- )
-
- return table
From 068a4106b6dfe7b8df122d0df1801c27805570b7 Mon Sep 17 00:00:00 2001
From: XFShii <148210896+XFShii@users.noreply.github.com>
Date: Wed, 26 Jun 2024 16:32:42 +0800
Subject: [PATCH 4/5] Add files via upload
---
apex/reporter/DashReportApp.py | 360 +++++++++++++++
apex/reporter/__init__.py | 0
apex/reporter/property_report.py | 702 +++++++++++++++++++++++++++++
apex/reporter/relaxation_report.py | 76 ++++
4 files changed, 1138 insertions(+)
create mode 100644 apex/reporter/DashReportApp.py
create mode 100644 apex/reporter/__init__.py
create mode 100644 apex/reporter/property_report.py
create mode 100644 apex/reporter/relaxation_report.py
diff --git a/apex/reporter/DashReportApp.py b/apex/reporter/DashReportApp.py
new file mode 100644
index 0000000..d77e593
--- /dev/null
+++ b/apex/reporter/DashReportApp.py
@@ -0,0 +1,360 @@
+import dash
+from dash import dcc, html, State
+from dash.dependencies import Input, Output
+import dash_bootstrap_components as dbc
+import plotly.graph_objects as go
+import webbrowser
+from threading import Timer
+from .relaxation_report import RelaxationReport
+from .property_report import *
+
+
+NO_GRAPH_LIST = ['relaxation']
+UI_FRONTSIZE = 18
+PLOT_FRONTSIZE = 18
+LINE_SIZE = 2
+MARKER_SIZE = 6
+REF_LINE_SIZE = 4
+REF_MARKER_SIZE = 9
+
+
+def return_prop_class(prop_type: str):
+ if prop_type == 'eos':
+ return EOSReport
+ elif prop_type == 'elastic':
+ return ElasticReport
+ elif prop_type == 'surface':
+ return SurfaceReport
+ elif prop_type == 'interstitial':
+ return InterstitialReport
+ elif prop_type == 'vacancy':
+ return VacancyReport
+ elif prop_type == 'gamma':
+ return GammaReport
+ elif prop_type == 'phonon':
+ return PhononReport
+ elif prop_type == 'DecohesionEnergy':
+ return DecohesionEnergyReport
+
+
+def return_prop_type(prop: str):
+ try:
+ prop_type = prop.split('_')[0]
+ except AttributeError:
+ return None
+ return prop_type
+
+
+def generate_test_datasets():
+ datasets = {
+ '/Users/zhuoyuan/labspace/ti-mo_test/Ti_test/DP_test': {
+ 'confs/std-hcp': {
+ 'result': {
+ 'eos_00': {
+ "14.743452666313036": -7.6612955,
+ "15.610714587860862": -7.7632485,
+ "16.477976509408688": -7.817405,
+ "17.345238430956513": -7.8335905,
+ "18.21250035250434": -7.8194775,
+ "19.079762274052165": -7.7812295,
+ }
+ }
+ }
+ }
+ }
+ return datasets
+
+
+class DashReportApp:
+ def __init__(self, datasets):
+ dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
+ self.datasets = datasets
+ self.all_dimensions = set()
+ self.all_datasets = set()
+ self.app = dash.Dash(
+ __name__,
+ suppress_callback_exceptions=True,
+ external_stylesheets=[dbc.themes.MATERIA, dbc_css]
+ )
+ #load_figure_template("materia")
+ self.app.layout = self.generate_layout()
+
+ """define callbacks"""
+ self.app.callback(
+ Output('graph', 'style'),
+ [Input('props-dropdown', 'value'),
+ Input('confs-radio', 'value')]
+ )(self.update_graph_visibility)
+
+ self.app.callback(
+ Output('graph', 'figure'),
+ [Input('props-dropdown', 'value'),
+ Input('confs-radio', 'value')]
+ )(self.update_graph)
+
+ self.app.callback(
+ Output('table', 'children'),
+ [Input('props-dropdown', 'value'),
+ Input('confs-radio', 'value')]
+ )(self.update_table)
+
+ self.app.callback(
+ Output('props-dropdown', 'options'),
+ [Input('confs-radio', 'value')]
+ )(self.update_dropdown_options)
+
+ @staticmethod
+ def plotly_color_cycle():
+ # https://plotly.com/python/discrete-color/
+ colors = [
+ '#636EFA', # blue
+ '#EF553B', # red
+ '#00CC96', # green
+ '#AB63FA', # purple
+ '#FFA15A', # orange
+ '#19D3F3', # cyan
+ '#FF6692', # pink
+ '#B6E880', # lime
+ '#FF97FF', # magenta
+ '#FECB52', # yellow
+ ]
+ while True:
+ for color in colors:
+ yield color
+
+ def generate_layout(self):
+ for w in self.datasets.values():
+ self.all_dimensions.update(w.keys())
+ for dimension in w.values():
+ self.all_datasets.update(dimension.keys())
+
+ # find the first default combination of configuration and property exist
+ default_dimension = None
+ default_dataset = None
+ for w_key, w in self.datasets.items():
+ if not w:
+ continue
+ for d_key, d in w.items():
+ if d:
+ default_dimension = d_key
+ default_dataset = next(iter(d.keys()))
+ break
+ if default_dataset:
+ break
+
+ radio_inline = False
+ if len(self.all_dimensions) > 10:
+ radio_inline = True
+ layout = html.Div(
+ [
+ html.H1("APEX Results Visualization Report", style={'textAlign': 'center'}),
+ html.Label('Configuration:', style={'font-weight': 'bold', "fontSize": UI_FRONTSIZE}),
+ dcc.RadioItems(
+ id='confs-radio',
+ options=[{'label': name, 'value': name} for name in self.all_dimensions],
+ value=default_dimension, inline=radio_inline,
+ style={"fontSize": UI_FRONTSIZE}
+ ),
+ html.Br(),
+ html.Label('Property:', style={'font-weight': 'bold', "fontSize": UI_FRONTSIZE}),
+ dcc.Dropdown(
+ id='props-dropdown',
+ options=[{'label': name, 'value': name} for name in self.all_datasets],
+ value=default_dataset,
+ style={"fontSize": UI_FRONTSIZE}
+ ),
+ html.Br(),
+ dcc.Graph(id='graph', style={'display': 'block', "fontSize": UI_FRONTSIZE},
+ className='graph-container'),
+ html.Div(id='table')
+ ],
+ style={'margin': '0 auto', 'maxWidth': '900px'}
+ )
+ return layout
+
+ def update_graph_visibility(self, selected_prop, selected_confs):
+ prop_type = return_prop_type(selected_prop)
+ valid_count = 0
+ if prop_type not in NO_GRAPH_LIST:
+ for w_dimension, dataset in self.datasets.items():
+ try:
+ _ = dataset[selected_confs][selected_prop]
+ except KeyError:
+ pass
+ else:
+ valid_count += 1
+ if prop_type in NO_GRAPH_LIST or valid_count == 0:
+ return {'display': 'none'}
+ else:
+ return {'display': 'block'}
+
+ def update_dropdown_options(self, selected_confs):
+ all_datasets = set()
+ for w in self.datasets.values():
+ if selected_confs in w:
+ all_datasets.update(w[selected_confs].keys())
+
+ return [{'label': name, 'value': name} for name in all_datasets]
+
+ def update_graph(self, selected_prop, selected_confs):
+ fig = go.Figure()
+ prop_type = return_prop_type(selected_prop)
+ color_generator = self.plotly_color_cycle()
+ if prop_type not in NO_GRAPH_LIST:
+ for w_dimension, dataset in self.datasets.items():
+ try:
+ data = dataset[selected_confs][selected_prop]['result']
+ except KeyError:
+ pass
+ else:
+ propCls = return_prop_class(prop_type)
+ # trace_name = f"{w_dimension} - {selected_confs} - {selected_prop}"
+ trace_name = w_dimension
+ traces, layout = propCls.plotly_graph(
+ data, trace_name,
+ color=next(color_generator)
+ )
+ # set color and width of reference lines
+ if prop_type != 'vacancy':
+ for trace in iter(traces):
+ if trace_name.split('/')[-1] in ['DFT', 'REF']:
+ trace.update({'line': {'color': 'black', 'width': REF_LINE_SIZE},
+ 'marker': {'color': 'black', 'size': REF_MARKER_SIZE}})
+ else:
+ trace.update({'line': {'width': LINE_SIZE}}, marker={'size': MARKER_SIZE})
+ fig.add_traces(traces)
+ fig.layout = layout
+ fig.update_layout(
+ font=dict(
+ family="Arial, sans-serif",
+ size=PLOT_FRONTSIZE,
+ color="Black"
+ ),
+ plot_bgcolor='rgba(0, 0, 0, 0)',
+ #plot_bgcolor='rgba(229, 229, 229, 100)',
+ #paper_bgcolor='rgba(0, 0, 0, 0)',
+ xaxis_title=dict(font=dict(size=PLOT_FRONTSIZE)),
+ yaxis_title=dict(font=dict(size=PLOT_FRONTSIZE)),
+ xaxis=dict(
+ mirror=True,
+ ticks='inside',
+ tickwidth=2,
+ showline=True,
+ linewidth=2,
+ linecolor='black',
+ gridcolor='lightgrey',
+ zerolinecolor='lightgrey',
+ zerolinewidth=0.2
+ ),
+ yaxis=dict(
+ mirror=True,
+ ticks='inside',
+ tickwidth=2,
+ showline=True,
+ linewidth=2,
+ linecolor='black',
+ gridcolor='lightgrey',
+ zerolinecolor='lightgrey',
+ zerolinewidth=0.2
+ ),
+ polar=dict(
+ bgcolor='rgba(0, 0, 0, 0)',
+ radialaxis=dict(
+ visible=True,
+ autorange=True,
+ ticks='inside',
+ tickwidth=2,
+ showline=True,
+ linewidth=2,
+ linecolor='black',
+ gridcolor='lightgrey',
+ ),
+ angularaxis=dict(
+ visible=True,
+ ticks='inside',
+ tickwidth=2,
+ showline=True,
+ linewidth=2,
+ linecolor='black',
+ gridcolor='lightgrey',
+ ),
+ ),
+ autotypenumbers='convert types'
+ )
+ return fig
+
+ def update_table(self, selected_prop, selected_confs):
+ table_index = 0
+ tables = []
+ prop_type = return_prop_type(selected_prop)
+ if prop_type == 'relaxation':
+ for w_dimension, dataset in self.datasets.items():
+ table_title = html.H3(f"{w_dimension} - {selected_prop}")
+ clip_id = f"clip-{table_index}"
+ clipboard = dcc.Clipboard(id=clip_id, style={"fontSize": UI_FRONTSIZE})
+ table = RelaxationReport.dash_table(dataset)
+ table.id = f"table-{table_index}"
+ tables.append(html.Div([table_title, clipboard, table],
+ style={'width': '100%', 'display': 'inline-block'}))
+ table_index += 1
+ else:
+ for w_dimension, dataset in self.datasets.items():
+ try:
+ data = dataset[selected_confs][selected_prop]['result']
+ except KeyError:
+ pass
+ else:
+ propCls = return_prop_class(prop_type)
+ table_title = html.H3(
+ f"{w_dimension} - {selected_confs} - {selected_prop}",
+ style={"fontSize": UI_FRONTSIZE}
+ )
+ table, df = propCls.dash_table(data)
+ table.id = f"table-{table_index}"
+ # add strips to table
+ table.style_data_conditional = [
+ {'if': {'row_index': 'odd'},
+ 'backgroundColor': 'rgb(248, 248, 248)'}
+ ]
+ # add clipboards
+ clip_id = f"clip-{table_index}"
+ clipboard = dcc.Clipboard(id=clip_id, style={"fontSize": UI_FRONTSIZE})
+ tables.append(
+ html.Div([table_title, clipboard, table],
+ style={'width': '50%', 'display': 'inline-block'})
+ )
+ table_index += 1
+
+ self._generate_dynamic_callbacks(table_index)
+
+ return html.Div(
+ tables, style={'display': 'flex', 'flex-wrap': 'wrap'}
+ )
+
+ @staticmethod
+ def csv_copy(_, data):
+ dff = pd.DataFrame(data)
+ return dff.to_csv(index=False) # do not include row names
+
+ def _generate_dynamic_callbacks(self, count):
+ for index in range(count):
+ self.app.callback(Output(f'clip-{index}', 'content'),
+ [Input(f'clip-{index}', 'n_clicks'),
+ State(f'table-{index}', 'data')])(self.csv_copy)
+
+ def run(self, **kwargs):
+ Timer(1.2, self.open_webpage).start()
+ print('Dash server running... (See the report at http://127.0.0.1:8050/)')
+ print('NOTE: If two Dash pages are automatically opened in your browser, you can close the first one.')
+ print('NOTE: If the clipboard buttons do not function well, try to reload the page one time.')
+ print('NOTE: Do not over-refresh the page as duplicate errors may occur. '
+ 'If did, stop the server and re-execute the apex report command.')
+ self.app.run(**kwargs)
+
+ @staticmethod
+ def open_webpage():
+ webbrowser.open('http://127.0.0.1:8050/')
+
+
+if __name__ == "__main__":
+ DashReportApp(datasets=generate_test_datasets()).run()
diff --git a/apex/reporter/__init__.py b/apex/reporter/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/apex/reporter/property_report.py b/apex/reporter/property_report.py
new file mode 100644
index 0000000..9c4d228
--- /dev/null
+++ b/apex/reporter/property_report.py
@@ -0,0 +1,702 @@
+import numpy as np
+from abc import ABC, abstractmethod
+import plotly.graph_objs as go
+from dash import dash_table
+import pandas as pd
+
+from apex.core.lib.utils import round_format, round_2d_format
+
+TABLE_WIDTH = '50%'
+TABLE_MIN_WIDTH = '95%'
+
+
+def random_color():
+ r = np.random.randint(50, 200)
+ g = np.random.randint(50, 200)
+ b = np.random.randint(50, 200)
+ return f'rgb({r}, {g}, {b})'
+
+
+class PropertyReport(ABC):
+ @staticmethod
+ @abstractmethod
+ def plotly_graph(res_data: dict, name: str):
+ """
+ Plot plotly graph.
+
+ Parameters
+ ----------
+ res_data : dict
+ The dict storing the result of the props
+ Returns:
+ -------
+ list[plotly.graph_objs]
+ The list of plotly graph object
+ plotly.graph_objs.layout
+ the layout
+ """
+ pass
+
+ @staticmethod
+ @abstractmethod
+ def dash_table(res_data: dict, decimal: int) -> [dash_table.DataTable, pd.DataFrame]:
+ """
+ Make Dash table.
+
+ Parameters
+ ----------
+ res_data : dict
+ The dict storing the result of the props
+ Returns:
+ -------
+ dash_table.DataTable
+ The dash table object
+ pd.DataFrame
+ """
+ pass
+
+
+class EOSReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ vpa = []
+ epa = []
+ for k, v in res_data.items():
+ vpa.append(k)
+ epa.append(v)
+ df = pd.DataFrame({
+ "VpA(A^3)": vpa,
+ "EpA(eV)": epa
+ })
+ trace = go.Scatter(
+ name=name,
+ x=df['VpA(A^3)'],
+ y=df['EpA(eV)'],
+ mode='lines+markers'
+ )
+ layout = go.Layout(
+ title='Energy of State',
+ xaxis=dict(
+ title_text="VpA (A3)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis=dict(
+ title_text="EpA (eV)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ )
+ )
+
+ return [trace], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ vpa = []
+ epa = []
+ for k, v in res_data.items():
+ vpa.append(float(k))
+ epa.append(float(v))
+ df = pd.DataFrame({
+ "VpA(A^3)": round_format(vpa, decimal),
+ "EpA(eV)": round_format(epa, decimal)
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
+class DecohesionEnergyReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ decohesion_e = [values[0] for values in res_data.values()]
+ stress = [values[1] for values in res_data.values()]
+ vacuum_size = [values[2] for values in res_data.values()]
+ vacuum_size = [str(item) for item in vacuum_size]
+ df = pd.DataFrame({
+ "separation distance (A)": vacuum_size,
+ "Decohesion energy (J/m^2)": decohesion_e,
+ "Decohesion stress (GPa)": [s / 1e9 for s in stress],
+ })
+ trace_E = go.Scatter(
+ name=f"{name} Decohesion Energy",
+ x=df['separation distance (A)'],
+ y=df['Decohesion energy (J/m^2)'],
+ mode='lines+markers',
+ yaxis='y1'
+ )
+
+ trace_S = go.Scatter(
+ name=f"{name} Decohesion Stress",
+ x=df['separation distance (A)'],
+ y=df['Decohesion stress (GPa)'],
+ mode='lines+markers',
+ yaxis='y2'
+ )
+ layout = go.Layout(
+ title=dict(
+ text='Decohesion Energy and Stress',
+ x=0.5, # 标题居中
+ xanchor='center'
+ ),
+ xaxis=dict(
+ title_text="separation distance (A)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis=dict(
+ title="Decohesion energy (J/m^2)",
+ titlefont=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis2=dict(
+ title="Decohesion stress (GPa)",
+ titlefont=dict(
+ size=18,
+ color="#7f7f7f"
+ ),
+ overlaying='y',
+ side='right'
+ )
+ )
+ trace = [trace_E, trace_S]
+ return trace, layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ decohesion_e = [values[0] for values in res_data.values()]
+ stress = [values[1] for values in res_data.values()]
+ vacuum_size = [values[2] for values in res_data.values()]
+ vacuum_size = [str(item) for item in vacuum_size]
+ df = pd.DataFrame({
+ "separation distance (A)": vacuum_size,
+ "Decohesion energy (J/m^2)": round_format(decohesion_e, decimal),
+ "Decohesion stress (GPa)": round_format([s / 1e9 for s in stress], decimal),
+ })
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+ return table, df
+
+
+class ElasticReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ elastic_tensor = res_data['elastic_tensor']
+ c11 = elastic_tensor[0][0]
+ c12 = elastic_tensor[0][1]
+ c13 = elastic_tensor[0][2]
+ c22 = elastic_tensor[1][1]
+ c23 = elastic_tensor[1][2]
+ c33 = elastic_tensor[2][2]
+ c44 = elastic_tensor[3][3]
+ c55 = elastic_tensor[4][4]
+ c66 = elastic_tensor[5][5]
+ BV = res_data['BV']
+ GV = res_data['GV']
+ EV = res_data['EV']
+ uV = res_data['uV']
+
+ polar = go.Scatterpolar(
+ name=name,
+ r=[c11, c12, c13, c22, c23, c33,
+ c44, c55, c66, BV, GV, EV, uV],
+ theta=['C11', 'C12', 'C13', 'C22', 'C23', 'C33',
+ 'C44', 'C55', 'C66', 'BV', 'GV', 'EV', 'uV'],
+ fill='none'
+ )
+
+ layout = go.Layout(
+ showlegend=True,
+ title='Elastic Property'
+ )
+
+ return [polar], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ ph = '-'
+ et = res_data['elastic_tensor']
+ BV = res_data['BV']
+ GV = res_data['GV']
+ EV = res_data['EV']
+ uV = res_data['uV']
+ null_t = [' '] * 6
+ BV_t = ['BV', BV, ph, ph, ph, ph]
+ GV_t = ['GV', GV, ph, ph, ph, ph]
+ EV_t = ['EV', EV, ph, ph, ph, ph]
+ uV_t = ['uV', uV, ph, ph, ph, ph]
+ table_tensor = [et[0], et[1], et[2], et[3], et[4], et[5],
+ null_t, BV_t, GV_t, EV_t, uV_t]
+
+ # round numbers in table
+ rounded_tensor = round_2d_format(table_tensor, decimal)
+
+ df = pd.DataFrame(
+ rounded_tensor,
+ columns=['Col 1', 'Col 2', 'Col 3', 'Col 4', 'Col 5', 'Col 6'],
+ )
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left', 'width': '150px'}
+ )
+
+ return table, df
+
+
+class SurfaceReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ miller = []
+ surf_e = []
+ epa = []
+ epa_equi = []
+ for k, v in res_data.items():
+ miller.append(k.split('_')[0])
+ surf_e.append(float(v[0]))
+ epa.append(float(v[1]))
+ epa_equi.append(float(v[2]))
+
+ # enclose polar plot
+ surf_e.append(surf_e[0])
+ miller.append(miller[0])
+ polar = go.Scatterpolar(
+ name=name,
+ r=surf_e,
+ theta=miller,
+ fill='none'
+ )
+
+ layout = go.Layout(
+ polar=dict(
+ radialaxis=dict(
+ visible=True,
+ autorange=True
+ )
+ ),
+ showlegend=True,
+ title='Surface Forming Energy'
+ )
+
+ return [polar], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ miller = []
+ surf_e = []
+ epa = []
+ epa_equi = []
+ for k, v in res_data.items():
+ miller.append(k.split('_')[0])
+ surf_e.append(float(v[0]))
+ epa.append(float(v[1]))
+ epa_equi.append(float(v[2]))
+ df = pd.DataFrame({
+ "Miller Index": miller,
+ "E_surface (J/m^2)": round_format(surf_e, decimal),
+ "EpA (eV)": round_format(epa, decimal),
+ "EpA_equi (eV)": round_format(epa_equi, decimal),
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
+
+class InterstitialReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ inter_struct = []
+ inter_form_e = []
+ struct_e = []
+ equi_e = []
+ for k, v in res_data.items():
+ inter_struct.append(k.split('_')[1])
+ inter_form_e.append(float(v[0]))
+ struct_e.append(float(v[1]))
+ equi_e.append(float(v[2]))
+
+ # enclose polar plot
+ inter_struct.append(inter_struct[0])
+ inter_form_e.append(inter_form_e[0])
+
+ polar = go.Scatterpolar(
+ name=name,
+ r=inter_form_e,
+ theta=inter_struct,
+ fill='none'
+ )
+
+ layout = go.Layout(
+ polar=dict(
+ radialaxis=dict(
+ visible=True,
+ autorange=True
+ )
+ ),
+ showlegend=True,
+ title='Interstitial Forming Energy'
+ )
+
+ return [polar], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ inter_struct = []
+ inter_form_e = []
+ struct_e = []
+ equi_e = []
+ for k, v in res_data.items():
+ inter_struct.append(k.split('_')[1])
+ inter_form_e.append(float(v[0]))
+ struct_e.append(float(v[1]))
+ equi_e.append(float(v[2]))
+ df = pd.DataFrame({
+ "Initial configuration ": inter_struct,
+ "E_form (eV)": round_format(inter_form_e, decimal),
+ "E_defect (eV)": round_format(struct_e, decimal),
+ "E_equi (eV)": round_format(equi_e, decimal),
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
+
+class VacancyReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ v = list(res_data.values())[0]
+ vac_form_e = float(v[0])
+ struct_e = float(v[1])
+ equi_e = float(v[2])
+
+ bar = go.Bar(
+ name=name,
+ # x=[vac_form_e, struct_e, equi_e],
+ # y=['E_form (eV)', 'E_defect (eV)', 'E_equi (eV)'],
+ x=[vac_form_e],
+ y=['E_form'],
+ orientation='h'
+ )
+
+ layout = go.Layout(
+ title='Vacancy Forming Energy',
+ xaxis=dict(
+ title_text="Vacancy Forming Energy (eV)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis=dict(
+ title_text="",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ ),
+ ),
+ showlegend=True
+ )
+
+ return [bar], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ vac_form_e = []
+ struct_e = []
+ equi_e = []
+ for k, v in res_data.items():
+ vac_form_e.append(float(v[0]))
+ struct_e.append(float(v[1]))
+ equi_e.append(float(v[2]))
+ df = pd.DataFrame({
+ "E_form (eV)": round_format(vac_form_e, decimal),
+ "E_defect (eV)": round_format(struct_e, decimal),
+ "E_equi (eV)": round_format(equi_e, decimal),
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
+
+class GammaReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ displ = []
+ displ_length = []
+ fault_en = []
+ struct_en = []
+ equi_en = []
+ for k, v in res_data.items():
+ displ.append(k)
+ displ_length.append(v[0])
+ fault_en.append(v[1])
+ struct_en.append((v[2]))
+ equi_en.append(v[3])
+ df = pd.DataFrame({
+ "displacement": displ,
+ "displace_length": displ_length,
+ "fault_en": fault_en
+ })
+ trace = go.Scatter(
+ name=name,
+ x=df['displacement'],
+ # x=df['displace_length'],
+ y=df['fault_en'],
+ mode='lines+markers'
+ )
+ layout = go.Layout(
+ title='Stacking Fault Energy (Gamma Line)',
+ xaxis=dict(
+ title_text="Slip Fraction",
+ # title_text="Displace_Length (Å)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis=dict(
+ title_text='Fault Energy (J/m2)',
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ )
+ )
+
+ return [trace], layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ displ = []
+ displ_length = []
+ fault_en = []
+ struct_en = []
+ equi_en = []
+ for k, v in res_data.items():
+ displ.append(float(k))
+ displ_length.append(v[0])
+ fault_en.append(v[1])
+ struct_en.append((v[2]))
+ equi_en.append(v[3])
+ df = pd.DataFrame({
+ "Slip_frac": round_format(displ, decimal),
+ "Slip_Length (Å)": round_format(displ_length, decimal),
+ "E_Fault (J/m^2)": round_format(fault_en, decimal),
+ "E_Slab (eV)": round_format(struct_en, decimal),
+ "E_Equilib (eV)": round_format(equi_en, decimal)
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
+
+class PhononReport(PropertyReport):
+ @staticmethod
+ def plotly_graph(res_data: dict, name: str, **kwargs):
+ bands = res_data['band']
+
+ band_path_list = []
+ for seg in bands[0]:
+ seg_list = [k for k in seg.keys()]
+ band_path_list.extend(seg_list)
+ band_list = []
+ for band in bands:
+ seg_result_list = []
+ for seg in band:
+ seg_result = [v for v in seg.values()]
+ seg_result_list.extend(seg_result)
+ band_list.append(seg_result_list)
+ pd_dict = {"Band Path": band_path_list}
+ for ii in range(len(band_list)):
+ pd_dict['Band %02d' % (ii + 1)] = band_list[ii]
+ df = pd.DataFrame(pd_dict)
+ traces = []
+
+
+ for ii in range(len(band_list)):
+ trace = go.Scatter(
+ x=df['Band Path'],
+ y=df['Band %02d' % (ii + 1)],
+ name='Band %02d' % (ii + 1),
+ legendgroup=name,
+ legendgrouptitle_text=name,
+ mode='lines',
+ line=dict(color=kwargs["color"], width=1.5)
+ )
+ traces.append(trace)
+
+ segment_value_list = res_data['segment']
+ band_path_info = res_data['band_path']
+ segment_value_iter = iter(segment_value_list)
+
+ x_label_list = []
+ connect_seg = False
+ pre_k = None
+ for seg in band_path_info:
+ for point in seg:
+ k = list(point.keys())[0]
+ if connect_seg:
+ new_k = f'{pre_k}/{k}'
+ x_label_list[-1][0] = new_k
+ connect_seg = False
+ else:
+ x_label_list.append([k, float(next(segment_value_iter))])
+ pre_k = k
+ connect_seg = True
+
+ # label special points
+ x_label_values_list = [x[1] for x in x_label_list]
+ annotations = []
+ shapes = []
+
+ for x_label in x_label_list:
+ # add label
+ annotations.append(go.layout.Annotation(
+ x=x_label[1],
+ y=1.08,
+ xref="x",
+ yref="paper",
+ text=x_label[0], # label text
+ showarrow=False,
+ yshift=0, # label position
+ xanchor='center'
+ ))
+
+ # add special vertical line
+ '''
+ shapes.append({
+ 'type': 'line',
+ 'x0': x_label[1],
+ 'y0': 0,
+ 'x1': x_label[1],
+ 'y1': 1,
+ 'xref': 'x',
+ 'yref': 'paper',
+ 'line': {
+ 'color': 'grey',
+ 'width': 1,
+ 'dash': 'dot',
+ },
+ })
+ '''
+
+ layout = go.Layout(
+ title='Phonon Spectra',
+ annotations=annotations,
+ shapes=shapes,
+ autotypenumbers='convert types',
+ xaxis=dict(
+ tickmode='array',
+ tickvals=x_label_values_list,
+ ticktext=[f'{float(val):.3f}' for val in x_label_values_list],
+ title_text="Band Path",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ ),
+ yaxis=dict(
+ title_text="Frequency (THz)",
+ title_font=dict(
+ size=18,
+ color="#7f7f7f"
+ )
+ )
+ )
+
+ return traces, layout
+
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ bands = res_data['band']
+ band_path_list = []
+ for seg in bands[0]:
+ seg_list = [float(k) for k in seg.keys()]
+ band_path_list.extend(seg_list)
+ band_path_list.append(' ')
+ band_path_list.pop()
+
+ band_list = []
+ for band in bands:
+ seg_result_list = []
+ for seg in band:
+ seg_result = [v for v in seg.values()]
+ seg_result_list.extend(seg_result)
+ seg_result_list.append(' ')
+ seg_result_list.pop()
+ band_list.append(round_format(seg_result_list, decimal))
+
+ pd_dict = {"Band Path": round_format(band_path_list, decimal)}
+ for ii in range(len(band_list)):
+ pd_dict['Band %02d' % (ii + 1)] = band_list[ii]
+
+ df = pd.DataFrame(pd_dict)
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left'}
+ )
+
+ return table, df
+
diff --git a/apex/reporter/relaxation_report.py b/apex/reporter/relaxation_report.py
new file mode 100644
index 0000000..6e6147a
--- /dev/null
+++ b/apex/reporter/relaxation_report.py
@@ -0,0 +1,76 @@
+import logging
+import json
+import numpy as np
+import plotly.graph_objs as go
+from dash import dash_table
+import pandas as pd
+from monty.json import MontyEncoder
+
+from apex.core.lib.utils import round_format, round_2d_format
+
+TABLE_WIDTH = '100%'
+TABLE_MIN_WIDTH = '100%'
+
+
+class RelaxationReport:
+ @staticmethod
+ def dash_table(res_data: dict, decimal: int = 3, **kwargs) -> dash_table.DataTable:
+ conf_list = []
+ equi_en = []
+ cell_vec_a = []
+ cell_vec_b = []
+ cell_vec_c = []
+ space_group_symbol = []
+ space_group_number = []
+ point_group_symbol = []
+ crystal_system = []
+ lattice_type = []
+ for conf, dataset in res_data.items():
+ try:
+ class_data = dataset['relaxation']['result']
+ struct_info = dataset['relaxation']['structure_info']
+ except KeyError:
+ pass
+ else:
+ data = json.dumps(class_data, cls=MontyEncoder, indent=4)
+ data = json.loads(data)
+ conf_list.append(conf)
+ equi_en.append(data["data"]["energies"]["data"][-1])
+ vec_a_length = np.linalg.norm(data["data"]["cells"]["data"][-1][0])
+ vec_b_length = np.linalg.norm(data["data"]["cells"]["data"][-1][1])
+ vec_c_length = np.linalg.norm(data["data"]["cells"]["data"][-1][2])
+ cell_vec_a.append(vec_a_length)
+ cell_vec_b.append(vec_b_length)
+ cell_vec_c.append(vec_c_length)
+ space_group_symbol.append(struct_info["space_group_symbol"])
+ space_group_number.append(struct_info["space_group_number"])
+ point_group_symbol.append(struct_info["point_group_symbol"])
+ crystal_system.append(struct_info["crystal_system"])
+ lattice_type.append(struct_info["lattice_type"])
+
+ # round numbers in table
+ # rounded_tensor = round_2d_format(data, decimal)
+
+ df = pd.DataFrame({
+ "Conf": conf_list,
+ "Equi E (eV)": equi_en,
+ "Cell Vector length a (Å)": cell_vec_a,
+ "Cell Vector length b (Å)": cell_vec_b,
+ "Cell Vector length c (Å)": cell_vec_c,
+ "Space Group Symbol": space_group_symbol,
+ "Space Group Number": space_group_number,
+ "Point Group Symbol": point_group_symbol,
+ "Crystal System": crystal_system,
+ "Lattice Type": lattice_type,
+ })
+
+ table = dash_table.DataTable(
+ data=df.to_dict('records'),
+ columns=[{'name': i, 'id': i} for i in df.columns],
+ style_table={'width': TABLE_WIDTH,
+ 'minWidth': TABLE_MIN_WIDTH,
+ 'overflowX': 'auto'},
+ style_cell={'textAlign': 'left', 'width': '150px'}
+ )
+
+ return table
From 73a7887f81261c96e9bdc6b72fb3b1f5fe722849 Mon Sep 17 00:00:00 2001
From: XFShii <148210896+XFShii@users.noreply.github.com>
Date: Thu, 27 Jun 2024 07:50:46 +0800
Subject: [PATCH 5/5] Update setup.py
---
setup.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/setup.py b/setup.py
index 9cb7cad..0020dd5 100644
--- a/setup.py
+++ b/setup.py
@@ -14,6 +14,7 @@
url="https://github.com/deepmodeling/APEX.git",
packages=setuptools.find_packages(),
install_requires=[
+ "numpy==1.26.4",
"pydflow>=1.7.83",
"pymatgen>=2023.8.10",
'pymatgen-analysis-defects>=2023.8.22',