Skip to content

Commit

Permalink
Expose arguments for cyclic result queries (#336)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
PProfizi authored Apr 4, 2023
1 parent ded7cc6 commit 42a4e79
Show file tree
Hide file tree
Showing 16 changed files with 2,530 additions and 184 deletions.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"scipy": ("https://docs.scipy.org/doc/scipy/", None),
"numpy": ("https://numpy.org/devdocs", None),
"matplotlib": ("https://matplotlib.org/stable", None),
"pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
# "pandas": ("https://pandas.pydata.org/pandas-docs/stable", None),
"pyvista": ("https://docs.pyvista.org/", None),
"ansys-dpf-core": ("https://dpfdocs.pyansys.com", None),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
elemental_stress = simulation.stress_elemental()
print(elemental_stress)

# extract elemental stresses on specific elements
# Extract elemental stresses on specific elements
elemental_stress = elemental_stress.select(element_ids=[5, 6, 7])
elemental_stress.plot()

Expand Down
9 changes: 3 additions & 6 deletions examples/01-Detailed-Examples/02-modal-extract-sub-results.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
Extract components of results - Modal Simulation
================================================
In this script modal simulation is processed to extract sub components of results like elastic
stain, displacement.
In this script, a modal simulation is processed to extract sub-components
of results like elastic strain and displacement.
"""

###############################################################################
# Perform required imports
# ------------------------
# Perform required imports. # This example uses a supplied file that you can
# This example uses a supplied file that you can
# get by importing the DPF ``examples`` package.

from ansys.dpf import post
Expand All @@ -29,9 +29,6 @@

# for no autocompletion, this line is equivalent to:
simulation = post.ModalMechanicalSimulation(example_path)

displacement = simulation.displacement(modes=[1, 2])
str(displacement)
# print the simulation to get an overview of what's available
print(simulation)

Expand Down
2 changes: 1 addition & 1 deletion examples/01-Detailed-Examples/04-invariants.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@


###############################################################################
# Compute Von Mises eqv averaged and unaveraged on stress and strain
# Compute von Mises eqv averaged and unaveraged on stress and strain
# ------------------------------------------------------------------------

stress_eqv = simulation.stress_eqv_von_mises(set_ids=[1])
Expand Down
62 changes: 62 additions & 0 deletions examples/01-Detailed-Examples/06-cyclic-results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
.. _ref_cyclic_results_example:
Extract cyclic results
======================
In this script, a modal analysis with cyclic symmetry is processed to show
how to expand the mesh and results.
"""

###############################################################################
# Perform required imports
# ------------------------
# This example uses a supplied file that you can
# get by importing the DPF ``examples`` package.

from ansys.dpf import post
from ansys.dpf.post import examples

###############################################################################
# Get ``Simulation`` object
# -------------------------
# Get the ``Simulation`` object that allows access to the result. The ``Simulation``
# object must be instantiated with the path for the result file. For example,
# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"``
# on Linux.

example_path = examples.find_simple_cyclic()
simulation = post.ModalMechanicalSimulation(example_path)

# print the simulation to get an overview of what's available
print(simulation)

#############################################################################
# Extract expanded displacement norm
# ----------------------------------

displacement_norm = simulation.displacement(
norm=True,
expand_cyclic=True,
)
print(displacement_norm)
displacement_norm.plot()

#############################################################################
# Extract equivalent von Mises nodal stress expanded on the first four sectors
# ----------------------------------------------------------------------------

stress_vm_sectors_1_2_3_4 = simulation.stress_eqv_von_mises_nodal(
expand_cyclic=[1, 2, 3, 4],
)
print(stress_vm_sectors_1_2_3_4)
stress_vm_sectors_1_2_3_4.plot()

#############################################################################
# Extract equivalent von Mises nodal stress without expansion
# -----------------------------------------------------------

stress_vm_sector_1 = simulation.stress_eqv_von_mises_nodal(
expand_cyclic=False,
)
print(stress_vm_sector_1)
stress_vm_sector_1.plot()
72 changes: 72 additions & 0 deletions examples/01-Detailed-Examples/07-multi-stage-cyclic-results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
.. _ref_multi-stage_cyclic_results_example:
Extract multi-stage cyclic results
==================================
In this script, a multi-stage modal analysis with cyclic symmetry is processed to show
how to expand the mesh and results.
"""

###############################################################################
# Perform required imports
# ------------------------
# This example uses a supplied file that you can
# get by importing the DPF ``examples`` package.

from ansys.dpf import post
from ansys.dpf.post import examples

###############################################################################
# Get ``Simulation`` object
# -------------------------
# Get the ``Simulation`` object that allows access to the result. The ``Simulation``
# object must be instantiated with the path for the result file. For example,
# ``"C:/Users/user/my_result.rst"`` on Windows or ``"/home/user/my_result.rst"``
# on Linux.

example_path = examples.download_multi_stage_cyclic_result()
simulation = post.ModalMechanicalSimulation(example_path)

# print the simulation to get an overview of what's available
print(simulation)

#############################################################################
# Extract expanded displacement norm
# ----------------------------------

displacement_norm = simulation.displacement(
norm=True,
expand_cyclic=True,
)
print(displacement_norm)
displacement_norm.plot()

#############################################################################
# Extract equivalent von Mises nodal stress without expansion
# -----------------------------------------------------------

stress_vm_sector_1_both_stages = simulation.stress_eqv_von_mises_nodal(
expand_cyclic=False,
)
print(stress_vm_sector_1_both_stages)
stress_vm_sector_1_both_stages.plot()

#############################################################################
# Extract equivalent von Mises nodal stress expanded on the first four sectors of the first stage
# -----------------------------------------------------------------------------------------------

stress_vm_sectors_1_2_3_4_first_stage = simulation.stress_eqv_von_mises_nodal(
expand_cyclic=[1, 2, 3, 4],
)
print(stress_vm_sectors_1_2_3_4_first_stage)
stress_vm_sectors_1_2_3_4_first_stage.plot()

#############################################################################
# Extract equivalent von Mises nodal stress expanded on the first two sectors of both stages
# ------------------------------------------------------------------------------------------

stress_vm_sectors_1_2_both_stages = simulation.stress_eqv_von_mises_nodal(
expand_cyclic=[[1, 2], [1, 2]],
)
print(stress_vm_sectors_1_2_both_stages)
stress_vm_sectors_1_2_both_stages.plot()
2 changes: 0 additions & 2 deletions requirements/requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
coverage==7.2.2
pandas==1.3.5; python_version < "3.8"
pandas==1.5.3; python_version >= "3.8"
pytest-cov==4.0.0
pytest-rerunfailures==11.0
pytest==7.2.1
Expand Down
78 changes: 56 additions & 22 deletions src/ansys/dpf/post/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,10 @@ def _update_str(self, width: int, max_colwidth: int):
lines.append(row_headers)
entity_ids = []
truncated = False
num_mesh_entities_to_ask = self._fc[0].size
if len(self._fc) > 0:
num_mesh_entities_to_ask = self._fc[0].size
else:
num_mesh_entities_to_ask = 0
if num_mesh_entities_to_ask > max_n_rows:
num_mesh_entities_to_ask = max_n_rows
truncated = True
Expand Down Expand Up @@ -516,6 +519,10 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
for i in range(len(combination))
]
to_append.append(empty) # row where row index headers are
if len(self._fc) == 0:
for i, _ in enumerate(to_append):
lines[i] = lines[i] + to_append[i]
break
# Get data in the FieldsContainer for those positions
# Create label_space from combination
label_space = {}
Expand Down Expand Up @@ -637,9 +644,12 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
txt = "\n" + "".join([line + "\n" for line in lines])
self._str = txt

def _first_n_ids_first_field(self, n: int):
def _first_n_ids_first_field(self, n: int) -> List[int]:
"""Return the n first entity IDs of the first field in the underlying FieldsContainer."""
return self._fc[0].scoping.ids[:n]
if len(self._fc) > 0:
return self._fc[0].scoping.ids[:n]
else:
return []

def __repr__(self):
"""Representation of the DataFrame."""
Expand Down Expand Up @@ -669,6 +679,7 @@ def plot(self, shell_layer=shell_layers.top, **kwargs) -> Union[DpfPlotter, None
The interactive plotter object used for plotting.
"""
label_space = {}
if kwargs != {}:
axis_kwargs, kwargs = self._filter_arguments(arguments=kwargs)
# Construct the associated label_space
Expand All @@ -682,22 +693,27 @@ def plot(self, shell_layer=shell_layers.top, **kwargs) -> Union[DpfPlotter, None
)
)
return
labels = fc.labels
# TODO: treat complex label by taking amplitude
for label in labels:
if label == "time":
value = fc.get_available_ids_for_label(label)[-1]
elif label == "frequencies":
value = fc.get_available_ids_for_label(label)[0]
elif label in axis_kwargs.keys():
value = axis_kwargs[label]
if isinstance(value, list):
raise ValueError(
f"Plot argument '{label}' must be a single value."
)
else:
value = fc.get_available_ids_for_label(label)[0]
label_space[label] = value
else:
axis_kwargs = {}
# If no kwarg was given, construct a default label_space
fc = self._fc
labels = fc.labels
if "time" in labels:
label = "time"
value = fc.get_available_ids_for_label(label)[-1]
label_space = {label: value}
elif "frequencies" in labels:
label = "frequencies"
value = fc.get_available_ids_for_label(label)[0]
label_space = {label: value}
else:
label_space = fc.get_label_space(0)
label_space = label_space

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

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

# Merge fields for all 'stage' label values if none selected
if "stage" in self._fc.labels and "stage" not in axis_kwargs.keys():
merge_stages_op = dpf.operators.utility.merge_fields_by_label(
fields_container=fc, label="stage"
)
fc = merge_stages_op.outputs.fields_container()
label_space.pop("stage")

fields = fc.get_fields(label_space=label_space)
plotter = DpfPlotter(**kwargs)
plotter.add_field(field=fields[0], **kwargs)
# for field in fields:
if len(fields) > 1:
warnings.warn(
UserWarning(
"Plotting criteria resulted in incompatible data. "
"Try narrowing down to specific values for each column."
)
# try:
# for field in fields[1:]:
# plotter.add_field(field=field, **kwargs)
# except Exception as e:
raise ValueError(
f"Plotting failed with filter {axis_kwargs} due to incompatible data."
)
return None
plotter.add_field(field=fields[0], **kwargs)
# warnings.warn(
# UserWarning(
# "Plotting criteria resulted in incompatible data. "
# "Try narrowing down to specific values for each column."
# )
# )
# return None
# field.plot(text="debug")
return plotter.show_figure(text=str(label_space), **kwargs)
return plotter.show_figure(
title=kwargs.pop("title", str(label_space)), **kwargs
)

def animate(
self,
Expand Down
Loading

0 comments on commit 42a4e79

Please sign in to comment.