Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d97cdfd
Added project setup
engpas Jun 25, 2025
6b1742f
Add evaluation and execution scripts
engpas Jun 25, 2025
c6559d2
Added README
engpas Jun 26, 2025
fd2ac0d
Fixed UV header
engpas Jun 26, 2025
ba06f36
Added metadata header specification
engpas Jun 26, 2025
ef604bd
Better way to run
engpas Jun 26, 2025
7199b9b
Added metadata header, run setup script via uv
engpas Jun 26, 2025
5829492
Added axis labels
engpas Jun 26, 2025
c5ddf34
Replace UV/Uv with lowercase uv
engpas Jun 30, 2025
19f2a72
Link uv on first mention
engpas Jun 30, 2025
e9a367c
cleanup
engpas Jun 30, 2025
d91dcd8
fix typo
engpas Jun 30, 2025
41c7c01
By default, don't clean up
engpas Jun 30, 2025
acaaf56
No venv cleanup
engpas Jun 30, 2025
627dfda
Remove mentions of pcluster, refactor cache explanation
engpas Jun 30, 2025
9598196
Make more consistent
engpas Jun 30, 2025
92f914f
Fixed command
engpas Jun 30, 2025
c4668b8
Title
engpas Jun 30, 2025
b0bdb22
Move to .rst
engpas Jun 30, 2025
8e3dd54
Move to .rst
engpas Jun 30, 2025
433c5d0
Added new example doc
engpas Jun 30, 2025
0d02bda
remove bullets
engpas Jun 30, 2025
2be533c
Fix some vale complaints
engpas Jul 7, 2025
da8964d
Merge branch 'main' into pengeler/python_uv_example
engpas Jul 7, 2025
c5fa60f
Added matplotlib, numpy, uv to the vocabulary
engpas Jul 7, 2025
e221cbb
More vale consistency
engpas Jul 7, 2025
037f8fe
Merge branch 'pengeler/python_uv_example' of https://github.com/ansys…
engpas Jul 7, 2025
1ac9deb
Maybe it likes air-gapped?
engpas Jul 7, 2025
35fee73
Application is just so much better than app...
engpas Jul 7, 2025
12c7d2f
Add footer, fix header
engpas Jul 7, 2025
2f9433f
Fix dash conversion issue
engpas Jul 7, 2025
306d687
Fix tables
engpas Jul 7, 2025
eb58dbf
python->Python
engpas Jul 7, 2025
0f92354
Better table style
engpas Jul 8, 2025
11c03c2
Merge branch 'main' into pengeler/python_uv_example
engpas Jul 30, 2025
dddb0a7
Add venv cleanup back with explanation of purpose
engpas Jul 30, 2025
25a2f42
Add back exe path communication with explanation, but commented out b…
engpas Jul 30, 2025
5f49439
Install pyhps from pypi
engpas Jul 30, 2025
a802c57
Register python_uv example
engpas Jul 30, 2025
c51c84b
Fix title underline length, cli args are code
engpas Jul 30, 2025
94cd742
More dangerous comment
engpas Jul 30, 2025
98c5973
Fix formatting
engpas Jul 30, 2025
bca0c1e
Merge branch 'main' into pengeler/python_uv_example
engpas Jul 30, 2025
5ecbf51
No minimum version needed
engpas Jul 30, 2025
6842ec3
Merge branch 'pengeler/python_uv_example' of https://github.com/ansys…
engpas Jul 30, 2025
89a2de3
Fix uv requirements
engpas Jul 31, 2025
fa72cad
Added imports needed for venv cleanup
engpas Jul 31, 2025
d46f9db
Apply suggestions from code review
engpas Aug 11, 2025
5bb244b
Rewrite sentence as suggested
engpas Aug 11, 2025
fee1deb
Add venv to vocabulary
engpas Aug 11, 2025
9998988
Merge branch 'main' into pengeler/python_uv_example
engpas Aug 11, 2025
592b8ee
Add indentation to trigger code block
engpas Aug 12, 2025
6256609
Merge branch 'pengeler/python_uv_example' of https://github.com/ansys…
engpas Aug 12, 2025
54184bb
Merge branch 'main' into pengeler/python_uv_example
engpas Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"StaticMixer_001.cfx",
"StaticMixer_001.def",
],
"python_uv": ["project_setup.py", "exec_script.py", "eval.py"],
}

