#!/usr/bin/env python
from __future__ import annotations
#import sys
import os
import functools
import itertools
import param
import panel as pn
import pandas as pd
import panel.widgets as pnw
from monty.termcolor import cprint
from monty.string import list_strings
from abipy.panels.core import AbipyParameterized, depends_on_btn_click, mpl, dfc, ply, ButtonContext, Loading
from abipy.tools.numtools import build_mesh
from abipy.ppcodes.ppgen import OncvGenerator
#from abipy.ppcodes.oncv_parser import OncvParser
GE_ANNOTATED = """
# Copied from ~oncvpsp/doc/32_Ge_annotated.dat
# ATOM AND REFERENCE CONFIGURATION
#
### atsym atomic symbol
### z atomic number
### nc number of core states
### nv number of valence states
### iexc exchange-correlation functional: 1-Wigner, 2-Hedin-Lundquist,
### 3-Perdew-Zunger-Ceperly-Alder, 4-Perdew-Burke-Enzerhof
### psfile format of pseudopotential file, psp8 for ABINIT, upf for PWSCF
#
# atsym, z, nc, nv, iexc psfile
Ge 32.0 5 3 3 psp8
#
#
### n principal quantum number
### l angular momentum
### f occupancy (MUST be >0)
#
# n, l, f (nc+nv lines)
1 0 2.0
2 0 2.0
2 1 6.0
3 0 2.0
3 1 6.0
3 2 10.0
4 0 2.0
4 1 2.0
#
# PSEUDOPOTENTIAL AND OPTIMIZATION
#
###lmax maximum angular momentum for which psp is calculated (<=3)
#
# lmax
2
#
#
### l angular momentum
### rc core radius for this l
### ep energy at which psp is generated (eigenvalue inserted for occupied
### state in reference configuration, positive energy must be specified
### for barrier-confined "scattering" state for unoccupied l <=lmax
### A small positive energy is usually good (0.1-0.25 Ha).
### ncon number of constraints for pseudo wave function to match all-
### electron wave function at rc, value + ncon-1 derivatives,
### must be between 3 and 5 ("M" in the paper, Eq.(6))
### nbas number of basis functions. Must be between ncon+2 and ncon+5
### ("N" in the paper, Eqs.(4-5))
### qcut wave vector defining "residual energy" in the RRKJ method
### ("q_c" in the paper, Eq.(1)
#
# l, rc, ep, ncon, nbas, qcut (lmax+1 lines, l's must be in order)
0 2.60 -0.00 4 8 5.00
1 2.60 -0.00 4 8 5.20
2 2.00 0.00 4 9 8.40
#
# LOCAL POTENTIAL
#
### lloc angular momentum whose semi-local psp is taken as local. lloc=4
### denotes a smooth polynomial continuation of the all-electron
### potential. If lloc<=lmax, remaining data are ignored, but
### must be there (zeros are OK). The rc corresponding to lloc
### MUST BE THE MINIMUM rc for lloc<=lmax, or <= the minumum for
### lloc=4 (usually the best choice)
### lpopt type of polynomial continuation for lloc=4. values 1-5
### permitted.
### 1) match 2 derivatives, r^0,2,4
### 2) match 2 derivatives, r^0,4,6
### 3) match 3 derivatives, r^0,4,5,6
### 4) match 3 derivatives, r^0,4,6,8
### 5) match 3 derivatives, r^0,2,4,6
### dvloc0 shift of r=0 potential from basic continuation (Ha) for lloc=4
### depends on lpopt above as follows, with x=(r/rc)
### 1) dvloc0*(1-x^2)^3
### 2) dvloc0*(1-x^4)^3
### 3-5) dvloc0*(1-x^4)^4
#
# lloc, lpopt, rc(5), dvloc0
4 5 2.0 0.0
#
# VANDERBILT-KLEINMAN-BYLANDER PROJECTORs
#
### l angular momentum
### nproj number of projectors, 1 or 2. automatically set to 0 for l=lloc
### debl energy added to basic psp energy ep for 2nd projector
### automatically reset to match 2nd projector with 2nd bound state
### at this l when it is occupied (ie., the psp is generated for a
### correcponding-l shallow core state)
#
# l, nproj, debl (lmax+1 lines, l's in order)
0 2 1.50
1 2 1.50
2 2 1.50
#
# MODEL CORE CHARGE
#
### icmod 0: no non-linear core correction charge. 1: smooth monotonic
### polynomial model core charge fit at "matching" rc following
### reference 35. For icmod = 2, 3, 4 see doc/core_correction.txt
### fcfact radius for above determined by rho_core(r)=fcfact*rho_pseudo_
### valence(r) values 0.25-0.5 are usually good (look at plots)
### For icmod = 3, fcfact has a different meaning and a third
### artument rcfact is added (see core_correction.txt)
### For icmod = 4,, fcfact is ignored but must be present (0.0 OK)
#
# icmod, fcfact, (rcfact)
0 0.25
#
# LOG DERIVATIVE ANALYSIS
#
### epsh1 lower energy limit for "phase-shift-like" log-derivative plots
### should be below the nearest core level for this l to make sure
### there are no ghosts, but -2.0 Ha usually is OK When semi-cores
### are treated as valence, this should be below the lowest core
### energy
### epsh2 upper energy limit, 2.0 usually good
### depsh energy mesh interval for plot, 0.02 usually good enough
#
# epsh1, epsh2, depsh
-2.0 2.0 0.02
#
# OUTPUT GRID
#
### rlmax maximum radius for Abinit psp code 8 format output. must be
### greater than maximum rc (including lloc=4 rc), but also determines
### range of diagnostic plots, so ~2-3*rcmax is usually good
### drl mesh spacing of linear radial mesh for Abinit output. 0.02 is good
### for "softer" psps, 0.01 is probably better with 1st row, 3d's, or
### semi-core psps.
#
# rlmax, drl
4.0 0.01
#
# TEST CONFIGURATIONS
#
### ncnf number of test configurations (<=4) The reference config is always
### run first as a consistency check. core always is the reference core
### configuration. The excitation energy of the configuration for the
### pseudo-atom will be compared with the all-electron result.
# ncnf
4
#
### n principal quantum number for all-electron atom
### l angular momentum
### f occupancy
#
# nvcnf (repeated ncnf times)
# number of valence states in this test configuration
# n, l, f (nvcnf lines, repeated follwing nvcnf's ncnf times)
# n l f
3
3 2 10.00
4 0 1.00
4 1 2.00
#
3
3 2 10.00
4 0 2.00
4 1 1.00
#
3
3 2 10.00
4 0 1.00
4 1 1.00
#
3
3 2 10.00
4 0 1.00
4 2 1.00
#
"""
[docs]
class Lparams(AbipyParameterized):
"""
Stores all the oncvpsp pseudization parameters for a given l.
"""
l = param.Integer(None, bounds=(0, None))
rc = param.Number(None, bounds=(0, None))
ep = param.Number(None, bounds=(None, None))
ncon = param.Integer(None, bounds=(0, 9))
nbas = param.Integer(None, bounds=(0, 9))
qcut = param.Number(None, bounds=(0, None))
nproj = param.Integer(None, bounds=(1, 5,))
debl = param.Number(None, bounds=(None, None))
[docs]
class Nlf(AbipyParameterized):
"""
Stores the value of n, l and occupancy f.
"""
n = param.Integer(None, bounds=(1, None))
l = param.Integer(None, bounds=(0, None))
f = param.Number(None, bounds=(0.0, None))
[docs]
def run_psgen(psgen: OncvGenerator, data: dict) -> dict:
"""
Helper function to run oncvpsp, parse the outputfiles and return dictionary with results
Args:
psgen (OncvGenerator): [description]
data (dict): input dictionary with extra data to be added to the output dict
"""
# Init return values
max_ecut = None
max_atan_logder_l1err = None
max_psexc_abserr = None
herm_err = None
status = None
nwarns = None
nerrs = None
try:
psgen.start()
retcode = psgen.wait()
results = psgen.parser.get_results()
if results is not None:
max_ecut = results.max_ecut
max_atan_logder_l1err = results.max_atan_logder_l1err
max_psexc_abserr = results.max_psexc_abserr
herm_err = results.herm_err
status = str(psgen.status)
nwarns = len(psgen.parser.warnings)
nerrs = len(psgen.parser.errors)
except Exception as exc:
print("Exception in run_psgen", exc)
d = dict(
status=status,
max_ecut=max_ecut,
max_atan_logder_l1err=max_atan_logder_l1err,
max_psexc_abserr=max_psexc_abserr,
herm_err=herm_err,
nwarns=nwarns,
nerrs=nerrs,
)
data = data.copy()
data.update(**d)
return data
[docs]
class OncvGui(AbipyParameterized):
calc_type = param.ObjectSelector(default="scalar-relativistic",
objects=["scalar-relativistic", "fully-relativistic", "non-relativistic"],
label="Relativistic effects")
max_nprocs = param.Integer(max(os.cpu_count() // 2, 1), bounds=(1, None))
dpi = param.Integer(82, bounds=(24, None))
#in_filepath = param.String("", doc="The path to the oncvps input file.")
qcut_num = param.Integer(2, bounds=(1, None))
qcut_step = param.Number(1, bounds=(0, None))
qcut_dir = param.Selector(["centered", ">", "<"])
rc_num = param.Integer(2, bounds=(1, None))
rc_step = param.Number(0.1, bounds=(0, None))
rc_dir = param.Selector(["centered", ">", "<"])
debl_num = param.Integer(2, bounds=(1, None))
debl_step = param.Number(1.0, bounds=(0, None))
debl_dir = param.Selector(["centered", ">", "<"])
rc5_num = param.Integer(2, bounds=(1, None))
rc5_step = param.Number(0.1, bounds=(0, None))
rc5_dir = param.Selector(["<", "centered", ">"])
dvloc0_num = param.Integer(2, bounds=(1, None))
dvloc0_step = param.Number(0.5, bounds=(0, None))
dvloc0_dir = param.Selector(["centered", "<", ">"])
fcfact_num = param.Integer(1, bounds=(1, None))
fcfact_step = param.Number(0.2, bounds=(0, None))
fcfact_dir = param.Selector(["centered", ">", "<"])
rcfact_num = param.Integer(1, bounds=(1, None))
rcfact_step = param.Number(0.2, bounds=(0, None))
rcfact_dir = param.Selector(["centered", ">", "<"])
ace_theme = param.ObjectSelector(default="chrome",
objects=pnw.CodeEditor.param.theme.objects,
doc="Theme of the editor")
history_idx = param.Integer(default=-1, label="History index")
[docs]
@classmethod
def from_file(cls, path: str, plotlyFlag: bool) -> OncvGui:
"""
Build an instance from a file with the oncvpsp input variables.
"""
return cls(oncv_input=OncvInput.from_file(path), plotlyFlag=plotlyFlag, in_filepath=path)
def __init__(self, oncv_input, plotlyFlag, in_filepath="", **params):
super().__init__(**params)
self.ace_kwargs = dict(sizing_mode='stretch_both', print_margin=False, language='text', height=600,
theme="chrome",
#theme="dracula",
#max_length=150,
)
self.input_ace = pnw.CodeEditor(value=str(oncv_input), **self.ace_kwargs)
# Add annotated example for documentation purposes.
self.annotated_example = pn.pane.HTML(f"<pre><code> {GE_ANNOTATED} </code></pre>")
self.in_filepath = pnw.TextInput(value=in_filepath, placeholder='Enter the filepath...')
self.out_area = pn.Column("## Oncvpsp output:", sizing_mode="stretch_width")
#self.out_runtests = pn.Column("## Basic tests:", sizing_mode="stretch_width")
# Define buttons
self.execute_btn = pnw.Button(name="Execute", button_type='primary')
self.execute_btn.on_click(self.on_execute_btn)
# This is the directory used to run oncvpsp when the user clicks execute_btn
#self._execute_stdout_path = None
# List storing all the inputs.
self.input_history = []
self.history_btn = pnw.Button(name="Compare", button_type='primary')
#self.history_btn.on_click(self.on_history_btn)
self.rc_qcut_btn = pnw.Button(name="Execute", button_type='primary')
self.plotlyFlag = plotlyFlag
[docs]
@param.depends("ace_theme")
def change_ace_theme(self):
#print("Changing theme")
self.input_ace.theme = self.ace_theme
[docs]
def starmap(self, func, list_of_args):
import time
time_start = time.time()
# Don't use more procs than tasks.
_max_nprocs_ = self.max_nprocs
_max_nprocs_ = min(_max_nprocs_, len(list_of_args))
if _max_nprocs_ == 1:
values = [func(*args) for args in list_of_args]
else:
# TODO: Optimize, use better algo
# This Pool uses threads instead of multiprocessing
# Cannot use multiprocessing because we have side effects in psgen
from multiprocessing.dummy import Pool
with Pool(processes=self.max_nprocs) as pool:
values = pool.starmap(func, list_of_args)
print(f"Done {len(list_of_args)} tasks in {time.time() - time_start:.2f} [s] with {_max_nprocs_} processe(s)")
return values
[docs]
def get_panel(self, as_dict=False, **kwargs):
"""Return tabs with widgets"""
d = {}
main = pn.Column(
pn.Row(
self.pws_col(["calc_type", "max_nprocs",
"dpi", "ace_theme", "execute_btn"]),
self.input_ace,
),
pn.Card(self.annotated_example, title='Annotated example', collapsed=True,
header_color="blue", sizing_mode="stretch_width"),
sizing_mode="stretch_width",
)
d["Main"] = pn.Column(main,
#pn.layout.Divider(),
self.out_area,
#self.out_runtests,
sizing_mode="stretch_width",
)
d["Rc_qcut_opt"] = self.get_rc_qcut_opt_view()
d["History"] = self.get_history_view()
if as_dict: return d
template = self.get_template_from_tabs(d, template=kwargs.get("template", None))
#self.tabs = template
#print(self.tabs)
return template
[docs]
def get_history_view(self) -> pn.Row:
return pn.Row(
self.pws_col(["## History",
"history_idx",
"history_btn",
]),
self.on_history_btn
)
[docs]
@depends_on_btn_click('history_btn')
def on_history_btn(self) -> pn.Column:
#print("hello")
hist_len = len(self.input_history)
idx = self.history_idx
if hist_len == 0 or idx >= hist_len:
return pn.Column(f"hist_len == {hist_len}")
ace_hist = pnw.CodeEditor(value=self.input_history[idx], **self.ace_kwargs)
fromlines = self.input_history[idx].splitlines()
tolines = self.input_ace.value.splitlines()
from difflib import HtmlDiff
html_table = HtmlDiff().make_table(fromlines, tolines) #, fromdesc='', todesc='', context=False, numlines=5)
return pn.Column(
f"## Input at history index: {idx}",
ace_hist,
pn.pane.HTML(html_table),
)
[docs]
def gridplot_psgens(self, psgens, titles, func_names="plot_atanlogder_econv") -> pn.GridBox:
"""
Return a GridBox with the figures obtained by calling `plotter.func_name`
for all the PseudoGenerators in psgens.
"""
# Generate plots with titles by calling `func_name`.
_m = functools.partial(mpl, with_divider=False, dpi=self.dpi)
func_names = list_strings(func_names)
figs = []
for psgen, title in zip(psgens, titles):
plotter = psgen.parser.get_plotter()
if plotter is not None:
for func_name in func_names:
plot_func = getattr(plotter, func_name)
figs.append(_m(plot_func(show=False, title=title, fig_close=True)))
# Insert the figures in a GridBox.
nfigs = len(figs)
nrows, ncols = 1, 1
if nfigs > 1:
ncols = 2 if len(func_names) == 1 else len(func_names)
nrows = nfigs // ncols + nfigs % ncols
elif nfigs == 0:
nrows, ncols = 0, 0
return pn.GridBox(*figs, ncols=ncols, nrows=nrows)
[docs]
def on_change_qcut(self, event) -> None:
"""
Change the value of qcut(l), run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
# Get initial qc(l) from input.
l = int(event.new)
oncv_input = self.get_oncv_input()
i0, qcut0 = oncv_input.find_lparam(l, "qcut")
# Define list of qc to be tested and build list of OncvGenerator.
qcut_values, _ = build_mesh(qcut0, self.qcut_num, self.qcut_step, self.qcut_dir)
psgens = []
try:
for qcut in qcut_values:
oncv_input.lparams[i0].qcut = qcut
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.lparams[i0].qcut = qcut0
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"qcut": qcut}) for psgen, qcut in zip(psgens, qcut_values)]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Qcut optimization for l: {l}. Click the icon to update the input",
dfw
),
self.get_qcut_widgets(oncv_input))
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, [f"qc = {qc:.2f}" for qc in qcut_values])
col.append(grid)
self.out_area.objects = col.objects
def _build_table(self, tasks, d_list):
"""
Build and return a Tabulator widget with the results.
Also, register callbacks so that it is possible to
update the input file by clicking on the icon in the last column.
"""
df = pd.DataFrame(d_list, columns=list(d_list[0].keys()))
# Sort results by max_ecut and add buttons to trigger callbacks
df = df.sort_values("max_ecut")
dfw = pn.widgets.Tabulator(df, buttons={'accept': '<i class="fa fa-print"></i>'})
def update_input(event):
#print(f'Clicked {event.column!r} on row {event.row}')
# Use the index to get the psgen as we have sorted along max_ecut
idx = dfw.value.index[event.row]
#print("select idx:", idx)
psgen = tasks[idx][0]
if event.column == "accept" and psgen.status == psgen.S_OK:
oncv_input = self.set_oncv_input_string(psgen.input_str)
self._update_out_area(psgen, oncv_input)
dfw.on_click(update_input)
return dfw
[docs]
def on_change_debl(self, event) -> None:
"""
Change the value of debl(l), run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
# Get initial qc(l) from input.
l = int(event.new)
oncv_input = self.get_oncv_input()
# Define list of qc to be tested and build list of OncvGenerator.
i0, debl0 = oncv_input.find_lparam(l, "debl")
debl_values, _ = build_mesh(debl0, self.debl_num, self.debl_step, self.debl_dir)
psgens = []
try:
for debl in debl_values:
oncv_input.lparams[i0].debl = debl
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.lparams[i0].debl = debl0
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"debl": debl}) for psgen, debl in zip(psgens, debl_values)]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Debl optimization for l: {l}. Click the icon to update the input",
dfw,
),
self.get_debl_widgets(oncv_input))
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, [f"debl = {debl:.2f}" for debl in debl_values],
func_names=["plot_atan_logders"])
col.append(grid)
self.out_area.objects = col.objects
[docs]
def on_change_rc5(self, event) -> None:
"""
Change the value of rc5 for the local part, run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
oncv_input = self.get_oncv_input()
# Define list of qc to be tested and build list of OncvGenerator.
rc5 = oncv_input.rc5
rc5_values, _ = build_mesh(rc5, self.rc5_num, self.rc5_step, self.rc5_dir)
psgens = []
try:
for new_rc in rc5_values:
oncv_input.rc5 = new_rc
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.rc5 = rc5
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"rc5": rc5}) for psgen, rc5 in zip(psgens, rc5_values)]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Rc5 optimization. Click the icon to update the input",
dfw,
),
self.get_rc5_widgets(oncv_input))
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, [f"rc5 = {rc5:.2f}" for rc5 in rc5_values],
func_names=["plot_atanlogder_econv", "plot_potentials"])
col.append(grid)
self.out_area.objects = col.objects
[docs]
def on_change_dvloc0(self, event) -> None:
"""
Change the value of dvloc0 for the local part, run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
oncv_input = self.get_oncv_input()
# Define list of qc to be tested and build list of OncvGenerator.
dvloc0 = oncv_input.dvloc0
dvloc_values, _ = build_mesh(dvloc0, self.dvloc0_num, self.dvloc0_step, self.dvloc0_dir)
psgens = []
try:
for new_dvloc in dvloc_values:
oncv_input.dvloc0 = new_dvloc
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.dvloc0 = dvloc0
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"dvloc0": dvloc}) for psgen, dvloc in zip(psgens, dvloc_values)]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## dvloc0 optimization. Click the icon to update the input",
dfw,
),
self.get_dvloc0_widgets(oncv_input))
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, [f"dvloc = {dvloc:.2f}" for dvloc in dvloc_values],
func_names=["plot_atanlogder_econv", "plot_potentials"])
col.append(grid)
self.out_area.objects = col.objects
[docs]
def on_change_rc(self, event) -> None:
"""
Change the value of rc(l), run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
# Get initial rc(l) from input.
l = int(event.new)
oncv_input = self.get_oncv_input()
# Define list of qc to be tested and build list of OncvGenerator.
i0, rc0 = oncv_input.find_lparam(l, "rc")
rc_values, _ = build_mesh(rc0, self.rc_num, self.rc_step, self.rc_dir)
psgens = []
try:
for rc in rc_values:
oncv_input.lparams[i0].rc = rc
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.lparams[i0].rc = rc0
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"rc": rc}) for psgen, rc in zip(psgens, rc_values)]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Rc optimization for l: {l}. Click the icon to update the input",
dfw,
),
self.get_rc_widgets(oncv_input),
)
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, [f"rc = {rc:.2f} for l: {l}" for rc in rc_values])
col.append(grid)
self.out_area.objects = col.objects
[docs]
def on_change_rhomodel(self, event) -> None:
"""
Change the parameters for the model core charge, run oncvpsp and show the results.
"""
with ButtonContext(event.obj), Loading(self.out_area):
# Get initial values from input.
oncv_input = self.get_oncv_input()
icmod = oncv_input.icmod
if icmod == 0: return
fcfact0 = oncv_input.fcfact
rcfact0 = oncv_input.rcfact
# Define list of values to be tested and build list of OncvGenerator.
fcfact_values, _ = build_mesh(fcfact0, self.fcfact_num, self.fcfact_step, self.fcfact_dir)
rcfact_values, _ = build_mesh(rcfact0, self.rcfact_num, self.rcfact_step, self.rcfact_dir)
psgens = []
tasks = []
titles = []
if icmod in (3, 4):
# fcfact x rcfact
for fc, rc in itertools.product(fcfact_values, rcfact_values):
try:
oncv_input.fcfact = fc
oncv_input.rcfact = rc
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
tasks.append((psgens[-1], {"fcfact": fc, "rcfact": rc}))
titles.append(f"fcfact: {fc}, rcfact: {rc}")
finally:
# Restore the initial value.
oncv_input.fcfact = fcfact0
oncv_input.rcfact = rcfact0
elif icmod in (1, 2):
# Only fcfact is used here.
for fc in fcfact_values:
try:
oncv_input.fcfact = fc
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
tasks.append((psgens[-1], {"fcfact": fc}))
titles.append(f"fcfact: {fc}")
finally:
# Restore the initial value.
oncv_input.fcfact = fcfact0
else:
raise ValueError(f"Invalid icmod: {icmod}")
# Run each generator, get results, build pandas Dataframe and show it in out_area.
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Rho model optimization for icmod: {icmod}. Click the icon to update the input",
dfw,
),
self.get_rhomodel_widgets(oncv_input),
)
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots
grid = self.gridplot_psgens(psgens, titles,
func_names=["plot_densities", "plot_den_formfact"])
col.append(grid)
self.out_area.objects = col.objects
[docs]
def get_rc_qcut_opt_view(self) -> pn.Row:
oncv_input = self.get_oncv_input()
menu_items = [(f"l = {l}", str(l)) for l in range(oncv_input.lmax + 1)]
menu_button = pnw.MenuButton(name='Change qcut(l)', items=menu_items, button_type='primary')
menu_button.on_click(self.on_change_rc_qcut)
qc_l = {p.l: p.qcut for p in oncv_input.lparams}
rc_l = {p.l: p.rc for p in oncv_input.lparams}
help_str = f"""
Here one can change the value of rc(l) and qcut(l).
The present values of qc_l are: {qc_l}
The present values of rc_l are: {rc_l}
"""
self.rc_qcut_out_area = pn.Column(sizing_mode="stretch_width")
wbox = pn.WidgetBox(menu_button,
*[self.param[k] for k in ("qcut_num", "qcut_step", "qcut_dir")],
*[self.param[k] for k in ("rc_num", "rc_step", "rc_dir")],
help_str)
return pn.Row(wbox, self.rc_qcut_out_area, sizing_mode="stretch_width")
#@depends_on_btn_click('rc_qcut_btn')
[docs]
def on_change_rc_qcut(self, event) -> None:
"""
Generate pseudos using a grid of (rc, qcut) values for given l.
"""
with ButtonContext(event.obj), Loading(self.rc_qcut_out_area):
# Get initial rc(l) from input.
l = int(event.new)
oncv_input = self.get_oncv_input()
# Define list of qc to be tested and build list of OncvGenerator.
i0, rc0 = oncv_input.find_lparam(l, "rc")
rc_values, _ = build_mesh(rc0, self.rc_num, self.rc_step, self.rc_dir)
# Define list of qc to be tested and build list of OncvGenerator.
i0, qcut0 = oncv_input.find_lparam(l, "qcut")
qcut_values, _ = build_mesh(qcut0, self.qcut_num, self.qcut_step, self.qcut_dir)
def rq_prod():
return itertools.product(rc_values, qcut_values)
psgens = []
try:
for rc, qcut in rq_prod():
oncv_input.lparams[i0].rc = rc
oncv_input.lparams[i0].qcut = qcut
psgens.append(OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False))
finally:
# Restore the initial value.
oncv_input.lparams[i0].rc = rc0
oncv_input.lparams[i0].qcut = qcut0
# Run each generator, get results, build pandas Dataframe and show it in out_area.
tasks = [(psgen, {"rc": rc, "qcut": qcut}) for psgen, (rc, qcut) in zip(psgens, rq_prod())]
d_list = self.starmap(run_psgen, tasks)
dfw = self._build_table(tasks, d_list)
head = pn.Row(pn.Column(
f"## Rc/qcut optimization for l: {l}. Click the icon to update the input",
dfw,
),
)
col = pn.Column(head, sizing_mode="stretch_width")
# Add plots:
grid = self.gridplot_psgens(psgens, [f"rc = {rc:.2f}, qc = {qc:.2f} for l: {l}"
for rc, qc in rq_prod()])
col.append(grid)
self.rc_qcut_out_area.objects = col.objects
#@depends_on_btn_click('execute_btn')
[docs]
def on_execute_btn(self, event) -> None:
"""
Build a new generator from the input file, run it and update out_area.
"""
with ButtonContext(event.obj), Loading(self.out_area):
oncv_input = self.get_oncv_input()
psgen = OncvGenerator(input_str=str(oncv_input), calc_type=self.calc_type, use_mgga=False)
print("Running in workdir:", psgen.workdir)
psgen.start()
retcode = psgen.wait()
#psget.check_status()
#if psgen.status != psgen.S_OK:
# cprint("oncvpsp returned %s. Exiting" % retcode, "red")
# return 1
#out_path = self._execute_stdout_path = psgen.stdout_path
# Parse the output file
#onc_parser = OncvParser(out_path)
#onc_parser.scan()
#if not onc_parser.run_completed:
# cprint("oncvpsp output is not complete. Exiting", "red")
# return 1
## Build the plotter and add figures to out_area.
#plotter = onc_parser.get_plotter()
# TODO:
# Tranfer final output file.
self._update_out_area(psgen, oncv_input)
def _update_out_area(self, psgen, oncv_input: OncvInput) -> None:
with Loading(self.out_area):
#self.psgen_to_save = psgen
plotter = psgen.parser.get_plotter()
if plotter is None:
self.out_area.objects = pn.Column("## Plotter is None")
return
if self.plotlyFlag:
_m = functools.partial(ply, with_divider=False)
else:
_m = functools.partial(mpl, with_divider=False, dpi=self.dpi)
save_btn = pnw.Button(name="Save output", button_type='primary')
save_btn.on_click(self.on_save_btn)
new_rows = [
pn.Row(save_btn, self.in_filepath),
pn.layout.Divider(),
"## Pseudized Wavefunctions",
pn.Row(_m(plotter.plot_radial_wfs(show=False)),
self.get_rc_widgets(oncv_input), height=600),
pn.Row(_m(plotter.plot_radial_wfs(what="scattering_states", show=False)), height=600),
pn.layout.Divider(),
"## Logder and convergence profile",
pn.Row(_m(plotter.plot_atanlogder_econv(show=False)),
self.get_qcut_widgets(oncv_input), height=500),
pn.Row(pn.Spacer(), self.get_debl_widgets(oncv_input), align='end', height=300),
pn.layout.Divider(),
"## Pseudized local part",
pn.Row(_m(plotter.plot_potentials(show=False)),
self.get_rc5_widgets(oncv_input), height=400),
pn.Row(pn.Spacer(), self.get_dvloc0_widgets(oncv_input), align="end", height=300),
pn.layout.Divider(),
"## Model core charge",
pn.Row(_m(plotter.plot_densities(show=False)),
self.get_rhomodel_widgets(oncv_input), height=600),
pn.Row(_m(plotter.plot_den_formfact(show=False)), height=600),
pn.layout.Divider(),
"## Projectors",
pn.Row(_m(plotter.plot_projectors(show=False)), height=600),
pn.layout.Divider(),
]
self.out_area.objects = new_rows
#self.tabs[0].active = 1
[docs]
def on_save_btn(self, event) -> None:
with ButtonContext(event.obj):
print("on_save_button")
#self._execute_stdout_path
out_path = self.in_filepath.value
if not out_path:
raise ValueError("out_path cannot be empty.")
#self.psgen_to_save = psgen
# TODO: Transfer final output file.
#shutil.copy(psgen.stdout_path, out_path)