ipyvasp 0.9.93__tar.gz → 0.9.94__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/PKG-INFO +1 -1
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/_enplots.py +27 -47
- ipyvasp-0.9.94/ipyvasp/_version.py +1 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/widgets.py +204 -106
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/PKG-INFO +1 -1
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/setup.py +1 -1
- ipyvasp-0.9.93/ipyvasp/_version.py +0 -1
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/LICENSE +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/README.md +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/__init__.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/__main__.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/_lattice.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/bsdos.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/cli.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/core/__init__.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/core/parser.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/core/plot_toolkit.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/core/serializer.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/core/spatial_toolkit.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/evals_dataframe.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/lattice.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/misc.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/potential.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp/utils.py +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/SOURCES.txt +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/dependency_links.txt +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/entry_points.txt +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/requires.txt +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/ipyvasp.egg-info/top_level.txt +0 -0
- {ipyvasp-0.9.93 → ipyvasp-0.9.94}/setup.cfg +0 -0
|
@@ -716,8 +716,9 @@ def splot_dos_lines(
|
|
|
716
716
|
**legend_kws,
|
|
717
717
|
}
|
|
718
718
|
add_legend(ax, **kwargs) # Labels are picked from plot
|
|
719
|
-
|
|
720
|
-
|
|
719
|
+
|
|
720
|
+
elim = elim if elim is not None else []
|
|
721
|
+
kws = dict(ylim=elim) if vertical else dict(xlim=elim)
|
|
721
722
|
xlabel, ylabel = "Energy (eV)", "DOS"
|
|
722
723
|
if vertical:
|
|
723
724
|
xlabel, ylabel = ylabel, xlabel
|
|
@@ -754,7 +755,7 @@ def _format_rgb_data(
|
|
|
754
755
|
if data["pros"].shape[2] == 2:
|
|
755
756
|
data["norms"][:, :, 2] = np.nan # Avoid wrong info here
|
|
756
757
|
elif data["pros"].shape[2] == 1:
|
|
757
|
-
data["
|
|
758
|
+
data["norms"][:, :, 1:] = np.nan
|
|
758
759
|
|
|
759
760
|
lws = np.sum(rgb, axis=2) # Sum of all colors
|
|
760
761
|
lws = maxwidth * lws / (float(np.max(lws)) or 1) # Normalize to maxwidth
|
|
@@ -773,8 +774,7 @@ def _format_rgb_data(
|
|
|
773
774
|
indices = range(np.shape(data["evals"])[1])
|
|
774
775
|
|
|
775
776
|
# Now process data to make single data for faster plotting.
|
|
776
|
-
|
|
777
|
-
K, E, C, S, PT, OT, KT, ET, jKbop = [], [], [], [], [], [], [], [], []
|
|
777
|
+
K, E, C, S, CDATA = [], [], [], [], []
|
|
778
778
|
for i, b in enumerate(indices):
|
|
779
779
|
K = [*K, *data["kpath"], np.nan]
|
|
780
780
|
E = [*E, *data["evals"][:, i], np.nan]
|
|
@@ -784,46 +784,26 @@ def _format_rgb_data(
|
|
|
784
784
|
"rgb(0,0,0)",
|
|
785
785
|
]
|
|
786
786
|
S = [*S, *data["widths"][:, i], data["widths"][-1, i]]
|
|
787
|
-
PT = [*PT, *[f"{txt} [{s}, {p}, {d}]" for (s, p, d) in data["norms"][:, i]], ""]
|
|
788
|
-
OT = [*OT, *[f"Occ: {t:>7.4f}" for t in data["occs"][:, i]], ""]
|
|
789
|
-
KT = [
|
|
790
|
-
*KT,
|
|
791
|
-
*[
|
|
792
|
-
f"K<sub>{j+1}</sub>: {x:>7.3f}{y:>7.3f}{z:>7.3f}"
|
|
793
|
-
for j, (x, y, z) in enumerate(data["kpoints"])
|
|
794
|
-
],
|
|
795
|
-
"",
|
|
796
|
-
]
|
|
797
|
-
ET = [
|
|
798
|
-
*ET,
|
|
799
|
-
*["{}".format(b + 1) for _ in data["kpath"]],
|
|
800
|
-
"",
|
|
801
|
-
] # Add bands subscripts to labels.
|
|
802
787
|
|
|
803
|
-
|
|
788
|
+
CDATA = [*CDATA , *[
|
|
804
789
|
{
|
|
805
790
|
"nk":j+1,
|
|
806
791
|
**{f"k{u}":v for u,v in zip("xyz",xyz)},
|
|
807
792
|
"nb":b+1,
|
|
808
793
|
"occ":occ,
|
|
809
|
-
**{c:v for c,v in zip("rgb",rgb)}
|
|
794
|
+
**{c:"" if np.isnan(v) else v for c,v in zip("rgb",rgb)}
|
|
810
795
|
}
|
|
811
|
-
for (j, xyz), occ,rgb in zip(
|
|
796
|
+
for (j, xyz), occ, rgb in zip(
|
|
812
797
|
enumerate(data["kpoints"]), data["occs"][:, i],data["norms"][:, i]
|
|
813
798
|
)
|
|
814
799
|
], {k:np.nan for k in ("nk","kx","ky","kz","nb","occ","r","g","b")}]
|
|
815
800
|
|
|
816
|
-
T = [
|
|
817
|
-
f"</br>{p} </br></br>Band: {e} {o}</br>{k}"
|
|
818
|
-
for (p, e, o, k) in zip(PT, ET, OT, KT)
|
|
819
|
-
]
|
|
820
801
|
return {
|
|
821
802
|
"K": K,
|
|
822
803
|
"E": E,
|
|
823
804
|
"C": C,
|
|
824
805
|
"S": S,
|
|
825
|
-
"
|
|
826
|
-
"jKbop": jKbop,
|
|
806
|
+
"CDATA": CDATA,
|
|
827
807
|
"labels": labels,
|
|
828
808
|
} # K, energy, marker color, marker size, text, labels that get changed
|
|
829
809
|
|
|
@@ -844,6 +824,11 @@ def _fmt_labels(ticklabels):
|
|
|
844
824
|
]
|
|
845
825
|
return ticklabels
|
|
846
826
|
|
|
827
|
+
_hover_temp = { # keep order same
|
|
828
|
+
"xy":"(%{x}, %{y})",
|
|
829
|
+
"k": "<br>K<sub>%{customdata.nk}</sub>: %{customdata.kx:.3f} %{customdata.ky:.3f} %{customdata.kz:.3f}",
|
|
830
|
+
"b":"Band: %{customdata.nb}, Occ: %{customdata.occ:.4f}"
|
|
831
|
+
}
|
|
847
832
|
|
|
848
833
|
@gu._fmt_doc(_docs)
|
|
849
834
|
def iplot_bands(
|
|
@@ -876,10 +861,7 @@ def iplot_bands(
|
|
|
876
861
|
maxwidth=1,
|
|
877
862
|
indices=indices,
|
|
878
863
|
) # moking other arrays, we need only
|
|
879
|
-
K, E
|
|
880
|
-
T = [
|
|
881
|
-
"Band" + t.split("Band")[1].split("Occ")[0] for t in T
|
|
882
|
-
] # Just Band number here
|
|
864
|
+
K, E = data["K"], data["E"]
|
|
883
865
|
|
|
884
866
|
if fig is None:
|
|
885
867
|
fig = go.Figure()
|
|
@@ -887,11 +869,12 @@ def iplot_bands(
|
|
|
887
869
|
kwargs = {
|
|
888
870
|
"mode": "markers + lines",
|
|
889
871
|
"marker": dict(size=0.1),
|
|
890
|
-
"
|
|
872
|
+
"hovertemplate": "<br>".join(_hover_temp.values()),
|
|
873
|
+
"customdata": [{k:v for k,v in d.items() if not k in 'rgb'} for d in data["CDATA"]], # useless rgb data to skip
|
|
891
874
|
**kwargs,
|
|
892
875
|
} # marker so that it is selectable by box, otherwise it does not
|
|
893
|
-
fig.add_trace(go.Scatter(x=K, y=E,
|
|
894
|
-
|
|
876
|
+
fig.add_trace(go.Scatter(x=K, y=E, **kwargs))
|
|
877
|
+
|
|
895
878
|
fig.update_layout(
|
|
896
879
|
template="plotly_white",
|
|
897
880
|
title=(
|
|
@@ -947,14 +930,7 @@ def iplot_rgb_lines(
|
|
|
947
930
|
data = _format_rgb_data(
|
|
948
931
|
K, E, pros, labels, interp, occs, kpoints, maxwidth=maxwidth, indices=indices
|
|
949
932
|
)
|
|
950
|
-
K, E, C, S,
|
|
951
|
-
data["K"],
|
|
952
|
-
data["E"],
|
|
953
|
-
data["C"],
|
|
954
|
-
data["S"],
|
|
955
|
-
data["T"],
|
|
956
|
-
data["labels"],
|
|
957
|
-
)
|
|
933
|
+
K, E, C, S, labels = [data[key] for key in "K E C S labels".split()]
|
|
958
934
|
|
|
959
935
|
if fig is None:
|
|
960
936
|
fig = go.Figure()
|
|
@@ -963,14 +939,18 @@ def iplot_rgb_lines(
|
|
|
963
939
|
kwargs.pop("marker_size", None) # Provided by S
|
|
964
940
|
kwargs.update(
|
|
965
941
|
{
|
|
966
|
-
"hovertext": T,
|
|
967
942
|
"marker": {
|
|
968
943
|
"line_color": "rgba(0,0,0,0)",
|
|
969
944
|
**kwargs.get("marker", {}),
|
|
970
945
|
"color": C,
|
|
971
946
|
"size": S,
|
|
972
947
|
},
|
|
973
|
-
"
|
|
948
|
+
"hovertemplate": "<br>".join([_hover_temp["xy"],
|
|
949
|
+
"<br>Projection: [{}, {}, {}]".format(*labels), # clean labels instead of ''
|
|
950
|
+
"Value: [%{customdata.r}, %{customdata.g}, %{customdata.b}]",
|
|
951
|
+
_hover_temp["k"], _hover_temp["b"],
|
|
952
|
+
]),
|
|
953
|
+
"customdata": data["CDATA"], # need for selection and hover template
|
|
974
954
|
}
|
|
975
955
|
) # marker edge should be free
|
|
976
956
|
|
|
@@ -983,7 +963,7 @@ def iplot_rgb_lines(
|
|
|
983
963
|
+ ", ".join(labels)
|
|
984
964
|
+ "]", # Do not set autosize = False, need to be responsive in widgets boxes
|
|
985
965
|
margin=go.layout.Margin(l=60, r=50, b=40, t=75, pad=0),
|
|
986
|
-
yaxis=go.layout.YAxis(title_text="Energy (eV)",
|
|
966
|
+
yaxis=go.layout.YAxis(title_text="Energy (eV)",range=elim or [min(E), max(E)]),
|
|
987
967
|
xaxis=go.layout.XAxis(
|
|
988
968
|
ticktext=_fmt_labels(xticklabels),
|
|
989
969
|
tickvals=xticks,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.94"
|
|
@@ -24,6 +24,7 @@ from ipywidgets import (
|
|
|
24
24
|
Text,
|
|
25
25
|
Stack,
|
|
26
26
|
SelectMultiple,
|
|
27
|
+
TagsInput,
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
# More imports
|
|
@@ -182,9 +183,9 @@ class Files:
|
|
|
182
183
|
"Apply a func(path) -> dict and create a dataframe."
|
|
183
184
|
return summarize(self._files,func, **kwargs)
|
|
184
185
|
|
|
185
|
-
def load_results(self):
|
|
186
|
-
"Load result.json files from these paths into a dataframe."
|
|
187
|
-
return load_results(self._files)
|
|
186
|
+
def load_results(self,exclude_keys=None):
|
|
187
|
+
"Load result.json files from these paths into a dataframe, with optionally excluding keys."
|
|
188
|
+
return load_results(self._files,exclude_keys=exclude_keys)
|
|
188
189
|
|
|
189
190
|
def input_info(self, *tags):
|
|
190
191
|
"Grab input information into a dataframe from POSCAR and INCAR. Provide INCAR tags (case-insinsitive) to select only few of them."
|
|
@@ -328,7 +329,6 @@ class Files:
|
|
|
328
329
|
return self.summarize(lambda path: {"size": get_file_size(path)})
|
|
329
330
|
|
|
330
331
|
|
|
331
|
-
|
|
332
332
|
@fix_signature
|
|
333
333
|
class _PropPicker(VBox):
|
|
334
334
|
"""Single projection picker with atoms and orbitals selection"""
|
|
@@ -336,9 +336,12 @@ class _PropPicker(VBox):
|
|
|
336
336
|
|
|
337
337
|
def __init__(self, system_summary=None):
|
|
338
338
|
super().__init__()
|
|
339
|
-
self._atoms =
|
|
340
|
-
|
|
339
|
+
self._atoms = TagsInput(description="Atoms", allowed_tags=[],
|
|
340
|
+
placeholder="Select atoms", allow_duplicates = False).add_class('props-tags')
|
|
341
|
+
self._orbs = TagsInput(description="Orbs", allowed_tags=[],
|
|
342
|
+
placeholder="Select orbitals", allow_duplicates = False).add_class('props-tags')
|
|
341
343
|
self.children = [self._atoms, self._orbs]
|
|
344
|
+
self.layout.width = '100%' # avoid horizontal collapse
|
|
342
345
|
self._atoms_map = {}
|
|
343
346
|
self._orbs_map = {}
|
|
344
347
|
|
|
@@ -349,13 +352,24 @@ class _PropPicker(VBox):
|
|
|
349
352
|
|
|
350
353
|
def _update_props(self, change):
|
|
351
354
|
"""Update props trait when selections change"""
|
|
352
|
-
|
|
353
|
-
|
|
355
|
+
_atoms = [self._atoms_map.get(tag, None) for tag in self._atoms.value]
|
|
356
|
+
_orbs = [self._orbs_map.get(tag, None) for tag in self._orbs.value]
|
|
354
357
|
|
|
358
|
+
# Filter out None values, and flatten
|
|
359
|
+
# Flatten and filter atoms
|
|
360
|
+
atoms = []
|
|
361
|
+
for ats in _atoms:
|
|
362
|
+
atoms.extend(ats if ats is not None else [])
|
|
363
|
+
|
|
364
|
+
# Flatten and filter orbitals
|
|
365
|
+
orbs = []
|
|
366
|
+
for ors in _orbs:
|
|
367
|
+
orbs.extend(ors if ors is not None else [])
|
|
368
|
+
|
|
355
369
|
if atoms and orbs:
|
|
356
|
-
self.props = {
|
|
357
|
-
'atoms': atoms, 'orbs': orbs,
|
|
358
|
-
'label': f"{self._atoms.value
|
|
370
|
+
self.props = {
|
|
371
|
+
'atoms': atoms, 'orbs': orbs,
|
|
372
|
+
'label': f"{'+'.join(self._atoms.value)} | {'+'.join(self._orbs.value)}"
|
|
359
373
|
}
|
|
360
374
|
else:
|
|
361
375
|
self.props = {}
|
|
@@ -363,11 +377,10 @@ class _PropPicker(VBox):
|
|
|
363
377
|
def _process(self, system_summary):
|
|
364
378
|
"""Process system data and setup widget options"""
|
|
365
379
|
if system_summary is None or not hasattr(system_summary, "orbs"):
|
|
366
|
-
|
|
367
|
-
return
|
|
380
|
+
return
|
|
368
381
|
|
|
369
382
|
sorbs = system_summary.orbs
|
|
370
|
-
self._orbs_map = {"
|
|
383
|
+
self._orbs_map = {"All": range(len(sorbs)), "s": [0]}
|
|
371
384
|
|
|
372
385
|
# p-orbitals
|
|
373
386
|
if set(["px", "py", "pz"]).issubset(sorbs):
|
|
@@ -397,19 +410,17 @@ class _PropPicker(VBox):
|
|
|
397
410
|
k: [idx] for idx, k in enumerate(sorbs[16:], start=16)
|
|
398
411
|
})
|
|
399
412
|
|
|
400
|
-
self._orbs.
|
|
413
|
+
self._orbs.allowed_tags = list(self._orbs_map.keys())
|
|
401
414
|
|
|
402
415
|
# Process atoms
|
|
403
416
|
self._atoms_map = {
|
|
404
|
-
"-": [],
|
|
405
417
|
"All": range(system_summary.NIONS),
|
|
406
418
|
**{k: v for k,v in system_summary.types.to_dict().items()},
|
|
407
419
|
**{f"{k}{n}": [v] for k,tp in system_summary.types.to_dict().items()
|
|
408
420
|
for n,v in enumerate(tp, 1)}
|
|
409
421
|
}
|
|
410
|
-
self._atoms.
|
|
411
|
-
self.
|
|
412
|
-
self._update_props(None) # then props trigger top projections
|
|
422
|
+
self._atoms.allowed_tags = list(self._atoms_map.keys())
|
|
423
|
+
self._update_props(None) # Trigger props update
|
|
413
424
|
|
|
414
425
|
def update(self, system_summary):
|
|
415
426
|
"""Update widget with new system data while preserving selections"""
|
|
@@ -418,10 +429,8 @@ class _PropPicker(VBox):
|
|
|
418
429
|
self._process(system_summary)
|
|
419
430
|
|
|
420
431
|
# Restore previous selections if still valid
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if old_orbs in self._orbs.options:
|
|
424
|
-
self._orbs.value = old_orbs
|
|
432
|
+
self._atoms.value = [tag for tag in old_atoms if tag in self._atoms.allowed_tags]
|
|
433
|
+
self._orbs.value = [tag for tag in old_orbs if tag in self._orbs.allowed_tags]
|
|
425
434
|
|
|
426
435
|
@fix_signature
|
|
427
436
|
class PropsPicker(VBox): # NOTE: remove New Later
|
|
@@ -475,8 +484,44 @@ class PropsPicker(VBox): # NOTE: remove New Later
|
|
|
475
484
|
for picker in self._pickers:
|
|
476
485
|
picker.update(system_summary)
|
|
477
486
|
|
|
478
|
-
def
|
|
479
|
-
"
|
|
487
|
+
def _clean_legacy_data(path):
|
|
488
|
+
"clean old style keys like VBM to vbm"
|
|
489
|
+
data = serializer.load(path.absolute()) # Old data loaded
|
|
490
|
+
if not any(key in data for key in ['VBM', 'α','vbm_k']):
|
|
491
|
+
return data # already clean
|
|
492
|
+
|
|
493
|
+
keys_map = {
|
|
494
|
+
"SYSTEM": "sys",
|
|
495
|
+
"VBM": "vbm", # Old: New
|
|
496
|
+
"CBM": "cbm",
|
|
497
|
+
"VBM_k": "kvbm", "vbm_k": "kvbm",
|
|
498
|
+
"CBM_k": "kcbm", "cbm_k": "kcbm",
|
|
499
|
+
"E_gap": "gap",
|
|
500
|
+
"\u0394_SO": "soc",
|
|
501
|
+
"α": "alpha",
|
|
502
|
+
"β": "beta",
|
|
503
|
+
"γ": "gamma",
|
|
504
|
+
}
|
|
505
|
+
new_data = {k:v for k,v in data.items() if k not in (*keys_map.keys(),*keys_map.values())} # keep other data
|
|
506
|
+
for old, new in keys_map.items():
|
|
507
|
+
if old in data:
|
|
508
|
+
new_data[new] = data[old] # Transfer value from old key to new key
|
|
509
|
+
elif new in data:
|
|
510
|
+
new_data[new] = data[new] # Keep existing new style keys
|
|
511
|
+
|
|
512
|
+
# save cleaned data
|
|
513
|
+
serializer.dump(new_data,format="json",outfile=path)
|
|
514
|
+
return new_data
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def load_results(paths_list, exclude_keys=None):
|
|
518
|
+
"Loads result.json from paths_list and returns a dataframe. Use exclude_keys to get subset of data."
|
|
519
|
+
if exclude_keys is not None:
|
|
520
|
+
if not isinstance(exclude_keys, (list,tuple)):
|
|
521
|
+
raise TypeError(f"exclude_keys should be list of keys, got {type(exclude_keys)}")
|
|
522
|
+
if not all([isinstance(key,str) for key in exclude_keys]):
|
|
523
|
+
raise TypeError(f"all keys in exclude_keys should be str!")
|
|
524
|
+
|
|
480
525
|
paths_list = [Path(p) for p in paths_list]
|
|
481
526
|
result_paths = []
|
|
482
527
|
if paths_list:
|
|
@@ -488,7 +533,8 @@ def load_results(paths_list):
|
|
|
488
533
|
|
|
489
534
|
def load_data(path):
|
|
490
535
|
try:
|
|
491
|
-
|
|
536
|
+
data = _clean_legacy_data(path)
|
|
537
|
+
return {k:v for k,v in data.items() if k not in (exclude_keys or [])}
|
|
492
538
|
except:
|
|
493
539
|
return {} # If not found, return empty dictionary
|
|
494
540
|
|
|
@@ -519,6 +565,23 @@ def _get_css(mode):
|
|
|
519
565
|
},
|
|
520
566
|
'.footer': {'overflow': 'auto','padding':0},
|
|
521
567
|
'.widget-vslider, .jupyter-widget-vslider': {'width': 'auto'}, # otherwise it spans too much area
|
|
568
|
+
'table': { # dataframe display sucks
|
|
569
|
+
'color':'var(--jp-content-font-color1)',
|
|
570
|
+
'background':'var(--jp-layout-color1)',
|
|
571
|
+
'tr': {
|
|
572
|
+
'^:nth-child(odd)': {'background':'var(--jp-widgets-input-background-color)',},
|
|
573
|
+
'^:nth-child(even)': {'background':'var(--jp-layout-color1)',},
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
'.props-picker': {
|
|
577
|
+
'background': 'var(--jp-widgets-input-background-color)', # make feels like single widget
|
|
578
|
+
'overflow-x': 'hidden', 'border-radius': '4px', 'padding': '4px',
|
|
579
|
+
},
|
|
580
|
+
'.props-tags': {
|
|
581
|
+
'background':'var(--jp-layout-color1)', 'border-radius': '4px', 'padding': '4px',
|
|
582
|
+
'> input': {'width': '100%'},
|
|
583
|
+
'> input::placeholder': {'color': 'var(--jp-ui-font-color1)'},
|
|
584
|
+
},
|
|
522
585
|
}
|
|
523
586
|
|
|
524
587
|
class _ThemedFigureInteract(ei.InteractBase):
|
|
@@ -566,13 +629,6 @@ class _ThemedFigureInteract(ei.InteractBase):
|
|
|
566
629
|
raise AttributeError("self._files = Files(...) was never set!")
|
|
567
630
|
return self._files
|
|
568
631
|
|
|
569
|
-
# NOTE: This to impelemet as selection
|
|
570
|
-
# import pandas as pd
|
|
571
|
-
|
|
572
|
-
# data = {k:v for k,v in kw.selected_data.items() if k != 'customdata' and 'indexes' not in k}
|
|
573
|
-
# data.update(pd.DataFrame(kw.selected_data.get('customdata',{})).to_dict(orient='list'))
|
|
574
|
-
|
|
575
|
-
# df = pd.DataFrame(data)
|
|
576
632
|
|
|
577
633
|
@fix_signature
|
|
578
634
|
class BandsWidget(_ThemedFigureInteract):
|
|
@@ -581,23 +637,44 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
581
637
|
You can observe three traits:
|
|
582
638
|
|
|
583
639
|
- file: Currently selected file
|
|
584
|
-
- clicked_data: Last clicked point data,
|
|
585
|
-
- selected_data: Last selection of points within a box or lasso
|
|
640
|
+
- clicked_data: Last clicked point data, which can be directly passed to a dataframe.
|
|
641
|
+
- selected_data: Last selection of points within a box or lasso, which can be directly passed to a dataframe and plotted accordingly.
|
|
586
642
|
|
|
587
643
|
- You can use `self.files.update` method to change source files without effecting state of widget.
|
|
588
|
-
- You can also use `self.iplot`, `self.splot` with `self.kws` to get static plts of current state.
|
|
644
|
+
- You can also use `self.iplot`, `self.splot` with `self.kws` to get static plts of current state, and self.results to get a dataframe.
|
|
645
|
+
- You can use store_clicks to provide extra names of points you want to click and save data, besides default ones.
|
|
589
646
|
"""
|
|
590
647
|
file = traitlets.Any(allow_none=True)
|
|
591
648
|
clicked_data = traitlets.Dict(allow_none=True)
|
|
592
649
|
selected_data = traitlets.Dict(allow_none=True)
|
|
593
650
|
|
|
594
|
-
def __init__(self, files, height="
|
|
651
|
+
def __init__(self, files, height="600px", store_clicks=None):
|
|
595
652
|
self.add_class("BandsWidget")
|
|
653
|
+
self._kb_fig = go.FigureWidget() # for extra stuff
|
|
654
|
+
self._kb_fig.update_layout(margin=dict(l=40, r=0, b=40, t=40, pad=0)) # show compact
|
|
596
655
|
self._files = Files(files)
|
|
597
656
|
self._bands = None
|
|
598
657
|
self._kws = {}
|
|
599
658
|
self._result = {}
|
|
600
|
-
|
|
659
|
+
self._extra_clicks = ()
|
|
660
|
+
|
|
661
|
+
if store_clicks is not None:
|
|
662
|
+
if not isinstance(store_clicks, (list,tuple)):
|
|
663
|
+
raise TypeError("store_clicks should be list of names "
|
|
664
|
+
f"of point to be stored from click on figure, got {type(store_clicks)}")
|
|
665
|
+
|
|
666
|
+
for name in store_clicks:
|
|
667
|
+
if not isinstance(name, str) or not name.isidentifier():
|
|
668
|
+
raise ValueError(f"items in store_clicks should be a valid python variable name, got {name!r}")
|
|
669
|
+
if name in ["vbm", "cbm", "so_max", "so_min"]:
|
|
670
|
+
raise ValueError(f"{name!r} already exists in default click points!")
|
|
671
|
+
reserved = "gap soc v a b c alpha beta gamma direct".split()
|
|
672
|
+
if name in reserved:
|
|
673
|
+
raise ValueError(f"{name!r} conflicts with reserved keys {reserved}")
|
|
674
|
+
|
|
675
|
+
self._extra_clicks += tuple(store_clicks)
|
|
676
|
+
|
|
677
|
+
super().__init__() # after extra clicks
|
|
601
678
|
|
|
602
679
|
traitlets.dlink((self.params.file,'value'),(self, 'file'))
|
|
603
680
|
traitlets.dlink((self.params.fig,'clicked'),(self, 'clicked_data'))
|
|
@@ -606,7 +683,7 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
606
683
|
self.relayout(
|
|
607
684
|
left_sidebar=[
|
|
608
685
|
'head','file','krange','kticks','brange', 'ppicks',
|
|
609
|
-
[HBox(),('theme','button')],
|
|
686
|
+
[HBox(),('theme','button')], 'kb_fig',
|
|
610
687
|
],
|
|
611
688
|
center=['hdata','fig','cpoint'], footer = self.groups.outputs,
|
|
612
689
|
right_sidebar = ['showft'],
|
|
@@ -614,13 +691,29 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
614
691
|
height=height
|
|
615
692
|
)
|
|
616
693
|
|
|
694
|
+
@traitlets.validate('selected_data','clicked_data')
|
|
695
|
+
def _flatten_dict(self, proposal):
|
|
696
|
+
data = proposal['value']
|
|
697
|
+
if data is None: return None # allow None stuff
|
|
698
|
+
|
|
699
|
+
if not isinstance(data, dict):
|
|
700
|
+
raise traitlets.TraitError(f"Expected a dict for selected_data, got {type(data)}")
|
|
701
|
+
|
|
702
|
+
_data = {k:v for k,v in data.items() if k != 'customdata' and 'indexes' not in k}
|
|
703
|
+
_data.update(pd.DataFrame(data.get('customdata',{})).to_dict(orient='list'))
|
|
704
|
+
return _data # since we know customdata, we can flatten dict
|
|
705
|
+
|
|
706
|
+
|
|
617
707
|
@ei.callback
|
|
618
708
|
def _update_theme(self, fig, theme):
|
|
619
|
-
|
|
709
|
+
super()._update_theme(fig, theme)
|
|
710
|
+
self._kb_fig.layout.template = fig.layout.template
|
|
711
|
+
self._kb_fig.layout.autosize = True
|
|
620
712
|
|
|
621
713
|
def _interactive_params(self):
|
|
622
714
|
return dict(
|
|
623
715
|
fig = self._fig, theme = self._theme, # include theme and fig
|
|
716
|
+
kb_fig = self._kb_fig, # show selected data
|
|
624
717
|
head = ipw.HTML("<b>Band Structure Visualizer</b>"),
|
|
625
718
|
file = self.files.to_dropdown(),
|
|
626
719
|
ppicks = PropsPicker(),
|
|
@@ -629,13 +722,39 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
629
722
|
kticks = Text(description="kticks", tooltip="0 index maps to minimum value of kpoints slider."),
|
|
630
723
|
brange = ipw.IntRangeSlider(description="bands",min=1, max=1), # number, not index
|
|
631
724
|
cpoint = ipw.ToggleButtons(description="Select from options and click on figure to store data points",
|
|
632
|
-
value=None, options=["vbm", "cbm"]), # the point where clicked
|
|
633
|
-
showft = ipw.IntSlider(description = 'h', orientation='vertical',min=0,max=50, value=0),
|
|
634
|
-
cdata =
|
|
635
|
-
projs =
|
|
725
|
+
value=None, options=["vbm", "cbm", *self._extra_clicks]).add_class('content-width-button'), # the point where clicked
|
|
726
|
+
showft = ipw.IntSlider(description = 'h', orientation='vertical',min=0,max=50, value=0,tooltip="outputs area's height ratio"),
|
|
727
|
+
cdata = 'fig.clicked',
|
|
728
|
+
projs = 'ppicks.projections', # for visual feedback on button
|
|
729
|
+
sdata = '.selected_data',
|
|
636
730
|
hdata = ipw.HTML(), # to show data in one place
|
|
637
731
|
)
|
|
638
732
|
|
|
733
|
+
@ei.callback('out-selected')
|
|
734
|
+
def _plot_data(self, kb_fig, sdata):
|
|
735
|
+
kb_fig.data = [] # clear in any case to avoid confusion
|
|
736
|
+
if not sdata: return # no change
|
|
737
|
+
|
|
738
|
+
df = pd.DataFrame(sdata)
|
|
739
|
+
if 'r' in sdata:
|
|
740
|
+
arr = df[['r','g','b']].to_numpy()
|
|
741
|
+
arr[arr == ''] = 0
|
|
742
|
+
arr, fmt = arr / (arr.max() or 1), lambda v : int(v*255) # color norms
|
|
743
|
+
df['color'] = [f"rgb({fmt(r)},{fmt(g)},{fmt(b)})" for r,g,b in arr]
|
|
744
|
+
else:
|
|
745
|
+
df['color'] = sdata['occ']
|
|
746
|
+
|
|
747
|
+
df['msize'] = df['occ']*7 + 10
|
|
748
|
+
cdata = (df[["ys","occ","r","g","b"]] if 'r' in sdata else df[['ys','occ']]).to_numpy()
|
|
749
|
+
rgb_temp = '<br>orbs: (%{customdata[2]},%{customdata[3]},%{customdata[4]})' if 'r' in sdata else ''
|
|
750
|
+
|
|
751
|
+
kb_fig.add_trace(go.Scatter(x=df.nk, y = df.nb, mode = 'markers', marker = dict(size=df.msize,color=df.color), customdata=cdata))
|
|
752
|
+
kb_fig.update_traces(hovertemplate=f"nk: %{{x}}, nb: %{{y}})<br>en: %{{customdata[0]:.4f}}<br>occ: %{{customdata[1]:.4f}}{rgb_temp}<extra></extra>")
|
|
753
|
+
kb_fig.update_layout(template = self._fig.layout.template, autosize=True,
|
|
754
|
+
title = "Selected Data", showlegend=False,coloraxis_showscale=False,
|
|
755
|
+
margin=dict(l=40, r=0, b=40, t=40, pad=0),font=dict(family="stix, serif", size=14)
|
|
756
|
+
)
|
|
757
|
+
|
|
639
758
|
@ei.callback('out-data')
|
|
640
759
|
def _load_data(self, file):
|
|
641
760
|
if not file: return # First time not available
|
|
@@ -654,11 +773,11 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
654
773
|
|
|
655
774
|
self.params.brange.max = self.bands.source.summary.NBANDS
|
|
656
775
|
if self.bands.source.summary.LSORBIT:
|
|
657
|
-
self.params.cpoint.options = ["vbm", "cbm", "so_max", "so_min"]
|
|
776
|
+
self.params.cpoint.options = ["vbm", "cbm", "so_max", "so_min", *self._extra_clicks]
|
|
658
777
|
else:
|
|
659
|
-
self.params.cpoint.options = ["vbm", "cbm"]
|
|
778
|
+
self.params.cpoint.options = ["vbm", "cbm",*self._extra_clicks]
|
|
660
779
|
if (path := file.parent / "result.json").is_file():
|
|
661
|
-
self._result =
|
|
780
|
+
self._result = _clean_legacy_data(path)
|
|
662
781
|
|
|
663
782
|
pdata = self.bands.source.poscar.data
|
|
664
783
|
self._result.update(
|
|
@@ -669,38 +788,6 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
669
788
|
}
|
|
670
789
|
)
|
|
671
790
|
self._show_data(self._result) # Load into view
|
|
672
|
-
|
|
673
|
-
def _clean_legacy_data(self, path):
|
|
674
|
-
"clean old style keys like VBM to vbm"
|
|
675
|
-
data = serializer.load(str(path.absolute())) # Old data loaded
|
|
676
|
-
|
|
677
|
-
if not any(key in data for key in ['VBM', 'α','vbm_k']):
|
|
678
|
-
return data # already clean
|
|
679
|
-
|
|
680
|
-
keys_map = {
|
|
681
|
-
"SYSTEM": "sys",
|
|
682
|
-
"VBM": "vbm", # Old: New
|
|
683
|
-
"CBM": "cbm",
|
|
684
|
-
"VBM_k": "kvbm",
|
|
685
|
-
"CBM_k": "kcbm",
|
|
686
|
-
"E_gap": "gap",
|
|
687
|
-
"\u0394_SO": "soc", "so_max":"so_max","so_min":"so_min", # need to include keys
|
|
688
|
-
"V": "v",
|
|
689
|
-
"α": "alpha",
|
|
690
|
-
"β": "beta",
|
|
691
|
-
"γ": "gamma",
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
new_data = {}
|
|
695
|
-
for old, new in keys_map.items():
|
|
696
|
-
if old in data:
|
|
697
|
-
new_data[new] = data[old] # Transfer value from old key to new key
|
|
698
|
-
elif new in data:
|
|
699
|
-
new_data[new] = data[new] # Keep existing new style keys
|
|
700
|
-
|
|
701
|
-
# save cleaned data
|
|
702
|
-
serializer.dump(new_data,format="json",outfile=path)
|
|
703
|
-
return new_data
|
|
704
791
|
|
|
705
792
|
@ei.callback
|
|
706
793
|
def _toggle_footer(self, showft):
|
|
@@ -711,7 +798,7 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
711
798
|
self._kws["kpairs"] = [krange,]
|
|
712
799
|
|
|
713
800
|
@ei.callback
|
|
714
|
-
def _warn_update(self, file
|
|
801
|
+
def _warn_update(self, file, kticks, brange, krange, projs):
|
|
715
802
|
self.params.button.description = "🔴 Update Graph"
|
|
716
803
|
|
|
717
804
|
@ei.callback('out-graph')
|
|
@@ -734,17 +821,18 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
734
821
|
_bands = range(l-1, h) # from number to index
|
|
735
822
|
|
|
736
823
|
self._kws = {**self._kws, "kticks": kticks, "bands": _bands}
|
|
737
|
-
|
|
824
|
+
ISPIN = self.bands.source.summary.ISPIN
|
|
738
825
|
if self.params.ppicks.projections:
|
|
739
|
-
self._kws
|
|
740
|
-
_fig = self.bands.iplot_rgb_lines(**self._kws, name="Up")
|
|
741
|
-
if
|
|
826
|
+
self._kws["projections"] = self.params.ppicks.projections
|
|
827
|
+
_fig = self.bands.iplot_rgb_lines(**self._kws, name="Up" if ISPIN == 2 else "")
|
|
828
|
+
if ISPIN == 2:
|
|
742
829
|
self.bands.iplot_rgb_lines(**self._kws, spin=1, name="Down", fig=fig)
|
|
743
830
|
|
|
744
831
|
self.iplot = partial(self.bands.iplot_rgb_lines, **self._kws)
|
|
745
832
|
self.splot = partial(self.bands.splot_rgb_lines, **self._kws)
|
|
746
833
|
else:
|
|
747
|
-
|
|
834
|
+
self._kws.pop("projections",None) # may be previous one
|
|
835
|
+
_fig = self.bands.iplot_bands(**self._kws, name="Up" if ISPIN == 2 else "")
|
|
748
836
|
if self.bands.source.summary.ISPIN == 2:
|
|
749
837
|
self.bands.iplot_bands(**self._kws, spin=1, name="Down", fig=fig)
|
|
750
838
|
|
|
@@ -758,21 +846,26 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
758
846
|
|
|
759
847
|
@ei.callback('out-click')
|
|
760
848
|
def _click_save_data(self, cdata):
|
|
761
|
-
if self.params.cpoint.value is None:
|
|
762
|
-
return self._show_and_save(self._result)
|
|
763
|
-
|
|
849
|
+
if self.params.cpoint.value is None: return # at reset-
|
|
764
850
|
data_dict = self._result.copy() # Copy old data
|
|
765
851
|
|
|
766
852
|
if cdata: # No need to make empty dict
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
853
|
+
key = self.params.cpoint.value
|
|
854
|
+
if key:
|
|
855
|
+
y = round(float(*cdata['ys']) + self.bands.data.ezero, 6) # Add ezero
|
|
856
|
+
if not key in self._extra_clicks:
|
|
857
|
+
data_dict[key] = y # Assign value back
|
|
858
|
+
|
|
772
859
|
if not key.startswith("so_"): # not spin-orbit points
|
|
773
860
|
cst, = cdata.get('customdata',[{}]) # single item
|
|
774
861
|
kp = [cst.get(f"k{n}", None) for n in 'xyz']
|
|
775
|
-
|
|
862
|
+
kp = tuple([round(k,6) if k else k for k in kp])
|
|
863
|
+
|
|
864
|
+
if key in ("vbm","cbm"):
|
|
865
|
+
data_dict[f"k{key}"] = kp
|
|
866
|
+
else: # user points, stor both for reference
|
|
867
|
+
data_dict[key] = {"k":kp,"e":y}
|
|
868
|
+
|
|
776
869
|
|
|
777
870
|
if data_dict.get("vbm", None) and data_dict.get("cbm", None):
|
|
778
871
|
data_dict["gap"] = np.round(data_dict["cbm"] - data_dict["vbm"], 6)
|
|
@@ -783,29 +876,33 @@ class BandsWidget(_ThemedFigureInteract):
|
|
|
783
876
|
)
|
|
784
877
|
|
|
785
878
|
self._result.update(data_dict) # store new data
|
|
786
|
-
self._show_and_save(self._result)
|
|
879
|
+
self._show_and_save(self._result, f"{key} = {data_dict[key]}")
|
|
787
880
|
self.params.cpoint.value = None # Reset to None to avoid accidental click at end
|
|
788
881
|
|
|
789
|
-
def _show_data(self, data):
|
|
882
|
+
def _show_data(self, data, last_click=None):
|
|
790
883
|
"Show data in html widget, no matter where it was called."
|
|
791
|
-
|
|
792
|
-
|
|
884
|
+
keys = "sys vbm cbm gap direct soc v a b c alpha beta gamma".split()
|
|
885
|
+
data = {key:data[key] for key in keys if key in data} # show only standard data
|
|
886
|
+
kv, kc = [self._result.get(k,[None]*3) for k in ('kvbm','kcbm')]
|
|
793
887
|
data['direct'] = (kv == kc) if None not in kv else False
|
|
888
|
+
|
|
889
|
+
# Add a caption to the table
|
|
890
|
+
caption = f"<caption style='caption-side:bottom; opacity:0.7;'><code>{last_click or 'clicked data is shown here'}</code></caption>"
|
|
891
|
+
|
|
794
892
|
headers = "".join(f"<th>{key}</th>" for key in data.keys())
|
|
795
893
|
values = "".join(f"<td>{format(value, '.4f') if isinstance(value, float) else value}</td>" for value in data.values())
|
|
796
894
|
self.params.hdata.value = f"""<table border='1' style='width:100%;max-width:100% !important;border-collapse:collapse;'>
|
|
797
|
-
<tr>{headers}</tr>\n<tr>{values}</tr></table>"""
|
|
895
|
+
{caption}<tr>{headers}</tr>\n<tr>{values}</tr></table>"""
|
|
798
896
|
|
|
799
|
-
def _show_and_save(self, data_dict):
|
|
800
|
-
self._show_data(data_dict)
|
|
897
|
+
def _show_and_save(self, data_dict, last_click=None):
|
|
898
|
+
self._show_data(data_dict,last_click=last_click)
|
|
801
899
|
if self.file:
|
|
802
900
|
serializer.dump(data_dict,format="json",
|
|
803
901
|
outfile=self.file.parent / "result.json")
|
|
804
902
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
return load_results(self.params.file.options)
|
|
903
|
+
def results(self, exclude_keys=None):
|
|
904
|
+
"Generate a dataframe form result.json file in each folder, with optionally excluding keys."
|
|
905
|
+
return load_results(self.params.file.options, exclude_keys=exclude_keys)
|
|
809
906
|
|
|
810
907
|
@property
|
|
811
908
|
def source(self):
|
|
@@ -876,7 +973,7 @@ class KPathWidget(_ThemedFigureInteract):
|
|
|
876
973
|
lab = Text(description="Labels", continuous_update=True),
|
|
877
974
|
kpt = Text(description="KPOINT", continuous_update=False),
|
|
878
975
|
delp = Button(description=" ", icon='trash', tooltip="Delete Selected Points"),
|
|
879
|
-
click =
|
|
976
|
+
click = 'fig.clicked',
|
|
880
977
|
lock = Button(description=" ", icon='unlock', tooltip="Lock/Unlock adding more points"),
|
|
881
978
|
info = ipw.HTML(), # consise information in one place
|
|
882
979
|
)
|
|
@@ -935,6 +1032,7 @@ class KPathWidget(_ThemedFigureInteract):
|
|
|
935
1032
|
def _update_theme(self, fig, theme):
|
|
936
1033
|
super()._update_theme(fig, theme) # call parent method, but important
|
|
937
1034
|
|
|
1035
|
+
|
|
938
1036
|
@ei.callback
|
|
939
1037
|
def _toggle_lock(self, lock):
|
|
940
1038
|
self.params.lock.icon = 'lock' if self.params.lock.icon == 'unlock' else 'unlock'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.93"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|