sys.path.append(os.path.abspath(os.path.dirname(__file__)))
Expand Down
171 changes: 171 additions & 0 deletions doc/source/examples/ex_python_uv.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
.. _example_python_uv:

.. note::

Go to the `bottom of this page`_ to download the ZIP file for the uv example.

Run arbitrary Python scripts on HPS
===================================

This example shows how to run arbitrary Python scripts. It uses
the `uv <https://docs.astral.sh/uv/>`__ package to generate the required
environments on the fly.

The example sets up a project that plots ``sin(x)`` using NumPy and
Matplotlib and then saves the figure to a file.

The metadata header present in the ``eval.py`` script, which defines the
dependencies, enables uv to take care of the environment setup:

.. code:: python

# /// script
# requires-python = "==3.12"
# dependencies = [
# "numpy",
# "matplotlib"
# ]
# ///

For more information, see `Inline script metadata
<https://packaging.python.org/en/latest/specifications/inline-script-metadata/#inline-script-metadata>`__
in the *Python Packaging User Guide* and `Running a script with dependencies <https://docs.astral.sh/uv/guides/scripts/#running-a-script-with-dependencies>`__
in the uv documentation.

Prerequisites
=============

For the example to run, uv must be installed and registered
on the scaler/evaluator. For installation instructions, see
`Installing uv <https://docs.astral.sh/uv/getting-started/installation/>`__
in the uv documentation.

Once uv is installed, the package must be registered in the
scaler/evaluator with the following properties:

.. list-table::
:header-rows: 1

* - Property
- Value
* - Name
- uv
* - Version
- 0.7.19
* - Installation Path
- /path/to/uv
* - Executable
- /path/to/uv/bin/uv

Be sure to adjust the version to the one you have installed.

Define a custom cache directory
-------------------------------

The preceding steps set up uv with the cache located in its default location
in the user home directory (~/.cache/uv). Depending on your
situation, you might prefer a different cache location, such as a shared
directory accessible to all evaluators. To define a custom uv
cache directory, add the following environment variable to the
uv package registration in the scaler/evaluator:

.. list-table::
:header-rows: 1

* - Environment Variable
- Value
* - UV_CACHE_DIR
- /path/to/custom/uv/cache/dir

Create offline air-gapped setups
--------------------------------

If internet is not available, you can create offline air-gapped setups for uv
using one of these options:

* Pre-populate the uv cache with all desired dependencies.
* Provide a local Python package index and set uv to use it. For
more information, see
`Package indexes <https://docs.astral.sh/uv/configuration/indexes/>`__
in the uv documentation. This index can then sit in a shared location,
with node-local caching applied.
* Use pre-generated virtual environments. For more information, see
`uv venv <https://docs.astral.sh/uv/reference/cli/#uv-venv>`__ in the
uv documentation.

To turn off network access, you can either set the
``UV_OFFLINE`` environment variable or use the ``--offline`` flag with
many uv commands.

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 generates a ``plot.png`` file.

Options
-------

The example supports the following command line arguments:

.. list-table::
:header-rows: 1

* - Flag
- Example
- Description
* - ``-U``, ``--url``
- ``--url=https://localhost:8443/hps``
- URL of the target HPS instance
* - ``-u``, ``--username``
- ``--username=repuser``
- Username to log into HPS
* - ``-p``, ``--password``
- ``--password=topSecret``
- Password to log into HPS
* - ``-j``, ``--num-jobs``
- ``--num-jobs=10``
- Number of jobs to generate

