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.
First, let’s begin by importing the necessary Python libraries for the script. There’s nothing out of the ordinary here, we use:
- os for handling paths and files
- re for looking for specific contents on the MDL file using regular expressions
- time as a mechanism to wait until XML files are compiled before continuing through the script
- subprocess to run system commands as one would from the command terminal
- numpy for some basic data handling
- matplotlib for plotting results
- IPython for showing plots on a separate matplotlib window (that’s what is done right after the imports)
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\2024"
exporttooml("altair_folder", "altairFolder")
EvalOmlScript("setaltairfolder(altairFolder)")
acc_path = r"C:\Program Files\Altair\2024\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 = os.path.join(os.getcwd(), r"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.