Package mbdyn :: Package bindings :: Module main
[hide private]

Source Code for Module mbdyn.bindings.main

  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 MBDyn service available from Python by L{WraptMBDyn}. This module  
 23  handles also the recovery of all the pointers to the C++ instances 
 24  by creating Python objects, described as a start in 
 25  L{mbdyn.bindings.basic_objects}. 
 26   
 27  An interface to all the MBDyn groups is also provided by L{Groups}. 
 28  """ 
 29  import os 
 30  import time 
 31   
 32  import mbdyn.bindings.swigModule as swigModule 
 33  import mbdyn.bindings.progressbar as progressbar 
 34   
 35  from mbdyn.bindings.basic_objects import BASIC_CLASS 
 36  from mbdyn.bindings.groups import GROUP 
 37  from mbdyn.bindings.frames import ReferenceFrame, FramesList 
 38   
 39   
40 -class ProgressBar:
41 """The MBDyn progress bar. Used for displaying the 42 C{Solver} advance and based on the progress bar defined 43 in L{mbdyn.bindings.progressbar}. 44 """ 45
46 - def __init__(self, final_time=None):
47 self.pbar = None 48 self.widgets = ['MBDyn progress: ', 49 progressbar.Percentage(), 50 progressbar.Bar(marker='#', left=' [', right='] ')] 51 if final_time != None: 52 self.create_for(final_time)
53
54 - def create_for(self, final_time):
55 """Create a progress bar according to the final time""" 56 self.pbar = progressbar.ProgressBar(maxval=final_time, 57 widgets=self.widgets) 58 self.pbar.start()
59
60 - def update(self, current_time):
61 """Update the progress bar""" 62 self.pbar.update(current_time)
63
64 - def stand_by(self, current_time):
65 """Pause the progress bar at the time given. The bar will 66 be seen in the console.""" 67 self.pbar.stand_by(current_time)
68
69 - def restart(self, current_time):
70 """Will write a new bar in the console""" 71 self.pbar.restart(current_time)
72
73 - def destroy(self):
74 """End of the progress bar""" 75 self.pbar.finish()
76 77
78 -class Groups:
79 """Gather MBDyn groups of nodes or elements. 80 The nodes and elements 81 are organised into groups by the C{DataManager}, described in the file 82 'I{dataman.h}'. The SWIG interface to the C{DataManager} is able to get 83 back the objects pointers, so then they can create Python objects as 84 described in L{mbdyn.bindings.basic_objects}. The SWIG interface 85 of the C{DataManager} is described in 'I{dataman.i}' and the instance 86 of that class is C{pDM}, attribute of the MBDyn C{Solver}. 87 88 This class will interact with C{pDM} and get back all 89 the MBDyn objects. However this step requires to have a corresponding 90 group where to store the object reference. That is the role of 91 the L{mbdyn.bindings.groups} module. 92 93 Moreover MBDyn uses a polymorphism on the C{Elem} class or C{Node} class. 94 This single class definition will 95 try to be converted to a general class for the group, this is achieved 96 in the method L{try_to_convert}. 97 98 Finally this class is responsible of listing present groups. For example:: 99 100 >>> wm = WraptMBDyn("input_file") 101 >>> wm.init() 102 >>> wm.elts 103 [automatic_structurals, 104 bodies, 105 joints, 106 beams, 107 forces] 108 109 C{elts} is a C{Groups} instance. 110 """ 111
112 - def __init__(self, item_key):
113 """Translation of the C++ enumeration done in MBDyn, each key 114 corresponds to an integer, indice of the group.""" 115 self.identities = [] 116 self.list = [] 117 self.list_keys = [] 118 for group_id, group_key in enumerate(GROUP[item_key]["KEYS"]): 119 self.identities.append((group_key, group_id))
120
121 - def add_group(self, group):
122 """Add a group of elements or nodes""" 123 self.list.append(group) 124 self.list_keys.append(group.key)
125
126 - def __getitem__(self, key):
127 """Return a group from the MBDyn key (STRUCTURAL, FORCE...)""" 128 return self.list[self.list_keys.index(key)]
129
130 - def try_to_convert(self):
131 """Try to call the C{convert} method for each group. 132 Due to the numerous groups, the difficulty is to make 133 a package that work without having an interface for everyone. 134 That's why this method tests if the SWIG module has 135 a function to convert the object from the 136 L{mbdyn.bindings.basic_objects} to a general class of the group. 137 If the SWIG module has a conversion to offer, the C{convert} method 138 of the group will be called by supplying the function reference. 139 The conversion details can then be found in L{mbdyn.bindings.groups}. 140 """ 141 for group in self.list: 142 convert_to_class_name = group.convert_method_name 143 try: 144 convert_to_class = getattr(swigModule, convert_to_class_name) 145 group.convert(convert_to_class) 146 except AttributeError: 147 pass
148
149 - def set_group_as_attributes(self):
150 """The present groups are set as attribute names. 151 The names used are defined in the 152 C{GROUP_TABLE} of L{mbdyn.bindings.groups}.""" 153 for group in self.list: 154 setattr(self, group.attr_name, group)
155
156 - def __repr__(self):
157 """Return all the present groups""" 158 string = "[" 159 string += ",\n".join(group.attr_name for group in self.list) 160 string += "]" 161 return string
162 163
164 -class WraptMBDyn:
165 """The MBDyn service for Python. C{WraptMBDyn} can run only 166 if it receives a MBDyn input file. In case the input file 167 is not in the current directory, the path has to be given by 168 C{working_dir}. Like in MBDyn, the output file can be omitted. 169 By defaults the results file are not written, so the simulation 170 is going faster and this is the user duty to choose what he wants 171 to save. The L{mbdyn} module offers an interface for those 172 functionalities. 173 This behavior can be turned off by L{write_mbdyn_files}. 174 175 C{WraptMBDyn} performs 4 calls that are empty, because they do not deal 176 with its work, but they are use for an interface. They are: 177 178 - L{do_as_init} 179 - L{update} 180 - L{save} 181 - L{final_action} 182 183 The first user of those calls is L{Simulation<mbdyn.main.Simulation>} but 184 the user could develop what he wants. 185 186 The MBDyn object tree is available once the L{init} method has been called. 187 Then the integration can be completely performed by L{run_full} or until 188 a particular time L{run_until}. 189 An demonstration of use for the file 'I{cantilever1}' from the 190 U{MBDyn tutorial 191 <http://www.aero.polimi.it/~mbdyn/documentation/tutorials/node10.html>} 192 saved in the directory 'I{input_file}':: 193 194 >>> from mbdyn import * 195 >>> wm = WraptMBDyn("cantilever1", "input_file") 196 >>> wm.init() 197 .. 198 >>> wm 199 [elts, 200 nodes] 201 >>> wm.write_mbdyn_files(True) 202 >>> wm.run_until(3.) 203 MBDyn progress: 49% [########################### ] 204 >>> wm.solver.current_time 205 2.999000072479248 206 >>> wm.run_full() 207 MBDyn progress: 100% [########################################################] 208 End of simulation at time 6.0 after 6000 steps 209 total iterations: 6075 210 total Jacobians: 6075 211 total error: 0.002095883945 212 CPU time consumed: 6.98 seconds 213 214 Nevertheless L{run_until} is interesting only for an operation not run 215 at each time step, else a reference to L{update} should be supplied. 216 """ 217
218 - def __init__(self, input_file=None, working_dir=".", 219 output_file=None, write_output_files=False):
220 self.input_file = input_file 221 self.working_dir = working_dir 222 self.output_file = output_file 223 self.write_output_files = write_output_files 224 225 self.swig_mbdyn = None 226 self.groups = {} 227 self.elts = None 228 self.nodes = None 229 self.frames = None 230 self.representation = "[]" 231 232 self.has_run_init = False 233 self.solver = None 234 self.current_dir = os.getcwd() 235 236 self._has_run_first_step = False 237 self.solver_status = "go" 238 self.pbar = None 239 self.cpu_tstart = None 240 self.continue_iter = True 241 242 if input_file != None: 243 self.create_swig_mbdyn(input_file, working_dir, output_file)
244
245 - def _get_output_file(self):
246 """Return the output file name for MBDyn""" 247 return self.input_file + "_result"
248
249 - def create_swig_mbdyn(self, input_file, working_dir=".", output_file=None):
250 """Create MBDyn in its SWIG version""" 251 self.input_file = input_file 252 self.working_dir = working_dir 253 if output_file == None: 254 self.output_file = self._get_output_file() 255 else: 256 self.output_file = output_file 257 self.swig_mbdyn = swigModule.WraptMBDyn(self.input_file, 258 self.output_file)
259
260 - def write_mbdyn_files(self, boolean):
261 """Tell if the MBDyn result files, in ASCII, need to be written 262 or not, by default there are not. The simulation will run faster 263 but the saving process will have to be handled by the user 264 script or L{mbdyn}.""" 265 self.write_output_files = boolean
266
267 - def init(self):
268 """Start the communication with MBDyn objects. 269 First the MBDyn solver is created, which corresponds 270 to the C{main} function of 'I{mbdyn.cc}'. The important 271 C{solver} attributes for the interface are: 272 273 - current_time 274 - time_step 275 - final_time 276 277 C{current_time} and C{time_step} are updated at each step. 278 The solver will run the input file parsing and thus fill 279 the C{DataManager}. The tree for navigating the MBDyn 280 objects is then created in L{_get_items}. Finally, 281 the customizable L{do_as_init} method is called 282 during that step. 283 """ 284 os.chdir(self.working_dir) 285 if not self.has_run_init: 286 self.swig_mbdyn.create_solver() 287 self.solver = self.swig_mbdyn.solver 288 self.solver.do_output = self.write_output_files 289 self.solver.run_init() 290 self._get_items() 291 self.do_as_init() 292 self.has_run_init = True 293 else: 294 print "'init()' has already been called, no effect" 295 os.chdir(self.current_dir)
296
297 - def _get_items(self):
298 """Scan the reference frames, nodes and elements from 299 the C{DataManager} and create a tree of objects. 300 The process is organised as follow: 301 302 1. Get the all groups for nodes and elements, 303 this step is done by L{_get_item_groups}. 304 305 2. Fill those groups with a general class for nodes 306 and elements, defined in L{mbdyn.bindings.basic_objects}. 307 L{_fill_item_groups} do that part. 308 309 3. Every group try to convert the top reference into a more 310 a more specific one. This part is contained in 311 L{try_to_convert<Groups.try_to_convert>} of L{Groups}. 312 313 4. Nodes and elements can now be accessed on the instance by 314 L{_set_group_as_attributes} 315 316 5. The simulation may have reference frames, it will be tested 317 in L{_try_get_frames} 318 """ 319 self._get_item_groups() 320 self._fill_item_groups() 321 for item_key in ["NODE", "ELEM"]: 322 self.groups[item_key].try_to_convert() 323 self._set_group_as_attributes() 324 self._try_get_frames()
325
326 - def _get_item_groups(self):
327 """Create all the groups existing in MBDyn. 328 For each possible group of nodes or elements, ask to the 329 C{DataManager} the number of items. If there is at least one, 330 the corresponding group defined in L{mbdyn.bindings.groups} 331 is created. 332 """ 333 for item_key in ["NODE", "ELEM"]: 334 self.groups[item_key] = Groups(item_key) 335 336 get_number = { 337 "NODE" : self.solver.pDM.get_number_nodes, 338 "ELEM" : self.solver.pDM.get_number_elements 339 } 340 for item_key in ["NODE", "ELEM"]: 341 for group_key, group_id in self.groups[item_key].identities: 342 nb_item = get_number[item_key](group_id) 343 if nb_item != 0: 344 ItemGroupClass = GROUP[item_key]["CLASS"][group_id] 345 self.groups[item_key].add_group(ItemGroupClass(group_key, 346 group_id, 347 nb_item))
348
349 - def _fill_item_groups(self):
350 """Fill the created groups of MBDyn object references. 351 The two classes used are C{Node} and C{Element} from 352 L{mbdyn.bindings.basic_objects}""" 353 get_item = { 354 "NODE" : self.solver.pDM.get_node, 355 "ELEM" : self.solver.pDM.get_element 356 } 357 for item_key in ["NODE", "ELEM"]: 358 for group in self.groups[item_key].list: 359 for item_id in range(group.number): 360 pitem = get_item[item_key](group.idx, item_id) 361 item = BASIC_CLASS[item_key](pitem) 362 group.add_item(item)
363
364 - def _set_group_as_attributes(self):
365 """Create the interface tree for nodes and elements. 366 Each created group of nodes or elements gets its attribute 367 name set on its corresponding L{Groups}, C{nodes} or C{elts}. 368 Those last ones are as well set on the C{WraptMBDyn}.""" 369 for item_group in self.groups.values(): 370 item_group.set_group_as_attributes() 371 self.nodes = self.groups["NODE"] 372 self.elts = self.groups["ELEM"] 373 self.representation = "[elts,\nnodes]"
374
375 - def _try_get_frames(self):
376 """Create the reference frames tree in case the input file 377 has some. This method manipulates the C{MBDynParser} 378 for getting the L{ReferenceFrame<mbdyn.bindings.frames>} 379 references and fill the L{FramesList<mbdyn.bindings.frames>} 380 """ 381 mbdyn_parser = self.swig_mbdyn.HP 382 nb_frames = mbdyn_parser.get_reference_frame_nb() 383 if nb_frames != 0: 384 self.frames = FramesList() 385 for indice in range(nb_frames): 386 pframe = mbdyn_parser.get_reference_frame(indice) 387 self.frames.add(ReferenceFrame(pframe)) 388 self.representation = "[elts,\nnodes,\nframes]"
389
390 - def __repr__(self):
391 """Return the representation of the object tree""" 392 return self.representation
393
394 - def do_as_init(self):
395 """Not used on C{WraptMBDyn} called during the L{init} method.""" 396 pass
397
398 - def update(self):
399 """Called before starting a new MBDyn time step. This method 400 is empty for C{WraptMBDyn} but is of use in case the user wants 401 to do change the MBDyn objects at run time.""" 402 pass
403
404 - def save(self):
405 """This is not the job of C{WraptMBDyn} but called before to start 406 the integration and at the end of each time step""" 407 pass
408 409
410 - def final_action(self):
411 """The final call at the end of the integration. For C{WraptMBDyn}, 412 nothing is done in that step but it may be of use for the class 413 manipulating the service.""" 414 pass
415
416 - def _check_status(self):
417 """Check if the first step has been run, else 418 restart the progress bar""" 419 if not self._has_run_first_step: 420 self._run_first_step() 421 else: 422 self.pbar.restart(self.solver.current_time)
423
424 - def run_full(self):
425 """Run the simulation until the end""" 426 self._check_status() 427 while self.continue_iter: 428 self.run_one_step()
429
430 - def run_until(self, t_stop):
431 """Run until the stop time. Note that this method 432 has to be used with care. If the stop time is over the final time, 433 the integration will anyway continue.""" 434 self._check_status() 435 436 # 't_future' is always one step forward 437 # for stopping before t_stop 438 t_future = self.solver.current_time + \ 439 self.solver.time_step 440 while t_future < t_stop: 441 self.run_one_step() 442 t_future += self.solver.time_step 443 444 self.pbar.stand_by(self.solver.current_time)
445
446 - def _run_first_step(self):
447 """Run the MBDyn first step. The reason of that method is in 448 the MBDyn source code, the first step is run separately.""" 449 if not self.has_run_init: 450 self.init() 451 # TODO: the saving process should be done in the 'init' method, 452 # but for this the save callback should be set before calling the 453 # init method of WraptMBDyn 454 self.save() 455 456 self.update() 457 self.solver.run_first_step() 458 self.save() 459 460 self.pbar = ProgressBar(self.solver.final_time + \ 461 self.solver.time_step) 462 self.cpu_tstart = time.clock() 463 self.pbar.update(self.solver.current_time) 464 self._has_run_first_step = True
465
466 - def run_one_step(self):
467 """Run one MBDyn integration step. The L{update} method 468 is called before a new time step. The L{save} method is called 469 after a time step. The L{final_action} is called when the 470 integration is finished.""" 471 self.pbar.update(self.solver.current_time) 472 self.update() 473 solver_status = self.solver.run_one_step() 474 if solver_status == "stop": 475 self.pbar.destroy() 476 self._output_final_message() 477 self.final_action() 478 self.continue_iter = False 479 else: 480 self.save()
481
482 - def _output_final_message(self):
483 """Output the MBDyn final message. This step could not be any more 484 done in C++ because the progress bar needed to be stopped before 485 writing a message. 486 """ 487 cpu_tstop = time.clock() 488 cpu_time = cpu_tstop - self.cpu_tstart 489 print "End of simulation at time ", self.solver.current_time, \ 490 " after ", self.solver.step_nb, " steps" 491 print "total iterations: ", self.solver.total_iter_nb 492 print "total Jacobians: ", self.solver.jacobians_nb 493 print "total error: ", self.solver.total_error 494 print "CPU time consumed: ", cpu_time, " seconds"
495