mbs_invdyn.py 25.2 KB
Newer Older
1
2
3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
4
Module to handle inverse dynamic analysis on Multibody systems.
5

6
7
8
9
10
Summary
-------
Define the class MbsInvdyn based on the MbsInvdyn structure of MBsysC. This
class has the functions required to manipulate the direct dynamic module. This
include setting the options, running an(or multiple) analysis and freeing
11
12
the memory.
"""
13
14
# Author: Robotran Team
# (c) Universite catholique de Louvain, 2019
15
16
17
18
19
20

import os
import ctypes

import numpy as np

21
# importing MbsysPy functions
22
from ..mbs_utilities import str_from_c_pointer
23
24
from ..mbs_utilities import bytes_to_str
from ..mbs_utilities import str_to_bytes
Louis Beauloye's avatar
Louis Beauloye committed
25
from ..mbs_utilities import mbs_msg
26

27
# importing MbsysPy classes
28
from .mbs_results import MbsResult
29
30

# importing libraries
31
from .._mbsysc_loader.loadlibs import libmodules
32
from .._mbsysc_loader.loadlibs import libutilities
33

34
# =============================================================================
35
# Global parameter of the current module
36
# =============================================================================
37
__DEBUG__ = False
38
39
40
__MODULE_DIR__ = os.path.dirname(os.path.abspath(__file__))


41
# =============================================================================
42
# Defining Python MbsInvdyn class
43
# =============================================================================
44
45

class MbsInvdyn(object):
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    """
    Class of the invdyn computation 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.
    """

62
    def __init__(self, mbs, user_path=None, symbolic_path=None):
63
        """
64
        Create an instance of the MbsInvdyn class for the provided MbsData instance.
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

        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
        -------
81
82
        MbsInvdyn: self
            A MbsInvdyn instance.
83
        """
84
        self.module_name = type(self).__name__
85
86

        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
87
            mbs_msg("DEBUG>>  Creating " + self.module_name + " struct for " + mbs.mbs_name + "' MBS.")
88

89
        self.mbs_invdyn_ptr = libmodules.mbs_new_invdyn(mbs.mbs_data_ptr)
90
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
91
            mbs_msg("DEBUG>>  " + self.module_name + " structure loaded")
92

93
        self.mbs = mbs
94

95
        self.user_path = self.mbs.user_path
96
97
        if user_path is not None:
            project_path = bytes_to_str(self.mbs.project_path)
98
99
100
            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
101
102
103
                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 + '".')
104
105
106
107
            else:
                self.user_path = user_path
        # Path to user function used by partitionning modue
        self.symbolic_path = self.mbs.symbolic_path
108
109
        if symbolic_path is not None:
            project_path = bytes_to_str(self.mbs.project_path)
110
111
112
            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
113
114
115
                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 + '".')
116
117
            else:
                self.symbolic_path = symbolic_path
118

Louis Beauloye's avatar
Louis Beauloye committed
119
        self.user_fun_list = ['cons_hJ', 'cons_jdqd', 'derivative', 'DrivenJoints',
120
121
                              'ExtForces', 'JointForces', 'LinkForces', 'Link3DForces',
                              'invdyn_init', 'invdyn_loop', 'invdyn_finish'
Louis Beauloye's avatar
Louis Beauloye committed
122
123
124
125
126
                              ]
        self.symb_fun_list = ['accelred', 'cons_hJ', 'cons_jdqd', 'invdyna',
                              'dirdyna', 'extforces', 'gensensor',
                              'link', 'link3D', 'sensor'
                              ]
127

128
129
130
        # Storing Results
        self.results = MbsResult(self.mbs)
        self.store_results = True
131

132
        # Exposing some memory
133
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
134
            mbs_msg("DEBUG>>  Exposing " + self.module_name + " fields")
135

136
137
        # Constraints
        self._h = self._Jac = self._jdqd = None
138
139
140
141
        if self.mbs.Ncons > 0:
            self._h = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.h, (self.mbs.Ncons + 1, ))
            self._Jac = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.Jac[0], (self.mbs.Ncons + 1, self.mbs.njoint + 1))
            self._jdqd = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.jdqd, (self.mbs.Ncons + 1, ))
142
        self._huserc = self._Juserc = self._jdqduserc = None
143
144
145
146
        if self.mbs.Nuserc > 0:
            self._huserc = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.huserc, (self.mbs.Nuserc + 1, ))
            self._Juserc = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.Juserc[0], (self.mbs.Nuserc + 1, self.mbs.njoint + 1))
            self._jdqduserc = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.jdqduserc, (self.mbs.Nuserc + 1, ))
147
        # Fields from equation of motion
148
149
150
151
152
        self._M = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.M[0], (self.mbs.njoint + 1, self.mbs.njoint + 1))
        self._c = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.c, (self.mbs.njoint + 1, ))
        self._F = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.F, (self.mbs.njoint + 1, ))
        self._phi = np.ctypeslib.as_array(self.mbs_invdyn_ptr.contents.mbs_aux.contents.phi, (self.mbs.njoint + 1, ))

153
    def __str__(self):
154
155
        """Return str(self)."""
        if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
156
            mbs_msg("DEBUG>>  start of __str")
157
158
159
160
161

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

162
    def __del__(self):
163
        """Delete the object by freeing the C-related memory."""
164
        libmodules.mbs_delete_invdyn(self.mbs_invdyn_ptr, self.mbs.mbs_data_ptr)
165
166
        if __DEBUG__:
            mbs_msg("DEBUG>>  " + self.module_name + " pointer deleted")
167

168
    def run(self, **kwargs):
169
        """
