Note
Go to the bottom of this page to download the ZIP file for the PyAnsys cantilever example.
PyAnsys workflow: Cantilever#
This example shows how to run PyAnsys scripts on HPS. It simulates a cantilever using a chain of PyAnsys packages:
Use PyAnsys Geometry to draw a cantilever design.
Use PyPrimeMesh to apply a swept mesh to the cantilever design.
Use PyMAPDL to calculate eigenfrequencies of the cantilever and then display them as output parameters.
The example parametrizes the physical cantilever dimensions, as well as several mesh and simulation properties.
Prerequisites#
Several packages need to be installed for the example. The paths and versions in the following setup instructions are for an Ansys 2025 R2 installation on a Linux system. Be sure to adjust them to your installation.
uv#
The example uses the uv package manager to run Python scripts. You can find more information, including installation instructions, in the example_python_uv example.
Ansys Geometry Service#
The PyAnsys Geometry package requires the Ansys Geometry Service. For installation instructions, see How is the Ansys Geometry Service installed? in the Frequently asked questions section of PyAnsys Geometry documentation. Once installed, the package must be registered in the scaler/evaluator as follows:
Property |
Value |
---|---|
Name |
Ansys Geometry Service |
Version |
2025 R2 |
Installation Path |
/ansys_inc/v252/GeometryService |
You must also ensure that these environment variables are defined:
Environment variable |
Value |
---|---|
ANSRV_GEO_LICENSE_SERVER |
|
ANSYS_GEOMETRY_SERVICE_ROOT |
/ansys_inc/v252/GeometryService |
Ansys Prime Server#
The PyPrimeMesh package requires Ansys Prime Server, which is automatically installed with Ansys 2023 R1 or later. Ansys Prime Server must be registered in the scaler/evaluator as follows:
Property |
Value |
---|---|
Name |
Ansys Prime Server |
Version |
2025 R2 |
Installation Path |
/ansys_inc/v252/meshing/Prime |
You must also ensure that these environment variables are defined:
Environment variable |
Value |
---|---|
AWP_ROOT252 |
/ansys_inc/v252 |
ANSYS_PRIME_ROOT |
/ansys_inc/v252/meshing/Prime |
Ansys Mechanical APDL#
Ansys Mechanical APDL is installed with the Ansys unified installer. The scaler/evaluator automatically detects this package, but you must ensure that these two environment variables are added to its registration for PyMAPDL:
Environment variable |
Value |
---|---|
AWP_ROOT252 |
/ansys_inc/v252 |
PYMAPDL_MAPDL_EXEC |
/ansys_inc/v252/ansys/bin/ansys252 |
HPS Python Client#
The example uses unmapped HPS parameters and therefore requires PyHPS 0.11 or later.
Run the example#
To run the example, execute the project_setup.py
script:
uv run project_setup.py
This command sets up a project with a number of jobs. Each job samples a different cantilever design point.
Options#
The example supports the following command line arguments:
Flag |
Example |
Description |
---|---|---|
|
|
URL of the target HPS instance |
|
|
Username to log into HPS |
|
|
Password to log into HPS |
|
|
Number of design points to generate |
|
|
Number of lowest eigenfrequencies to calculate |
|
|
Frequency in Hertz to target for the lowest cantilever mode |
|
|
Split each step into a different task |
Parameters#
The example defines the following HPS parameters:
Parameter |
Description |
---|---|
canti_length |
Length of the cantilever [um] |
canti_width |
Width of the cantilever [um] |
canti_thickness |
Thickness of the cantilever [um] |
arm_cutoff_width |
Amount to thin the cantilever arm by [um] |
arm_cutoff_length |
Length of the cantilever arm [um] |
arm_slot_width |
Width of the slot cut into the cantilever arm [um] |
arm_slot |
Whether there is a slot in the cantilever arm |
young_modulus |
Young Modulus of the cantilever material [Pa] |
density |
Density of the cantilever material [kg/m^3] |
poisson_ratio |
Poisson ratio of the cantilever material |
mesh_swept_layers |
Number of layers to generate when sweeping the mesh |
num_modes |
Number of eigenfrequencies to calculate |
popup_plots |
Whether to show popup plots (requires a framebuffer) |
port_geometry |
Port used by Ansys GeometryService |
port_mesh |
Port used by Ansys Prime Server |
port_mapdl |
Port used by the Ansys Mechanical APDL service |
freq_mode_i |
Frequency of i-th eigenmode [Hz], iϵ{1,…,num_modes} |
clean_venv |
Whether to delete the virtual environments after execution |
Logic of the example#
The example comprises several files. Their roles are as follows:
The project_setup.py
script handles all communication with the HPS instance.
It sets up a project, uploads files, defines parameters, and applies settings.
The exec_script
script is the execution script that starts
the evaluation scripts. It first writes all
HPS parameters to an input_parameters.json
file, discovers
the available software, and then runs the active evaluation script with uv.
Finally, the execution script fetches parameters that may have been written to
the output_parameters.json
file by the evaluation script and sends them
back to the evaluator. Optionally, the execution script cleans up the ephemeral virtual environments
created by uv.
The eval_scripts
folder contains the PyAnsys evaluation scripts.
Each of the evaluation scripts first reads the parameters supplied by the execution script
in the input_parameters.json
file, then starts a PyAnsys service, and
finally runs the PyAnsys program.
The eval_combined.py
script combines all stages in one monolithic task.
The other three evaluation scripts split the three stages into three successive tasks.
You can find more information on the content of these scripts in the
User guide
of PyAnsys Geometry, in the
User guide
of PyPrimeMesh, and in the
User guide
of PyMAPDL.
Code#
The files relevant for the single task version follow.
Here is the project_setup.py
script for project creation:
# /// script
# requires-python = "==3.10"
# dependencies = [
# "ansys-hps-client @ git+https://github.com/ansys/pyhps.git@main",
# "packaging",
# "typer",
# ]
# ///
import logging
import os
import random
import sys
import typer
from packaging import version
import ansys.hps.client
from ansys.hps.client import Client, HPSError
from ansys.hps.client.jms import (
BoolParameterDefinition,
File,
FitnessDefinition,
FloatParameterDefinition,
IntParameterDefinition,
JmsApi,
Job,
JobDefinition,
Project,
ProjectApi,
ResourceRequirements,
Software,
SuccessCriteria,
TaskDefinition,
)
log = logging.getLogger(__name__)
def main(client, num_jobs, num_modes, target_frequency, split_tasks):
log.debug("=== Create Project")
jms_api = JmsApi(client)
tasks_config = "Split" if split_tasks else "Combined"
proj = jms_api.create_project(
Project(
name=f"Cantilever - {num_jobs} Designs - {tasks_config}",
priority=1,
active=True,
),
replace=True,
)
project_api = ProjectApi(client, proj.id)
log.debug("=== Define Files")
# Input files
cwd = os.path.dirname(__file__)
step_names = ["geometry", "mesh", "mapdl"]
if not split_tasks:
step_names = ["combined"]
files = []
for step_name in step_names:
name = f"eval_{step_name}"
files.append(
File(
name=f"{name}",
evaluation_path=f"{name}.py",
type="text/plain",
src=os.path.join(cwd, "eval_scripts", f"{name}.py"),
)
)
files.append(
File(
name="exec_script",
evaluation_path="exec_script.py",
type="text/plain",
src=os.path.join(cwd, "exec_script.py"),
)
)
# Output files
files.extend(
[
File(
name="cantilever_geometry",
evaluation_path="cantilever.x_t",
collect=True,
type="text/plain",
),
File(
name="cantilever_mesh",
evaluation_path="cantilever.cdb",
collect=True,
type="application/cdb",
),
]
)
files.append(
File(name="canti_plot", evaluation_path="canti_plot.png", collect=True, type="image/png")
)
files = project_api.create_files(files)
file_ids = {f.name: f.id for f in files}
log.debug("== Define Parameters")
# Input parameters
params = [
FloatParameterDefinition(
name="canti_length",
lower_limit=20.0,
upper_limit=1000.0,
default=350.0,
units="um",
mode="input",
),
FloatParameterDefinition(
name="canti_width",
lower_limit=10.0,
upper_limit=500.0,
default=50,
units="um",
mode="input",
),
FloatParameterDefinition(
name="canti_thickness",
lower_limit=0.05,
upper_limit=5.0,
default=0.5,
units="um",
mode="input",
),
FloatParameterDefinition(
name="arm_cutoff_width",
lower_limit=0.0,
upper_limit=250.0,
default=0.0,
units="um",
mode="input",
),
FloatParameterDefinition(
name="arm_cutoff_length",
lower_limit=20.0,
upper_limit=1000.0,
default=0.0,
units="um",
mode="input",
),
FloatParameterDefinition(
name="arm_slot_width",
lower_limit=0.0,
upper_limit=200.0,
default=0.0,
units="um",
mode="input",
),
BoolParameterDefinition(name="arm_slot", default=False, mode="input"),
FloatParameterDefinition(
name="young_modulus",
lower_limit=1e6,
upper_limit=1e13,
default=3.0e11,
units="Pa",
mode="input",
),
FloatParameterDefinition(
name="density",
lower_limit=1e3,
upper_limit=6e3,
default=3200,
units="kg/m^3",
mode="input",
),
FloatParameterDefinition(
name="poisson_ratio",
lower_limit=0.1,
upper_limit=0.6,
default=0.23,
units="",
mode="input",
),
IntParameterDefinition(
name="mesh_swept_layers",
lower_limit=2,
upper_limit=100,
default=10,
units="",
mode="input",
),
IntParameterDefinition(
name="num_modes",
default=num_modes,
lower_limit=num_modes,
upper_limit=num_modes,
units="",
mode="input",
),
BoolParameterDefinition(name="popup_plots", default=False, mode="input"),
IntParameterDefinition(name="port_geometry", default=50052, mode="input"),
IntParameterDefinition(name="port_mesh", default=50052, mode="input"),
IntParameterDefinition(name="port_mapdl", default=50052, mode="input"),
BoolParameterDefinition(name="clean_venv", default=False, mode="input"),
]
# Output parameters
for i in range(num_modes):
params.append(
FloatParameterDefinition(name=f"freq_mode_{i + 1}", units="Hz", mode="output")
)
params = project_api.create_parameter_definitions(params)
log.debug("=== Define Tasks")
if split_tasks:
task_def_geometry = TaskDefinition(
name="geometry",
software_requirements=[Software(name="uv"), Software(name="Ansys GeometryService")],
resource_requirements=ResourceRequirements(
num_cores=1.0,
memory=2 * 1024 * 1024 * 1024, # 2 GB
disk_space=500 * 1024 * 1024, # 500 MB
),
execution_level=0,
max_execution_time=500.0,
num_trials=3,
use_execution_script=True,
execution_script_id=file_ids["exec_script"],
input_file_ids=[file_ids["eval_geometry"]],
output_file_ids=[file_ids["cantilever_geometry"], file_ids["canti_plot"]],
success_criteria=SuccessCriteria(
return_code=0,
require_all_output_files=True,
),
)
task_def_mesh = TaskDefinition(
name="mesh",
software_requirements=[Software(name="uv"), Software(name="Ansys Prime Server")],
resource_requirements=ResourceRequirements(
num_cores=1.0,
memory=8 * 1024 * 1024 * 1024, # 8 GB
disk_space=500 * 1024 * 1024, # 500 MB
),
execution_level=1,
max_execution_time=500.0,
num_trials=3,
use_execution_script=True,
execution_script_id=file_ids["exec_script"],
input_file_ids=[file_ids["eval_mesh"], file_ids["cantilever_geometry"]],
output_file_ids=[file_ids["cantilever_mesh"]],
success_criteria=SuccessCriteria(
return_code=0,
require_all_output_files=True,
# hpc_resources=HpcResources(exclusive=True),
),
)
task_def_mapdl = TaskDefinition(
name="mapdl",
software_requirements=[
Software(name="uv"),
Software(name="Ansys Mechanical APDL", version="2025 R2"),
],
resource_requirements=ResourceRequirements(
num_cores=2.0,
memory=8 * 1024 * 1024 * 1024, # 8 GB
disk_space=500 * 1024 * 1024, # 500 MB
# hpc_resources=HpcResources(exclusive=True),
),
execution_level=2,
max_execution_time=500.0,
num_trials=3,
use_execution_script=True,
execution_script_id=file_ids["exec_script"],
input_file_ids=[file_ids["eval_mapdl"], file_ids["cantilever_mesh"]],
success_criteria=SuccessCriteria(
return_code=0,
require_all_output_parameters=True,
),
)
task_defs = project_api.create_task_definitions(
[task_def_geometry, task_def_mesh, task_def_mapdl]
)
else:
task_def_combined = TaskDefinition(
name="combined",
software_requirements=[
Software(name="uv"),
Software(name="Ansys GeometryService"),
Software(name="Ansys Prime Server"),
Software(name="Ansys Mechanical APDL", version="2025 R2"),
],
resource_requirements=ResourceRequirements(
num_cores=2.0,
memory=8 * 1024 * 1024 * 1024, # 8 GB
disk_space=1024 * 1024 * 1024, # 1 GB
# hpc_resources=HpcResources(exclusive=True),
),
execution_level=0,
max_execution_time=1200.0,
num_trials=3,
use_execution_script=True,
execution_script_id=file_ids["exec_script"],
input_file_ids=[file_ids["eval_combined"]],
output_file_ids=[
file_ids["cantilever_mesh"],
file_ids["cantilever_geometry"],
file_ids["canti_plot"],
],
success_criteria=SuccessCriteria(
return_code=0,
require_all_output_parameters=True,
require_all_output_files=True,
),
)
task_defs = project_api.create_task_definitions([task_def_combined])
log.debug("== Define Fitness")
fd = FitnessDefinition(error_fitness=2.0)
fd.add_fitness_term(
name="frequency",
type="target_constraint",
weighting_factor=1.0,
expression=f"map_target_constraint(values['freq_mode_1'], \
{target_frequency}, {0.01 * target_frequency}, {0.5 * target_frequency})",
)
log.debug("== Define Job")
job_def = JobDefinition(
name="JobDefinition.1",
active=True,
parameter_definition_ids=[p.id for p in params],
task_definition_ids=[td.id for td in task_defs],
fitness_definition=fd,
)
job_def = project_api.create_job_definitions([job_def])[0]
# Refresh parameters
params = project_api.get_parameter_definitions(job_def.parameter_definition_ids)
params_by_name = {p["name"]: p for p in params}
log.debug(f"== Create {num_jobs} Jobs")
jobs = []
for i in range(num_jobs):
values = generate_parameter_values_for_job(i, params_by_name)
jobs.append(
Job(name=f"Job.{i}", values=values, eval_status="pending", job_definition_id=job_def.id)
)
jobs = project_api.create_jobs(jobs)
log.info(f"Created project '{proj.name}', ID='{proj.id}")
return proj
def entrypoint(
url: str = typer.Option("https://localhost:8443/hps", "-U", "--url"),
username: str = typer.Option("repuser", "-u", "--username"),
password: str = typer.Option("repuser", "-p", "--password"),
num_jobs: int = typer.Option(20, "-n", "--num-jobs"),
num_modes: int = typer.Option(3, "-m", "--num-modes"),
freq_tgt: float = typer.Option(100.0, "-f", "--target-frequency"),
split_tasks: bool = typer.Option(False, "-s", "--split-tasks"),
):
log.debug("Arguments:")
log.debug(
f"{url}\n{username}, {password}\njobs: {num_jobs}\nmodes: {num_modes}\nfreq: {freq_tgt}"
)
logging.basicConfig(format="[%(asctime)s | %(levelname)s] %(message)s", level=logging.DEBUG)
client = Client(url=url, username=username, password=password)
try:
main(client, num_jobs, num_modes, freq_tgt, split_tasks)
except HPSError as e:
log.error(str(e))
def sample_parameter(p, minval=None, maxval=None):
if minval is None:
minval = p.lower_limit
if maxval is None:
maxval = p.upper_limit
return minval + random.random() * (maxval - minval)
def generate_parameter_values_for_job(i, params_by_name):
values = {}
values["canti_length"] = sample_parameter(params_by_name["canti_length"])
values["canti_width"] = sample_parameter(params_by_name["canti_width"])
values["canti_thickness"] = sample_parameter(params_by_name["canti_thickness"])
values["arm_cutoff_width"] = sample_parameter(None, 0.0, values["canti_width"] / 2.5)
values["arm_cutoff_length"] = sample_parameter(None, 10.0, values["canti_length"])
if random.random() > 0.5:
values["arm_slot"] = True
values["arm_slot_width"] = sample_parameter(
None,
(values["canti_width"] - 2 * values["arm_cutoff_width"]) * 0.2,
(values["canti_width"] - 2 * values["arm_cutoff_width"]) * 0.25,
)
values["port_geometry"] = random.randint(49153, 59999)
values["port_mesh"] = random.randint(49153, 59999)
values["port_mapdl"] = random.randint(49153, 59999)
values["clean_venv"] = True
return values
if __name__ == "__main__":
# Verify that ansys-hps-client version is > 0.10.1 to ensure support for unmapped parameters
if version.parse(ansys.hps.client.__version__) <= version.parse("0.10.1"):
print(
f"ERR: ansys-hps-client version {ansys.hps.client.__version__} \
does not satisfy '>0.10.1'"
)
sys.exit(1)
typer.run(entrypoint)
Here is the exec_script.py
script:
"""Simplistic execution script for Python with uv.
Command formed: uv run <script_file> <input_file>
"""
import json
import os
import shutil
from ansys.rep.common.logging import log
from ansys.rep.evaluator.task_manager import ApplicationExecution
class PythonExecution(ApplicationExecution):
def execute(self):
log.info("Start uv execution script")
# Identify files
script_file = next(
(f for f in self.context.input_files if f["name"].startswith("eval_")), None
)
assert script_file, "Python script file missing"
input_filename = "input_parameters.json"
output_filename = "output_parameters.json"
# Use assigned resources
self.context.parameter_values["num_cores"] = self.context.resource_requirements["num_cores"]
self.context.parameter_values["memory_b"] = self.context.resource_requirements["memory"]
# Pass parameters
with open(input_filename, "w") as in_file:
json.dump(self.context.parameter_values, in_file, indent=4)
# Identify applications
app_uv = next((a for a in self.context.software if a["name"] == "uv"), None)
# app_xvfb = next((a for a in self.context.software if a["name"] == "Xvfb-run"), None)
assert app_uv, "Cannot find app uv"
# Add " around exe if needed for Windows
exes = {"uv": app_uv["executable"]}
for k, v in exes.items():
if " " in v and not v.startswith('"'):
exes[k] = f'"{v}"' # noqa
# Invoke xvfb if available
# if app_xvfb != None:
# exes['uv'] = "pkill Xvfb; " + app_xvfb['executable'] + " " + exes['uv']
# Use properties from resource requirements
# None currently
# Pass env vars correctly
env = dict(os.environ)
env.update(self.context.environment)
## Run evaluation script
cmd = f"{exes['uv']} run {script_file['path']} {input_filename}"
self.run_and_capture_output(cmd, shell=True, env=env)
# Extract parameters if needed
try:
log.debug(f"Loading output parameters from {output_filename}")
with open(output_filename) as out_file:
output_parameters = json.load(out_file)
self.context.parameter_values.update(output_parameters)
log.debug(f"Loaded output parameters: {output_parameters}")
except Exception as ex:
log.info("No output parameters found.")
log.debug(f"Failed to read output parameters from file: {ex}")
# Clean up venv cache
if "exe" in output_parameters.keys() and self.context.parameter_values.get("clean_venv"):
try:
venv_cache = os.path.abspath(os.path.join(output_parameters["exe"], "..", ".."))
venv_cache_parent = os.path.join(venv_cache, "..")
if os.path.exists(venv_cache):
log.debug(f"Current venv cache: {os.listdir(venv_cache_parent)}")
log.debug(f"Cleaning venv cache at {venv_cache}...")
shutil.rmtree(venv_cache)
else:
log.debug(f"Venv cache path {venv_cache} does not exist.")
except Exception as ex:
log.debug(f"Couldn't clean venv cache at {venv_cache}: {ex}")
log.info("End Python execution script")
Here is the eval_combined.py
script for combined evaluation:
# /// script
# requires-python = "==3.10"
# dependencies = [
# "ansys-geometry-core[all]",
# "ansys-meshing-prime[all]==0.7",
# "ansys.mapdl.core",
# "matplotlib"
# ]
# ///
import json
import os
import sys
import matplotlib.patches as patches
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import ansys.meshing.prime as prime
from ansys.geometry.core import launch_modeler
from ansys.geometry.core.designer import DesignFileFormat
from ansys.geometry.core.math import Point2D
from ansys.geometry.core.sketch import Sketch
from ansys.mapdl.core import launch_mapdl
from ansys.meshing.prime.graphics import PrimePlotter
def geometry(params):
# Read parameters, convert to meters
um2m = 1e-6
width = params["canti_width"] * um2m
length = params["canti_length"] * um2m
thickness = params["canti_thickness"] * um2m
arm_cutoff_width = params["arm_cutoff_width"] * um2m
arm_cutoff_length = params["arm_cutoff_length"] * um2m
arm_slot = params["arm_slot"]
arm_slot_width = params["arm_slot_width"] * um2m
popup_plots = params["popup_plots"]
port = params["port_geometry"]
arm_width = width - 2 * arm_cutoff_width
# Check input
if (
arm_cutoff_length > length
or arm_width <= 0.0
or (arm_slot and arm_slot_width > arm_width)
or (arm_slot and arm_slot_width > width)
):
print("SanityError: Cantilever dimensions are not sane.")
# Draw Cantilever in 2D Sketch
canti_sketch = Sketch()
canti_sketch.segment(
start=Point2D([0.0, -arm_width / 2.0]), end=Point2D([0.0, arm_width / 2.0])
)
canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, arm_width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([length, width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([length, -width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, -width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([arm_cutoff_length, -arm_width / 2.0]))
canti_sketch.segment_to_point(end=Point2D([0.0, -arm_width / 2.0]))
# Add arm slot if it exists
if arm_slot:
canti_sketch.box(
Point2D([arm_cutoff_length / 2.0 + 2.5e-6, 0.0]),
arm_cutoff_length - 5e-6,
arm_slot_width,
)
# Create a modeler, extrude sketches, union bodies
try:
modeler = launch_modeler(port=port)
print(modeler)
design = modeler.create_design("cantilever")
design.extrude_sketch("cantilever", canti_sketch, thickness)
# Plot if requested
if popup_plots:
design.plot()
# Draw matplotlib figure
points = [
[0.0, -arm_width / 2.0],
[0.0, arm_width / 2.0],
[arm_cutoff_length, arm_width / 2.0],
[arm_cutoff_length, width / 2.0],
[length, width / 2.0],
[length, -width / 2.0],
[arm_cutoff_length, -width / 2.0],
[arm_cutoff_length, -arm_width / 2.0],
[0.0, -arm_width / 2.0],
]
xs, ys = zip(*points, strict=False)
fig, ax = plt.subplots()
ax.fill(xs, ys, color="green", alpha=1, edgecolor="black", linewidth=0.3)
if arm_slot:
slot_points = [
[2.5e-6, arm_slot_width / 2.0],
[arm_cutoff_length - 2.5e-6, arm_slot_width / 2.0],
[arm_cutoff_length - 2.5e-6, -arm_slot_width / 2.0],
[2.5e-6, -arm_slot_width / 2.0],
]
slot_xs, slot_ys = zip(*slot_points, strict=False)
ax.fill(slot_xs, slot_ys, color="white", alpha=1, edgecolor="black", linewidth=0.3)
wall = patches.Rectangle(
xy=(-length, -max(width, length)),
width=length,
height=2 * max(width, length),
linewidth=0.3,
edgecolor="black",
facecolor="#B16100",
hatch="///",
alpha=1.0,
)
ax.add_patch(wall)
ax.set_aspect("equal", adjustable="box")
ax_length = 0.05 * length + 1.1 * max(length, width)
ax.set_xlim(-0.05 * length, 1.1 * max(length, width))
ax.set_ylim(-ax_length / 2.0, ax_length / 2.0)
ax.set_title("Cantilever")
formatter = FuncFormatter(lambda x, _: f"{x * 1e6:.1f}")
ax.xaxis.set_major_formatter(formatter)
ax.yaxis.set_major_formatter(formatter)
ax.set_xlabel("x [um]")
ax.set_ylabel("y [um]")
plt.savefig("canti_plot.png", dpi=500)
# export has different extensions on different OSs (.x_t, .xmt_txt)
# design.export_to_parasolid_text(os.getcwd())
design.download(
os.path.join(os.getcwd(), design.name + ".x_t"), DesignFileFormat.PARASOLID_TEXT
)
except Exception as e:
print(f"Exception in geometry: {e}")
finally:
modeler.exit()
def mesh(params, ansys_prime_root):
# Read relevant dimensions from parameters
um2mm = 1e-3
swept_layers = params["mesh_swept_layers"]
thickness = params["canti_thickness"] * um2mm
length = min(params["canti_length"], params["arm_cutoff_length"]) * um2mm
width = (params["canti_width"] - 2 * params["arm_cutoff_width"]) * um2mm
if params["arm_slot"]:
width -= params["arm_slot_width"] * um2mm
width /= 2.0
min_size = min(length, width) * 0.05
max_size = min(length, width) * 0.3
popup_plots = params["popup_plots"]
port = params["port_mesh"]
cad_file = "cantilever.x_t"
if not os.path.isfile(cad_file):
print(f"ERROR: Input file {cad_file} does not exist.")
return 1
with prime.launch_prime(prime_root=ansys_prime_root, port=port) as prime_client:
model = prime_client.model
mesh_util = prime.lucid.Mesh(model=model)
# Import geometry
mesh_util.read(file_name=cad_file, cad_reader_route=prime.CadReaderRoute.PROGRAMCONTROLLED)
# Set mesh size
sizing_params = prime.GlobalSizingParams(model=model, min=min_size, max=max_size)
model.set_global_sizing_params(params=sizing_params)
part = model.parts[0]
sweeper = prime.VolumeSweeper(model)
stacker_params = prime.MeshStackerParams(
model=model,
direction=[0, 0, 1],
max_offset_size=thickness / swept_layers,
delete_base=True,
)
# Create the base face
createbase_results = sweeper.create_base_face(
part_id=part.id,
topo_volume_ids=part.get_topo_volumes(),
params=stacker_params,
)
base_faces = createbase_results.base_face_ids
part.add_labels_on_topo_entities(["base_faces"], base_faces)
if popup_plots:
scope = prime.ScopeDefinition(model=model, label_expression="base_faces")
display = PrimePlotter()
display.plot(model, scope=scope)
display.show()
base_scope = prime.lucid.SurfaceScope(
entity_expression="base_faces",
part_expression=part.name,
scope_evaluation_type=prime.ScopeEvaluationType.LABELS,
)
# Create mesh on base face
mesh_util.surface_mesh(
min_size=min_size, max_size=max_size, scope=base_scope, generate_quads=True
)
if popup_plots:
display = PrimePlotter()
display.plot(model, scope=scope, update=True)
display.show()
# Sweep base mesh through body
sweeper.stack_base_face(
part_id=part.id,
base_face_ids=base_faces,
topo_volume_ids=part.get_topo_volumes(),
params=stacker_params,
)
if popup_plots:
display = PrimePlotter()
display.plot(model, update=True)
display.show()
mesh_file = "cantilever.cdb"
mesh_util.write(os.path.join(os.getcwd(), mesh_file))
def extract_frequencies(output_string, num_freqs):
mode_freqs = {}
lines = output_string.split("\n")
for line in lines:
split = line.split()
if len(split) < 2 or len(split[0]) == 0:
continue
print(split)
if split[0][0].isdigit():
index = int(split[0])
mode_freqs[f"freq_mode_{index}"] = float(split[1])
if index == num_freqs:
break
return mode_freqs
def mapdl(params):
# Read Parameters
num_modes = params["num_modes"]
young_modulus = params["young_modulus"]
density = params["density"]
poisson_ratio = params["poisson_ratio"]
popup_plots = params["popup_plots"]
port = params["port_mapdl"]
num_cores = params["num_cores"]
memory_mb = params["memory_b"] / 1024.0**2
input_filename = "cantilever.cdb"
if not os.path.isfile(input_filename):
print(f"ERROR: Input file {input_filename} does not exist.")
return 1
try:
# Launch MAPDL as a service
mapdl = launch_mapdl(
start_instance=True,
mode="grpc",
loglevel="DEBUG",
run_location=f"{os.getcwd()}",
port=port,
nproc=num_cores,
ram=memory_mb,
running_on_hpc=False,
)
print(mapdl)
mapdl.mute = True
mapdl.clear()
# Load input file
mapdl.input("cantilever.cdb")
mapdl.prep7()
# Define material properties
mapdl.mp("EX", 1, young_modulus)
mapdl.mp("EY", 1, young_modulus)
mapdl.mp("EZ", 1, young_modulus)
mapdl.mp("PRXY", 1, poisson_ratio)
mapdl.mp("PRYZ", 1, poisson_ratio)
mapdl.mp("PRXZ", 1, poisson_ratio)
mapdl.mp("DENS", 1, density)
# Apply prestress
mapdl.allsel()
mapdl.inistate("DEFINE", val5=100.0e6, val6=100.0e6, val7=0.0)
# Apply cantilever boundary conditions
mapdl.allsel()
mapdl.emodif("ALL", "MAT", i1=1)
mapdl.nsel("S", "LOC", "X", 0)
print(f"Fixing {len(mapdl.get_array('NODE', item1='NLIST'))} nodes")
mapdl.d("ALL", "ALL")
# Set keyopt properties
mapdl.allsel()
mapdl.etlist()
element_type_id = int(mapdl.get("ETYPE", "ELEM", "1", "ATTR", "TYPE"))
mapdl.keyopt(f"{element_type_id}", "2", "3", verbose=True)
# Solve modal
mapdl.slashsolu()
mapdl.antype("MODAL")
mapdl.modopt("LANB", num_modes)
mapdl.mxpand(num_modes)
output = mapdl.solve(verbose=False)
# Extract calculated Eigenfrequencies
mapdl.post1()
output = mapdl.set("LIST", mute=False)
mode_freqs = extract_frequencies(output, 20)
if popup_plots:
mode_num = 1
mapdl.set(1, mode_num)
mapdl.plnsol("u", "sum")
mode_freqs["exe"] = sys.executable
with open("output_parameters.json", "w") as out_file:
json.dump(mode_freqs, out_file, indent=4)
except Exception as e:
print(f"Exception in mapdl: {e}")
finally:
mapdl.exit()
if __name__ == "__main__":
print(f"python exe: {sys.executable}")
# Fetch parameters
input_file_name = sys.argv[1]
input_file_path = os.path.abspath(input_file_name)
with open(input_file_path) as input_file:
params = json.load(input_file)
ansys_prime_root = os.environ.get("ANSYS_PRIME_ROOT", None)
# Run program step by step
print("===Designing Geometry")
geometry(params)
print("===Drawing Mesh")
mesh(params, ansys_prime_root)
print("===Computing Eigenfrequencies")
mapdl(params)
Download the ZIP file for the PyAnsys cantilever example and use a tool such as 7-Zip to extract the files.