Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/replace_scheme_eval_calls
Browse files Browse the repository at this point in the history
hpohekar authored Nov 26, 2024

Verified

This commit was signed with the committer’s verified signature.
2 parents 6f9c4ea + 18084aa commit f58fbae
Showing 7 changed files with 163 additions and 99 deletions.
180 changes: 88 additions & 92 deletions doc/source/user_guide/fields/field_data.rst
Original file line number Diff line number Diff line change
@@ -5,31 +5,24 @@
Field data
==========

You can use field data objects to access Fluent surface, scalar, vector, and
pathlines data.
You can use field data objects to access Fluent surface, scalar, vector, and pathlines data.

Accessing field data objects
----------------------------

In order to access field data, launch the fluent solver, and make field data
available (for example, either by reading a case file and then initializing as in the following code, or
by reading case and data files).
To work with field data, ensure the Fluent solver is launched and the relevant data is made available.
You can do this either by loading both case and data files or by reading a case file and initializing.

.. code-block:: python
>>> import ansys.fluent.core as pyfluent
>>> from ansys.fluent.core import examples
>>> import_file_name = examples.download_file("mixing_elbow.msh.h5", "pyfluent/mixing_elbow")
>>> from ansys.fluent.core.examples import download_file
>>> solver = pyfluent.launch_fluent(mode=pyfluent.FluentMode.SOLVER)
>>> solver.settings.file.read(file_type="case", file_name=import_file_name)
>>> solver.settings.solution.initialization.hybrid_initialize()
The field data object is an attribute of the :obj:`~ansys.fluent.core.session_solver.Solver` object:

.. code-block:: python
>>> field_data = solver.fields.field_data
>>> case_path = download_file(file_name="exhaust_system.cas.h5", directory="pyfluent/exhaust_system")
>>> data_path = download_file(file_name="exhaust_system.dat.h5", directory="pyfluent/exhaust_system")
>>> solver.settings.file.read_case_data(file_name=case_path)
>>> field_data = solver.fields.field_data # This creates an instance of the FieldData class.
Simple requests
---------------
@@ -38,8 +31,8 @@ Here are the methods for requesting each type of field:

- ``get_surface_data`` for surface data.
- ``get_scalar_field_data`` for scalar field data.
- ``get_vector_field_data`` for vector field data
- ``get_pathlines_field_data`` for vector field data
- ``get_vector_field_data`` for vector field data.
- ``get_pathlines_field_data`` for pathlines field data.

Get surface data
~~~~~~~~~~~~~~~~
@@ -50,89 +43,109 @@ the ``get_surface_data`` method and specifying ``Vertices`` for ``data_types``.
>>> from ansys.fluent.core.services.field_data import SurfaceDataType
>>> vertices_data = field_data.get_surface_data(surfaces=["cold-inlet"], data_types=[SurfaceDataType.Vertices])
>>> vertices_data["cold-inlet"][SurfaceDataType.Vertices].shape
(241, 3)
>>> vertices_data["cold-inlet"][SurfaceDataType.Vertices][5]
array([-0.2 , -0.10167995, 0.00362008], dtype=float32)
>>> vertices_data = field_data.get_surface_data(
>>> surfaces=["inlet"], data_types=[SurfaceDataType.Vertices]
>>> )
# The method retrieves surface vertex coordinates as a NumPy array.
# Shape: (389, 3) - This means 389 vertices, each defined by 3 coordinates (x, y, z).
>>> vertices_data["inlet"][SurfaceDataType.Vertices].shape
(389, 3)
>>> vertices_data["inlet"][SurfaceDataType.Vertices][5]
# Example: The 6th vertex (zero-based indexing) has coordinates [-0.3469, 0.0, -0.0386].
array([-0.34689394, 0. , -0.03863097], dtype=float32)
You can call the same method to get the corresponding surface face normals and centroids.
For ``data_types``, specifying ``FacesNormal`` and ``FacesCentroid`` respectively.
You can call the same method to get the corresponding surface face normals and centroids
by specifying ``FacesNormal`` and ``FacesCentroid`` for ``data_types`` respectively.