Files
-----

Descriptions follow of the relevant example files.

The project creation script, ``project_setup.py``, handles all communication with the
HPS instance, defines the project, and generates the jobs.

.. literalinclude:: ../../../examples/python_uv/project_setup.py
:language: python
:lines: 23-
:caption: project_setup.py

The script ``eval.py``, which is evaluated on HPS, contains the code to plot a sine and then save
the figure.

.. literalinclude:: ../../../examples/python_uv/eval.py
:language: python
:lines: 23-
:caption: eval.py

The execution script, ``exec_script.py``, uses uv to run the evaluation script.

.. literalinclude:: ../../../examples/python_uv/exec_script.py
:language: python
:lines: 23-
:caption: exec_script.py

Download the ZIP file for the uv example and use
a tool such as 7-Zip to extract the files.

.. _bottom of this page:

.. button-link:: ../_downloads/python_uv.zip
:color: black
:expand:

Download ZIP file
5 changes: 4 additions & 1 deletion doc/source/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ one :download:`ZIP file <../../../build/pyhps_examples.zip>`.
ex_fluent_nozzle
ex_cfx_static_mixer
ex_python_two_bar
ex_python_uv

.. list-table::
:header-rows: 1
Expand All @@ -59,9 +60,11 @@ one :download:`ZIP file <../../../build/pyhps_examples.zip>`.
- Submit a CFX solve job to the HPS server using an execution script.
* - :ref:`example_python_two_bar`
- Create an HPS project that solves a two-bar truss problem with Python.
* - :ref:`example_python_uv`
- Run arbitrary Python scripts on HPS using uv.

A link to download the required resources is available on each example page. If desired,
you can download the required resources for all examples through the link below.
you can download the required resources for all examples using the following link.

.. _bottom of this page:

Expand Down
4 changes: 4 additions & 0 deletions doc/styles/config/vocabularies/ANSYS/accept.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ isort
[Kk]eycloak
Koenig
marshmallow_oneofschema
Matplotlib
NumPy
Mises
postprocess
[Pp]ydantic
pytest
subpackage
uv
venv
von
Wintermantel
50 changes: 50 additions & 0 deletions examples/python_uv/eval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# /// script
# requires-python = "==3.12"
# dependencies = [
# "numpy",
# "matplotlib"
# ]
# ///


import matplotlib.pyplot as plt
import numpy as np

if __name__ == "__main__":
# Generate plot
ts = np.linspace(0.0, 10.0, 100)
ys = np.sin(ts)

fig, ax = plt.subplots()
ax.plot(ts, ys)
ax.set_xlabel("Time [s]")
ax.set_ylabel("Displacement [cm]")
plt.savefig("plot.png", dpi=200)

# Uncomment to enable venv cleanup in exec script, see execution script for details
# import json
# import sys
# with open("output_parameters.json", "w") as out_file:
# json.dump({"exe": sys.executable}, out_file, indent=4)
88 changes: 88 additions & 0 deletions examples/python_uv/exec_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Simple execution script for Python with uv.

Command formed: uv run <script_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"] == "eval"), None)
assert script_file, "Python script file missing"
output_filename = "output_parameters.json"

# Identify applications
app_uv = next((a for a in self.context.software if a["name"] == "uv"), 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

# Pass env vars correctly
env = dict(os.environ)
env.update(self.context.environment)

## Run evaluation script
cmd = f"{exes['uv']} run {script_file['path']}"
self.run_and_capture_output(cmd, shell=True, env=env)

# Read eval.py output parameters
output_parameters = {}
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}")

# If exe path is in out params, delete the venv folder to avoid runaway uv venv cache
# See https://github.com/astral-sh/uv/issues/13431
if "exe" in output_parameters.keys():
try:
venv_cache = os.path.abspath(os.path.join(output_parameters["exe"], "..", ".."))
if os.path.exists(venv_cache):
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")
Loading
Loading