Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/api/general/services/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Services
:template: flobject-module-template.rst
:recursive:

batch_ops
error_handler
events
field_data
Expand Down
1 change: 1 addition & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
numpydoc_validation_exclude = {
"ansys.fluent.core.solver.settings_231.",
"ansys.fluent.core.solver.settings_232.",
"ansys.fluent.core.services.batch_ops.BatchOps.__init__",
}

# Favicon
Expand Down
54 changes: 35 additions & 19 deletions src/ansys/fluent/core/services/batch_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import inspect
from typing import List, Tuple
import weakref

import grpc

Expand All @@ -26,23 +27,36 @@ def execute(self, request):


class BatchOps:
"""Class to perform batch operations.
"""Class to execute operations in batch in Fluent.

Examples
--------
>>> with pyfluent.BatchOps(solver):
>>> solver.tui.file.read_case("elbow.cas.h5")
>>> solver.solution.initialization.hybrid_initialize()
>>> solver.tui.file.read_case("mixing_elbow.cas.h5")
>>> solver.results.graphics.mesh["mesh-1"] = {}

Above will be executed in server through a single grpc call during exiting the with
block. Only the non-getter rpc methods are supported.
Above code will execute both operations through a single gRPC call upon exiting the
``with`` block.

The getter rpc methods within the with block are executed right away. Make
sure that they do not depend on execution of non-getter methods.
Operations that perform queries in Fluent are executed immediately, while others are
queued for batch execution. Some queries are executed behind the scenes while
queueing an operation for batch execution, and we must ensure that they do not
depend on previously queued operations.


For example,

>>> with pyfluent.BatchOps(solver):
>>> solver.tui.file.read_case("mixing_elbow.cas.h5")
>>> solver.results.graphics.mesh["mesh-1"] = {}
>>> solver.results.graphics.mesh["mesh-1"].surfaces_list = ["wall-elbow"]

will throw a ``KeyError`` as ``solver.results.graphics.mesh["mesh-1"]`` attempts to
access the ``mesh-1`` mesh object which has not been created yet.
"""

_proto_files = None
_instance = None
_instance = lambda: None

@classmethod
def instance(cls) -> "BatchOps":
Expand All @@ -53,7 +67,7 @@ def instance(cls) -> "BatchOps":
BatchOps
BatchOps instance
"""
return cls._instance
return cls._instance()

class Op:
"""Class to create a single batch operation."""
Expand Down Expand Up @@ -118,12 +132,13 @@ def update_result(self, status, data):
self._result = obj

def __new__(cls, session):
if cls._instance is None:
cls._instance = super(BatchOps, cls).__new__(cls)
cls._instance._service = session._batch_ops_service
cls._instance._ops: List[BatchOps.Op] = []
cls._instance.batching = False
return cls._instance
if cls.instance() is None:
instance = super(BatchOps, cls).__new__(cls)
instance._service = session._batch_ops_service
instance._ops: List[BatchOps.Op] = []
instance.batching = False
cls._instance = weakref.ref(instance)
return cls.instance()

def __enter__(self):
"""Entering the with block."""
Expand All @@ -135,10 +150,11 @@ def __exit__(self, exc_type, exc_value, exc_tb):
"""Exiting from the with block."""
LOG.debug("Executing batch operations")
self.batching = False
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)
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)

def add_op(self, package: str, service: str, method: str, request):
"""Queue a single batch operation. Only the non-getter operations will
Expand Down
42 changes: 16 additions & 26 deletions src/ansys/fluent/core/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,14 @@ def ordered_children(self) -> list:
"""Get the ordered task list held by this task. Sorting is in terms
of the workflow order and only includes this task's top-level tasks, while other tasks
can be obtained by calling ordered_children() on a parent task. Given the
workflow:

o Workflow
|
|--o A
|
|--o B
| |
| |--o C
| |
| |--o D
|
|--o E
workflow::

Workflow
├── A
├── B
│ ├── C
│ └── D
└── E

C and D are the ordered children of task B.

Expand Down Expand Up @@ -286,19 +281,14 @@ def ordered_children(self) -> list:
"""Get the ordered task list held by the workflow. Sorting is in terms
of the workflow order and only includes the top-level tasks, while other tasks
can be obtained by calling ordered_children() on a parent task. Given the
workflow:

o Workflow
|
|--o A
|
|--o B
| |
| |--o C
| |
| |--o D
|
|--o E
workflow::

Workflow
├── A
├── B
│ ├── C
│ └── D
└── E

the ordered children of the workflow are A, B, E, while B has ordered children
C and D.
Expand Down
44 changes: 32 additions & 12 deletions tests/test_batch_ops.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import sys

import pytest
from util.solver_workflow import new_solver_session # noqa: F401

from ansys.api.fluent.v0 import batch_ops_pb2
import ansys.fluent.core as pyfluent
from ansys.fluent.core import examples


@pytest.mark.dev
@pytest.mark.fluent_232
@pytest.mark.skip("Failing in github")
def test_batch_ops(new_solver_session):
import_filename = examples.download_file(
@pytest.mark.skipif(
sys.platform.startswith("linux"), reason="Linux specific issue in server"
)
def test_batch_ops_create_mesh(new_solver_session):
solver = new_solver_session
case_filename = examples.download_file(
"mixing_elbow.cas.h5", "pyfluent/mixing_elbow"
)
with pyfluent.BatchOps(new_solver_session):
assert len(pyfluent.BatchOps.instance()._ops) == 0
new_solver_session.tui.file.read_case(import_filename)
new_solver_session.setup.models.energy.enabled = False
assert len(pyfluent.BatchOps.instance()._ops) == 2
assert all(
op._status == batch_ops_pb2.STATUS_SUCCESSFUL
for op in pyfluent.BatchOps.instance()._ops
with pyfluent.BatchOps(solver):
solver.file.read_case(file_name=case_filename)
solver.results.graphics.mesh["mesh-1"] = {}
assert not solver.scheme_eval.scheme_eval("(case-valid?)")
assert "mesh-1" not in solver.results.graphics.mesh.get_object_names()
assert solver.scheme_eval.scheme_eval("(case-valid?)")
assert "mesh-1" in solver.results.graphics.mesh.get_object_names()


@pytest.mark.dev
@pytest.mark.fluent_232
@pytest.mark.skipif(
sys.platform.startswith("linux"), reason="Linux specific issue in server"
)
def test_batch_ops_create_mesh_and_access_fails(new_solver_session):
solver = new_solver_session
case_filename = examples.download_file(
"mixing_elbow.cas.h5", "pyfluent/mixing_elbow"
)
with pytest.raises(KeyError):
with pyfluent.BatchOps(solver):
solver.file.read_case(file_name=case_filename)
solver.results.graphics.mesh["mesh-1"] = {}
solver.results.graphics.mesh["mesh-1"].surfaces_list = ["wall-elbow"]
assert not solver.scheme_eval.scheme_eval("(case-valid?)")