.. code-block:: python
>>> faces_normal_and_centroid_data = field_data.get_surface_data(
>>> data_types=[SurfaceDataType.FacesNormal, SurfaceDataType.FacesCentroid], surfaces=["cold-inlet"]
>>> data_types=[SurfaceDataType.FacesNormal, SurfaceDataType.FacesCentroid],
>>> surfaces=["inlet"]
>>> )
>>> faces_normal_and_centroid_data["cold-inlet"][SurfaceDataType.FacesNormal].shape
(152, 3)
>>> faces_normal_and_centroid_data["cold-inlet"][SurfaceDataType.FacesCentroid][15]
array([-0.2 , -0.11418786, 0.03345207], dtype=float32)
# FacesNormal shape: (262, 3) - 262 face normals, each with 3 components (x, y, z).
# FacesCentroid shape: (262, 3) - Centroids for each of the 262 faces, given as (x, y, z).
>>> faces_normal_and_centroid_data["inlet"][SurfaceDataType.FacesNormal].shape
(262, 3)
>>> faces_normal_and_centroid_data["inlet"][SurfaceDataType.FacesCentroid][15]
# Example: The centroid of the 16th face has coordinates [-0.3463, 0.0, -0.0328].
array([-0.34634298, 0. , -0.03276413], dtype=float32)
You can request face connectivity data for given ``surfaces`` by calling
the ``get_surface_data`` method and specifying ``FacesConnectivity`` for ``data_types``.

.. code-block:: python
>>> faces_connectivity_data = field_data.get_surface_data(
>>> data_types=[SurfaceDataType.FacesConnectivity], surfaces=["cold-inlet"]
>>> data_types=[SurfaceDataType.FacesConnectivity], surfaces=["inlet"]
>>> )
>>> faces_connectivity_data["cold-inlet"][SurfaceDataType.FacesConnectivity][5]
array([12, 13, 17, 16])
# FacesConnectivity provides indices of vertices for each face. For example:
# Face 6 is connected to vertices 4, 5, 12, and 11.
>>> faces_connectivity_data["inlet"][SurfaceDataType.FacesConnectivity][5]
array([ 4, 5, 12, 11])
Get scalar field data
~~~~~~~~~~~~~~~~~~~~~
You can call the ``get_scalar_field_data`` method to get scalar field data, such as absolute pressure:

.. code-block:: python
>>> abs_press_data = field_data.get_scalar_field_data(field_name="absolute-pressure", surfaces=["cold-inlet"])
>>> abs_press_data["cold-inlet"].shape
(241,)
>>> abs_press_data["cold-inlet"][120]
101325.0
>>> abs_press_data = field_data.get_scalar_field_data(
>>> field_name="absolute-pressure", surfaces=["inlet"]
>>> )
# Shape: (389,) - A single scalar value (e.g., pressure) for each of the 389 vertices.
>>> abs_press_data["inlet"].shape
(389,)
>>> abs_press_data["inlet"][120]
# Example: The absolute pressure at the 121st vertex is 102031.4 Pascals.
102031.4
Get vector field data
~~~~~~~~~~~~~~~~~~~~~
You can call the ``get_vector_field_data`` method to get vector field data.

.. code-block:: python
>>> velocity_vector_data = field_data.get_vector_field_data(field_name="velocity", surfaces=["cold-inlet"])
>>> velocity_vector_data["cold-inlet"].shape
(152, 3)
>>> velocity_vector_data = field_data.get_vector_field_data(field_name="velocity", surfaces=["inlet", "inlet1"])
# Shape: (262, 3) - Velocity vectors for 262 faces, each with components (vx, vy, vz) for 'inlet'.
>>> velocity_vector_data["inlet"].shape
(262, 3)
# Shape: (265, 3) - Velocity vectors for 265 faces, each with components (vx, vy, vz) for 'inlet1'.
>>> velocity_vector_data["inlet1"].shape
(265, 3)
Get pathlines field data
~~~~~~~~~~~~~~~~~~~~~~~~
You can call the ``get_pathlines_field_data`` method to get pathlines field data.

