Recommendations / howto¶
Definition .h vs Implementation .cxx¶
The header files .h should only contain definitions. Exceptions are allowed
for properties that directly return the content of an attribute
(example: DataStructure.getName()).
Others functions should be implemented in the .cxx file
(example: DataStructure.addDependency()).
It avoids to rebuild everything for a small change.
Default arguments and overloaded methods in Python Interface¶
When a C++ method has default arguments, the default values must also be explicitly defined in the Python interface (because they are not part of the function’s type information). Do not use overloading just for default arguments.
Overloaded methods have to be described in the Python interface using
py::overload_cast< types... >( method ).
Define overloaded methods in the Python interface only if the types of the arguments
differ.
See as example Mesh.getNodes() and its interface in
bibcxx/PythonBindings/MeshInterface.cxx.
#include "aster_pybind.h";
void exportMeshToPython( py::module_ &mod )
{
py::class_< Mesh, Mesh::MeshPtr, BaseMesh >( mod, "Mesh" )
.def( "getNodes",
py::overload_cast< const std::string, const bool, const bool >( &Mesh::getNodes,
py::const_ ),
R"(
... docstring ...
Arguments:
group_name (str): Description
...
Returns:
type: Description.
)",
py::arg( "group_name" ) = "", py::arg( "localNumbering" ) = true,
py::arg( "same_rank" ) = true )
.def( "getNodes",
py::overload_cast< const bool, const bool >( &Mesh::getNodes, py::const_ ),
py::arg( "localNumbering" ), py::arg( "same_rank" ) = true )
- Note:
The docstring should not be repeated, or only if the arguments are very different between overloaded methods because all docstrings will be exported in Sphinx pages.
Default values must be consistent with those of the class header.
constmethods must use the additional argumentpy::const_.C++ methods should not create
PyObjectbutstd::map,std::vector…
How to return different types from a unique Python interface¶
The situation is as follows:
On the C++ side, we have 2 member functions of the class ModeResult
with different names and which return different types:
AssemblyMatrixDisplacementRealPtr getDisplacementRealStiffnessMatrix();
AssemblyMatrixTemperatureRealPtr getTemperatureRealStiffnessMatrix();
We want to interface these 2 functions by only one getStiffnessMatrix on the
Python side but which returns the 2 different types according to the case.
To do this, we use the std::variant type to store both types returned by the 2 functions.
using MatrixVariant = std::variant< AssemblyMatrixDisplacementRealPtr,
AssemblyMatrixTemperatureRealPtr >;
Then, we have to write a getStiffnessMatrix function that returns one or the
other type depending on the case but storing it in a std::variant.
This function will become a member function of the python class
code_aster.Objects.ModeResult so the C++ function
must take as argument a ModeResultPtr.
MatrixVariant getStiffnessMatrix( const ModeResultPtr self )
{
auto mat1 = self->getDisplacementRealStiffnessMatrix();
if( mat1 != nullptr )
return MatrixVariant( mat1 );
auto mat2 = self->getTemperatureRealStiffnessMatrix();
return MatrixVariant( mat2 );
};
In the pybind11 interface of the ModeResult class, we must add the function:
.def( "getStiffnessMatrix", &getStiffnessMatrix )
NB: In the real life, getStiffnessMatrix is a template function.
Macro-Commands¶
Legacy Macro-commands do not work as is.
There is no need to define an executor manually. Default
ExecuteMacrois just adapted bycode_aster.Commands.operatorusing the right catalog description.The body of the macro-command, the
ops()function, is automatically called by therun()factory.Results of macro-commands are created directly by the
ops()function (called byexec_()).create_result()method does nothing else registering the additional results (declared withCO()).The
ops()function must now returns the result object it creates.
For user Macro-commands or those from Contrib directory, an executor must be manually added (since the catalog description can not be imported from the official ones). A convenient function allows to easily define this executor:
from code_aster.Supervis.ExecuteCommand import UserMacro
MA_MACRO = UserMacro("MA_MACRO", MA_MACRO_cata, ma_macro_ops)
Required changes¶
The
ops()function returned an exit code as integer.Now, it must return the created result object, or None if there is not.
In code_aster legacy the keywords arguments passed to
ops()contained all existing keywords, eventually with None value.Now, only the user keywords + the default keywords are passed. So, only compulsory keywords and those having a default value can be arguments of the
ops()function. If needed, these arguments may be wrapped by_F()that provides a[]operator that returns None if a keyword does not exist.Example:
def my_macro_ops(INFO, **kwargs): """...""" kwargs = _F(kwargs) para = kwargs['NOM_PARA'] # no failure even if the keyword does not exist
Tests on DataStructures types must be changed. For example:
Replace
AsType(obj) is fonction_sdaster,type(obj) is fonction_sdasterorisinstance(obj, fonction_sdaster)by
obj.getType() == "FONCTION"Object
MCLISTdoes not exist anymore. List of factor keywords is just a list or a tuple.Just use
force_list()to ensure to have a list even if the user passed only one occurrence..List_F()does not exist anymore.Replace
POUTRE.List_F()byforce_list(POUTRE).Temporarly one can use
POUTRE = ListFact(POUTRE)not to change the code and letPOUTRE.List_F()with a dummy.List_F()function that does nothing.Usage of logical units: See
code_aster.Helpers.LogicalUnit.Additional results (CO() objects):
They must be registered with
register_result(). It replaces DeclareOut() but must be called after the result creation.- self.DeclareOut('num', numeddl) + # self.DeclareOut('num', numeddl) num = NUME_DDL(MATR_RIGI=_a, INFO=info) + self.register_result(num, numeddl)
In the legacy version some testcases sometimes define
OBJ = CO('NAME')and then pass eitherNAMEorOBJto children commands. Now using the legacy mode of macro-commands that publishesNAMEin the parent contextOBJcan not be passed to children commands. It will not have the expected type (it stays aCOobject and not becomes aTableorMesh!).When the new mode will be enabled one will just use
result.NAMEwithout ambiguity.
Parallel specific DataStructures¶
Q: How to pass a code_aster.Objects.ParallelMesh to a command?
A: The solution is in “a code_aster.Objects.ParallelMesh is a code_aster.Objects.Mesh”. It is just necessary to declare a
DataStructure is the Python command description (catalog) that matches the
same type.
Example: code_aster.Objects.ParallelMesh.getType()
returns MAILLAGE_P, so one defines:
class maillage_p(maillage_sdaster):
pass
Common errors¶
The compilation works but
waf_debug installends withstderr: Segmentation faultduring the compilation of elements catalogs.Explanation: It may be an error in a Python function called from a C or Fortran function. Check it by manually importing the module in a Python interpreter:
$ cp ../src/build/mpidebug/catalo/cata_ele.ojb fort.4 $ python >>> from code_aster import CA >>> CA.init(CATALOGUE={"FICHIER": "CATAELEM", "UNITE": 4}) >>> CA.MAJ_CATA(ELEMENT={}) >>> exit()
An undefined symbol in an underlying library, for example
libaster.so, may also causestderr: Segmentation fault. Try to import the libraries one by one.The compilation works but the first execution fails with
ImportError: generic_type: type "PythonBool" is already registered!.Solution: You just adds a pybind11 binding for a new class? You probably forgot the
shared::ptrancestor (xxPtr).py::class_< Picklable, PicklablePtr >( mod, "Picklable" )