Preparing refactoring in robots

This commit is contained in:
Matteo Giantomassi 2016-08-01 16:32:28 +02:00
parent 2a2e5ef427
commit 5bbfc64d66
5 changed files with 255 additions and 133 deletions

View File

@ -33,16 +33,16 @@ from abipy.abio.robots import GsrRobot, SigresRobot, MdfRobot, DdbRobot, abirobo
from abipy.abio.inputs import AbinitInput, MultiDataset, AnaddbInput, OpticInput
from abipy.abio.abivars import AbinitInputFile
from abipy.abio.factories import *
#from abipy.electrons import ElectronBands, ElectronDosPlotter, ElectronBandsPlotter
from abipy.electrons.ebands import ElectronBands, ElectronDosPlotter, ElectronBandsPlotter, ebands_gridplot
from abipy.electrons.ebands import (ElectronBands, ElectronDosPlotter, ElectronBandsPlotter,
ebands_gridplot, ebands_animate)
from abipy.electrons.gsr import GsrFile
from abipy.electrons.psps import PspsFile
from abipy.electrons.gw import SigresFile, SigresPlotter
from abipy.electrons.bse import MdfFile
from abipy.electrons.scissors import ScissorsBuilder
from abipy.electrons.scr import ScrFile
#from abipy.dfpt import PhbstFile, PhononBands, PhdosFile, PhdosReader
from abipy.dfpt.phonons import PhbstFile, PhononBands, PhdosFile, PhdosReader, phbands_gridplot
from abipy.dfpt.phonons import (PhbstFile, PhononBands, PhdosFile, PhdosReader,
phbands_gridplot)
from abipy.dfpt.ddb import DdbFile
from abipy.dfpt.anaddbnc import AnaddbNcFile
from abipy.dynamics.hist import HistFile
@ -141,6 +141,16 @@ def abiopen(filepath):
return cls.from_file(filepath)
def print_frame(x):
"""
Print entire pandas DataFrame.
"""
import pandas as pd
with pd.option_context('display.max_rows', len(x),
'display.max_columns', len(list(x.keys()))):
print(x)
def software_stack():
"""
Import all the hard dependencies. Returns ordered dict: package --> string with version info.

View File

@ -75,8 +75,11 @@ class Robot(object):
for label, ncfile in args:
self.add_file(label, ncfile)
#@abstractmethod
#def get_dataframe(self, **kwargs):
@classmethod
def for_ext(cls, ext):
def class_for_ext(cls, ext):
"""Return the Robot subclass associated to the given extension."""
for subcls in cls.__subclasses__():
if subcls.EXT in (ext, ext.upper()):
@ -86,6 +89,9 @@ class Robot(object):
"The list of supported extensions is:\n%s" %
[cls.EXT for cls in Robot.__subclasses__()])
# Deprecated. Use class_for_ext
for_ext = class_for_ext
@classmethod
def from_dir(cls, top, walk=True):
"""
@ -116,6 +122,19 @@ class Robot(object):
return cls(*items)
@classmethod
def from_files(cls, filenames):
"""
Build a Robot from a list of files.
"""
from abipy.abilab import abiopen
filenames = [f for f in filenames if f.endswith(cls.EXT + ".nc") or f.endswith(cls.EXT)]
items = []
for f in filenames:
ncfile = abiopen(os.path.join(dirpath, f))
if ncfile is not None: items.append((ncfile.filepath, ncfile))
return cls(*items)
@classmethod
def from_flow(cls, flow, outdirs="all", nids=None):
"""
@ -166,7 +185,9 @@ class Robot(object):
return robot
def add_extfile_of_node(self, node, nids=None):
"""Add the file produced by this node to the robot."""
"""
Add the file produced by this node to the robot.
"""
if nids and node.node_id not in nids: return
filepath = node.outdir.has_abiext(self.EXT)
if filepath:
@ -231,7 +252,7 @@ class Robot(object):
lines = ["%s with %d files in memory" % (self.__class__.__name__, len(self.ncfiles))]
for i, f in enumerate(self.ncfiles):
path = f.relpath if len(f.relpath) < len(f.filepath) else f.filepath
lines.append(" [%d] %s" % (i, path))
lines.append(" [%d] %s" % (i, path))
return "\n".join(lines)
__str__ = __repr__
@ -281,18 +302,6 @@ class Robot(object):
if nids is not None: raise ValueError("nids cannot be used when obj is a directory.")
return cls.from_dir(obj)
@staticmethod
def _get_geodict(structure):
"""
Return a dictionary with info on the structure (used to build pandas dataframes).
"""
abc, angles = structure.lattice.abc, structure.lattice.angles
return dict(
a=abc[0], b=abc[1], c=abc[2], volume=structure.volume,
angle0=angles[0], angle1=angles[1], angle2=angles[2],
formula=structure.formula,
)
def _exec_funcs(self, funcs, arg):
"""
Execute list of callables funcs. Each func receives arg as argument.
@ -342,30 +351,28 @@ class GsrRobot(Robot, NotebookWriter):
the pandas :class:`DataFrame`
funcs:
Function or list of functions to execute to add more data to the DataFrame.
Each function receives a GsrFile object and returns a tuple (key, value)
Each function receives a :class:`GsrFile` object and returns a tuple (key, value)
where key is a string with the name of column and value is the value to be inserted.
"""
# TODO add more columns
# Add attributes specified by the users
attrs = [
"nsppol", "ecut", "pawecutdg", #"nspinor", "nspden", #"magnetization",
"tsmear", "nkpts", "energy", "pressure", "max_force",
"energy", "pressure", "max_force",
"ecut", "pawecutdg",
"tsmear", "nkpts",
"nsppol", "nspinor", "nspden",
] + kwargs.pop("attrs", [])
rows, row_names = [], []
for label, gsr in self:
row_names.append(label)
#d = {aname: getattr(gsr, aname) for aname in attrs}
d = {}
d = OrderedDict()
for aname in attrs:
try:
d[aname] = getattr(gsr, aname)
except NetcdfReaderError:
pass
d[aname] = getattr(gsr, aname, None)
# Add info on structure.
if kwargs.get("with_geo", True):
d.update(self._get_geodict(gsr.structure))
d.update(gsr.structure.get_geodict())
# Execute funcs.
d.update(self._exec_funcs(kwargs.get("funcs", []), gsr))
@ -432,7 +439,9 @@ class GsrRobot(Robot, NotebookWriter):
class SigresRobot(Robot):
"""This robot analyzes the results contained in multiple SIGRES files."""
"""
This robot analyzes the results contained in multiple SIGRES files.
"""
EXT = "SIGRES"
def merge_dataframes_sk(self, spin, kpoint, **kwargs):
@ -458,7 +467,9 @@ class SigresRobot(Robot):
rows, row_names = [], []
for label, sigr in self:
row_names.append(label)
d = {aname: getattr(sigr, aname) for aname in attrs}
d = OrderedDict()
for aname in attrs:
d[aname] = getattr(sig, aname, None)
d.update({"qpgap": sigr.get_qpgap(spin, kpoint)})
# Add convergence parameters
@ -466,7 +477,7 @@ class SigresRobot(Robot):
# Add info on structure.
if kwargs.get("with_geo", False):
d.update(self._get_geodict(sigr.structure))
d.update(sigr.structure.get_geodict())
# Execute funcs.
d.update(self._exec_funcs(kwargs.get("funcs", []), sigr))
@ -490,7 +501,9 @@ class SigresRobot(Robot):
class MdfRobot(Robot):
"""This robot analyzes the results contained in multiple MDF files."""
"""
This robot analyzes the results contained in multiple MDF files.
"""
EXT = "MDF.nc"
def get_mdf_plotter(self):
@ -504,11 +517,11 @@ class MdfRobot(Robot):
rows, row_names = [], []
for i, (label, mdf) in enumerate(self):
row_names.append(label)
d = dict(
exc_mdf=mdf.exc_mdf,
rpa_mdf=mdf.rpanlf_mdf,
gwrpa_mdf=mdf.gwnlf_mdf,
)
d = OrderedDict([
("exc_mdf", mdf.exc_mdf),
("rpa_mdf", mdf.rpanlf_mdf),
("gwrpa_mdf", mdf.gwnlf_mdf),
])
#d = {aname: getattr(mdf, aname) for aname in attrs}
#d.update({"qpgap": mdf.get_qpgap(spin, kpoint)})
@ -517,7 +530,7 @@ class MdfRobot(Robot):
# Add info on structure.
if kwargs.get("with_geo", False):
d.update(self._get_geodict(mdf.structure))
d.update(mdf.structure.get_geodict())
# Execute funcs.
d.update(self._exec_funcs(kwargs.get("funcs", []), mdf))
@ -550,7 +563,9 @@ class DdbRobot(Robot):
@property
def qpoints_union(self):
"""Return numpy array with the q-points in reduced coordinates found in the DDB files."""
"""
Return numpy array with the q-points in reduced coordinates found in the DDB files.
"""
qpoints = []
for (label, ddb) in enumerate(self):
qpoints.extend(q for q in ddb.qpoints if q not in qpoints)
@ -585,7 +600,7 @@ class DdbRobot(Robot):
rows, row_names = [], []
for i, (label, ddb) in enumerate(self):
row_names.append(label)
d = dict(
d = OrderedDict(
# exc_mdf=mdf.exc_mdf,
)
#d = {aname: getattr(ddb, aname) for aname in attrs}
@ -602,7 +617,7 @@ class DdbRobot(Robot):
# Add info on structure.
if kwargs.get("with_geo", True):
d.update(self._get_geodict(phbands.structure))
d.update(phbands.structure.get_geodict())
# Execute funcs.
d.update(self._exec_funcs(kwargs.get("funcs", []), ddb))

View File

@ -549,6 +549,35 @@ class Structure(pymatgen.Structure):
return np.sqrt(self.dot(coords, coords, space=space,
frac_coords=frac_coords))
def get_geodict(self, with_spglib=True):
"""
Return a :class:`OrderedDict` with the most important structural parameters:
- Chemical formula and number of atoms.
- Lattice lengths, angles and volume.
- The spacegroup number computed by Abinit (set to None if not available).
- The spacegroup number and symbol computed by spglib (set to None not `with_spglib`).
Useful to construct pandas DataFrames
Args:
with_spglib: If True, spglib is invoked to get the spacegroup symbol and number
"""
abc, angles = self.lattice.abc, self.lattice.angles
# Get spacegroup info from spglib.
spglib_symbol, spglib_number = None, None
if with_spglib: spglib_symbol, spglib_number = self.get_spacegroup_info()
# Get spacegroup number computed by Abinit if available.
abispg_number = None if self.spacegroup is None else self.spacegroup.spgid
return OrderedDict([
("formula", self.formula), ("num_sites", self.num_sites),
("angle0", angles[0]), ("angle1", angles[1]), ("angle2", angles[2]),
("a", abc[0]), ("b", abc[1]), ("c", abc[2]), ("volume", self.volume),
("abispg_num", abispg_number),
("spglib_symb", spglib_symbol), ("spglib_num", spglib_number),
])
def show_bz(self, **kwargs):
"""
Gives the plot (as a matplotlib object) of the symmetry line path in the Brillouin Zone.
@ -630,29 +659,6 @@ class Structure(pymatgen.Structure):
else:
raise visu.Error("Don't know how to export data for %s" % visu_name)
def molecular_viewer(self, **kwargs):
#from chemview import enable_notebook, MolecularViewer
from chemview import MolecularViewer
#from pymatgen.core.bonds import CovalentBond, get_bond_length
#bonds = []
#for j, site1 in enumerate(self):
# for i, site2 in enumerate(self):
# if j <= i: continue
# if CovalentBond.is_bonded(site1, site2, tol=0.2):
# bonds.append((i, j))
#bonds = self.get_covalent_bonds(tol=0.2)
#bonds=[(0, 1), (1, 0)]
bonds = [(0, 0), (1, 1)]
topology = dict(
atom_types=[site.specie.symbol for site in self],
bonds=bonds,
)
v = MolecularViewer(self.cart_coords, topology, **kwargs)
return v
def write_structure(self, filename):
"""Write structure fo file."""
if filename.endswith(".nc"):
@ -1418,32 +1424,15 @@ def frame_from_structures(struct_objects, index=None, with_spglib=True):
Support filenames, structure objects, Abinit input files, dicts and many more types.
See `Structure.as_structure` for the complete list.
index: Index of the dataframe.
with_spglib: If True, spglib is invoked to get the spacegroup symbol and number
with_spglib: If True, spglib is invoked to get the spacegroup symbol and number.
Return:
pandas :class:`DataFrame`
"""
structures = [Structure.as_structure(obj) for obj in struct_objects]
dict_list = []
for structure in structures:
abc, angles = structure.lattice.abc, structure.lattice.angles
# Get spacegroup info from spglib.
spglib_symbol, spglib_number = None, None
if with_spglib:
spglib_symbol, spglib_number = structure.get_spacegroup_info()
# Get spacegroup number computed by Abinit if available.
abispg_number = None if structure.spacegroup is None else structure.spacegroup.spgid
# Use OrderedDict to have columns ordered nicely.
dict_list.append(OrderedDict([
("formula", structure.formula), ("num_sites", structure.num_sites),
("angle0", angles[0]), ("angle1", angles[1]), ("angle2", angles[2]),
("a", abc[0]), ("b", abc[1]), ("c", abc[2]), ("volume", structure.volume),
("abispg_num", abispg_number),
("spglib_symb", spglib_symbol), ("spglib_num", spglib_number),
]))
# Use OrderedDict to have columns ordered nicely.
odict_list = [(structure.get_geodict(with_spglib=with_spglib)) for structure in structures]
import pandas as pd
return pd.DataFrame(dict_list, index=index,
columns=list(dict_list[0].keys()) if dict_list else None)
return pd.DataFrame(odict_list, index=index,
columns=list(odict_list[0].keys()) if odict_list else None)

View File

@ -28,6 +28,9 @@ class TestStructure(AbipyTest):
# Call pymatgen machinery to get the high-symmetry stars.
print(structure.hsym_stars)
geodict = structure.get_geodict()
assert geodict["abispg_num"] is not None
if self.which("xcrysden") is not None:
# Export data in Xcrysden format.
structure.export(".xsf")

View File

@ -7,15 +7,143 @@ import argparse
from abipy import abilab
def abicomp_struct(options):
"""
Compare crystalline structures.
"""
paths = options.paths
index = [os.path.relpath(p) for p in paths]
frame = abilab.frame_from_structures(paths, index=None)
print("File list:")
for i, p in enumerate(paths):
print("%d %s" % (i, p))
print()
abilab.print_frame(frame)
return 0
def abicomp_ebands(options):
"""
Plot electron bands on a grid.
"""
paths = options.paths
eb_objects = paths
titles = paths
abilab.ebands_gridplot(eb_objects, titles=titles, edos_objects=None, edos_kwargs=None)
#animate = True
#if animate:
# abilab.ebands_animate(eb_objects, edos_objects=None, edos_kwargs=None,
# interval=250, savefile=None, show=True)
return 0
def abicomp_phbands(options):
"""
Plot phonon bands on a grid.
"""
paths = options.paths
phb_objects = paths
titles = paths
abilab.phbands_gridplot(phb_objects, titles=titles, phdos_objects=None, phdos_kwargs=None)
return 0
#def abicomp_pseudos(options):
# paths = options.paths
# index = [os.path.relpath(p) for p in paths]
# frame = abilab.frame_from_pseudos(paths, index=None)
# print("File list:")
# for i, p in enumerate(paths):
# print("%d %s" % (i, p))
# print()
# abilab.print_frame(frame)
# return 0
def abicomp_robot(options):
"""
Analyze multiple files with a robot. The command has two different variants.
[1] The files can be listed explicitly as in:
abicomp.py robot out1_GSR.nc out1_GSR.nc
or, alternatively, with the shell syntax:
abicomp.py robot *_GSR.nc
[2] It's possible to use a directory as the first (and only) argument
of the robot command followed by the Abinit file extension as in:
abicomp.py robot . GSR.nc
By default, the script with call `robot.get_dataframe()` to create an print a table
with the most important results. For finer control, use --ipython to start an ipython
console to interact with the robot directly.
"""
# To define an Help action
#http://stackoverflow.com/questions/20094215/argparse-subparser-monolithic-help-output?rq=1
paths = options.paths
# Temp code
robot = abilab.abirobot(".", "GSR")
print(robot)
abilab.print_frame(robot.get_dataframe())
return 0
if os.path.isdir(paths[0]):
# Directory + extension
top, ext = paths[:2]
cls = Robot.class_for_ext(ext)
robot = cls.from_dir(top, walk=True)
else:
# File list.
#cls = Robot.class_for_ext(ext)
robot = Robot.from_files(paths)
if options.ipython:
import IPython
IPython.embed(header=str(robot) + "\nType `robot` in the terminal and use <TAB> to list its methods", robot=robot)
elif options.notebook:
robot.make_and_open_notebook(nbpath=None, daemonize=True)
else:
print(robot)
abilab.print_frame(robot.get_dataframe())
return 0
def abicomp_gs_scf(options):
"""
Compare ground-state SCF cycles.
"""
paths = options.paths
f0 = abilab.AbinitOutputFile(paths[0])
f0.compare_gs_scf_cycles(paths[1:])
return 0
def abicomp_dfpt2_scf(options):
"""
Compare DFPT SCF cycles.
"""
paths = options.paths
f0 = abilab.AbinitOutputFile(paths[0])
f0.compare_d2de_scf_cycles(paths[1:])
return 0
def main():
def str_examples():
return """\
Usage example:\n
abidiff.py struct */*/outdata/out_GSR.nc => Compare structures in multiple files.
abidiff.py ebands out1_GSR.nc out2_GSR.nc => Plot electron bands on a grid.
abidiff.py gs_scf run1.abo run2.abo => Compare the SCF cycles in two output files.
abidiff.py dfpt2_scf => Compare the DFPT SCF cycles in two output files.
Usage example:
abidiff.py struct */*/outdata/out_GSR.nc => Compare structures in multiple files.
abidiff.py ebands out1_GSR.nc out2_GSR.nc => Plot electron bands on a grid.
abidiff.py phbands out1_PHBST.nc out2_PHBST.nc => Plot electron bands on a grid.
abidiff.py gs_scf run1.abo run2.abo => Compare the SCF cycles in two output files.
abidiff.py dfpt2_scf => Compare the DFPT SCF cycles in two output files.
"""
def show_examples_and_exit(err_msg=None, error_code=1):
@ -41,20 +169,26 @@ Usage example:\n
# Create the parsers for the sub-commands
subparsers = parser.add_subparsers(dest='command', help='sub-command help', description="Valid subcommands")
# Subparser for gs_scf command.
p_gs_scf = subparsers.add_parser('gs_scf', parents=[copts_parser], help="Compare ground-state SCF cycles.")
# Subparser for dfpt2_scf command.
p_dftp2_scf = subparsers.add_parser('dfpt2_scf', parents=[copts_parser], help="Compare DFPT SCF cycles.")
# Subparser for struct command.
p_struct = subparsers.add_parser('struct', parents=[copts_parser], help="Compare crystalline structures.")
p_struct = subparsers.add_parser('struct', parents=[copts_parser], help=abicomp_struct.__doc__)
# Subparser for ebands command.
p_ebands = subparsers.add_parser('ebands', parents=[copts_parser], help="Compare electron bands.")
p_ebands = subparsers.add_parser('ebands', parents=[copts_parser], help=abicomp_ebands.__doc__)
# Subparser for gsr command.
#p_gsr = subparsers.add_parser('gsr', parents=[copts_parser], help="Compare electron bands.")
# Subparser for phbands command.
p_phbands = subparsers.add_parser('phbands', parents=[copts_parser], help=abicomp_phbands.__doc__)
# Subparser for pseudos command.
#p_pseudos = subparsers.add_parser('pseudos', parents=[copts_parser], help=abicomp_pseudos.__doc__)
# Subparser for robot command.
p_robot = subparsers.add_parser('robot', parents=[copts_parser], help=abicomp_robot.__doc__)
# Subparser for gs_scf command.
p_gs_scf = subparsers.add_parser('gs_scf', parents=[copts_parser], help=abicomp_gs_scf.__doc__)
# Subparser for dfpt2_scf command.
p_dftp2_scf = subparsers.add_parser('dfpt2_scf', parents=[copts_parser], help=abicomp_dfpt2_scf.__doc__)
# Parse the command line.
try:
@ -76,37 +210,8 @@ Usage example:\n
#sns.set(style='ticks', palette='Set2')
#sns.despine()
paths = options.paths
if options.command == "gs_scf":
f0 = abilab.AbinitOutputFile(paths[0])
f0.compare_gs_scf_cycles(paths[1:])
elif options.command == "dfpt2_scf":
f0 = abilab.AbinitOutputFile(paths[0])
f0.compare_d2de_scf_cycles(paths[1:])
elif options.command == "struct":
index = [os.path.relpath(p) for p in paths]
frame = abilab.frame_from_structures(paths, index=None)
print("File list:")
for i, p in enumerate(paths):
print("%d %s" % (i, p))
print()
print(frame)
elif options.command == "ebands":
eb_objects = paths
titles = paths
abilab.ebands_gridplot(eb_objects, titles=titles, edos_objects=None, edos_kwargs=None)
else:
raise RuntimeError("Don't know what to do with command: %s!" % options.command)
# Dispatch
#return globals()["dojo_" + options.command](options)
return 0
return globals()["abicomp_" + options.command](options)
if __name__ == "__main__":