Skip to content

Commit

Permalink
Fix iselect, update DataFrame.axes, Naming convention (#304)
Browse files Browse the repository at this point in the history
* Add test for DataFrame.animate. Not ready yet.

* DataFrame.axes returns [df.index, df.columns] just like in Pandas.

* Make naming coherent between result queries and Index names

* Fix iselect with list of indices on label

* Fix test_simulation/test_elemental_ns_on_nodal_result

* Update 01-static-simulation.py and 02-modal-simulation.py with new axis naming.

* Fix and update 03-transient-simulation.py

* Fix multi-label selection (do not raise 'unknown axis' anymore) and update 04-harmonic-complex-results.py
  • Loading branch information
PProfizi committed Mar 8, 2023
1 parent 23e8c30 commit c3d3b7c
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 171 deletions.
12 changes: 7 additions & 5 deletions examples/00-Different-analysis-types/01-static-simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@
# ---------------------------------

# To get X displacements
x_displacement = displacement.select(comp="X")
x_displacement = displacement.select(components="X")
print(x_displacement)


# equivalent to
# equivalent to
x_displacement = simulation.displacement(components=["X"])
print(x_displacement)

# plot
x_displacement.plot()

# extract displacement on specific nodes
nodes_displacement = displacement.select(node=[1, 10, 100])
nodes_displacement = displacement.select(node_ids=[1, 10, 100])
nodes_displacement.plot()

# equivalent to:
Expand All @@ -66,7 +66,9 @@
# ---------------------------------
# Compute the norm of displacement on a selection of nodes

nodes_displacement = simulation.displacement(node_ids=simulation.mesh.node_ids[10:], norm=True)
nodes_displacement = simulation.displacement(
node_ids=simulation.mesh.node_ids[10:], norm=True
)
print(nodes_displacement)
nodes_displacement.plot()

Expand All @@ -87,7 +89,7 @@
print(elemental_stress)

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

# Compute nodal eqv stresses from the result file
Expand Down
4 changes: 2 additions & 2 deletions examples/00-Different-analysis-types/02-modal-simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
print(displacement_norm)

for set_id in simulation.set_ids:
displacement_norm.plot(set_id=set_id)
displacement_norm.plot(set_ids=set_id)


###############################################################################
Expand All @@ -61,4 +61,4 @@
print(displacement_norm)

for set_id in modes:
displacement_norm.plot(set_id=set_id)
displacement_norm.plot(set_ids=set_id)
20 changes: 14 additions & 6 deletions examples/00-Different-analysis-types/03-transient-simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,31 @@
# Extract displacement at all times or on a selection
# ---------------------------------------------------

# query the displacement vectorial field for all times
displacement = simulation.displacement(all_sets=True)
print(displacement)
displacement.animate(deform=True)
# animation shows the norm of vectorial fields with several components
displacement.animate(deform=True, title="U")


# equivalent to
# get specific components with "components"
x_displacement = simulation.displacement(all_sets=True, components=["X"])
print(x_displacement)
displacement.animate(deform=True)
x_displacement.animate(deform=True, title="UX")


# get the norm of a vectorial result with "norm=True"
displacement_norm = simulation.displacement(all_sets=True, norm=True)
print(displacement_norm)
displacement_norm.animate(deform=True, title="U norm")

# get the available time set ids in the simulation
print(simulation.set_ids)

# extract displacement on given time steps or select the times steps from teh already evaluated
# extract displacement on given time steps or select the times steps from the already evaluated
# displacements
displacement = simulation.displacement(set_ids=simulation.set_ids[5:])
displacement = displacement.select(set_id=simulation.set_ids[5:])
displacement = displacement.select(set_ids=simulation.set_ids[5:])
print(displacement)

###############################################################################
Expand All @@ -73,4 +81,4 @@
# ---------------------------------

strain_eqv = simulation.elastic_strain_eqv_von_mises_nodal(all_sets=True)
strain_eqv.animate()
strain_eqv.animate(deform=True, title="E_eqv")
30 changes: 18 additions & 12 deletions examples/00-Different-analysis-types/04-harmonic-complex-results.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,17 @@
displacement = simulation.displacement(set_ids=[1, 2])
print(displacement)

subdisp = displacement.select(complex=0, set_id=1)
subdisp.plot()
subdisp = displacement.select(complex=0, set_ids=1)
print(subdisp)
subdisp.plot(title="U tot real")

subdisp = displacement.select(complex=1, set_id=1)
subdisp.plot()
subdisp = displacement.select(complex=1, set_ids=1)
print(subdisp)
subdisp.plot(title="U tot imaginary")

subdisp = displacement.select(complex=0, set_id=2)
subdisp.plot()
subdisp = displacement.select(complex=0, set_ids=2)
print(subdisp)
subdisp.plot(title="U tot real")

###############################################################################
# Extract stress eqv over a list of frequencies sets
Expand All @@ -60,11 +63,14 @@
stress_eqv = simulation.stress_eqv_von_mises_nodal(set_ids=[1, 2])
print(stress_eqv)

sub_eqv = stress_eqv.select(complex=0, set_id=1)
sub_eqv.plot()
sub_eqv = stress_eqv.select(complex=0, set_ids=1)
print(sub_eqv)
sub_eqv.plot(title="S_eqv real")

sub_eqv = stress_eqv.select(complex=1, set_id=1)
sub_eqv.plot()
sub_eqv = stress_eqv.select(complex=1, set_ids=1)
print(sub_eqv)
sub_eqv.plot(title="S_eqv imaginary")

sub_eqv = stress_eqv.select(complex=0, set_id=2)
sub_eqv.plot()
sub_eqv = stress_eqv.select(complex=0, set_ids=2)
print(sub_eqv)
sub_eqv.plot(title="S_eqv real")
131 changes: 70 additions & 61 deletions src/ansys/dpf/post/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
MultiIndex,
ResultsIndex,
SetIndex,
ref_labels,
)

