Package mbdyn :: Package bindings
[hide private]

Package bindings

source code

The interface between MBDyn in its SWIG version and Python. After exposing the package motivation, two problems are addressed. The first one is a communication problem, MBDyn objects should receive and return Python objects. Thus the C++ interface should be completely hidden. The second deals with the access to MBDyn objects. The user must be able to navigate in the objects tree if he wants to communicate with them. However MBDyn was never exposing its object organisation, because the user was supposed to write an input file. Finally the WraptMBDyn philosophy is presented.

1 Package motivation

In short, the motivation is to turn MBDyn as a service that can be controlled from Python. The mbdyn module is mainly an extension to the MBDyn functionalities by avoiding the user to write an input file and instead deal with objects. The bindings part will introduce communication. But why was it needed?

In MBDyn the C++ objects are updated at each iteration, no matter if the solver has reached a step of equilibrium or not. The main method called on an object is AssRes, which stands obviously for 'assemble residual' during the research of equilibrium between internal and external work (the Newton Raphson iterations). If an user wants to develop a new object, he has to provide the necessary methods called by the solver. Then he can link it to MBDyn thanks to the ltdl library from the libtool package. Above the problem to develop code in a low language, the main issue is at runtime: the object will update its residual in C++. If for finding the residual an external library needs to be called, this operation will also have to be implemented in C++. This is particularly tedious when the external calculations will need many information about the simulation status (node positions, orientation matrix, time and so on). All the topological work of keeping objects reference will have to be done in C++ and all the information that those objects return are inconvenient comparing to a dynamic language (the Vec3 object in section 2 gives an example). Moreover for the development part, speed is not critical because the simulation is run for few iterations. Instead, a convenient way to access any value is more interesting for starting coupling calculations.

Python can do such work as it allows references to objects. The motivation is to control the solver resolution and provides a way to navigate in the objects tree for accessing the relevant values. Once the prototyping part of the program reached, the user could continue to describe the topology of its simulation in Python, but call extra calculations from a low language library. The top package, mbdyn, will now be able to manipulate C++ instances. For the user, the last step is to develop everything in pure C++ inside the MBDyn code, nevertheless it will be for a working solution, not a research phase. The next section explained how the communication with Python was reached, but the MBDyn control is addressed in section 3.

2 Python communication

The tool used to establish a connection between C++ and Python is SWIG. The interface is contained in the file swigModule.py, that is automatically generated (and for this reason not present in the Epydoc documentation) from SWIG input files. The process also creates the library _swigModule.so, dynamically linked to the MBDyn shared library, libmbdyn.so. The result is an access to MBDyn C++ objects from a script language.

However the interface achieved is raw, it means that the user has to deal with the C++ methods used in MBDyn and gets back pointer to C++ instances. For example a call like node.GetXCurr() will return a Vec3 vector object defined in 'libmbmath.i'. For filling or getting values from such a vector, the set_value and get_value methods need to be used. It is clearly easier to return Numpy arrays for the user. Moreover as the SWIG interface returns references, if the value needs to be saved at a particular time step the vector object will have to be copied. Numpy provided a convenient way to deal with such problem by array.copy(). That's why every vector or matrix will correspond to a Numpy array.

To the user, the C++ interface is completely hidden by writing a Python object on the top of a SWIG one. This object is responsible of managing a C++ reference by calling the appropriate methods and translating everything between SWIG and Python. Such objects can be found in mbdyn.bindings.basic_objects, mbdyn.bindings.nodes, mbdyn.bindings.elements, mbdyn.bindings.frames and so on for more specific MBDyn objects. The next section deals with the MBDyn architecture.

3 Accessing MBDyn objects

This section is made of notes on the MBDyn internal way of work. It explains the modifications to the MBDyn source code and cites the concerned source files in C++ but also in SWIG.

3.1 Controlling the solver

For controlling the solver, the class SolverBinding has been written in the files 'solver_bindings.h' and 'solver_binings.cc'. The SWIG interface to those files is in 'solver.i'. The main modification is to have split the Run method of the MBDyn Solver class (defined in 'solver.h') into three methods that control the resolution: run_init, run_first_step and run_one_step. Moreover the most important values, like the time and time step, have been made accessible trough a float (MBDyn was using a special declaration).

The current implementation does not allow to update the value at each iteration but only at each time step, when an equilibrium has been reached. This approach is of course less rigorous but could already provide good results between a pure MBDyn solution and a bindings one. The reason is in the time step. If the time step is sufficiently small for the problem, then the equilibrium will be reached in a few iteration, ideally just one. As a result, the solver will not reduce the time step from a significant manner. The external loads provided by an extra calculation at a time t will be the ones used by MBDyn to find the equilibrium between the internal and external work. In case of loads depending of time, such approach could nevertheless create an instability by supplying a load that can not reduced by the solver. As MBDyn was an executable, and not a library, the main function of 'mbdyn.cc' has been transformed into the class WraptMBDyn, defined in 'mbdyn_bindings.h. The interface to that class is in 'mbdyn.i' and has a SolverBinding instance under the attribute solver. By that way the solver control is achieved through WraptMBDyn, a class that is going to receive a MBDyn input file and thus represent the MBDyn service in Python. The next step will be to establish a communication with MBDyn objects describing the problem: the reference frames, nodes and elements.

