Skip to content

Commit 42a4e79

Browse files
authored
Expose arguments for cyclic result queries (#336)
* Fix typos in 02-modal-extract-sub-results.py * Fix incoherent part in 02-modal-extract-sub-results.py * Expose read_cyclic pin for ModalMechanicalSimulation.displacement * Add 06-cyclic-results.py (WIP) * Proposition with boolean arguments "expand_sectors" and "merge_stages" * Refactor to accept 'expand_cyclic' keyword, True by default (read_cyclic=3), with False (read_cyclic=1). Can take a list of int, or a list of lists of int (multi-stage) to specify sector numbers to expand. * Expose phase_angle_cyclic argument, connected to pin 19 (phi) * Treat multi-stage case for plotting, improve label_space print in plot title * Fix typehinting * Fix typehinting * Improve typehinting * Refactor into Simulation._treat_cyclic, propagate to all result APIs for Static, Modal and Harmonic * Improve coverage * Improve coverage * Improve coverage * Improve Dataframe output for norm * Improve Dataframe output for single comp result * Improve Dataframe output for equivalent result * Working 06-cyclic-results.py * First working 07-multi-stage-cyclic-results.py * Fix bug when title argument is given to plot * Fix step "Set licensing if necessary" in CI and CI_release for retro tests * Fix ANSYS_VERSION for "upload test results" step of retro in ci.yml and ci_release.yml * Print empty dataframes * Update examples * Updates from comments * Updates from comments * Fix "Extract elemental nodal" -> "Extract" as location can be changed * Fix "Von Mises" -> "von Mises" * Remove Pandas from requirements_test.txt * Force one-based indexing for sectors in cyclic * Force one-based indexing for sectors in cyclic * Force one-based indexing for sectors in cyclic * Add dosctring examples to ModalMechanicalSimulation.displacement
1 parent ded7cc6 commit 42a4e79

16 files changed

+2530
-184
lines changed

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"scipy": ("https://docs.scipy.org/doc/scipy/", None),
6666
"numpy": ("https://numpy.org/devdocs", None),
6767
"matplotlib": ("https://matplotlib.org/stable", None),
68-
"pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
68+
# "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
6969
"pyvista": ("https://docs.pyvista.org/", None),
7070
"ansys-dpf-core": ("https://dpfdocs.pyansys.com", None),
7171
}

examples/00-Different-analysis-types/01-static-simulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
elemental_stress = simulation.stress_elemental()
8989
print(elemental_stress)
9090

91-
# extract elemental stresses on specific elements
91+
# Extract elemental stresses on specific elements
9292
elemental_stress = elemental_stress.select(element_ids=[5, 6, 7])
9393
elemental_stress.plot()
9494

