From 358d1387e7c8978b07ed59014591f4758aa03464 Mon Sep 17 00:00:00 2001 From: gmatteo Date: Mon, 3 Feb 2020 16:36:27 +0100 Subject: [PATCH] Add abiopen FILE.json --panel --- CHANGELOG.rst | 1 + abipy/core/structure.py | 4 +- abipy/electrons/ebands.py | 12 +++++ abipy/electrons/fatbands.py | 14 +++-- abipy/electrons/tests/test_ebands.py | 2 + abipy/electrons/tests/test_fatbands.py | 3 ++ abipy/panels/core.py | 42 +++++++++++++-- abipy/panels/fatbands.py | 71 ++++++++++++++++++++++++++ abipy/panels/gsr.py | 7 ++- abipy/panels/structure.py | 5 +- abipy/scripts/abiopen.py | 41 ++++++++------- docs/links.rst | 1 + 12 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 abipy/panels/fatbands.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2e7e31fd..00ce8270 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ Release 0.8.0: xxxx-xx-xx * Add examples and flows for effective mass calculations * Add examples for quasi-harmonic calculations and post-processing tools * Add support for JSON files (including MSONable format) to abiopen.py + Supports `--notebook`, `--panel` options such as `abiopen.py FILE.json --panel` * Improved support for EPH calculations. * Add `primitive` command to `abistruct.py` to get primitive structure from spglib diff --git a/abipy/core/structure.py b/abipy/core/structure.py index 7c1fedd2..33b5777d 100644 --- a/abipy/core/structure.py +++ b/abipy/core/structure.py @@ -226,7 +226,7 @@ class Structure(pymatgen.Structure, NotebookWriter): new = ncfile.read_structure(cls=cls) new.set_abi_spacegroup(AbinitSpaceGroup.from_ncreader(ncfile)) - # Try to read indsym from file (added in 8.9.x) + # Try to read indsym table from file (added in 8.9.x) indsym = ncfile.read_value("indsym", default=None) if indsym is not None: # Fortran --> C convention @@ -2065,7 +2065,7 @@ class Structure(pymatgen.Structure, NotebookWriter): nbv.new_code_cell("print(structure)"), nbv.new_code_cell("print(structure.abi_string)"), nbv.new_code_cell("structure"), - nbv.new_code_cell("print(structure.spglib_summary())"), + nbv.new_code_cell("print(structure.spget_summary())"), nbv.new_code_cell("if structure.abi_spacegroup is not None: print(structure.abi_spacegroup)"), nbv.new_code_cell("print(structure.hsym_kpoints)"), nbv.new_code_cell("structure.plot_bz();"), diff --git a/abipy/electrons/ebands.py b/abipy/electrons/ebands.py index 487ac450..93c73868 100644 --- a/abipy/electrons/ebands.py +++ b/abipy/electrons/ebands.py @@ -851,6 +851,18 @@ class ElectronBands(Has_Structure): """True if time-reversal symmetry is used in the BZ sampling.""" return has_timrev_from_kptopt(self.kptopt) + @lazy_property + def supports_fermi_surface(self): + """ + True if the kpoints used for the energies can be employed to visualize Fermi surface. + Fermi surface viewers require gamma-centered k-mesh. + """ + if self.kpoints.is_mpmesh: + mpdivs, shifts = self.kpoints.mpdivs_shifts + if shifts is not None and np.all(shifts == 0.0): + return True + return False + def kindex(self, kpoint): """ The index of the k-point in the internal list of k-points. diff --git a/abipy/electrons/fatbands.py b/abipy/electrons/fatbands.py index 8d5b3397..165d3b59 100644 --- a/abipy/electrons/fatbands.py +++ b/abipy/electrons/fatbands.py @@ -1542,6 +1542,13 @@ class FatBandsFile(AbinitNcFile, Has_Header, Has_Structure, Has_ElectronBands, N yield self.plot_pjdos_lview(show=False) yield self.plot_pjdos_typeview(show=False) + def get_panel(self): + """ + Build panel with widgets to interact with the |FatbandsFile| either in a notebook or in panel app. + """ + from abipy.panels.fatbands import FatBandsFilePanel + return FatBandsFilePanel(self).get_panel() + def write_notebook(self, nbpath=None): """ Write a jupyter_ notebook to nbpath. If nbpath is None, a temporay file in the current @@ -1587,9 +1594,10 @@ class FatBandsFile(AbinitNcFile, Has_Header, Has_Structure, Has_ElectronBands, N if self.prtdos == 3 and self.ebands.kpoints.is_path: nb.cells.extend([ - nbv.new_markdown_cell("## L-DOSes with fatbands\n" - "(require `prtdos=3`, `fbnc` must contain a k-path, " - "`pjdosfile` is a `FATBANDS.nc` file with a BZ sampling)"), + nbv.new_markdown_cell( + "## L-DOSes with fatbands\n" + "(require `prtdos=3`, `fbnc` must contain a k-path, " + "`pjdosfile` is a `FATBANDS.nc` file with a BZ sampling)"), nbv.new_code_cell("fbnc.plot_fatbands_with_pjdos(pjdosfile=None, ylims=ylims, view='type');"), ]) diff --git a/abipy/electrons/tests/test_ebands.py b/abipy/electrons/tests/test_ebands.py index d38000fc..c7b3c705 100644 --- a/abipy/electrons/tests/test_ebands.py +++ b/abipy/electrons/tests/test_ebands.py @@ -479,6 +479,7 @@ class ElectronBandsTest(AbipyTest): with abilab.abiopen(abidata.ref_file("mgb2_kmesh181818_FATBANDS.nc")) as fbnc_kmesh: ebands = fbnc_kmesh.ebands str(ebands) + assert ebands.supports_fermi_surface ebands.to_bxsf(self.get_tmpname(text=True)) # Test Ebands3d @@ -525,6 +526,7 @@ class ElectronBandsFromRestApi(AbipyTest): new_fermie = r.ebands_kpath.set_fermie_to_vbm() assert new_fermie == r.ebands_kpath.fermie + assert not r.ebands_kpath.supports_fermi_surface edos = r.ebands_kmesh.get_edos() new_fermie = r.ebands_kpath.set_fermie_from_edos(edos) diff --git a/abipy/electrons/tests/test_fatbands.py b/abipy/electrons/tests/test_fatbands.py index 49545600..462a28bf 100644 --- a/abipy/electrons/tests/test_fatbands.py +++ b/abipy/electrons/tests/test_fatbands.py @@ -39,6 +39,9 @@ class TestElectronFatbands(AbipyTest): if self.has_nbformat(): fbnc_kpath.write_notebook(nbpath=self.get_tmpname(text=True)) + if self.has_panel(): + assert hasattr(fbnc_kpath.get_panel(), "show") + fbnc_kmesh = FatBandsFile(abidata.ref_file("mgb2_kmesh181818_FATBANDS.nc")) repr(fbnc_kmesh); str(fbnc_kmesh) assert fbnc_kmesh.ebands.kpoints.is_ibz diff --git a/abipy/panels/core.py b/abipy/panels/core.py index 07aca26d..24640cd0 100644 --- a/abipy/panels/core.py +++ b/abipy/panels/core.py @@ -48,8 +48,8 @@ class PanelWithElectronBands(AbipyParameterized): #, metaclass=abc.ABCMeta): # - `fermie`: shift all eigenvalues to have zero energy at the Fermi energy (`self.fermie`). # - Number e.g e0=0.5: shift all eigenvalues to have zero energy at 0.5 eV # - None: Don't shift energies, equivalent to e0=0 + set_fermie_to_vbm = pnw.Checkbox(name="Set Fermie to VBM") - #set_fermie_to_vbm plot_ebands_btn = pnw.Button(name="Plot e-bands", button_type='primary') # DOS plot. @@ -58,19 +58,25 @@ class PanelWithElectronBands(AbipyParameterized): #, metaclass=abc.ABCMeta): edos_width = pnw.Spinner(name='e-DOS Gaussian broadening (eV)', value=0.2, step=0.05, start=1e-6, end=None) plot_edos_btn = pnw.Button(name="Plot e-DOS", button_type='primary') + # Fermi surface plot. + fs_viewer = pnw.Select(name="FS viewer", options=["matplotlib", "xcrysden"]) + plot_fermi_surface_btn = pnw.Button(name="Plot Fermi surface", button_type='primary') + #@abc.abstractproperty #def ebands(self): # """Returns the |ElectronBands| object.""" def get_plot_ebands_widgets(self): """Widgets to plot ebands.""" - return pn.Column(self.with_gaps, - self.plot_ebands_btn) + return pn.Column(self.with_gaps, self.set_fermie_to_vbm, self.plot_ebands_btn) @param.depends('plot_ebands_btn.clicks') def on_plot_ebands_btn(self): """Button triggering ebands plot.""" if self.plot_ebands_btn.clicks == 0: return + if self.set_fermie_to_vbm.value: + self.ebands.set_fermie_to_vbm() + fig1 = self.ebands.plot(e0="fermie", ylims=None, with_gaps=self.with_gaps.value, max_phfreq=None, fontsize=8, **self.fig_kwargs) @@ -89,9 +95,37 @@ class PanelWithElectronBands(AbipyParameterized): #, metaclass=abc.ABCMeta): if self.plot_edos_btn.clicks == 0: return edos = self.ebands.get_edos(method=self.edos_method.value, step=self.edos_step.value, width=self.edos_width.value) fig = edos.plot(**self.fig_kwargs) - #print(edos) return pn.Row(self._mp(fig), sizing_mode='scale_width') + def get_plot_fermi_surface_widgets(self): + """Widgets to compute e-DOS.""" + return pn.Column(self.fs_viewer, self.plot_fermi_surface_btn) + + @param.depends('plot_fermi_surface_btn.clicks') + def on_plot_fermi_surface_btn(self): + if self.plot_fermi_surface_btn.clicks == 0: return + if hasattr(self, "_eb3d"): + eb3d = self._eb3d + else: + # Build ebands in full BZ. + eb3d = self._eb3d = self.ebands.get_ebands3d() + + if self.fs_viewer.value == "matplotlib": + # Use matplotlib to plot isosurfaces corresponding to the Fermi level (default) + # Warning: requires skimage package, rendering could be slow. + fig = eb3d.plot_isosurfaces(e0="fermie", cmap=None, **self.fig_kwargs) + return pn.Row(self._mp(fig), sizing_mode='scale_width') + + elif self.fs_viewer.value == "xcrysden": + # Alternatively, it's possible to export the data in xcrysden format + # and then use `xcrysden --bxsf mgb2.bxsf` + #eb3d.to_bxsf("mgb2.bxsf") + # If you have mayavi installed, try: + #eb3d.mvplot_isosurfaces() + + else: + raise ValueError("Invalid choice: %s" % self.fs_viewer.value) + class BaseRobotPanel(AbipyParameterized): """pass""" diff --git a/abipy/panels/fatbands.py b/abipy/panels/fatbands.py new file mode 100644 index 00000000..8d6a378e --- /dev/null +++ b/abipy/panels/fatbands.py @@ -0,0 +1,71 @@ +"""Panels for interacting with FATBANDS files.""" +import param +import panel as pn +import panel.widgets as pnw +import bokeh.models.widgets as bkw + +from .core import PanelWithElectronBands, PanelWithEbandsRobot + + +class FatBandsFilePanel(PanelWithElectronBands): + """ + Panel with widgets to interact with a |FatBandsFile|. + """ + def __init__(self, ncfile, **params): + super().__init__(**params) + self.ncfile = ncfile + + @property + def ebands(self): + """|ElectronBands|.""" + return self.ncfile.ebands + + def get_panel(self): + """Return tabs with widgets to interact with the DDB file.""" + tabs = pn.Tabs(); app = tabs.append + app(("Summary", pn.Row(bkw.PreText(text=self.ncfile.to_string(verbose=self.verbose), + sizing_mode="scale_both")))) + app(("e-Bands", pn.Row(self.get_plot_ebands_widgets(), self.on_plot_ebands_btn))) + + # Add DOS tab only if k-sampling. + if self.ncfile.ebands.kpoints.is_ibz: + app(("e-DOS", pn.Row(self.get_plot_edos_widgets(), self.on_plot_edos_btn))) + + if self.ncfile.ebands.supports_fermi_surface: + # Fermi surface requires gamma-centered k-mesh + app(("Fermi Surface", pn.Row(self.get_plot_fermi_surface_widgets(), self.on_plot_fermi_surface_btn))) + + return tabs + + +#class GsrRobotPanel(PanelWithEbandsRobot): +# """ +# A Panel to interoperate with multiple GSR files. +# """ +# +# gsr_dataframe_btn = pnw.Button(name="Compute", button_type='primary') +# +# def __init__(self, robot, **params): +# super().__init__(**params) +# self.robot = robot +# +# @param.depends("gsr_dataframe_btn.clicks") +# def on_gsr_dataframe_btn(self): +# if self.gsr_dataframe_btn.clicks == 0: return +# df = self.robot.get_dataframe(with_geo=True) +# return pn.Column(self._df(df), sizing_mode='stretch_width') +# +# def get_panel(self): +# """Return tabs with widgets to interact with the |GsrRobot|.""" +# tabs = pn.Tabs(); app = tabs.append +# app(("Summary", pn.Row(bkw.PreText(text=self.robot.to_string(verbose=self.verbose), +# sizing_mode="scale_both")))) +# app(("e-Bands", pn.Row(self.get_ebands_plotter_widgets(), self.on_ebands_plotter_btn))) +# +# # Add e-DOS tab only if all ebands have k-sampling. +# if all(abifile.ebands.kpoints.is_ibz for abifile in self.robot.abifiles): +# app(("e-DOS", pn.Row(self.get_edos_plotter_widgets(), self.on_edos_plotter_btn))) +# +# app(("GSR-DataFrame", pn.Row(self.gsr_dataframe_btn, self.on_gsr_dataframe_btn))) +# +# return tabs diff --git a/abipy/panels/gsr.py b/abipy/panels/gsr.py index d7742677..d03ee96b 100644 --- a/abipy/panels/gsr.py +++ b/abipy/panels/gsr.py @@ -28,9 +28,14 @@ class GsrFilePanel(PanelWithElectronBands): app(("e-Bands", pn.Row(self.get_plot_ebands_widgets(), self.on_plot_ebands_btn))) # Add DOS tab only if k-sampling. - if self.gsr.ebands.kpoints.is_ibz: + kpoints = self.gsr.ebands.kpoints + if kpoints.is_ibz: app(("e-DOS", pn.Row(self.get_plot_edos_widgets(), self.on_plot_edos_btn))) + if self.gsr.ebands.supports_fermi_surface: + # Fermi surface requires gamma-centered k-mesh + app(("Fermi Surface", pn.Row(self.get_plot_fermi_surface_widgets(), self.on_plot_fermi_surface_btn))) + return tabs diff --git a/abipy/panels/structure.py b/abipy/panels/structure.py index 2408d628..bd81dc8f 100644 --- a/abipy/panels/structure.py +++ b/abipy/panels/structure.py @@ -6,6 +6,7 @@ import bokeh.models.widgets as bkw from abipy.panels.core import AbipyParameterized + pn.config.js_files = { '$': 'https://code.jquery.com/jquery-3.4.1.slim.min.js', "clipboard": "https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js", @@ -79,7 +80,7 @@ class StructurePanel(AbipyParameterized): @param.depends("viewer_btn.clicks") def view(self): if self.viewer_btn.clicks == 0: return - return self.structure.nglview() + #return self.structure.nglview() #import nglview as nv #view = nv.demo(gui=False) #return view @@ -96,12 +97,12 @@ class StructurePanel(AbipyParameterized): spin_mode=self.label2mode[self.spin_mode.value], smearing=None) gs_inp.pop_vars(("charge", "chksymbreak")) + gs_inp.set_vars(ecut="?? # depends on pseudos", nband="?? # depends on pseudos") if self.gs_type.value == "relax": gs_inp.set_vars(optcell=2, ionmov=2, ecutsm=0.5, dilatmx=1.05) gs_inp.set_mnemonics(False) - #return gs_inp._repr_html_() return """
%s
diff --git a/abipy/scripts/abiopen.py b/abipy/scripts/abiopen.py index 59d1dc4a..7cd7bb47 100755 --- a/abipy/scripts/abiopen.py +++ b/abipy/scripts/abiopen.py @@ -87,7 +87,7 @@ Use `-v` to increase verbosity level (can be supplied multiple times e.g -vv). JSON file are supported as well. In this case, abiopen.py tries to reconstruct python objects assuming JSON document in MSONable format and then invokes ipython with the `data` object. -Use `-e` to print the JSON documenting without reconstructing python objects. +Use `-e` or `--notebook` or `--panel` to print the JSON dictionary without reconstructing python objects. Table mapping file extension to AbiPy object: @@ -133,8 +133,6 @@ def get_parser(with_epilog=False): help=("Set matplotlib interactive backend. " "Possible values: GTKAgg, GTK3Agg, GTK, GTKCairo, GTK3Cairo, WXAgg, WX, TkAgg, Qt4Agg, Qt5Agg, macosx." "See also: https://matplotlib.org/faq/usage_faq.html#what-is-a-backend.")) - #parser.add_argument('--pylustrator', action='store_true', default=False, - # help="Style matplotlib plots with pylustrator. See https://pylustrator.readthedocs.io/en/latest/") return parser @@ -178,11 +176,6 @@ def main(): sns.set(context=options.seaborn, style='darkgrid', palette='deep', font='sans-serif', font_scale=1, color_codes=False, rc=None) - #if options.pylustrator: - # # Start pylustrator to style matplotlib plots - # import pylustrator - # pylustrator.start() - if not os.path.exists(options.filepath): raise RuntimeError("%s: no such file" % options.filepath) @@ -272,18 +265,37 @@ Use `print(abifile)` to print the object. def handle_json(options): """Handle JSON file.""" - if not options.notebook: + if options.notebook: + # Visualize JSON document in jupyter + cmd = "jupyter-lab %s" % options.filepath + print("Executing:", cmd) + process = subprocess.Popen(cmd.split(), shell=False) #, stdout=fd, stderr=fd) + cprint("pid: %s" % str(process.pid), "yellow") + return 0 + + elif options.panel: + # Visualize JSON document in panel dashboard + import json + import panel as pn + with open(options.filepath, "rt") as fh: + d = json.load(fh) + json_pane = pn.pane.JSON(d, name='JSON', height=300, width=500) + app = pn.Row(json_pane.controls(jslink=True), json_pane) + app.show() + return 0 + + else: if options.print: - # Print object to terminal. + # Print python object to terminal. data = abilab.mjson_load(options.filepath) pprint(data, indent=4) return 0 elif options.expose: + # Pretty-print dict to terminal. import json with open(options.filepath, "rt") as fh: data = json.load(fh) pprint(data, indent=4) - return 0 data = abilab.mjson_load(options.filepath) @@ -294,13 +306,6 @@ def handle_json(options): The object initialized from JSON (MSONable) is associated to the `data` python variable. """) - else: - cmd = "jupyter-lab %s" % options.filepath - print("Executing:", cmd) - process = subprocess.Popen(cmd.split(), shell=False) #, stdout=fd, stderr=fd) - cprint("pid: %s" % str(process.pid), "yellow") - return 0 - if __name__ == "__main__": sys.exit(main()) diff --git a/docs/links.rst b/docs/links.rst index 34078e88..774669b9 100644 --- a/docs/links.rst +++ b/docs/links.rst @@ -110,6 +110,7 @@ .. |MdfFile| replace:: :class:`abipy.electrons.bse.MdfFile` .. |DdbFile| replace:: :class:`abipy.dfpt.ddb.DdbFile` .. |HistFile| replace:: :class:`abipy.dynamics.hist.HistFile` +.. |FatBandsFile| replace:: :class:`abipy.electrons.fatbands.FatBandsFile` .. |DielectricTensorGenerator| replace:: :class:`abipy.dfpt.ddb.DielectricTensorGenerator` .. |DdbRobot| replace:: :class:`abipy.dfpt.ddb.DdbRobot` .. |AnaddbNcFile| replace:: :class:`abipy.dfpt.anaddb.AnaddbNcFile`