diff --git a/_unittest/test_98_Icepak.py b/_unittest/test_98_Icepak.py index b9d858fb6ea..5c35da5e11c 100644 --- a/_unittest/test_98_Icepak.py +++ b/_unittest/test_98_Icepak.py @@ -1278,3 +1278,62 @@ def test_68_mesh_priority_3d_comp(self, add_app): assert app.mesh.add_priority(entity_type=2, comp_name="all_3d_objects1", priority=2) app.close_project(name="3d_comp_mesh_prio_test", save_project=False) + + def test_69_recirculation_boundary(self): + box = self.aedtapp.modeler.create_box([5, 5, 5], [1, 2, 3], "BlockBoxEmpty", "copper") + box.solve_inside = False + assert not self.aedtapp.assign_recirculation_opening( + [box.top_face_x, box.bottom_face_x, box.bottom_face_y], box.top_face_x, flow_assignment="10kg_per_s_m2" + ) + assert self.aedtapp.assign_recirculation_opening( + [box.top_face_x, box.bottom_face_x], box.top_face_x, conductance_external_temperature="25cel" + ) + assert self.aedtapp.assign_recirculation_opening( + [box.top_face_x, box.bottom_face_x], box.top_face_x, start_time="0s" + ) + self.aedtapp.solution_type = "Transient" + assert self.aedtapp.assign_recirculation_opening([box.top_face_x, box.bottom_face_x], box.top_face_x) + assert self.aedtapp.assign_recirculation_opening([box.top_face_x.id, box.bottom_face_x.id], box.top_face_x.id) + assert not self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Conductance", + flow_direction=[1], + ) + temp_dict = {"Function": "Square Wave", "Values": ["1cel", "0s", "1s", "0.5s", "0cel"]} + flow_dict = {"Function": "Sinusoidal", "Values": ["0kg_per_s_m2", 1, 1, "1s"]} + recirc = self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Temperature", + assignment_value=temp_dict, + flow_assignment=flow_dict, + ) + assert recirc + assert recirc.update() + self.aedtapp.solution_type = "SteadyState" + assert not self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Temperature", + assignment_value=temp_dict, + flow_assignment=flow_dict, + ) + assert not self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Temperature", + flow_direction="Side", + ) + assert self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Temperature", + flow_direction=[0, 1, 0], + ) + assert not self.aedtapp.assign_recirculation_opening( + [box.top_face_x.id, box.bottom_face_x.id], + box.top_face_x.id, + thermal_specification="Temperature", + flow_assignment=flow_dict, + ) diff --git a/pyaedt/icepak.py b/pyaedt/icepak.py index 474ba02677e..0690a92e050 100644 --- a/pyaedt/icepak.py +++ b/pyaedt/icepak.py @@ -4869,3 +4869,182 @@ def assign_symmetry_wall( raise SystemExit except (GrpcApiError, SystemExit): return None + + @pyaedt_function_handler() + def assign_recirculation_opening(self, face_list, extract_face, thermal_specification="Temperature", + assignment_value="0cel", conductance_external_temperature=None, + flow_specification="Mass Flow", flow_assignment="0kg_per_s_m2", + flow_direction=None, start_time=None, end_time=None, boundary_name=None): + """Assign recirculation faces. + + Parameters + ---------- + face_list : list + List of modeler.cad.elements3d.FacePrimitive or of integers + containing faces ids. + extract_face : modeler.cad.elements3d.FacePrimitive, int + Face of the face on the extract side. + thermal_specification : str, optional + Type of the thermal assignment across the two recirculation + faces. Options are ``"Conductance"``, ``"Heat Input"`` and + ``"Temperature"``. Default is ``"Temperature"``. + assignment_value : str or dict, optional + String with value and units of the thermal assignment. For a + transient assignment, a dictionary can be used. The dictionary + should contain two keys: ``"Function"`` and ``"Values"``. + - For the ``"Function"`` key, acceptable values are + ``"Exponential"``, ``"Linear"``, ``"Piecewise Linear"``, + ``"Power Law"``, ``"Sinusoidal"``, and ``"Square Wave"``. + - For the ``"Values"`` key, a list of strings containing the + parameters required by the ``"Function"`` key selection. For + example, when``"Linear"`` is set as the ``"Function"`` key, two + parameters are required: the value of the variable at t=0 and the + slope of the line. For the parameters required by each + ``"Function"`` key selection, see the Icepak documentation. + The parameters must contain the units where needed. + The default value is ``"0cel"``. + conductance_external_temperature : str, optional + External temperature value, needed if ``thermal_specification`` + is set to ``"Conductance"``. Default is ``None``. + flow_specification : str, optional + Flow specification for the recirculation zone. Available + options are: ``"Mass Flow"``, ``"Mass Flux"``, and + ``"Volume Flow"``. The default value is ``"Mass Flow"``. + flow_assignment : str or dict, optional + String with value and units of the flow assignment. For a + transient assignment, a dictionary can be used. The dictionary + should contain two keys: ``"Function"`` and ``"Values"``. + - For the ``"Function"`` key, acceptable values are + ``"Exponential"``, ``"Linear"``, ``"Piecewise Linear"``, + ``"Power Law"``, ``"Sinusoidal"``, and ``"Square Wave"``. + - For the ``"Values"`` key, a list of strings containing the + parameters required by the ``"Function"`` key selection. For + example, when``"Linear"`` is set as the ``"Function"`` key, two + parameters are required: the value of the variable at t=0 and the + slope of the line. For the parameters required by each + ``"Function"`` key selection, see the Icepak documentation. + The parameters must contain the units where needed. + The default value is ``"0kg_per_s_m2"``. + flow_direction : list, optional + Flow direction enforced at the recirculation zone. The default value + is ``None`` in which case the normal direction is used. + start_time : str, optional + Start of the time interval. Relevant only if the simulation is + transient. The default value is ``"0s"``. + end_time : str, optional + End of the time interval. Relevant only if the simulation is + transient. The default value is ``"0s"``. + boundary_name : str, optional + Name of the recirculation boundary. The default is ``None``, in + which case the boundary is automatically generated. + + Returns + ------- + :class:`pyaedt.modules.Boundary.BoundaryObject` + Boundary object when successful or ``None`` when failed. + + References + ---------- + + >>> oModule.AssignRecircBoundary + + Examples + -------- + >>> from pyaedt import Icepak + >>> ipk = Icepak() + >>> ipk.solution_type = "Transient" + >>> box = ipk.modeler.create_box([5, 5, 5], [1, 2, 3], "BlockBoxEmpty", "copper") + >>> box.solve_inside = False + >>> recirc = ipk.assign_recirculation_opening([box.top_face_x, box.bottom_face_x], box.top_face_x, + >>> flow_assignment="10kg_per_s_m2") + + """ + if not len(face_list) == 2: + self.logger.error("Recirculation boundary condition must be assigned to two faces.") + return False + if conductance_external_temperature is not None and thermal_specification is not "Conductance": + self.logger.warning( + '``conductance_external_temperature`` will not have any effect unless the ``thermal_specification`` ' + 'is ``"Conductance"``.') + if conductance_external_temperature is not None and thermal_specification is not "Conductance": + self.logger.warning( + '``conductance_external_temperature`` needs to be specified when ``thermal_specification`` ' + 'is ``"Conductance"``. Setting ``conductance_external_temperature`` to ``"AmbientTemp"``.') + if (start_time is not None or end_time is not None) and not self.solution_type == "Transient": + self.logger.warning( + '``start_time`` and ``end_time`` will not have any effect unless for steady-state simulations.') + elif self.solution_type == "Transient" and not (start_time and end_time): + self.logger.warning( + '``start_time`` and ``end_time`` should be declared for transient simulations. Setting them to "0s".') + start_time = "0s" + end_time = "0s" + assignment_dict = { + "Conductance": "Conductance", + "Heat Input": "Heat Flow", + "Temperature": "Temperature Change" + } + props = {} + if not isinstance(face_list[0], int): + face_list = [f.id for f in face_list] + props["Faces"] = face_list + if isinstance(extract_face, int): + extract_face = [extract_face] + else: + extract_face = [extract_face.id] + props["ExtractFace"] = extract_face + props["Thermal Condition"] = thermal_specification + if isinstance(assignment_value, dict): + if not self.solution_type == "Transient": + self.logger.error("Transient assignment is supported only in transient designs.") + return None + assignment = self._parse_variation_data( + assignment_dict[thermal_specification], + "Transient", + variation_value=assignment_value["Values"], + function=assignment_value["Function"], + ) + props.update(assignment) + else: + props[assignment_dict[thermal_specification]] = assignment_value + if thermal_specification == "Conductance": + props["External Temp"] = conductance_external_temperature + if isinstance(flow_assignment, dict): + if not self.solution_type == "Transient": + self.logger.error("Transient assignment is supported only in transient designs.") + return None + assignment = self._parse_variation_data( + flow_specification + " Rate", + "Transient", + variation_value=flow_assignment["Values"], + function=flow_assignment["Function"], + ) + props.update(assignment) + else: + props[flow_specification + " Rate"] = flow_assignment + if flow_direction is None: + props["Supply Flow Direction"] = "Normal" + else: + props["Supply Flow Direction"] = "Specified" + if not (isinstance(flow_direction, list)): + self.logger.error("``flow_direction`` can be only ``None`` or a list of strings or floats.") + return False + elif len(flow_direction) != 3: + self.logger.error("``flow_direction`` must have only three components.") + return False + for direction, val in zip(["X", "Y", "Z"], flow_direction): + props[direction] = str(val) + if self.solution_type == "Transient": + props["Start"] = start_time + props["End"] = end_time + if not boundary_name: + boundary_name = generate_unique_name("Recirculating") + + bound = BoundaryObject(self, boundary_name, props, "Recirculating") + try: + if bound.create(): + self._boundaries[bound.name] = bound + return bound + else: # pragma: no cover + raise SystemExit + except (GrpcApiError, SystemExit): # pragma: no cover + return None diff --git a/pyaedt/modules/Boundary.py b/pyaedt/modules/Boundary.py index 630fd3937cf..fccfc365197 100644 --- a/pyaedt/modules/Boundary.py +++ b/pyaedt/modules/Boundary.py @@ -556,6 +556,8 @@ def create(self): self._app.oboundary.AssignStationaryWallBoundary(self._get_args()) elif bound_type == "Symmetry Wall": self._app.oboundary.AssignSymmetryWallBoundary(self._get_args()) + elif bound_type == "Recirculating": + self._app.oboundary.AssignRecircBoundary(self._get_args()) elif bound_type == "Resistance": self._app.oboundary.AssignResistanceBoundary(self._get_args()) elif bound_type == "Conducting Plate": @@ -728,6 +730,8 @@ def update(self): self._app.oboundary.EditStationaryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover elif bound_type == "Symmetry Wall": self._app.oboundary.EditSymmetryWallBoundary(self._boundary_name, self._get_args()) # pragma: no cover + elif bound_type == "Recirculating": + self._app.oboundary.EditRecircBoundary(self._boundary_name, self._get_args()) elif bound_type == "Resistance": self._app.oboundary.EditResistanceBoundary(self._boundary_name, self._get_args()) # pragma: no cover elif bound_type == "Conducting Plate":