mbs_modal.py 22.3 KB
Newer Older
1
2
# -*- coding: utf-8 -*-
"""
3
Module to handle modal analysis on Multibody systems.
4

5
6
7
8
9
10
Summary
-------
Define the class MbsModal based on the MbsModal structure of MBsysC. This
class has the functions required to manipulate the direct modal analysis module.
This includes setting the options, running an(or multiple) analysis and freeing
the memory.
11
12
13
"""

import os
14
15
import ctypes
import numpy as np
16
17
18
19

# importing MbsysPy functions
from ..mbs_utilities import bytes_to_str
from ..mbs_utilities import str_to_bytes
Louis Beauloye's avatar
Louis Beauloye committed
20
from ..mbs_utilities import mbs_msg
21

22
# importing libraries
23
from .._mbsysc_loader.loadlibs import libmodules
24

25
26

# =============================================================================
27
# Global parameter of the current module
28
# =============================================================================
29
__DEBUG__ = False
30
31
32
__MODULE_DIR__ = os.path.dirname(os.path.abspath(__file__))


33
# =============================================================================
34
# Defining Python MbsModal class
35
# =============================================================================
36
37

class MbsModal(object):
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    """
    Class of the modal analysis module.

    Attributes
    ----------
    mbs: MbsData
        Instance of MbsData related to the analysis.
    results: MbsResult
        Instance of MbsResult containing the results of the direct dynamics analysis.
    symbolic_path: str
        Path to the folder containing the symbolic functions(python modules)
        to be loaded.
    user_path: str
        Path to the folder containing the user functions(python modules) to be loaded.
    """

54
    def __init__(self, mbs, user_path=None, symbolic_path=None):
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
        """
        Create an instance of the MbsModal class for the provided MbsData instance.

        Parameters
        ----------
        mbs: MbsData
            Instance of MbsData related to the analysis.
        user_path: str or None, optionnal
            The path to the folder containing the user functions. If not provided
           ('None') the path is retrieved from the MbsData instance 'mbs'.
            default is None
        symbolic_path: str or None, optionnal
            The path to the folder containing the symbolic functions. If not
            provided('None') the path is retrieved from the MbsData instance 'mbs'.
            default is None

        Returns
        -------
        MbsModal: self
            A MbsModal instance.
        """
76
        self.module_name = type(self).__name__
77
78

        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
79
            mbs_msg("DEBUG>>  Creating MbsModal struct for " + mbs.mbs_name + "' MBS.")
80

81
        self.mbs_modal_ptr = libmodules.mbs_new_modal(mbs.mbs_data_ptr)
82
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
83
            mbs_msg("DEBUG>>  MbsModal structure loaded")
84

85
        self.mbs = mbs
86

87
        self.user_path = self.mbs.user_path
88
89
        if user_path is not None:
            project_path = bytes_to_str(self.mbs.project_path)
90
91
92
            user_path = os.path.join(project_path, user_path)
            # Error handeling
            if not os.path.isdir(user_path):
Louis Beauloye's avatar
Louis Beauloye committed
93
94
95
                mbs_msg('The user function directory for direct dynamic module does not exist: "' + user_path + '"')
                mbs_msg('The current root folder is: "' + os.getcwd() + '"')
                mbs_msg('The following directory is used instead: "' + self.user_path + '".')
96
97
98
99
            else:
                self.user_path = user_path
        # Path to user function used by partitionning modue
        self.symbolic_path = self.mbs.symbolic_path
100
101
        if symbolic_path is not None:
            project_path = bytes_to_str(self.mbs.project_path)
102
103
104
            symbolic_path = os.path.join(project_path, symbolic_path)
            # Error handeling
            if not os.path.isdir(symbolic_path):
Louis Beauloye's avatar
Louis Beauloye committed
105
106
107
                mbs_msg('Thesymbolic function directory for direct dynamic module does not exist: "' + symbolic_path + '"')
                mbs_msg('The current root folder is: "' + os.getcwd() + '"')
                mbs_msg('The following directory is used instead: "' + self.symbolic_path + '".')
108
109
            else:
                self.symbolic_path = symbolic_path
110

Louis Beauloye's avatar
Louis Beauloye committed
111
112
113
114
115
116
117
        self.user_fun_list = ['cons_hJ', 'cons_jdqd', 'derivative', 'DrivenJoints',
                              'ExtForces', 'JointForces', 'LinkForces', 'Link3DForces'
                              ]
        self.symb_fun_list = ['accelred', 'cons_hJ', 'cons_jdqd', 'invdyna',
                              'dirdyna', 'extforces', 'gensensor',
                              'link', 'link3D', 'sensor'
                              ]
118

119
        # Storing Results
Louis Beauloye's avatar
Louis Beauloye committed
120
        self.results = MbsResultModal()
121
        self.store_results = False
122

123
        # Exposing some memory
124
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
125
            mbs_msg("DEBUG>>  Exposing " + self.module_name + " fields")
126

127
128
        # Constraints
        self._h = self._Jac = self._jdqd = None
129
130
131
132
        if self.mbs.Ncons > 0:
            self._h = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.h, (self.mbs.Ncons + 1, ))
            self._Jac = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.Jac[0], (self.mbs.Ncons + 1, self.mbs.njoint + 1))
            self._jdqd = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.jdqd, (self.mbs.Ncons + 1, ))
133
        self._huserc = self._Juserc = self._jdqduserc = None
134
135
136
137
        if self.mbs.Nuserc > 0:
            self._huserc = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.huserc, (self.mbs.Nuserc + 1, ))
            self._Juserc = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.Juserc[0], (self.mbs.Nuserc + 1, self.mbs.njoint + 1))
            self._jdqduserc = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.jdqduserc, (self.mbs.Nuserc + 1, ))
138
        # Fields from equation of motion
139
140
141
142
143
        self._M = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.M[0], (self.mbs.njoint + 1, self.mbs.njoint + 1))
        self._c = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.c, (self.mbs.njoint + 1, ))
        self._F = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.F, (self.mbs.njoint + 1, ))
        self._phi = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.aux.contents.phi, (self.mbs.njoint + 1, ))

144
    def __str__(self):
145
146
        """Return str(self)."""
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
147
            mbs_msg("DEBUG>>  start of __str")
148
149
150
151
152
153

        msg = 'MbsModal instance of the project "' + str(self.mbs_name) +\
              '", loaded from the file "' + str(self.mbs_filename) + '".'

        return msg

154
    def __del__(self):
155
        """Delete the object by freeing the C-related memory."""
156
        libmodules.mbs_delete_modal(self.mbs_modal_ptr, self.mbs.mbs_data_ptr)
157
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
158
            mbs_msg("DEBUG>>  " + self.module_name + " pointer deleted")
159

160
    def run(self, **kwargs):
161
        """
162
163
164
165
166
167
168
169
170
171
172
173
        Run a modal analysis.

        Options can be setted with the function 'set_options()' before calling
        this function. Options can be retrieved with the function 'get_options()'.

        Results are stored in the field 'results' if the options 'store_results'
        is set to True.

        Returns
        -------
        self.results: MbsResultModal
            The instance containing the results of the analysis.
174
        """
175
        # Assign required user functions
176
        if self.mbs.opt_load_c < 2:
177
            if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
178
                mbs_msg("DEBUG>>  Loading user functions")
179

180
            self.mbs.__load_user_fct__(__MODULE_DIR__, self.user_fun_list, self.user_path)
181
            self.mbs.__assign_user_fct__(self.user_fun_list, self)
182

183
        # Assign required symbolic functions
184
        if self.mbs.opt_load_c < 1:
185
186
            # Loading symbolic function
            if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
187
                mbs_msg("DEBUG>>  Loading symbolic functions")
188
189

            self.mbs.__load_symbolic_fct__(__MODULE_DIR__, self.symb_fun_list, self.symbolic_path)
190
            self.mbs.__assign_symb_fct__(self.symb_fun_list, self)
191

192
193
        self.set_options(**kwargs)

194
        error = libmodules.mbs_run_modal(self.mbs_modal_ptr, self.mbs.mbs_data_ptr)
195

196
        # Unassign user functions
197
        if self.mbs.opt_load_c < 2:
198
            self.mbs.__unassign_user_fct__(self.user_fun_list)
199

200
        # Unassign required symbolic functions
201
        if self.mbs.opt_load_c < 1:
202
            self.mbs.__unassign_symb_fct__(self.symb_fun_list)
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
        if error < 0:
            mbs_msg("\n--------------------------------------------------------------------------------\n"  # There is 80 characters printed
                    "READ CAREFULLY !!!\n"
                    "--------------------------------------------------------------------------------\n\n"
                    "An error occurs during the modal analysis.\n"
                    "The messages above give deeper informations on what went wrong.\n"
                    "The messages below gives classic error and error backtrace.\n\n"

                    "Usual errors are:\n"
                    "    - Some options are not correct;\n"
                    "    - Some mass or Inertia component is missing, leading to a invalid mass\n"
                    "      matrix during the computation;\n"
                    "    - The system is in a singular configuration;\n"
                    "    - An error in user function (joint force, external forces) return wrong\n"
                    "       value. Excessive force can generate infinte acceleration.\n"
                    "\n"
                    "If the simulation runs a little you should:\n"
                    "    - In all cases, open the results/animation file and check the motion of the \n"
                    "      system until it fails.\n"
                    "    - In case of loop closure problem, you can alos check the dedicated animation\n"
                    "      under the name 'Failed_loop_closing_procedure_q.res' if the related option\n"
                    "      has been enable.\n"
                    "\n--------------------------------------------------------------------------------\n"
                    "\n--------------------------------------------------------------------------------\n")
            raise RuntimeError("MbsModal.run() failed, read previous messages.")

        if self.store_results:
            self.load_results()

Louis Beauloye's avatar
Louis Beauloye committed
233
        return self.results
234

Louis Beauloye's avatar
Louis Beauloye committed
235
    def load_results(self):
236
237
238
239
240
241
242
243
244
245
        """Load the results of an modal analysis."""
        self.results.eval_a = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.eval_a, (self.nx, ))
        self.results.eval_b = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.eval_b, (self.nx, ))

        self.results.evec_a = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.evec_a[0], (self.nx, self.nx))
        self.results.evec_b = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.evec_b[0], (self.nx, self.nx))

        self.results.evec_r = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.evec_r[0], (self.nx, self.nx))
        self.results.evec_phi = np.ctypeslib.as_array(self.mbs_modal_ptr.contents.evec_phi[0], (self.nx, self.nx))

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    def set_user_fct_from_file(self, function_name, user_path, user_file):
        """
        Load a user function from a file chosen by the user instead of the default one in the userfctR folder.

        The function is then unassigned by the module at the end of the run().

        Parameters
        ----------
        function_name : str
            name of the user function to replace.
        user_path : str
            path to the new user function file.
        user_file : str
            name of the new user function file.


        """
        self.mbs.__set_user_fct_from_file__(function_name, user_path, user_file)

    def set_user_fct_from_ptr(self, function_name, user_fct_ptr):
        """
        Load a user function chosen by the user instead of the default one in the userfctR folder.

        The function is then unassigned by the module at the end of the run().

        Parameters
        ----------
        function_name : str
            name of the user function to replace.
        user_fct_ptr : ptr
            new user function pointer.

        """
        self.mbs.__set_user_fct_from_ptr__(function_name, user_fct_ptr)