display_width = 80
display_max_colwidth = 10
display_width = 100
display_max_colwidth = 12
display_max_lines = 6


Expand Down Expand Up @@ -91,11 +92,9 @@ def index(self) -> Union[MultiIndex, Index]:
return self._index

@property
def axes(self) -> List[str]:
"""Returns a list of the axes of the DataFrame with the row Index and the column Index."""
names = self.index.names
names.extend(self.columns.names)
return names
def axes(self) -> List[MultiIndex]:
"""Returns a list with the row MultiIndex first and the columns MultiIndex second."""
return [self.index, self.columns]

@property
def results_index(self) -> Union[ResultsIndex, None]:
Expand Down Expand Up @@ -127,6 +126,22 @@ def _core_object(self):
"""Returns the underlying PyDPF-Core class:`ansys.dpf.core.FieldsContainer` object."""
return self._fc

def _filter_arguments(self, arguments):
"""Filter arguments based on available Index names."""
rows, columns = self.axes
axes_names = rows.names
axes_names.extend(columns.names)
axis_arguments = {}
keys = list(arguments.keys())
for argument in keys:
if argument in axes_names:
axis_arguments[argument] = arguments.pop(argument)
# raise ValueError(
# f"The DataFrame has no axis {argument}, cannot select it. "
# f"Available axes are: {axes_names}."
# )
return axis_arguments, arguments

def select(self, **kwargs) -> DataFrame:
"""Returns a new DataFrame based on selection criteria (value-based).
Expand All @@ -146,16 +161,9 @@ def select(self, **kwargs) -> DataFrame:
A DataFrame of the selected values.
"""
# Check for invalid arguments
axes = self.axes
for argument in kwargs.keys():
if argument not in axes:
raise ValueError(
f"The DataFrame has no axis {argument}, cannot select it. "
f"Available axes are: {axes}."
)
if "set_id" in kwargs.keys():
kwargs["time"] = kwargs["set_id"]
axis_kwargs, _ = self._filter_arguments(arguments=kwargs)
if ref_labels.set_ids in axis_kwargs.keys():
axis_kwargs[ref_labels.time] = axis_kwargs[ref_labels.set_ids]
# Initiate a workflow
server = self._fc._server
wf = dpf.Workflow(server=server)
Expand All @@ -176,30 +184,37 @@ def select(self, **kwargs) -> DataFrame:
results_index = self.results_index

