diff --git a/doc/api_rstgen.py b/doc/api_rstgen.py index ce24a229ecf..0ab9169a6c9 100644 --- a/doc/api_rstgen.py +++ b/doc/api_rstgen.py @@ -154,7 +154,7 @@ def _get_file_path(folder_name: str, file_name: str): "api_upgrade", "batch_ops", "datamodel_se", - "datamodel_tui", + "text_interface", "events", "field_data", "health_check", diff --git a/doc/changelog.d/5260.maintenance.md b/doc/changelog.d/5260.maintenance.md new file mode 100644 index 00000000000..9ef72742357 --- /dev/null +++ b/doc/changelog.d/5260.maintenance.md @@ -0,0 +1 @@ +Restructure some more grpc services. diff --git a/doc/changelog.d/5262.maintenance.md b/doc/changelog.d/5262.maintenance.md new file mode 100644 index 00000000000..082a49e8913 --- /dev/null +++ b/doc/changelog.d/5262.maintenance.md @@ -0,0 +1 @@ +Refactor Batch Ops service. diff --git a/doc/changelog.d/5264.maintenance.md b/doc/changelog.d/5264.maintenance.md new file mode 100644 index 00000000000..addfcfc0636 --- /dev/null +++ b/doc/changelog.d/5264.maintenance.md @@ -0,0 +1 @@ +Update monitors service. diff --git a/doc/changelog.d/5265.maintenance.md b/doc/changelog.d/5265.maintenance.md new file mode 100644 index 00000000000..e677452807a --- /dev/null +++ b/doc/changelog.d/5265.maintenance.md @@ -0,0 +1 @@ +Update solution variable diff --git a/doc/source/user_guide/legacy/tui.rst b/doc/source/user_guide/legacy/tui.rst index bd62e8b00ad..493fb031f7b 100644 --- a/doc/source/user_guide/legacy/tui.rst +++ b/doc/source/user_guide/legacy/tui.rst @@ -48,14 +48,14 @@ To see the documentation for the viscous model menu options, you can run: >>> help(solver_session.tui.define.models.viscous) Help on viscous in module ansys.fluent.core.generated.solver.tui_241 object: - class viscous(ansys.fluent.core.services.datamodel_tui.TUIMenu) + class viscous(ansys.fluent.core.services.text_interface.TUIMenu) | viscous(service, version, mode, path) | | Enters the viscous model menu. | | Method resolution order: | viscous - | ansys.fluent.core.services.datamodel_tui.TUIMenu + | ansys.fluent.core.services.text_interface.TUIMenu | builtins.object | | Methods defined here: diff --git a/src/ansys/fluent/core/_grpc_services/__init__.py b/src/ansys/fluent/core/_grpc_services/__init__.py index 6a369d8e316..7a017624d31 100644 --- a/src/ansys/fluent/core/_grpc_services/__init__.py +++ b/src/ansys/fluent/core/_grpc_services/__init__.py @@ -36,6 +36,13 @@ from ansys.fluent.core._grpc_services.application_runtime_service_v0 import ( ApplicationRuntimeService as ApplicationRuntimeServiceV0, ) +from ansys.fluent.core._grpc_services.batch_ops_service_v0 import ( + BatchOpsService as BatchOpsServiceV0, +) +from ansys.fluent.core._grpc_services.events_service import EventsService +from ansys.fluent.core._grpc_services.events_service_v0 import ( + EventsService as EventsServiceV0, +) from ansys.fluent.core._grpc_services.field_data_service import FieldDataService from ansys.fluent.core._grpc_services.field_data_service_v0 import ( FieldDataService as FieldDataServiceV0, @@ -44,6 +51,10 @@ from ansys.fluent.core._grpc_services.health_check_service_v0 import ( HealthCheckService as HealthCheckServiceV0, ) +from ansys.fluent.core._grpc_services.monitor_service import MonitorService +from ansys.fluent.core._grpc_services.monitor_service_v0 import ( + MonitorService as MonitorServiceV0, +) from ansys.fluent.core._grpc_services.object_model_service import ObjectModelService from ansys.fluent.core._grpc_services.object_model_service_v0 import ( ObjectModelService as ObjectModelServiceV0, @@ -62,6 +73,20 @@ from ansys.fluent.core._grpc_services.settings_service_v0 import ( SettingsService as SettingsServiceV0, ) +from ansys.fluent.core._grpc_services.solution_variable_service import ( + SolutionVariableService, +) +from ansys.fluent.core._grpc_services.solution_variable_service_v0 import ( + SolutionVariableService as SolutionVariableServiceV0, +) +from ansys.fluent.core._grpc_services.text_interface_service import TextInterfaceService +from ansys.fluent.core._grpc_services.text_interface_service_v0 import ( + TextInterfaceService as TextInterfaceServiceV0, +) +from ansys.fluent.core._grpc_services.transcript_service import TranscriptService +from ansys.fluent.core._grpc_services.transcript_service_v0 import ( + TranscriptService as TranscriptServiceV0, +) def _server_supports_v1(channel) -> bool: @@ -205,3 +230,53 @@ def object_model(self) -> ObjectModelService | ObjectModelServiceV0: if self._proto_version == "v1" else self._get_instantiated_grpc_service(ObjectModelServiceV0) ) + + @cached_property + def events(self) -> EventsService | EventsServiceV0: + """gRPC stub for events operations.""" + return ( + self._get_instantiated_grpc_service(EventsService) + if self._proto_version == "v1" + else self._get_instantiated_grpc_service(EventsServiceV0) + ) + + @cached_property + def batch_ops(self) -> BatchOpsServiceV0: + """gRPC stub for batch RPC operations (v0 only — no v1 implementation).""" + return self._get_instantiated_grpc_service(BatchOpsServiceV0) + + @cached_property + def transcript(self) -> TranscriptService | TranscriptServiceV0: + """gRPC stub for transcript operations.""" + return ( + self._get_instantiated_grpc_service(TranscriptService) + if self._proto_version == "v1" + else self._get_instantiated_grpc_service(TranscriptServiceV0) + ) + + @cached_property + def text_interface(self) -> TextInterfaceService | TextInterfaceServiceV0: + """gRPC stub for text interface operations.""" + return ( + self._get_instantiated_grpc_service(TextInterfaceService) + if self._proto_version == "v1" + else self._get_instantiated_grpc_service(TextInterfaceServiceV0) + ) + + @cached_property + def monitor(self) -> MonitorService | MonitorServiceV0: + """gRPC stub for monitor operations.""" + return ( + self._get_instantiated_grpc_service(MonitorService) + if self._proto_version == "v1" + else self._get_instantiated_grpc_service(MonitorServiceV0) + ) + + @cached_property + def solution_variable(self) -> SolutionVariableService | SolutionVariableServiceV0: + """gRPC stub for solution variable operations.""" + return ( + self._get_instantiated_grpc_service(SolutionVariableService) + if self._proto_version == "v1" + else self._get_instantiated_grpc_service(SolutionVariableServiceV0) + ) diff --git a/src/ansys/fluent/core/_grpc_services/application_runtime_service.py b/src/ansys/fluent/core/_grpc_services/application_runtime_service.py index 809aa8d7fd0..bfbd4197615 100644 --- a/src/ansys/fluent/core/_grpc_services/application_runtime_service.py +++ b/src/ansys/fluent/core/_grpc_services/application_runtime_service.py @@ -233,7 +233,7 @@ def get_gpu_config(self) -> bool | list[int]: case "specific_gpu_ids": return list(response.specific_gpu_ids.gpu_ids) - def start_python_journal(self, journal_name: str | None = None) -> int: + def start_python_journal(self, journal_name: str | None = None) -> str: """StartPythonJournal RPC.""" request = application_runtime_pb2.StartPythonJournalRequest() if journal_name: diff --git a/src/ansys/fluent/core/_grpc_services/application_runtime_service_v0.py b/src/ansys/fluent/core/_grpc_services/application_runtime_service_v0.py index 3546d757c43..e180999a078 100644 --- a/src/ansys/fluent/core/_grpc_services/application_runtime_service_v0.py +++ b/src/ansys/fluent/core/_grpc_services/application_runtime_service_v0.py @@ -114,7 +114,7 @@ def get_app_mode(self) -> str: case app_utilities_pb2.APP_MODE_SOLVER_AERO: return "solver_aero" - def start_python_journal(self, journal_name: str | None = None) -> int: + def start_python_journal(self, journal_name: str | None = None) -> str: """StartPythonJournal RPC.""" request = app_utilities_pb2.StartPythonJournalRequest() if journal_name: diff --git a/src/ansys/fluent/core/_grpc_services/batch_ops_service_v0.py b/src/ansys/fluent/core/_grpc_services/batch_ops_service_v0.py new file mode 100644 index 00000000000..4e38834a68f --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/batch_ops_service_v0.py @@ -0,0 +1,151 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Batch RPC service (v0 proto API).""" + +import inspect +import logging +import sys +from types import ModuleType + +import grpc + +import ansys.api.fluent.v0 as api +from ansys.api.fluent.v0 import batch_ops_pb2, batch_ops_pb2_grpc +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import GrpcErrorInterceptor + +network_logger: logging.Logger = logging.getLogger("pyfluent.networking") + + +class BatchOpsService(ServiceProtocol): + """Class wrapping the batch RPC service of Fluent (v0 proto API).""" + + _api_module = api + _proto_module = batch_ops_pb2 + + def __init__( + self, + channel: grpc.Channel, + metadata: list[tuple[str, str]], + fluent_error_state=None, + ) -> None: + """__init__ method of BatchOpsService class.""" + del fluent_error_state # unused + intercept_channel = grpc.intercept_channel( + channel, + GrpcErrorInterceptor(), + ) + self._stub = batch_ops_pb2_grpc.BatchOpsStub(intercept_channel) + self._metadata = metadata + self._proto_files: list[ModuleType] | None = None + self._response_cls_cache: dict[tuple[str, str, str], type | None] = {} + + def _ensure_proto_files(self) -> None: + """Lazily populate the list of all known proto file descriptors.""" + if self._proto_files is not None: + return + owner_proto_files = [ + mod + for _, mod in inspect.getmembers(self._api_module, inspect.ismodule) + if hasattr(mod, "DESCRIPTOR") + ] + loaded_proto_files = [ + mod + for name, mod in sys.modules.items() + if name.endswith("_pb2") and hasattr(mod, "DESCRIPTOR") + ] + self._proto_files = owner_proto_files + [ + mod for mod in loaded_proto_files if mod not in owner_proto_files + ] + + def _get_response_cls(self, package: str, service: str, method: str) -> type | None: + """Return the proto response class for the given RPC, or None if not found.""" + key = (package, service, method) + if key in self._response_cls_cache: + return self._response_cls_cache[key] + self._ensure_proto_files() + result = None + for file in self._proto_files: + file_desc = file.DESCRIPTOR + if file_desc.package == package: + service_desc = file_desc.services_by_name.get(service) + if service_desc: + method_desc = service_desc.methods_by_name.get(method) + if ( + method_desc + and not method_desc.client_streaming + and not method_desc.server_streaming + ): + response_cls_name = method_desc.output_type.name + try: + result = getattr(file, response_cls_name) + break + except AttributeError: + pass + self._response_cls_cache[key] = result + return result + + def get_op_metadata( + self, package: str, service: str, method: str + ) -> tuple[bool, type | None]: + """Return ``(is_supported, response_cls)`` for the given RPC. + + An operation is considered supported when it is not a getter and the + proto descriptor exposes a non-streaming unary response type. + """ + if method.startswith("Get") or method.startswith("get"): + return False, None + response_cls = self._get_response_cls(package, service, method) + return response_cls is not None, response_cls + + def execute(self, ops: list) -> list[tuple]: + """Execute a batch of queued operations and return ``(status, result)`` pairs. + + Execute is a bidirectional-streaming RPC: the client sends a stream of + ``ExecuteRequest`` messages and the server returns a corresponding + stream of ``ExecuteResponse`` messages. Each response is deserialized + into the appropriate proto message type before being returned. + """ + requests = ( + self._proto_module.ExecuteRequest( + package=op._package, + service=op._service_name, + method=op._method, + request_body=op._request_body, + ) + for op in ops + ) + results = [] + for op, response in zip( + ops, self._stub.Execute(requests, metadata=self._metadata) + ): + result = None + if op.response_cls is not None: + result = op.response_cls() + try: + result.ParseFromString(response.response_body) + except Exception as ex: + network_logger.warning(ex) + results.append((response.status, result)) + return results diff --git a/src/ansys/fluent/core/services/events_v1.py b/src/ansys/fluent/core/_grpc_services/events_service.py similarity index 51% rename from src/ansys/fluent/core/services/events_v1.py rename to src/ansys/fluent/core/_grpc_services/events_service.py index 5ba4218e08a..820b573656a 100644 --- a/src/ansys/fluent/core/services/events_v1.py +++ b/src/ansys/fluent/core/_grpc_services/events_service.py @@ -21,66 +21,49 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the events gRPC service of Fluent (v1 proto API). - -All shared logic lives in events.py (v0). This module adds v1-specific -pause/resume-on-solution-event RPC methods that migrated from the -ApplicationRuntime service in the v1 proto API. -""" +"""Wrapper over the events gRPC service of Fluent (v1 proto API).""" import grpc -from ansys.api.fluent.v1 import events_pb2 as EventsProtoModule -from ansys.api.fluent.v1 import events_pb2_grpc as EventsGrpcModule -from ansys.fluent.core.services.events import EventsService as _EventsServiceV0 +from ansys.api.fluent.v1 import events_pb2, events_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService +from ansys.fluent.core.services._protocols import ServiceProtocol from ansys.fluent.core.streaming_services.events_streaming_v1 import SolverEvent -class EventsService(_EventsServiceV0): +class EventsService(StreamingService, ServiceProtocol): """Class wrapping the events gRPC service of Fluent (v1 proto API).""" - def _create_stub(self, channel: grpc.Channel): - """Create the v1 gRPC stub.""" - return EventsGrpcModule.EventsStub(channel) - - def pause_solve_for( - self, request: EventsProtoModule.PauseSolveForRequest - ) -> EventsProtoModule.PauseSolveForResponse: - """PauseSolveFor RPC of Events service (v1).""" - return self._stub.PauseSolveFor(request, metadata=self._metadata) - - def resume_solve( - self, request: EventsProtoModule.ResumeSolveRequest - ) -> EventsProtoModule.ResumeSolveResponse: - """ResumeSolve RPC of Events service (v1).""" - return self._stub.ResumeSolve(request, metadata=self._metadata) - - def cancel_pause_solve( - self, request: EventsProtoModule.CancelPauseSolveRequest - ) -> EventsProtoModule.CancelPauseSolveResponse: - """CancelPauseSolve RPC of Events service (v1).""" - return self._stub.CancelPauseSolve(request, metadata=self._metadata) + def __init__( + self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state + ): + """__init__ method of EventsService class.""" + super().__init__( + stub=events_pb2_grpc.EventsStub(channel), + metadata=metadata, + ) + del fluent_error_state # unused in v1 def register_pause_on_solution_events(self, solution_event: SolverEvent) -> int: """Register pause on solution events.""" - request = EventsProtoModule.PauseSolveForRequest() - request.solution_event = EventsProtoModule.SOLUTION_EVENT_UNSPECIFIED + request = events_pb2.PauseSolveForRequest() + request.solution_event = events_pb2.SOLUTION_EVENT_UNSPECIFIED match solution_event: case SolverEvent.ITERATION_ENDED: - request.solution_event = EventsProtoModule.SOLUTION_EVENT_ITERATION + request.solution_event = events_pb2.SOLUTION_EVENT_ITERATION case SolverEvent.TIMESTEP_ENDED: - request.solution_event = EventsProtoModule.SOLUTION_EVENT_TIME_STEP - response = self.pause_solve_for(request) + request.solution_event = events_pb2.SOLUTION_EVENT_TIME_STEP + response = self._stub.PauseSolveFor(request, metadata=self._metadata) return response.registration_id def resume_on_solution_event(self, registration_id: int) -> None: """Resume on solution event.""" - request = EventsProtoModule.ResumeSolveRequest() + request = events_pb2.ResumeSolveRequest() request.registration_id = registration_id - self.resume_solve(request) + self._stub.ResumeSolve(request, metadata=self._metadata) def unregister_pause_on_solution_events(self, registration_id: int) -> None: """Unregister pause on solution events.""" - request = EventsProtoModule.CancelPauseSolveRequest() + request = events_pb2.CancelPauseSolveRequest() request.registration_id = registration_id - self.cancel_pause_solve(request) + self._stub.CancelPauseSolve(request, metadata=self._metadata) diff --git a/src/ansys/fluent/core/_grpc_services/events_service_v0.py b/src/ansys/fluent/core/_grpc_services/events_service_v0.py new file mode 100644 index 00000000000..9665b8aa90f --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/events_service_v0.py @@ -0,0 +1,46 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the events gRPC service of Fluent (v0 proto API).""" + +import grpc + +from ansys.api.fluent.v0 import events_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService +from ansys.fluent.core.services._protocols import ServiceProtocol + + +class EventsService( + StreamingService, ServiceProtocol +): # pyright: ignore[reportUnsafeMultipleInheritance] + """Class wrapping the events gRPC service of Fluent.""" + + def __init__( + self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state + ): + """__init__ method of EventsService class.""" + super().__init__( + stub=events_pb2_grpc.EventsStub(channel), + metadata=metadata, + ) + del fluent_error_state # unused in v0 diff --git a/src/ansys/fluent/core/services/monitor_v1.py b/src/ansys/fluent/core/_grpc_services/monitor_service.py similarity index 70% rename from src/ansys/fluent/core/services/monitor_v1.py rename to src/ansys/fluent/core/_grpc_services/monitor_service.py index f09d2a11fd6..f1d14f3f16e 100644 --- a/src/ansys/fluent/core/services/monitor_v1.py +++ b/src/ansys/fluent/core/_grpc_services/monitor_service.py @@ -21,17 +21,19 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the monitor gRPC service of Fluent (v1 proto API). - -All shared logic lives in monitor.py (v0). This module keeps only v1-specific -proto/stub/response-field differences. -""" +"""Wrapper over the monitor gRPC service of Fluent (v1 proto API).""" from google.protobuf.json_format import MessageToDict +import grpc -from ansys.api.fluent.v1 import monitor_pb2 as MonitorModuleV1 -from ansys.api.fluent.v1 import monitor_pb2_grpc as MonitorGrpcModuleV1 -import ansys.fluent.core.services.monitor as _v0 +from ansys.api.fluent.v1 import monitor_pb2, monitor_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import ( + BatchInterceptor, + ErrorStateInterceptor, + TracingInterceptor, +) # v1 MessageToDict produces camelCase keys for the renamed snake_case fields # (x_label → xLabel, y_label → yLabel, unit_info → unitInfo). Callers of @@ -63,28 +65,30 @@ def _normalize_monitor_set_dict_keys(data: dict) -> dict: return data -class MonitorsService(_v0.MonitorsService): - """Monitors gRPC service wrapper (v1 proto API). - - Inherits the interceptor setup and ``_create_stub`` hook from the v0 - base class. Only the stub factory and the response-field access in - ``get_monitors_info`` differ between protocol versions. - """ +class MonitorService( + StreamingService, ServiceProtocol +): # pyright: ignore[reportUnsafeMultipleInheritance] + """Class wrapping the monitor gRPC service of Fluent.""" - def _create_stub(self, intercept_channel): - """Create the v1 Monitor gRPC stub.""" - return MonitorGrpcModuleV1.MonitorStub(intercept_channel) + def __init__(self, channel: grpc.Channel, metadata, fluent_error_state): + """__init__ method of MonitorService class.""" + intercept_channel = grpc.intercept_channel( + channel, + ErrorStateInterceptor(fluent_error_state), + TracingInterceptor(), + BatchInterceptor(), + ) + self._stub = monitor_pb2_grpc.MonitorStub(intercept_channel) + self._metadata = metadata + super().__init__( + stub=self._stub, + metadata=self._metadata, + ) def get_monitors_info(self) -> dict: - """Get monitors information (v1 proto). - - Overrides v0 to use the renamed ``monitor_sets`` response field - (v0: ``monitorset``) and normalises the camelCase dict keys produced - by ``MessageToDict`` back to the v0 legacy spellings so that all - consumers remain version-agnostic. - """ + """Get monitors information (v1 proto).""" monitors_info = {} - request = MonitorModuleV1.GetMonitorsRequest() + request = monitor_pb2.GetMonitorsRequest() response = self._stub.GetMonitors(request, metadata=self._metadata) for monitor_set in response.monitor_sets: # v1: monitor_sets (v0: monitorset) monitor_info = _normalize_monitor_set_dict_keys(MessageToDict(monitor_set)) diff --git a/src/ansys/fluent/core/services/monitor.py b/src/ansys/fluent/core/_grpc_services/monitor_service_v0.py similarity index 77% rename from src/ansys/fluent/core/services/monitor.py rename to src/ansys/fluent/core/_grpc_services/monitor_service_v0.py index 4c4b57bce14..3cff15feb97 100644 --- a/src/ansys/fluent/core/services/monitor.py +++ b/src/ansys/fluent/core/_grpc_services/monitor_service_v0.py @@ -21,50 +21,41 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the monitor gRPC service of Fluent.""" +"""Wrapper over the monitor gRPC service of Fluent (v0 proto API).""" from google.protobuf.json_format import MessageToDict import grpc -from ansys.api.fluent.v0 import monitor_pb2 as MonitorModule -from ansys.api.fluent.v0 import monitor_pb2_grpc as MonitorGrpcModule +from ansys.api.fluent.v0 import monitor_pb2, monitor_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService from ansys.fluent.core.services._protocols import ServiceProtocol from ansys.fluent.core.services.interceptors import ( BatchInterceptor, ErrorStateInterceptor, TracingInterceptor, ) -from ansys.fluent.core.services.streaming import StreamingService -class MonitorsService( +class MonitorService( StreamingService, ServiceProtocol ): # pyright: ignore[reportUnsafeMultipleInheritance] """Class wrapping the monitor gRPC service of Fluent.""" def __init__(self, channel: grpc.Channel, metadata, fluent_error_state): - """__init__ method of MonitorsService class.""" + """__init__ method of MonitorService class.""" intercept_channel = grpc.intercept_channel( channel, ErrorStateInterceptor(fluent_error_state), TracingInterceptor(), BatchInterceptor(), ) - self._stub = self._create_stub(intercept_channel) + self._stub = monitor_pb2_grpc.MonitorStub(intercept_channel) self._metadata = metadata super().__init__( stub=self._stub, metadata=self._metadata, ) - def _create_stub(self, intercept_channel): - """Create the Monitor gRPC stub. - - Extracted as a hook so that the v1 adapter can swap in the v1 stub - without duplicating the entire ``__init__`` interceptor chain. - """ - return MonitorGrpcModule.MonitorStub(intercept_channel) - def get_monitors_info(self) -> dict: """Get monitors information. @@ -78,7 +69,7 @@ def get_monitors_info(self) -> dict: Dictionary containing the monitors information. """ monitors_info = {} - request = MonitorModule.GetMonitorsRequest() + request = monitor_pb2.GetMonitorsRequest() response = self._stub.GetMonitors(request, metadata=self._metadata) for monitor_set in response.monitorset: monitor_info = MessageToDict(monitor_set) diff --git a/src/ansys/fluent/core/_grpc_services/solution_variable_service.py b/src/ansys/fluent/core/_grpc_services/solution_variable_service.py new file mode 100644 index 00000000000..9c6751a2f46 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/solution_variable_service.py @@ -0,0 +1,457 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the solution variable gRPC service of Fluent (v1 proto API).""" + +import math +from typing import Any, Sequence + +import grpc +import numpy as np +import numpy.typing as npt + +from ansys.api.fluent.v1 import ( + field_data_pb2, + solution_variable_pb2, + solution_variable_pb2_grpc, +) +from ansys.fluent.core._grpc_services.field_data_service import _FieldDataConstants +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import ( + GrpcErrorInterceptor, + TracingInterceptor, +) + + +class SolutionVariables: + """Class containing information for multiple solution variables.""" + + class SolutionVariable: + """Class containing information for single solution variable.""" + + def __init__( + self, solution_variable_info: solution_variable_pb2.SolutionVariableInfo + ): + """Initialize SolutionVariable.""" + self.name = solution_variable_info.name + self.dimension = solution_variable_info.dimension + self.field_type = _FieldDataConstants.proto_field_type_to_np_data_type[ + solution_variable_info.field_type + ] + + def __repr__(self): + return f"name:{self.name} dimension:{self.dimension} field_type:{self.field_type}" + + def __init__( + self, + solution_variables_info: Sequence[solution_variable_pb2.SolutionVariableInfo], + ): + """Initialize SolutionVariables.""" + self._solution_variables_info: dict[ + str, "SolutionVariables.SolutionVariable" + ] = { + solution_variable_info.name: SolutionVariables.SolutionVariable( + solution_variable_info + ) + for solution_variable_info in solution_variables_info + } + + def _filter(self, solution_variables_info): + self._solution_variables_info = { + k: v + for k, v in self._solution_variables_info.items() + if k + in [ + solution_variable_info.name + for solution_variable_info in solution_variables_info + ] + } + + def __getitem__(self, name: str) -> "SolutionVariables.SolutionVariable": + return self._solution_variables_info[name] + + def get(self, name: str) -> "SolutionVariables.SolutionVariable | None": + """Get name from solution variables""" + return self._solution_variables_info.get(name) + + @property + def solution_variables(self) -> list[str]: + """Solution variables.""" + return list(self._solution_variables_info.keys()) + + +class ZonesInfo: + """Class containing information for multiple zones.""" + + class ZoneInfo: + """Class containing information for single zone.""" + + class PartitionsInfo: + """Class containing information for partitions.""" + + def __init__(self, partition_info): + """Initialize PartitionsInfo.""" + self.count = partition_info.count + self.start_index = partition_info.start_index if self.count > 0 else 0 + self.end_index = partition_info.end_index if self.count > 0 else 0 + + def __init__(self, zone_info): + """Initialize ZoneInfo.""" + self.name = zone_info.name + self.zone_id = zone_info.zone_id + self.zone_type = zone_info.zone_type + self.thread_type = zone_info.thread_type + self.partitions_info = [ + self.PartitionsInfo(partition_info) + for partition_info in zone_info.partitions_info + ] + + @property + def count(self) -> int: + """Get zone count.""" + return sum( + [partition_info.count for partition_info in self.partitions_info] + ) + + def __repr__(self): + partition_str = "" + for i, partition_info in enumerate(self.partitions_info): + partition_str += f"\n\t{i}. {partition_info.count}[{partition_info.start_index}:{partition_info.end_index}]" + return f"name:{self.name} count: {self.count} zone_id:{self.zone_id} zone_type:{self.zone_type} thread_type:{'Cell' if self.thread_type == solution_variable_pb2.ThreadType.THREAD_TYPE_CELL else 'Face'}{partition_str}" + + def __init__(self, zones_info, domains_info): + """Initialize ZonesInfo.""" + self._zones_info: dict[str, "ZonesInfo.ZoneInfo"] = { + zone_info.name: self.ZoneInfo(zone_info) for zone_info in zones_info + } + self._domains_info: dict[str, int] = { + domain_info.name: domain_info.domain_id for domain_info in domains_info + } + + def __getitem__(self, name: str) -> "ZonesInfo.ZoneInfo": + return self._zones_info[name] + + def get(self, name: str) -> "ZonesInfo.ZoneInfo | None": + """Get name from zones info""" + return self._zones_info.get(name) + + @property + def zone_names(self) -> list[str]: + """Get zone names.""" + return list(self._zones_info.keys()) + + @property + def domains(self) -> list[str]: + """Get domain names.""" + return list(self._domains_info.keys()) + + def domain_id(self, domain_name) -> int: + """Get domain id.""" + return self._domains_info.get(domain_name, None) + + +class SolutionVariableService(ServiceProtocol): + """SVAR service of Fluent.""" + + def __init__(self, channel: grpc.Channel, metadata, fluent_error_state): + """__init__ method of SVAR service class.""" + intercept_channel = grpc.intercept_channel( + channel, + GrpcErrorInterceptor(), + TracingInterceptor(), + ) + self._stub = solution_variable_pb2_grpc.SolutionVariableStub(intercept_channel) + self._metadata = metadata + del fluent_error_state # unused variable + + def get_data( + self, + variable_name: str, + zone_names: list[str], + domain_name: str, + allowed_solution_variable_names, + allowed_domain_names, + allowed_zone_names, + ) -> tuple[dict[int, str], dict[Any, npt.NDArray[Any]]]: + """Get SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names: List[str] + Zone names list for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + allowed_solution_variable_names: AllowedSolutionVariableNames + AllowedSolutionVariableNames object to validate solution variable names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + + Returns + ------- + dict[Any, npt.NDArray[Any]] + Dictionary containing SVAR data. + """ + svars_request = solution_variable_pb2.GetSolutionVariableDataRequest( + provide_bytes_stream=_FieldDataConstants.bytes_stream, + chunk_size=_FieldDataConstants.chunk_size, + ) + svars_request.domain_id = allowed_domain_names.valid_name(domain_name) + svars_request.name = allowed_solution_variable_names.valid_name( + variable_name, + zone_names, + domain_name, + ) + zone_id_name_map = {} + for zone_name in zone_names: + zone_id = allowed_zone_names.valid_name(zone_name) + zone_id_name_map[zone_id] = zone_name + svars_request.zones.append(zone_id) + + return zone_id_name_map, extract_svars( + self._stub.GetSolutionVariableData(svars_request, metadata=self._metadata) + ) + + def set_data( + self, + variable_name: str, + zone_names_to_data: dict[str, np.ndarray], + domain_name: str, + allowed_solution_variable_names, + allowed_domain_names, + allowed_zone_names, + ) -> None: + """Set SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names_to_data: Dict[str, np.array] + Dictionary containing zone names for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + allowed_solution_variable_names: AllowedSolutionVariableNames + AllowedSolutionVariableNames object to validate solution variable names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + + Returns + ------- + None + """ + variable_name = allowed_solution_variable_names.valid_name( + variable_name, + list(zone_names_to_data.keys()), + domain_name, + ) + domain_id = allowed_domain_names.valid_name(domain_name) + zone_ids_to_svar_data = { + allowed_zone_names.valid_name(zone_name): solution_variable_data + for zone_name, solution_variable_data in zone_names_to_data.items() + } + + def generate_set_data_requests(): + set_data_requests = [] + + set_data_requests.append( + solution_variable_pb2.SetSolutionVariableDataRequest( + header=solution_variable_pb2.SolutionVariableHeader( + name=variable_name, domain_id=domain_id + ) + ) + ) + + for zone_id, solution_variable_data in zone_ids_to_svar_data.items(): + max_array_size = ( + _FieldDataConstants.chunk_size + / np.dtype(solution_variable_data.dtype).itemsize + ) + solution_variable_data_list = np.array_split( + solution_variable_data, + math.ceil(solution_variable_data.size / max_array_size), + ) + set_data_requests.append( + solution_variable_pb2.SetSolutionVariableDataRequest( + payload_info=solution_variable_pb2.Info( + field_type=_FieldDataConstants.np_data_type_to_proto_field_type[ + solution_variable_data.dtype.type + ], + field_size=solution_variable_data.size, + zone=zone_id, + ) + ) + ) + set_data_requests += [ + solution_variable_pb2.SetSolutionVariableDataRequest( + payload=( + solution_variable_pb2.Payload( + float_payload=field_data_pb2.FloatPayload( + payloads=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.float32 + else ( + solution_variable_pb2.Payload( + double_payload=field_data_pb2.DoublePayload( + payloads=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.float64 + else ( + solution_variable_pb2.Payload( + int_payload=field_data_pb2.IntPayload( + payloads=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.int32 + else solution_variable_pb2.Payload( + long_payload=field_data_pb2.LongPayload( + payloads=solution_variable_data + ) + ) + ) + ) + ) + ) + for solution_variable_data in solution_variable_data_list + if solution_variable_data.size > 0 + ] + + for set_data_request in set_data_requests: + yield set_data_request + + self._stub.SetSolutionVariableData( + generate_set_data_requests(), metadata=self._metadata + ) + + def get_variables_info( + self, + zone_names: list[str], + domain_name: str, + allowed_zone_names, + allowed_domain_names, + ) -> SolutionVariables: + """Get SVARs info for zones in the domain. + + Parameters + ---------- + zone_names : List[str] + List of zone names. + domain_name: str, optional + Domain name.The default is ``mixture``. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + + Returns + ------- + SolutionVariables + Object containing information for SVARs which are common for list of zone names. + """ + solution_variables_info = None + for zone_name in zone_names: + request = solution_variable_pb2.GetSolutionVariableInfoRequest( + domain_id=allowed_domain_names.valid_name(domain_name), + zone_id=allowed_zone_names.valid_name(zone_name), + ) + response = self._stub.GetSolutionVariableInfo( + request, metadata=self._metadata + ) + if solution_variables_info is None: + solution_variables_info = SolutionVariables(response.svars_info) + else: + solution_variables_info._filter(response.svars_info) + return solution_variables_info + + def get_zones_info(self) -> ZonesInfo: + """Get Zones info. + + Parameters + ---------- + None + + Returns + ------- + SolutionVariableInfo.ZonesInfo + Object containing information for all zones. + """ + request = solution_variable_pb2.GetZonesInfoRequest() + response = self._stub.GetZonesInfo(request, metadata=self._metadata) + return ZonesInfo(response.zones_info, response.domains_info) + + +def extract_svars(solution_variables_data): + """Extract SVAR data via a server call (v1 proto payload shape).""" + + def _extract_svar(field_datatype, field_size, solution_variables_data): + field_arr = np.empty(field_size, dtype=field_datatype) + field_datatype_item_size = np.dtype(field_datatype).itemsize + index = 0 + for solution_variable_data in solution_variables_data: + chunk = solution_variable_data.payload + if chunk.byte_payload: + count = min( + len(chunk.byte_payload) // field_datatype_item_size, + field_size - index, + ) + field_arr[index : index + count] = np.frombuffer( + chunk.byte_payload, field_datatype, count=count + ) + index += count + if index == field_size: + return field_arr + else: + payload = ( + chunk.float_payload.payloads + or chunk.int_payload.payloads + or chunk.double_payload.payloads + or chunk.long_payload.payloads + ) + count = len(payload) + field_arr[index : index + count] = np.fromiter( + payload, dtype=field_datatype + ) + index += count + if index == field_size: + return field_arr + + zones_svar_data = {} + for array in solution_variables_data: + if array.WhichOneof("array") == "payload_info": + zones_svar_data[array.payload_info.zone] = _extract_svar( + _FieldDataConstants.proto_field_type_to_np_data_type[ + array.payload_info.field_type + ], + array.payload_info.field_size, + solution_variables_data, + ) + elif array.WhichOneof("array") == "header": + continue + + return zones_svar_data diff --git a/src/ansys/fluent/core/_grpc_services/solution_variable_service_v0.py b/src/ansys/fluent/core/_grpc_services/solution_variable_service_v0.py new file mode 100644 index 00000000000..72900675bf5 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/solution_variable_service_v0.py @@ -0,0 +1,443 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the solution variable gRPC service of Fluent (v0 proto API).""" + +import math +from typing import Any, Sequence + +import grpc +import numpy as np +import numpy.typing as npt + +from ansys.api.fluent.v0 import field_data_pb2, svar_pb2, svar_pb2_grpc +from ansys.fluent.core._grpc_services.field_data_service_v0 import _FieldDataConstants +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import ( + GrpcErrorInterceptor, + TracingInterceptor, +) + + +class SolutionVariables: + """Class containing information for multiple solution variables.""" + + class SolutionVariable: + """Class containing information for single solution variable.""" + + def __init__(self, solution_variable_info: svar_pb2.SvarInfo): + """Initialize SolutionVariable.""" + self.name = solution_variable_info.name + self.dimension = solution_variable_info.dimension + self.field_type = _FieldDataConstants.proto_field_type_to_np_data_type[ + solution_variable_info.fieldType + ] + + def __repr__(self): + return f"name:{self.name} dimension:{self.dimension} field_type:{self.field_type}" + + def __init__(self, solution_variables_info: Sequence[svar_pb2.SvarInfo]): + """Initialize SolutionVariables.""" + self._solution_variables_info: dict[ + str, "SolutionVariables.SolutionVariable" + ] = { + solution_variable_info.name: SolutionVariables.SolutionVariable( + solution_variable_info + ) + for solution_variable_info in solution_variables_info + } + + def _filter(self, solution_variables_info): + self._solution_variables_info = { + k: v + for k, v in self._solution_variables_info.items() + if k + in [ + solution_variable_info.name + for solution_variable_info in solution_variables_info + ] + } + + def __getitem__(self, name: str) -> "SolutionVariables.SolutionVariable": + return self._solution_variables_info[name] + + def get(self, name: str) -> "SolutionVariables.SolutionVariable | None": + """Get name from solution variables""" + return self._solution_variables_info.get(name) + + @property + def solution_variables(self) -> list[str]: + """Solution variables.""" + return list(self._solution_variables_info.keys()) + + +class ZonesInfo: + """Class containing information for multiple zones.""" + + class ZoneInfo: + """Class containing information for single zone.""" + + class PartitionsInfo: + """Class containing information for partitions.""" + + def __init__(self, partition_info): + """Initialize PartitionsInfo.""" + self.count = partition_info.count + self.start_index = partition_info.startIndex if self.count > 0 else 0 + self.end_index = partition_info.endIndex if self.count > 0 else 0 + + def __init__(self, zone_info): + """Initialize ZoneInfo.""" + self.name = zone_info.name + self.zone_id = zone_info.zoneId + self.zone_type = zone_info.zoneType + self.thread_type = zone_info.threadType + self.partitions_info = [ + self.PartitionsInfo(partition_info) + for partition_info in zone_info.partitionsInfo + ] + + @property + def count(self) -> int: + """Get zone count.""" + return sum( + [partition_info.count for partition_info in self.partitions_info] + ) + + def __repr__(self): + partition_str = "" + for i, partition_info in enumerate(self.partitions_info): + partition_str += f"\n\t{i}. {partition_info.count}[{partition_info.start_index}:{partition_info.end_index}]" + return f"name:{self.name} count: {self.count} zone_id:{self.zone_id} zone_type:{self.zone_type} threadType:{'Cell' if self.thread_type == svar_pb2.ThreadType.CELL_THREAD else 'Face'}{partition_str}" + + def __init__(self, zones_info, domains_info): + """Initialize ZonesInfo.""" + self._zones_info: dict[str, "ZonesInfo.ZoneInfo"] = { + zone_info.name: self.ZoneInfo(zone_info) for zone_info in zones_info + } + self._domains_info: dict[str, int] = { + domain_info.name: domain_info.domainId for domain_info in domains_info + } + + def __getitem__(self, name: str) -> "ZonesInfo.ZoneInfo": + return self._zones_info[name] + + def get(self, name: str) -> "ZonesInfo.ZoneInfo | None": + """Get name from zones info""" + return self._zones_info.get(name) + + @property + def zone_names(self) -> list[str]: + """Get zone names.""" + return list(self._zones_info.keys()) + + @property + def domains(self) -> list[str]: + """Get domain names.""" + return list(self._domains_info.keys()) + + def domain_id(self, domain_name) -> int: + """Get domain id.""" + return self._domains_info.get(domain_name, None) + + +class SolutionVariableService(ServiceProtocol): + """SVAR service of Fluent.""" + + def __init__(self, channel: grpc.Channel, metadata, fluent_error_state): + """__init__ method of SVAR service class.""" + intercept_channel = grpc.intercept_channel( + channel, + GrpcErrorInterceptor(), + TracingInterceptor(), + ) + self._stub = svar_pb2_grpc.svarStub(intercept_channel) + self._metadata = metadata + del fluent_error_state # unused variable + + def get_data( + self, + variable_name: str, + zone_names: list[str], + domain_name: str, + allowed_solution_variable_names, + allowed_domain_names, + allowed_zone_names, + ) -> tuple[dict[int, str], dict[Any, npt.NDArray[Any]]]: + """Get SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names: List[str] + Zone names list for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + allowed_solution_variable_names: AllowedSolutionVariableNames + AllowedSolutionVariableNames object to validate solution variable names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + + Returns + ------- + dict[Any, npt.NDArray[Any]] + Dictionary containing SVAR data. + """ + svars_request = svar_pb2.GetSvarDataRequest( + provideBytesStream=_FieldDataConstants.bytes_stream, + chunkSize=_FieldDataConstants.chunk_size, + ) + svars_request.domainId = allowed_domain_names.valid_name(domain_name) + svars_request.name = allowed_solution_variable_names.valid_name( + variable_name, + zone_names, + domain_name, + ) + zone_id_name_map = {} + for zone_name in zone_names: + zone_id = allowed_zone_names.valid_name(zone_name) + zone_id_name_map[zone_id] = zone_name + svars_request.zones.append(zone_id) + + return zone_id_name_map, extract_svars( + self._stub.GetSvarData(svars_request, metadata=self._metadata) + ) + + def set_data( + self, + variable_name: str, + zone_names_to_data: dict[str, np.ndarray], + domain_name: str, + allowed_solution_variable_names, + allowed_domain_names, + allowed_zone_names, + ) -> None: + """Set SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names_to_data: Dict[str, np.array] + Dictionary containing zone names for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + allowed_solution_variable_names: AllowedSolutionVariableNames + AllowedSolutionVariableNames object to validate solution variable names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + + Returns + ------- + None + """ + variable_name = allowed_solution_variable_names.valid_name( + variable_name, + list(zone_names_to_data.keys()), + domain_name, + ) + domain_id = allowed_domain_names.valid_name(domain_name) + zone_ids_to_svar_data = { + allowed_zone_names.valid_name(zone_name): solution_variable_data + for zone_name, solution_variable_data in zone_names_to_data.items() + } + + def generate_set_data_requests(): + set_data_requests = [] + + set_data_requests.append( + svar_pb2.SetSvarDataRequest( + header=svar_pb2.SvarHeader(name=variable_name, domainId=domain_id) + ) + ) + + for zone_id, solution_variable_data in zone_ids_to_svar_data.items(): + max_array_size = ( + _FieldDataConstants.chunk_size + / np.dtype(solution_variable_data.dtype).itemsize + ) + solution_variable_data_list = np.array_split( + solution_variable_data, + math.ceil(solution_variable_data.size / max_array_size), + ) + set_data_requests.append( + svar_pb2.SetSvarDataRequest( + payloadInfo=svar_pb2.Info( + fieldType=_FieldDataConstants.np_data_type_to_proto_field_type[ + solution_variable_data.dtype.type + ], + fieldSize=solution_variable_data.size, + zone=zone_id, + ) + ) + ) + set_data_requests += [ + svar_pb2.SetSvarDataRequest( + payload=( + svar_pb2.Payload( + floatPayload=field_data_pb2.FloatPayload( + payload=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.float32 + else ( + svar_pb2.Payload( + doublePayload=field_data_pb2.DoublePayload( + payload=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.float64 + else ( + svar_pb2.Payload( + intPayload=field_data_pb2.IntPayload( + payload=solution_variable_data + ) + ) + if solution_variable_data.dtype.type == np.int32 + else svar_pb2.Payload( + longPayload=field_data_pb2.LongPayload( + payload=solution_variable_data + ) + ) + ) + ) + ) + ) + for solution_variable_data in solution_variable_data_list + if solution_variable_data.size > 0 + ] + + yield from set_data_requests + + self._stub.SetSvarData(generate_set_data_requests(), metadata=self._metadata) + + def get_variables_info( + self, + zone_names: list[str], + domain_name: str, + allowed_zone_names, + allowed_domain_names, + ) -> SolutionVariables: + """Get SVARs info for zones in the domain. + + Parameters + ---------- + zone_names : List[str] + List of zone names. + domain_name: str, optional + Domain name.The default is ``mixture``. + allowed_zone_names: AllowedZoneNames + AllowedZoneNames object to validate zone names. + allowed_domain_names: AllowedDomainNames + AllowedDomainNames object to validate domain names. + + Returns + ------- + SolutionVariables + Object containing information for SVARs which are common for list of zone names. + """ + solution_variables_info = None + for zone_name in zone_names: + request = svar_pb2.GetSvarsInfoRequest( + domainId=allowed_domain_names.valid_name(domain_name), + zoneId=allowed_zone_names.valid_name(zone_name), + ) + response = self._stub.GetSvarsInfo(request, metadata=self._metadata) + if solution_variables_info is None: + solution_variables_info = SolutionVariables(response.svarsInfo) + else: + solution_variables_info._filter(response.svarsInfo) + return solution_variables_info + + def get_zones_info(self) -> ZonesInfo: + """Get Zones info. + + Parameters + ---------- + None + + Returns + ------- + SolutionVariableInfo.ZonesInfo + Object containing information for all zones. + """ + request = svar_pb2.GetZonesInfoRequest() + response = self._stub.GetZonesInfo(request, metadata=self._metadata) + return ZonesInfo(response.zonesInfo, response.domainsInfo) + + +def extract_svars(solution_variables_data): + """Extracts SVAR data via a server call.""" + + def _extract_svar( + field_datatype: npt.DTypeLike, field_size: int, solution_variables_data + ) -> npt.NDArray[np.float64] | None: + field_arr = np.empty(field_size, dtype=field_datatype) + field_datatype_item_size = np.dtype(field_datatype).itemsize + index = 0 + for solution_variable_data in solution_variables_data: + chunk = solution_variable_data.payload + if chunk.bytePayload: + count = min( + len(chunk.bytePayload) // field_datatype_item_size, + field_size - index, + ) + field_arr[index : index + count] = np.frombuffer( + chunk.bytePayload, field_datatype, count=count + ) + index += count + if index == field_size: + return field_arr + else: + payload: Sequence[float] = ( + chunk.floatPayload.payload + or chunk.intPayload.payload + or chunk.doublePayload.payload + or chunk.longPayload.payload + ) + count = len(payload) + field_arr[index : index + count] = np.fromiter( + payload, dtype=field_datatype + ) + index += count + if index == field_size: + return field_arr + + zones_svar_data = dict[Any, npt.NDArray[Any] | None]() + for array in solution_variables_data: + if array.WhichOneof("array") == "payloadInfo": + zones_svar_data[array.payloadInfo.zone] = _extract_svar( + _FieldDataConstants.proto_field_type_to_np_data_type[ + array.payloadInfo.fieldType + ], + array.payloadInfo.fieldSize, + solution_variables_data, + ) + elif array.WhichOneof("array") == "header": + continue + + return zones_svar_data diff --git a/src/ansys/fluent/core/_grpc_services/text_interface_service.py b/src/ansys/fluent/core/_grpc_services/text_interface_service.py new file mode 100644 index 00000000000..04e4aac2858 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/text_interface_service.py @@ -0,0 +1,173 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the text interface gRPC service of Fluent (v1 proto API).""" + +from typing import Any + +from google.protobuf.json_format import MessageToDict +import grpc + +from ansys.api.fluent.v1 import text_interface_pb2, text_interface_pb2_grpc +from ansys.api.fluent.v1.variant_pb2 import Variant +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import ( + BatchInterceptor, + ErrorStateInterceptor, + GrpcErrorInterceptor, + TracingInterceptor, +) + + +class TextInterfaceService(ServiceProtocol): + """Class wrapping the text interface gRPC service of Fluent (v1 proto API).""" + + def __init__( + self, + channel: grpc.Channel, + metadata: list[tuple[str, str]], + fluent_error_state, + ) -> None: + """__init__ method of TextInterfaceService class.""" + self._channel = channel + self._fluent_error_state = fluent_error_state + intercept_channel = grpc.intercept_channel( + self._channel, + GrpcErrorInterceptor(), + ErrorStateInterceptor(self._fluent_error_state), + TracingInterceptor(), + BatchInterceptor(), + ) + self._stub = text_interface_pb2_grpc.TextInterfaceStub(intercept_channel) + self._metadata = metadata + + @staticmethod + def _normalize_attribute_name(attribute: str) -> str: + name = attribute.upper() + return name if name.startswith("ATTRIBUTE_") else f"ATTRIBUTE_{name}" + + def get_attribute_value( + self, path: str, attribute: str, include_unavailable: bool + ) -> Any: + """Get the attribute value.""" + request = text_interface_pb2.GetAttributeValueRequest() + request.path = path + request.attribute = text_interface_pb2.Attribute.Value( + self._normalize_attribute_name(attribute) + ) + if include_unavailable: + request.args["include_unavailable"] = 1 + response = self._stub.GetAttributeValue(request, metadata=self._metadata) + return _convert_gvalue_to_value(response.value) + + def get_state( + self, request: text_interface_pb2.GetStateRequest + ) -> text_interface_pb2.GetStateResponse: + """GetState RPC of DataModel service.""" + return self._stub.GetState(request, metadata=self._metadata) + + def set_state( + self, request: text_interface_pb2.SetStateRequest + ) -> text_interface_pb2.SetStateResponse: + """SetState RPC of DataModel service.""" + return self._stub.SetState(request, metadata=self._metadata) + + def execute_command(self, path: str, *args, **kwargs) -> Any: + """Execute the command.""" + request = text_interface_pb2.ExecuteCommandRequest() + request.path = path + if kwargs: + for k, v in kwargs.items(): + _convert_value_to_gvalue(v, request.args.fields[k]) + else: + _convert_value_to_gvalue(args, request.args.fields["tui_args"]) + return self._stub.ExecuteCommand(request, metadata=self._metadata) + + def execute_query(self, path: str, *args, **kwargs) -> Any: + """Execute the query.""" + request = text_interface_pb2.ExecuteQueryRequest() + request.path = path + return self._stub.ExecuteQuery(request, metadata=self._metadata) + + def get_static_info(self, path: str): + """GetStaticInfo RPC of DataModel service.""" + request = text_interface_pb2.GetSchemaRequest() + request.path = path + response = self._stub.GetSchema(request, metadata=self._metadata) + # Note: MessageToDict's parameter names are different in different protobuf versions + return MessageToDict(response.info, True) + + def get_child_names( + self, path: str, include_unavailable: bool = False + ) -> list[str]: + """Get the names of child menus.""" + attribute = text_interface_pb2.Attribute.Name( + text_interface_pb2.Attribute.ATTRIBUTE_CHILD_NAMES + ).lower() + return self.get_attribute_value(path, attribute, include_unavailable) + + def get_doc_string(self, path: str, include_unavailable: bool = False) -> str: + """Get docstring for a menu.""" + attribute = text_interface_pb2.Attribute.Name( + text_interface_pb2.Attribute.ATTRIBUTE_HELP_STRING + ).lower() + return self.get_attribute_value(path, attribute, include_unavailable) + + +def _convert_value_to_gvalue(val: Any, gval: Variant) -> None: + """Convert Python datatype to Value type of google/protobuf/struct.proto.""" + if isinstance(val, bool): + gval.bool_value = val + elif isinstance(val, int) or isinstance(val, float): + gval.number_value = val + elif isinstance(val, str): + gval.string_value = val + elif isinstance(val, list) or isinstance(val, tuple): + # set the one_of to list_value + gval.list_value.values.add() + gval.list_value.values.pop() + for item in val: + item_gval = gval.list_value.values.add() + _convert_value_to_gvalue(item, item_gval) + elif isinstance(val, dict): + for k, v in val.items(): + _convert_value_to_gvalue(v, gval.struct_value.fields[k]) + + +def _convert_gvalue_to_value(gval: Variant) -> Any: + """Convert Value type of google/protobuf/struct.proto to Python datatype.""" + if gval.HasField("bool_value"): + return gval.bool_value + elif gval.HasField("number_value"): + return gval.number_value + elif gval.HasField("string_value"): + return gval.string_value + elif gval.HasField("list_value"): + val = [] + for item in gval.list_value.values: + val.append(_convert_gvalue_to_value(item)) + return val + elif gval.HasField("struct_value"): + val = {} + for k, v in gval.struct_value.fields.items(): + val[k] = _convert_gvalue_to_value(v) + return val diff --git a/src/ansys/fluent/core/_grpc_services/text_interface_service_v0.py b/src/ansys/fluent/core/_grpc_services/text_interface_service_v0.py new file mode 100644 index 00000000000..f2d5c12f112 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/text_interface_service_v0.py @@ -0,0 +1,171 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the text interface gRPC service of Fluent (v0 proto API).""" + +from typing import Any + +from google.protobuf.json_format import MessageToDict +import grpc + +from ansys.api.fluent.v0 import datamodel_tui_pb2, datamodel_tui_pb2_grpc +from ansys.api.fluent.v0.variant_pb2 import Variant +from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.interceptors import ( + BatchInterceptor, + ErrorStateInterceptor, + GrpcErrorInterceptor, + TracingInterceptor, +) + + +class TextInterfaceService(ServiceProtocol): + """Class wrapping the text interface gRPC service of Fluent (v0 proto API).""" + + def __init__( + self, + channel: grpc.Channel, + metadata: list[tuple[str, str]], + fluent_error_state, + ) -> None: + """__init__ method of TextInterfaceService class.""" + self._channel = channel + self._fluent_error_state = fluent_error_state + intercept_channel = grpc.intercept_channel( + self._channel, + GrpcErrorInterceptor(), + ErrorStateInterceptor(self._fluent_error_state), + TracingInterceptor(), + BatchInterceptor(), + ) + self._stub = datamodel_tui_pb2_grpc.DataModelStub(intercept_channel) + self._metadata = metadata + + def get_attribute_value( + self, path: str, attribute: str, include_unavailable: bool + ) -> Any: + """Get the attribute value.""" + request = datamodel_tui_pb2.GetAttributeValueRequest() + request.path = path + request.attribute = datamodel_tui_pb2.Attribute.Value(attribute.upper()) + if include_unavailable: + request.args["include_unavailable"] = 1 + response = self._stub.GetAttributeValue(request, metadata=self._metadata) + return _convert_gvalue_to_value(response.value) + + def get_state( + self, request: datamodel_tui_pb2.GetStateRequest + ) -> datamodel_tui_pb2.GetStateResponse: + """GetState RPC of DataModel service.""" + return self._stub.GetState(request, metadata=self._metadata) + + def set_state( + self, request: datamodel_tui_pb2.SetStateRequest + ) -> datamodel_tui_pb2.SetStateResponse: + """SetState RPC of DataModel service.""" + return self._stub.SetState(request, metadata=self._metadata) + + def execute_command(self, path: str, *args, **kwargs) -> Any: + """Execute the command.""" + request = datamodel_tui_pb2.ExecuteCommandRequest() + request.path = path + if kwargs: + for k, v in kwargs.items(): + _convert_value_to_gvalue(v, request.args.fields[k]) + else: + _convert_value_to_gvalue(args, request.args.fields["tui_args"]) + return self._stub.ExecuteCommand(request, metadata=self._metadata) + + def execute_query(self, path: str, *args, **kwargs) -> Any: + """Execute the query.""" + request = datamodel_tui_pb2.ExecuteQueryRequest() + request.path = path + if kwargs: + for k, v in kwargs.items(): + _convert_value_to_gvalue(v, request.args.fields[k]) + else: + _convert_value_to_gvalue(args, request.args.fields["tui_args"]) + return self._stub.ExecuteQuery(request, metadata=self._metadata) + + def get_static_info(self, path: str): + """GetStaticInfo RPC of DataModel service.""" + request = datamodel_tui_pb2.GetStaticInfoRequest() + request.path = path + response = self._stub.GetStaticInfo(request, metadata=self._metadata) + # Note: MessageToDict's parameter names are different in different protobuf versions + return MessageToDict(response.info, True) + + def get_child_names( + self, path: str, include_unavailable: bool = False + ) -> list[str]: + """Get the names of child menus.""" + attribute = datamodel_tui_pb2.Attribute.Name( + datamodel_tui_pb2.Attribute.CHILD_NAMES + ).lower() + return self.get_attribute_value(path, attribute, include_unavailable) + + def get_doc_string(self, path: str, include_unavailable: bool = False) -> str: + """Get docstring for a menu.""" + attribute = datamodel_tui_pb2.Attribute.Name( + datamodel_tui_pb2.Attribute.HELP_STRING + ).lower() + return self.get_attribute_value(path, attribute, include_unavailable) + + +def _convert_value_to_gvalue(val: Any, gval: Variant) -> None: + """Convert Python datatype to Value type of google/protobuf/struct.proto.""" + if isinstance(val, bool): + gval.bool_value = val + elif isinstance(val, int) or isinstance(val, float): + gval.number_value = val + elif isinstance(val, str): + gval.string_value = val + elif isinstance(val, list) or isinstance(val, tuple): + # set the one_of to list_value + gval.list_value.values.add() + gval.list_value.values.pop() + for item in val: + item_gval = gval.list_value.values.add() + _convert_value_to_gvalue(item, item_gval) + elif isinstance(val, dict): + for k, v in val.items(): + _convert_value_to_gvalue(v, gval.struct_value.fields[k]) + + +def _convert_gvalue_to_value(gval: Variant) -> Any: + """Convert Value type of google/protobuf/struct.proto to Python datatype.""" + if gval.HasField("bool_value"): + return gval.bool_value + elif gval.HasField("number_value"): + return gval.number_value + elif gval.HasField("string_value"): + return gval.string_value + elif gval.HasField("list_value"): + val = [] + for item in gval.list_value.values: + val.append(_convert_gvalue_to_value(item)) + return val + elif gval.HasField("struct_value"): + val = {} + for k, v in gval.struct_value.fields.items(): + val[k] = _convert_gvalue_to_value(v) + return val diff --git a/src/ansys/fluent/core/_grpc_services/transcript_service.py b/src/ansys/fluent/core/_grpc_services/transcript_service.py new file mode 100644 index 00000000000..6aea370d293 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/transcript_service.py @@ -0,0 +1,46 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the transcript gRPC service of Fluent (v1 proto API).""" + +import grpc + +from ansys.api.fluent.v1 import transcript_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService +from ansys.fluent.core.services._protocols import ServiceProtocol + + +class TranscriptService( + StreamingService, ServiceProtocol +): # pyright: ignore[reportUnsafeMultipleInheritance] + """Class wrapping the transcript gRPC service of Fluent.""" + + def __init__( + self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state + ) -> None: + """__init__ method of TranscriptService class.""" + super().__init__( + stub=transcript_pb2_grpc.TranscriptStub(channel), + metadata=metadata, + ) + del fluent_error_state # unused in v1 diff --git a/src/ansys/fluent/core/_grpc_services/transcript_service_v0.py b/src/ansys/fluent/core/_grpc_services/transcript_service_v0.py new file mode 100644 index 00000000000..75f807080d1 --- /dev/null +++ b/src/ansys/fluent/core/_grpc_services/transcript_service_v0.py @@ -0,0 +1,46 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Wrapper over the transcript gRPC service of Fluent (v0 proto API).""" + +import grpc + +from ansys.api.fluent.v0 import transcript_pb2_grpc +from ansys.fluent.core._grpc_services.streaming_service import StreamingService +from ansys.fluent.core.services._protocols import ServiceProtocol + + +class TranscriptService( + StreamingService, ServiceProtocol +): # pyright: ignore[reportUnsafeMultipleInheritance] + """Class wrapping the transcript gRPC service of Fluent.""" + + def __init__( + self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state + ) -> None: + """__init__ method of TranscriptService class.""" + super().__init__( + stub=transcript_pb2_grpc.TranscriptStub(channel), + metadata=metadata, + ) + del fluent_error_state # unused in v0 diff --git a/src/ansys/fluent/core/codegen/tuigen.py b/src/ansys/fluent/core/codegen/tuigen.py index 67e164a4402..87c10268c38 100644 --- a/src/ansys/fluent/core/codegen/tuigen.py +++ b/src/ansys/fluent/core/codegen/tuigen.py @@ -54,7 +54,7 @@ from ansys.fluent.core.codegen import StaticInfoType from ansys.fluent.core.codegen.data.fluent_gui_help_patch import XML_HELP_PATCH from ansys.fluent.core.docker.utils import get_ghcr_fluent_image_name -from ansys.fluent.core.services.datamodel_tui import ( +from ansys.fluent.core.services.text_interface import ( convert_path_to_grpc_path, convert_tui_menu_to_func_name, ) @@ -294,7 +294,7 @@ def generate(self) -> None: "# This is an auto-generated file. DO NOT EDIT!\n" "#\n" "# pylint: disable=line-too-long\n\n" - "from ansys.fluent.core.services.datamodel_tui " + "from ansys.fluent.core.services.text_interface " "import PyMenu, TUIMenu, TUIMethod\n\n\n" ) self._main_menu.name = "main_menu" diff --git a/src/ansys/fluent/core/data_model_cache.py b/src/ansys/fluent/core/data_model_cache.py index d0eeae3b40f..4d52ded69b3 100644 --- a/src/ansys/fluent/core/data_model_cache.py +++ b/src/ansys/fluent/core/data_model_cache.py @@ -139,7 +139,10 @@ def _is_dict_parameter_type(version: FluentVersion, rules: str, rules_path: str) module = load_module( rules, config.codegen_outdir / f"datamodel_{version.number}" / f"{rules}.py" ) - except FileNotFoundError: # no codegen or during codegen + except ( + ImportError, + FileNotFoundError, + ): # no codegen, during codegen or outdated codegen return False cls = module.Root comps = rules_path.split("/") diff --git a/src/ansys/fluent/core/services/__init__.py b/src/ansys/fluent/core/services/__init__.py index 76c81c56bc9..6c28b917a95 100644 --- a/src/ansys/fluent/core/services/__init__.py +++ b/src/ansys/fluent/core/services/__init__.py @@ -21,91 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Provides a module to create gRPC services.""" - -from ansys.fluent.core.services.batch_ops import BatchOps, BatchOpsService -from ansys.fluent.core.services.datamodel_tui import ( - DatamodelService as DatamodelService_TUI_V0, -) -from ansys.fluent.core.services.datamodel_tui_v1 import ( - DatamodelService as DatamodelService_TUI, -) -from ansys.fluent.core.services.events import EventsService as EventsServiceV0 -from ansys.fluent.core.services.events_v1 import EventsService -from ansys.fluent.core.services.monitor import MonitorsService as MonitorsServiceV0 -from ansys.fluent.core.services.monitor_v1 import MonitorsService as MonitorsService -from ansys.fluent.core.services.solution_variables import ( - SolutionVariableData as SolutionVariableDataV0, -) -from ansys.fluent.core.services.solution_variables import ( - SolutionVariableService as SolutionVariableServiceV0, -) -from ansys.fluent.core.services.solution_variables_v1 import ( - SolutionVariableData, - SolutionVariableService, -) -from ansys.fluent.core.services.transcript import ( - TranscriptService as TranscriptServiceV0, -) -from ansys.fluent.core.services.transcript_v1 import TranscriptService -from ansys.fluent.core.utils.fluent_version import FluentVersion - -__all__ = ( - "BatchOpsService", - "BatchOps", - "DatamodelService_TUI", - "DatamodelService_TUI_V0", - "EventsService", - "EventsServiceV0", - "MonitorsService", - "MonitorsServiceV0", - "SolutionVariableData", - "SolutionVariableDataV0", - "SolutionVariableService", - "SolutionVariableServiceV0", - "TranscriptService", - "TranscriptServiceV0", - "service_creator", -) - - -_service_cls_by_name_v0 = { - "tui": DatamodelService_TUI_V0, - "events": EventsServiceV0, - "monitors": MonitorsServiceV0, - "svar": SolutionVariableServiceV0, - "svar_data": SolutionVariableDataV0, - "transcript": TranscriptServiceV0, - "batch_ops": BatchOpsService, -} - -_service_cls_by_name = { - "tui": DatamodelService_TUI, - "events": EventsService, - "monitors": MonitorsService, - "svar": SolutionVariableService, - "svar_data": SolutionVariableData, - "transcript": TranscriptService, - "batch_ops": BatchOpsService, -} - - -# This class is swapped in Fluent Python Console -class service_creator: - """A gRPC service creator.""" - - def __init__(self, service_name: str, supports_v1: bool | None = None): - """Initialize service_creator.""" - if supports_v1: - self._service_cls = _service_cls_by_name[service_name] - else: - self._service_cls = _service_cls_by_name_v0[service_name] - - def create(self, *args, **kwargs): - """Create a gRPC service.""" - return self._service_cls(*args, **kwargs) - - """Provides a module to create gRPC services.""" from functools import cached_property @@ -116,20 +31,29 @@ def create(self, *args, **kwargs): ApplicationRuntimeV252, ApplicationRuntimeV261, ) +from ansys.fluent.core.services.events import Events, EventsV251, EventsV261 from ansys.fluent.core.services.field_data import ( FieldData, FieldDataV251, FieldDataV261, ) from ansys.fluent.core.services.health_check import HealthCheck +from ansys.fluent.core.services.monitors import Monitor from ansys.fluent.core.services.object_model import ObjectModel, ObjectModelV261 from ansys.fluent.core.services.reduction import Reduction from ansys.fluent.core.services.scheme_interpreter import SchemeInterpreter from ansys.fluent.core.services.settings import Settings, SettingsV251, SettingsV261 +from ansys.fluent.core.services.solution_variables import ( + SolutionVariableData, + SolutionVariableInfo, +) +from ansys.fluent.core.services.text_interface import TextInterface +from ansys.fluent.core.services.transcript import Transcript from ansys.fluent.core.streaming_services.field_data_streaming import ( FieldDataStreaming, FieldDataStreamingV261, ) +from ansys.fluent.core.utils.fluent_version import FluentVersion class ServiceFactory: @@ -256,3 +180,58 @@ def object_model(self): self._service_factory.object_model, self._service_factory.scheme_interpreter, ) + + @cached_property + def events(self): + """Events service.""" + match self._product_version: + case v if v >= FluentVersion.v271: + return Events( + self._service_factory.events, + ) + case v if v >= FluentVersion.v252 and v < FluentVersion.v271: + return EventsV261( + self._service_factory.events, + self._service_factory.application_runtime, + ) + case _: + return EventsV251( + self._service_factory.events, + self._service_factory.scheme_interpreter, + ) + + @cached_property + def transcript(self): + """Transcript service.""" + return Transcript(self._service_factory.transcript) + + @cached_property + def batch_ops(self): + """Batch operations service.""" + return self._service_factory.batch_ops + + @cached_property + def text_interface(self): + """Text interface service.""" + return TextInterface( + self._service_factory.text_interface, + self._service_factory.application_runtime, + self._service_factory.scheme_interpreter, + ) + + @cached_property + def monitor(self): + """Monitor service.""" + return Monitor(self._service_factory.monitor) + + @cached_property + def solution_variable_info(self): + """Solution variable info service.""" + return SolutionVariableInfo(self._service_factory.solution_variable) + + @cached_property + def solution_variable_data(self): + """Solution variable data service.""" + return SolutionVariableData( + self._service_factory.solution_variable, self.solution_variable_info + ) diff --git a/src/ansys/fluent/core/services/abstract_events.py b/src/ansys/fluent/core/services/abstract_events.py new file mode 100644 index 00000000000..33860b139db --- /dev/null +++ b/src/ansys/fluent/core/services/abstract_events.py @@ -0,0 +1,52 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Abstract events wrapper.""" + +from abc import ABC, abstractmethod + +from ansys.fluent.core.streaming_services.events_streaming import ( + SolverEvent as SolverEventV0, +) +from ansys.fluent.core.streaming_services.events_streaming_v1 import SolverEvent + + +class AbstractEvents(ABC): + """Abstract base class for the events.""" + + @abstractmethod + def register_pause_on_solution_events( + self, solution_event: SolverEvent | SolverEventV0 + ) -> int: + """Register pause on solution events.""" + pass + + @abstractmethod + def resume_on_solution_event(self, registration_id: int) -> None: + """Resume on solution event.""" + pass + + @abstractmethod + def unregister_pause_on_solution_events(self, registration_id: int) -> None: + """Unregister pause on solution events.""" + pass diff --git a/src/ansys/fluent/core/services/abstract_field_data.py b/src/ansys/fluent/core/services/abstract_field_data.py index 80dd95a1560..9f22e7515c4 100644 --- a/src/ansys/fluent/core/services/abstract_field_data.py +++ b/src/ansys/fluent/core/services/abstract_field_data.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Abstract reduction wrapper.""" +"""Abstract field data wrapper.""" from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any diff --git a/src/ansys/fluent/core/services/transcript_v1.py b/src/ansys/fluent/core/services/abstract_monitors.py similarity index 64% rename from src/ansys/fluent/core/services/transcript_v1.py rename to src/ansys/fluent/core/services/abstract_monitors.py index 8e757b8d141..d6a04ddcb00 100644 --- a/src/ansys/fluent/core/services/transcript_v1.py +++ b/src/ansys/fluent/core/services/abstract_monitors.py @@ -21,23 +21,25 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the transcript gRPC service of Fluent (v1 proto API). +"""Abstract monitor wrapper.""" -All shared logic lives in transcript.py (v0). This module keeps only -v1-specific stub binding required for compatibility. -""" +from abc import ABC, abstractmethod -import grpc -from ansys.api.fluent.v1 import transcript_pb2_grpc as TranscriptGrpcModule -from ansys.fluent.core.services.transcript import ( - TranscriptService as _TranscriptServiceV0, -) +class AbstractMonitor(ABC): + """Abstract base class for the health check.""" + @abstractmethod + def get_monitors_info(self) -> dict: + """Get monitors information. -class TranscriptService(_TranscriptServiceV0): - """Class wrapping the transcript gRPC service of Fluent (v1 proto API).""" + Parameters + ---------- + None - def _create_stub(self, channel: grpc.Channel): - """Create the v1 gRPC stub.""" - return TranscriptGrpcModule.TranscriptStub(channel) + Returns + ------- + dict + Dictionary containing the monitors information. + """ + pass diff --git a/src/ansys/fluent/core/services/abstract_solution_variables.py b/src/ansys/fluent/core/services/abstract_solution_variables.py new file mode 100644 index 00000000000..24f43bd3493 --- /dev/null +++ b/src/ansys/fluent/core/services/abstract_solution_variables.py @@ -0,0 +1,142 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Abstract solution variables wrapper.""" + +from abc import ABC, abstractmethod + +import numpy as np + + +class AbstractSolutionVariableInfo(ABC): + """Abstract base class for solution variable information.""" + + @abstractmethod + def get_variables_info( + self, zone_names: list[str], domain_name: str | None = "mixture" + ): + """Get SVARs info for zones in the domain. + + Parameters + ---------- + zone_names : List[str] + List of zone names. + domain_name: str, optional + Domain name.The default is ``mixture``. + + Returns + ------- + SolutionVariableInfo.SolutionVariables + Object containing information for SVARs which are common for list of zone names. + """ + pass + + @abstractmethod + def get_zones_info(self): + """Get Zones info. + + Parameters + ---------- + None + + Returns + ------- + SolutionVariableInfo.ZonesInfo + Object containing information for all zones. + """ + pass + + +class AbstractSolutionVariableData(ABC): + """Abstract base class for solution variable data.""" + + @abstractmethod + def get_data( + self, + variable_name: str, + zone_names: list[str], + domain_name: str | None = "mixture", + ) -> "AbstractData": + """Get SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names: List[str] + Zone names list for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + + Returns + ------- + AbstractData + Object containing SVAR data. + """ + pass + + @abstractmethod + def set_data( + self, + variable_name: str, + zone_names_to_data: dict[str, np.ndarray], + domain_name: str | None = "mixture", + ) -> None: + """Set SVAR data on zones. + + Parameters + ---------- + variable_name : str + Name of the solution variable. + zone_names_to_data: Dict[str, np.array] + Dictionary containing zone names for solution variable data. + domain_name : str, optional + Domain name. The default is ``mixture``. + + Returns + ------- + None + """ + pass + + +class AbstractData(ABC): + """Abstract base class for solution variable data.""" + + @property + @abstractmethod + def domain(self): + """Domain name.""" + pass + + @property + @abstractmethod + def zone_names(self): + """Zone names.""" + pass + + @property + @abstractmethod + def data(self): + """Solution variable data.""" + pass diff --git a/src/ansys/fluent/core/services/abstract_text_interface.py b/src/ansys/fluent/core/services/abstract_text_interface.py new file mode 100644 index 00000000000..be05c3bd1c6 --- /dev/null +++ b/src/ansys/fluent/core/services/abstract_text_interface.py @@ -0,0 +1,148 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""Abstract text interface wrapper.""" + +from abc import ABC, abstractmethod +from typing import Any + + +class AbstractTextInterface(ABC): + """Abstract base class for the text interface.""" + + @abstractmethod + def get_attribute_value( + self, path: str, attribute: str, include_unavailable: bool + ) -> Any: + """Get attribute value at a path. + + Parameters + ---------- + path : str + Path to the menu. + attribute : str + Attribute name. + include_unavailable : bool + Whether to query over static TUI metadata. + + Returns + ------- + Any + Attribute value (any Python datatype) + """ + pass + + @abstractmethod + def execute_command(self, path: str, *args, **kwargs) -> Any: + """Execute a command at a path with positional or keyword arguments. + + Parameters + ---------- + path : str + Path to the menu. + *args + Positional arguments of the command. + **kwargs + Keyword arguments of the command. + + Returns + ------- + Any + Command result (any Python datatype) + """ + pass + + @abstractmethod + def execute_query(self, path: str, *args, **kwargs) -> Any: + """Execute a query at a path with positional or keyword arguments. + + Parameters + ---------- + path : str + Path to the menu. + *args + Positional arguments of the query. + **kwargs + Keyword arguments of the query. + + Returns + ------- + Any + Query result (any Python datatype) + """ + pass + + @abstractmethod + def get_static_info(self, path: str): + """Get static info at a path. + + Parameters + ---------- + path : str + Path to the menu. + + Returns + ------- + dict[str, Any] + Static info at the menu level. + """ + pass + + @abstractmethod + def get_child_names( + self, path: str, include_unavailable: bool = False + ) -> list[str]: + """Get the names of child menus. + + Parameters + ---------- + path : str + Path to the menu. + + include_unavailable : bool, optional + Whether to query over static TUI metadata. The default is ``False``. + + Returns + ------- + List[str] + Names of child menus. + """ + pass + + @abstractmethod + def get_doc_string(self, path: str, include_unavailable: bool = False) -> str: + """Get docstring for a menu. + + Parameters + ---------- + path : str + Path to the menu. + + include_unavailable : bool, optional + Whether to query over static TUI metadata. The default is ``False``. + + Returns + ------- + str + """ + pass diff --git a/src/ansys/fluent/core/services/api_upgrade.py b/src/ansys/fluent/core/services/api_upgrade.py index 97a4b0a1503..9ad1e4c7026 100644 --- a/src/ansys/fluent/core/services/api_upgrade.py +++ b/src/ansys/fluent/core/services/api_upgrade.py @@ -25,7 +25,7 @@ from typing import TypeVar -from ansys.fluent.core.services.application_runtime import ApplicationRuntime +from ansys.fluent.core.utils.fluent_version import FluentVersion _TApiUpgradeAdvisor = TypeVar("_TApiUpgradeAdvisor", bound="ApiUpgradeAdvisor") @@ -35,12 +35,14 @@ class ApiUpgradeAdvisor: def __init__( self, - app_utilities: ApplicationRuntime, + application_runtime_service, + scheme_interpreter_service, version: str, mode: str, ) -> None: """Initialize ApiUpgradeAdvisor.""" - self._app_utilities = app_utilities + self._app_utilities = application_runtime_service + self._scheme_interpreter = scheme_interpreter_service self._version = version self._mode = mode self._id = None @@ -50,14 +52,54 @@ def _can_advise(self) -> bool: return not config.skip_api_upgrade_advice and self._mode == "solver" + @staticmethod + def _start_python_journal( + scheme_eval_service, journal_name: str | None = None + ) -> str | None: + """Start python journal - scheme based implementation.""" + if journal_name: + scheme_eval_service.exec([f'(api-start-python-journal "{journal_name}")']) + else: + scheme_eval_service.eval( + "(define pyfluent-journal-str-port (open-output-string))" + ) + scheme_eval_service.eval("(api-echo-python-port pyfluent-journal-str-port)") + return "1" + + @staticmethod + def _stop_python_journal(scheme_eval_service, journal_id: str | None = None) -> str: + """Stop python journal - scheme based implementation.""" + if journal_id: + scheme_eval_service.eval( + "(api-unecho-python-port pyfluent-journal-str-port)" + ) + journal_str = scheme_eval_service.eval( + "(close-output-port pyfluent-journal-str-port)" + ) + return journal_str + else: + scheme_eval_service.exec(["(api-stop-python-journal)"]) + return "" + def __enter__(self) -> _TApiUpgradeAdvisor: if self._can_advise(): - self._id = self._app_utilities.start_python_journal() + if FluentVersion(self._version) >= FluentVersion.v252: + self._id = self._app_utilities.start_python_journal() + else: + self._id = self._start_python_journal(self._scheme_interpreter) + return self def __exit__(self, exc_type, exc_value, exc_tb) -> None: if self._can_advise(): - journal_str = (self._app_utilities.stop_python_journal(self._id)).strip() + if FluentVersion(self._version) >= FluentVersion.v252: + journal_str = ( + self._app_utilities.stop_python_journal(self._id) + ).strip() + else: + journal_str = ( + self._stop_python_journal(self._scheme_interpreter, self._id) + ).strip() if ( journal_str.startswith("solver.") and not journal_str.startswith("solver.tui") diff --git a/src/ansys/fluent/core/services/application_runtime.py b/src/ansys/fluent/core/services/application_runtime.py index 3e4dfc58007..e436bd3c0cd 100644 --- a/src/ansys/fluent/core/services/application_runtime.py +++ b/src/ansys/fluent/core/services/application_runtime.py @@ -142,7 +142,7 @@ def get_gpu_config(self) -> bool | list[int]: """Get GPU config.""" return self.service.get_gpu_config() - def start_python_journal(self, journal_name: str | None = None) -> int: + def start_python_journal(self, journal_name: str | None = None) -> str: """Start python journal.""" return self.service.start_python_journal(journal_name=journal_name) @@ -322,7 +322,7 @@ def get_gpu_config(self) -> bool | list[int]: f"Failed to parse malformed GPU ID string configuration: {config_str!r}" ) - def start_python_journal(self, journal_name: str | None = None) -> int: + def start_python_journal(self, journal_name: str | None = None) -> str: """Start python journal.""" return self.service.start_python_journal(journal_name=journal_name) @@ -553,7 +553,7 @@ def get_gpu_config(self) -> bool | list[int]: f"Failed to parse malformed GPU ID string configuration: {config_str!r}" ) - def start_python_journal(self, journal_name: str | None = None) -> int: + def start_python_journal(self, journal_name: str | None = None) -> str | None: """Start python journal.""" if journal_name: self.scheme.exec([f'(api-start-python-journal "{journal_name}")']) @@ -562,7 +562,7 @@ def start_python_journal(self, journal_name: str | None = None) -> int: self.scheme.eval("(api-echo-python-port pyfluent-journal-str-port)") return "1" - def stop_python_journal(self, journal_id: str | None = None) -> str: + def stop_python_journal(self, journal_id: str | None = None) -> str | None: """Stop python journal.""" if journal_id: self.scheme.eval("(api-unecho-python-port pyfluent-journal-str-port)") @@ -572,6 +572,7 @@ def stop_python_journal(self, journal_id: str | None = None) -> str: return journal_str else: self.scheme.exec(["(api-stop-python-journal)"]) + return "" def is_beta_enabled(self) -> bool: """Return whether beta features are enabled.""" @@ -622,7 +623,7 @@ def register_pause_on_solution_events(self, solution_event: SolverEvent) -> int: ) () ) - {"#t" if solution_event == SolverEvent.TIMESTEP_ENDED else "#f"} + {"#t" if solution_event.name == "TIMESTEP_ENDED" else "#f"} ) (car ids) ) diff --git a/src/ansys/fluent/core/services/batch_ops.py b/src/ansys/fluent/core/services/batch_ops.py index 241a6de0cf8..5fe288c0c95 100644 --- a/src/ansys/fluent/core/services/batch_ops.py +++ b/src/ansys/fluent/core/services/batch_ops.py @@ -23,20 +23,10 @@ """Batch RPC service.""" -import inspect import logging -import sys -from types import ModuleType from typing import TypeVar import weakref -from google.protobuf.message import Message -import grpc - -import ansys.api.fluent.v0 as api -from ansys.api.fluent.v0 import batch_ops_pb2, batch_ops_pb2_grpc -from ansys.fluent.core.services._protocols import ServiceProtocol - __all__ = ("BatchOps",) _TBatchOps = TypeVar("_TBatchOps", bound="BatchOps") @@ -44,32 +34,6 @@ network_logger: logging.Logger = logging.getLogger("pyfluent.networking") -class BatchOpsService(ServiceProtocol): - """Class wrapping methods in batch RPC service.""" - - def __init__(self, channel: grpc.Channel, metadata: list[tuple[str, str]]) -> None: - """__init__ method of BatchOpsService class.""" - - from ansys.fluent.core.services.interceptors import GrpcErrorInterceptor - - intercept_channel = grpc.intercept_channel( - channel, - GrpcErrorInterceptor(), - ) - self._stub = self._create_stub(intercept_channel) - self._metadata = metadata - - def _create_stub(self, intercept_channel): - """Create the gRPC stub. Override in subclasses to use a different proto version.""" - return batch_ops_pb2_grpc.BatchOpsStub(intercept_channel) - - def execute( - self, request: batch_ops_pb2.ExecuteRequest - ) -> batch_ops_pb2.ExecuteResponse: - """Execute RPC of BatchOps service.""" - return self._stub.Execute(request, metadata=self._metadata) - - class BatchOps: """Class to execute operations in batch in Fluent. @@ -99,10 +63,6 @@ class BatchOps: access the ``mesh-1`` mesh object which has not been created yet. """ - _proto_files: list[ModuleType] | None = None - _api_module = api - _proto_module = batch_ops_pb2 - def _instance(): return None @@ -122,86 +82,31 @@ class Op: def __init__( self, - owner_cls: type["BatchOps"], package: str, service: str, method: str, request_body: bytes, ) -> None: """__init__ method of Op class.""" - self._request = owner_cls._proto_module.ExecuteRequest( - package=package, - service=service, - method=method, - request_body=request_body, - ) - if not owner_cls._proto_files: - owner_proto_files = [ - x[1] - for x in inspect.getmembers(owner_cls._api_module, inspect.ismodule) - if hasattr(x[1], "DESCRIPTOR") - ] - loaded_proto_files = [ - module - for name, module in sys.modules.items() - if name.endswith("_pb2") and hasattr(module, "DESCRIPTOR") - ] - owner_cls._proto_files = owner_proto_files + [ - module - for module in loaded_proto_files - if module not in owner_proto_files - ] + self._package = package + self._service_name = service + self._method = method + self._request_body = request_body self._supported = False self.response_cls = None - for file in owner_cls._proto_files: - file_desc = file.DESCRIPTOR - if file_desc.package == package: - service_desc = file_desc.services_by_name.get(service) - if service_desc: - # TODO Add custom option in .proto files to identify getters - if not method.startswith("Get") and not method.startswith( - "get" - ): - method_desc = service_desc.methods_by_name.get(method) - if ( - method_desc - and not method_desc.client_streaming - and not method_desc.server_streaming - ): - self._supported = True - response_cls_name = method_desc.output_type.name - # TODO Get the response_cls from message_factory - try: - self.response_cls = getattr(file, response_cls_name) - break - except AttributeError: - pass - if self._supported: - self._request = owner_cls._proto_module.ExecuteRequest( - package=package, - service=service, - method=method, - request_body=request_body, - ) - self._status = None - self._result = None + self._status = None + self._result = None self.queued = False - def update_result(self, status: batch_ops_pb2.ExecuteStatus, data: str) -> None: + def update_result(self, status, result) -> None: """Update results after the batch operation is executed.""" - obj = self.response_cls() - try: - obj.ParseFromString(data) - except Exception as ex: - # It will log any exception coming from grpc layer during parsing of data. - network_logger.warning(ex) self._status = status - self._result = obj + self._result = result def __new__(cls, session) -> _TBatchOps: if cls.instance() is None: instance = super().__new__(cls) - instance._service: BatchOpsService = session._batch_ops_service + instance._service = session._batch_ops_service instance._ops: list[BatchOps.Op] = [] instance.batching = False cls._instance = weakref.ref(instance) @@ -218,12 +123,11 @@ def __exit__(self, exc_type, exc_value, exc_tb) -> None: network_logger.debug("Executing batch operations") self.batching = False if not exc_type: - requests = (x._request for x in self._ops) - responses = self._service.execute(requests) - for i, response in enumerate(responses): - self._ops[i].update_result(response.status, response.response_body) + results = self._service.execute(self._ops) + for op, (status, result) in zip(self._ops, results): + op.update_result(status, result) - def add_op(self, package: str, service: str, method: str, request: Message) -> Op: + def add_op(self, package: str, service: str, method: str, request) -> Op: """Queue a single batch operation. Only the non-getter operations will be queued. @@ -244,12 +148,9 @@ def add_op(self, package: str, service: str, method: str, request: Message) -> O BatchOps.Op object with a queued attribute which is true if the operation has been queued. """ - op = self.__class__.Op( - self.__class__, - package, - service, - method, - request.SerializeToString(), + op = self.__class__.Op(package, service, method, request.SerializeToString()) + op._supported, op.response_cls = self._service.get_op_metadata( + package, service, method ) if op._supported: network_logger.debug( diff --git a/src/ansys/fluent/core/services/datamodel_tui_v1.py b/src/ansys/fluent/core/services/datamodel_tui_v1.py deleted file mode 100644 index c351a4b0fac..00000000000 --- a/src/ansys/fluent/core/services/datamodel_tui_v1.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. -# 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. - -"""Wrappers over TUI-based datamodel gRPC service of Fluent (v1 proto API). - -This module intentionally reuses the shared menu/runtime logic from -``datamodel_tui.py`` and keeps only v1-specific proto/stub/request differences. -""" - -from typing import Any - -import grpc - -from ansys.api.fluent.v1 import text_interface_pb2 as DataModelProtoModule -from ansys.api.fluent.v1 import text_interface_pb2_grpc as DataModelGrpcModule -from ansys.fluent.core.services import ( - datamodel_tui as _v0, # v0 base: shared menu/runtime logic is reused; only v1-specific proto/stub differences are overridden below -) - -Path = _v0.Path -logger = _v0.logger - -_convert_value_to_gvalue = _v0._convert_value_to_gvalue -_convert_gvalue_to_value = _v0._convert_gvalue_to_value - -PyMenu = _v0.PyMenu -TUIMethod = _v0.TUIMethod -TUIMenu = _v0.TUIMenu -TUICommand = _v0.TUICommand - -convert_tui_menu_to_func_name = _v0.convert_tui_menu_to_func_name -convert_path_to_grpc_path = _v0.convert_path_to_grpc_path - - -class DatamodelServiceImpl(_v0.DatamodelServiceImpl): - """Class wrapping the TUI-based datamodel gRPC service of Fluent (v1).""" - - def __init__( - self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state - ) -> None: - """Initialize DatamodelServiceImpl.""" - self._channel = channel - self._fluent_error_state = fluent_error_state - intercept_channel = grpc.intercept_channel( - self._channel, - _v0.GrpcErrorInterceptor(), - _v0.ErrorStateInterceptor(self._fluent_error_state), - _v0.TracingInterceptor(), - _v0.BatchInterceptor(), - ) - self._stub = DataModelGrpcModule.TextInterfaceStub(intercept_channel) - self._metadata = metadata - - def get_static_info(self, request): - """GetSchema RPC of DataModel service.""" - return self._stub.GetSchema(request, metadata=self._metadata) - - -class DatamodelService(_v0.DatamodelService): - """Pure Python wrapper of DatamodelServiceImpl (v1).""" - - def __init__( - self, - channel: grpc.Channel, - metadata: list[tuple[str, str]], - fluent_error_state, - app_utilities, - scheme_eval, - ) -> None: - """Initialize DatamodelService.""" - self._impl = DatamodelServiceImpl(channel, metadata, fluent_error_state) - self._app_utilities = app_utilities - self._scheme_eval = scheme_eval - - @staticmethod - def _normalize_attribute_name(attribute: str) -> str: - name = attribute.upper() - return name if name.startswith("ATTRIBUTE_") else f"ATTRIBUTE_{name}" - - def get_attribute_value( - self, path: str, attribute: str, include_unavailable: bool - ) -> Any: - """Get the attribute value.""" - request = DataModelProtoModule.GetAttributeValueRequest() - request.path = path - request.attribute = DataModelProtoModule.Attribute.Value( - self._normalize_attribute_name(attribute) - ) - if include_unavailable: - request.args["include_unavailable"] = 1 - response = self._impl.get_attribute_value(request) - return _convert_gvalue_to_value(response.value) - - def execute_query(self, path: str, *args, **kwargs) -> Any: - """Execute the query.""" - request = DataModelProtoModule.ExecuteQueryRequest() - request.path = path - if args or kwargs: - logger.debug( - "Ignoring query args for v1 TUI ExecuteQuery; request schema has no args field." - ) - return self._impl.execute_query(request) diff --git a/src/ansys/fluent/core/services/events.py b/src/ansys/fluent/core/services/events.py index 5adff75f2be..2f9faf5cf20 100644 --- a/src/ansys/fluent/core/services/events.py +++ b/src/ansys/fluent/core/services/events.py @@ -21,27 +21,145 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the events gRPC service of Fluent.""" +"""High level events wrapper.""" -import grpc +from ansys.fluent.core.services.abstract_events import AbstractEvents +from ansys.fluent.core.streaming_services.events_streaming import ( + SolverEvent as SolverEventV0, +) +from ansys.fluent.core.streaming_services.events_streaming_v1 import SolverEvent -from ansys.api.fluent.v0 import events_pb2_grpc as EventsGrpcModule -from ansys.fluent.core.services._protocols import ServiceProtocol -from ansys.fluent.core.services.streaming import StreamingService +class Events(AbstractEvents): + """Events backed by the Events gRPC service.""" -class EventsService( - StreamingService, ServiceProtocol -): # pyright: ignore[reportUnsafeMultipleInheritance] - """Class wrapping the events gRPC service of Fluent.""" + def __init__(self, service): + """Initialize Events.""" + self.service = service - def __init__(self, channel: grpc.Channel, metadata: list[tuple[str, str]]): - """__init__ method of EventsService class.""" - super().__init__( - stub=self._create_stub(channel), - metadata=metadata, + def register_pause_on_solution_events( + self, solution_event: SolverEvent | SolverEventV0 + ) -> int: + """Register pause on solution events.""" + return self.service.register_pause_on_solution_events(solution_event) + + def resume_on_solution_event(self, registration_id: int) -> None: + """Resume on solution event.""" + self.service.resume_on_solution_event(registration_id) + + def unregister_pause_on_solution_events(self, registration_id: int) -> None: + """Unregister pause on solution events.""" + self.service.unregister_pause_on_solution_events(registration_id) + + def begin_streaming(self, request, started_evt, id, stream_begin_method): + """Begin streaming from Fluent.""" + return self.service.begin_streaming( + request, started_evt, id=id, stream_begin_method=stream_begin_method + ) + + def end_streaming(self, id, stream_begin_method) -> None: + """End streaming from Fluent.""" + self.service.end_streaming(id, stream_begin_method) + + +class EventsV261(AbstractEvents): + """Events backed by the Events gRPC service.""" + + def __init__(self, service, application_runtime_service): + """Initialize ApplicationRuntime.""" + self.service = service + self.application_runtime_service = application_runtime_service + + def register_pause_on_solution_events( + self, solution_event: SolverEvent | SolverEventV0 + ) -> int: + """Register pause on solution events.""" + return self.application_runtime_service.register_pause_on_solution_events( + solution_event + ) + + def resume_on_solution_event(self, registration_id: int) -> None: + """Resume on solution event.""" + self.application_runtime_service.resume_on_solution_event(registration_id) + + def unregister_pause_on_solution_events(self, registration_id: int) -> None: + """Unregister pause on solution events.""" + self.application_runtime_service.unregister_pause_on_solution_events( + registration_id + ) + + def begin_streaming(self, request, started_evt, id, stream_begin_method): + """Begin streaming from Fluent.""" + return self.service.begin_streaming( + request, started_evt, id=id, stream_begin_method=stream_begin_method + ) + + def end_streaming(self, id, stream_begin_method) -> None: + """End streaming from Fluent.""" + self.service.end_streaming(id, stream_begin_method) + + +class EventsV251(AbstractEvents): + """Events backed by the Events gRPC service.""" + + def __init__(self, service, scheme_interpreter_service): + """Initialize ApplicationRuntime.""" + self.service = service + self.scheme_interpreter_service = scheme_interpreter_service + + def register_pause_on_solution_events( + self, solution_event: SolverEvent | SolverEventV0 + ) -> int: + """Register pause on solution events.""" + unique_id: int = self.scheme_interpreter_service.eval( + f""" + (let + ((ids + (let loop ((i 1)) + (define next-id (string->symbol (format #f "pyfluent-~d" i))) + (if (check-monitor-existence next-id) + (loop (1+ i)) + (list i next-id) + ) + ) + )) + (register-solution-monitor + (cadr ids) + (lambda (niter time) + (if (integer? niter) + (begin + (events/transmit 'auto-pause (cons (car ids) niter)) + (grpcserver/auto-pause (is-server-running?) (cadr ids)) + ) + ) + () + ) + {"#t" if solution_event.name == "TIMESTEP_ENDED" else "#f"} + ) + (car ids) + ) + """ + ) + return unique_id + + def resume_on_solution_event(self, registration_id: int) -> None: + """Resume on solution event.""" + self.scheme_interpreter_service.eval( + f"(grpcserver/auto-resume (is-server-running?) 'pyfluent-{registration_id})" + ) + + def unregister_pause_on_solution_events(self, registration_id: int) -> None: + """Unregister pause on solution events.""" + self.scheme_interpreter_service.eval( + f"(cancel-solution-monitor 'pyfluent-{registration_id})" + ) + + def begin_streaming(self, request, started_evt, id, stream_begin_method): + """Begin streaming from Fluent.""" + return self.service.begin_streaming( + request, started_evt, id=id, stream_begin_method=stream_begin_method ) - def _create_stub(self, channel: grpc.Channel): - """Create the gRPC stub. Override in subclasses to use a different proto version.""" - return EventsGrpcModule.EventsStub(channel) + def end_streaming(self, id, stream_begin_method) -> None: + """End streaming from Fluent.""" + self.service.end_streaming(id, stream_begin_method) diff --git a/src/ansys/fluent/core/services/monitors.py b/src/ansys/fluent/core/services/monitors.py new file mode 100644 index 00000000000..4acf9b7a416 --- /dev/null +++ b/src/ansys/fluent/core/services/monitors.py @@ -0,0 +1,59 @@ +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +"""High level monitor wrapper.""" + + +from ansys.fluent.core.services.abstract_monitors import AbstractMonitor + + +class Monitor(AbstractMonitor): + """Monitor backed by the Monitor gRPC service.""" + + def __init__(self, service): + """Initialize Monitor.""" + self.service = service + + def get_monitors_info(self) -> dict: + """Get monitors information. + + Parameters + ---------- + None + + Returns + ------- + dict + Dictionary containing the monitors information. + """ + return self.service.get_monitors_info() + + def begin_streaming(self, request, started_evt, id, stream_begin_method): + """Begin streaming from Fluent.""" + return self.service.begin_streaming( + request, started_evt, id=id, stream_begin_method=stream_begin_method + ) + + def end_streaming(self, id, stream_begin_method) -> None: + """End streaming from Fluent.""" + self.service.end_streaming(id, stream_begin_method) diff --git a/src/ansys/fluent/core/services/object_model.py b/src/ansys/fluent/core/services/object_model.py index 4a78840d70e..2dd0f46735c 100644 --- a/src/ansys/fluent/core/services/object_model.py +++ b/src/ansys/fluent/core/services/object_model.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrappers over datamodel gRPC service of Fluent.""" +"""High level object model wrapper.""" from collections.abc import Callable, Iterator, Sequence import functools diff --git a/src/ansys/fluent/core/services/solution_variables.py b/src/ansys/fluent/core/services/solution_variables.py index 96daa5f73f5..d457bd4e85d 100644 --- a/src/ansys/fluent/core/services/solution_variables.py +++ b/src/ansys/fluent/core/services/solution_variables.py @@ -23,24 +23,16 @@ """Wrappers over SVAR gRPC service of Fluent.""" -from collections.abc import Sequence -import math from typing import Any -import warnings -import grpc import numpy as np import numpy.typing as npt -from ansys.api.fluent.v0 import field_data_pb2 as FieldDataProtoModule -from ansys.api.fluent.v0 import svar_pb2 as SvarProtoModule -from ansys.api.fluent.v0 import svar_pb2_grpc as SvarGrpcModule from ansys.fluent.core.fields.live_field_data import override_help_text -from ansys.fluent.core.pyfluent_warnings import PyFluentDeprecationWarning -from ansys.fluent.core.services._protocols import ServiceProtocol -from ansys.fluent.core.services.interceptors import ( - GrpcErrorInterceptor, - TracingInterceptor, +from ansys.fluent.core.services.abstract_solution_variables import ( + AbstractData, + AbstractSolutionVariableData, + AbstractSolutionVariableInfo, ) from ansys.fluent.core.solver.error_message import allowed_name_error_message from ansys.fluent.core.utils.deprecate import deprecate_arguments @@ -51,67 +43,37 @@ _to_field_name_str = naming_strategy().to_string -class _FieldDataConstants: - """Defines constants for Fluent field data.""" - - # data mapping - proto_field_type_to_np_data_type = { - FieldDataProtoModule.FieldType.INT_ARRAY: np.int32, - FieldDataProtoModule.FieldType.LONG_ARRAY: np.int64, - FieldDataProtoModule.FieldType.FLOAT_ARRAY: np.float32, - FieldDataProtoModule.FieldType.DOUBLE_ARRAY: np.float64, - } - np_data_type_to_proto_field_type = { - np.int32: FieldDataProtoModule.FieldType.INT_ARRAY, - np.int64: FieldDataProtoModule.FieldType.LONG_ARRAY, - np.float32: FieldDataProtoModule.FieldType.FLOAT_ARRAY, - np.float64: FieldDataProtoModule.FieldType.DOUBLE_ARRAY, - } - chunk_size = 256 * 1024 - bytes_stream = True - payloadTags = { - FieldDataProtoModule.PayloadTag.OVERSET_MESH: 1, - FieldDataProtoModule.PayloadTag.ELEMENT_LOCATION: 2, - FieldDataProtoModule.PayloadTag.NODE_LOCATION: 4, - FieldDataProtoModule.PayloadTag.BOUNDARY_VALUES: 8, - } - - -class SolutionVariableService(ServiceProtocol): - """SVAR service of Fluent.""" - - def __init__(self, channel: grpc.Channel, metadata): - """__init__ method of SVAR service class.""" - intercept_channel = grpc.intercept_channel( - channel, - GrpcErrorInterceptor(), - TracingInterceptor(), - ) - self._stub = self._create_stub(intercept_channel) - self._metadata = metadata +class Data(AbstractData): + """Solution variable data.""" - def _create_stub(self, intercept_channel): - """Create the gRPC stub. Override in subclasses to use a different proto version.""" - return SvarGrpcModule.svarStub(intercept_channel) + def __init__(self, domain_name, zone_id_name_map, solution_variable_data): + """Initialize Data.""" + self._domain_name = domain_name + self._data = { + zone_id_name_map[zone_id]: zone_data + for zone_id, zone_data in solution_variable_data.items() + } - def get_data(self, request): - """GetSvarData RPC of SVAR service.""" - return self._stub.GetSvarData(request, metadata=self._metadata) + @property + def domain(self): + """Domain name.""" + return self._domain_name - def set_data(self, request): - """SetSvarData RPC of SVAR service.""" - return self._stub.SetSvarData(request, metadata=self._metadata) + @property + def zone_names(self): + """Zone names.""" + return list(self._data.keys()) - def get_variables_info(self, request): - """GetSvarsInfo RPC of SVAR service.""" - return self._stub.GetSvarsInfo(request, metadata=self._metadata) + @property + def data(self): + """Solution variable data.""" + return self._data - def get_zones_info(self, request): - """GetZonesInfo RPC of SVAR service.""" - return self._stub.GetZonesInfo(request, metadata=self._metadata) + def __getitem__(self, name): + return self._data.get(name, None) -class SolutionVariableInfo: +class SolutionVariableInfo(AbstractSolutionVariableInfo): """Provide access to Fluent SVARs and Zones information. Examples @@ -131,160 +93,16 @@ class SolutionVariableInfo: >>> name:wall count: 3630 zone_id:3 zone_type:wall thread_type:Face """ - class SolutionVariables: - """Class containing information for multiple solution variables.""" - - class SolutionVariable: - """Class containing information for single solution variable.""" - - def __init__(self, solution_variable_info: SvarProtoModule.SvarInfo): - """Initialize SolutionVariable.""" - self.name = solution_variable_info.name - self.dimension = solution_variable_info.dimension - self.field_type = _FieldDataConstants.proto_field_type_to_np_data_type[ - solution_variable_info.fieldType - ] - - def __repr__(self): - return f"name:{self.name} dimension:{self.dimension} field_type:{self.field_type}" - - def __init__(self, solution_variables_info: Sequence[SvarProtoModule.SvarInfo]): - """Initialize SolutionVariables.""" - self._solution_variables_info: dict[ - str, "SolutionVariableInfo.SolutionVariables.SolutionVariable" - ] = { - solution_variable_info.name: SolutionVariableInfo.SolutionVariables.SolutionVariable( - solution_variable_info - ) - for solution_variable_info in solution_variables_info - } - - def _filter(self, solution_variables_info): - self._solution_variables_info = { - k: v - for k, v in self._solution_variables_info.items() - if k - in [ - solution_variable_info.name - for solution_variable_info in solution_variables_info - ] - } - - def __getitem__( - self, name: str - ) -> "SolutionVariableInfo.SolutionVariables.SolutionVariable": - return self._solution_variables_info[name] - - def get( - self, name: str - ) -> "SolutionVariableInfo.SolutionVariables.SolutionVariable | None": - """Get name from solution variables""" - return self._solution_variables_info.get(name) - - @property - def solution_variables(self) -> list[str]: - """Solution variables.""" - return list(self._solution_variables_info.keys()) - - @property - def svars(self) -> list[str]: - """Solution variables.""" - warnings.warn( - "svars is deprecated, use solution_variables instead", - PyFluentDeprecationWarning, - ) - return self.solution_variables - - class ZonesInfo: - """Class containing information for multiple zones.""" - - class ZoneInfo: - """Class containing information for single zone.""" - - class PartitionsInfo: - """Class containing information for partitions.""" - - def __init__(self, partition_info): - """Initialize PartitionsInfo.""" - self.count = partition_info.count - self.start_index = ( - partition_info.startIndex if self.count > 0 else 0 - ) - self.end_index = partition_info.endIndex if self.count > 0 else 0 - - def __init__(self, zone_info): - """Initialize ZoneInfo.""" - self.name = zone_info.name - self.zone_id = zone_info.zoneId - self.zone_type = zone_info.zoneType - self.thread_type = zone_info.threadType - self.partitions_info = [ - self.PartitionsInfo(partition_info) - for partition_info in zone_info.partitionsInfo - ] - - @property - def count(self) -> int: - """Get zone count.""" - return sum( - [partition_info.count for partition_info in self.partitions_info] - ) - - def __repr__(self): - partition_str = "" - for i, partition_info in enumerate(self.partitions_info): - partition_str += f"\n\t{i}. {partition_info.count}[{partition_info.start_index}:{partition_info.end_index}]" - return f"name:{self.name} count: {self.count} zone_id:{self.zone_id} zone_type:{self.zone_type} threadType:{'Cell' if self.thread_type == SvarProtoModule.ThreadType.CELL_THREAD else 'Face'}{partition_str}" - - def __init__(self, zones_info, domains_info): - """Initialize ZonesInfo.""" - self._zones_info: dict[str, "SolutionVariableInfo.ZonesInfo.ZoneInfo"] = { - zone_info.name: self.ZoneInfo(zone_info) for zone_info in zones_info - } - self._domains_info: dict[str, int] = { - domain_info.name: domain_info.domainId for domain_info in domains_info - } - - def __getitem__(self, name: str) -> "SolutionVariableInfo.ZonesInfo.ZoneInfo": - return self._zones_info[name] - - def get(self, name: str) -> "SolutionVariableInfo.ZonesInfo.ZoneInfo | None": - """Get name from zones info""" - return self._zones_info.get(name) - - @property - def zones(self): - """Get zone names.""" - warnings.warn( - "'zones' is deprecated, use 'zone_names' instead", - PyFluentDeprecationWarning, - ) - return self.zone_names - - @property - def zone_names(self) -> list[str]: - """Get zone names.""" - return list(self._zones_info.keys()) - - @property - def domains(self) -> list[str]: - """Get domain names.""" - return list(self._domains_info.keys()) - - def domain_id(self, domain_name) -> int: - """Get domain id.""" - return self._domains_info.get(domain_name, None) - def __init__( self, - service: SolutionVariableService, + service, ): """Initialize SolutionVariableInfo.""" self._service = service def get_variables_info( self, zone_names: list[str], domain_name: str | None = "mixture" - ) -> SolutionVariables: + ): """Get SVARs info for zones in the domain. Parameters @@ -300,34 +118,14 @@ def get_variables_info( Object containing information for SVARs which are common for list of zone names. """ - allowed_zone_names = _AllowedZoneNames(self) - allowed_domain_names = _AllowedDomainNames(self) - solution_variables_info = None - for zone_name in zone_names: - request = SvarProtoModule.GetSvarsInfoRequest( - domainId=allowed_domain_names.valid_name(domain_name), - zoneId=allowed_zone_names.valid_name(zone_name), - ) - response = self._service.get_variables_info(request) - if solution_variables_info is None: - solution_variables_info = SolutionVariableInfo.SolutionVariables( - response.svarsInfo - ) - else: - solution_variables_info._filter(response.svarsInfo) - return solution_variables_info - - def get_svars_info( - self, zone_names: list[str], domain_name: str | None = "mixture" - ) -> SolutionVariables: - """Get solution variables info.""" - warnings.warn( - "get_svars_info is deprecated, use get_variables_info instead", - PyFluentDeprecationWarning, + return self._service.get_variables_info( + zone_names=zone_names, + domain_name=domain_name, + allowed_zone_names=_AllowedZoneNames(self), + allowed_domain_names=_AllowedDomainNames(self), ) - return self.get_variables_info(zone_names=zone_names, domain_name=domain_name) - def get_zones_info(self) -> ZonesInfo: + def get_zones_info(self): """Get Zones info. Parameters @@ -339,9 +137,7 @@ def get_zones_info(self) -> ZonesInfo: SolutionVariableInfo.ZonesInfo Object containing information for all zones. """ - request = SvarProtoModule.GetZonesInfoRequest() - response = self._service.get_zones_info(request) - return SolutionVariableInfo.ZonesInfo(response.zonesInfo, response.domainsInfo) + return self._service.get_zones_info() class InvalidSolutionVariableNameError(ValueError): @@ -495,60 +291,7 @@ def __call__(self, *args, **kwargs): return self._svar_accessor(*args, **kwargs) -def extract_svars(solution_variables_data): - """Extracts SVAR data via a server call.""" - - def _extract_svar( - field_datatype: npt.DTypeLike, field_size: int, solution_variables_data - ) -> npt.NDArray[np.float64] | None: - field_arr = np.empty(field_size, dtype=field_datatype) - field_datatype_item_size = np.dtype(field_datatype).itemsize - index = 0 - for solution_variable_data in solution_variables_data: - chunk = solution_variable_data.payload - if chunk.bytePayload: - count = min( - len(chunk.bytePayload) // field_datatype_item_size, - field_size - index, - ) - field_arr[index : index + count] = np.frombuffer( - chunk.bytePayload, field_datatype, count=count - ) - index += count - if index == field_size: - return field_arr - else: - payload: Sequence[float] = ( - chunk.floatPayload.payload - or chunk.intPayload.payload - or chunk.doublePayload.payload - or chunk.longPayload.payload - ) - count = len(payload) - field_arr[index : index + count] = np.fromiter( - payload, dtype=field_datatype - ) - index += count - if index == field_size: - return field_arr - - zones_svar_data = dict[Any, npt.NDArray[Any] | None]() - for array in solution_variables_data: - if array.WhichOneof("array") == "payloadInfo": - zones_svar_data[array.payloadInfo.zone] = _extract_svar( - _FieldDataConstants.proto_field_type_to_np_data_type[ - array.payloadInfo.fieldType - ], - array.payloadInfo.fieldSize, - solution_variables_data, - ) - elif array.WhichOneof("array") == "header": - continue - - return zones_svar_data - - -class SolutionVariableData: +class SolutionVariableData(AbstractSolutionVariableData): """Provides access to Fluent SVAR data on zones. Examples @@ -574,47 +317,9 @@ class SolutionVariableData: >>> solution_variable_data.set_data(variable_name="SV_T", domain_name="mixture", zone_names_to_data=zone_names_to_data) """ - class Data: - """Solution variable data.""" - - def __init__(self, domain_name, zone_id_name_map, solution_variable_data): - """Initialize Data.""" - self._domain_name = domain_name - self._data = { - zone_id_name_map[zone_id]: zone_data - for zone_id, zone_data in solution_variable_data.items() - } - - @property - def domain(self): - """Domain name.""" - return self._domain_name - - @property - def zones(self): - """Zone names.""" - warnings.warn( - "'zones' is deprecated, use 'zone_names' instead", - PyFluentDeprecationWarning, - ) - return self.zone_names - - @property - def zone_names(self): - """Zone names.""" - return list(self._data.keys()) - - @property - def data(self): - """Solution variable data.""" - return self._data - - def __getitem__(self, name): - return self._data.get(name, None) - def __init__( self, - service: SolutionVariableService, + service, solution_variable_info: SolutionVariableInfo, ): """Initialize SolutionVariableData.""" @@ -638,11 +343,6 @@ def _update_solution_variable_info(self): self._solution_variable_info ) - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) def create_empty_array( self, variable_name: str, @@ -672,11 +372,6 @@ def create_empty_array( dtype=solution_variables_info[variable_name].field_type, ) - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) def get_data( self, variable_name: str, @@ -696,59 +391,24 @@ def get_data( Returns ------- - SolutionVariableData.Data + Data Object containing SVAR data. """ self._update_solution_variable_info() - svars_request = SvarProtoModule.GetSvarDataRequest( - provideBytesStream=_FieldDataConstants.bytes_stream, - chunkSize=_FieldDataConstants.chunk_size, - ) - svars_request.domainId = self._allowed_domain_names.valid_name(domain_name) - svars_request.name = self._allowed_solution_variable_names.valid_name( - variable_name, - zone_names, - domain_name, - ) - zone_id_name_map = {} - for zone_name in zone_names: - zone_id = self._allowed_zone_names.valid_name(zone_name) - zone_id_name_map[zone_id] = zone_name - svars_request.zones.append(zone_id) - - return SolutionVariableData.Data( - domain_name, - zone_id_name_map, - extract_svars(self._service.get_data(svars_request)), - ) - - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) - def get_svar_data( - self, - variable_name: str, - zone_names: list[str], - domain_name: str | None = "mixture", - ) -> Data: - """Get solution variable data.""" - warnings.warn( - "get_svar_data is deprecated, use get_data instead", - PyFluentDeprecationWarning, - ) - return self.get_data( + zone_id_name_map, svar_data = self._service.get_data( variable_name=variable_name, zone_names=zone_names, domain_name=domain_name, + allowed_solution_variable_names=self._allowed_solution_variable_names, + allowed_domain_names=self._allowed_domain_names, + allowed_zone_names=self._allowed_zone_names, + ) + return Data( + domain_name, + zone_id_name_map, + svar_data, ) - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) def set_data( self, variable_name: str, @@ -771,106 +431,11 @@ def set_data( None """ self._update_solution_variable_info() - variable_name = self._allowed_solution_variable_names.valid_name( - variable_name, - list(zone_names_to_data.keys()), - domain_name, - ) - domain_id = self._allowed_domain_names.valid_name(domain_name) - zone_ids_to_svar_data = { - self._allowed_zone_names.valid_name(zone_name): solution_variable_data - for zone_name, solution_variable_data in zone_names_to_data.items() - } - - def generate_set_data_requests(): - set_data_requests = [] - - set_data_requests.append( - SvarProtoModule.SetSvarDataRequest( - header=SvarProtoModule.SvarHeader( - name=variable_name, domainId=domain_id - ) - ) - ) - - for zone_id, solution_variable_data in zone_ids_to_svar_data.items(): - max_array_size = ( - _FieldDataConstants.chunk_size - / np.dtype(solution_variable_data.dtype).itemsize - ) - solution_variable_data_list = np.array_split( - solution_variable_data, - math.ceil(solution_variable_data.size / max_array_size), - ) - set_data_requests.append( - SvarProtoModule.SetSvarDataRequest( - payloadInfo=SvarProtoModule.Info( - fieldType=_FieldDataConstants.np_data_type_to_proto_field_type[ - solution_variable_data.dtype.type - ], - fieldSize=solution_variable_data.size, - zone=zone_id, - ) - ) - ) - set_data_requests += [ - SvarProtoModule.SetSvarDataRequest( - payload=( - SvarProtoModule.Payload( - floatPayload=FieldDataProtoModule.FloatPayload( - payload=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.float32 - else ( - SvarProtoModule.Payload( - doublePayload=FieldDataProtoModule.DoublePayload( - payload=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.float64 - else ( - SvarProtoModule.Payload( - intPayload=FieldDataProtoModule.IntPayload( - payload=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.int32 - else SvarProtoModule.Payload( - longPayload=FieldDataProtoModule.LongPayload( - payload=solution_variable_data - ) - ) - ) - ) - ) - ) - for solution_variable_data in solution_variable_data_list - if solution_variable_data.size > 0 - ] - - yield from set_data_requests - - self._service.set_data(generate_set_data_requests()) - - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) - def set_svar_data( - self, - variable_name: str, - zone_names_to_svar_data: dict[str, npt.NDArray[Any]], - domain_name: str | None = "mixture", - ) -> None: - """Set solution variable data.""" - warnings.warn( - "set_svar_data is deprecated, use set_data instead", - PyFluentDeprecationWarning, - ) - return self.set_data( + self._service.set_data( variable_name=variable_name, - zone_names_to_data=zone_names_to_svar_data, + zone_names_to_data=zone_names_to_data, domain_name=domain_name, + allowed_solution_variable_names=self._allowed_solution_variable_names, + allowed_domain_names=self._allowed_domain_names, + allowed_zone_names=self._allowed_zone_names, ) diff --git a/src/ansys/fluent/core/services/solution_variables_v1.py b/src/ansys/fluent/core/services/solution_variables_v1.py deleted file mode 100644 index 8d35010d3e0..00000000000 --- a/src/ansys/fluent/core/services/solution_variables_v1.py +++ /dev/null @@ -1,380 +0,0 @@ -# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. -# 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. - -"""Wrappers over SVAR gRPC service of Fluent (v1 proto API). - -All shared logic lives in solution_variables.py (v0). This module keeps only -v1-specific proto construction/parsing required for compatibility. -""" - -import math -from typing import Dict, List - -import numpy as np - -from ansys.api.fluent.v1 import field_data_pb2 as FieldDataProtoModule -from ansys.api.fluent.v1 import solution_variable_pb2 as SvarProtoModule -from ansys.api.fluent.v1 import solution_variable_pb2_grpc as SvarGrpcModule -from ansys.fluent.core.services import solution_variables as _v0 -from ansys.fluent.core.utils.deprecate import deprecate_arguments - - -class _FieldDataConstants: - """Defines constants for Fluent field data.""" - - proto_field_type_to_np_data_type = { - FieldDataProtoModule.FieldType.FIELD_TYPE_INT_ARRAY: np.int32, - FieldDataProtoModule.FieldType.FIELD_TYPE_LONG_ARRAY: np.int64, - FieldDataProtoModule.FieldType.FIELD_TYPE_FLOAT_ARRAY: np.float32, - FieldDataProtoModule.FieldType.FIELD_TYPE_DOUBLE_ARRAY: np.float64, - } - np_data_type_to_proto_field_type = { - np.int32: FieldDataProtoModule.FieldType.FIELD_TYPE_INT_ARRAY, - np.int64: FieldDataProtoModule.FieldType.FIELD_TYPE_LONG_ARRAY, - np.float32: FieldDataProtoModule.FieldType.FIELD_TYPE_FLOAT_ARRAY, - np.float64: FieldDataProtoModule.FieldType.FIELD_TYPE_DOUBLE_ARRAY, - } - chunk_size = 256 * 1024 - bytes_stream = True - payloadTags = { - FieldDataProtoModule.PayloadTag.PAYLOAD_TAG_OVERSET_MESH: 1, - FieldDataProtoModule.PayloadTag.PAYLOAD_TAG_ELEMENT_LOCATION: 2, - FieldDataProtoModule.PayloadTag.PAYLOAD_TAG_NODE_LOCATION: 4, - FieldDataProtoModule.PayloadTag.PAYLOAD_TAG_BOUNDARY_VALUES: 8, - } - - -override_help_text = _v0.override_help_text -PyFluentDeprecationWarning = _v0.PyFluentDeprecationWarning -allowed_name_error_message = _v0.allowed_name_error_message -_to_field_name_str = _v0._to_field_name_str - -InvalidSolutionVariableNameError = _v0.InvalidSolutionVariableNameError -ZoneError = _v0.ZoneError -_AllowedNames = _v0._AllowedNames -_AllowedSvarNames = _v0._AllowedSvarNames -_AllowedZoneNames = _v0._AllowedZoneNames -_AllowedDomainNames = _v0._AllowedDomainNames -_SvarMethod = _v0._SvarMethod - - -class SolutionVariableService(_v0.SolutionVariableService): - """SVAR service of Fluent (v1 proto API).""" - - def _create_stub(self, intercept_channel): - """Create the v1 gRPC stub.""" - return SvarGrpcModule.SolutionVariableStub(intercept_channel) - - def get_data(self, request): - """GetSolutionVariableData RPC of SolutionVariable service (v1).""" - return self._stub.GetSolutionVariableData(request, metadata=self._metadata) - - def set_data(self, request): - """SetSolutionVariableData RPC of SolutionVariable service (v1).""" - return self._stub.SetSolutionVariableData(request, metadata=self._metadata) - - def get_variables_info(self, request): - """GetSolutionVariableInfo RPC of SolutionVariable service (v1).""" - return self._stub.GetSolutionVariableInfo(request, metadata=self._metadata) - - -class SolutionVariableInfo(_v0.SolutionVariableInfo): - """Provide access to Fluent SVARs and Zones information (v1 proto API).""" - - class SolutionVariables(_v0.SolutionVariableInfo.SolutionVariables): - """Class containing information for multiple solution variables.""" - - class SolutionVariable( - _v0.SolutionVariableInfo.SolutionVariables.SolutionVariable - ): - """Class containing information for single solution variable.""" - - def __init__(self, solution_variable_info): - """Initialize SolutionVariable.""" - self.name = solution_variable_info.name - self.dimension = solution_variable_info.dimension - self.field_type = _FieldDataConstants.proto_field_type_to_np_data_type[ - solution_variable_info.field_type - ] - - def __init__(self, solution_variables_info): - """Initialize SolutionVariables.""" - self._solution_variables_info = {} - for solution_variable_info in solution_variables_info: - self._solution_variables_info[solution_variable_info.name] = ( - self.SolutionVariable(solution_variable_info) - ) - - class ZonesInfo(_v0.SolutionVariableInfo.ZonesInfo): - """Class containing information for multiple zones.""" - - class ZoneInfo(_v0.SolutionVariableInfo.ZonesInfo.ZoneInfo): - """Class containing information for single zone.""" - - class PartitionsInfo( - _v0.SolutionVariableInfo.ZonesInfo.ZoneInfo.PartitionsInfo - ): - """Class containing information for partitions.""" - - def __init__(self, partition_info): - """Initialize PartitionsInfo.""" - self.count = partition_info.count - self.start_index = ( - partition_info.start_index if self.count > 0 else 0 - ) - self.end_index = partition_info.end_index if self.count > 0 else 0 - - def __init__(self, zone_info): - """Initialize ZoneInfo.""" - self.name = zone_info.name - self.zone_id = zone_info.zone_id - self.zone_type = zone_info.zone_type - self.thread_type = zone_info.thread_type - self.partitions_info = [ - self.PartitionsInfo(partition_info) - for partition_info in zone_info.partitions_info - ] - - def __init__(self, zones_info, domains_info): - """Initialize ZonesInfo.""" - self._zones_info = {} - self._domains_info = {} - for zone_info in zones_info: - self._zones_info[zone_info.name] = self.ZoneInfo(zone_info) - for domain_info in domains_info: - self._domains_info[domain_info.name] = domain_info.domain_id - - def get_variables_info( - self, zone_names: List[str], domain_name: str | None = "mixture" - ) -> SolutionVariables: - """Get SVARs info for zones in the domain.""" - allowed_zone_names = _AllowedZoneNames(self) - allowed_domain_names = _AllowedDomainNames(self) - solution_variables_info = None - for zone_name in zone_names: - request = SvarProtoModule.GetSolutionVariableInfoRequest( - domain_id=allowed_domain_names.valid_name(domain_name), - zone_id=allowed_zone_names.valid_name(zone_name), - ) - response = self._service.get_variables_info(request) - if solution_variables_info is None: - solution_variables_info = SolutionVariableInfo.SolutionVariables( - response.svars_info - ) - else: - solution_variables_info._filter(response.svars_info) - return solution_variables_info - - def get_zones_info(self) -> ZonesInfo: - """Get zones info.""" - request = SvarProtoModule.GetZonesInfoRequest() - response = self._service.get_zones_info(request) - return SolutionVariableInfo.ZonesInfo( - response.zones_info, response.domains_info - ) - - -def extract_svars(solution_variables_data): - """Extract SVAR data via a server call (v1 proto payload shape).""" - - def _extract_svar(field_datatype, field_size, solution_variables_data): - field_arr = np.empty(field_size, dtype=field_datatype) - field_datatype_item_size = np.dtype(field_datatype).itemsize - index = 0 - for solution_variable_data in solution_variables_data: - chunk = solution_variable_data.payload - if chunk.byte_payload: - count = min( - len(chunk.byte_payload) // field_datatype_item_size, - field_size - index, - ) - field_arr[index : index + count] = np.frombuffer( - chunk.byte_payload, field_datatype, count=count - ) - index += count - if index == field_size: - return field_arr - else: - payload = ( - chunk.float_payload.payloads - or chunk.int_payload.payloads - or chunk.double_payload.payloads - or chunk.long_payload.payloads - ) - count = len(payload) - field_arr[index : index + count] = np.fromiter( - payload, dtype=field_datatype - ) - index += count - if index == field_size: - return field_arr - - zones_svar_data = {} - for array in solution_variables_data: - if array.WhichOneof("array") == "payload_info": - zones_svar_data[array.payload_info.zone] = _extract_svar( - _FieldDataConstants.proto_field_type_to_np_data_type[ - array.payload_info.field_type - ], - array.payload_info.field_size, - solution_variables_data, - ) - elif array.WhichOneof("array") == "header": - continue - - return zones_svar_data - - -class SolutionVariableData(_v0.SolutionVariableData): - """Provides access to Fluent SVAR data on zones (v1 proto API).""" - - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) - def get_data( - self, - variable_name: str, - zone_names: List[str], - domain_name: str | None = "mixture", - ) -> _v0.SolutionVariableData.Data: - """Get SVAR data on zones.""" - self._update_solution_variable_info() - svars_request = SvarProtoModule.GetSolutionVariableDataRequest( - provide_bytes_stream=_FieldDataConstants.bytes_stream, - chunk_size=_FieldDataConstants.chunk_size, - ) - svars_request.domain_id = self._allowed_domain_names.valid_name(domain_name) - svars_request.name = self._allowed_solution_variable_names.valid_name( - variable_name, - zone_names, - domain_name, - ) - zone_id_name_map = {} - for zone_name in zone_names: - zone_id = self._allowed_zone_names.valid_name(zone_name) - zone_id_name_map[zone_id] = zone_name - svars_request.zones.append(zone_id) - - return _v0.SolutionVariableData.Data( - domain_name, - zone_id_name_map, - extract_svars(self._service.get_data(svars_request)), - ) - - @deprecate_arguments( - old_args="solution_variable_name", - new_args="variable_name", - version="v0.35.1", - ) - def set_data( - self, - variable_name: str, - zone_names_to_data: Dict[str, np.array], - domain_name: str | None = "mixture", - ) -> None: - """Set SVAR data on zones.""" - self._update_solution_variable_info() - variable_name = self._allowed_solution_variable_names.valid_name( - variable_name, - list(zone_names_to_data.keys()), - domain_name, - ) - domain_id = self._allowed_domain_names.valid_name(domain_name) - zone_ids_to_svar_data = { - self._allowed_zone_names.valid_name(zone_name): solution_variable_data - for zone_name, solution_variable_data in zone_names_to_data.items() - } - - def generate_set_data_requests(): - set_data_requests = [] - - set_data_requests.append( - SvarProtoModule.SetSolutionVariableDataRequest( - header=SvarProtoModule.SolutionVariableHeader( - name=variable_name, domain_id=domain_id - ) - ) - ) - - for zone_id, solution_variable_data in zone_ids_to_svar_data.items(): - max_array_size = ( - _FieldDataConstants.chunk_size - / np.dtype(solution_variable_data.dtype).itemsize - ) - solution_variable_data_list = np.array_split( - solution_variable_data, - math.ceil(solution_variable_data.size / max_array_size), - ) - set_data_requests.append( - SvarProtoModule.SetSolutionVariableDataRequest( - payload_info=SvarProtoModule.Info( - field_type=_FieldDataConstants.np_data_type_to_proto_field_type[ - solution_variable_data.dtype.type - ], - field_size=solution_variable_data.size, - zone=zone_id, - ) - ) - ) - set_data_requests += [ - SvarProtoModule.SetSolutionVariableDataRequest( - payload=( - SvarProtoModule.Payload( - float_payload=FieldDataProtoModule.FloatPayload( - payloads=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.float32 - else ( - SvarProtoModule.Payload( - double_payload=FieldDataProtoModule.DoublePayload( - payloads=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.float64 - else ( - SvarProtoModule.Payload( - int_payload=FieldDataProtoModule.IntPayload( - payloads=solution_variable_data - ) - ) - if solution_variable_data.dtype.type == np.int32 - else SvarProtoModule.Payload( - long_payload=FieldDataProtoModule.LongPayload( - payloads=solution_variable_data - ) - ) - ) - ) - ) - ) - for solution_variable_data in solution_variable_data_list - if solution_variable_data.size > 0 - ] - - for set_data_request in set_data_requests: - yield set_data_request - - self._service.set_data(generate_set_data_requests()) diff --git a/src/ansys/fluent/core/services/datamodel_tui.py b/src/ansys/fluent/core/services/text_interface.py similarity index 56% rename from src/ansys/fluent/core/services/datamodel_tui.py rename to src/ansys/fluent/core/services/text_interface.py index 42950468936..fa299a5c4d6 100644 --- a/src/ansys/fluent/core/services/datamodel_tui.py +++ b/src/ansys/fluent/core/services/text_interface.py @@ -31,177 +31,138 @@ import logging from typing import Any -from google.protobuf.json_format import MessageToDict -import grpc - -from ansys.api.fluent.v0 import datamodel_tui_pb2 as DataModelProtoModule -from ansys.api.fluent.v0 import datamodel_tui_pb2_grpc as DataModelGrpcModule -from ansys.api.fluent.v0.variant_pb2 import Variant -from ansys.fluent.core.services._protocols import ServiceProtocol +from ansys.fluent.core.services.abstract_text_interface import AbstractTextInterface from ansys.fluent.core.services.api_upgrade import ApiUpgradeAdvisor -from ansys.fluent.core.services.interceptors import ( - BatchInterceptor, - ErrorStateInterceptor, - GrpcErrorInterceptor, - TracingInterceptor, -) Path = list[str] logger: logging.Logger = logging.getLogger("pyfluent.tui") -class DatamodelServiceImpl: - """Class wrapping the TUI-based datamodel gRPC service of Fluent.""" - - def __init__( - self, channel: grpc.Channel, metadata: list[tuple[str, str]], fluent_error_state - ) -> None: - """__init__ method of DatamodelServiceImpl class.""" - self._channel = channel - self._fluent_error_state = fluent_error_state - intercept_channel = grpc.intercept_channel( - self._channel, - GrpcErrorInterceptor(), - ErrorStateInterceptor(self._fluent_error_state), - TracingInterceptor(), - BatchInterceptor(), - ) - self._stub = DataModelGrpcModule.DataModelStub(intercept_channel) - self._metadata = metadata - - def get_attribute_value( - self, request: DataModelProtoModule.GetAttributeValueRequest - ) -> DataModelProtoModule.GetAttributeValueResponse: - """GetAttributeValue RPC of DataModel service.""" - return self._stub.GetAttributeValue(request, metadata=self._metadata) - - def get_state( - self, request: DataModelProtoModule.GetStateRequest - ) -> DataModelProtoModule.GetStateResponse: - """GetState RPC of DataModel service.""" - return self._stub.GetState(request, metadata=self._metadata) - - def set_state( - self, request: DataModelProtoModule.SetStateRequest - ) -> DataModelProtoModule.SetStateResponse: - """SetState RPC of DataModel service.""" - return self._stub.SetState(request, metadata=self._metadata) - - def execute_command( - self, request: DataModelProtoModule.ExecuteCommandRequest - ) -> DataModelProtoModule.ExecuteCommandResponse: - """ExecuteCommand RPC of DataModel service.""" - return self._stub.ExecuteCommand(request, metadata=self._metadata) - - def execute_query( - self, request: DataModelProtoModule.ExecuteQueryRequest - ) -> DataModelProtoModule.ExecuteQueryResponse: - """ExecuteQuery RPC of DataModel service.""" - return self._stub.ExecuteQuery(request, metadata=self._metadata) - - def get_static_info(self, request): - """GetStaticInfo RPC of DataModel service.""" - return self._stub.GetStaticInfo(request, metadata=self._metadata) - - -def _convert_value_to_gvalue(val: Any, gval: Variant) -> None: - """Convert Python datatype to Value type of google/protobuf/struct.proto.""" - if isinstance(val, bool): - gval.bool_value = val - elif isinstance(val, int) or isinstance(val, float): - gval.number_value = val - elif isinstance(val, str): - gval.string_value = val - elif isinstance(val, list) or isinstance(val, tuple): - # set the one_of to list_value - gval.list_value.values.add() - gval.list_value.values.pop() - for item in val: - item_gval = gval.list_value.values.add() - _convert_value_to_gvalue(item, item_gval) - elif isinstance(val, dict): - for k, v in val.items(): - _convert_value_to_gvalue(v, gval.struct_value.fields[k]) - - -def _convert_gvalue_to_value(gval: Variant) -> Any: - """Convert Value type of google/protobuf/struct.proto to Python datatype.""" - if gval.HasField("bool_value"): - return gval.bool_value - elif gval.HasField("number_value"): - return gval.number_value - elif gval.HasField("string_value"): - return gval.string_value - elif gval.HasField("list_value"): - val = [] - for item in gval.list_value.values: - val.append(_convert_gvalue_to_value(item)) - return val - elif gval.HasField("struct_value"): - val = {} - for k, v in gval.struct_value.fields.items(): - val[k] = _convert_gvalue_to_value(v) - return val - - -class DatamodelService(ServiceProtocol): - """Pure Python wrapper of DatamodelServiceImpl.""" +class TextInterface(AbstractTextInterface): + """Pure Python wrapper of text interface grpc service.""" def __init__( self, - channel: grpc.Channel, - metadata: list[tuple[str, str]], - fluent_error_state, - app_utilities, - scheme_eval, + service, + application_runtime_service, + scheme_interpreter_service, ) -> None: """__init__ method of DatamodelService class.""" - self._impl = DatamodelServiceImpl(channel, metadata, fluent_error_state) - self._app_utilities = app_utilities - self._scheme_eval = scheme_eval + self.service = service + self._app_utilities = application_runtime_service + self._scheme_eval = scheme_interpreter_service def get_attribute_value( self, path: str, attribute: str, include_unavailable: bool ) -> Any: - """Get the attribute value.""" - request = DataModelProtoModule.GetAttributeValueRequest() - request.path = path - request.attribute = DataModelProtoModule.Attribute.Value(attribute.upper()) - if include_unavailable: - request.args["include_unavailable"] = 1 - response = self._impl.get_attribute_value(request) - return _convert_gvalue_to_value(response.value) + """Get attribute value at a path. + + Parameters + ---------- + path : str + Path to the menu. + attribute : str + Attribute name. + include_unavailable : bool + Whether to query over static TUI metadata. + + Returns + ------- + Any + Attribute value (any Python datatype) + """ + return self.service.get_attribute_value(path, attribute, include_unavailable) def execute_command(self, path: str, *args, **kwargs) -> Any: - """Execute the command.""" - request = DataModelProtoModule.ExecuteCommandRequest() - request.path = path - if kwargs: - for k, v in kwargs.items(): - _convert_value_to_gvalue(v, request.args.fields[k]) - else: - _convert_value_to_gvalue(args, request.args.fields["tui_args"]) - return self._impl.execute_command(request) + """Execute a command at a path with positional or keyword arguments. + + Parameters + ---------- + path : str + Path to the menu. + *args + Positional arguments of the command. + **kwargs + Keyword arguments of the command. + + Returns + ------- + Any + Command result (any Python datatype) + """ + return self.service.execute_command(path, *args, **kwargs) def execute_query(self, path: str, *args, **kwargs) -> Any: - """Execute the query.""" - request = DataModelProtoModule.ExecuteQueryRequest() - request.path = path - if kwargs: - for k, v in kwargs.items(): - _convert_value_to_gvalue(v, request.args.fields[k]) - else: - _convert_value_to_gvalue(args, request.args.fields["tui_args"]) - return self._impl.execute_query(request) + """Execute a query at a path with positional or keyword arguments. + + Parameters + ---------- + path : str + Path to the menu. + *args + Positional arguments of the query. + **kwargs + Keyword arguments of the query. + + Returns + ------- + Any + Query result (any Python datatype) + """ + return self.service.execute_query(path, *args, **kwargs) def get_static_info(self, path: str): - """Get static info.""" - request = DataModelProtoModule.GetStaticInfoRequest() - request.path = path - response = self._impl.get_static_info(request) - # Note: MessageToDict's parameter names are different in different protobuf versions - return MessageToDict(response.info, True) + """Get static info at a path. + + Parameters + ---------- + path : str + Path to the menu. + + Returns + ------- + dict[str, Any] + Static info at the menu level. + """ + return self.service.get_static_info(path) + + def get_child_names( + self, path: str, include_unavailable: bool = False + ) -> list[str]: + """Get the names of child menus. + + Parameters + ---------- + path : str + Path to the menu. + + include_unavailable : bool, optional + Whether to query over static TUI metadata. The default is ``False``. + + Returns + ------- + List[str] + Names of child menus. + """ + return self.service.get_child_names(path, include_unavailable) + + def get_doc_string(self, path: str, include_unavailable: bool = False) -> str: + """Get docstring for a menu. + + Parameters + ---------- + path : str + Path to the menu. + + include_unavailable : bool, optional + Whether to query over static TUI metadata. The default is ``False``. + + Returns + ------- + str + """ + return self.service.get_doc_string(path, include_unavailable) class PyMenu: @@ -219,9 +180,7 @@ class PyMenu: Get docstring for a menu. """ - def __init__( - self, service: DatamodelService, version, mode, path: Path | str - ) -> None: + def __init__(self, service, version, mode, path: Path | str) -> None: """__init__ method of PyMenu class.""" self._service = service self._version = version @@ -241,12 +200,7 @@ def get_child_names(self, include_unavailable: bool = False) -> list[str]: List[str] Names of child menus. """ - attribute = DataModelProtoModule.Attribute.Name( - DataModelProtoModule.Attribute.CHILD_NAMES - ).lower() - return self._service.get_attribute_value( - self._path, attribute, include_unavailable - ) + return self._service.get_child_names(self._path, include_unavailable) def execute(self, *args, **kwargs) -> Any: """Execute a command or query at a path with positional or keyword arguments. @@ -265,6 +219,7 @@ def execute(self, *args, **kwargs) -> Any: """ with ApiUpgradeAdvisor( self._service._app_utilities, + self._service._scheme_eval, self._version, self._mode, ): @@ -285,12 +240,7 @@ def get_doc_string(self, include_unavailable: bool = False) -> str: ------- str """ - attribute = DataModelProtoModule.Attribute.Name( - DataModelProtoModule.Attribute.HELP_STRING - ).lower() - return self._service.get_attribute_value( - self._path, attribute, include_unavailable - ) + return self._service.get_doc_string(self._path, include_unavailable) def get_static_info(self) -> dict[str, Any]: """Get static info at menu level. diff --git a/src/ansys/fluent/core/services/transcript.py b/src/ansys/fluent/core/services/transcript.py index c6c9450360b..0d04a083018 100644 --- a/src/ansys/fluent/core/services/transcript.py +++ b/src/ansys/fluent/core/services/transcript.py @@ -21,27 +21,22 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Wrapper over the transcript gRPC service of Fluent.""" +"""High level transcript wrapper.""" -import grpc -from ansys.api.fluent.v0 import transcript_pb2_grpc as TranscriptGrpcModule -from ansys.fluent.core.services._protocols import ServiceProtocol -from ansys.fluent.core.services.streaming import StreamingService +class Transcript: + """Transcript backed by the Transcript gRPC service.""" + def __init__(self, service): + """Initialize Transcript.""" + self.service = service -class TranscriptService( - StreamingService, ServiceProtocol -): # pyright: ignore[reportUnsafeMultipleInheritance] - """Class wrapping the transcript gRPC service of Fluent.""" - - def __init__(self, channel: grpc.Channel, metadata: list[tuple[str, str]]) -> None: - """__init__ method of TranscriptService class.""" - super().__init__( - stub=self._create_stub(channel), - metadata=metadata, + def begin_streaming(self, request, started_evt, id, stream_begin_method): + """Begin streaming from Fluent.""" + return self.service.begin_streaming( + request, started_evt, id=id, stream_begin_method=stream_begin_method ) - def _create_stub(self, channel: grpc.Channel): - """Create the gRPC stub. Override in subclasses to use a different proto version.""" - return TranscriptGrpcModule.TranscriptStub(channel) + def end_streaming(self, id, stream_begin_method) -> None: + """End streaming from Fluent.""" + self.service.end_streaming(id, stream_begin_method) diff --git a/src/ansys/fluent/core/session.py b/src/ansys/fluent/core/session.py index fe92fae350a..152a4586eed 100644 --- a/src/ansys/fluent/core/session.py +++ b/src/ansys/fluent/core/session.py @@ -43,7 +43,6 @@ PyFluentDeprecationWarning, PyFluentUserWarning, ) -from ansys.fluent.core.services import service_creator from ansys.fluent.core.services.scheme_interpreter import SchemeInterpreter from ansys.fluent.core.streaming_services.datamodel_event_streaming import ( DatamodelEvents as DatamodelEventsV0, @@ -184,9 +183,7 @@ def _build_from_fluent_connection( self.rp_vars = RPVars(self.scheme.string_eval) self._preferences = None - self._transcript_service = service_creator( - "transcript", supports_v1=fluent_connection._server_supports_v1 - ).create(fluent_connection._channel, fluent_connection._metadata) + self._transcript_service = fluent_connection._service_factory.transcript self.transcript = ( Transcript if fluent_connection._server_supports_v1 else TranscriptV0 )(self._transcript_service) @@ -197,15 +194,7 @@ def _build_from_fluent_connection( self.journal = Journal(self.application_runtime) - self._datamodel_service_tui = service_creator( - "tui", supports_v1=fluent_connection._server_supports_v1 - ).create( - fluent_connection._channel, - fluent_connection._metadata, - self._error_state, - self.application_runtime, - self.scheme, - ) + self._datamodel_service_tui = fluent_connection._service_factory.text_interface self._datamodel_service_se = fluent_connection._service_factory.object_model self._datamodel_service_se.file_transfer_service = file_transfer_service @@ -217,14 +206,10 @@ def _build_from_fluent_connection( ) self._datamodel_events.start() - self._batch_ops_service = service_creator( - "batch_ops", supports_v1=fluent_connection._server_supports_v1 - ).create(fluent_connection._channel, fluent_connection._metadata) + self._batch_ops_service = fluent_connection._service_factory.batch_ops if event_type: - events_service = service_creator( - "events", supports_v1=fluent_connection._server_supports_v1 - ).create(fluent_connection._channel, fluent_connection._metadata) + events_service = fluent_connection._service_factory.events if fluent_connection._server_supports_v1: self.events = EventsManager[event_type]( event_type, diff --git a/src/ansys/fluent/core/session_shared.py b/src/ansys/fluent/core/session_shared.py index 8a647759044..a188d44c8e8 100644 --- a/src/ansys/fluent/core/session_shared.py +++ b/src/ansys/fluent/core/session_shared.py @@ -27,7 +27,7 @@ from ansys.fluent.core.module_config import config from ansys.fluent.core.pyfluent_warnings import warning_for_fluent_dev_version -from ansys.fluent.core.services.datamodel_tui import TUIMenu +from ansys.fluent.core.services.text_interface import TUIMenu from ansys.fluent.core.utils import load_module _CODEGEN_MSG_TUI = ( diff --git a/src/ansys/fluent/core/session_solver.py b/src/ansys/fluent/core/session_solver.py index 5c34b969c1f..680d4443a4e 100644 --- a/src/ansys/fluent/core/session_solver.py +++ b/src/ansys/fluent/core/session_solver.py @@ -36,23 +36,7 @@ from ansys.fluent.core.fields.live_field_data import ZoneInfo, ZoneType from ansys.fluent.core.module_config import config from ansys.fluent.core.pyfluent_warnings import PyFluentDeprecationWarning -from ansys.fluent.core.services import MonitorsServiceV0, service_creator -from ansys.fluent.core.services.monitor_v1 import MonitorsService from ansys.fluent.core.services.scheme_interpreter import SchemeInterpreter -from ansys.fluent.core.services.solution_variables import ( - SolutionVariableData as SolutionVariableDataV0, -) -from ansys.fluent.core.services.solution_variables import ( - SolutionVariableInfo as SolutionVariableInfoV0, -) -from ansys.fluent.core.services.solution_variables import ( - SolutionVariableService as SolutionVariableServiceV0, -) -from ansys.fluent.core.services.solution_variables_v1 import ( - SolutionVariableData, - SolutionVariableInfo, - SolutionVariableService, -) from ansys.fluent.core.session import BaseSession from ansys.fluent.core.session_shared import ( _make_datamodel_module, @@ -171,29 +155,16 @@ def _build_from_fluent_connection( self._fluent_version = None self._bg_session_threads = [] self._launcher_args = launcher_args - self._solution_variable_service = service_creator( - "svar", supports_v1=fluent_connection._server_supports_v1 - ).create(fluent_connection._channel, fluent_connection._metadata) self.fields.reduction = fluent_connection._service_factory.reduction self.fields.reduction.set_context(self) - if fluent_connection._server_supports_v1: - self.fields.solution_variable_info = SolutionVariableInfo( - self._solution_variable_service - ) - else: - self.fields.solution_variable_info = SolutionVariableInfoV0( - self._solution_variable_service - ) - - self.fields.solution_variable_data = self._solution_variable_data( - fluent_connection._server_supports_v1 + self.fields.solution_variable_info = ( + fluent_connection._service_factory.solution_variable_info ) - - monitors_service = service_creator( - "monitors", supports_v1=fluent_connection._server_supports_v1 - ).create( - fluent_connection._channel, fluent_connection._metadata, self._error_state + self.fields.solution_variable_data = ( + fluent_connection._service_factory.solution_variable_data ) + + monitors_service = fluent_connection._service_factory.monitor #: Manage Fluent's solution monitors. _MonitorsManager = ( MonitorsManager @@ -222,14 +193,6 @@ def _build_from_fluent_connection( weakref.WeakMethod(self._stop_bg_sessions), at_start=True ) - def _solution_variable_data( - self, supports_v1: bool - ) -> SolutionVariableDataV0 | SolutionVariableData: - """Return the SolutionVariableData handle.""" - return service_creator("svar_data", supports_v1=supports_v1).create( - self._solution_variable_service, self.fields.solution_variable_info - ) - @property def settings(self) -> "settings_root.root": """Settings root handle.""" @@ -244,24 +207,6 @@ def settings(self) -> "settings_root.root": ) return cast("settings_root.root", self._settings) - @property - def svar_data(self): - """``SolutionVariableData`` handle.""" - warnings.warn( - "svar_data is deprecated. Use fields.solution_variable_data instead.", - PyFluentDeprecationWarning, - ) - return self.fields.solution_variable_data - - @property - def svar_info(self): - """``SolutionVariableInfo`` handle.""" - warnings.warn( - "svar_info is deprecated. Use fields.solution_variable_info instead.", - PyFluentDeprecationWarning, - ) - return self.fields.solution_variable_info - def _get_zones_info(self) -> list[ZoneInfo]: zones_info = [] # v0 ThreadType: CELL_THREAD=0, FACE_THREAD=1 diff --git a/src/ansys/fluent/core/streaming_services/events_streaming.py b/src/ansys/fluent/core/streaming_services/events_streaming.py index b39a9df976f..1a4314f7b5a 100644 --- a/src/ansys/fluent/core/streaming_services/events_streaming.py +++ b/src/ansys/fluent/core/streaming_services/events_streaming.py @@ -411,6 +411,7 @@ def __init__( # can also be done in other streaming services self._session = session self._sync_event_ids = {} + self._service = session_events_service def _construct_event_info( self, response: EventsProtoModule.BeginStreamingResponse, event: TEvent @@ -599,7 +600,7 @@ def unregister_callback(self, callback_id: str): del callbacks_map[callback_id] sync_event_id = self._sync_event_ids.pop(callback_id, None) if sync_event_id: - self._session.application_runtime.unregister_pause_on_solution_events( + self._service.unregister_pause_on_solution_events( registration_id=sync_event_id ) @@ -617,10 +618,8 @@ def _register_solution_event_sync_callback( callback_id: str, callback: Callable, ) -> tuple[TEvent, Callable]: - unique_id: int = ( - self._session.application_runtime.register_pause_on_solution_events( - solution_event=event_type - ) + unique_id: int = self._service.register_pause_on_solution_events( + solution_event=event_type ) def on_pause(session, event_info: SolutionPausedEventInfo): @@ -641,9 +640,7 @@ def on_pause(session, event_info: SolutionPausedEventInfo): exc_info=True, ) finally: - session.application_runtime.resume_on_solution_event( - registration_id=unique_id - ) + self._service.resume_on_solution_event(registration_id=unique_id) self._sync_event_ids[callback_id] = unique_id return self._event_type.SOLUTION_PAUSED, on_pause diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 79c2cffe04d..c833f79195f 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -108,7 +108,7 @@ def _get_expected_tui_api_output(mode): # # pylint: disable=line-too-long -from ansys.fluent.core.services.datamodel_tui import PyMenu, TUIMenu, TUIMethod +from ansys.fluent.core.services.text_interface import PyMenu, TUIMenu, TUIMethod diff --git a/tests/test_session.py b/tests/test_session.py index 39e7034dccd..ccf6c88ba21 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -698,8 +698,6 @@ def test_get_set_state_on_solver(new_solver_session): def test_solver_structure(new_solver_session): solver = new_solver_session - with pytest.warns(PyFluentDeprecationWarning): - solver.svar_data assert { "field_data", @@ -1088,7 +1086,7 @@ def test_dir_for_session(new_meshing_session_wo_exit): "reduction", ]: # Deprecated methods are accessible but hidden in dir() - if attr in ["field_data", "field_info"]: + if attr in ["field_data", "field_info", "svar_data", "svar_info"]: assert getattr(solver, attr, None) is None else: assert getattr(solver, attr) diff --git a/tests/test_settings_api.py b/tests/test_settings_api.py index 903d78d3354..d0162e34e4d 100644 --- a/tests/test_settings_api.py +++ b/tests/test_settings_api.py @@ -64,7 +64,6 @@ def test_setup_models_viscous_model_settings(new_solver_session) -> None: assert viscous_model.model() == "inviscid" -# Failing for 24.1 but passes for 24.2 and 25.1 def test_wildcard(new_solver_session): solver = new_solver_session case_path = download_file("elbow_source_terms.cas.h5", "pyfluent/mixing_elbow") diff --git a/tests/test_solution_variables.py b/tests/test_solution_variables.py index cd0f701ac78..a9fe1cee626 100644 --- a/tests/test_solution_variables.py +++ b/tests/test_solution_variables.py @@ -38,8 +38,6 @@ def test_solution_variables(new_solver_session): solution_variable_info = solver.fields.solution_variable_info solution_variable_data = solver.fields.solution_variable_data - assert solution_variable_info == solver.svar_info - assert solution_variable_data == solver.svar_data solver.file.read_case_data(file_name=import_file_name) @@ -150,8 +148,6 @@ def test_solution_variables_single_precision(new_solver_session_sp): solution_variable_info = solver.fields.solution_variable_info solution_variable_data = solver.fields.solution_variable_data - assert solution_variable_info == solver.svar_info - assert solution_variable_data == solver.svar_data solver.file.read_case_data(file_name=import_file_name) diff --git a/tests/test_tui_api.py b/tests/test_tui_api.py index 5ea194df276..c306ecf8555 100644 --- a/tests/test_tui_api.py +++ b/tests/test_tui_api.py @@ -28,7 +28,7 @@ from ansys.fluent.core import FluentVersion from ansys.fluent.core.examples.downloads import download_file -from ansys.fluent.core.services.datamodel_tui import TUIMenu +from ansys.fluent.core.services.text_interface import TUIMenu @pytest.mark.skip(reason=SKIP_UNKNOWN)