Drive MotionSolve Simulations with Python and Altair Compose


Altair Compose is a tool that can enhance our usage of other simulation tools. In this small post I will show you how you can use it to run many multibody systems simulations in batch using MotionSolve, Altair’s multibody dynamics solver. Concretely, this example shows how to take an input model (MDL file) and change an input force for exploring different conditions.

 

Note that Compose features not only OML but Python scripting as well. This example showcases the latter. The model to simulate consists of a mass and a spring in which the input force, exerted on the mass at the top of the spring, is read from an input CSV. All CSV files present in a directory will be read as force inputs for running simulations.

 

A green ball on a springDescription automatically generated

 

First, let’s begin by importing the necessary Python libraries for the script. There’s nothing out of the ordinary here, we use:

 

 

We will also declare some important paths to be used later for running MotionSolve using OML functions and system commands.

 

import os

import re

import time

import subprocess

import numpy as np

import matplotlib.pyplot as plt

from IPython import get_ipython

 

# Configure plots to be created in floating window

get_ipython().run_line_magic('matplotlib', 'qt')

 

# Set Altair folder & Altair Compute Console path

altair_folder = r"C:\Program Files\Altair\2023.1"

exporttooml("altair_folder", "altairFolder")

EvalOmlScript("setaltairfolder(altairFolder)")

acc_path = r"C:\Program Files\Altair\2023.1\common\acc\scripts\acc.bat";

 

Next, the relevant directories and files, i.e. input directory, desired output directory and MDL with the model are defined. An output directory is created if needed and all CSV files present in the input directory are stored in a list (will be needed in later steps).

 

# Set model file, input & output directories

inputs_dir = os.path.join(os.getcwd(), r"Input Files")

mdl_file = os.path.join(inputs_dir, "SpringMass.mdl")

mdl_name = os.path.basename(mdl_file)

outputs_dir = r"C:\Users\rsanchez\Desktop\Outputs"

 

# Create output directory if needed

if not os.path.exists(outputs_dir):

      os.makedirs(outputs_dir)

 

# Fetch all available CSVs in input directory

input_files = os.listdir(inputs_dir)

load_names = [f for f in input_files if f.endswith(".csv")]

 

Afterwards, the MDL multibody model is read as a string so we can edit all references to external files. This will be needed for running simulations with different force inputs. For each of these inputs, a copy of the model is created, referencing a different CSV file and a different MRF file to output, in a separate directory.

 

# Read MDL contents & find input file definitions

with open(mdl_file, 'r') as file:

    mdl_contents = file.read()

matches = re.findall(r'"([^"]*(\.csv|\.mrf))"', mdl_contents)

 

# Prepare each load case

xml_files = []

res_dirs = []

for load_name in load_names:

 

      # Create directory for each output & copy MDL

      this_out_dir = os.path.join(outputs_dir, load_name[:-4])

      if not os.path.exists(this_out_dir):

            os.makedirs(this_out_dir)

      this_mdl_file = os.path.join(this_out_dir, mdl_name)

     

      # Create results subdirectory

      this_res_dir = os.path.join(this_out_dir, "Results")

      if not os.path.exists(this_res_dir):

            os.makedirs(this_res_dir)

      res_dirs.append(this_res_dir)

     

      # Create MDL

      csv_match = matches[0][0];

      load_file = os.path.join(inputs_dir, load_name).replace("\\", r"/")

      new_mdl_contents = mdl_contents.replace(csv_match, load_file)

      mrf_match = matches[2][0]

      new_mrf_file = os.path.join(this_out_dir, mdl_name[:-4] + ".mrf")

      new_mdl_contents = new_mdl_contents.replace(mrf_match, new_mrf_file)

      with open(this_mdl_file, 'w') as file:

            file.write(new_mdl_contents)

 

Once we have all the MDL files we need, we can compile each of them into an XML file before the actual multibody simulation. This is done calling MotionSolve in batch from Compose with function callaltairbatch. Note that these are not Python commands but, rather, they are OML commands being called using the very easy to use Python – OML bridging commands. With these, variables can be easily taken from the Python environment to the OML environment and vice versa and specific OML commands (or even full files) can be executed.

 

      # Compile XML

      this_xml_file = this_mdl_file[:-4] + '.xml'

      xml_files.append(this_xml_file)

      exporttooml("this_mdl_file", "thisMdlFile")

      exporttooml("this_xml_file", "thisXmlFile")

      EvalOmlScript("callaltairbatch('MDL', thisMdlFile, thisXmlFile, 'MotionSolve')")

 

Also, a while loop is implemented to stop the flow of the script while the XML files are being created.

 

# Wait for XML files to be created

while sum([os.path.isfile(f) for f in xml_files]) < len(xml_files):

      time.sleep(0.1)

 

Now that the XML models are ready for simulation, we need to create a BAT file that will execute the system commands to run each of them. Those commands consist of calling the Altair Compute Console to run MotionSolve using each XML file as an input and with a different location for the produced results files.

 

# Create BAT file for running all jobs with nt threads

nt = 9

res_files = [os.path.join(d, mdl_name[:-4]) for d in res_dirs]

bat_file = os.path.join(outputs_dir, 'ParallelSims.bat');

with open(bat_file, 'w') as file:

      for i in range(len(res_files)):

            xml_file = xml_files[i]

            res_file = res_files[i]

            file.write("call \"{}\" -nt {} ".format(acc_path, nt))

            file.write("-file {} -solver MS -outfile {}\n".format(xml_file, res_file))

 

Finally, the BAT file is run using the subprocess library and the results files created are read back into Compose using OML CAE Readers library and Python – OML bridging commands once more. Results from all simulations can be compared in the created plot window.

 

# Run all jobs

print("Running simulations...")

result = subprocess.run(bat_file, capture_output = True, text = True)

print('done')

 

# Read results from H3D & plot results

for res_name in res_files:

      res_file = res_name + ".h3d"

      exporttooml("res_file", "resFile")

      EvalOmlScript("t = gettimesteplist(resFile, 1, 'Force (Primitives)', 1, 'Z');")

      EvalOmlScript("data = readcae(resFile, 1, 'Force (Primitives)', 1, 'Z', [], 1);")

      getomlvar("t", "t")

      getomlvar("data", "data")

      t = np.squeeze(t)

      data = np.squeeze(data)

      plt.plot(t, data)

plt.show()

 

I hope this very small example helps you get started using Compose to accelerate your simulation workflows. The script, input and model files needed for replicating are attached to this article.

 

Reach out in the community product forums if you are having issues leveraging Compose in this way. Alternatively, email hwsupport@altair.com or reach out for support from your Altair One account if you need assistance from experts.