From d81eac396a84337d02db1c80fc2d8c8867da49ed Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Sun, 11 Feb 2024 22:12:25 +0100 Subject: [PATCH 1/2] Update fluent nozzle example --- archive_examples.py | 1 - doc/source/examples/ex_fluent_nozzle.rst | 9 +- examples/fluent_nozzle/exec_fluent.py | 383 ----------------------- examples/fluent_nozzle/project_setup.py | 49 +-- 4 files changed, 19 insertions(+), 423 deletions(-) delete mode 100644 examples/fluent_nozzle/exec_fluent.py diff --git a/archive_examples.py b/archive_examples.py index ddbc32904..a49ed4bab 100644 --- a/archive_examples.py +++ b/archive_examples.py @@ -64,7 +64,6 @@ def archive_examples(): ], "fluent_nozzle": [ "project_setup.py", - "exec_fluent.py", "solve.jou", "nozzle.cas", ], diff --git a/doc/source/examples/ex_fluent_nozzle.rst b/doc/source/examples/ex_fluent_nozzle.rst index 7b7f360f5..640800fb7 100644 --- a/doc/source/examples/ex_fluent_nozzle.rst +++ b/doc/source/examples/ex_fluent_nozzle.rst @@ -3,7 +3,7 @@ Fluent nozzle ============= -This example shows how to submit a Fluent nozzle model for solving on HPS. +This example shows how to submit a Fluent nozzle model for solving on Ansys HPC Platform Services. .. only:: builder_html @@ -18,9 +18,4 @@ Here is the ``project_setup.py`` script for this example: :lines: 23- :caption: project_setup.py -The example uses an ``exec_fluent.py`` execution script instead of a solver command line. - -.. literalinclude:: ../../../examples/fluent_nozzle/exec_fluent.py - :language: python - :lines: 23- - :caption: exec_fluent.py +The example uses an execution script stored server side. \ No newline at end of file diff --git a/examples/fluent_nozzle/exec_fluent.py b/examples/fluent_nozzle/exec_fluent.py deleted file mode 100644 index b1fc864ed..000000000 --- a/examples/fluent_nozzle/exec_fluent.py +++ /dev/null @@ -1,383 +0,0 @@ -# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. -# 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. - -""" -Copyright (C) 2021 ANSYS, Inc. and its subsidiaries. All Rights Reserved. -""" -import _thread -import json -import logging -import os -import platform -import subprocess -import time -import traceback - -from ansys.rep.common.logging import log -from ansys.rep.evaluator.task_manager import ApplicationExecution -from ansys.rep.evaluator.task_manager.context import SubmitContext -import psutil - - -class FluentExecution(ApplicationExecution): - isLinux = platform.platform().startswith("Linux") - - def __init__(self, context): - self.CleanupScript = None - self.FluentTranscript = None - self.error_detected = False - self.fluent_children = [] - ApplicationExecution.__init__(self, context) - - def execute(self): - try: - log.info("Start FLUENT execution script") - - pythoncode_version = "0.1" - log.info("python code version " + pythoncode_version) - - log.info("Evaluator Platform: " + platform.platform()) - - num_cores = self.context.resource_requirements["num_cores"] - log.info(f"Requested cores: {num_cores}") - - # self.environmentInfo.defaultMpi - defaultMpi = "intel" - - # create defaults for inputs not provided - inputs = { - "fluent_dimension": self.context.execution_context.get("fluent_dimension", "2d") - } - inputs["fluent_precision"] = self.context.execution_context.get( - "fluent_precision", "dp" - ) - inputs["fluent_meshing"] = self.context.execution_context.get("fluent_meshing", False) - inputs["fluent_numGPGPUsPerMachine"] = self.context.execution_context.get( - "fluent_numGPGPUsPerMachine", 0 - ) - inputs["fluent_defaultFluentVersion"] = self.context.execution_context.get( - "fluent_defaultFluentVersion", None - ) - inputs["fluent_MPIType"] = self.context.execution_context.get( - "fluent_MPIType", defaultMpi - ) - inputs["fluent_otherEnvironment"] = self.context.execution_context.get( - "fluent_otherEnvironment", "{}" - ) - inputs["fluent_UDFBat"] = self.context.execution_context.get("fluent_UDFBat", None) - inputs["fluent_useGUI"] = self.context.execution_context.get("fluent_useGUI", False) - inputs["fluent_additionalArgs"] = self.context.execution_context.get( - "fluent_additionalArgs", "" - ) - - log.info("Task inputs ") - for name in inputs.keys(): - if inputs[name] == None: - continue - log.info("\t-" + name + ":<" + str(inputs[name]) + ">") - - log.info("Checking if required inputs are provided...") - - valid_launcher_dimensions = ["2d", "3d"] - if not inputs["fluent_dimension"] in valid_launcher_dimensions: - raise Exception( - "Required Input is invalid! fluent_dimension(" - + inputs["fluent_dimension"] - + ")\nValid values are " - + format(valid_launcher_dimensions) - ) - - valid_launcher_precisions = ["sp", "dp"] - if not inputs["fluent_precision"] in valid_launcher_precisions: - raise Exception( - "Required Input is invalid! fluent_precision(" - + inputs["fluent_precision"] - + ")\nValid values are " - + format(valid_launcher_precisions) - ) - - # Identify application - app_name = "Ansys Fluent" - app = next((a for a in self.context.software if a["name"] == app_name), None) - assert app, f"{app_name} is required for execution" - - log.info("Using " + app["name"] + " " + app["version"]) - log.info("Current directory: " + os.getcwd()) - - files = [f for f in os.listdir(".") if os.path.isfile(f)] - for f in files: - log.info(" " + f) - - jouFile = next((f for f in self.context.input_files if f["name"] == "jou"), None) - log.info("journal file path: " + jouFile["path"]) - - if jouFile == None or not os.path.isfile(jouFile["path"]): - raise Exception("File " + jouFile["path"] + " does not exist!") - - # Add " around exe if needed for Windows - exe = app["executable"] # should already be platform specific - log.info("Fluent executable: " + exe) - - if inputs["fluent_UDFBat"] == None: - if self.isLinux: - pass # no need in Linux, None is OK - else: - inputs["fluent_UDFBat"] = os.path.join(os.path.dirname(exe), "udf.bat") - log.info("Setting fluent_UDFBat to " + inputs["fluent_UDFBat"]) - - otherEnvironment = json.loads(inputs["fluent_otherEnvironment"]) - noGuiOptions = None - if not inputs["fluent_useGUI"]: - if self.isLinux: - noGuiOptions = " -gu -driver null" - else: - noGuiOptions = " -hidden -driver null" - - log.debug(f"exe: {exe}") - args = inputs["fluent_dimension"] - args += inputs["fluent_precision"] if inputs["fluent_precision"] == "dp" else "" - args += " -meshing" if inputs["fluent_meshing"] else "" - args += " -t" + format(num_cores) - if inputs["fluent_MPIType"] != None and inputs["fluent_MPIType"] != "": - args += " -mpi=" + format(inputs["fluent_MPIType"]) - if inputs["fluent_numGPGPUsPerMachine"] > 0: - args += " -gpgp=" + format(inputs["fluent_numGPGPUsPerMachine"]) - args += " -i " + jouFile["path"] - # args+= cnf - if not noGuiOptions == None: - args += noGuiOptions - args += " " + inputs["fluent_additionalArgs"] + " " - - cmd = [os.path.basename(exe)] - cmd.extend(args.split(" ")) - - rc = None - firstchild = None - - fluent_env = os.environ.copy() - - for oenv in otherEnvironment: - if "FLUENT_GUI" == oenv["Name"]: - continue - # if "FLUENT_AAS"==oenv['Name']:continue - fluent_env[oenv["Name"]] = oenv["Value"] - log.info("Fluent environment:") - for k in fluent_env: - try: - log.info("\t- " + k + "\n\t\t " + fluent_env[k]) - except: - log.info("\t- error while printing " + k) - - log.info(" ".join(cmd)) - - max_wait_time = 120 - tried_time = 0 - self.error_detected = False - with subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=fluent_env, executable=exe - ) as self.proc: - log.info("Fluent started\npid:" + format(self.proc.pid)) - log.info("TODO: start new thread to monitor process children") - t3 = _thread.start_new_thread(self.monitor_children, (self.proc,)) - log.info("Fluent started a new thread to monitor its children") - t4 = _thread.start_new_thread(self.monitor_transcript, (self.proc,)) - log.info("Fluent started a new thread to monitor its transcript") - - t1 = _thread.start_new_thread(self.process_output, (self.proc,)) - log.info("Fluent started a new thread for stdout log") - t2 = _thread.start_new_thread(self.process_error, (self.proc,)) - log.info("Fluent started a new thread for stderr log") - while True: - if self.error_detected: - log.info("Error: Solver exited with error") - log.info("TODO: implement child process kill") - for child in self.fluent_children: - pToKill = psutil.Process(child) - pToKill.kill() - raise Exception("Solver exited with errors.") - if rc is None: - rc = self.proc.poll() - elif firstchild is None: - time.sleep(3) - tried_time = tried_time + 3 - if len(self.fluent_children) == 0: - if tried_time < max_wait_time: - log.info("\t- no fluent children process found, continue") - continue - else: - log.info( - "\t- can not start fluent in " - + format(max_wait_time) - + "seconds, quit the process" - ) - break - firstchild = self.fluent_children[0] - log.info("rc:" + format(rc) + " ,firstchild:" + format(firstchild)) - elif not psutil.pid_exists(firstchild): - log.info("\t- fluent exits normally") - break - - log.info("Finished Fluent solve") - if rc != 0: - log.info(f"Error: Solver exited with errors ({rc}).") - raise Exception("Solver exited with errors.") - - except Exception as e: - log.info("====== error in execute =========") - log.debug(traceback.print_exc()) - log.info(str(e)) - log.info("====== error in execute =========") - raise e - - # monitor the children of the main process - def monitor_children(self, proc): - starting_process = psutil.Process(proc.pid) - try: - while True: - for child in starting_process.children(): - if not child.pid in self.fluent_children: - self.fluent_children.append(child.pid) - time.sleep(0.001) - except Exception as e: - if not "psutil.NoSuchProcess" in format(e): - errormessage = traceback.format_exc() - log.info(errormessage) - log.info("<" + format(e) + ">") - - # monitor creation and content of transcript files and record content to corresponding logs - def monitor_transcript(self, proc): - try: - while True: - log.info("Looking for fluent automatically generated transcript file...") - if not self.FluentTranscript == None: - break - time.sleep(1) - for fn in os.listdir("."): - if not fn.endswith(".trn"): - continue - if fn.endswith(format(self.proc.pid) + ".trn"): - self.FluentTranscript = fn - for childpid in self.fluent_children: - if fn.endswith(format(childpid) + ".trn"): - log.info( - "Warning: a fluent child process generated transcript <" - + format(fn) - + "> is found!" - ) - self.FluentTranscript = fn - if not self.FluentTranscript == None: - break - log.info("Fluent transcript detected: <" + format(self.FluentTranscript) + ">") - - current_line = 0 - while True: - time.sleep(1) - with open(self.FluentTranscript) as f: - for _ in range(current_line): - next(f) - for line in f: - log.info(line.rstrip()) - current_line = current_line + 1 - msg = line.rstrip() - if msg.startswith("ANSYS LICENSE STDOUT ERROR"): - self.error_detected = True - log.info("License error detected in fluent") - if msg.startswith("Unexpected license problem"): - self.error_detected = True - log.info("Unexpected license error detected in fluent") - if msg.startswith( - "Warning: An error or interrupt occurred while reading the journal file" - ): - self.error_detected = True - log.info("An error detected in fluent, killing fluent...") - if msg.startswith("Error:"): - self.error_detected = True - log.info("An error detected in fluent, killing fluent...") - if msg.startswith("Cleanup script file is"): - self.CleanupScript = msg.replace("Cleanup script file is ", "") - log.debug("Execute kills script is : " + self.CleanupScript) - if msg.startswith('Opening input/output transcript to file "'): - self.FluentTranscript = msg.replace( - 'Opening input/output transcript to file "', "" - ).replace('".', "") - log.debug("Fluent transcript is : " + self.FluentTranscript) - except Exception as e: - errormessage = traceback.format_exc() - log.info(errormessage) - log.info("<" + format(e) + ">") - - # monitor the stdout of the main process and log information to corresponding logs - def process_output(self, proc): - for line in iter(proc.stdout.readline, b""): - msg = line.decode("utf-8").rstrip() - log.info(msg) - if msg.startswith("ANSYS LICENSE MANAGER ERROR"): - self.error_detected = True - if msg.startswith("Cleanup script file is"): - self.CleanupScript = msg.replace("Cleanup script file is ", "") - log.debug("Execute kills script is : " + self.CleanupScript) - if msg.startswith('Opening input/output transcript to file "'): - self.FluentTranscript = msg.replace( - 'Opening input/output transcript to file "', "" - ).replace('".', "") - log.debug("Fluent transcript is : " + self.FluentTranscript) - # log.info(msg) - if self.error_detected: - log.debug(msg) - proc.stdout.close() - - # monitor the stderr of the main process and log information to corresponding logs - def process_error(self, proc): - for line in iter(proc.stderr.readline, b""): - msg = line.decode("utf-8").rstrip() - log.error(msg) - if msg.startswith("Fatal error in MPI_Init: Internal MPI error!"): - if self.CleanupScript == None: - self.proc.kill() - else: - p = subprocess.Popen( - self.CleanupScript, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - stdout, stderr = p.communicate() - proc.stderr.close() - - -# EXAMPLE: this function will only be called if this script is run at the command line. -if __name__ == "__main__": - log = logging.getLogger() - logging.basicConfig(format="%(message)s", level=logging.DEBUG) - - try: - log.info("Loading sample Fluent context...") - - with open("fluent_context.json", "r") as f: - context = json.load(f) - print(context) - - submit_context = SubmitContext(**context) - - log.info("Executing...") - ex = FluentExecution(submit_context).execute() - log.info("Execution ended.") - - except Exception as e: - log.error(str(e)) diff --git a/examples/fluent_nozzle/project_setup.py b/examples/fluent_nozzle/project_setup.py index 2ffa121dc..c51b5fb07 100644 --- a/examples/fluent_nozzle/project_setup.py +++ b/examples/fluent_nozzle/project_setup.py @@ -33,7 +33,6 @@ JmsApi, Job, JobDefinition, - Licensing, Project, ProjectApi, ResourceRequirements, @@ -61,9 +60,9 @@ def create_project(client, name, num_jobs=20, version=__ansys_apps_version__): files = [] files.append( File( - name="inp", + name="case", evaluation_path="nozzle.cas", - type="text/plain", + type="application/octet-stream", src=os.path.join(cwd, "nozzle.cas"), ) ) @@ -76,15 +75,6 @@ def create_project(client, name, num_jobs=20, version=__ansys_apps_version__): ) ) - files.append( - File( - name="exec_fluent", - evaluation_path="exec_fluent.py", - type="application/x-python-code", - src=os.path.join(cwd, "exec_fluent.py"), - ) - ) - files.append( File( name="trn", evaluation_path="fluent*.trn", type="text/plain", collect=True, monitor=True @@ -138,30 +128,31 @@ def create_project(client, name, num_jobs=20, version=__ansys_apps_version__): log.debug("=== JobDefinition with simulation workflow and parameters") job_def = JobDefinition(name="JobDefinition.1", active=True) + exec_script_file = project_api.copy_default_execution_script( + f"fluent-v{version[2:4]}{version[6]}-exec_fluent.py" + ) + # Task definition - num_input_files = 3 + num_input_files = 2 task_def = TaskDefinition( - name="Fluent_run", + name="Fluent Run", software_requirements=[ Software(name="Ansys Fluent", version=version), ], execution_command=None, # Only execution currently supported + use_execution_script=True, + execution_script_id=exec_script_file.id, resource_requirements=ResourceRequirements( - num_cores=1.0, - memory=250, - disk_space=5, + num_cores=4, + distributed=True, ), execution_level=0, execution_context={ - "fluent_dimension": "3d", - "fluent_precision": "dp", - "fluent_meshing": False, - "fluent_numGPGPUsPerMachine": 0, - "fluent_MPIType": "intel", - "fluent_otherEnvironment": "{}", - "fluent_useGUI": False, + "dimension": "3d", + "double_precision": True, + "mode": "solution", }, - max_execution_time=50.0, + max_execution_time=120.0, num_trials=1, input_file_ids=[f.id for f in files[:num_input_files]], output_file_ids=[f.id for f in files[num_input_files:]], @@ -174,13 +165,8 @@ def create_project(client, name, num_jobs=20, version=__ansys_apps_version__): ], require_all_output_files=False, ), - licensing=Licensing(enable_shared_licensing=False), # Shared licensing disabled by default ) - task_def.use_execution_script = True - task_def.execution_command = None - task_def.execution_script_id = file_ids["exec_fluent"] - task_defs = [task_def] task_defs = project_api.create_task_definitions(task_defs) @@ -203,9 +189,8 @@ def create_project(client, name, num_jobs=20, version=__ansys_apps_version__): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-n", "--name", type=str, default="fluent_nozzle") + parser.add_argument("-n", "--name", type=str, default="Fluent nozzle") parser.add_argument("-j", "--num-jobs", type=int, default=1) - parser.add_argument("-es", "--use-exec-script", default=True, action="store_true") parser.add_argument("-U", "--url", default="https://127.0.0.1:8443/hps") parser.add_argument("-u", "--username", default="repuser") parser.add_argument("-p", "--password", default="repuser") From a9665c630dbae2566638942c8f89ae0f672940a8 Mon Sep 17 00:00:00 2001 From: Federico Negri Date: Sun, 11 Feb 2024 22:13:35 +0100 Subject: [PATCH 2/2] update examples test --- examples/fluent_2d_heat_exchanger/project_setup.py | 2 +- tests/test_examples.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/fluent_2d_heat_exchanger/project_setup.py b/examples/fluent_2d_heat_exchanger/project_setup.py index 31ed38e72..00b58ee7a 100644 --- a/examples/fluent_2d_heat_exchanger/project_setup.py +++ b/examples/fluent_2d_heat_exchanger/project_setup.py @@ -148,7 +148,7 @@ def create_project( if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-n", "--name", type=str, default="Fluent 2D Heat Exchanger") + parser.add_argument("-n", "--name", type=str, default="Fluent 2D heat exchanger") parser.add_argument("-es", "--use-exec-script", default=False, type=bool) parser.add_argument("-U", "--url", default="https://localhost:8443/hps") parser.add_argument("-u", "--username", default="repuser") diff --git a/tests/test_examples.py b/tests/test_examples.py index 4010475bb..dd30e9a79 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -154,7 +154,7 @@ def test_mapdl_linked_analyses(client): for incremental_version in [True, False]: project = create_project( client, - name="Test Linked Analyses", + name=f"Test linked analyses (incremental={incremental_version})", incremental=incremental_version, use_exec_script=False, version=ansys_version, @@ -174,14 +174,14 @@ def test_fluent_2d_heat_exchanger(client): from examples.fluent_2d_heat_exchanger.project_setup import create_project - project = create_project(client, name="Fluent Test", version=ansys_version) + project = create_project(client, name="Fluent test (command)", version=ansys_version) assert project is not None jms_api = JmsApi(client) project_api = ProjectApi(client, project.id) assert len(project_api.get_jobs()) == 1 - assert jms_api.get_project(id=project.id).name == "Fluent Test" + assert jms_api.get_project(id=project.id).name == "Fluent test (command)" jms_api.delete_project(project) @@ -191,7 +191,7 @@ def test_fluent_2d_heat_exchanger_with_exec_script(client): from examples.fluent_2d_heat_exchanger.project_setup import create_project project = create_project( - client, name="Fluent Test", version=ansys_version, use_exec_script=True + client, name="Fluent test (exec script)", version=ansys_version, use_exec_script=True ) assert project is not None @@ -199,7 +199,7 @@ def test_fluent_2d_heat_exchanger_with_exec_script(client): project_api = ProjectApi(client, project.id) assert len(project_api.get_jobs()) == 1 - assert jms_api.get_project(id=project.id).name == "Fluent Test" + assert jms_api.get_project(id=project.id).name == "Fluent test (exec script)" jms_api.delete_project(project) @@ -208,14 +208,14 @@ def test_fluent_nozzle(client): from examples.fluent_nozzle.project_setup import create_project - project = create_project(client, name="Fluent Nozzle Test", num_jobs=1, version=ansys_version) + project = create_project(client, name="Fluent nozzle test", num_jobs=1, version=ansys_version) assert project is not None jms_api = JmsApi(client) project_api = ProjectApi(client, project.id) assert len(project_api.get_jobs()) == 1 - assert jms_api.get_project(id=project.id).name == "Fluent Nozzle Test" + assert jms_api.get_project(id=project.id).name == "Fluent nozzle test" jms_api.delete_project(project)