3.2 Navigating the MBDyn tree

In MBDyn, the nodes and elements are organised into groups by the DataManager, describe in 'dataman.h'. The reference frames are however part of the MBDynParser, defined in 'mbpar.h'. The DataManager is attribute of the SolverBinding while the MBDynParser is attribute of WraptMBDyn. The corresponding SWIG interfaces can then be found in 'dataman.i' and 'mbdyn_parser.i'. Any of those items, reference frame, node or element, will have to be accessed.

Inside a group all the items are linked by pointers. The group keeps the reference of the first item, then every item has a reference to the next one (the definition of a linked list). This design allows to loop in all the items and thus calls the needed methods for a resolution step. Again the AssRes method is an example. In the Python world, each linked list will become a simple list made of references to MBDyn items. This is the work of the mbdyn.bindings.groups. Moreover for nodes and elements, a manager of all the groups will be defined by Groups. The situation is simpler for the reference frames as there is only one group and one class, so everything is contained in mbdyn.bindings.frames.

All the reference frames, nodes and elements will become accessible from WraptMBDyn, it was not seen as convenient to access nodes and elements through the solver. When scanning the input file, the init method will create the groups that are found and store the references. Thus any object will be accessible by a Python syntax, the last step is the communication.

3.3 Accessing values at run time

This section only concerns nodes and elements, that are however the most important items. In the C++ code the groups of the DataManager use a single class definition for different classes. This MBDyn polymorphism is described by Node in 'elem.h' and Elem in 'elem.h'. Thus the DataManager returns pointers to those classes but the object of interest is more specific. It was not a problem for MBDyn because the developers knew what were the methods defined as virtual. In case of an user interface, any method may need to be called.

The starting point was to provide a general node and element class in mbdyn.bindings.basic_objects. When parsing the MBDyn input file, those items were organised into groups, as explained in the section 3.2. Each group have also a general class acting as a template. For example the class Force in 'force.h' is the top class of the force group. The same remark applied for Joint in 'joint.h. Now for getting down of the hierarchy, a dynamic cast process will have to be used from C++. Each group will need to have a function, executing a dynamic cast, that will convert a reference from a top class to a group reference. This step is for example achieved by convert_to_force in 'force.cc' and 'force.i' contains its SWIG interface. Inside a group, more specific classes are also available. So the conversion process may be pushed further by applying one more dynamic cast and finally getting the reference of interest. The initial reference can finally be lost because the lowest class of a hierarchy inherits all the methods from the top classes.

This process is also accomplished in the init method of WraptMBDyn, so the user gets back useful references as soon as the input file has been parsed. After each time step, implemented MBDyn objects are exposed to the user and return their current values. A reference will be kept under the mbdyn_inst attribute for each corresponding object of the mbdyn package. And the user can even keep such reference in its own script, thus using the MBDyn results that he wants at run time. Nevertheless to get back useful objects, an implementation needs to be done first in C++, then in SWIG and finally in Python. As a consequence, the next section explains how to keep the simplest interface.

4 The simplest interface to the MBDyn service

This module is only supposed to supervised the MBDyn process, the addition of new functionalities should not be done in that part. The goal is only to access some of the objects and see if their results match the MBDyn ones. However, this module will not handle the saving of values nor add new methods on MBDyn objects. What is the development achieved so far?

Based on that philosophy, the structural nodes have been wrapped in mbdyn.bindings.nodes. Some of the forces and joints have been implemented in mbdyn.bindings.forces and mbdyn.bindings.joints. The possibility to access values at run time has however changed the MBDyn way of work about drivers. The user is supposed to describe its object behavior in the input file. He can for example modified the amplitude of a force according to time but the direction is kept constant according to the initial value or according to the node orientation matrix. Those two classes are ConservativeForce and FollowerForce defined in 'strforce.h'. As expected in the AssRes method of those two classes, once the amplitude of the driver got, its multiplication by the direction returns the force value. Now in the file 'strforce.h', the BindingsForce inherits from the ConservativeForce, so all the needed methods used in MBDyn are present. Then one line is modify in the AssRes method: the force value is a vector provided by Python. This interface, described in 'force.i', supply also the FollowerForce functionalities because at each time step the user can get the rotation matrix of the corresponding node. He can also applied the force value that he wants, to a degree that can make the solver crash, but that will be surely part of its research phase. The achievement is to fill a force value at run time by using the nodes properties from Python.

This package asks comprehension efforts but is like a minimalist interface to MBDyn objects. It can potentially bring all the multibody dynamic library to Python but will always use MBDyn as a service. For further development, this is important to test WraptMBDyn independently. Thus by giving an input file written by hand, objects that are available can be directly identified. The service calls however some update and save methods that will be references to the Simulation ones. To conclude, extra services need to be developed in mbdyn because Python references from that package can be seen as MBDyn instances.

Submodules [hide private]