@@ -77,6 +77,8 @@ def __init__(
7777 self ._last_display_width = display_width
7878 self ._last_display_max_colwidth = display_max_colwidth
7979
80+ self ._last_minmax : dict = {"axis" : None , "min" : None , "max" : None }
81+
8082 @property
8183 def columns (self ) -> MultiIndex :
8284 """Returns the MultiIndex for the columns of the DataFrame."""
@@ -513,7 +515,7 @@ def treat_elemental_nodal(treat_lines, pos, n_comp, n_ent, n_lines):
513515 else empty
514516 for i in range (len (combination ))
515517 ]
516- to_append .append (empty )
518+ to_append .append (empty ) # row where row index headers are
517519 # Get data in the FieldsContainer for those positions
518520 # Create label_space from combination
519521 label_space = {}
@@ -833,3 +835,201 @@ def animate(
833835 return fc .animate (
834836 save_as = save_as , deform_by = deform_by , scale_factor = scale_factor , ** kwargs
835837 )
838+
839+ def min (self , axis : Union [int , str , None ] = 0 ) -> Union [DataFrame , float ]:
840+ """Return the minimum value over the requested axis.
841+
842+ Parameters
843+ ----------
844+ axis:
845+ Axis to perform minimum across.
846+ Defaults to the MeshIndex (0), the row index containing mesh entity IDs.
847+ This computes the minimum across the mesh for each set.
848+ Can also be the SetIndex (1), the column index containing set (time/frequency) IDs.
849+ This computes the minimum across sets (time/frequency) for each mesh entity.
850+
851+ Returns
852+ -------
853+ A scalar if the result of the query is a single number,
854+ or a DataFrame if several numbers along one or several axes.
855+
856+ Examples
857+ --------
858+ >>> from ansys.dpf import post
859+ >>> from ansys.dpf.post import examples
860+ >>> simulation = post.StaticMechanicalSimulation(examples.download_crankshaft())
861+ >>> displacement = simulation.displacement(all_sets=True)
862+ >>> # Compute the maximum displacement value for each component at each time-step
863+ >>> minimum_over_mesh = displacement.min(axis="node_ids")
864+ >>> print(minimum_over_mesh) # doctest: +NORMALIZE_WHITESPACE
865+ results U (m)
866+ set_ids 1 2 3
867+ components
868+ X -7.4732e-04 -1.5081e-03 -2.2755e-03
869+ Y -4.0138e-04 -8.0316e-04 -1.2014e-03
870+ Z -2.1555e-04 -4.3299e-04 -6.5101e-04
871+ >>> # Compute the maximum displacement for each node and component across time
872+ >>> minimum_over_time = displacement.min(axis="set_ids")
873+ >>> print(minimum_over_time) # doctest: +NORMALIZE_WHITESPACE
874+ results U (m)
875+ node_ids components
876+ 4872 X -3.4137e-05
877+ Y 5.1667e-04
878+ Z -4.1346e-06
879+ 9005 X -5.5625e-05
880+ Y 4.8445e-04
881+ Z -4.9795e-07
882+ ...
883+ >>> # Compute the maximum displacement overall
884+ >>> minimum_overall = minimum_over_time.min()
885+ >>> print(minimum_overall) # doctest: +NORMALIZE_WHITESPACE
886+ results U (m)
887+ components
888+ X -2.2755e-03
889+ Y -1.2014e-03
890+ Z -6.5101e-04
891+ """
892+ self ._query_min_max (axis )
893+ return self ._last_minmax ["min" ]
894+
895+ def max (self , axis : Union [int , str , None ] = 0 ) -> Union [DataFrame , float ]:
896+ """Return the maximum value over the requested axis.
897+
898+ Parameters
899+ ----------
900+ axis:
901+ Axis to perform maximum across.
902+ Defaults to the MeshIndex (0), the row index containing mesh entity IDs.
903+ This computes the maximum across the mesh for each set.
904+ Can also be the SetIndex (1), the column index containing set (time/frequency) IDs.
905+ This computes the maximum across sets (time/frequency) for each mesh entity.
906+
907+ Returns
908+ -------
909+ A scalar if the result of the query is a single number,
910+ or a DataFrame if several numbers along one or several axes.
911+
912+ Examples
913+ --------
914+ >>> from ansys.dpf import post
915+ >>> from ansys.dpf.post import examples
916+ >>> simulation = post.StaticMechanicalSimulation(examples.download_crankshaft())
917+ >>> displacement = simulation.displacement(all_sets=True)
918+ >>> # Compute the maximum displacement value for each component at each time-step
919+ >>> maximum_over_mesh = displacement.max(axis="node_ids")
920+ >>> print(maximum_over_mesh) # doctest: +NORMALIZE_WHITESPACE
921+ results U (m)
922+ set_ids 1 2 3
923+ components
924+ X 7.3303e-04 1.4495e-03 2.1441e-03
925+ Y 1.3962e-03 2.7884e-03 4.1656e-03
926+ Z 2.1567e-04 4.3321e-04 6.5135e-04
927+ >>> # Compute the maximum displacement for each node and component across time
928+ >>> maximum_over_time = displacement.max(axis="set_ids")
929+ >>> print(maximum_over_time) # doctest: +NORMALIZE_WHITESPACE
930+ results U (m)
931+ node_ids components
932+ 4872 X 5.6781e-06
933+ Y 1.5417e-03
934+ Z -2.6398e-06
935+ 9005 X -2.6323e-06
936+ Y 1.4448e-03
937+ Z 5.3134e-06
938+ ...
939+ >>> # Compute the maximum displacement overall
940+ >>> maximum_overall = maximum_over_time.max()
941+ >>> print(maximum_overall) # doctest: +NORMALIZE_WHITESPACE
942+ results U (m)
943+ components
944+ X 2.1441e-03
945+ Y 4.1656e-03
946+ Z 6.5135e-04
947+ """
948+ self ._query_min_max (axis )
949+ return self ._last_minmax ["max" ]
950+
951+ def _query_min_max (self , axis : Union [int , str , None ]) -> None :
952+ """Create a DPF workflow based on the query arguments for min/max."""
953+ # Translate None query to empty dict
954+ if axis in [None , 0 , self .index .mesh_index .name ]:
955+ axis = 0
956+ elif axis in [1 , ref_labels .set_ids ]:
957+ axis = 1
958+ else :
959+ raise ValueError (f"'{ axis } ' is not an available axis value." )
960+
961+ # print(f"{axis=}")
962+ # If same query as last and last is not None, do not change
963+ if self ._last_minmax ["axis" ] == axis and not self ._last_minmax ["axis" ] is None :
964+ return
965+ # If in need of an update, create the appropriate workflow
966+ wf = dpf .Workflow (server = self ._fc ._server )
967+ wf .progress_bar = False
968+
969+ # If over mesh
970+ if axis == 0 :
971+ min_max_op = dpf .operators .min_max .min_max_over_label_fc (
972+ fields_container = self ._fc ,
973+ label = "time" ,
974+ server = self ._fc ._server ,
975+ )
976+ # Here the fields are located on the label ("time"), so we have to "transpose" it.
977+ # Extract the data for each time (entity) from the field and create a fields_container
978+
979+ min_fc = dpf .FieldsContainer (server = self ._fc ._server )
980+ min_fc .add_label (label = "time" )
981+ min_field = min_max_op .outputs .field_min ()
982+ for i , time in enumerate (min_field .scoping .ids ):
983+ min_fc .add_field (
984+ label_space = {"time" : time },
985+ field = dpf .fields_factory .field_from_array (
986+ arr = min_field .get_entity_data (i ),
987+ server = self ._fc ._server ,
988+ ),
989+ )
990+
991+ max_fc = dpf .FieldsContainer (server = self ._fc ._server )
992+ max_fc .add_label (label = "time" )
993+ max_field = min_max_op .outputs .field_max ()
994+ for i , time in enumerate (max_field .scoping .ids ):
995+ max_fc .add_field (
996+ label_space = {"time" : time },
997+ field = dpf .fields_factory .field_from_array (
998+ arr = max_field .get_entity_data (i ),
999+ server = self ._fc ._server ,
1000+ ),
1001+ )
1002+
1003+ index = MultiIndex (
1004+ indexes = [i for i in self .index if i != self .index .mesh_index ]
1005+ )
1006+ columns = self .columns
1007+
1008+ # If over time
1009+ else :
1010+ min_max_op = dpf .operators .min_max .min_max_over_time_by_entity (
1011+ fields_container = self ._fc ,
1012+ server = self ._fc ._server ,
1013+ )
1014+ wf .set_output_name ("min" , min_max_op .outputs .min )
1015+ wf .set_output_name ("max" , min_max_op .outputs .max )
1016+
1017+ index = self .index
1018+ columns = MultiIndex (
1019+ indexes = [c for c in self .columns if c != self .columns .set_ids ]
1020+ )
1021+
1022+ min_fc = wf .get_output ("min" , dpf .types .fields_container )
1023+ max_fc = wf .get_output ("max" , dpf .types .fields_container )
1024+
1025+ self ._last_minmax ["min" ] = DataFrame (
1026+ data = min_fc ,
1027+ index = index ,
1028+ columns = columns ,
1029+ )
1030+ self ._last_minmax ["max" ] = DataFrame (
1031+ data = max_fc ,
1032+ index = index ,
1033+ columns = columns ,
1034+ )
1035+ self ._last_minmax ["axis" ] = axis
0 commit comments