281
282
    def set_options(self, **kwargs):
        """
283
284
        Set the specified options for Modal module.

285
286
        Parameters
        ----------
287
288
        save2file: int
            Determine whether results are written to files on disk(in MbsModalOptions::respath folder):
289
            1: results are saved
290
            0: results are not saved
291
            default = 1
292
        resfilename: str
293
            The keyword used for determining the name of result files
294
        respath: str
295
296
            Path in which result file are saved.
            Default: the resultsR folder of the project
297
        animpath: str
298
299
            Path in which anim file is saved.
            Default: the animationR folder of the project
300
        save_anim: int
301
            1 to save the anim file
302
            0 otherwise(only valid if 'save2file' is set to 1)
303
            default = 1
304
        save_mat: int
305
            no = 0, yes = 1
306
            flag to save the linearized matrix A, Mr, Gr, Kr
307
            default = 0
308
        save_eval: int
309
310
311
            no = 0, yes = 1
            flag to save the eigen values in two separate files: "_a" for Real part and "_b" for Imag part
            default = 0
312
        save_evec: int
313
            no = 0, yes = 1
314
            flag to save the eigen vector in two separate files:
315
316
            "_a" for Real part, "_b" for Imag part, "_r" for magnitude and "_phi" for phase
            default = 0
317
        save_result: int
318
319
320
            no = 0, yes = 1
            flag to save the modal results summary
            default = 1
321
        save_anim: int
322
323
324
            no = 0, yes = 1
            flag to save and generate the animation for the modes
            default = 0
325
        mode_ampl: float
326
327
328
            amplification factor for the animation of the modes
            application dependent
            default = 0.2
329
        compute_uxd: int
330
331
332
            no = 0, yes = 1
            flag to compute the modes associated with the extra constitutive differential equations
            default = 1
333
        compute_JS: int
334
335
            no = 0, yes = 1
            flag to compute the non diagonal terms
336
            default = 0
337
            TO IMPLEMENT !
338
        norm_evec: int
339
340
341
            no = 0, yes = 1
            flag to normalize the eigenvectors
            default = 1
342
        lpk_itermax: int
343
344
            options for the lpk gradient
            default = 10
345
        lpk_relincr: float
346
347
            options for the lpk gradient
            default = 1e-2
348
        lpk_absincr: float
349
350
            options for the lpk gradient
            default = 1e-3
351
        lpk_equitol: float
352
353
            options for the lpk gradient
            default = 1e-6
354
        lpk_lintol: float
355
356
            options for the lpk gradient
            default = 1e-3
357
        anim_speed: int
358
359
            flag to decide whether the independent speeds have to be integrated while generating the animation files
            default = 0
360
        anim_dt: int
361
362
            delta t for the animation generation
            default = 1e-2
363
        anim_t: int
364
365
            time for the animation generation
            default = 2
366
        verbose: int
367
368
369
            Gives informations during the process.
            default: 1, to disable set it to 0.
        """
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
        options = {'save2file': {'convert': int, 'c_name': 'save2file'},
                   'resfilename': {'convert': str_to_bytes, 'c_name': 'resfilename'},
                   'respath': {'convert': str_to_bytes, 'c_name': 'respath'},
                   'animpath': {'convert': str_to_bytes, 'c_name': 'animpath'},
                   'save_anim': {'convert': int, 'c_name': 'save_anim'},
                   'save_mat': {'convert': int, 'c_name': 'save_mat'},
                   'save_eval': {'convert': int, 'c_name': 'save_eval'},
                   'save_evec': {'convert': int, 'c_name': 'save_evec'},
                   'save_result': {'convert': int, 'c_name': 'save_result'},
                   'mode_ampl': {'convert': float, 'c_name': 'mode_ampl'},
                   'compute_uxd': {'convert': int, 'c_name': 'compute_uxd'},
                   'compute_JS': {'convert': int, 'c_name': 'compute_JS'},
                   'norm_evec': {'convert': int, 'c_name': 'norm_evec'},
                   'lpk_itermax': {'convert': int, 'c_name': 'lpk_itermax'},
                   'lpk_relincr': {'convert': float, 'c_name': 'lpk_relincr'},
                   'lpk_absincr': {'convert': float, 'c_name': 'lpk_absincr'},
                   'lpk_equitol': {'convert': float, 'c_name': 'lpk_equitol'},
                   'lpk_lintol': {'convert': float, 'c_name': 'lpk_lintol'},
                   'anim_speed': {'convert': int, 'c_name': 'anim_speed'},
                   'anim_dt': {'convert': int, 'c_name': 'anim_dt'},
                   'anim_t': {'convert': int, 'c_name': 'anim_t'},
                   'verbose': {'convert': int, 'c_name': 'verbose'},
                   }
393
        for key, value in kwargs.items():
394
395
396
397
            if key not in options:
                raise TypeError("{:} is an invalid option name.".format(key))
            try:
                c_name = options[key]['c_name']
Louis Beauloye's avatar
Louis Beauloye committed
398
                setattr(self.mbs_modal_ptr.contents.options.contents, c_name, options[key]['convert'](value))
399
400
401
402
403
            except ValueError as err:
                raise TypeError("{:} is {:}, can not be casted from {:}.".format(key, options[key]['convert'], type(value))).with_traceback(err.__traceback__) from None
            except AttributeError as err:
                # if error during cast of strings (i.e in str_to_bytes)
                raise TypeError("{:} is str, can not be casted from {:}.".format(key, type(value))).with_traceback(err.__traceback__) from None
404

405
406
    def get_options(self, *args):
        """
407
408
        Get the specified options for Modal module.

409
410
        Parameters
        ----------
411
412
        The different options specifed in the documentation of set_options.

413
414
        Returns
        -------
415
        The value of the different options specifed in the documentation of set_options.
416
417
418
419
420
421
        """
        options = []
        for key in args:
            if key == "save2file":
                options.append(self.mbs_modal_ptr.contents.options.contents.save2file)
            elif key == "resfilename":
