Boston MA, 3 March 2019
These slides have been generated using jupyter, nbconvert and revealjs
The notebook can be downloaded from this github repo
To install and configure the software, follow these installation instructions
Layered structure designed for different use-cases:
Closely connected to the ABINIT executable:
ABINIT and AbiPy communicate through netcdf files
Using pip and python wheels:
pip install abipy --user
Using conda (recommended):
conda install abipy --channel abinit
From the github repository (develop mode):
git clone https://github.com/abinit/abipy.git
cd abipy
python setup.py develop
For further info see http://abinit.github.io/abipy/installation.html
%embed https://abinit.github.io/abipy/index.html
%embed https://nbviewer.jupyter.org/github/abinit/abitutorials/blob/master/abitutorials/index.ipynb
High-level logic for:
Main entry point:
from abipy import abilab
abifile = abilab.abiopen("filename.nc")
where filename.nc is a netcdf file (support also text files e.g. run.abo, run.log, out_DDB)
abifile is the AbiFile subclass associated to the given file extension:
abiopen.py --help
) Command line interface: use abiopen.py FILE
to:
--print
option)--expose
option)--notebook
option)from abipy import abilab
import abipy.data as abidata
gsr_kpath = abilab.abiopen("si_nscf_GSR.nc")
Conventions:
Several AbiPy objects provide plot methods returning matplotlib figures
print(gsr_kpath.structure)
Full Formula (Si2) Reduced Formula: Si abc : 3.866975 3.866975 3.866975 angles: 60.000000 60.000000 60.000000 Sites (2) # SP a b c --- ---- ---- ---- ---- 0 Si 0 0 0 1 Si 0.25 0.25 0.25 Abinit Spacegroup: spgid: 227, num_spatial_symmetries: 48, has_timerev: True, symmorphic: True
print(gsr_kpath.ebands)
================================= Structure ================================= Full Formula (Si2) Reduced Formula: Si abc : 3.866975 3.866975 3.866975 angles: 60.000000 60.000000 60.000000 Sites (2) # SP a b c --- ---- ---- ---- ---- 0 Si 0 0 0 1 Si 0.25 0.25 0.25 Abinit Spacegroup: spgid: 227, num_spatial_symmetries: 48, has_timerev: True, symmorphic: True Number of electrons: 8.0, Fermi level: 5.598 (eV) nsppol: 1, nkpt: 14, mband: 8, nspinor: 1, nspden: 1 smearing scheme: none, tsmear_eV: 0.272, occopt: 1 Direct gap: Energy: 2.532 (eV) Initial state: spin=0, kpt=[+0.000, +0.000, +0.000], name: $\Gamma$, weight: 0.000, band=3, eig=5.598, occ=2.000 Final state: spin=0, kpt=[+0.000, +0.000, +0.000], name: $\Gamma$, weight: 0.000, band=4, eig=8.130, occ=0.000 Fundamental gap: Energy: 0.524 (eV) Initial state: spin=0, kpt=[+0.000, +0.000, +0.000], name: $\Gamma$, weight: 0.000, band=3, eig=5.598, occ=2.000 Final state: spin=0, kpt=[+0.000, +0.429, +0.429], weight: 0.000, band=4, eig=6.123, occ=0.000 Bandwidth: 11.856 (eV) Valence maximum located at: spin=0, kpt=[+0.000, +0.000, +0.000], name: $\Gamma$, weight: 0.000, band=3, eig=5.598, occ=2.000 Conduction minimum located at: spin=0, kpt=[+0.000, +0.429, +0.429], weight: 0.000, band=4, eig=6.123, occ=0.000
gsr_kpath.ebands.kpoints.plot();
gsr_kpath.ebands.plot(with_gaps=True, title="Silicon band structure");
with abilab.abiopen("si_scf_GSR.nc") as scf_gsr:
ebands_kmesh = scf_gsr.ebands
print(ebands_kmesh.kpoints)
K-mesh with divisions: [8, 8, 8], shifts: [0.0, 0.0, 0.0] kptopt: 1 (Use space group symmetries and TR symmetry) Number of points in the IBZ: 29 0) [+0.000, +0.000, +0.000], weight=0.002 1) [+0.125, +0.000, +0.000], weight=0.016 2) [+0.250, +0.000, +0.000], weight=0.016 3) [+0.375, +0.000, +0.000], weight=0.016 4) [+0.500, +0.000, +0.000], weight=0.008 5) [+0.125, +0.125, +0.000], weight=0.012 6) [+0.250, +0.125, +0.000], weight=0.047 7) [+0.375, +0.125, +0.000], weight=0.047 8) [+0.500, +0.125, +0.000], weight=0.047 9) [-0.375, +0.125, +0.000], weight=0.047 10) [-0.250, +0.125, +0.000], weight=0.047 ... (More than 10 k-points)
edos = ebands_kmesh.get_edos()
edos.plot();
gsr_kpath.ebands.plot_with_edos(edos);
plotter = abilab.ElectronBandsPlotter()
plotter.add_ebands(label="BZ sampling", bands="si_scf_GSR.nc")
plotter.add_ebands(label="k-path", bands="si_nscf_GSR.nc")
plotter.gridplot(with_gaps=True);
High-level interface to operate on multiple files with the same file extension
Useful for:
Each Robot is associated to a file extension, e.g.
Robots can be constructed from:
Command line interface provided by the abicomp.py script:
To generate notebook to compare multiple GSR files, use:
abicomp.py gsr out1_GSR.nc out2_GSR.nc --notebook
ls flow_base3_ngkpt
out0_GSR.nc out1_GSR.nc out2_GSR.nc out3_GSR.nc
robot_enekpt = abilab.GsrRobot.from_dir("flow_base3_ngkpt");
robot_enekpt.abifiles[0]
<GsrFile, flow_base3_ngkpt/out0_GSR.nc>
robot_enekpt["out0_GSR.nc"]
<GsrFile, flow_base3_ngkpt/out0_GSR.nc>
ene_table = robot_enekpt.get_dataframe()
print(ene_table.keys())
Index(['formula', 'natom', 'alpha', 'beta', 'gamma', 'a', 'b', 'c', 'volume', 'abispg_num', 'spglib_symb', 'spglib_num', 'spglib_lattice_type', 'energy', 'pressure', 'max_force', 'ecut', 'pawecutdg', 'tsmear', 'nkpt', 'nsppol', 'nspinor', 'nspden'], dtype='object')
ene_table
formula | natom | alpha | beta | gamma | a | b | c | volume | abispg_num | ... | energy | pressure | max_force | ecut | pawecutdg | tsmear | nkpt | nsppol | nspinor | nspden | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
out0_GSR.nc | Si2 | 2 | 60.0 | 60.0 | 60.0 | 3.866975 | 3.866975 | 3.866975 | 40.888292 | 227 | ... | -241.251546 | -3.586342 | 3.051564e-27 | 8.0 | -1.0 | 0.01 | 2 | 1 | 1 | 1 |
out1_GSR.nc | Si2 | 2 | 60.0 | 60.0 | 60.0 | 3.866975 | 3.866975 | 3.866975 | 40.888292 | 227 | ... | -241.417959 | -3.907310 | 4.677360e-27 | 8.0 | -1.0 | 0.01 | 10 | 1 | 1 | 1 |
out2_GSR.nc | Si2 | 2 | 60.0 | 60.0 | 60.0 | 3.866975 | 3.866975 | 3.866975 | 40.888292 | 227 | ... | -241.421158 | -3.895679 | 0.000000e+00 | 8.0 | -1.0 | 0.01 | 28 | 1 | 1 | 1 |
out3_GSR.nc | Si2 | 2 | 60.0 | 60.0 | 60.0 | 3.866975 | 3.866975 | 3.866975 | 40.888292 | 227 | ... | -241.421391 | -3.895437 | 2.762091e-27 | 8.0 | -1.0 | 0.01 | 60 | 1 | 1 | 1 |
4 rows × 23 columns
ene_table.sort_values(by="nkpt", inplace=True)
# Add 2 columns with energies in Ha and difference wrt to the last point.
ene_table["energy_Ha"] = ene_table["energy"] * abilab.units.eV_to_Ha
ene_table["ediff_Ha"] = ene_table["energy_Ha"] - ene_table["energy_Ha"][-1]
# Select columns with the syntax:
ene_table[["nkpt", "energy", "energy_Ha", "ediff_Ha"]]
nkpt | energy | energy_Ha | ediff_Ha | |
---|---|---|---|---|
out0_GSR.nc | 2 | -241.251546 | -8.865831 | 0.006242 |
out1_GSR.nc | 10 | -241.417959 | -8.871946 | 0.000126 |
out2_GSR.nc | 28 | -241.421158 | -8.872064 | 0.000009 |
out3_GSR.nc | 60 | -241.421391 | -8.872073 | 0.000000 |
ene_table.plot(x="nkpt", y=["energy_Ha", "ediff_Ha", "pressure"],
style="-o", subplots=True);
Command line interface provided by the abicomp.py script
See also lesson_base3
Let's assume we want to reuse the raw data for our research work:
With python we can easily connect the different parts of the puzzle:
For a more comprehensive discussion see this abitutorial
ddb = abilab.DdbFile.from_mpid("mp-1009129")
print(ddb)
================================= File Info ================================= Name: mp-1009129qy153fif_DDB Directory: /var/folders/89/47k8wfdj11x035svqf8qnl4m0000gn/T Size: 218.73 kb Access Time: Fri Mar 1 15:43:15 2019 Modification Time: Fri Mar 1 15:43:15 2019 Change Time: Fri Mar 1 15:43:15 2019 ================================= Structure ================================= Full Formula (Mg1 O1) Reduced Formula: MgO abc : 2.908638 2.908638 2.656848 angles: 90.000000 90.000000 120.000000 Sites (2) # SP a b c --- ---- -------- -------- --- 0 Mg 0 0 0 1 O 0.333333 0.666667 0.5 Abinit Spacegroup: spgid: 0, num_spatial_symmetries: 12, has_timerev: True, symmorphic: False ================================== DDB Info ================================== Number of q-points in DDB: 72 guessed_ngqpt: [ 9 9 10] (guess for the q-mesh divisions made by AbiPy) ecut = 44.000000, ecutsm = 0.000000, nkpt = 405, nsym = 12, usepaw = 0 nsppol 1, nspinor 1, nspden 1, ixc = -116133, occopt = 1, tsmear = 0.010000 Has total energy: False, Has forces: False Has stress tensor: False Has (at least one) atomic pertubation: True Has (at least one diagonal) electric-field perturbation: True Has (at least one) Born effective charge: True Has (all) strain terms: False Has (all) internal strain terms: False Has (all) piezoelectric terms: False
# Return PHBST and PHDOS netcdf files.
phbstnc, phdosnc = ddb.anaget_phbst_and_phdos_files(
ndivsm=20, nqsmall=20, lo_to_splitting=True, asr=2,
chneut=1, dipdip=1, dos_method="tetra")
phbands = phbstnc.phbands
phdos = phdosnc.phdos
print(phbands)
================================= Structure ================================= Full Formula (Mg1 O1) Reduced Formula: MgO abc : 2.908638 2.908638 2.656848 angles: 90.000000 90.000000 120.000000 Sites (2) # SP a b c --- ---- -------- -------- --- 0 Mg 0 0 0 1 O 0.333333 0.666667 0.5 Abinit Spacegroup: spgid: 0, num_spatial_symmetries: 12, has_timerev: True, symmorphic: False Number of q-points: 345 Atomic mass units: {12.0: 24.305, 8.0: 15.9994} Has non-analytical contribution for q --> 0: True
phbands.plot(title="MgO phonons with LO-TO splitting");
phbands.plot_with_phdos(phdos, units="Thz", title="AlAs phonons bands and DOS");
phdos.plot_harmonic_thermo();
Programmatic interface to generate input files:
Can invoke ABINIT to get important parameters such as:
inp = abilab.AbinitInput(structure="si.cif", pseudos="14si.pspnc")
inp["ecut"] = 8
"ecut" in inp
True
inp.set_vars(kptopt=1, ngkpt=[2, 2, 2],
shiftk=[0.0, 0.0, 0.0, 0.5, 0.5, 0.5] # 2 shifts in one list
);
inp.set_autokmesh(nksmall=2)
{'ngkpt': array([2, 2, 2]), 'kptopt': 1, 'nshiftk': 4, 'shiftk': array([[0.5, 0.5, 0.5], [0.5, 0. , 0. ], [0. , 0.5, 0. ], [0. , 0. , 0.5]])}
print(inp.structure.formula)
Si2
for pseudo in inp.pseudos:
print(pseudo)
<NcAbinitPseudo: 14si.pspnc> summary: Troullier-Martins psp for element Si Thu Oct 27 17:31:21 EDT 1994 number of valence electrons: 4.0 maximum angular momentum: d angular momentum for local part: d XC correlation: LDA_XC_TETER93 supports spin-orbit: False radius for non-linear core correction: 1.80626423934776 hint for low accuracy: ecut: 0.0, pawecutdg: 0.0 hint for normal accuracy: ecut: 0.0, pawecutdg: 0.0 hint for high accuracy: ecut: 0.0, pawecutdg: 0.0
from pseudo_dojo.core.pseudos import OfficialDojoTable
pseudo_table = OfficialDojoTable.from_dojodir('ONCVPSP-PBEsol-PDv0.4','standard')
inp
inp.set_kpath(ndivsm=10)
{'kptbounds': array([[0. , 0. , 0. ], [0.5 , 0. , 0.5 ], [0.5 , 0.25 , 0.75 ], [0.375, 0.375, 0.75 ], [0. , 0. , 0. ], [0.5 , 0.5 , 0.5 ], [0.625, 0.25 , 0.625], [0.5 , 0.25 , 0.75 ], [0.5 , 0.5 , 0.5 ], [0.375, 0.375, 0.75 ], [0.625, 0.25 , 0.625], [0.5 , 0. , 0.5 ]]), 'kptopt': -11, 'ndivsm': 10, 'iscf': -2}
Once we have an AbinitInput, it is possible to execute Abinit to:
Methods invoking Abinit start with the abi prefix followed by a verb:
To call Abinit from AbiPy, one has to prepare a configuration file (manager.yml) providing all the information required to execute/submit Abinit jobs:
$PATH
, $LD_LIBRARY_PATH
qadapters:
# List of qadapters objects
- priority: 1
queue:
qtype: shell
qname: localhost
job:
mpi_runner: mpirun
pre_run:
# abinit exec must be in $PATH
- export PATH=$HOME/git_repos/abinit/_build/src/98_main:$PATH
limits:
timelimit: 30:00
max_cores: 2
hardware:
num_nodes: 2
sockets_per_node: 1
cores_per_socket: 2
mem_per_node: 4Gb
Examples of configuration files for clusters are available here
Use abirun.py doc_manager to get documentation inside the shell
inp["paral_kgb"] = 1
pconfs = inp.abiget_autoparal_pconfs(max_ncpus=5)
print("Best efficiency:\n", pconfs.sort_by_efficiency()[0])
print("Best speedup:\n", pconfs.sort_by_speedup()[0])
Best efficiency: {'efficiency': 0.98, 'mem_per_cpu': 0.0, 'mpi_ncpus': 3, 'omp_ncpus': 1, 'tot_ncpus': 3, 'vars': {'bandpp': 1, 'npband': 1, 'npfft': 1, 'npimage': 1, 'npkpt': 3, 'npspinor': 1}} Best speedup: {'efficiency': 0.969, 'mem_per_cpu': 0.0, 'mpi_ncpus': 5, 'omp_ncpus': 1, 'tot_ncpus': 5, 'vars': {'bandpp': 1, 'npband': 1, 'npfft': 1, 'npimage': 1, 'npkpt': 5, 'npspinor': 1}}
inp.abiget_irred_phperts(qpt=(0.25, 0, 0))
[{'qpt': [0.25, 0.0, 0.0], 'ipert': 1, 'idir': 1}, {'qpt': [0.25, 0.0, 0.0], 'ipert': 1, 'idir': 2}]
inp.abiget_irred_strainperts()
[{'qpt': [0.0, 0.0, 0.0], 'ipert': 1, 'idir': 1}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 5, 'idir': 1}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 5, 'idir': 2}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 5, 'idir': 3}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 6, 'idir': 1}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 6, 'idir': 2}, {'qpt': [0.0, 0.0, 0.0], 'ipert': 6, 'idir': 3}]
multi = abilab.MultiDataset(structure="si.cif", pseudos="14si.pspnc", ndtset=2)
multi.set_vars(ecut=4);
all(inp["ecut"] == 4 for inp in multi)
True
multi[0].set_vars(ngkpt=[2, 2, 2], tsmear=0.004)
multi[1].set_vars(ngkpt=[4, 4, 4], tsmear=0.008);
multi.get_vars_dataframe("ngkpt", "tsmear")
ngkpt | tsmear | |
---|---|---|
dataset 0 | [2, 2, 2] | 0.004 |
dataset 1 | [4, 4, 4] | 0.008 |
gs1, gs2 = multi.split_datasets()
Minimal input from user:
Default values designed to cover the most common scenarios
abinp.py ebands mp-149
multi = abilab.ebands_input(structure="si.cif",
pseudos="14si.pspnc",
ecut=8,
spin_mode="unpolarized",
smearing=None,
dos_kppa=5000)
multi.get_vars_dataframe("kptopt", "iscf", "ngkpt")
kptopt | iscf | ngkpt | |
---|---|---|---|
dataset 0 | 1 | None | [8, 8, 8] |
dataset 1 | -11 | -2 | None |
dataset 2 | 1 | -2 | [14, 14, 14] |
multi = abilab.g0w0_with_ppmodel_inputs(
structure="si.cif", pseudos="14si.pspnc",
kppa=1000, nscf_nband=50, ecuteps=2, ecutsigx=4, ecut=8,
spin_mode="unpolarized")
multi.get_vars_dataframe("optdriver", "ngkpt", "nband", "ecuteps", "ecutsigx")
optdriver | ngkpt | nband | ecuteps | ecutsigx | |
---|---|---|---|---|---|
dataset 0 | None | [8, 8, 8] | 14 | None | None |
dataset 1 | None | [8, 8, 8] | 50 | None | None |
dataset 2 | 3 | [8, 8, 8] | 50 | 2 | None |
dataset 3 | 4 | [8, 8, 8] | 50 | 2 | 4 |
Use e.g.
abistruct.py --help
for manpageabistruct.py COMMAND --help
for help about COMMAND
HTML documentation available at http://abinit.github.io/abipy/scripts/index.html
abistruct.py spglib si_scf_GSR.nc
abistruct.py convert si_scf_GSR.nc -f cif
abiopen.py si_scf_GSR.nc --print
Two different approaches:
Both approaches share the same codebase (AbinitInput, factory functions, AbiPy objects).
Number and type of calculations are important ➝ choose the approach that suits to your needs.
Workflow generators for common cases
Templates for database insertion based on mongoengine and mongodb documents
Let's start with an empty flow in the hello_flow directory
from abipy import flowtk
hello_flow = flowtk.Flow(workdir="hello_flow")
and use the graphviz tool after each step to show what's happening.
hello_flow.get_graphviz()
def make_scf_nscf_inputs():
"""
Build and return two input files for the GS-SCF and the GS-NSCF tasks.
"""
multi = abilab.MultiDataset(structure="si.cif", pseudos="14si.pspnc", ndtset=2)
# Set global variables (dataset1 and dataset2)
multi.set_vars(ecut=6, nband=8)
# Dataset 1 (GS-SCF run)
multi[0].set_kmesh(ngkpt=[8, 8, 8], shiftk=[0, 0, 0])
multi[0].set_vars(tolvrs=1e-6)
# Dataset 2 (GS-NSCF run on a k-path)
kptbounds = [
[0.5, 0.0, 0.0], # L point
[0.0, 0.0, 0.0], # Gamma point
[0.0, 0.5, 0.5], # X point
]
multi[1].set_kpath(ndivsm=6, kptbounds=kptbounds)
multi[1].set_vars(tolwfr=1e-12)
# Return two input files for the GS and the NSCF run
scf_input, nscf_input = multi.split_datasets()
return scf_input, nscf_input
from abipy.abio.factories import ebands_input
scf_input, nscf_input = ebands_input(structure="si.cif", pseudos="14si.pspnc")
scf_input, nscf_input = make_scf_nscf_inputs()
hello_flow.register_scf_task(scf_input, append=True)
hello_flow.get_graphviz()
hello_flow[0]
<Work, node_id=387421, workdir=hello_flow/w0>
hello_flow[0][0]
<ScfTask, node_id=387422, workdir=hello_flow/w0/t0>
hello_flow.register_nscf_task(nscf_input, deps={hello_flow[0][0]: "DEN"},
append=True)
hello_flow.get_graphviz(engine="dot")
for task in hello_flow[0]:
print(task)
<ScfTask, node_id=387422, workdir=hello_flow/w0/t0> <NscfTask, node_id=387423, workdir=hello_flow/w0/t1>
flow_with_file = flowtk.Flow(workdir="flow_with_file")
den_filepath = abidata.ref_file("si_DEN.nc")
flow_with_file.register_nscf_task(nscf_input, deps={den_filepath: "DEN"})
for nband in [10, 20]:
flow_with_file.register_nscf_task(nscf_input.new_with_vars(nband=nband),
deps={den_filepath: "DEN"}, append=False)
print("nband in tasks:", [task.input["nband"] for task in flow_with_file.iflat_tasks()])
flow_with_file.get_graphviz()
nband in tasks: [8, 10, 20]
Now we are finally ready for the calculation of the vibrational spectrum of AlAs.
Once we have a function returning an input for SCF calculations, it's just a matter of of passing the SCF input to the from_scf_input factory function
def build_flow_alas_phonons():
"""
Build and return a Flow to compute the dynamical matrix on a (2, 2, 2) qmesh
as well as DDK and Born effective charges.
The final DDB with all perturbations will be merged automatically and placed
in the Flow `outdir` directory.
"""
from abipy import flowtk
scf_input = make_scf_input(ecut=6, ngkpt=(4, 4, 4))
return flowtk.PhononFlow.from_scf_input("flow_alas_phonons", scf_input,
ph_ngqpt=(2, 2, 2), with_becs=True)
flow_phbands = build_flow_alas_phonons()
flow_phbands.get_graphviz()
abirun.py flow_workdir scheduler
For futher info, see this notebook
Databases can be used to
Building an Abiflows workflow for DFPT requires:
from pseudo_dojo.core.pseudos import OfficialDojoTable
from abiflows.fireworks.workflows.abinit_workflows import PhononFullFWWorkflow
# Pseudopotential table from the PseudoDojo package
pseudo_table = OfficialDojoTable.from_dojodir('ONCVPSP-PBEsol-PDv0.4','standard')
# Create fireworks workflow with default settings
structure = abilab.Structure.from_file("si.cif")
wf = PhononFullFWWorkflow.from_factory(structure=structure, pseudos=pseudo_table)
from abiflows.fireworks.workflows.abinit_workflows import *
from abiflows.database.mongoengine.utils import DatabaseData
from abiflows.database.mongoengine.abinit_results import RelaxResult
from pseudo_dojo.core.pseudos import OfficialDojoTable
# Pseudopotential Table from PseudoDojo
pseudo_table = OfficialDojoTable.from_dojodir('ONCVPSP-PBEsol-PDv0.4','standard')
# Database with relaxed structures
source_db = DatabaseData(host='database_address', port=27017,
collection='collection_name_used_for_relax',
database='database_used_to_store_relax_calc')
# Database used to store DFPT results.
db = DatabaseData(host='database_address', port=27017, collection='phonon_bs',
database='database_name_eg_phonons')
# Connect to the database
source_db.connect_mongoengine()
# Download relaxed structure from the database.
with source_db.switch_collection(RelaxResult) as RelaxResult:
relaxed_structure = RelaxResult.objects(mp_id="mp-149")[0].structure
wf = PhononFullFWWorkflow.from_factory(structure=structure, pseudos=pseudo_table)
wf.add_mongoengine_db_insertion(db)
wf.add_final_cleanup(["WFK", "1WF", "WFQ", "1POT", "1DEN"])
wf.add_to_db()
from abiflows.database.mongoengine.abinit_results import PhononResult
# Find results for SiC
r = PhononResult.objects(mp_id='mp-8062')[0]
# Get DDB object from the database.
with r.abinit_output.ddb.abiopen() as ddb:
# Run anaddb
phbst, phdos = ddb.anaget_phbst_and_phdos_files(ngqpt=[8, 8, 5])
# Use AbiPy API
phbst.phbands.plot_with_phdos(phdos.phdos, units='cm-1')
The ab-initio community is migrating to python to implement:
Difficulties for users:
Advantages for users:
"An investment in knowledge pays the best interest" (B. Franklin)