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.

Parts
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
Shells
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