422
                options.append(bytes_to_str(ctypes.string_at(self.mbs_modal_ptr.contents.options.contents.resfilename)))
423
            elif key == "respath":
424
                options.append(bytes_to_str(ctypes.string_at(self.mbs_modal_ptr.contents.options.contents.respath)))
425
            elif key == "animpath":
426
                options.append(bytes_to_str(ctypes.string_at(self.mbs_modal_ptr.contents.options.contents.animpath)))
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
            elif key == "save_anim":
                options.append(self.mbs_modal_ptr.contents.options.contents.save_anim)
            elif key == "save_mat":
                options.append(self.mbs_modal_ptr.contents.options.contents.save_mat)
            elif key == "save_eval":
                options.append(self.mbs_modal_ptr.contents.options.contents.save_eval)
            elif key == "save_evec":
                options.append(self.mbs_modal_ptr.contents.options.contents.save_evec)
            elif key == "save_result":
                options.append(self.mbs_modal_ptr.contents.options.contents.save_result)
            elif key == "mode_ampl":
                options.append(self.mbs_modal_ptr.contents.options.contents.mode_ampl)
            elif key == "compute_uxd":
                options.append(self.mbs_modal_ptr.contents.options.contents.compute_uxd)
            elif key == "compute_JS":
                options.append(self.mbs_modal_ptr.contents.options.contents.compute_JS)
            elif key == "norm_evec":
                options.append(self.mbs_modal_ptr.contents.options.contents.norm_evec)
            elif key == "lpk_itermax":
                options.append(self.mbs_modal_ptr.contents.options.contents.lpk_itermax)
            elif key == "lpk_relincr":
                options.append(self.mbs_modal_ptr.contents.options.contents.lpk_relincr)
            elif key == "lpk_absincr":
                options.append(self.mbs_modal_ptr.contents.options.contents.lpk_absincr)
            elif key == "lpk_equitol":
                options.append(self.mbs_modal_ptr.contents.options.contents.lpk_equitol)
            elif key == "lpk_lintol":
                options.append(self.mbs_modal_ptr.contents.options.contents.lpk_lintol)
            elif key == "anim_speed":
                options.append(self.mbs_modal_ptr.contents.options.contents.anim_speed)
            elif key == "anim_dt":
                options.append(self.mbs_modal_ptr.contents.options.contents.anim_dt)
            elif key == "anim_t":
                options.append(self.mbs_modal_ptr.contents.options.contents.anim_t)
            elif key == "verbose":
                options.append(self.mbs_modal_ptr.contents.options.contents.verbose)
            else:
Louis Beauloye's avatar
Louis Beauloye committed
464
                mbs_msg(">>" + self.module_name + ">> The option " + key + " is not defined in this module")
465

466
467
468
469
        if len(options) == 0:
            return
        if len(options) == 1:
            return options[0]
470

Louis Beauloye's avatar
Louis Beauloye committed
471
        return tuple(options)
472
473

    # =========================================================================
Louis Beauloye's avatar
Louis Beauloye committed
474
    # Defining properties
475
476
477
478
    # =========================================================================
    # No property yo be defined.


Louis Beauloye's avatar
Louis Beauloye committed
479
class MbsResultModal(object):
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    """Class dedicated to the results of a modal analysis.

    Attributes
    ----------
    eval_a: numpy.ndarray
        Result of the Eigenvalue problem: real part of the nx eigen values.
    eval_b: numpy.ndarray
        Result of the Eigenvalue problem: imaginary part of the nx eigen values.
    evec_a: numpy.ndarray
        Result of the Eigenvalue problem: real part of the eigen vectors (each
        column is a eigenvector).
    evec_b: numpy.ndarray
        Result of the Eigenvalue problem: imaginary part of the eigen vectors (each
        column is a eigenvector).
    evec_r: numpy.ndarray
        Result of the Eigenvalue problem: norm of the eigen vectors (each column
        is a eigenvector).
    evec_phi: numpy.ndarray
        Result of the Eigenvalue problem: phase of the eigen vectors (each column
        is a eigenvector).
    """

Louis Beauloye's avatar
Louis Beauloye committed
502
503
    def __init__(self):
        self.eval_a = None
504
        self.eval_b = None
Louis Beauloye's avatar
Louis Beauloye committed
505
        self.evec_a = None
506
507
        self.evec_b = None
        self.evec_r = None
Louis Beauloye's avatar
Louis Beauloye committed
508
        self.evec_phi = None