Info | ||
---|---|---|
| ||
iWrap User Manual can be accessed in two ways:
|
Warning | ||
---|---|---|
| ||
iWrap is currently being actively developed, so it is not publicly available yet. The first 'production' release is planned at the begin of 2022. |
Table of Contents |
---|
Introduction
Motivations
Complex simulations often combines a number of physics codes, potentially provided by various actors and written in different programming languages. To make them working together, an additional layer, that 'orchestrates' execution of particular codes, and takes care on passing data between 'producers' and 'consumers' is needed. Sometimes the functionality of such layer is provided by dedicated software (aka 'workflow orchestrators', like Kepler https://kepler-project.org/), sometimes it can be handled by much simpler mechanism like Python scripts. Unfortunately all components ('actors') that constitute a computing scenario ('workflow') must be implemented in the same programming language, defining the same API.
Unfortunately, in most cases, scientific, simulation codes that performs computing intensive calculations (due to performance reasons) are written in C++ or Fortran, while 'workflow orchestrators' are implemented in (more popular nowadays) languages, like Java, Python, etc. Hence the need for a 'wrapper' that intermediates between native code language and language of the orchestrator. Such wrappers can be developed manually, however users may benefit from a tool that automatise this process - iWrap
iWrap - actor generator
iWrap is a modular component generator, implemented in Python, used for creating IMAS actors from physics models. This mechanism allows to integrate physics codes written in one language (Fortran, CPP) within complex computing scenarios designed in other language (e.g. Python).
It's plug-in based modular design with clear separation of concerns allows to generate various types of actors and easily change data access paradigm (from dataset descriptor for AL to direct HDC data for instance)
For user conveniency it provides two kinds of interfaces: user friendly graphical interface that allows non-experienced users to define an actor in intuitive way and command line interface foreseen for more advanced users that may want to e.g. automatise actor generation process using scripts.
- iWrap generates a Fortran/CPP wrapper, which intermediates between Kepler actor and user code in terms of:
- reading/writing of in/out physical data (IDS)
- passing other arguments to/from the actor
- iWrap creates a Python script (aka an actor) that:
- calls a user code
- provides error handling
- calls debugger (if run in "debug" mode)
Info |
---|
Glossary Scenario (aka workflow)
Actor
Native code
|
Preparation of code
A signature of user code must follow strict rules to allow interaction between it and wrapping actor. Please use following >>link<< to get detailed guidelines for integration of native code into workflows using iWrap
Code and actor description
iWrap, to properly wrap the code, needs detailed informations about both: the wrapped code and an actor to be generated. A formal description of the code provides information about the programming language used, arguments passed to/from the code, type of these arguments, etc, etc, while an actor description tells iWrap how to name generated actor, where to put it, etc. Such descriptions has to be provided in YAML format file, prepared manually, or automatically with help of iWrap GUI.
Info | ||
---|---|---|
| ||
iWrap GUI allows to generate an actor without the need for manual preparation of actor/code description. |
YAML file syntax
The YAML file consists of two independent parts (aka 'YAML documents'), marked by tags corresponding to their roles: actor_description
and code_description
. Only code description part is mandatory, and actor description could be provided in a file, or using iWrap commandline switches or interacting with GUI.
The structure of the file is following:
Code Block | ||||
---|---|---|---|---|
| ||||
# actor description part - optional
--- !actor_description
<see chapter below for details>
...
# code description part - mandatory
--- !code_description
<see chapter below for details>
...
|
Native code description
Description of the native code has to be provided as a YAML document. It consist of two parts. The first one contains generic information common for all languages, The latter one contains information specific for a given language of the native code (currently defined only for Fortran and CPP).
Warning |
---|
|
Common part
Generic information common for all (or at least majority of) programming languages.
programming_language
- meaning: language of physics code
- value: one of predefined values: 'Fortran', 'CPP'
- example: 'Fortran'
code_name
- meaning:
- name of user method / subroutine to be called,
- must be exactly the same as name of called method / subroutine
- it is used also as an actor name and the name of directory where actor is installed
- value: string
- example: 'my_subroutine'
- meaning:
data_type
- meaning: data type handled by the physics code
- value: 'legacy' (currently only 'Legacy IDS' type has been implemented)
- example: 'legacy'
arguments
- list of arguments- argument definition:
- name:
- meaning: user defined argument name
- value: string
- example: equilibrium00
- type:
- meaning: a type of an IDS argument
- value:
- predefined name of one of the IDSes
- example: 'equilibrium'
- intent
- meaning: determines if given argument is input or output one
- value: predefined - string "IN", "OUT"
- name:
- argument definition:
code_path:
- meaning: path to system library (C, CPP) , script (Python), etc containing the physics code, including method/subroutine to be run
- value: string, valid path to file
- example: 'any text'
code_parameters
- a structure containingparameters
and schemaentry
:parameters
:- meaning: path to XML file containing user defined parameters of the physics code
- value: string, valid path to file
- example: './code_parameters/parameters.xml'
schema
:- meaning: path to XSD file contains schema of XML parameters, to be able to validate them
- value: string, valid path to file
- example: './code_parameters/parameters.xsd'
documentation
:- meaning: human readable description of native code
- value: string
- example: 'any text'
Language specific part - Fortran/C++
...
- meaning: the name/vendor of the compiler (and not compiler command!) used to compile native codes
- value: string, one of vendors of compilers, currently: 'Intel' or 'GCC'
- example: 'Intel'
...
- meaning: MPI compiler flavour to be used
- values: string, one of: MPICH, MPICH2, MVAPICH2, OpenMPI, etc.
- example 'MPICH2'
...
- meaning: if user code should be compiled with OpenMP flag
- values: boolean
- example 'true'
...
example:
|
...
example:
...
- ./lib/custom/libcustom1.a
- ./lib/custom/libcustom2.a
Example - Fortran code description
Code Block | ||||
---|---|---|---|---|
| ||||
--- !code_description
programming_language: Fortran
code_name: demo_code
data_type: legacy
arguments:
- name: equilibrium00
type: equilibrium
intent: IN
- name: equilibrium11
type: equilibrium
intent: OUT
code_path: ./lib/libmy_lib.a
code_parameters:
parameters: ./code_paramneters/parameters.xml
schema: ./code_paramneters/parameters.xsd
documentation: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam... '
language_specific:
compiler: Intel
mpi_flavour: MPICH2
open_mp: false
system_libraries:
- fftw3f
- glib
- mkl
custom_libraries:
- ./lib/custom/libcustom1.a
- ./lib/custom/libcustom2.a |
Actor description
Warning |
---|
|
Actor description syntax
actor_name
:- meaning: the arbitrary, user defined name of the actor. It determines: the name of class to be generated and directory where actor will be put
- value: string
- example: 'core2dist'
actor_type
:- meaning:
- values: 'python' (currently only python type has been implemented)
- example
data_type
:- meaning: data type handled at the workflow level
- value: 'legacy' (currently only 'Legacy IDS' type has been implemented)
- example: 'legacy'
install_dir
:- optional parameter
- meaning: user chosen folder, where an actor will be installed
- values: string,
- example : /my/working/dir/IWRAP_ACTORS
- if not defined, a default installation directory will be used
Example
Code Block | ||||
---|---|---|---|---|
| ||||
--- !actor_description
actor_name: core2dist
actor_type: python
data_type: legacy
install_dir: /my/working/dir/IWRAP_ACTORS
... |
Actor generation
iWrap commandline
iWrap graphical interface
Usage of actor within workflow
Actor import and creation
To make an actor class visible inside a workflow script it has to be imported:
Code Block | ||
---|---|---|
| ||
from <actor_package>.actor import <actor_class> |
In a current version both: <actor_package> and <actor_class> are set to the same value provided by user as an 'actor name'.
To import an actor named e.g. 'physics_ii' a correct import will look like:
Code Block | ||
---|---|---|
| ||
from physics_ii.actor import physics_ii |
An actor instance can be created using already imported actor class in 'usual pythonic' way:
Code Block | ||
---|---|---|
| ||
actor_object = <actor name>()
e.g.
actor_object = physics_ii() |
Actor runtime settings
Among the actor properties one is especially important: runtime_settings.
This property tells the wrapper how native code should be run and defines:
...
Import of enumerated values: from <actor name>.python_common.job_settings import RunMode
...
Example:
Code Block | ||
---|---|---|
| ||
from physics_ii.python_common.job_settings import RunMode
self.physics_ii.runtime_settings.run_mode = RunMode.STANDALONE |
...
Example:
Code Block | ||
---|---|---|
| ||
from physics_ii.python_common.job_settings import DebugMode
self.physics_ii.runtime_settings.run_mode = DebugMode.STANDALONE |
...
- Currently only number of nodes to run a code in parallel are defined
- Defined by setting:
<actor name>.runtime_settings.mpi.number_of_processes = value
- Please note:
- MPI code is run always in standalone mode
- If a native code is not marked as 'MPI' during actor generation, this setting is ignored
...
- This attribute defines settings of temporary storage being used while passing IDSes between an actor and native code.
- Defined by setting:
<actor name>.runtime_settings.ids_storage.<storage_parameter> = value
- Storage parameters that can be set:
db_name:
- Meaning: name of data base to be used
- Default value: 'tmp'
shot:
- Meaning - shot number
- Default value - 9999
run
:- Meaning - run number
- Default value - 9999
backend:
- Meaning - backend to be used
- Default value -
imas.imasdef.MEMORY_BACKEND
persistent_backend
- Meaning - backend to be used when temporary data cannot be stored in memory (e.g. while running actor in a standalone mode, when a native code is run as separate process, so it doesn't share memory with other actors.
- Default value - imas.imasdef.MDSPLUS_BACKEND
- Please note: for most of the purposes it is fine to not set this property and leave default values unchanged.
...
- Sandbox settings
- Batch job settings
- OpenMP settings
Actor life cycle
During its 'life' an actor goes through several states, that can be passed only in a strict order:
Creation of the object
Code Block | ||
---|---|---|
| ||
actor_object = <actor name>()
e.g.
actor_object = physics_ii() |
...
Tuning up the actor before its initialization and native code execution
- See chapter above
...
Calling initialize()
method of the actor to perform internal initialisation actions
Code Block |
---|
actor_object.initialize() |
...
Code Block |
---|
<output IDS or list of IDSes> = actor_object(<input IDS/IDSes>)
e.g.
output_distribution_sources = actor_object(input_core_profiles) |
...
Calling finalize()
method of the actor to perform internal finalisation actions
Code Block |
---|
actor_object.finalize() |
The simplest workflow
A skeleton of the very simple workflow could be implemented like this:
Code Block | ||
---|---|---|
| ||
# Import of the actor class
from <actor name>.actor import <actor name>
# Creation of actor object
actor_object = <actor name>()
# Setting up runtime properties (if necessary)
# Actor initialisation
actor_object.initialize()
# Native code run
<output IDS or list of IDSes> = actor_object(<input IDS/IDSes>)
# Actor finalisation
actor_object.finalize() |
Workflow example
Code Block | ||
---|---|---|
| ||
import sys
import imas, os
from core2dist.actor import core2dist
from core2dist.python_common.job_settings import RunMode, DebugMode
class ExampleWorkflowManager:
def __init__(self):
self.actor_cp2ds = core2dist()
self.input_entry = None
self.output_entry = None
def init_workflow(self):
# INPUT/OUTPUT CONFIGURATION
shot = 134174
run_in = 37
input_user_or_path = 'public'
input_database = 'iter'
run_out = 10
output_user_or_path = os.getenv('USER')
output_database = input_database
# OPEN INPUT DATAFILE TO GET DATA FROM IMAS SCENARIO DATABASE
print('=> Open input datafile')
self.input_entry = imas.DBEntry(imas.imasdef.MDSPLUS_BACKEND,input_database,shot,run_in,input_user_or_path)
self.input_entry.open()
# CREATE OUTPUT DATAFILE
print('=> Create output datafile')
self.output_entry = imas.DBEntry(imas.imasdef.MDSPLUS_BACKEND,output_database,shot,run_out,output_user_or_path)
self.output_entry.create()
# # # # # # # # Initialization of ALL actors # # # # # # # #
#
actor_run_mode = os.getenv( 'ACTOR_RUN_MODE', 'NORMAL')
if actor_run_mode == 'STANDALONE':
print('Running STANDALONE version.')
self.actor_cp2ds.runtime_settings.run_mode = RunMode.STANDALONE
#self.actor_cp2ds.runtime_settings.debug_mode = DebugMode.ATTACH
self.actor_cp2ds.initialize()
def execute_workflow(self):
# READ INPUT IDSS FROM LOCAL DATABASE
print('=> Read input IDSs')
input_core_profiles = self.input_entry.get('core_profiles')
# EXECUTE PHYSICS CODE
print('=> Execute physics code')
output_distribution_sources = self.actor_cp2ds(input_core_profiles)
# SAVE IDSS INTO OUTPUT FILE
print('=> Export output IDSs to local database')
self.output_entry.put(output_distribution_sources)
print('Done exporting.')
def end_workflow(self):
# Finalise ALL actors
self.actor_cp2ds.finalize()
#other finalisation actions
self.input_entry.close()
self.output_entry.close()
manager = ExampleWorkflowManager()
manager.init_workflow()
manager.execute_workflow()
manager.end_workflow(
|