diff --git a/src/aiidalab_qe/app/result/components/summary/model.py b/src/aiidalab_qe/app/result/components/summary/model.py index 11d9ee9f5..2f9d268ef 100644 --- a/src/aiidalab_qe/app/result/components/summary/model.py +++ b/src/aiidalab_qe/app/result/components/summary/model.py @@ -1,5 +1,8 @@ from __future__ import annotations +import json +from pathlib import Path + import traitlets as tl from importlib_resources import files from jinja2 import Environment @@ -64,33 +67,25 @@ def generate_report_html(self): """Read from the builder parameters and generate a html for reporting the inputs for the `QeAppWorkChain`. """ - - def _fmt_yes_no(truthy): - return "yes" if truthy else "no" - env = Environment() - env.filters.update( - { - "fmt_yes_no": _fmt_yes_no, - } - ) - template = ( - files(templates) - .joinpath(f"workflow_{DEFAULT['summary_format']}_summary.jinja") - .read_text() - ) - style = files(styles).joinpath("style.css").read_text() + template = files(templates).joinpath("workflow_summary.jinja").read_text() + summary_format = DEFAULT["summary_format"] + style = files(styles).joinpath(f"summary/{summary_format}.css").read_text() parameters = self._generate_report_parameters() report = {key: value for key, value in parameters.items() if value is not None} - - return env.from_string(template).render(style=style, **report) + schema = json.load(Path(__file__).parent.joinpath("schema.json").open()) + return env.from_string(template).render( + style=style, + report=report, + schema=schema, + format=summary_format, + ) def generate_report_text(self, report_dict): """Generate a text for reporting the inputs for the `QeAppWorkChain` :param report_dict: dictionary generated by the `generate_report_dict` function. """ - report_string = ( "All calculations are performed within the density-functional " "theory formalism as implemented in the Quantum ESPRESSO code. " @@ -168,122 +163,130 @@ def _generate_report_parameters(self): # drop support for old ui parameters if "workchain" not in ui_parameters: return {} + initial_structure = qeapp_wc.inputs.structure + basic = ui_parameters["workchain"] + advanced = ui_parameters["advanced"] + ctime = qeapp_wc.ctime + mtime = qeapp_wc.mtime + report = { - "pk": qeapp_wc.pk, - "uuid": str(qeapp_wc.uuid), - "label": qeapp_wc.label, - "description": qeapp_wc.description, - "creation_time": format_time(qeapp_wc.ctime), - "creation_time_relative": relative_time(qeapp_wc.ctime), - "modification_time": format_time(qeapp_wc.mtime), - "modification_time_relative": relative_time(qeapp_wc.mtime), - "structure_pk": initial_structure.pk, - "structure_uuid": initial_structure.uuid, - "formula": initial_structure.get_formula(), - "num_atoms": len(initial_structure.sites), - "space_group": "{} ({})".format( - *initial_structure.get_pymatgen().get_space_group_info() - ), - "cell_lengths": "{:.3f} {:.3f} {:.3f}".format( - *initial_structure.cell_lengths - ), - "cell_angles": "{:.0f} {:.0f} {:.0f}".format( - *initial_structure.cell_angles - ), - "relaxed": None - if ui_parameters["workchain"]["relax_type"] == "none" - else ui_parameters["workchain"]["relax_type"], - "relax_method": ui_parameters["workchain"]["relax_type"], - "electronic_type": ui_parameters["workchain"]["electronic_type"], - "material_magnetic": ui_parameters["workchain"]["spin_type"], - "protocol": ui_parameters["workchain"]["protocol"], - "initial_magnetic_moments": ui_parameters["advanced"][ - "initial_magnetic_moments" - ], - "properties": ui_parameters["workchain"]["properties"], + "workflow_properties": { + "pk": qeapp_wc.pk, + "uuid": str(qeapp_wc.uuid), + "label": qeapp_wc.label, + "description": qeapp_wc.description, + "creation_time": f"{format_time(ctime)} ({relative_time(ctime)})", + "modification_time": f"{format_time(mtime)} ({relative_time(mtime)})", + }, + "initial_structure_properties": { + "structure_pk": initial_structure.pk, + "structure_uuid": initial_structure.uuid, + "formula": initial_structure.get_formula(), + "num_atoms": len(initial_structure.sites), + "space_group": "{} ({})".format( + *initial_structure.get_pymatgen().get_space_group_info() + ), + "cell_lengths": "{:.3f} {:.3f} {:.3f}".format( + *initial_structure.cell_lengths + ), + "cell_angles": "{:.0f} {:.0f} {:.0f}".format( + *initial_structure.cell_angles + ), + }, + "basic_settings": { + "relaxed": "off" + if basic["relax_type"] == "none" + else basic["relax_type"], + "protocol": basic["protocol"], + "spin_type": "off" if basic["spin_type"] == "none" else "on", + "electronic_type": basic["electronic_type"], + "periodicity": PERIODICITY_MAPPING.get( + qeapp_wc.inputs.structure.pbc, "xyz" + ), + }, + "advanced_settings": {}, } - # - report.update( - { - "bands_computed": "bands" in ui_parameters["workchain"]["properties"], - "pdos_computed": "pdos" in ui_parameters["workchain"]["properties"], - } - ) - # update pseudo family information to report - pseudo_family = ui_parameters["advanced"].get("pseudo_family") + + pseudo_family = advanced.get("pseudo_family") pseudo_family_info = pseudo_family.split("/") pseudo_library = pseudo_family_info[0] + pseudo_version = pseudo_family_info[1] functional = pseudo_family_info[2] if pseudo_library == "SSSP": pseudo_protocol = pseudo_family_info[3] elif pseudo_library == "PseudoDojo": pseudo_protocol = pseudo_family_info[4] - report.update( - { - "pseudo_family": pseudo_family, - "pseudo_library": pseudo_library, - "pseudo_version": pseudo_family_info[1], - "functional": functional, - "pseudo_protocol": pseudo_protocol, - "pseudo_link": PSEUDO_LINK_MAP[pseudo_library], - "functional_link": FUNCTIONAL_LINK_MAP[functional], - } - ) + report["advanced_settings"] |= { + "functional": { + "url": FUNCTIONAL_LINK_MAP[functional], + "value": functional, + }, + "pseudo_library": { + "url": PSEUDO_LINK_MAP[pseudo_library], + "value": f"{pseudo_library} {pseudo_protocol} v{pseudo_version}", + }, + } + # Extract the pw calculation parameters from the workchain's inputs # energy_cutoff is same for all pw calculations when pseudopotentials are fixed - # as well as the smearing settings (semaring and degauss) and scf kpoints distance + # as well as the smearing settings (smearing and degauss) and scf kpoints distance # read from the first pw calculation of relax workflow. # It is safe then to extract these parameters from the first pw calculation, since the # builder is anyway set with subworkchain inputs even it is not run which controlled by # the properties inputs. - pw_parameters = qeapp_wc.inputs.relax.base.pw.parameters.get_dict() - energy_cutoff_wfc = pw_parameters["SYSTEM"]["ecutwfc"] - energy_cutoff_rho = pw_parameters["SYSTEM"]["ecutrho"] - occupation = pw_parameters["SYSTEM"]["occupations"] - scf_kpoints_distance = ( - qeapp_wc.inputs.relax.base.kpoints_distance.base.attributes.get("value") - ) - report.update( - { - "energy_cutoff_wfc": energy_cutoff_wfc, - "energy_cutoff_rho": energy_cutoff_rho, - "occupation_type": occupation, - "scf_kpoints_distance": scf_kpoints_distance, - } - ) + relax = qeapp_wc.inputs.relax.base + pw_parameters = relax.pw.parameters.get_dict() + system = pw_parameters["SYSTEM"] + occupation = system["occupations"] + report["advanced_settings"] |= { + "energy_cutoff_wfc": system["ecutwfc"], + "energy_cutoff_rho": system["ecutrho"], + "occupation_type": occupation, + } if occupation == "smearing": - report["degauss"] = pw_parameters["SYSTEM"]["degauss"] - report["smearing"] = pw_parameters["SYSTEM"]["smearing"] - report["tot_charge"] = pw_parameters["SYSTEM"].get("tot_charge", 0.0) - report["vdw_corr"] = VDW_CORRECTION_VERSION.get( - pw_parameters["SYSTEM"].get("dftd3_version"), - pw_parameters["SYSTEM"].get("vdw_corr", "none"), + report["advanced_settings"] |= { + "smearing": system["smearing"], + "degauss": system["degauss"], + } + report["advanced_settings"]["scf_kpoints_distance"] = ( + relax.kpoints_distance.base.attributes.get("value") ) - report["periodicity"] = PERIODICITY_MAPPING.get( - qeapp_wc.inputs.structure.pbc, "xyz" + if "bands" in qeapp_wc.inputs: + report["advanced_settings"]["bands_kpoints_distance"] = ( + PwBandsWorkChain.get_protocol_inputs( + basic["protocol"] + )["bands_kpoints_distance"] + ) + if "pdos" in qeapp_wc.inputs: + report["advanced_settings"]["nscf_kpoints_distance"] = ( + qeapp_wc.inputs.pdos.nscf.kpoints_distance.base.attributes.get("value") + ) + + vdw_corr = VDW_CORRECTION_VERSION.get( + system.get("dftd3_version"), + system.get("vdw_corr", "none"), ) + report["advanced_settings"] |= { + "tot_charge": system.get("tot_charge", 0.0), + "vdw_corr": "off" if vdw_corr == "none" else vdw_corr, + } - # Spin-Orbit coupling - report["spin_orbit"] = pw_parameters["SYSTEM"].get("lspinorb", False) + if basic["spin_type"] == "collinear": + if tot_magnetization := system.get("tot_magnetization", False): + report["advanced_settings"]["tot_magnetization"] = tot_magnetization + else: + report["advanced_settings"]["initial_magnetic_moments"] = advanced[ + "initial_magnetic_moments" + ] if hubbard_dict := ui_parameters["advanced"].pop("hubbard_parameters", None): hubbard_parameters = hubbard_dict["hubbard_u"] - report["hubbard_u"] = hubbard_parameters - report["tot_magnetization"] = pw_parameters["SYSTEM"].get( - "tot_magnetization", False - ) + report["advanced_settings"]["hubbard_u"] = hubbard_parameters - # hard code bands and pdos - if "bands" in qeapp_wc.inputs: - report["bands_kpoints_distance"] = PwBandsWorkChain.get_protocol_inputs( - report["protocol"] - )["bands_kpoints_distance"] + spin_orbit = system.get("lspinorb", False) + report["advanced_settings"]["spin_orbit"] = "on" if spin_orbit else "off" - if "pdos" in qeapp_wc.inputs: - report["nscf_kpoints_distance"] = ( - qeapp_wc.inputs.pdos.nscf.kpoints_distance.base.attributes.get("value") - ) return report @staticmethod diff --git a/src/aiidalab_qe/app/result/components/summary/schema.json b/src/aiidalab_qe/app/result/components/summary/schema.json new file mode 100644 index 000000000..b1bf03c11 --- /dev/null +++ b/src/aiidalab_qe/app/result/components/summary/schema.json @@ -0,0 +1,177 @@ +{ + "pk": { + "title": "PK", + "type": "text", + "description": "The AiiDA PK of the calculation" + }, + "uuid": { + "title": "UUID", + "type": "text", + "description": "The AiiDA UUID of the calculation" + }, + "label": { + "title": "Label", + "type": "text", + "description": "The label of the calculation" + }, + "description": { + "title": "Description", + "type": "text", + "description": "The description of the calculation" + }, + "creation_time": { + "title": "Creation time", + "type": "text", + "description": "The creation time of the calculation" + }, + "modification_time": { + "title": "Modification time", + "type": "text", + "description": "The modification time of the calculation" + }, + "structure_pk": { + "title": "Structure PK", + "type": "text", + "description": "The AiiDA PK of the structure" + }, + "structure_uuid": { + "title": "Structure UUID", + "type": "text", + "description": "The AiiDA UUID of the structure" + }, + "formula": { + "title": "Chemical formula", + "type": "text", + "description": "The formula of the structure" + }, + "num_atoms": { + "title": "Number of atoms", + "type": "text", + "description": "The number of atoms in the structure" + }, + "space_group": { + "title": "Space group", + "type": "text", + "description": "The space group of the structure" + }, + "cell_lengths": { + "title": "Cell lengths in Å", + "type": "text", + "description": "The cell lengths of the structure" + }, + "cell_angles": { + "title": "Cell angles in degrees", + "type": "text", + "description": "The cell angles of the structure" + }, + "relaxed": { + "title": "Structure geometry optimization", + "type": "text", + "description": "Whether the structure geometry is optimized (and to what level)" + }, + "protocol": { + "title": "Protocol", + "type": "text", + "description": "The protocol used for the calculation" + }, + "spin_type": { + "title": "Magnetism", + "type": "text", + "description": "The spin type considered" + }, + "electronic_type": { + "title": "Electronic type", + "type": "text", + "description": "The electronic type considered" + }, + "periodicity": { + "title": "Periodicity", + "type": "text", + "description": "The periodicity of the structure" + }, + "functional": { + "title": "Functional", + "type": "link", + "description": "The DFT functional used for the calculation" + }, + "pseudo_library": { + "title": "Pseudopotential library", + "type": "link", + "description": "The pseudopotential library used for the calculation" + }, + "pseudo_protocol": { + "title": "Pseudopotential protocol", + "type": "text", + "description": "The pseudopotential protocol used for the calculation" + }, + "energy_cutoff_wfc": { + "title": "Energy cutoff (wave functions)", + "type": "text", + "description": "The energy cutoff for wavefunctions" + }, + "energy_cutoff_rho": { + "title": "Energy cutoff (charge density)", + "type": "text", + "description": "The energy cutoff for the charge density" + }, + "occupation_type": { + "title": "Occupation type (SCF)", + "type": "text", + "description": "The occupation type used for the SCF calculation" + }, + "degauss": { + "title": "Smearing width (degauss)", + "type": "text", + "description": "The smearing width used for the calculation" + }, + "smearing": { + "title": "Smearing type", + "type": "text", + "description": "The smearing type used for the calculation" + }, + "scf_kpoints_distance": { + "title": "K-point mesh distance (SCF)", + "type": "text", + "description": "The distance between k-points for the SCF calculation" + }, + "bands_kpoints_distance": { + "title": "K-point mesh distance (Bands)", + "type": "text", + "description": "The distance between k-points for the bands calculation" + }, + "nscf_kpoints_distance": { + "title": "K-point mesh distance (NSCF)", + "type": "text", + "description": "The distance between k-points for the PDOS calculation" + }, + "tot_charge": { + "title": "Total charge", + "type": "text", + "description": "The total charge of the system" + }, + "vdw_corr": { + "title": "Van der Waals correction", + "type": "text", + "description": "The van der Waals correction used for the calculation" + }, + "tot_magnetization": { + "title": "Total magnetization", + "type": "text", + "description": "The total magnetization of the system" + }, + "initial_magnetic_moments": { + "title": "Initial magnetic moments", + "type": "text", + "description": "The initial magnetic moments of the system" + }, + "hubbard_u": { + "title": "DFT+U", + "type": "text", + "description": "The Hubbard U values used for the calculation" + }, + "spin_orbit": { + "title": "Spin-orbit coupling", + "type": "bool", + "description": "Whether spin-orbit coupling is considered" + } +} diff --git a/src/aiidalab_qe/app/static/styles/summary.css b/src/aiidalab_qe/app/static/styles/summary.css index c15882a13..0d62ae2bc 100644 --- a/src/aiidalab_qe/app/static/styles/summary.css +++ b/src/aiidalab_qe/app/static/styles/summary.css @@ -29,13 +29,8 @@ width: 200%; } -.summary-panel:first-child tr, -.summary-panel:first-child td { - padding: 0 8px; -} - -.summary-panel:first-child td:first-child { - width: 250px; +.workflow-summary .link { + text-decoration: none; } .settings-summary { @@ -54,10 +49,6 @@ margin-left: 20px; } -.settings-summary a { - text-decoration: none; -} - /* Error details */ .error-container { diff --git a/src/aiidalab_qe/app/static/styles/summary/list.css b/src/aiidalab_qe/app/static/styles/summary/list.css new file mode 100644 index 000000000..ecfdf33e7 --- /dev/null +++ b/src/aiidalab_qe/app/static/styles/summary/list.css @@ -0,0 +1,25 @@ +.summary-list table { + width: 100%; + border: none; +} + +.summary-list .section .entry { + background-color: #fff; + line-height: 1.5; +} + +.summary-list .section .entry .key { + font-weight: bold; + margin-right: 5px; +} + +.summary-list .section .entry .key::after { + content: ":"; +} + +.summary-list .section .entry .key, +.summary-list .section .entry .value { + padding: 0; + display: inline-block; + width: fit-content; +} diff --git a/src/aiidalab_qe/app/static/styles/summary/table.css b/src/aiidalab_qe/app/static/styles/summary/table.css new file mode 100644 index 000000000..773f6452e --- /dev/null +++ b/src/aiidalab_qe/app/static/styles/summary/table.css @@ -0,0 +1,36 @@ +.summary-table table { + font-family: arial, sans-serif; + border-collapse: collapse; + border: 1px solid #bbbbbb; + width: 100%; +} + +.summary-table .section .entry { + text-align: left; + background-color: var(--color-init); +} + +.summary-table .section .entry:nth-child(even) { + background-color: #fff; +} + +.summary-table .section .entry .key, +.summary-table .section .entry .value { + padding: 0 8px; +} + +.summary-table .section .entry .key { + width: 250px; +} + +@media (max-width: 1199px) { + .summary-table .section .entry .key { + width: 170px; + } +} + +@media (max-width: 991px) { + .summary-table .section .entry .key { + width: 125px; + } +} diff --git a/src/aiidalab_qe/app/static/templates/workflow_list_summary.jinja b/src/aiidalab_qe/app/static/templates/workflow_list_summary.jinja deleted file mode 100644 index 99189cc06..000000000 --- a/src/aiidalab_qe/app/static/templates/workflow_list_summary.jinja +++ /dev/null @@ -1,153 +0,0 @@ - - -
-
-

Workflow properties

- -
-
-

Initial structure properties

- -
-
-

Basic settings

- -
-
-

Advanced settings

- -
-
- - diff --git a/src/aiidalab_qe/app/static/templates/workflow_summary.jinja b/src/aiidalab_qe/app/static/templates/workflow_summary.jinja new file mode 100644 index 000000000..668149ba5 --- /dev/null +++ b/src/aiidalab_qe/app/static/templates/workflow_summary.jinja @@ -0,0 +1,31 @@ + + + + + +
+ {% for section in report %} +
+

{{ section | replace ('_', ' ') | capitalize }}

+ + {% for key, value in report[section].items() %} + + + + + {% endfor %} +
{{ schema[key].title }} + {% if schema[key].type == "link" %} + + {{ value['value'] }} + + {% else %} + {{ value }} + {% endif %} +
+
+ {% endfor %} +
+ diff --git a/src/aiidalab_qe/app/static/templates/workflow_table_summary.jinja b/src/aiidalab_qe/app/static/templates/workflow_table_summary.jinja deleted file mode 100644 index 78571afdf..000000000 --- a/src/aiidalab_qe/app/static/templates/workflow_table_summary.jinja +++ /dev/null @@ -1,189 +0,0 @@ - - -
-

Workflow properties

- - - - - - - - - - - - - - - - - - - - - - - - - -
PK{{ pk }}
UUID{{ uuid }}
Label{{ label }}
Description{{ description }}
Creation time{{ creation_time }} ({{ creation_time_relative }})
Modification time{{ modification_time }} ({{ modification_time_relative }})
-
-
-

Initial structure properties

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Structure PK{{ structure_pk }}
Structure UUID{{ structure_uuid }}
Chemical formula{{ formula }}
Number of atoms{{ num_atoms }}
Space group{{ space_group }}
Cell lengths in Å{{ cell_lengths }}
Cell angles in °{{ cell_angles }}
-
-
-

Basic settings

- - - - - - {% if relaxed %} - - - - - {% endif %} - - - - - - - - - - - - - - - - -
Structure geometry optimization{{ relaxed | fmt_yes_no }}
Optimization method{{ relax_method }}
Protocol{{ protocol }}
Magnetism{{ material_magnetic }}
Electronic type{{ electronic_type }}
Periodicity{{ periodicity }}
-
-
-

Advanced settings

- - - - - - - - - - - - - - - - - - - - - - {% if occupation_type == "smearing" %} - - - - - - - - - {% endif %} - - - - - {% if bands_computed %} - - - - - {% endif %} - {% if pdos_computed %} - - - - - {% endif %} - - - - - - - - - {% if material_magnetic == "collinear" %} - {% if tot_magnetization %} - - - - - {% else %} - - - - - {% endif %} - {% endif %} - {% if hubbard_u %} - - - - - {% endif %} - {% if spin_orbit %} - - - - - {% endif %} -
Functional - - {{ functional }} - -
Pseudopotential library - - {{ pseudo_library }} {{ pseudo_protocol }} v{{ pseudo_version }} - -
Energy cutoff (wave functions){{ energy_cutoff_wfc }} Ry
Energy cutoff (charge density){{ energy_cutoff_rho }} Ry
Occupation type (SCF){{ occupation_type }}
Smearing width (degauss){{ degauss }} Ry
Smearing type{{ smearing }}
K-point mesh distance (SCF){{ scf_kpoints_distance }} Å-1
K-point line distance (Bands){{ bands_kpoints_distance }} Å-1
K-point mesh distance (NSCF){{ nscf_kpoints_distance }} Å-1
Total charge{{ tot_charge }}
Van der Waals correction{{ vdw_corr }}
Total magnetization{{ tot_magnetization }}
Initial magnetic moments{{ initial_magnetic_moments }}
DFT+U{{ hubbard_u }}
Spin-orbit coupling{{ spin_orbit }}
-
- -