.. code-block:: python
>>> path_lines_data = field_data.get_pathlines_field_data(field_name="velocity", surfaces=["cold-inlet"])
>>> path_lines_data["cold-inlet"]["vertices"].shape
(76152, 3)
>>> len(path_lines_data["cold-inlet"]["lines"])
76000
>>> path_lines_data["cold-inlet"]["velocity"].shape
(76152, )
>>> path_lines_data["cold-inlet"]["lines"][100]
>>> path_lines_data = field_data.get_pathlines_field_data(
>>> field_name="velocity", surfaces=["inlet"]
>>> )
# Vertices shape: (29565, 3) - 29565 pathline points, each with coordinates (x, y, z).
# Lines: A list where each entry contains indices of vertices forming a pathline.
# Velocity shape: (29565,) - Scalar velocity values at each pathline point.
>>> path_lines_data["inlet"]["vertices"].shape
(29565, 3)
>>> len(path_lines_data["inlet"]["lines"])
29303
>>> path_lines_data["inlet"]["velocity"].shape
(29565,)
>>> path_lines_data["inlet"]["lines"][100]
# Example: Pathline 101 connects vertices 100 and 101.
array([100, 101])
Dictionary containing a map of surface IDs to the path-line data is returned.
For example, pathlines connectivity, vertices, and field.


Making multiple requests in a single transaction
------------------------------------------------
You can get data for multiple fields in a single transaction.

First create transaction object for field data.
First, create a transaction object for field data.

.. code-block:: python
>>> transaction = solver.fields.field_data.new_transaction()
# This creates a new transaction object for batching multiple requests.
Then combine requests for multiple fields using ``add_<items>_request`` methods in a single transaction:

@@ -141,55 +154,39 @@ Then combine requests for multiple fields using ``add_<items>_request`` methods
- ``add_vector_fields_request`` adds a vector fields request.
- ``add_pathlines_fields_request`` adds a pathlines fields request.

Following code demonstrate adding multiple requests to a single transaction.

.. code-block::
.. code-block:: python
>>> transaction.add_surfaces_request(
>>> surfaces=[1], data_types = [SurfaceDataType.Vertices, SurfaceDataType.FacesCentroid],
>>> )
>>> transaction.add_surfaces_request(
>>> surfaces=[2], data_types = [SurfaceDataType.Vertices, SurfaceDataType.FacesConnectivity],
>>> surfaces=[1], data_types=[SurfaceDataType.Vertices, SurfaceDataType.FacesCentroid]
>>> )
# Adds a request for surface data such as vertices and centroids.
>>> transaction.add_scalar_fields_request(
>>> surfaces=[1,2], field_name="pressure", node_value=True, boundary_value=True
>>> surfaces=[1, 2], field_name="pressure", node_value=True, boundary_value=True
>>> )
>>> transaction.add_vector_fields_request(surfaces=[1,2], field_name="velocity")
>>> transaction.add_pathlines_fields_request(surfaces=[1,2], field_name="temperature")
# Adds a request for scalar field data like pressure.
>>> transaction.add_vector_fields_request(
>>> surfaces=[1, 2], field_name="velocity"
>>> )
# Adds a request for vector field data like velocity.
You can call the ``get_fields`` method to get the data for all these requests. This call also
clears all requests, so that subsequent calls to the ``get_fields`` method yield nothing until
more requests are added.
You can call the ``get_fields`` method to execute the transaction and retrieve the data:

.. code-block::
.. code-block:: python
>>> payload_data = transaction.get_fields()
# Executes all requests and returns the combined field data.
``payload_data`` is a dictionary containing the requested fields as a numpy array in the following order:

``tag -> surface_id [int] -> field_name [str] -> field_data[np.array]``

.. note::
``get_fields`` call also clears all requests, so that subsequent calls to this method
yield nothing until more requests are added.


Tag
---

Fluent versions earlier than 2023 R1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A tag is int, generated by applying ``bitwise or`` on all tags for a request. Here is a list
of supported tags and their values:

* OVERSET_MESH: 1,
* ELEMENT_LOCATION: 2,
* NODE_LOCATION: 4,
* BOUNDARY_VALUES: 8,

For example, if you request the scalar field data for element location[2], in the
dictionary, ``tag`` is ``2``. Similarly, if you request the boundary values[8] for
node location[4], ``tag`` is ``(4|8)`` or 12.

