Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
168 changes: 168 additions & 0 deletions examples/01-transient_analyses/03-mode_superposition_transient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Copyright (C) 2020 - 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.

"""
.. _ref_msup_transient:

Mode-superposition transient analysis
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This example shows how to postprocess a mode-superposition transient analysis
and visualize the outputs. It also shows how to select modes for the modal expansion.

"""

# Import the necessary modules
from ansys.dpf import core as dpf
from ansys.dpf.core import examples

###############################################################################
# Download the mode-superposition transient result example. This example is
# not included in DPF-Core by default to speed up the installation.
# Downloading this example should take only a few seconds.
#
# Next, create the model and display the state of the result. This mode-superposition transient
# result file contains several individual results, each at a different timestamp, automatically
# expanded on all available modes.

# mode_superposition_transient_files = examples.find_msup_transient()
mode_superposition_transient_files = {
# "rst": r"D:\ANSYSDev\Sandbox\UnitTestDataFiles\expansion\msup\Transient\plate1\file.rst",
"transient_rst": r"D:\ANSYSDev\resources\Innovation_courses\IntroductionToMSUPTransientAnalysis\IntroductionToMSUPTransientAnalysis_files\dp0\SYS-1\MECH\file.rst",
# "rdsp": r"D:\ANSYSDev\Sandbox\UnitTestDataFiles\expansion\msup\Transient\plate1\file.rdsp",
"rdsp": r"D:\ANSYSDev\resources\Innovation_courses\IntroductionToMSUPTransientAnalysis\IntroductionToMSUPTransientAnalysis_files\dp0\SYS-1\MECH\file.rdsp",
# "mode": r"D:\ANSYSDev\Sandbox\UnitTestDataFiles\expansion\msup\Transient\plate1\modal\file.mode",
"mode": r"D:\ANSYSDev\resources\Innovation_courses\IntroductionToMSUPTransientAnalysis\IntroductionToMSUPTransientAnalysis_files\dp0\SYS\MECH\file0.mode",
# "modal_rst": r"D:\ANSYSDev\Sandbox\UnitTestDataFiles\expansion\msup\Transient\plate1\modal\file.rst",
"modal_rst": r"D:\ANSYSDev\resources\Innovation_courses\IntroductionToMSUPTransientAnalysis\IntroductionToMSUPTransientAnalysis_files\dp0\SYS\MECH\file.rst",
}

###############################################################################
# Modal superposition on all modes available
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Create a datasource with upstream modal information
data_sources = dpf.DataSources(mode_superposition_transient_files["rdsp"])

up_stream_data_sources = dpf.DataSources(mode_superposition_transient_files["mode"])
up_stream_data_sources.add_file_path(mode_superposition_transient_files["modal_rst"])
data_sources.add_upstream(up_stream_data_sources)

# Load into a model
model = dpf.Model(data_sources)
print(model)

###############################################################################
# Get the expanded displacement fields for each time-step
disp = model.results.displacement.on_all_time_freqs.eval()
print(disp)

###############################################################################
# Animate the result
disp.animate(scale_factor=5.0)

###############################################################################
# Get the expanded displacement fields on selected time-steps:
disp = model.results.displacement.on_time_scoping([1, 2, 3]).eval()
print(disp)

###############################################################################
# Get the expanded displacement fields on part of the mesh, for selected time-steps:
partial_scoping = dpf.mesh_scoping_factory.nodal_scoping(
model.metadata.meshed_region.nodes.scoping.ids[:200]
)
disp = model.results.displacement.on_time_scoping([1, 2, 3]).on_mesh_scoping(partial_scoping).eval()
print(disp)

###############################################################################
# Modal superposition on selected modes
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# To select a subset of modes for expansion, you cannot use the source operators directly.
# Below is a workflow to extract results while specifying modes for expansion:

# First build a data source for the modal response factors
transient_response_ds = dpf.DataSources(result_path=mode_superposition_transient_files["rdsp"])
# Build a StreamsContainer from the DataSources to benefit from improved performance.
transient_response_sc = dpf.operators.metadata.streams_provider(
data_sources=transient_response_ds
).eval()

# Define the time-steps of interest
time_scoping = dpf.time_freq_scoping_factory.scoping_by_sets(list(range(1, 22)))

# Extract the result of interest
displacement_fc = dpf.operators.result.displacement(
streams_container=transient_response_sc, # Input here the modal response data source
time_scoping=time_scoping, # Input here the time-steps of interest
mesh_scoping=None, # Specify here the region of interest
).eval()

# The FieldsContainer contains one field per time-step, with data for each mode (entity)
print(displacement_fc)