170
171
172
173
        Run an inverse dynamics analysis.

        Options can be setted with set_options.
        Options can be retrieved with get_options
174

175
        Results are stored in the field results(if store_results == True)
176

177
178
179
180
        Returns
        -------
        self.results: MbsResult
            The MbsResults containing the results of the analysis.
181
        """
182
        # Assign required user functions
183
        if self.mbs.opt_load_c < 2:
184
            if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
185
                mbs_msg("DEBUG>>  Loading user functions")
186

187
            self.mbs.__load_user_fct__(__MODULE_DIR__, self.user_fun_list, self.user_path)
188
            self.mbs.__assign_user_fct__(self.user_fun_list, self)
189

190
        # Assign required symbolic functions
191
        if self.mbs.opt_load_c < 1:
192
193
            # Loading symbolic function
            if __DEBUG__:
Louis Beauloye's avatar
Louis Beauloye committed
194
                mbs_msg("DEBUG>>  Loading symbolic functions")
195
196

            self.mbs.__load_symbolic_fct__(__MODULE_DIR__, self.symb_fun_list, self.symbolic_path)
197
            self.mbs.__assign_symb_fct__(self.symb_fun_list, self)
198

199
200
        self.set_options(**kwargs)

201
        error2 = 0  # Default value
202
        if not self.store_results:
203
            error = libmodules.mbs_run_invdyn(self.mbs_invdyn_ptr, self.mbs.mbs_data_ptr)
204

205
        else:
206
207
            # save2file forced to 1 because if buffers don't have the complete
            # results, results are loaded from files.
208
            self.set_options(save2file=1)
209
210
211
            error = libmodules.mbs_invdyn_init(self.mbs_invdyn_ptr, self.mbs.mbs_data_ptr)
            if (error >= 0):
                error = libmodules.mbs_invdyn_loop(self.mbs_invdyn_ptr, self.mbs.mbs_data_ptr)
212

213
            results_loaded = False
214
            # Results(buffer) memory is kept BUT FILES WILL BE WRITTEN LATER
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
            if error >= 0:
                if self.get_options("save2file"):
                    results_loaded = self.results.load_results_from_buffer(self.mbs_invdyn_ptr,
                                                                           self.get_options("t0"),
                                                                           self.get_options("resfilename"))

                    # If failed to load from buffer, save the user output (and vector) name
                    if not results_loaded:
                        results_filename = bytes_to_str(ctypes.string_at(self.mbs_dirdyn_ptr.contents.buffers[0].contents.filename))
                        # Check if user vector output have been defined
                        user_output_vector_filenames = []
                        nb_user_output_vector = libutilities.get_output_vector_nb()
                        first_buffer_id = self.buffer_nb - nb_user_output_vector
                        for i in range(nb_user_output_vector):
                            vector_name = bytes_to_str(self.mbs_dirdyn_ptr.contents.buffers[first_buffer_id + i].contents.filename)
                            user_output_vector_filenames.append(os.path.basename(vector_name))

                        # Check if user auto output have been used
                        user_output_filenames = []
                        nbOutput = self.mbs_dirdyn_ptr.contents.user_buffer.contents.nx
                        for i in range(nbOutput):
                            name = bytes_to_str(self.mbs_dirdyn_ptr.contents.user_buffer.contents.names[i])
                            user_output_filenames.append(self.get_options("resfilename") + '_' + name + '.res')

                # finish function is required to close the module and write the results to disk.
                error2 = libmodules.mbs_invdyn_finish(self.mbs_invdyn_ptr, self.mbs.mbs_data_ptr)

                if error2 >= 0 and self.get_options("save2file") and not results_loaded:
                    mbs_msg("The beginning of the integration is not available in the buffer.\n"
                            "The complete results are loaded from files.\n")
                    self.results.load_results_from_file(results_filename,
                                                        user_output=user_output_filenames,
                                                        user_vector=user_output_vector_filenames)
248

249
        # Unassign user functions
250
        if self.mbs.opt_load_c < 2:
251
            self.mbs.__unassign_user_fct__(self.user_fun_list)
252

253
        # Unassing required symbolic functions
254
        if self.mbs.opt_load_c < 1:
255
            self.mbs.__unassign_symb_fct__(self.symb_fun_list)
256

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
        if error < 0 or error2 < 0:
            mbs_msg("\n--------------------------------------------------------------------------------\n"
                    "READ CAREFULLY !!!\n"
                    "--------------------------------------------------------------------------------\n\n"
                    "An error occurs during inverse dynamic module.\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"
                    "    - Incompatible module options;\n"
                    "    - Invalid path to trajectory files;\n"
                    "    - Invalid trajectory files contents;\n"
                    "    - The motion reach an singular configuration;\n"
                    "\n"
                    "If the simulation runs a little you should:\n"
                    "    - Open the results files and check the values obtained before failure.\n"
                    "\n--------------------------------------------------\n"
                    "\n--------------------------------------------------\n")
            raise RuntimeError("MbsInvdyn.run() failed, read previous messages.")

277
        return self.results
278

279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    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):
297
        """