examples/01-Detailed-Examples/02-modal-extract-sub-results.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
44
Extract components of results - Modal Simulation
55
================================================
6-
In this script modal simulation is processed to extract sub components of results like elastic
7-
stain, displacement.
6+
In this script, a modal simulation is processed to extract sub-components
7+
of results like elastic strain and displacement.
88
"""
99

1010
###############################################################################
1111
# Perform required imports
1212
# ------------------------
13-
# Perform required imports. # This example uses a supplied file that you can
13+
# This example uses a supplied file that you can
1414
# get by importing the DPF ``examples`` package.
1515

1616
from ansys.dpf import post
@@ -29,9 +29,6 @@
2929

3030
# for no autocompletion, this line is equivalent to:
3131
simulation = post.ModalMechanicalSimulation(example_path)
32-
33-
displacement = simulation.displacement(modes=[1, 2])
34-
str(displacement)
3532
# print the simulation to get an overview of what's available
3633
print(simulation)
3734

examples/01-Detailed-Examples/04-invariants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

6161

6262
###############################################################################
63-
# Compute Von Mises eqv averaged and unaveraged on stress and strain
63+
# Compute von Mises eqv averaged and unaveraged on stress and strain
6464
# ------------------------------------------------------------------------
6565

6666
stress_eqv = simulation.stress_eqv_von_mises(set_ids=[1])
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
.. _ref_cyclic_results_example:
3+
4+
Extract cyclic results
5+
======================
6+
In this script, a modal analysis with cyclic symmetry is processed to show
7+
how to expand the mesh and results.
8+
"""
9+
10+
###############################################################################
11+
# Perform required imports
12+
# ------------------------
13+
# This example uses a supplied file that you can
14+
# get by importing the DPF ``examples`` package.
15+
16+
from ansys.dpf import post
17+
from ansys.dpf.post import examples
18+
19+
###############################################################################
20+
# Get ``Simulation`` object
21+
# -------------------------
22+
# Get the ``Simulation`` object that allows access to the result. The ``Simulation``
23+
# object must be instantiated with the path for the result file. For example,
24+
# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"``
25+
# on Linux.
26+
27+
example_path = examples.find_simple_cyclic()
28+
simulation = post.ModalMechanicalSimulation(example_path)
29+
30+
# print the simulation to get an overview of what's available
31+
print(simulation)
32+
33+
#############################################################################
34+
# Extract expanded displacement norm
35+
# ----------------------------------
36+
37+
displacement_norm = simulation.displacement(
38+
norm=True,
39+
expand_cyclic=True,
40+
)
41+
print(displacement_norm)
42+
displacement_norm.plot()
43+
44+
#############################################################################
45+
# Extract equivalent von Mises nodal stress expanded on the first four sectors
46+
# ----------------------------------------------------------------------------
47+
48+
stress_vm_sectors_1_2_3_4 = simulation.stress_eqv_von_mises_nodal(
49+
expand_cyclic=[1, 2, 3, 4],
50+
)
51+
print(stress_vm_sectors_1_2_3_4)
52+
stress_vm_sectors_1_2_3_4.plot()
53+
54+
#############################################################################
55+
# Extract equivalent von Mises nodal stress without expansion
56+
# -----------------------------------------------------------
57+
58+
stress_vm_sector_1 = simulation.stress_eqv_von_mises_nodal(
59+
expand_cyclic=False,
60+
)
61+
print(stress_vm_sector_1)
62+
stress_vm_sector_1.plot()
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
.. _ref_multi-stage_cyclic_results_example:
3+
4+
Extract multi-stage cyclic results
5+
==================================
6+
In this script, a multi-stage modal analysis with cyclic symmetry is processed to show
7+
how to expand the mesh and results.
8+
"""
9+
10+
###############################################################################
11+
# Perform required imports
12+
# ------------------------
13+
# This example uses a supplied file that you can
14+
# get by importing the DPF ``examples`` package.
15+
16+
from ansys.dpf import post
17+
from ansys.dpf.post import examples
18+
19+
###############################################################################
20+
# Get ``Simulation`` object
21+
# -------------------------
22+
# Get the ``Simulation`` object that allows access to the result. The ``Simulation``
23+
# object must be instantiated with the path for the result file. For example,
24+
# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"``
25+
# on Linux.
26+
27+
example_path = examples.download_multi_stage_cyclic_result()
28+
simulation = post.ModalMechanicalSimulation(example_path)
29+
30+
# print the simulation to get an overview of what's available
31+
print(simulation)
32+
33+
#############################################################################
34+
# Extract expanded displacement norm
35+
# ----------------------------------
36+
37+
displacement_norm = simulation.displacement(
38+
norm=True,
39+
expand_cyclic=True,
40+
)
41+
print(displacement_norm)
42+
displacement_norm.plot()
43+
44+
#############################################################################
45+
# Extract equivalent von Mises nodal stress without expansion
46+
# -----------------------------------------------------------
47+
48+
stress_vm_sector_1_both_stages = simulation.stress_eqv_von_mises_nodal(
49+
expand_cyclic=False,
50+
)
51+
print(stress_vm_sector_1_both_stages)
52+
stress_vm_sector_1_both_stages.plot()
53+
54+
#############################################################################
55+
# Extract equivalent von Mises nodal stress expanded on the first four sectors of the first stage
56+
# -----------------------------------------------------------------------------------------------
57+
58+
stress_vm_sectors_1_2_3_4_first_stage = simulation.stress_eqv_von_mises_nodal(
59+
expand_cyclic=[1, 2, 3, 4],
60+
)
61+
print(stress_vm_sectors_1_2_3_4_first_stage)
62+
stress_vm_sectors_1_2_3_4_first_stage.plot()
63+
64+
#############################################################################
65+
# Extract equivalent von Mises nodal stress expanded on the first two sectors of both stages
66+
# ------------------------------------------------------------------------------------------
67+
68+
stress_vm_sectors_1_2_both_stages = simulation.stress_eqv_von_mises_nodal(
69+
expand_cyclic=[[1, 2], [1, 2]],
70+
)
71+
print(stress_vm_sectors_1_2_both_stages)
72+
stress_vm_sectors_1_2_both_stages.plot()

requirements/requirements_test.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
coverage==7.2.2
2-
pandas==1.3.5; python_version < "3.8"
3-
pandas==1.5.3; python_version >= "3.8"
42
pytest-cov==4.0.0
53
pytest-rerunfailures==11.0
64
pytest==7.2.1

src/ansys/dpf/post/dataframe.py

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ def _update_str(self, width: int, max_colwidth: int):
420420
lines.append(row_headers)
421421
entity_ids = []
422422
truncated = False
423-
num_mesh_entities_to_ask = self._fc[0].size
423+
if len(self._fc) > 0:
424+
num_mesh_entities_to_ask = self._fc[0].size
425+
else:
426+
num_mesh_entities_to_ask = 0
424427
if num_mesh_entities_to_ask > max_n_rows:
425428
num_mesh_entities_to_ask = max_n_rows
426429
truncated = True
@@ -516,6 +519,10 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
516519
for i in range(len(combination))
517520
]
518521
to_append.append(empty) # row where row index headers are
522+
if len(self._fc) == 0:
523+
for i, _ in enumerate(to_append):
524+
lines[i] = lines[i] + to_append[i]
525+
break
519526
# Get data in the FieldsContainer for those positions
520527
# Create label_space from combination
521528
label_space = {}
@@ -637,9 +644,12 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
637644
txt = "\n" + "".join([line + "\n" for line in lines])
638645
self._str = txt
639646

640-
def _first_n_ids_first_field(self, n: int):
647+
def _first_n_ids_first_field(self, n: int) -> List[int]:
641648
"""Return the n first entity IDs of the first field in the underlying FieldsContainer."""
642-
return self._fc[0].scoping.ids[:n]
649+
if len(self._fc) > 0:
650+
return self._fc[0].scoping.ids[:n]
651+
else:
652+
return []
643653

644654
def __repr__(self):
645655
"""Representation of the DataFrame."""
@@ -669,6 +679,7 @@ def plot(self, shell_layer=shell_layers.top, **kwargs) -> Union[DpfPlotter, None
669679
The interactive plotter object used for plotting.
670680
671681
"""
682+
label_space = {}
672683
if kwargs != {}:
673684
axis_kwargs, kwargs = self._filter_arguments(arguments=kwargs)
674685
# Construct the associated label_space
@@ -682,22 +693,27 @@ def plot(self, shell_layer=shell_layers.top, **kwargs) -> Union[DpfPlotter, None
682693
)
683694
)
684695
return
696+
labels = fc.labels
697+
# TODO: treat complex label by taking amplitude
698+
for label in labels:
699+
if label == "time":
700+
value = fc.get_available_ids_for_label(label)[-1]
701+
elif label == "frequencies":
702+
value = fc.get_available_ids_for_label(label)[0]
703+
elif label in axis_kwargs.keys():
704+
value = axis_kwargs[label]
705+
if isinstance(value, list):
706+
raise ValueError(
707+
f"Plot argument '{label}' must be a single value."
708+
)
709+
else:
710+
value = fc.get_available_ids_for_label(label)[0]
711+
label_space[label] = value
685712
else:
686713
axis_kwargs = {}
687714
# If no kwarg was given, construct a default label_space
688715
fc = self._fc
689-
labels = fc.labels
690-
if "time" in labels:
691-
label = "time"
692-
value = fc.get_available_ids_for_label(label)[-1]
693-
label_space = {label: value}
694-
elif "frequencies" in labels:
695-
label = "frequencies"
696-
value = fc.get_available_ids_for_label(label)[0]
697-
label_space = {label: value}
698-
else:
699716
label_space = fc.get_label_space(0)
700-
label_space = label_space
701717

702718
for field in fc:
703719
# Treat multi-layer field
@@ -719,24 +735,42 @@ def plot(self, shell_layer=shell_layers.top, **kwargs) -> Union[DpfPlotter, None
719735
fc = changeOp.get_output(0, dpf.types.fields_container)
720736
break
721737

738+
# Merge fields for all 'elshape' label values if none selected
722739
if "elshape" in self._fc.labels and "elshape" not in axis_kwargs.keys():
723740
merge_solids_shell_op = dpf.operators.logic.solid_shell_fields(fc)
724741
fc = merge_solids_shell_op.eval()
725742

743+
# Merge fields for all 'stage' label values if none selected
744+
if "stage" in self._fc.labels and "stage" not in axis_kwargs.keys():
745+
merge_stages_op = dpf.operators.utility.merge_fields_by_label(
746+
fields_container=fc, label="stage"
747+
)
748+
fc = merge_stages_op.outputs.fields_container()
749+
label_space.pop("stage")
750+
726751
fields = fc.get_fields(label_space=label_space)
727752
plotter = DpfPlotter(**kwargs)
753+
plotter.add_field(field=fields[0], **kwargs)
728754
# for field in fields:
729755
if len(fields) > 1:
730-
warnings.warn(
731-
UserWarning(
732-
"Plotting criteria resulted in incompatible data. "
733-
"Try narrowing down to specific values for each column."
734-
)
756+
# try:
757+
# for field in fields[1:]:
758+
# plotter.add_field(field=field, **kwargs)
759+
# except Exception as e:
760+
raise ValueError(
761+
f"Plotting failed with filter {axis_kwargs} due to incompatible data."
735762
)
736-
return None
737-
plotter.add_field(field=fields[0], **kwargs)
763+
# warnings.warn(
764+
# UserWarning(
765+
# "Plotting criteria resulted in incompatible data. "
766+
# "Try narrowing down to specific values for each column."
767+
# )
768+
# )
769+
# return None
738770
# field.plot(text="debug")
739-
return plotter.show_figure(text=str(label_space), **kwargs)
771+
return plotter.show_figure(
772+
title=kwargs.pop("title", str(label_space)), **kwargs
773+
)
740774

741775
def animate(
742776
self,

0 commit comments

Comments
 (0)