Package mbdyn :: Package bindings
[hide private]

Source Code for Package mbdyn.bindings

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # This file is part of MBDyn sim suite. 
  5  # Copyright (C) 2007 André ESPAZE, as part of a Master thesis supervised by 
  6  # Martin O.L.Hansen (DTU) and Nicolas Chauvat (Logilab) 
  7   
  8  # MBDyn sim suite is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 21  # 
 22  """The interface between MBDyn in its SWIG version and Python. 
 23  After exposing the package motivation, two problems  
 24  are addressed. The first one is a communication problem,  
 25  MBDyn objects should receive and return Python objects. Thus the C++ interface 
 26  should be completely hidden. The second deals with the access to   
 27  MBDyn objects. The user must be able to navigate in the objects tree  
 28  if he wants to communicate with them. However MBDyn was never  
 29  exposing its object organisation, because the user was supposed to write  
 30  an input file. Finally the L{WraptMBDyn} philosophy is presented. 
 31   
 32  1 Package motivation 
 33  ==================== 
 34   
 35  In short, the motivation is to turn MBDyn as a service that can be 
 36  controlled from Python. The L{mbdyn} module is mainly an extension  
 37  to the MBDyn functionalities by avoiding the user to write an input file 
 38  and instead deal with objects. The bindings part will  
 39  introduce communication. But why was it needed? 
 40   
 41  In MBDyn the C++ objects are updated at each iteration, no matter if  
 42  the solver has reached a step of equilibrium or not. The main method called on 
 43  an object is C{AssRes}, which stands obviously for 'I{assemble residual}'  
 44  during the research of equilibrium between internal and external work  
 45  (the Newton Raphson iterations). If an user wants to develop a new object, he  
 46  has to provide the necessary methods called by the solver. Then he can link it 
 47  to MBDyn thanks to the I{ltdl} library from the 
 48  U{libtool<http://www.gnu.org/software/libtool/>} 
 49  package. Above the problem to develop code in a low language, 
 50  the main issue is at runtime: the object will update its residual in C++. 
 51  If for finding the residual an external library needs to be called,  
 52  this operation will also have to be implemented in C++. 
 53  This is particularly tedious when  
 54  the external calculations will need many information about the simulation  
 55  status (node positions, orientation matrix, time and so on). All the  
 56  topological work of keeping objects reference will have to be done in C++ 
 57  and all the information that those objects return are inconvenient  
 58  comparing to a dynamic language (the C{Vec3} object  
 59  in section 2 gives an example). Moreover for the development part, speed is  
 60  not critical because the simulation is run for few iterations. Instead,  
 61  a convenient way to access any value is more interesting 
 62  for starting coupling calculations.  
 63   
 64  Python can do such work as it allows  
 65  U{references 
 66  <http://en.wikipedia.org/wiki/Reference_%28computer_science%29>}  
 67  to objects. The motivation  
 68  is to control the solver resolution and provides a way to navigate 
 69  in the objects tree for accessing the relevant values. Once the  
 70  prototyping part of the program reached, the user could continue 
 71  to describe the topology of its simulation in Python, but call 
 72  extra calculations from a low language library. The top package, 
 73  L{mbdyn}, will now be able to manipulate 
 74  C++ instances. For the user, the last step  
 75  is to develop everything in pure C++ inside the MBDyn code,  
 76  nevertheless it will be for a working solution, not a research phase. 
 77  The next section explained how the communication  
 78  with Python was reached, but the MBDyn control is addressed in section 3. 
 79   
 80  2 Python communication 
 81  ====================== 
 82   
 83  The tool used to establish a connection between C++ and Python 
 84  is U{SWIG<http://www.swig.org/>}. 
 85  The interface is contained in the file C{swigModule.py}, 
 86  that is automatically generated (and for  
 87  this reason not present in the Epydoc documentation) from SWIG input 
 88  files. The process also creates the library  
 89  C{_swigModule.so}, dynamically linked to the MBDyn shared library,  
 90  C{libmbdyn.so}. The result is an access to MBDyn C++ objects 
 91  from a script language. 
 92   
 93  However  
 94  the interface achieved is raw, it means that the user has to deal with 
 95  the C++ methods used in MBDyn and gets back pointer to C++ instances.  
 96  For example a call like C{node.GetXCurr()} will return a C{Vec3} vector  
 97  object defined in 'I{libmbmath.i}'. For filling or getting values from  
 98  such a vector, the C{set_value} and C{get_value} methods need to be used. 
 99  It is clearly easier to return U{Numpy<http://numpy.scipy.org>} arrays for 
100  the user. Moreover as the SWIG  
101  interface returns references, if the value needs to be saved at a particular 
102  time step the vector object will have to be copied. Numpy provided  
103  a convenient way to deal with such problem by C{array.copy()}. That's why  
104  every vector or matrix will correspond to a Numpy array.  
105   
106  To the user, the C++ interface is completely hidden by writing a Python object 
107  on the top of a SWIG one. This object is responsible of managing a C++  
108  reference by calling the appropriate methods and translating everything 
109  between SWIG and Python. 
110  Such objects can be found in L{mbdyn.bindings.basic_objects}, 
111  L{mbdyn.bindings.nodes}, L{mbdyn.bindings.elements}, L{mbdyn.bindings.frames} 
112  and so on for more specific MBDyn objects. The next section deals with  
113  the MBDyn architecture. 
114   
115  3 Accessing MBDyn objects 
116  ========================= 
117   
118  This section is made of notes on the MBDyn internal way of work. It explains 
119  the modifications to the MBDyn source code and cites the concerned source  
120  files in C++ but also in SWIG. 
121   
122  3.1 Controlling the solver 
123  -------------------------- 
124   
125  For controlling the solver, the class C{SolverBinding} has been written in  
126  the files 'I{solver_bindings.h}' and 'I{solver_binings.cc}'. The SWIG interface 
127  to those files is in 'I{solver.i}'. The main modification is to have split  
128  the C{Run} method of the MBDyn C{Solver} class (defined in 'I{solver.h}') into 
129  three methods that control the resolution: C{run_init}, C{run_first_step} and 
130  C{run_one_step}. Moreover the most important values, like the time and time  
131  step, have been made accessible trough a float (MBDyn was using a special  
132  declaration). 
133   
134  The current implementation does not allow to update the value at each iteration 
135  but only at each time step, when an equilibrium has been reached. This approach 
136  is of course less rigorous but could already provide good results between  
137  a pure MBDyn solution and a bindings one. The reason is in the time step.  
138  If the time step is sufficiently small for the problem, then the equilibrium  
139  will be reached in a few iteration, ideally just one. As a result, the solver 
140  will not reduce the time step from a significant manner. The external loads 
141  provided by an extra calculation at a time M{t} will be the ones used by  
142  MBDyn to find the equilibrium between the internal and external work. In case 
143  of loads depending of time, such approach could nevertheless create an 
144  instability by supplying a load that can not reduced by the solver. 
145  As MBDyn was an executable, and not a library, the C{main} function of  
146  'I{mbdyn.cc}' has been transformed into the class C{WraptMBDyn}, defined  
147  in 'I{mbdyn_bindings.h}. The interface to that class is in 'I{mbdyn.i}' and  
148  has a C{SolverBinding} instance under the attribute C{solver}. By that way 
149  the solver control is achieved through L{WraptMBDyn}, a class that is going 
150  to receive a MBDyn input file and thus represent the MBDyn service in Python. 
151  The next step will be to establish a communication with MBDyn objects 
152  describing the problem: the reference frames, nodes and elements. 
153   
154  3.2 Navigating the MBDyn tree 
155  ----------------------------- 
156   
157  In MBDyn, the nodes and elements are organised into groups by the 
158  C{DataManager}, describe in 'I{dataman.h}'. The reference frames 
159  are however part of the C{MBDynParser}, defined in 'I{mbpar.h}'.  
160  The C{DataManager} is attribute of the C{SolverBinding} while  
161  the C{MBDynParser} is attribute of C{WraptMBDyn}. The corresponding 
162  SWIG interfaces can then be found in 'I{dataman.i}' and  
163  'I{mbdyn_parser.i}'. Any of those items, reference frame, node or  
164  element, will have to be accessed. 
165   
166  Inside a group all the items are linked by pointers. The group keeps 
167  the reference of the first item, then every item has a reference to  
168  the next one (the definition of a 
169  U{linked list<http://en.wikipedia.org/wiki/Linked_list>}). 
170  This design allows  
171  to loop in all the items and thus calls the needed methods for a  
172  resolution step. Again the C{AssRes} method is an example. 
173  In the  Python world, each linked list will become a simple list 
174  made of references to MBDyn items. This is the work of 
175  the L{mbdyn.bindings.groups}. Moreover for nodes and elements,  
176  a manager of all the groups will be defined by  
177  L{Groups<mbdyn.bindings.main.Groups>}. The situation is simpler for  
178  the reference frames as there is only one group and one class, so 
179  everything is contained in L{mbdyn.bindings.frames}. 
180   
181  All the reference frames, nodes and elements will become accessible  
182  from L{WraptMBDyn}, it was not seen as convenient to access nodes  
183  and elements through the C{solver}. When scanning the input file, 
184  the L{init<mbdyn.bindings.main.WraptMBDyn.init>} 
185  method will create the groups that are found and store the references. 
186  Thus any object will be accessible by a Python syntax, the last step 
187  is the communication. 
188   
189  3.3 Accessing values at run time 
190  -------------------------------- 
191   
192  This section only concerns nodes and elements, that are however the most 
193  important items. In the C++ code the groups of the C{DataManager} use  
194  a single class definition for different classes. This MBDyn 
195  U{polymorphism<http://en.wikipedia.org/wiki/Polymorphism_(computer_science)>} 
196  is described by C{Node} in 'I{elem.h}' and C{Elem} in 'I{elem.h}'.  
197  Thus the C{DataManager} returns pointers to those classes but the object  
198  of interest is more specific. It was not a problem for MBDyn because 
199  the developers knew what were the methods defined as 
200  U{virtual<http://en.wikipedia.org/wiki/Virtual_function>}. In case of 
201  an user interface, any method may need to be called.  
202   
203  The starting point was to provide a general node and element class in 
204  L{mbdyn.bindings.basic_objects}. When parsing the MBDyn input file, 
205  those items were organised into groups, as explained in the section 3.2. 
206  Each group have also a general class acting as a template. For 
207  example the class C{Force} in 'I{force.h}' is the top class  
208  of the force group. The same remark applied for C{Joint} in 'I{joint.h}. 
209  Now for getting down of the hierarchy, a  
210  dynamic cast process will have to be used from C++.  
211  Each group will need to have a function, executing a dynamic cast, that will 
212  convert a reference from a top class to a group reference. This step is 
213  for example achieved by C{convert_to_force} in 'I{force.cc}' and 'I{force.i}' 
214  contains its SWIG interface. 
215  Inside a group, more specific classes are also available. So the conversion 
216  process may be pushed further by applying one more dynamic cast 
217  and finally getting the reference of interest. The initial  
218  reference can finally be lost because the lowest class of a hierarchy inherits 
219  all the methods from the top classes.  
220   
221  This process is also accomplished in the  
222  L{init<mbdyn.bindings.main.WraptMBDyn.init>} method of L{WraptMBDyn}, 
223  so the user gets back useful references as soon as the input file 
224  has been parsed. After each time step, implemented MBDyn objects 
225  are exposed to the user and return their current values. 
226  A reference will be kept under the C{mbdyn_inst} attribute for each 
227  corresponding object of the L{mbdyn} package. And the user can even 
228  keep such reference in its own script, thus using the MBDyn results  
229  that he wants at run time. 
230  Nevertheless to get back useful objects, an implementation 
231  needs to be done first in C++, then in SWIG and finally in Python. As  
232  a consequence,  
233  the next section explains how to keep the simplest interface. 
234   
235  4 The simplest interface to the MBDyn service 
236  ============================================= 
237   
238  This module is only supposed to supervised the MBDyn process, the 
239  addition of new functionalities should not be done in that part.  
240  The goal is only to access some of the objects and see if their results 
241  match the MBDyn ones. However, this module will not handle the  
242  saving of values nor add new methods on MBDyn objects. What is  
243  the development achieved so far? 
244   
245  Based on that philosophy, the structural nodes have been wrapped  
246  in L{mbdyn.bindings.nodes}. Some of the forces and joints 
247  have been implemented in L{mbdyn.bindings.forces} and  
248  L{mbdyn.bindings.joints}. The possibility to access values at run time 
249  has however changed the MBDyn way of work about drivers. The user 
250  is supposed to describe its object behavior in the input file. He  
251  can for example modified the amplitude of a force according to time 
252  but the direction is kept constant according to the initial value 
253  or according to the node orientation matrix. Those two classes are 
254  C{ConservativeForce} and C{FollowerForce} defined in 'I{strforce.h}'.  
255  As expected in the C{AssRes} method of those two classes, once the amplitude 
256  of the driver got, 
257  its multiplication by the direction returns the force 
258  value. Now in the file 'I{strforce.h}', the  
259  C{BindingsForce} inherits from the C{ConservativeForce}, so all the 
260  needed methods used in MBDyn are present. 
261  Then one line is modify in the C{AssRes} method: 
262  the force value is a vector provided by Python. This interface,  
263  described in 'I{force.i}', supply also the C{FollowerForce} functionalities 
264  because at each time step the user can get the rotation matrix 
265  of the corresponding node. He can also applied the force value that he wants, 
266  to a degree that can make the solver crash, but that will be surely part 
267  of its research phase. The achievement is to fill 
268  a force value at run time by using the nodes properties from Python. 
269   
270  This package asks comprehension efforts but is like a minimalist interface 
271  to MBDyn objects. It can potentially bring all the multibody dynamic 
272  library to Python but will always use MBDyn as a service. 
273  For further development, this is important to test L{WraptMBDyn} 
274  independently. Thus by giving an input file written 
275  by hand, objects that are available can be directly identified. The  
276  service calls however some C{update} and C{save} methods that will be 
277  references to the L{Simulation<mbdyn.main.Simulation>} ones. 
278  To conclude, extra services need to be developed in L{mbdyn}  
279  because Python references from that package can be seen as MBDyn instances. 
280  """ 
281