Skip to content

Commit f7dc485

Browse files
PProfizirafacanton
andauthored
Expose streamlines (#480)
* Small typing fix * Draft of ansys.dpf.post.tools.streamlines.plot_streamlines() * Apply suggestions from code review Co-authored-by: Rafael Canton <[email protected]> * Implement remarks on sources definition * Remove __init__.py from ansys/dpf/post/tools * Revert "Remove __init__.py from ansys/dpf/post/tools" This reverts commit 6b8cdab. * Add a conftest.py for Doctest * Rename sub-package to "helpers" as tools.py already exists in ansys/dpf/post/ * Remove debug * Update pre-commit * Move the select method to a new helpers/selections module, ensuring retro-compatibility while preparing deprecation. * Handle multi-zone with a merge step, and add a set_id argument to select set in transient dataframe. --------- Co-authored-by: Rafael Canton <[email protected]>
1 parent 54e39b1 commit f7dc485

20 files changed

+283
-64
lines changed

conftest.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""This runs at the init of the doctest pytest session."""
2+
import doctest
3+
from doctest import DocTestRunner
4+
from unittest import mock
5+
6+
from ansys.dpf.core.misc import module_exists
7+
import pytest
8+
9+
from ansys.dpf import core
10+
11+
# enable matplotlib off_screen plotting to avoid test interruption
12+
13+
if module_exists("matplotlib"):
14+
import matplotlib as mpl
15+
16+
mpl.use("Agg")
17+
18+
19+
# enable off_screen plotting to avoid test interruption
20+
core.settings.disable_off_screen_rendering()
21+
core.settings.bypass_pv_opengl_osmesa_crash()
22+
23+
24+
class _DPFDocTestRunner(DocTestRunner):
25+
def run(self, test, compileflags=None, out=None, clear_globs=True):
26+
try:
27+
return DocTestRunner.run(self, test, compileflags, out, clear_globs)
28+
except doctest.UnexpectedException as e:
29+
feature_str = "Feature not supported. Upgrade the server to"
30+
if feature_str in str(e.exc_info):
31+
pass
32+
else:
33+
raise e
34+
35+
36+
@pytest.fixture(autouse=True)
37+
def _doctest_runner_dpf():
38+
with mock.patch("doctest.DocTestRunner", _DPFDocTestRunner):
39+
yield

src/ansys/dpf/post/examples/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
Examples
44
--------
5-
This module module exposes PyDPF-Core functionalities.
5+
This package exposes PyDPF-Core functionalities.
66
77
See `here <https://dpf.docs.pyansys.com/dev/api/ansys.dpf.core.examples.html>`_
88
for a description of the PyDPF-Core ``example`` API.
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Tools package.
2+
3+
Tools
4+
-----
5+
This package regroups helpers for different common post-treatment functionalities.
6+
"""
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Module containing helpers to build selections.
2+
3+
Selections
4+
----------
5+
6+
"""
7+
from ansys.dpf.post import selection
8+
9+
10+
def select(
11+
time_freq_indexes=None,
12+
time_freq_sets=None,
13+
time_freq_values=None,
14+
named_selection_names=None,
15+
**kwargs,
16+
):
17+
"""Creates a ``Selection`` instance allowing to choose the domain on which to evaluate results.
18+
19+
The results domain defines the time frequency and the spatial selection.
20+
21+
Parameters
22+
----------
23+
time_freq_indexes:
24+
Time/freq indexes to select.
25+
time_freq_sets:
26+
Time/freq sets to select.
27+
time_freq_values:
28+
Time/freq values to select.
29+
named_selection_names:
30+
Time/freq named selection to select.
31+
32+
"""
33+
current_selection = selection.Selection()
34+
if time_freq_indexes:
35+
current_selection.select_time_freq_indexes(time_freq_indexes)
36+
if time_freq_sets:
37+
current_selection.select_time_freq_sets(time_freq_sets)
38+
if time_freq_values:
39+
current_selection.select_time_freq_values(time_freq_values)
40+
if named_selection_names:
41+
current_selection.select_named_selection(named_selection_names)
42+
return current_selection
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Module containing the helpers for streamlines.
2+
3+
Streamlines
4+
-----------
5+
6+
"""
7+
from typing import List, Union
8+
9+
from ansys.dpf.core.helpers import streamlines as core_streamlines
10+
from ansys.dpf.core.plotter import DpfPlotter
11+
12+
from ansys.dpf import core as dpf
13+
from ansys.dpf import post
14+
15+
16+
def plot_streamlines(
17+
dataframe: post.DataFrame,
18+
sources: List[dict],
19+
set_id: int = 1,
20+
streamline_thickness: Union[float, List[float]] = 0.01,
21+
plot_mesh: bool = True,
22+
mesh_opacity: float = 0.3,
23+
plot_contour: bool = True,
24+
contour_opacity: float = 0.3,
25+
**kwargs,
26+
):
27+
"""Plot streamlines based on a vector field DataFrame.
28+
29+
Parameters
30+
----------
31+
dataframe:
32+
A `post.DataFrame` object containing a vector field.
33+
If present, merges the `DataFrame` across its `zone` label before plotting.
34+
sources:
35+
A list of dictionaries defining spherical point sources for the streamlines.
36+
Expected keywords are "center", "radius", "max_time" and "n_points".
37+
Keyword "max_time" is for the maximum integration pseudo-time for the streamline
38+
computation algorithm, which defines the final length of the lines.
39+
More information is available at :func:`pyvista.DataSetFilters.streamlines`.
40+
set_id:
41+
ID of the set (time-step) for which to compute streamlines if the `DataFrame` object
42+
contains temporal data.
43+
streamline_thickness:
44+
Thickness of the streamlines plotted. Use a list to specify a value for each source.
45+
plot_contour:
46+
Whether to plot the field's norm contour along with the streamlines.
47+
contour_opacity:
48+
Opacity to use for the field contour in case "plot_contour=True".
49+
plot_mesh:
50+
Whether to plot the mesh along the streamlines in case "plot_contour=False".
51+
mesh_opacity:
52+
Opacity to use for the mesh in case "plot_contour=False" and "plot_mesh=True".
53+
**kwargs:
54+
55+
"""
56+
# Select data to work with
57+
fc = dataframe._fc
58+
if "zone" in dataframe.columns.names:
59+
fc = dpf.operators.utility.merge_fields_by_label(
60+
fields_container=fc,
61+
label="zone",
62+
).outputs.fields_container()
63+
if set_id not in dataframe.columns.set_index.values:
64+
raise ValueError("The set_id requested is not available in this dataframe.")
65+
field = fc.get_field_by_time_id(timeid=set_id)
66+
meshed_region = field.meshed_region
67+
68+
# Initialize the plotter
69+
plt = DpfPlotter(**kwargs)
70+
71+
if plot_contour:
72+
plt.add_field(field=field, opacity=contour_opacity)
73+
elif plot_mesh:
74+
plt.add_mesh(meshed_region=meshed_region, opacity=mesh_opacity)
75+
if not isinstance(streamline_thickness, list):
76+
streamline_thickness = [streamline_thickness] * len(sources)
77+
# Add streamlines for each source
78+
for i, source in enumerate(sources):
79+
pv_streamline, pv_source = core_streamlines.compute_streamlines(
80+
meshed_region=meshed_region,
81+
field=field,
82+
return_source=True,
83+
source_radius=source["radius"],
84+
source_center=source["center"],
85+
n_points=source["n_points"] if "n_points" in source else 100,
86+
max_time=source["max_time"] if "max_time" in source else None,
87+
)
88+
plt.add_streamlines(
89+
pv_streamline, source=pv_source, radius=streamline_thickness[i]
90+
)
91+
92+
plt.show_figure(**kwargs)

src/ansys/dpf/post/index.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -379,20 +379,28 @@ def names(self):
379379

380380
@property
381381
def results_index(self) -> Union[ResultsIndex, None]:
382-
"""Returns the available ResultsIndex is present."""
382+
"""Returns the available ResultsIndex if present."""
383383
for index in self._indexes:
384384
if isinstance(index, ResultsIndex):
385385
return index
386386
return None
387387

388388
@property
389389
def mesh_index(self) -> Union[MeshIndex, None]:
390-
"""Returns the available ResultsIndex is present."""
390+
"""Returns the available ResultsIndex if present."""
391391
for index in self._indexes:
392392
if isinstance(index, MeshIndex):
393393
return index
394394
return None
395395

396+
@property
397+
def set_index(self) -> Union[SetIndex, None]:
398+
"""Returns the available SetIndex if present."""
399+
for index in self._indexes:
400+
if isinstance(index, SetIndex):
401+
return index
402+
return None
403+
396404
# @property
397405
# def label_names(self):
398406
# """Returns a list with the name of each label Index."""

src/ansys/dpf/post/simulation.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ def _build_selection(
811811
node_ids: Union[List[int], None] = None,
812812
location: Union[locations, str] = locations.nodal,
813813
external_layer: bool = False,
814-
skin: Union[bool] = False,
814+
skin: Union[bool, List[int]] = False,
815815
expand_cyclic: Union[bool, List[Union[int, List[int]]]] = True,
816816
) -> Selection:
817817
tot = (

src/ansys/dpf/post/tools.py

+4-40
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,11 @@
1-
"""This module holds factories to create geometry, selections...
1+
"""This module holds a deprecated link to the legacy `ansys.dpf.post.tools.select` method.
22
33
tools
44
=====
55
6-
These factories help creating Objects used to defined which results are evaluated.
6+
This module has been replaced by the `helpers` sub-package.
7+
Please use `ansys.dpf.post.helpers.selections.select` instead.
78
89
"""
910

10-
from ansys.dpf.post import selection
11-
12-
# from ansys.dpf.core.geometry_factory import *
13-
14-
15-
def select(
16-
time_freq_indexes=None,
17-
time_freq_sets=None,
18-
time_freq_values=None,
19-
named_selection_names=None,
20-
**kwargs,
21-
):
22-
"""Creates a ``Selection`` instance allowing to choose the domain on which to evaluate results.
23-
24-
The results domain defines the time frequency and the spatial selection.
25-
26-
Parameters
27-
----------
28-
time_freq_indexes:
29-
Time/freq indexes to select.
30-
time_freq_sets:
31-
Time/freq sets to select.
32-
time_freq_values:
33-
Time/freq values to select.
34-
named_selection_names:
35-
Time/freq named selection to select.
36-
37-
"""
38-
current_selection = selection.Selection()
39-
if time_freq_indexes:
40-
current_selection.select_time_freq_indexes(time_freq_indexes)
41-
if time_freq_sets:
42-
current_selection.select_time_freq_sets(time_freq_sets)
43-
if time_freq_values:
44-
current_selection.select_time_freq_values(time_freq_values)
45-
if named_selection_names:
46-
current_selection.select_named_selection(named_selection_names)
47-
return current_selection
11+
from ansys.dpf.post.helpers.selections import select # noqa: F401

tests/test_dataframe.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import ansys.dpf.core as core
2-
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
32
import numpy as np
43
import pytest
54
from pytest import fixture
@@ -17,6 +16,7 @@
1716
from ansys.dpf.post.modal_mechanical_simulation import ModalMechanicalSimulation
1817
from ansys.dpf.post.static_mechanical_simulation import StaticMechanicalSimulation
1918
from ansys.dpf.post.transient_mechanical_simulation import TransientMechanicalSimulation
19+
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
2020

2121

2222
@fixture

tests/test_faces.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from ansys.dpf.core import examples
2-
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
32
import pytest
43
from pytest import fixture
54

65
from ansys.dpf import core as dpf
76
from ansys.dpf import post
87
from ansys.dpf.post.faces import Face, FaceListById, FaceListByIndex
98
from ansys.dpf.post.nodes import NodeListByIndex
9+
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
1010

1111

1212
@pytest.mark.skipif(

tests/test_fluid_simulation.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from ansys.dpf.core import examples
2-
from conftest import (
3-
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0,
4-
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_1,
5-
)
62
import pytest
73
from pytest import fixture
84

95
from ansys.dpf import core as dpf
106
from ansys.dpf import post
7+
from conftest import (
8+
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0,
9+
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_1,
10+
)
1111

1212

1313
@pytest.mark.skipif(

tests/test_load_simulation.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import ansys.dpf.core as dpf
2-
from conftest import (
3-
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_4_0,
4-
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0,
5-
)
62
import pytest
73

84
from ansys.dpf import post
@@ -12,6 +8,10 @@
128
from ansys.dpf.post.modal_mechanical_simulation import ModalMechanicalSimulation
139
from ansys.dpf.post.static_mechanical_simulation import StaticMechanicalSimulation
1410
from ansys.dpf.post.transient_mechanical_simulation import TransientMechanicalSimulation
11+
from conftest import (
12+
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_4_0,
13+
SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0,
14+
)
1515

1616

1717
def test_load_simulation_static_mechanical(simple_bar, complex_model):

tests/test_mesh.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import ansys.dpf.core as dpf
2-
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
32
import numpy as np
43
import pytest
54
from pytest import fixture
65

76
from ansys.dpf.post import FluidSimulation, Mesh, StaticMechanicalSimulation
87
from ansys.dpf.post.connectivity import ConnectivityListByIndex
98
from ansys.dpf.post.faces import Face
9+
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
1010

1111

1212
@fixture

tests/test_mesh_info.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
21
import pytest
32
from pytest import fixture
43

54
from ansys.dpf import core as dpf
65
from ansys.dpf import post
76
from ansys.dpf.post import examples
7+
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
88

99

1010
@pytest.mark.skipif(

tests/test_named_selection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import conftest
21
import pytest
32
from pytest import fixture
43

54
from ansys.dpf import core as dpf
65
from ansys.dpf.post import StaticMechanicalSimulation
76
from ansys.dpf.post.named_selection import NamedSelection
7+
import conftest
88

99

1010
@fixture

tests/test_phase.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from ansys.dpf.core import examples
2-
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
32
import pytest
43
from pytest import fixture
54

65
from ansys.dpf import core as dpf
76
from ansys.dpf import post
7+
from conftest import SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0
88

99

1010
@pytest.mark.skipif(

0 commit comments

Comments
 (0)