Fluent versions 2023 R1 and later
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A tag is tuple of input, value pairs for which field data is generated.

For example, if you request the scalar field data for element location, in the
@@ -216,7 +213,6 @@ depending on the request arguments:
- ``centroid``, which contains face centroids
- ``face-normal``, which contains face normals


Scalar field request
~~~~~~~~~~~~~~~~~~~~
The response to a scalar field request contains a single field with the same
@@ -256,17 +252,17 @@ Some sample use cases are demonstrated below:
>>> field_data.get_scalar_field_data.field_name.allowed_values()
['abs-angular-coordinate', 'absolute-pressure', 'angular-coordinate',
'anisotropic-adaption-cells', 'axial-coordinate', 'axial-velocity',
'anisotropic-adaption-cells', 'aspect-ratio', 'axial-coordinate', 'axial-velocity',
'boundary-cell-dist', 'boundary-layer-cells', 'boundary-normal-dist', ...]
>>> transaction = field_data.new_transaction()
>>> transaction.add_scalar_fields_request.field_name.allowed_values()
['abs-angular-coordinate', 'absolute-pressure', 'angular-coordinate',
'anisotropic-adaption-cells', 'axial-coordinate', 'axial-velocity',
'anisotropic-adaption-cells', 'aspect-ratio', 'axial-coordinate', 'axial-velocity',
'boundary-cell-dist', 'boundary-layer-cells', 'boundary-normal-dist', ...]
>>> field_data.get_scalar_field_data.surface_name.allowed_values()
['cold-inlet', 'hot-inlet', 'outlet', 'symmetry-xyplane', 'wall-elbow', 'wall-inlet']
['in1', 'in2', 'in3', 'inlet', 'inlet1', 'inlet2', 'out1', 'outlet', 'solid_up:1', 'solid_up:1:830', 'solid_up:1:830-shadow']
>>> field_data.get_surface_data.surface_ids.allowed_values()
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
8 changes: 8 additions & 0 deletions src/ansys/fluent/core/__init__.py
Original file line number Diff line number Diff line change
@@ -133,3 +133,11 @@ def version_info() -> str:

# Whether to clear environment variables related to Fluent parallel mode
CLEAR_FLUENT_PARA_ENVS = False

# Set stdout of the launched Fluent process
# Valid values are same as subprocess.Popen's stdout argument
LAUNCH_FLUENT_STDOUT = None