298
299
300
        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().
301

302
303
        Parameters
        ----------
304
305
306
307
308
309
        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)
310

311
312
313
314
    def set_options(self, **kwargs):
        """

        Set the specified options for Invdyn module.
315

316
317
        Parameters
        ----------
318
        motion: int
319
            Determines how the evolution of q, qd qdd are provided.
320
321
322
323
324
            oneshot(default): Uses q, qd and qdd at the current time and configuration of the MBS.
            closeloop: Only for inverse kinematics analysis. Same as oneshot but save the iteration of the close loop process(NR).
            trajectory: Compute the evolution of between t0 and tf.
        trajectoryqname: char
            Give the path and filename(with extention) to the file containing the input trajectory in position of the joints.
325
326
327
328
            Only required for motion == 'trajectory' if some joints are independant.
            By default, pointer to NULL.
            Requirement on the file:
            First column is the time, other are the joints coordinates.
329
            Either provide only the independant joint(by increasing index), user_drivenjoint will be called.
330
            Either provide all the joint, user_drivenjoint will be neglected.
331
        trajectoryqdname: char
332
            Give the path and filename(with extention) to the file containing the input trajectory in velocity of the joints.
333
334
335
336
            Only required for motion == 'trajectory' if some joints are independant.
            By default, pointer to NULL.
            Requirement on the file:
            First column is the time, other are the joints velocities.
337
            Either provide only the independant joint(by increasing index), user_drivenjoint will be called.
338
            Either provide all the joint, user_drivenjoint will be neglected.
339
        trajectoryqddname: char
340
            Give the path and filename(with extention) to the file containing the input trajectory in acceleration of the joints.
341
342
343
344
            Only required for motion == 'trajectory' if some joints are independant.
            By default, pointer to NULL.
            Requirement on the file:
            First column is the time, other are the joints accelerations.
345
            Either provide only the independant joint(by increasing index), user_drivenjoint will be called.
346
            Either provide all the joint, user_drivenjoint will be neglected.
347
        t0: float
348
            initial time of the simulation, default = 0.0
349
        tf: float
350
            final time of the simulation, default = 5.0
351
        dt: float
352
            Time step, default = 0.001
353
354
            For motion == 'trajectory'(for inverse kinematic analysis, there must be at least one independent joint):
            Set to 0.0 to use the time vector of the coordinate input file(MbsSolvekinOptions::t0 and MbsSolvekinOptions::tf are ignored).
355
            Specify a value to use specific time vecor.
356
357
        save2file: int
            Determine whether results are written to files on disk(in MbsSolvekinOptions::respath folder):
358
            1: results are saved
359
            0: results are not saved
360
            default = 1
361
        resfilename: str
362
            The keyword used for determining the name of result files
363
        respath: str
364
365
            Path in which result file are saved.
            Default: the resultsR folder of the project
366
        animpath: str
367
368
            Path in which anim file is saved.
            Default: the animationR folder of the project
369
        save_anim: int
370
            1 to save the anim file
371
            0 otherwise(only valid if 'save2file' is set to 1)
372
            default = 1
373
        framerate: int
374
375
            number of frame per second for the .anim file
            default = 1000
376
        saveperiod: int
377
            The number of time steps between two buffer records
378
379
            default: 1(every time step are recorded)
        max_save_user: int
380
381
            The maximal number of user variables saved
            default: 12
382
        buffersize: int
383
384
385
386
            The number of time step that can be recorded in the buffer.
            Results are written to disk when the buffer is full.
            default: -1
            compute the buffer size for saving results only once at the end according to dt0, t0 and tf.
387
        verbose: int
388
389
            Gives informations during the process.
            default: 1, to disable set it to 0.
390
        store_results: boolean
391
392
            1 to save a copy of the results from the buffers
            default = 1
393

394
        """
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
        def motion(value):
            if(type(value) is int):
                if (value >= 0 and value <= 2):
                    return value
                else:
                    raise ValueError
            elif type(value) is str:
                if value == "oneshot":
                    return 0
                elif value == "closeloop":
                    return 1
                elif value == "trajectory":
                    return 2
                else:
                    raise ValueError
            else:
                raise TypeError

        options = {'motion': {'convert': motion, 'c_name': 'motion'},
                   'trajectoryqname': {'convert': str_to_bytes, 'c_name': 'trajectoryqname'},
                   'trajectoryqdname': {'convert': str_to_bytes, 'c_name': 'trajectoryqdname'},
                   'trajectoryqddname': {'convert': str_to_bytes, 'c_name': 'trajectoryqddname'},
                   't0': {'convert': float, 'c_name': 't0'},
                   'tf': {'convert': float, 'c_name': 'tf'},
                   'dt': {'convert': float, 'c_name': 'dt'},
                   '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'},
                   'framerate': {'convert': int, 'c_name': 'framerate'},
                   'saveperiod': {'convert': int, 'c_name': 'saveperiod'},
                   'max_save_user': {'convert': int, 'c_name': 'max_save_user'},
                   'buffersize': {'convert': int, 'c_name': 'buffersize'},
                   'verbose': {'convert': int, 'c_name': 'verbose'},
                   'store_results': {'convert': int, 'c_name': 'store_results'},
                   }
