Explain how to subclass Tasks and avoid pickle errors

This commit is contained in:
Matteo Giantomassi 2018-08-21 22:34:08 +02:00
parent ff65fab909
commit 1df8b4e162
7 changed files with 127 additions and 38 deletions

View File

@ -11,7 +11,7 @@ import numpy as np
import spglib
from six.moves import cStringIO
from tabulate import tabulate
from monty.string import is_string
from monty.itertools import iuptri
from monty.functools import lazy_property
@ -119,7 +119,7 @@ def _get_det(mat):
class Operation(object):
"""
Abstract base class that defines the methods that must be
implememted by the concrete class representing some sort of operation
implemented by the concrete class representing some sort of operation
"""
@abc.abstractmethod
def __eq__(self, other):
@ -148,6 +148,17 @@ class Operation(object):
def isE(self):
"""True if self is the identity operator"""
#def commute(self, other)
# return self * other == other * self
#def commutator(self, other)
# return self * other - other * self
#def anticommute(self, other)
# return self * other == - other * self
#def direct_product(self, other)
class SymmOp(Operation, SlotPickleMixin):
"""
@ -236,6 +247,18 @@ class SymmOp(Operation, SlotPickleMixin):
self.afm_sign == 1)
# end operator protocol.
#@lazy_property
#def order(self):
# """Order of the operation."""
# n = 0
# o = self
# while m < 1000:
# if o.isE: return n
# n += 1
# o = self * o
# else:
# raise ValueError("Cannot find order")
def __repr__(self):
return str(self)
@ -431,7 +454,7 @@ class OpSequence(collections.Sequence):
return -1
def is_group(self):
"""True if the list of operations represent a group."""
"""True if this set of operations represent a group."""
check = 0
# Identity must be present.
@ -454,7 +477,7 @@ class OpSequence(collections.Sequence):
return check == 0
def is_commutative(self):
"""True if operations in self commute with each other."""
"""True if all operations commute with each other."""
for op1, op2 in iuptri(self, diago=False):
if op1 * op2 != op2 * op1:
return False
@ -462,7 +485,7 @@ class OpSequence(collections.Sequence):
return True
def is_abelian_group(self):
"""True if self is a commutative group."""
"""True if commutative group."""
return self.is_commutative() and self.is_group()
def asdict(self):
@ -472,6 +495,15 @@ class OpSequence(collections.Sequence):
"""
return {op: idx for idx, op in enumerate(self)}
#def is_subset(self, other)
# indmap = {}
# for i, op in self:
# j = other.find(op)
# if j != -1: indmap[i] = j
# return indmap
#def is_superset(self, other)
@lazy_property
def mult_table(self):
"""
@ -849,7 +881,7 @@ class LittleGroup(OpSequence):
# Add character_table from Bilbao database.
bilbao_ptgrp = bilbao_ptgroup(self.kgroup.sch_symbol)
lines.extend(l.strip() for l in bilbao_ptgrp.to_string().splitlines())
app(bilbao_ptgrp.to_string(verbose=verbose))
app("")
# Write warning if non-symmorphic little group with k-point at zone border.
@ -1073,6 +1105,9 @@ class LatticeRotation(Operation):
# return t
# TODO: Need to find an easy way to map classes in internal database
# onto classes computed by client code when calculation has been done
# with non-conventional settings (spglib?)
class Irrep(object):
"""
This object represents an irreducible representation.
@ -1117,6 +1152,9 @@ class Irrep(object):
def character(self):
return self._character
#@lazy_property
#def dataframe(self):
def bilbao_ptgroup(sch_symbol):
"""
@ -1184,29 +1222,58 @@ class BilbaoPointGroup(object):
"""List with the names of the irreps."""
return list(self.irreps_by_name.keys())
@property
@lazy_property
def character_table(self):
"""Table of strings with the character of the irreps."""
"""
Dataframe with irreps.
"""
# 1st row: ptgroup_name class names and multiplicity of each class
#name_mult = [name + " [" + str(mult) + "]" for (name, mult) in zip(self.class_names, self.class_len)]
#table = [[self.sch_symbol] + name_mult]
#app = table.append
## Add row: irrep_name, character.
#for irrep in self.irreps:
# character = list(map(str, irrep.character))
# app([irrep.name] + character)
#from tabulate import tabulate
#s = tabulate(table[1:], headers=table[0], tablefmt="simple", numalign="left")
#print(s)
# Caveat: class names are not necessarly unique --> use np.stack
import pandas as pd
name_mult = [name + " [" + str(mult) + "]" for (name, mult) in zip(self.class_names, self.class_len)]
table = [[self.sch_symbol] + name_mult]
app = table.append
columns = ["name"] + name_mult
# Add row: irrep_name, character.
for irrep in self.irreps:
character = list(map(str, irrep.character))
app([irrep.name] + character)
stack = np.stack([irrep.character for irrep in self.irreps])
index = [irrep.name for irrep in self.irreps]
df = pd.DataFrame(stack, columns=name_mult, index=index)
df.index.name = "Irrep"
df.columns.name = self.sch_symbol
return table
# TODO
#print(df)
# Convert complex --> real if all entries in a colums are real.
#for k in name_mult:
# if np.all(np.isreal(df[k].values)):
# #df[k] = df[k].values.real
# df[k] = df[k].astype(float)
def to_string(self, tablefmt="simple", numalign="left"):
return df
def to_string(self, verbose=0):
"""
Write a string with the character_table to the given ``stream``.
``tablefmt`` and ``numalign`` options are passed to ``tabulate``.
Return string with the character_table
"""
s = tabulate(self.character_table[1:], headers=self.character_table[0],
tablefmt=tablefmt, numalign=numalign)
return s
return self.character_table.to_string()
#def decompose(self, character):
# od = collections.OrderedDict()
# for irrep in self.irreps:
# irrep.name
# irrep.character
# return od
#def show_irrep(self, irrep_name):
# """Show the mapping rotation --> irrep mat."""

View File

@ -1991,7 +1991,7 @@ class DdbRobot(Robot):
("eV", "meV", "Ha", "cm-1", "Thz"). Case-insensitive.
asr, chneut, dipdip: Anaddb input variable. See official documentation.
with_geo: True if structure info should be added to the dataframe
with_spglib: True to compute sgplib space group and add it to the DataFrame.
with_spglib: True to compute spglib space group and add it to the DataFrame.
abspath: True if paths in index should be absolute. Default: Relative to getcwd().
funcs: Function or list of functions to execute to add more data to the DataFrame.
Each function receives a |DdbFile| object and returns a tuple (key, value)
@ -2080,7 +2080,7 @@ class DdbRobot(Robot):
ddb_header_keys: List of keywords in the header of the DDB file
whose value will be added to the Dataframe.
with_structure: True to add structure parameters to the DataFrame.
with_spglib: True to compute sgplib space group and add it to the DataFrame.
with_spglib: True to compute spglib space group and add it to the DataFrame.
with_path: True to add DDB path to dataframe
manager: |TaskManager| object. If None, the object is initialized from the configuration file
verbose: verbosity level. Set it to a value > 0 to get more information
@ -2256,7 +2256,7 @@ class DdbRobot(Robot):
def write_notebook(self, nbpath=None):
"""
Write a jupyter_ notebook to nbpath. If ``nbpath`` is None, a temporay file in the current
Write a jupyter_ notebook to nbpath. If ``nbpath`` is None, a temporary file in the current
working directory is created. Return path to the notebook.
"""
nbformat, nbv, nb = self.get_nbformat_nbv_nb(title=None)

View File

@ -437,7 +437,7 @@ class DdbRobotTest(AbipyTest):
# Test anacompare_elastic
ddb_header_keys=["nkpt", "tsmear"]
r = robot.anacompare_elastic(ddb_header_keys=ddb_header_keys,
r = robot.anacompare_elastic(ddb_header_keys=ddb_header_keys, with_path=True,
with_structure=True, with_spglib=False, relaxed_ion="automatic", piezo="automatic", verbose=1)
df, edata_list = r.df, r.elastdata_list
assert "tensor_name" in df.keys()

View File

@ -7,7 +7,7 @@ This gallery contains python scripts to generate AbiPy flows from the command li
Run the scripts to generate the directory with the flow and then use :ref:`abirun.py` to execute the flow.
Alternatively, one can use the ``-s`` option to generate the flow and run it immediately with the scheduler.
Use ``--help`` to further information on the available options.
Use ``--help`` for further information on the available options.
.. warning::

View File

@ -11,4 +11,8 @@ the :ref:`abiopen.py` script and the command::
abiopen.py FILE -nb
or use one of the options of :ref:`abiview.py` to plot the results automatically.
issue
abiopen.py FILE --expose
to generate plots automatically or use one of the options of :ref:`abiview.py` to plot the results automatically.

View File

@ -32,6 +32,8 @@ TODO list:
* Read forces in read_structure ?
* Automate CHANGELOG creation.
## Medium priority
* remove phononflow
@ -93,8 +95,6 @@ TODO list:
* DONE plot_networkx does not work with flows containing callbacks e.g. run_qptdm_flow
FIXED with graphviz
* DONE Use graphvix in flow sphinx-gallery
* Check xsf_write_data and visualization of potentials.
* Add phbands.to_bxsf and histogram for phonon modes at a given q-point.
@ -132,8 +132,6 @@ TODO list:
* Remove GUI code.
* _repr_html_ for structure and other basic objects (pymatgen/and abipy)
* nbjsmol (build system, refactor API?)
* fatbands with SOC (waiting for Matthieu's refactoring)
@ -150,3 +148,7 @@ TODO list:
* Replace SIGRES with new fileformat based on SIGEPH (long-term)
* Update spack recipe, add support for EasyBuild, revamp homebrew (?)
* Classification of phonons/electrons
* Error handler for tolwfr to increase nband / nbdduf and resubmit

View File

@ -16,10 +16,10 @@ Feel free to suggest new entries!
Suggestions:
* Start with the examples available in examples/flows before embarking on large scale calculations.
* Make sure the Abinit executable compiled on the machine can be executed both on the frontend
* Make sure the Abinit executable compiled on the machine can be executed both on the front end
and the compute node (ask your sysadmin)
* If you are running on clusters in which the architecture of the compute node is completely different
from the one available on the frontend, use ``shell_runner``
from the one available on the front end, use ``shell_runner``
* Use the ``debug`` command
Do not:
@ -91,7 +91,7 @@ When running many calculations,
Use ``prtwf -1`` to tell Abinit to produce the wavefunction file only
if SCF cycle didn't converged so that AbiPy can reuse the file to restart the calculation.
Note that it's possibile to use::
Note that it's possible to use::
flow.use_smartio()
@ -101,9 +101,25 @@ for their children.
How to extend tasks/works with specialized code
-----------------------------------------------
Remember that pickle_ does not support classes defined inside scripts.
Remember that pickle_ does not support classes defined inside scripts (`__main__`).
This means that `abirun.py` will likely raise an exception when trying to
reconstruct the object from the pickle file:
.. code-block:: python
AttributeError: Cannot get attribute 'MyWork' on <module '__main__'
If you need to subclass one of the AbiPy Tasks/Works/Flows, define the subclass
in a separated python module and import the module inside your script.
We suggest to create a python module in the AbiPy package e.g. `abipy/flowtk/my_works.py`
in order to have an absolute import that allows one to use
.. code-block:: python
from abipy.flowtk.my_works import MyWork
in the script without worrying about relative paths and relative imports.
Kill a scheduler running in background
--------------------------------------
@ -123,6 +139,6 @@ Try to understand why a task failed
-----------------------------------
There are several reasons why a task could fail.
Some of these reasons could be related to hardaware failure, disk quota,
OS or resource manager errors.
others are related to Abinit-specific errors.
Some of these reasons could be related to hardware failure, disk quota,
OS errors or resource manager errors.
Others are related to Abinit-specific errors.