# Treat selection on a label
if any([label in kwargs.keys() for label in self._fc.labels]):
if any([label in axis_kwargs.keys() for label in self._fc.labels]):
fc = dpf.FieldsContainer()
fc.labels = self._fc.labels
for i, field in enumerate(self._fc):
to_add = True
label_space = self._fc.get_label_space(i)
for fc_key in label_space.keys():
if fc_key in kwargs.keys() or (
fc_key == "time" and "set_id" in kwargs.keys()
if fc_key in axis_kwargs.keys() or (
fc_key == ref_labels.time
and ref_labels.set_ids in axis_kwargs.keys()
):
key = fc_key
if fc_key == "time" and "set_id" in kwargs.keys():
key = "set_id"
if not isinstance(kwargs[key], list):
kwargs[key] = [kwargs[key]]
if label_space[fc_key] not in kwargs[key]:
continue
if (
fc_key == ref_labels.time
and ref_labels.set_ids in axis_kwargs.keys()
):
key = ref_labels.set_ids
if not isinstance(axis_kwargs[key], list):
axis_kwargs[key] = [axis_kwargs[key]]
if label_space[fc_key] not in axis_kwargs[key]:
to_add = False
break
if to_add:
fc.add_field(label_space=label_space, field=field)
input_fc = fc

# # Treat selection on components
if "comp" in kwargs.keys():
if ref_labels.components in axis_kwargs.keys():
from ansys.dpf.post.simulation import component_label_to_index

comp_to_extract = kwargs["comp"]
comp_to_extract = axis_kwargs[ref_labels.components]
if not isinstance(comp_to_extract, list):
comp_to_extract = [comp_to_extract]
component_indexes = [component_label_to_index[c] for c in comp_to_extract]
Expand All @@ -217,19 +232,19 @@ def select(self, **kwargs) -> DataFrame:
else:
mesh_index_name = self.index.mesh_index.name
if (
mesh_index_name in kwargs.keys()
mesh_index_name in axis_kwargs.keys()
and mesh_index.location != locations.elemental_nodal
):
if "node" in mesh_index_name:
node_ids = kwargs[mesh_index_name]
if ref_labels.node_ids in mesh_index_name:
node_ids = axis_kwargs[mesh_index_name]
if not isinstance(node_ids, (DPFArray, list)):
node_ids = [node_ids]
mesh_scoping = dpf.mesh_scoping_factory.nodal_scoping(
node_ids=node_ids,
server=server,
)
elif "element" in mesh_index_name:
element_ids = kwargs[mesh_index_name]
elif ref_labels.element_ids in mesh_index_name:
element_ids = axis_kwargs[mesh_index_name]
if not isinstance(element_ids, list):
element_ids = [element_ids]
mesh_scoping = dpf.mesh_scoping_factory.elemental_scoping(
Expand All @@ -249,7 +264,7 @@ def select(self, **kwargs) -> DataFrame:
out = rescope_fc.outputs.fields_container
mesh_index = MeshIndex(location=location, values=mesh_scoping.ids)
elif (
mesh_index_name in kwargs.keys()
mesh_index_name in axis_kwargs.keys()
and mesh_index.location == locations.elemental_nodal
):
raise NotImplementedError(
Expand Down Expand Up @@ -306,24 +321,21 @@ def iselect(self, **kwargs) -> DataFrame:
A DataFrame of the selected values.
"""
# Check for invalid arguments
axes = self.axes
for argument in kwargs.keys():
if argument not in axes:
raise ValueError(
f"The DataFrame has no axis {argument}, cannot select it. "
f"Available axes are: {axes}."
)
for label in kwargs.keys():
indices = kwargs[label]
axis_kwargs, _ = self._filter_arguments(arguments=kwargs)
for label in axis_kwargs.keys():
if label in self.index.names:
ids = getattr(self.index, label).values[indices]
values = getattr(self.index, label).values
else:
values = getattr(self.columns, label).values
indices = axis_kwargs[label]
if isinstance(indices, list):
ids = [values[i] for i in indices]
else:
ids = getattr(self.columns, label).values[indices]
ids = values[indices]
if isinstance(ids, DPFArray):
ids = ids.tolist()
kwargs[label] = ids
return self.select(**kwargs)
axis_kwargs[label] = ids
return self.select(**axis_kwargs)

def __len__(self):
"""Return the length of the DataFrame."""
Expand Down Expand Up @@ -477,8 +489,8 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
label_space = {}
for label_name in self.labels:
value = combination[label_positions_in_combinations[label_name]]
if label_name == "set_id":
label_name = "time"
if label_name == ref_labels.set_ids:
label_name = ref_labels.time
label_space[label_name] = value
fields = self._fc.get_fields(label_space=label_space)
values = []
Expand Down Expand Up @@ -616,6 +628,8 @@ def plot(self, shell_layer=shell_layers.top, **kwargs):
`set_id` 1 by using `df.plot(set_ids=1)`.
One can get the list of available axes using
:func:`DataFrame.axes <ansys.dpf.post.DataFrame.axes>`.
Also supports additional keyword arguments for the plotter. For additional keyword
arguments, see ``help(pyvista.plot)``.
Returns
-------
Expand All @@ -625,16 +639,9 @@ def plot(self, shell_layer=shell_layers.top, **kwargs):
from ansys.dpf.core.plotter import DpfPlotter as Plotter

if kwargs != {}:
# Check for invalid arguments
axes = self.axes
for argument in kwargs.keys():
if argument not in axes:
raise ValueError(
f"The DataFrame has no axis {argument}, cannot plot it. "
f"Available axes are: {axes}."
)
axis_kwargs, kwargs = self._filter_arguments(arguments=kwargs)
# Construct the associated label_space
fc = self.select(**kwargs)._fc
fc = self.select(**axis_kwargs)._fc
else:
# If no kwarg was given, construct a default label_space
fc = self._fc
Expand Down Expand Up @@ -683,7 +690,7 @@ def plot(self, shell_layer=shell_layers.top, **kwargs):

def animate(
self,
save_as: Union[PathLike, None] = None,
save_as: Union[PathLike, str, None] = None,
deform: bool = False,
scale_factor: Union[List[float], float] = 1.0,
**kwargs,
Expand Down Expand Up @@ -718,6 +725,8 @@ def animate(
f"unable to animate on the deformed mesh:\n{e}"
)
)
else:
deform_by = False
return self._fc.animate(
save_as=save_as, deform_by=deform_by, scale_factor=scale_factor, **kwargs
)
)
Loading

0 comments on commit c3d3b7c

Please sign in to comment.