432

433
        for key, value in kwargs.items():
434
435
436
437
438
439
440
441
442
443
            if key not in options:
                raise TypeError("{:} is an invalid option name.".format(key))
            try:
                c_name = options[key]['c_name']
                setattr(self.mbs_invdyn_ptr.contents.options.contents, c_name, options[key]['convert'](value))
            except ValueError as err:
                # if wrong value in motion
                if key == 'motion':
                    raise ValueError('>>INVDYN>>  {:} is not a valid motion'.format(value)).with_traceback(err.__traceback__) from None
                # if error during the cast
444
                else:
445
446
447
448
449
450
451
452
                    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

            except TypeError as err:
                # if wrong type in motion
Louis Beauloye's avatar
Louis Beauloye committed
453
454
                if key == 'motion':
                    raise TypeError("{:} is str or int, can not be casted from {:}.".format(key, type(value))).with_traceback(err.__traceback__) from None
455

456
457
    def get_options(self, *args):
        """
458
        Get the specified options for Invdyn module.
459

460
461
        Parameters
        ----------
462
        The different options specifed in the documentation of 'set_options()'
463

464
465
        Returns
        -------
466
        The value of the different options specifed in the documentation of 'set_options()'
467
468
469
470
        """
        options = []
        for key in args:
            if key == "motion":