# Set stderr of the launched Fluent process
# Valid values are same as subprocess.Popen's stderr argument
LAUNCH_FLUENT_STDERR = None
4 changes: 4 additions & 0 deletions src/ansys/fluent/core/launcher/launcher_utils.py
Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@ def _get_subprocess_kwargs_for_fluent(env: Dict[str, Any], argvals) -> Dict[str,
kwargs: Dict[str, Any] = {}
if is_slurm:
kwargs.update(stdout=subprocess.PIPE)
else:
kwargs.update(
stdout=pyfluent.LAUNCH_FLUENT_STDOUT, stderr=pyfluent.LAUNCH_FLUENT_STDERR
)
if is_windows():
kwargs.update(shell=True, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
11 changes: 8 additions & 3 deletions src/ansys/fluent/core/launcher/standalone_launcher.py
Original file line number Diff line number Diff line change
@@ -177,6 +177,8 @@ def __init__(
The allocated machines and core counts are queried from the scheduler environment and
passed to Fluent.
"""
import ansys.fluent.core as pyfluent

self.argvals, self.new_session = _get_argvals_and_session(locals().copy())
self.file_transfer_service = file_transfer_service
if os.getenv("PYFLUENT_SHOW_SERVER_GUI") == "1":
@@ -214,7 +216,9 @@ def __init__(
self.argvals["topy"], self.argvals["journal_file_names"]
)

if is_windows():
if is_windows() and not (
pyfluent.LAUNCH_FLUENT_STDOUT or pyfluent.LAUNCH_FLUENT_STDERR
):
# Using 'start.exe' is better; otherwise Fluent is more susceptible to bad termination attempts.
self._launch_cmd = 'start "" ' + self._launch_string
else:
@@ -231,7 +235,7 @@ def __call__(self):
try:
logger.debug(f"Launching Fluent with command: {self._launch_cmd}")

subprocess.Popen(self._launch_cmd, **self._kwargs)
process = subprocess.Popen(self._launch_cmd, **self._kwargs)

try:
_await_fluent_launch(
@@ -247,7 +251,7 @@ def __call__(self):
logger.warning(
f"Retrying Fluent launch with less robust command: {launch_cmd}"
)
subprocess.Popen(launch_cmd, **self._kwargs)
process = subprocess.Popen(launch_cmd, **self._kwargs)
_await_fluent_launch(
self._server_info_file_name,
self.argvals["start_timeout"],
@@ -264,6 +268,7 @@ def __call__(self):
launcher_args=self.argvals,
inside_container=False,
)
session._process = process
start_watchdog = _confirm_watchdog_start(
self.argvals["start_watchdog"],
self.argvals["cleanup_on_exit"],
15 changes: 14 additions & 1 deletion src/ansys/fluent/core/search.py
Original file line number Diff line number Diff line change
@@ -163,11 +163,24 @@ def _print_search_results(queries: list, api_tree_data: dict):
results = []
api_tree_data = api_tree_data if api_tree_data else _get_api_tree_data()
api_tree_datas = [api_tree_data["api_objects"], api_tree_data["api_tui_objects"]]
for api_tree_data in api_tree_datas:

def _get_results(api_tree_data):
results = []
for query in queries:
for api_object in api_tree_data:
if api_object.split()[0].endswith(query):
results.append(api_object)
return results

settings_results = _get_results(api_tree_datas[0])
tui_results = _get_results(api_tree_datas[1])

settings_results.sort()
tui_results.sort()

results.extend(settings_results)
results.extend(tui_results)

if pyfluent.PRINT_SEARCH_RESULTS:
for result in results:
print(result)
6 changes: 3 additions & 3 deletions src/ansys/fluent/core/solver/flobject.py
Original file line number Diff line number Diff line change
@@ -1055,7 +1055,7 @@ def to_python_keys(cls, value):
ret[mname] = ccls.to_python_keys(mvalue)
return ret
else:
return value
return {}

_child_classes = {}
child_names = []
@@ -1325,7 +1325,7 @@ def to_python_keys(cls, value):
ret[k] = cls.child_object_type.to_python_keys(v)
return ret
else:
return value
return {}

_child_classes = {}
command_names = []
@@ -1531,7 +1531,7 @@ def to_python_keys(cls, value):
if isinstance(value, collections.abc.Sequence):
return [cls.child_object_type.to_python_keys(v) for v in value]
else:
return value
return []

_child_classes = {}
command_names = []
38 changes: 38 additions & 0 deletions tests/test_settings_api.py
Original file line number Diff line number Diff line change
@@ -377,6 +377,44 @@ def test_deprecated_settings_with_settings_api_aliases(mixing_elbow_case_data_se
"minimum": -0.0001,
"maximum": 0.0001,
}
solver.settings.results.graphics.contour["temperature"] = {}
solver.settings.results.graphics.contour["temperature"] = {
"field": "temperature",
"surfaces_list": "wall*",
"color_map": {
"visible": True,
"size": 100,
"color": "field-velocity",
"log_scale": False,
"format": "%0.1f",
"user_skip": 9,
"show_all": True,
"position": 1,
"font_name": "Helvetica",
"font_automatic": True,
"font_size": 0.032,
"length": 0.54,
"width": 6,
"bground_transparent": True,
"bground_color": "#CCD3E2",
"title_elements": "Variable and Object Name",
},
"range_option": {
"option": "auto-range-off",
"auto_range_off": {
"maximum": 400.0,
"minimum": 300,
"clip_to_range": False,
},
},
}
assert solver.settings.results.graphics.contour["temperature"].range_options() == {
"global_range": True,
"auto_range": False,
"clip_to_range": False,
"minimum": 300,
"maximum": 400.0,
}


@pytest.mark.fluent_version(">=23.1")

0 comments on commit f58fbae

Please sign in to comment.