1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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