From a0bcfb496f3fd8e3c9944d8da2fa6342237d5658 Mon Sep 17 00:00:00 2001 From: Edan Bainglass Date: Sun, 12 Jan 2025 11:24:27 +0000 Subject: [PATCH] Spruce up advanced panel styling --- .../app/configuration/advanced/advanced.py | 101 +++++++-------- .../configuration/advanced/hubbard/hubbard.py | 30 ++--- .../advanced/magnetization/magnetization.py | 19 +-- .../advanced/magnetization/model.py | 3 +- .../app/configuration/advanced/model.py | 9 +- .../configuration/advanced/pseudos/model.py | 16 ++- .../configuration/advanced/pseudos/pseudos.py | 120 ++++++++++-------- .../advanced/smearing/smearing.py | 23 ++-- tests/test_pseudo.py | 2 +- 9 files changed, 164 insertions(+), 159 deletions(-) diff --git a/src/aiidalab_qe/app/configuration/advanced/advanced.py b/src/aiidalab_qe/app/configuration/advanced/advanced.py index 4a43bc40a..0007796e3 100644 --- a/src/aiidalab_qe/app/configuration/advanced/advanced.py +++ b/src/aiidalab_qe/app/configuration/advanced/advanced.py @@ -73,9 +73,16 @@ def render(self): if self.rendered: return - # clean-up workchain settings + self.reset_to_defaults_button = ipw.Button( + description="Reset to defaults", + button_style="primary", + icon="undo", + layout=ipw.Layout(width="fit-content"), + ) + self.reset_to_defaults_button.on_click(self._on_reset_to_defaults_button_click) + self.clean_workdir = ipw.Checkbox( - description="Tick to delete the work directory after the calculation is finished", + description="Delete the work directory after the calculation", indent=False, layout=ipw.Layout(width="fit-content"), ) @@ -83,13 +90,6 @@ def render(self): (self._model, "clean_workdir"), (self.clean_workdir, "value"), ) - self.reset_to_defaults_button = ipw.Button( - description="Reset to defaults", - button_style="warning", - icon="undo", - layout=ipw.Layout(width="fit-content"), - ) - self.reset_to_defaults_button.on_click(self._on_reset_to_defaults_button_click) # Smearing setting widget self.smearing.render() @@ -98,8 +98,8 @@ def render(self): self.kpoints_distance = ipw.BoundedFloatText( min=0.0, step=0.05, - description="K-points distance (1/Å):", - style={"description_width": "initial"}, + description="K-points distance:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "kpoints_distance"), @@ -110,7 +110,7 @@ def render(self): (self.kpoints_distance, "disabled"), lambda _: not self._model.has_pbc, ) - self.mesh_grid = ipw.HTML() + self.mesh_grid = ipw.HTML(layout=ipw.Layout(margin="0 0 0 10px")) ipw.dlink( (self._model, "mesh_grid"), (self.mesh_grid, "value"), @@ -125,7 +125,7 @@ def render(self): max=3, step=0.01, description="Total charge:", - style={"description_width": "initial"}, + style={"description_width": "150px"}, ) ipw.link( (self._model, "total_charge"), @@ -135,7 +135,7 @@ def render(self): # Van der Waals setting widget self.van_der_waals = ipw.Dropdown( description="Van der Waals correction:", - style={"description_width": "initial"}, + style={"description_width": "150px"}, ) ipw.dlink( (self._model, "van_der_waals_options"), @@ -153,8 +153,9 @@ def render(self): self.scf_conv_thr = ipw.BoundedFloatText( min=1e-15, max=1.0, - description="SCF conv.:", - style={"description_width": "initial"}, + format="0.0e", + description="SCF:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "scf_conv_thr"), @@ -167,8 +168,9 @@ def render(self): self.forc_conv_thr = ipw.BoundedFloatText( min=1e-15, max=1.0, - description="Force conv.:", - style={"description_width": "initial"}, + format="0.0e", + description="Force:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "forc_conv_thr"), @@ -181,8 +183,9 @@ def render(self): self.etot_conv_thr = ipw.BoundedFloatText( min=1e-15, max=1.0, - description="Energy conv.:", - style={"description_width": "initial"}, + format="0.0e", + description="Energy:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "etot_conv_thr"), @@ -196,8 +199,8 @@ def render(self): min=20, max=1000, step=1, - description="Max. electron steps:", - style={"description_width": "initial"}, + description="Electronic:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "electron_maxstep"), @@ -208,8 +211,8 @@ def render(self): min=50, max=1000, step=1, - description="Max. optimization steps:", - style={"description_width": "initial"}, + description="Ionic:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "optimization_maxsteps"), @@ -219,48 +222,38 @@ def render(self): self.children = [ InAppGuide(identifier="advanced-settings"), - ipw.HBox( - children=[ - self.clean_workdir, - self.reset_to_defaults_button, - ], - layout=ipw.Layout( - justify_content="space-between", - margin="0 0 10px 0", - ), - ), + self.reset_to_defaults_button, + self.clean_workdir, self.total_charge, self.van_der_waals, self.magnetization, - ipw.HTML("Convergence thresholds:"), - ipw.HBox( - children=[ - self.forc_conv_thr, - self.etot_conv_thr, - self.scf_conv_thr, - ], - layout=ipw.Layout(justify_content="space-between"), - ), - ipw.HBox( - children=[ - self.electron_maxstep, - self.optimization_maxsteps, - ], - ), + ipw.HTML("Convergence thresholds"), + self.forc_conv_thr, + self.etot_conv_thr, + self.scf_conv_thr, + ipw.HTML("Maximum cycle steps"), + self.electron_maxstep, + self.optimization_maxsteps, self.smearing, + ipw.HTML("K-points"), ipw.HTML(""" -
+
The k-points mesh density of the SCF calculation is set by the - protocol. The value below represents the maximum distance - between the k-points in each direction of reciprocal space. Smaller - is more accurate and costly. + protocol. +
+ The value below represents the maximum distance between k-points + in each direction of reciprocal space. +
+ Smaller is more accurate and costly.
"""), ipw.HBox( children=[ self.kpoints_distance, + ipw.HTML("Å-1"), self.mesh_grid, - ] + ], + layout=ipw.Layout(align_items="center"), ), self.hubbard, self.pseudos, diff --git a/src/aiidalab_qe/app/configuration/advanced/hubbard/hubbard.py b/src/aiidalab_qe/app/configuration/advanced/hubbard/hubbard.py index 7f6e55dd1..053b150cc 100644 --- a/src/aiidalab_qe/app/configuration/advanced/hubbard/hubbard.py +++ b/src/aiidalab_qe/app/configuration/advanced/hubbard/hubbard.py @@ -28,9 +28,8 @@ def render(self): return self.activate_hubbard_checkbox = ipw.Checkbox( - description="", + description="Define U values", indent=False, - layout=ipw.Layout(max_width="10%"), ) ipw.link( (self._model, "is_active"), @@ -44,7 +43,6 @@ def render(self): self.define_eigenvalues_checkbox = ipw.Checkbox( description="Define eigenvalues", indent=False, - layout=ipw.Layout(max_width="30%"), ) ipw.link( (self._model, "has_eigenvalues"), @@ -64,12 +62,8 @@ def render(self): self.container = ipw.VBox() self.children = [ - ipw.HBox( - children=[ - ipw.HTML("Hubbard (DFT+U)"), - self.activate_hubbard_checkbox, - ] - ), + ipw.HTML("Hubbard (DFT+U)"), + self.activate_hubbard_checkbox, self.container, ] @@ -107,16 +101,13 @@ def _build_hubbard_widget(self): children = [] - if self._model.input_structure: - children.append(ipw.HTML("Define U value [eV] ")) - for label in self._model.orbital_labels: float_widget = ipw.BoundedFloatText( description=label, min=0, max=20, step=0.1, - layout={"width": "160px"}, + style={"description_width": "150px"}, ) link = ipw.link( (self._model, "parameters"), @@ -130,7 +121,14 @@ def _build_hubbard_widget(self): ], ) self.links.append(link) - children.append(float_widget) + children.append( + ipw.HBox( + children=[ + float_widget, + ipw.HTML("eV"), + ], + ) + ) if self._model.needs_eigenvalues_widget: children.append(self.eigenvalues_container) @@ -158,7 +156,7 @@ def update(index, spin, state, symbol, value): for state_index in range(num_states): eigenvalues_up = ipw.Dropdown( - description=f"{state_index+1}", + description=f"{state_index + 1}", layout=ipw.Layout(width="65px"), style={"description_width": "initial"}, ) @@ -191,7 +189,7 @@ def update(index, spin, state, symbol, value): spin_up_row.children += (eigenvalues_up,) eigenvalues_down = ipw.Dropdown( - description=f"{state_index+1}", + description=f"{state_index + 1}", layout=ipw.Layout(width="65px"), style={"description_width": "initial"}, ) diff --git a/src/aiidalab_qe/app/configuration/advanced/magnetization/magnetization.py b/src/aiidalab_qe/app/configuration/advanced/magnetization/magnetization.py index 681d8ad9e..27c673f7d 100644 --- a/src/aiidalab_qe/app/configuration/advanced/magnetization/magnetization.py +++ b/src/aiidalab_qe/app/configuration/advanced/magnetization/magnetization.py @@ -48,12 +48,9 @@ def render(self): if self.rendered: return - self.header = ipw.HTML("Magnetization:") + self.header = ipw.HTML("Magnetization") - self.unit = ipw.HTML( - value="µB", - layout=ipw.Layout(margin="2px 2px 5px"), - ) + self.unit = ipw.HTML("µB") self.magnetization_type_help = ipw.HTML() ipw.dlink( @@ -62,11 +59,8 @@ def render(self): ) self.magnetization_type = ipw.ToggleButtons( - style={ - "description_width": "initial", - "button_width": "initial", - }, - layout=ipw.Layout(margin="0 0 10px 0"), + style={"button_width": "initial"}, + layout=ipw.Layout(margin="0 0 5px"), ) ipw.dlink( (self._model, "type_options"), @@ -82,7 +76,7 @@ def render(self): max=100, step=1, description="Total magnetization:", - style={"description_width": "initial"}, + style={"description_width": "150px"}, ) ipw.link( (self._model, "total"), @@ -94,7 +88,6 @@ def render(self): self.tot_magnetization, self.unit, ], - layout=ipw.Layout(align_items="center"), ) self.kind_moment_widgets = ipw.VBox() @@ -161,6 +154,7 @@ def _build_kinds_widget(self): min=-7, max=7, step=0.1, + style={"description_width": "150px"}, ) link = ipw.link( (self._model, "moments"), @@ -180,7 +174,6 @@ def _build_kinds_widget(self): kind_moment_widget, self.unit, ], - layout=ipw.Layout(align_items="center"), ) ) diff --git a/src/aiidalab_qe/app/configuration/advanced/magnetization/model.py b/src/aiidalab_qe/app/configuration/advanced/magnetization/model.py index dcf3ef704..649ad7ae0 100644 --- a/src/aiidalab_qe/app/configuration/advanced/magnetization/model.py +++ b/src/aiidalab_qe/app/configuration/advanced/magnetization/model.py @@ -82,7 +82,8 @@ def update_type_help(self): content=""" If a nonzero ground-state magnetization is expected, you must assign a nonzero value to at least one atomic - type (note that the app already provide tentative initial values). + type (the app already provides tentative initial values). +
To simulate an antiferromagnetic state, first, if you have not done so already, please use the atom tag editor (Select structure -> Edit structure -> Edit atom tags) to mark atoms of diff --git a/src/aiidalab_qe/app/configuration/advanced/model.py b/src/aiidalab_qe/app/configuration/advanced/model.py index a09253701..e153a25dc 100644 --- a/src/aiidalab_qe/app/configuration/advanced/model.py +++ b/src/aiidalab_qe/app/configuration/advanced/model.py @@ -25,6 +25,7 @@ from .smearing import SmearingConfigurationSettingsModel DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore +SIGFIGS = 5 class AdvancedConfigurationSettingsModel( @@ -296,7 +297,7 @@ def _update_thresholds(self, parameters): self.forc_conv_thr_step = self._defaults["forc_conv_thr_step"] def _set_value_and_step(self, attribute, value): - self._defaults[attribute] = value + self._defaults[attribute] = round(value, SIGFIGS) if value != 0: order_of_magnitude = np.floor(np.log10(abs(value))) step = 10 ** (order_of_magnitude - 1) @@ -309,9 +310,9 @@ def _set_pw_parameters(self, pw_parameters): control_params = pw_parameters.get("CONTROL", {}) electron_params = pw_parameters.get("ELECTRONS", {}) - self.forc_conv_thr = control_params.get("forc_conv_thr", 0.0) - self.etot_conv_thr = control_params.get("etot_conv_thr", 0.0) - self.scf_conv_thr = electron_params.get("conv_thr", 0.0) + self.forc_conv_thr = round(control_params.get("forc_conv_thr", 0.0), SIGFIGS) + self.etot_conv_thr = round(control_params.get("etot_conv_thr", 0.0), SIGFIGS) + self.scf_conv_thr = round(electron_params.get("conv_thr", 0.0), SIGFIGS) self.electron_maxstep = electron_params.get("electron_maxstep", 80) self.total_charge = system_params.get("tot_charge", 0) diff --git a/src/aiidalab_qe/app/configuration/advanced/pseudos/model.py b/src/aiidalab_qe/app/configuration/advanced/pseudos/model.py index 725b3c230..1981682a2 100644 --- a/src/aiidalab_qe/app/configuration/advanced/pseudos/model.py +++ b/src/aiidalab_qe/app/configuration/advanced/pseudos/model.py @@ -97,13 +97,15 @@ class PseudosConfigurationSettingsModel( PSEUDO_HELP_WO_SOC = """
- If you are unsure, select 'SSSP efficiency', which for most - calculations will produce sufficiently accurate results at - comparatively small computational costs. If your calculations require a - higher accuracy, select 'SSSP accuracy' or 'PseudoDojo stringent', - which will be computationally more expensive. SSSP is the standard - solid-state pseudopotentials. The PseudoDojo used here has the SR - relativistic type. + If you are unsure, select 'SSSP efficiency', which for most calculations + will produce sufficiently accurate results at comparatively small + computational costs. +
+ If your calculations require a higher accuracy, select 'SSSP accuracy' or + 'PseudoDojo stringent', which will be computationally more expensive. +
+ SSSP is the standard solid-state pseudopotentials. + The PseudoDojo version used here is the SR relativistic type.
""" diff --git a/src/aiidalab_qe/app/configuration/advanced/pseudos/pseudos.py b/src/aiidalab_qe/app/configuration/advanced/pseudos/pseudos.py index 5feced660..6bf026069 100644 --- a/src/aiidalab_qe/app/configuration/advanced/pseudos/pseudos.py +++ b/src/aiidalab_qe/app/configuration/advanced/pseudos/pseudos.py @@ -81,13 +81,14 @@ def render(self): self.functional_help = ipw.HTML("""
- The exchange-correlation energy is calculated using this functional. We - currently provide support for two well-established generalized gradient - approximation (GGA) functionals: PBE and PBEsol. + The exchange-correlation energy is calculated using this functional. +
+ We currently provide support for two well-established generalized + gradient approximation (GGA) functionals: PBE and PBEsol.
""") - self.functional = ipw.Dropdown(style={"description_width": "initial"}) + self.functional = ipw.ToggleButtons() ipw.dlink( (self._model, "functional_options"), (self.functional, "options"), @@ -97,7 +98,7 @@ def render(self): (self.functional, "value"), ) - self.library = ipw.ToggleButtons(layout=ipw.Layout(max_width="80%")) + self.library = ipw.ToggleButtons() ipw.dlink( (self._model, "library_options"), (self.library, "options"), @@ -107,16 +108,6 @@ def render(self): (self.library, "value"), ) - self.setter_widget_helper = ipw.HTML(""" -
- The pseudopotential for each kind of atom in the structure can be - custom set. The default pseudopotential and cutoffs are get from - the pseudo family. The cutoffs used for the calculation are the - maximum of the default from all pseudopotentials and can be custom - set. -
- """) - self.setter_widget = ipw.VBox() self._status_message = StatusHTML(clear_after=20) @@ -125,23 +116,17 @@ def render(self): (self._status_message, "message"), ) - self.cutoff_helper = ipw.HTML(""" -
- Please set the cutoffs for the calculation. The default cutoffs are get - from the pseudo family. -
- """) self.ecutwfc = ipw.FloatText( - description="Wavefunction cutoff (Ry)", - style={"description_width": "initial"}, + description="Wavefunction", + style={"description_width": "150px"}, ) ipw.link( (self._model, "ecutwfc"), (self.ecutwfc, "value"), ) self.ecutrho = ipw.FloatText( - description="Charge density cutoff (Ry)", - style={"description_width": "initial"}, + description="Charge density", + style={"description_width": "150px"}, ) ipw.link( (self._model, "ecutrho"), @@ -152,40 +137,62 @@ def render(self): ipw.HTML("

Accuracy and precision

"), ipw.HTML("""
- The exchange-correlation functional and pseudopotential - library is set by the protocol configured in the - "Workflow" tab. Here you can override the defaults if - desired. + The exchange-correlation functional and pseudopotential library is + set by the protocol configured in the Basic settings + tab. +
+ Here you can override the defaults if desired.
"""), - ipw.HBox( - [ - ipw.VBox( - children=[ - self.functional_prompt, - self.functional, - self.functional_help, - ], - layout=ipw.Layout(max_width="40%"), - ), - ipw.VBox( - children=[ - self.family_prompt, - self.library, - self.family_help, - ], - layout=ipw.Layout(max_width="60%"), - ), - ] + ipw.VBox( + children=[ + self.functional_prompt, + self.functional, + self.functional_help, + ], + ), + ipw.VBox( + children=[ + self.family_prompt, + self.library, + self.family_help, + ], ), - self.setter_widget_helper, + ipw.HTML("Pseudopotentials"), + ipw.HTML(""" +
+ The pseudopotential for each kind of atom in the structure can be + custom set. +
+ The default pseudopotential and cutoffs are taken from the + pseudopotential family. +
+ Recommended wavefunction (ψ) and charge density (ρ) cutoffs are + given to the right of each pseudopotential. +
+ """), # noqa: RUF001 self.setter_widget, - self.cutoff_helper, + ipw.HTML("Cutoffs"), + ipw.HTML(""" +
+ The cutoffs used for the calculation are the maximum of the + default cutoffs from all pseudopotentials. + You can override them here. +
+ """), ipw.HBox( children=[ self.ecutwfc, + ipw.HTML("Ry"), + ], + layout=ipw.Layout(align_items="center"), + ), + ipw.HBox( + children=[ self.ecutrho, + ipw.HTML("Ry"), ], + layout=ipw.Layout(align_items="center"), ), self._status_message, ] @@ -322,7 +329,10 @@ def render(self): if self.rendered: return - self.pseudo_text = ipw.Text(description=self.kind_name) + self.pseudo_text = ipw.Text( + description=self.kind_name, + style={"description_width": "50px"}, + ) pseudo_link = ipw.dlink( (self, "pseudo"), (self.pseudo_text, "value"), @@ -331,14 +341,18 @@ def render(self): self.file_upload = ipw.FileUpload( description="Upload", multiple=False, + layout=ipw.Layout( + width="fit-content", + margin="2px 10px 2px 2px", + ), ) self.file_upload.observe(self._on_file_upload, "value") cutoffs_message_template = """
- Recommended ecutwfc: {ecutwfc} Ry ecutrho: {ecutrho} Ry + ψ: {ecutwfc} Ry | ρ: {ecutrho} Ry
- """ + """ # noqa: RUF001 self.cutoff_message = ipw.HTML() cutoff_link = ipw.dlink( diff --git a/src/aiidalab_qe/app/configuration/advanced/smearing/smearing.py b/src/aiidalab_qe/app/configuration/advanced/smearing/smearing.py index 90be98780..60ecf1613 100644 --- a/src/aiidalab_qe/app/configuration/advanced/smearing/smearing.py +++ b/src/aiidalab_qe/app/configuration/advanced/smearing/smearing.py @@ -21,7 +21,7 @@ def render(self): self.smearing = ipw.Dropdown( description="Smearing type:", - style={"description_width": "initial"}, + style={"description_width": "150px"}, ) ipw.dlink( (self._model, "type_options"), @@ -34,8 +34,8 @@ def render(self): self.degauss = ipw.FloatText( step=0.005, - description="Smearing width (Ry):", - style={"description_width": "initial"}, + description="Smearing width:", + style={"description_width": "150px"}, ) ipw.link( (self._model, "degauss"), @@ -43,19 +43,22 @@ def render(self): ) self.children = [ + ipw.HTML("Smearing"), ipw.HTML(""" -

+

The smearing type and width is set by the chosen protocol. - It is not advised unless you've mastered smearing effects - (click here for a discussion). -

+
+ Changes are not advised unless you've mastered + smearing effects. +
"""), + self.smearing, ipw.HBox( children=[ - self.smearing, self.degauss, - ] + ipw.HTML("Ry"), + ], ), ] diff --git a/tests/test_pseudo.py b/tests/test_pseudo.py index 9ef3041a8..d63bf9217 100644 --- a/tests/test_pseudo.py +++ b/tests/test_pseudo.py @@ -230,7 +230,7 @@ def test_pseudo_upload_widget(generate_upf_data): w.cutoffs = [30, 240] w.render() - message = "Recommended ecutwfc: {ecutwfc} Ry ecutrho: {ecutrho} Ry" + message = "ψ: {ecutwfc} Ry | ρ: {ecutrho} Ry" # noqa: RUF001 assert w.pseudo.filename == "O_old.upf" assert w.kind_name == "O1"