471
472
473
474
475
476
                if self.mbs_invdyn_ptr.contents.options.contents.motion == 0:
                    options.append("oneshot")
                elif self.mbs_invdyn_ptr.contents.options.contents.motion == 1:
                    options.append("closeloop")
                elif self.mbs_invdyn_ptr.contents.options.contents.motion == 2:
                    options.append("trajectory")
477
            elif key == "trajectoryqname":
478
479
480
                address = self.mbs_invdyn_ptr.contents.options.contents.trajectoryqname
                defaut = None
                options.append(str_from_c_pointer(address, defaut))
481
            elif key == "trajectoryqdname":
482
483
484
                address = self.mbs_invdyn_ptr.contents.options.contents.trajectoryqdname
                defaut = None
                options.append(str_from_c_pointer(address, defaut))
485
            elif key == "trajectoryqddname":
486
487
488
                address = self.mbs_invdyn_ptr.contents.options.contents.trajectoryqddname
                defaut = None
                options.append(str_from_c_pointer(address, defaut))
489
490
491
492
493
494
495
496
497
            elif key == "t0":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.t0)
            elif key == "tf":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.tf)
            elif key == "dt":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.dt)
            elif key == "save2file":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.save2file)
            elif key == "resfilename":
498
499
500
                address = self.mbs_invdyn_ptr.contents.options.contents.resfilename
                defaut = 'invdyn'
                options.append(str_from_c_pointer(address, defaut))
501
            elif key == "respath":
502
503
504
                address = self.mbs_invdyn_ptr.contents.options.contents.respath
                defaut = os.path.abspath(os.path.join(self.mbs.project_path, 'resultsR'))
                options.append(str_from_c_pointer(address, defaut))
505
            elif key == "animpath":
506
507
508
                address = self.mbs_invdyn_ptr.contents.options.contents.animpath
                defaut = os.path.abspath(os.path.join(self.mbs.project_path, 'animationR'))
                options.append(str_from_c_pointer(address, defaut))
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
            elif key == "save_anim":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.save_anim)
            elif key == "framerate":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.framerate)
            elif key == "saveperiod":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.saveperiod)
            elif key == "max_save_user":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.max_save_user)
            elif key == "buffersize":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.buffersize)
            elif key == "verbose":
                options.append(self.mbs_invdyn_ptr.contents.options.contents.verbose)
            elif key == "store_results":
                options.append(self.store_results)
            else:
Louis Beauloye's avatar
Louis Beauloye committed
524
                mbs_msg(">>" + self.module_name + ">> The option " + key + " is not defined in this module")
525

526
527
528
529
        if len(options) == 0:
            return
        if len(options) == 1:
            return options[0]
530

531
        return tuple(options)