Commit d3a2475e authored by Olivier Lantsoght's avatar Olivier Lantsoght
Browse files

[MBsysPy] New module dedicated to results.

parent 4f5f0274
# -*- coding: utf-8 -*-
"""
Module to handle results from MBS analysis.
Summary
-------
Define the class MbsResults dedicated to load the MbsBuffer C structure. Such
structures contains the result of the multibody analysis, except for modal
analysis.
The module internal class '_buffer_ids' must be updated to be coherent with the
enumeration in the file "mbs_buffer.h". Currently only the value of 'BUFFER_Q'
is used.
"""
# Author: Robotran Team
# (c) Universite catholique de Louvain, 2021
import os
import ctypes
import numpy as np
from enum import IntEnum, unique
# importing MbsysPy functions
from ..mbs_utilities import bytes_to_str
from ..mbs_utilities import mbs_warning
from ..mbs_utilities import mbs_error
from ..mbs_utilities import mbs_msg
# =============================================================================
# Global parameter of the current module
# =============================================================================
__DEBUG__ = False
__MODULE_DIR__ = os.path.dirname(os.path.abspath(__file__))
# =============================================================================
# Enumeration of the buffer ids
# =============================================================================
@unique
class _buffer_ids(IntEnum):
"""Enum of the buffer ids.
This class is used as it ensure no duplicated value. Thought it does not
prevent value jumps.
"""
BUFFER_Q = 0
BUFFER_QD = 1
BUFFER_QDD = 2
BUFFER_QQ = 3
BUFFER_UX = 4
BUFFER_UXD = 5
BUFFER_LINK_Z = 6
BUFFER_LINK_ZD = 7
BUFFER_LINK_F = 8
BUFFER_VISU = 9
BUFFER_QC = 10
BUFFER_QA = 11
BUFFER_LAMBDA = 12
BUFFER_X = 13
BUFFER_F = 14
BUFFER_R = 15
BUFFER_OTHER = 16
# =============================================================================
# Defining Python MbsResults class
# =============================================================================
class MbsResult(object):
"""
Class containing results of multibody analysis (except for modal analysis).
The attributes are set to None if they are relevant for the current analysis.
The user-defined outputs (vector or floats) are stored in the dictionnary
'outputs'.
Attributes
----------
q : numpy.ndarray
Array containing the history of the generalized coordinates.
qd : numpy.ndarray
Array containing the history of the generalized velocities.
qdd : numpy.ndarray
Array containing the history of the generalized accelerations.
Lambda : numpy.ndarray
Array containing the history of the lagrange multipliers related to the
constraints.
mbs : MbsData
Instance of MbsData from where results where computed.
Fl : numpy.ndarray
Array containing the history of the forces in each links.
outputs : dict
Dict containing the names (as key) and the history (as value) of the user
outputs. Each history is a numpy.ndarray with first column being the time.
Qa : numpy.ndarray
Array containing the history of the actuation force (or torque) in each
articulations, the value for non-articulated joints is always 0.
This field is only relevant for inverse dynamic.
Qc : numpy.ndarray
Array containing the history of the force introduced in driven joints
to respect the user-specified trajectory (position, velocity, acceleration).
The values for non-driven joints is always 0.
Qq : numpy.ndarray
Array containing the history of the joint forces.
t : numpy.ndarray
Array containing the time vector.
ux : numpy.ndarray
Array containing the history of the user variables.
uxd : numpy.ndarray
Array containing the history of the time derivative of the user variables.
Z : numpy.ndarray
Array containing the history of the lenght of each link.
Zd : numpy.ndarray
Array containing the history of the speed (spreading) of each link.
Notes
-----
The module internal class '_buffer_ids' must be updated to be coherent with
the enumeration in the file "mbs_buffer.h"
"""
def __init__(self, mbs):
"""Initialize the fields to empty list or dict."""
self.q = None
self.qd = None
self.qdd = None
self.Qq = None
self.ux = None
self.uxd = None
self.Z = None
self.Zd = None
self.Fl = None
self.Qc = None
self.Qa = None
self.Lambda = None
self.outputs = None
self.t = None
self.mbs = mbs
def set_buffer(self, buf_id, buf_array, name=None):
"""Set the buffer array in the field corresponding the the buffer id.
Parameters
----------
buf_id : int
Value of the buffer id to be set. Buffer id are defined in the
_buffer_ids class.
buf_array : numpy.ndarray
Array with the history of the variable stored in the specified buffer.
name : str, optionnal
Name of the user buffer to be set, ignored for other buffer.
Returns
-------
bool
True if success, False otherwhise.
"""
# List with ignored buffer ids
ignored_buffers = [_buffer_ids.BUFFER_X.value,
_buffer_ids.BUFFER_F.value,
_buffer_ids.BUFFER_R.value,
_buffer_ids.BUFFER_VISU.value]
# Check and set buffer
if buf_id == _buffer_ids.BUFFER_Q.value:
self.q = buf_array
elif buf_id == _buffer_ids.BUFFER_QD.value:
self.qd = buf_array
elif buf_id == _buffer_ids.BUFFER_QDD.value:
self.qdd = buf_array
elif buf_id == _buffer_ids.BUFFER_QQ.value:
self.Qq = buf_array
elif buf_id == _buffer_ids.BUFFER_UX.value:
self.ux = buf_array
elif buf_id == _buffer_ids.BUFFER_UXD.value:
self.uxd = buf_array
elif buf_id == _buffer_ids.BUFFER_LINK_Z.value:
self.Z = buf_array
elif buf_id == _buffer_ids.BUFFER_LINK_ZD.value:
self.Zd = buf_array
elif buf_id == _buffer_ids.BUFFER_LINK_F.value:
self.Fl = buf_array
elif buf_id == _buffer_ids.BUFFER_QC.value:
self.Qc = buf_array
elif buf_id == _buffer_ids.BUFFER_QA.value:
self.Qa = buf_array
elif buf_id == _buffer_ids.BUFFER_LAMBDA.value:
self.Lamda = buf_array
elif buf_id == _buffer_ids.BUFFER_.value:
self.outputs[name] = buf_array
elif buf_id in ignored_buffers:
pass
else:
mbs_warning(f"Invalid buffer index value: {buf_id}.")
return False
return True
def load_results_from_buffer(self, c_ptr, t0=None, resfilename=''):
"""Load the results from the buffer if completely available.
Parameters
----------
c_ptr : ctypes.POINTER to either MbsDirdyn_c or MbsSolvekin_c or MbsInvdyn_c
Pointer to the c-structure of the current module.
The module must contain the fields 'buffers' (MbsBuffer**), 'bufferNb'
(int) and 'user_buffer' (MbsGrowingBuffer*).
t0 : float, optional
Initial time (if relevant) of the analysis. If the initil time in the
buffer does not match the provided values, the buffer are not loaded.
If the value is None, then the buffers are loaded whatever the available
timestep.
The default is None.
resfilename : str, optionnal
The option of 'resfilename' of the module, this variable is only used
to recreate the user output vectors name from the user filename.
The default is '' (empty str).
Returns
-------
bool
True if the whole analysis was loaded from the buffers.
False if nothing was loaded from the buffers (incomplete of invalid buffers).
Notes
-----
Usually the first buffer is dedicated to generalized coordinates. At the
exception of equilibrium analysis, where it is the fourth buffer. Even
if equilibrium analysis is not (yet) handled by this function the loading
iterate the buffers unil finding the one dedicated to generalized
coordinates.
The buffer of the generalized coordinates is loaded to check if the full
simulation is available in the buffer.
"""
# get usefull contents
nb_buffers = c_ptr.contents.bufferNb # Size of the buffer_list
buffer_list = c_ptr.contents.buffers # "list" to each buffer pointer
user_buffer = c_ptr.contents.user_buffer # Pointer to the user_buffer
# Locate generalized coordinate buffer
index_q = -1
for i in range(nb_buffers):
buffer_id = buffer_list[i].contents.id
if buffer_id == _buffer_ids.BUFFER_Q.value:
index_q = i
break
# Raise warning if it was not located
if index_q < 0:
raise RuntimeWarning("Generalized coordinates buffer missing in current module.")
return False
# Load generalized buffer
nb_var = buffer_list[index_q].contents.nx + 1 # Time is not counted in nx.
nb_steps = buffer_list[index_q].contents.index # Filled values in the buffer.
if nb_steps == 0:
# nb_steps is the number of steps not (yet) save to disk.
# If the value is 0 it means that all has been saved to disk.
# But all is also still in memory.
nb_steps = buffer_list[index_q].contents.size
# Retrieve time and generalized coordinates.
q = np.copy(np.ctypeslib.as_array(buffer_list[index_q].contents.tx, (nb_steps, nb_var)))
t = q[:, 0]
if t0 is not None and t[0] != t0:
return False
# Load each buffer, number of step is constant
for i in range(nb_buffers):
nb_var = buffer_list[i].contents.nx + 1 # Time is not counted in nx.
buffer_array = np.copy(np.ctypeslib.as_array(buffer_list[i].contents.tx, (nb_steps, nb_var)))
buffer_id = buffer_list[i].contents.id
name = None
#For user-vector-buffer, retrieve the buffer name
if buffer_id == _buffer_ids.BUFFER_OTHER.value:
# get filename
name = bytes_to_str(os.path.basename(buffer_list[i].contents.filename)[:-4])
# Remove module prefixe from filename
name = name[len(self.get_options("resfilename")) + 1:]
# Set the buffer
self.set_buffer(buffer_id, buffer_array, name)
# Load scalar user buffer
nb_steps = user_buffer.contents.index # Filled values in the buffer.
if not nb_steps:
# nb_steps is the number of steps not (yet) save to disk.
# If the value is 0 it means that all has been saved to disk.
# But all is also still in memory.
nb_steps = user_buffer.contents.size
nb_var = user_buffer.contents.nx
for i in range(nb_var):
user_out = np.copy(np.ctypeslib.as_array(user_buffer.contents.X[i], (1, nb_steps)))
name = bytes_to_str(user_buffer.contents.names[i])
self.outputs[name] = user_out[0]
return True
def load_results_from_file(self, filename, result_path="resultsR", module=0,
user_output=None, user_vector=None):
"""
Load the results from the files into a MbsResult instance.
This function is called if the buffers do not contain the full simulation.
Parameters
----------
filename : str
The resfilename containing the results to load. The required suffix
(ie. "_q.res") will be added automatically
result_path : str, optional
The relative path of the result folder from the project folder.
default is "resultsR"
module : int
The module in which this function is called. In some modules, some
results files don't exist. Module ids corresponds to the
'MbsData.process' value. If set to 0 we try to load all files
default is 0
user_output : list
List containing the user output files to be loaded if not None.
default is None.
user_vector : list
List containing the user vector output files to be loaded if not None.
default is None.
"""
project_path = self.mbs.project_path
baseFileName = os.path.basename(filename)
baseFileName = baseFileName[:-6]
result_path = os.path.join(project_path, result_path)
# Error handeling
if not os.path.isdir(result_path):
if __DEBUG__:
mbs_msg('DEBUG>> The result directory does not exist: "' + result_path + '"')
# Generalized coordinates
CurFile = baseFileName + '_q.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.q = np.loadtxt(path)
if np.array(self.q).ndim > 1:
self.t = self.q[:, 0]
else:
self.t = self.q[0]
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Generalized velocities
CurFile = baseFileName + '_qd.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.qd = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Generalized accelerations
CurFile = baseFileName + '_qdd.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.qdd = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
if module != 5:
CurFile = baseFileName + '_Qq.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Qq = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Generalized user state
if self.mbs.Nux and (module != 5 and module != 6):
CurFile = baseFileName + '_ux.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.ux = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
CurFile = baseFileName + '_uxd.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.uxd = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Link
if self.mbs.Nlink and module != 5:
CurFile = baseFileName + '_linkF.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Fl = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
CurFile = baseFileName + '_linkZ.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Z = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
CurFile = baseFileName + '_linkZd.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Zd = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Qc
if self.mbs.nqc and module != 5:
CurFile = baseFileName + '_Qc.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Qc = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
# Qa
if self.mbs.nqa and (module != 5 and module != 3):
CurFile = baseFileName + '_Qa.res'
path = os.path.abspath(os.path.join(result_path, CurFile))
if(os.path.isfile(path)):
self.Qa = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
if user_output:
for CurFile in user_output:
path = os.path.abspath(os.path.join(result_path, CurFile))
name = CurFile[len(baseFileName) + 1:-4]
if(os.path.isfile(path)):
self.outputs[name] = np.loadtxt(path)[:, 1:]
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
if user_vector:
for CurFile in user_vector:
path = os.path.abspath(os.path.join(result_path, CurFile))
name = CurFile[len(baseFileName) + 1:-4]
if(os.path.isfile(path)):
self.outputs[name] = np.loadtxt(path)
else:
if __DEBUG__:
mbs_msg("DEBUG>> file '" + CurFile + "' not found in folder '" + os.path.dirname(path))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment