Vortex-Radioss
The following documentation is created to help users of Vortex-Radioss understand how and what the library does.
The library has three main functions:
To read the results from an Radioss animation file and presents the data as a Python dictionary
To read the results from an Radioss time history file and presents the data as a Python dictionary
To read the results from an Radioss animation file and convert into LS-Dyna’s d3plot file format - Not all output data is currently supported, but this will be expanded over time
The library can be accessed here:
Please click here for the OpenRadioss discussion thread.
Demonstration
Here we have a demonstration of the d3plot convertor:
A 2020 Nissan Rogue frontal crash was simulated using OpenRadioss
The animation files were converted to d3plots using Vortex-Radioss
The results were animated using LS-Prepost
Useage
Using the animation and time history readers
Python dictionaries are objects that return another object when a key is presented. The entries are dynamic and are dependent on the results file used, the names are taken from the results file. The returned object will either be a numpy array, another dictionary or an integer/float.
First however we need to read in the results file:
For the animation files we use:
from vortex_radioss.animtod3plot.RadiossReader import RadiossReader ra = RadiossReader(file)
Similarly to read the time history we use:
from vortex_radioss.animtod3plot.RadiossTHReader import RadiossTHReader rt = RadiossReader(file)
Overview of dictionaries
To return the dictionaries:
ra.raw_header ra.raw_arrays ra.arrays rt.raw_header rt.arrays
raw_header
The raw header dictionaries contain high level dimensional data, they are either flags to specify whether or not a class of data has been requested, or they contain sizing information (such as the number of shell elements etc). This information is used to calculate the memory address of where data arrays exist in the remaining file. The Radioss arrays differ from the LS-Dyna arrays in that each animation file has its own header section, whereas for LS-Dyna only one header section is created, the header files for Radioss are for the most part identical between animation files.
raw_arrays
The raw array dictionary for Radioss animation files contain the array data as it exists in the binary file, the format is however a little unintuitive, so to make things easier we unpack some arrays into easier to access format which is then returned in the regular arrays dictionary. There is however no reason why this dictionary could not be used, we just recommend the regular array dictionary for ease of use.
arrays
The regular array dictionaries contain the main data arrays produced by the analysis.
Using the dictionaries
As with all Python dictionaries a key is required to be provided to access the dictionary contents. To see what possible key options are available the .keys() option can be used. For example for ra.arrays.
print(ra.arrays.keys())
A typical output may look like this:
dict_keys(['timesteps', 'node_coordinates', 'element_beam_node_indexes', 'element_shell_node_indexes', 'element_solid_node_indexes', 'node_ids', 'node_time_step', 'node_mass_change', 'node_velocity', 'node_displacement', 'node_acceleration', 'node_contact_forces', 'node_contact_pressure_/_normal', 'node_contact_pressure_/_tangent', 'node_tied_contact_forces', 'element_beam_ids', 'element_shell_ids', 'element_solid_ids', 'element_beam_part_indexes', 'element_beam_part_ids', 'element_shell_part_indexes', 'element_shell_part_ids', 'element_solid_part_indexes', 'element_solid_part_ids', 'element_beam_is_alive', 'element_shell_is_alive', 'element_solid_is_alive', 'element_beam_plastic_strain', 'element_beam_specific_energy', 'element_shell_plastic_strain', 'element_shell_density', 'element_shell_specific_energy', 'element_shell_thickness', 'element_shell_plastic_strain_upper', 'element_shell_plastic_strain_lower', 'Stress (upper)', 'Stress (lower)', 'element_solid_plastic_strain', 'element_solid_specific_energy', 'element_solid_stress', '2:rbodies_&_rbe2_&_rbe3_model10000000', '3:rwalls_model2_&_rbe3_model10000000', '1:global_model2_&_rbe3_model10000000', 'material_text_a', 'material_type_a', 'properties_text_a', 'properties_type_a'])
We can use a key and check what the returned object is:
print(type(ra.arrays["element_shell_density"]))
For this we receive:
<class 'numpy.ndarray'>
As this is a numpy array we can check the shape:
print(ra.arrays["element_shell_density"].shape)
For this we receive:
(10163016,)
Our array is a 1D scalar array with a length equivalent to the number of shells in the model.
A similar process would be used for the time history files. The main difference between the animation file reader and the time history file reader is that the animation file reader only contains the output for a single time stamp whereas the time history reader will produce arrays that contain the full time history of the simulation. Therefore multiple animation files will need to be read in and the user will need to append the arrays to produce the full time history.
Using the animation to d3plot conversion module
The animation to d3plot conversion module is used as follows. The file path stem is the path to the first Radioss animation file, without the A001 suffix.
from vortex_radioss.animtod3plot.Anim_to_D3plot import readAndConvert readAndConvert(filepath_stem)
The d3plot files will be generated in the same directory as the animation files:
Additional options
Additional flags can be applied to change the output from the convertor tool.
Masks
Radioss will sometimes generate non-structural elements for its own internal use, by default the conversion tool will filter these elements out, to display everything set “use_shell_mask=False” :
readAndConvert(filepath_stem, use_shell_mask=False)
Quiet mode
In order to disable any text output to screen during the conversion:
readAndConvert(filepath_stem, silent=True)
Single precision output
By default the library outputs in I8R8 which is double precision format, smaller file sizes by a factor of 2 can be achieved by using I4R4 single precision format. As with LS-Dyna single precision executables are smaller limits on ranges for numbering of entities, as we don’t have checks in place for out of range numbering, an easy to access option has not yet been implemented.
However, if users wish to enable single precision mode at risk, this can be achieved by modifying the following lines in the Anim_to_D3plot.py file from:
self._d3plot.header.itype = np.int64 self._d3plot.header.ftype = np.float64 self._d3plot.header.wordsize = 8
To:
self._d3plot.header.itype = np.int32 self._d3plot.header.ftype = np.float32 self._d3plot.header.wordsize = 4
Table of supported outputs
The tables below outline the data types currently available for conversion and offer guidance on the database requests required to meet the necessary pre-requisites.
Data | Units | Required | Database Request | Comments |
---|---|---|---|---|
Mass (Shell Parts | Mass |
Shell Density Shell Thickness |
/ANIM/SHELL/THIC /ANIM/SHELL/DENS |
Eroded mass is not included |
Data | Units | Required | Database Request | Comments |
---|---|---|---|---|
Thickness | Distance |
Shell Thickness |
/ANIM/SHELL/THIC |
|
Internal Energy Density | Energy/Volume |
Specific Energy Density |
/ANIM/SHELL/DENS /ANIM/SHELL/ENER |
Note change in units |
Stress Tensor | Pressure | Stress |
/ANIM/SHELL/TENS/STRESS/UPPER /ANIM/SHELL/TENS/STRESS/LOWER |
Components may differ when converted due to differing coordinate system definitions, invariant stresses e.g VonMises, Tresca, J2, Principle, etc unaffected |
Strain Tensor | None | Strain |
/ANIM/SHELL/TENS/STRAIN/UPPER /ANIM/SHELL/TENS/STRAIN/LOWER |
Components may differ when converted due to differing coordinate system definitions, invariant stresses e.g VonMises, Tresca, J2, Principle, etc unaffected |
Effective Plastic Strain | None | Plastic Strain Tensor |
/ANIM/SHELL/EPSP/UPPER /ANIM/SHELL/EPSP/LOWER |
Mid-plane not currently calculated |
More detailed operation
LS-Dyna generally has additional rules on how the data is presented in the d3plot, as such some additional functions were created to ensure that the format is correct.
Vectorization
The code has been written to take advantage of numpy vectorisation. A large proportion of code execution speed is how fast data can be read from / written to IO and is therefore heavily dependent on how fast the drive is. For sake of simplicity vectorisation is using numpy array functions as opposed to using Python loops.
Memory Management
In order to minimize the memory requirements only one plot state is kept in memory at one time. This means that the same model containing 5 or 5000 plot states will consume the same peak memory. As Lasso-Python does not currently support this option we instead trick it by generating a temporary d3plot containing just one plot state. We then delete the duplicate header file and rename the array file to the correct name and index. This does generate unnecessary IO with unnecessary header writes, we hope to update lasso-python with the ability to write just arrays directly.
Numerical order and zero ids
The ordering of ID’s for items such as shells, solids, nodes, parts, beams etc must be written in ascending order. Duplicate ID’s and values of 0 will break some post-processors. Tracking and sequential functions were created to map the re-ordering and re-number items. These are named generate_sorter, and sequential respectively, generate_sorter will return a mapping array from the old index to the new index and is applied though the function apply_sorter. The mapping arrays are only generated once for the first animation file, but are applied to every plot state.
Masks
To remove unwanted elements from the d3plot the masking function generate_mask_map was created to remove any shells with a part ID of zero, to complicate things further, some of these zero part ids parts are useful, e.g displaying rigid walls. The generate_display_entity function will therefore search for a matching text string within the part names and generate the smallest, unique, non-zero, positive part id for the part. The masks are applied by modifying the sorter mapping arrays to exclude removed items.
Empty Parts
Parts that contain zero items will break some post-processors. In order to do this empty_part_tracker and inverted_part_ids_tracker are generated to delete the empty parts and update the part arrays and the element part index arrays respectively.
Creating New Convertor Functions
The structure for defining the convertor functions is as follows.
First the class of functions needs adding to the database_extent_binary dictionary if the entry does not already exist. For example for the shells we currently have the shell group defined as follows:
flag = "SHELLS" database_extent_binary[flag] = {} _ = database_extent_binary[flag]
Within the shell group we define other subgroups containing lists of our outputted data names. For example group 0 contains, element_shell_is_alive, .element_shell_stress, element_shell_effective_plastic_strain.
If one entry within a group is able to be converted, all entries must be created. This is because LS-Dyna often groups outputs together under a single flag, failure to define all members of the group can break post-processors. Similarly if a higher group is converted, all members of lower groups must also be converted. Where data is not able to be converted, zero arrays are generated as padding, this is a waste of disk space but there is no way around it:
database_extent_binary[flag][0] = [] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_is_alive] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_stress] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_effective_plastic_strain] database_extent_binary[flag][1] = _[0] + [ArrayType.element_shell_thickness] database_extent_binary[flag][1] = _[1] + [ArrayType.element_shell_internal_energy]
Within the shell group we define other subgroups containing lists of our outputted data names. For example group 0 contains, element_shell_is_alive, .element_shell_stress, element_shell_effective_plastic_strain.
If one entry within a group is able to be converted, all entries must be created. This is because LS-Dyna often groups outputs together under a single flag, failure to define all members of the group can break post-processors. Similarly if a higher group is converted, all members of lower groups must also be converted. Where data is not able to be converted, zero arrays are generated as padding, this is a waste of disk space but there is no way around it:
database_extent_binary[flag][0] = [] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_is_alive] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_stress] database_extent_binary[flag][0] = _[0] + [ArrayType.element_shell_effective_plastic_strain] database_extent_binary[flag][1] = _[0] + [ArrayType.element_shell_thickness] database_extent_binary[flag][1] = _[1] + [ArrayType.element_shell_internal_energy]
Each data type as its own definition defined. The “dependents” list checks the Radioss arrays for the entry, all entries must be present for conversion to take place. Additional objects can be passed into the conversion function via the “additional” list, these are however not checked. The conversion function name is defined under the “convert” definition. We also provide the relevant tracking array for the array type to ensure correct ordering. When a data array cannot be converted, a numpy zero array with size “shape” is generated instead as a filler array.
array_requirements[ArrayType.element_shell_internal_energy] = {} _ = array_requirements[ArrayType.element_shell_internal_energy] # Radioss outputs needed to compute Dyna output _["dependents"] = ["element_shell_specific_energy", "element_shell_density"] _["shape"] = (1,n_shell) _["convert"] = convert.element_shell_internal_energy _["tracker"] = shell_ids_tracker _["additional"] = []
The conversion functions are defined as follows. They return a numpy array in the correct shape for output.
@staticmethod def element_shell_internal_energy(*data): # data[0] is shell energy per unit mass (Radioss units) # data[1] is shell density # out is shell energy per unit volume (LS-Dyna units) out = np.multiply(data[0], data[1]) return out
Future updates
We are continuing to expand this library. If you would like to be automatically notified when we release updates, or would like to be notified on any of our future projects. Please consider subscribing to our newsletter below:
Testimonials
-
"This is a remarkable contribution. It promises to garner significant interest from the community, enhancing accessibility to OpenRadioss, fostering seamless integration with various CAE tools and workflows."
-
"This is a great milestone! "This will boost the usage of OpenRadioss."