###############################################################################
# Now scope the FieldsContainer to the modes of interest
mode_scoping = dpf.Scoping(ids=list(range(1, 3))) # Modes of interest
displacement_fc = dpf.operators.scoping.rescope_fc(
fields_container=displacement_fc,
mesh_scoping=mode_scoping, # Input here the modes of interest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you input this to the mesh_scoping, it is understood as a Nodal scoping. Is this what you mean?

Copy link
Contributor Author

@PProfizi PProfizi Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rafacanton no, interestingly enough, this displacement_fc contains fields located at Modal, which is unexpected but actually logical when using an rdsp file as input, hence the rescope on a generic Scoping (tried instantiating a Scoping as Modal but it does not work):

DPF  Fields Container
  with 21 field(s)
  defined on labels: time 

  with:
  - field 0 {time:  1} with Modal location, 1 components and 6 entities.
  - field 1 {time:  2} with Modal location, 1 components and 6 entities.
  - field 2 {time:  3} with Modal location, 1 components and 6 entities.
  - field 3 {time:  4} with Modal location, 1 components and 6 entities.
  - field 4 {time:  5} with Modal location, 1 components and 6 entities.
  - field 5 {time:  6} with Modal location, 1 components and 6 entities.
  - field 6 {time:  7} with Modal location, 1 components and 6 entities.
  - field 7 {time:  8} with Modal location, 1 components and 6 entities.
  - field 8 {time:  9} with Modal location, 1 components and 6 entities.
  - field 9 {time:  10} with Modal location, 1 components and 6 entities.
  - field 10 {time:  11} with Modal location, 1 components and 6 entities.
  - field 11 {time:  12} with Modal location, 1 components and 6 entities.
  - field 12 {time:  13} with Modal location, 1 components and 6 entities.
  - field 13 {time:  14} with Modal location, 1 components and 6 entities.
  - field 14 {time:  15} with Modal location, 1 components and 6 entities.
  - field 15 {time:  16} with Modal location, 1 components and 6 entities.
  - field 16 {time:  17} with Modal location, 1 components and 6 entities.
  - field 17 {time:  18} with Modal location, 1 components and 6 entities.
  - field 18 {time:  19} with Modal location, 1 components and 6 entities.
  - field 19 {time:  20} with Modal location, 1 components and 6 entities.
  - field 20 {time:  21} with Modal location, 1 components and 6 entities.

While trying out different combinations, I realized that this is really not straightforward with the API currently in place. For example, the Modal scoping seems to exist server-side but is impossible to build client-side.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: this example is useful to showcase how to do this, but really we lack helpers regarding mode manipulation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a bit confusing, I think it's worth adding a comment to explain

).eval()

# The FieldsContainer contains one field per time-step, with data for each selected mode (entity)
print(displacement_fc)

###############################################################################
# Get the modal basis of interest, with mode selection
modal_basis_ds = dpf.DataSources(result_path=mode_superposition_transient_files["mode"])
modal_basis_ds.add_file_path(
mode_superposition_transient_files["modal_rst"]
) # Associate mesh data to the mode shapes
modal_basis_sc = dpf.operators.metadata.streams_provider(data_sources=modal_basis_ds).eval()
modal_basis_fc = dpf.operators.result.modal_basis(
streams_container=modal_basis_sc,
time_scoping=mode_scoping, # Input here the modes of interest
mesh_scoping=None, # Specify here the region of interest
).eval()

# The modal basis FieldsContainer contains one field per mode shape
print(modal_basis_fc)

###############################################################################
# Plot each mode shape
for n, modal_basis_f in enumerate(modal_basis_fc):
modal_basis_f.plot(deform_by=modal_basis_f, text=f"mode shape {n+1}")

###############################################################################
# Apply modal superposition
modal_superposition_fc = dpf.operators.math.modal_superposition(
modal_basis=modal_basis_fc, # FieldsContainer obtained via the modal_basis operator
solution_in_modal_space=displacement_fc, # FieldsContainer obtained via the result operator
time_scoping=time_scoping, # Specify here the time-steps of interest
mesh_scoping=None, # Specify here the region of interest
).eval()

# We obtain the displacement fields at each time-step, with modal superposition
print(modal_superposition_fc)

###############################################################################
# Animate the result
modal_superposition_fc.animate(scale_factor=5.0)
4 changes: 3 additions & 1 deletion src/ansys/dpf/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING
import warnings

if TYPE_CHECKING: # pragma: nocover
from ansys.dpf.core.scoping import Scoping
Expand Down Expand Up @@ -323,7 +324,8 @@ def _cache_streams_provider(self):
self._stream_provider.inputs.connect(self._data_sources)
try:
self._stream_provider.run()
except:
except Exception as e:
warnings.warn(f"Could not initialize the stream provider:\n{e}")
self._stream_provider = None

def release_streams(self):
Expand Down
Loading