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