47
Interface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts to interfere with such a complex program as FEFLOW represents. Therefore, the IFM provides support in all phases of building and maintaining own modules. An assistant guides the initial creation process, the project management rescues from editing Makefiles and others. A callback editor generates the source code for each event you attempt to handle. Further tools and editors complete the module development environment. This Section describes the module maintenance process along a very simple example. Accordingly, you can follow each step on your computer. Feel free to apply minor changes to see their influence on the results. 1.1 Prerequisites Before you can start to write own modules you have to install the Interface Manager Development Kit from the FEFLOW distribution CDROM. See the CDROM booklet for hints on installation. FEFLOW must be restarted after installation! Furthermore, a C or C++ language development option has to be installed on your computer. Even if you intend to write your code in an another language (e.g. FORTRAN or Pascal) - what you can do in form of Mixed Language Programming - the entry points of a module, the callbacks, have to be written in C or C++. The IFM also accepts, among commercial compilers, the GNU public domain compiler gcc. On Windows systems, mainly Microsoft Visual C++ 6 is used. The procedure is quite different compared to the one described below for UNIX systems. See the file ifm_example_vc6.pdf in the /doc folder of the FEFLOW CD for a step-by-step description. For programming of own dialogs you will need graphical user interface libraries. Since FEFLOW uses OSF/Motif™ [2] for its own graphical interface it is recommended to use the same tool kit. But, all other X11- and Intrinsic (Xt)-based tool kits such as Athena or OpenLook [6] will work as well. On Windows systems, MFC can be used to create module dialogs. The IFM project maintenance is based on the use of the program imake and its corresponding Imakefile 1 , which is a platform-independent version (in real a preliminary stage) of the standard Makefile. The development option on all IFM-supported UNIX platforms already contains, by default, the necessary files patched with the machine/operating system configuration settings. For MS Windows and UNIX configurations without the imake facility a generic version is incorporated in the IFM Development Kit. Before applying the kit at first time you will be prompted for the correct configuration parameters. 1.2 Creating a New Module Commonly, a module will be built in form of a project. Each project occupies an extra directory with a unique Imakefile containing the rules to (re-)build the module or parts of the module. Based on this Imakefile and a number of rules (usually located in /usr/lib/X11/config) the shell script xmkmf generates a platform-dependent Makefile. Besides the Imakefile one or more source files, a HTML help file, and a copyright file are normally located in the project directory. The IFM allows creating, editing, building and administrating projects in a visual manner. To create a new module (project) select a certain interface and simply press the New module button. An assistant will guide you through the creation process. Let's create a simple module for the interface Simulation now: First select an interface, e.g., Simulation, for which the module should be created. 1 The Imakefile was introduced with the X11 project on the Massachusetts Institute of Technology (MIT) to define platform-independent rules for building projects which are based on the make(1) utility.

Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

  • Upload
    others

  • View
    46

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Interface Manager for Programmers

1. Programming Tool Kit Understandably, there is a natural caution before someone attempts to interfere with such a complex program as FEFLOW represents. Therefore, the IFM provides support in all phases of building and maintaining own modules. An assistant guides the initial creation process, the project management rescues from editing Makefiles and others. A callback editor generates the source code for each event you attempt to handle. Further tools and editors complete the module development environment.

This Section describes the module maintenance process along a very simple example. Accordingly, you can follow each step on your computer. Feel free to apply minor changes to see their influence on the results.

1.1 Prerequisites Before you can start to write own modules you have to install the Interface Manager Development Kit from the FEFLOW distribution CDROM. See the CDROM booklet for hints on installation. FEFLOW must be restarted after installation!

Furthermore, a C or C++ language development option has to be installed on your computer. Even if you intend to write your code in an another language (e.g. FORTRAN or Pascal) - what you can do in form of Mixed Language Programming - the entry points of a module, the callbacks, have to be written in C or C++. The IFM also accepts, among commercial compilers, the GNU public domain compiler gcc. On Windows systems, mainly Microsoft Visual C++ 6 is used. The procedure is quite different compared to the one described below for UNIX systems. See the file ifm_example_vc6.pdf in the /doc folder of the FEFLOW CD for a step-by-step description.

For programming of own dialogs you will need graphical user interface libraries. Since FEFLOW uses OSF/Motif™ [2] for its own graphical interface it is recommended to use the same tool kit. But, all other X11- and Intrinsic (Xt)-based tool kits such as Athena or OpenLook [6] will work as well. On Windows systems, MFC can be used to create module dialogs.

The IFM project maintenance is based on the use of the program imake and its corresponding Imakefile1, which is a platform-independent version (in real a preliminary stage) of the standard Makefile. The development option on all IFM-supported UNIX platforms already contains, by default, the necessary files patched with the machine/operating system configuration settings. For MS Windows and UNIX configurations without the imake facility a generic version is incorporated in the IFM Development Kit. Before applying the kit at first time you will be prompted for the correct configuration parameters.

1.2 Creating a New Module Commonly, a module will be built in form of a project. Each project occupies an extra directory with a unique Imakefile containing the rules to (re-)build the module or parts of the module. Based on this Imakefile and a number of rules (usually located in /usr/lib/X11/config) the shell script xmkmf generates a platform-dependent Makefile. Besides the Imakefile one or more source files, a HTML help file, and a copyright file are normally located in the project directory.

The IFM allows creating, editing, building and administrating projects in a visual manner. To create a new module (project) select a certain interface and simply press the New module button. An assistant will guide you through the creation process.

Let's create a simple module for the interface Simulation now:

• First select an interface, e.g., Simulation, for which the module should be created.

1 The Imakefile was introduced with the X11 project on the Massachusetts Institute of Technology (MIT) to define platform-independent rules for building projects which are based on the make(1) utility.

Page 2: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

• Open the assistant dialog by pressing the New Module button. A window appears as shown in Fig.

Fig 1: Creating a new module (step 1)

• Enter the name for the project, e.g., “sample1“, and check that the base directory is set correctly. The module will be created as <base directory>/<project name>/<project name>.so. If you want to change the base directory, simply enter a new path. You can also browse for it by pressing the file selection button . When all settings are okay, press the Next button. In turn, the dialog behaves as shown in Fig. 2.

Fig. 2: Creating a new module (step 2)

Most text fields are already filled with a default value or a value derived from the project name. You can accept the settings or edit them according to your needs. At least, you have to enter some words as a Short module description.

Module name: This name is used to identify the module. It must be unique among all other registered modules with the same major revision. It is possible to have more than one module with the same registered name as long as the modules differ in the major revision number (see the notes on revision handling below). The name of a module should be handled as a fixed parameter because FEM files using this module refer to this name only. Thus, if no module is registered under the stored name (perhaps by renaming) no module can be attached and the file can be loaded only partially. The syntax of the name must follow the naming conventions of the C language [A-Za-z0-9_], however, it is case-insensitive.

Page 3: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

The Display name is used for the visualization of the module in FEFLOW's graphical user interface. This name may be changed as often as you wish without influencing the identifying and attaching process of the module. Any characters (lower and upper case) and white blanks are suitable, but it is restricted to one line.

The Rev. (Revision) number is very important to guarantee the compatibility of module data at the time of saving a FEM file with the module data at loading time. Internally, the revision number is represented by a hexadecimal number multiplied with 0x1000. For instance: 1.0 is represented by 0x1000, 2.1a (which is also valid) is represented by 0x21a0 and so on. Literal revision numbers like "beta" or "1.1g" are invalid. The result of the integer division revision/0x1000 forms the Major revision, the fraction revision%0x1000 represents the Minor revision. Changing the major revision number it forces a new file name for the module DSO, e.g. tune.so.2 for major revision 2 instead of tune.so.1 for major revision 1. Thus, both modules can be registered in the IFM at the same time.

While loading a FEM file into FEFLOW it tries to attach the "best" module according to the following rules where load revision specifies the revision number found in the FEM file and module revision refers to the revision number of the module to be attached:

• The interface requested in the FEM file must exist.

• The module name found in the FEM file must match exactly the internal name of a module in the given interface.

• The module revision must be equal to or greater than the load revision.

• If multiple modules with the same name are registered the module with the closest revision number becomes selected.

The name of the Registration procedure is the only symbol which has to be exported by the module DSO. Usually, inside this function the module name, module revision, the visual name, a copyright note, a reference to a basic HTML help page and all implemented callback functions have to be registered. Vary this name only if it conflicts with a module-internal symbol which you cannot change. Otherwise you have to inform each user of your module about this different entry point, because if he tries to register the module with the default settings he will get an error message from the runtime linker rld due to the unknown symbol RegisterModule.

Press the Next button if you are sure that all fields have been filled properly. Fig. 3 shows the appearance of the next step.

Fig. 3: Creating a new module (step 3)

• In this step you can define a copyright note which will be displayed by pressing the Copyright button. The shown file name is the name of the copyright file which will be created in the final step. You can also enable the Inline toggle button. In this case, the copyright text will be realized as a string in the generated source file. If you want to use the contents of an already existing file use

Page 4: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

the Import button. If you want to share the contents you have entered with other modules use the Export button. The Edit button raises a simple copyright editor (Fig. 4).

Fig. 4: Copyright editor

The IFM generates by default a typical shareware copyright note from a template located in $FEFLOW_HOME/config/descript.ifm. You can edit the template in the General settings dialog.

• After accepting the copyright note by pressing the Next button, you are prompted for the confirmation of all project creation settings (Fig. 5):

Fig. 5: Creating a new module (step 4)

Accept the collected information by pressing the Finish button or go back via the Prev button to change any settings made before.

Page 5: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Once you have pressed the Finish button the complete project including the corresponding directory and all above-mentioned files are created. If no errors encounter you will get a message box according Fig. 6:

If you decide to build the project the body of the module will be immediately compiled and linked so that you will get a valid module with the specified features. You can not expect real functionality, of course, since no callback function was implemented yet. You are referred to the next Section to see how such callbacks can be added.

If you close the message box by pressing the No button, you are becoming able to add callbacks before the first compilation run.

1.3 Adding Callback Functions Callback functions have to provide exactly the same argument lists as FEFLOW is used while calling. Therefore, the IFM provides a tool for generating the entry points of all desired callbacks. This Section describes how to add and remove such callbacks and how to build the module after source code modifications.

Let's continue the building process of the sample module. As the next step we want to implement a specific functionality by adding a callback function. For this purpose select a function, e.g. PreTimeStep, from the list of callbacks (Fig. 7). Next, press the Add button located directly below the list. The sign left from the callback name changes from minus (-) to plus (+) to indicate that the callback is implemented. Press the Edit button to open an editor which is positioned exactly at the beginning of the function body. For the first, we will print simply a message into FEFLOW's Log window.

Remove the TODO comment and insert the cursive marked line:

void PreTimeStep (IfmDocument pDoc) { IfmInfo (pDoc, "PreTimeStep(): t = %lf\n" IfmGetAbsoluteSimulationTime (pDoc)); } You will find more information about the used API functions IfmGetAbsoluteSimulationTime() and IfmInfo() as well as about all other API functions in Appendix B of the IFM description in the online help system.

Fig. 6: Confirmation for module compiling

Fig. 7: Adding a callback

Page 6: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Fig. 8 shows the IFM dialog with the newly created module sample1 and the opened editor.

Fig. 8: Adding a module callback function

Save the file and close the editor through the corresponding menu entries. Now you can build the module by pressing the Build button. The IFM pops up a new window for logging the Make process. Fig. 9 displays the command window.

Fig. 9: Command window

• Once you have built the module successfully you can attach it to a FEM problem:

• Load a preferably non-steady FEM problem, e.g. from demo data set qmass_t2d_u.fem.

• Open the Select modules dialog by the problem editor’s IFM Modules... entry (Fig. 10).

• Select Sample1 and activate it by pressing the button.

• Close the dialog.

Page 7: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Fig. 10: Module selection dialog

Now you can start the simulator. An example of the output in FEFLOW's LOG window is shown in Fig. 11.

Fig. 11: Simulator run with attached IFM module

Page 8: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

1.4 Changing Module Properties In the process of module development it becomes necessary to change some module properties such as revision, parameters, list of project files, and others. For this purpose the IFM provides the module Properties dialog. The dialog is non-modal, so you can open it for multiple modules at the same time and copy some settings, for instance, the copyright note, parts of the module description and others.

1.4.1 General Settings Fig. 12 displays the layout of the General settings page.

Fig. 12: Module properties - General settings

See the Section 1.2 Creating a New Module (Step 2) for the description of all entries.

1.4.2 Project Files The Files page (Fig. 13) manages the project settings. The list of files contains all project sources and targets. Based on this list the IFM generates the main part of the Imakefile and some statements in the module's registration procedure. There are a number of predefined files on the top of the list followed by a sorted list of user-added items.

Fig. 13: Module properties - Files

Page 9: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Predefined Files

Table 1: Predefined files

File Description Imakefile A platform-independent collection of rules to build the module target from its

sources. The shell script xmkmf (X Make Makefile) invokes the C preprocessor driver imake which creates a platform-dependent Makefile based on the rules defined in Imakefile and some other files, especially Imake.tmpl located in /usr/lib/X11/config (or the like). The Imakefile includes furthermore an IFM-specific template, Ifm.tmpl, located by default in the directory $FEFLOW_HOME/src/ifm/cf. While executing xmkmf within FEFLOW's graphical environment the IFM extends the imake search path to include files by this directory. If you want to execute xmkmf from the command line you must append -I$FEFLOW_HOME/src/ifm/cf to the environment variable IMAKEINCLUDE. Browse also for Imakefile to adjust all file paths after moving the module project to an other location.

Makefile This file defines the rules how the target (here the module) can be built from its sources. Because this file is generated automatically from the Imakefile, any changes made here are lost after the next xmkmf run (invoked automatically after changing the Imakefile). Sometimes it might be useful to edit this file for debugging purposes. The name of the Makefile cannot be changed!

Target (Module) Represents the binary code of the module. You can change the name, but you can't neither edit nor remove it.

Copyright Specifies the copyright file. If the name of this files remains left blank, the copyright text from the copyright page (Fig. ) will be referenced by a local variable in the source code. Otherwise, the specified file will be created or modified with the contents of the copyright page.

HTML page Defines the primary HTML help page used for the detailed description of the module with its options and parameters. The contents of this file are displayed by the selected HTML browser from the More info ... button in the IFM dialog.

Primary source It contains the module registration procedure which will be maintained by the IFM. You should avoid changes between the marker lines /* --- IFMREG_BEGIN --- */ and /* --- IFMREG_END --- */ where the IFM will place its automatically generated source code

Other Files The IFM project management is capable of adding other arbitrary sources to the module project. Mostly, these can be further source files, object files and libraries. In principle, it is possible to add any kind of files, e.g., text files, interface definition files, and others. To add targets from a special source, e.g., XDR files (External Data Representation), you can add the definition source file (.x) first, and afterwards the derived sources. At least you have to insert manually the rule for getting the target from the source in the Imakefile.

Project Targets

Fig. 14: Build operations

Fig. 14 documents the time stamp of the last building.

Page 10: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

On the other hand, options are incorporated to rebuild the module (all Commands are entries in the user's personal profile):

• Build module: Only modified sources are recompiled. This button executes the MakeCommand which defaults to make.

• Rebuild all: The complete project has to be rebuilt. All compilation products are removed and recreated. This button performs the RebuildCommand which defaults to make clean; make.

• Dependencies: All source files are scanned for #include directives. A list of dependencies will be appended to the Makefile. This button executes the DependCommand which defaults to make depend.

1.4.3Editing the Copyright

Fig. 15: Module properties - Copyright editor

There are two modes for editing:

• Editing the template: The default copyright note is built by parsing a template. The IFM description file $FEFLOW_HOME/config/descript.ifm contains the original template. In this page you have the opportunity to redefine your private template which will be stored in your personal profile "InterfaceManager/ConfigDlg/CopyrightTemplate". The syntax of templates is summarized in Table 2.

• Editing the copyright text directly

Among common characters the template can contain special fields, which will be replaced at run-time.

Table 2: Supported tags in the copyright template

Tag Replacement <pw[:part] > Insert a part of the passwd structure. See manual page for getpwuid(3) for

more information. With part is not specified the user's full name will be inserted. <date[:format]> Insert the current date. If format is specified it is used to format the date à la

date(1) and strftime(3). Without format the long format of the year (%Y), e.g. 1997, is used.

<env:var> Insert the value of an environment variable. If the variable does not exist, $var will be inserted.

Page 11: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

2 Supported Interfaces

2.1 Generic Callbacks Among the callbacks concerning the true declared interface each interface contains a number of accompanying callbacks for I/O, editing and notification. Some administration callbacks are therefore common to all supported interfaces. In general, they are called at every time when a global state has been changed. Events, such as loading and unloading of modules, attaching and detaching modules from FEM problems, error and exception handling, and file I/O belong to this group.

void ExceptionHandler(IfmHandle pHdl, IfmExceptionContext* pCtx); Called from all-level API functions on invalid arguments, calling in bad contexts, etc. Safely, you can call only module-level API functions. Otherwise, check the passed handle with functions like IfmIsDocument(IfmHandle). See the description on IfmExceptionContext in Section 0 3.2 Exception Handling.

2.2 Simulation The simulation interface allows the extension of FEFLOW's internal simulation loop with additional methods which are provided by external modules. In principal, the module is called always before and after each basic simulation step. All callback names of the Simulation interface are self-documenting, for instance, the callback PreTimeStep() will be called just before each time step, PostHeatSimulation() just after solving the heat transport equations and so on. Depending on the context (problem class) some callbacks are omitted from calling, e.g. for flow-only problems neither Pre-/PostMassSimulation() nor Pre-/PostHeatSimulation are invoked.

For coupled non-linear problems some callbacks may be called more than once per time step - until the convergence criterion is satisfied.

Fig. 16 reveals the points of calling external modules within FEFLOW's simulator.

Fig. 16: Simulation interface callbacks

FEFLOW Simulator

Flow Simulation

PreSimulation()

PreTimeStep()

PreFlowSimulation()

PostFlowSimulation()

PreMassSimulation()

PostMassSimulation()

Mass Simulation

Heat Simulation

PreHeatSimulation()

PostHeatSimulation()

PostTimeStep()

PostSimulation()

t = t +1

External Module

t = 0

Page 12: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

The above flow will be performed by the FEFLOW kernel at each time when user activates the button .

void PreSimulation (IfmDocument pDoc); PreSimulation() is called before running the simulation time loop and allows the implementation of own preprocessing functionality or allocation of additional memory needed for the module.

void PostSimulation (IfmDocument pDoc); PostSimulation() is called after running the simulation time loop and allows the implementation of own postprocessing functionality or freeing of memory which has been additionally allocated during the module run.

void PreTimeStep (IfmDocument pDoc); PreTimeStep() is called at beginning of each time step and allows the implementation of own functionality for updating time-related properties and user-specific actions.

void PostTimeStep (IfmDocument pDoc); PostTimeStep() is called at the end of a current time step and allows the implementation of own functionality for updating time-related properties and user-specific actions.

void PreFlowSimulation (IfmDocument pDoc); PreFlowSimulation() is called immediately before solving the flow equations and allows the implementation of own functionality for properties and actions related to the flow problem before it becomes solved.

void PostFlowSimulation (IfmDocument pDoc); PostFlowSimulation() is called immediately after solving the flow equations and allows the implementation of own functionality for properties and actions related to the flow problem after it has been solved.

void PreMassSimulation (IfmDocument pDoc); PreMassSimulation() is called immediately before solving the mass transport equations and allows the implementation of own functionality for properties and actions related to the mass transport problem before it becomes solved.

void PostMassSimulation (IfmDocument pDoc); PostMassSimulation() is called immediately after solving the mass transport equations and allows the implementation of own functionality for properties and actions related to the mass transport problem after it has been solved.

void PreHeatSimulation (IfmDocument pDoc); PreHeatSimulation() is called immediately before solving the heat transport equations and allows the implementation of own functionality for properties and actions related to the heat transport problem before it becomes solved.

void PostHeatSimulation (IfmDocument pDoc); PostHeatSimulation() is called immediately after solving the heat transport equations and allows the implementation of own functionality for properties and actions related to the heat transport problem after it has been solved.

IfmResult OnInitModule(IfmModule pMod); Called immediately after the module was loaded. Return with False (0) if the Module API Version provided by FEFLOW is older than Module API Version burned in at compile time. The code of this callback will be automatically generated while creating a module project. If you want to insert additional code call only module-level API functions. Calling other functions causes a „Bad context“ exception.

void OnExitModule(IfmModule pMod); Called immediately before the module will be unloaded. No other callback will be called after OnExitModule(). Use this callback for global cleanup like freeing memory, removing temporary files, etc.

Page 13: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

IfmResult OnBeginDocument(IfmDocument pDoc); Called immediately after loading the module and attaching it to the current active FEM problem. OnBeginDocument() is the absolutely first callback where document-level API functions can be applied. Like in OnInitModule() you should return False if the Document API Version provided by FEFLOW is older than Document API Version burned in at module compile time. Usually, this callback is used to allocate memory for the module's FEM problem extension. In contrast to OnInitModule() OnBeginDocument() it can be called multiply - for each FEM problem (document) once. However, the current version of FEFLOW (4.6x) supports only one document at a time.

void OnEndDocument(IfmDocument pDoc); OnEndDocument() is the counterpart to OnBeginDocument(). It is called as the last callback during the lifetime of a document. Usually, it should be implemented for document cleanup - freeing memory, etc.

void Serialize(IfmDocument pDoc, IfmArchive pArc); Serialize() is the only method for I/O provided by the IFM. Called from four points (INIT, LOAD, STORE, FREE) it ensures the same module state over multiple sessions. The same API functions are used for both ASCII and binary file I/O. See Section 0 3.3 Serialization for a more detailed description.

IfmResult OnChangeProblemClass(IfmDocument pDoc); Called every time when the FEFLOW user has changed the FEM problem class. It allows the module programmer to reject further application of the module when the problem class doesn't meet the requirements of the module.

IfmResult OnChangeTopology(IfmDocument pDoc); Called at every time when the topology has been changed, normally after a manual or adaptive (automatic) mesh refinement/derefinement. Modules performing expensive topologic operations (joining, permuting, etc.) can use this callback to reduce the effort.

void OnEditDocument (IfmDocument pDoc, Widget hParent); Called when the user has pressed the Edit button in the Module selection dialog. Use OnEdit() to open an own-written dialog window. Commonly, inside this window the user can edit features defined in the module's FEM problem extension. (See OnBeginDocument(), Serialize()).

void OnActivate (IfmDocument pDoc, Widget button); OnActivate() is called when the user presses the module's taskbar button outside of the problem editor. Inside the problem editor, the callback OnEditDocument() is called instead.

void OnEnterProblemEditor (IfmDocument pDoc); OnEnterProblemEditor() is called immediately after the problem editor has been opened. For deciding whether the editor is called from within the simulator or not, implement OnEnterSimulator() and OnLeaveSimulator() also.

void OnLeaveProblemEditor (IfmDocument pDoc); OnLeaveSimulator() is called immediately before the problem editor is going to be closed.

void OnEnterSimulator (IfmDocument pDoc); OnEnterSimulator() is called immediately after the simulator menu has been opened. Remind the difference to PreSimulation(). Use the callback to raise graphical elements related to the simulation process (e.g., diagrams). You should implement OnLeaveSimulator() also to hide the raised elements.

void OnLeaveSimulator (IfmDocument pDoc); OnLeaveSimulator() is called immediately before the simulator menu is going to be closed. Use this callback to hide graphical elements created in OnEnterSimulator().

void OnStateChanged (IfmDocument pDoc, IfmDOC_STATE newState); OnStateChanged() notifies the module about changes document state, e.g. the module has been becoming enabled or disabled.

Page 14: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

IfmBool OnStateChanging (IfmDocument pDoc, IfmDOC_STATE newState); OnStateChanging() is called when the module's state becomes changed, e.g., the module becomes enabled or disabled. If this function returns FALSE the state change willl be rejected, otherwise accepted.

void PostLoadDocument (IfmDocument pDoc); PostLoadDocument() is called after loading the current FEM problem including all IFM modules.

IfmBool OnTimeStepConstraint (IfmDocument pDoc, double tCurrent, double* dtProposed); OnTimeStepConstraint() is called when the simulator determines the next time step. The current time as well as the proposed length of the time step are passed to this callback function. The proposed interval can be reduced due to the needs of the module (adjusting to power function points, etc.). If the proposed interval is increased, a warning will appear.

void OnMarkElementsForAMR (IfmAmr pAmr); OnMarkElementsForAMR() will be called after a full time step to calculate a new mesh topology by refining (enriching) or derefining (deenrichment) the existing elements. Call IfmSetAmrElementRefinement() to mark an element for refining/derefining and resetting the markup.

IfmResult PreRefineProblem (IfmDocument pDoc, int nNodeCount2D, int nElemCount2D); PreRefineProblem() is called after the calculation of a new mesh topology via mesh refinement/derefinement to fill the refined problem with copied or interpolated values. Implement PreRefineProblem() to allocate memory for module-maintained attribute vectors. Subsequent calls of OnRefineNodeAttributes() and OnRefineElementAttributes() have to be used to calculate new parameter values. Finally, PostRefineProblem() is called to install (swap) or discard (on error) the prepared vector.

void PostRefineProblem (IfmDocument pDoc, IfmBool bDiscard); PostRefineProblem() is called after the mesh refinement to install or discard the prepared module-held parameters. See PreRefineProblem() for more information. If the argument bDiscard is False, the refined parameters should be installed and the previously used parameters can be freed. If the is not enough memory for FEFLOW and all modules to reallocate the FEM problem PostRefineProblem() is called with bDiscard is True. In this case the new parameter vectors should be freed and the initial state before PreRefineProblem() has to be re-established.

void OnRefineElementAttributes (IfmDocument pDoc, int nNewCount, int* pNewElem, int nOldCount, int* pOldElem); OnRefineElementAttributes() is called while the mesh refinement to fill new elements with attribute data or to join multiple elements into one or more new elements. Use the indices of the old elements to calculate the values for all new elements.

void OnRefineNodeAttributes (IfmDocument pDoc, int nNewNode, int nNodeFrom, int nNodeTo); OnRefineNodeAttributes() is called while the mesh refinement to interpolate attributes for a new node based on one row two old nodes. If nOldTo is -1 the attributes can be copied from node nOldFrom to node nNewNode.

void OnLockPowerID (IfmDocument pDoc, int nPowerID); is called to determine if a power function is used by the IFM module. The module should return True (1) to indicate that the power function used and therefore has to be locked.

Page 15: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

2.3 Regionalization The Regionalization interface is much simpler than the Simulation interface: a few callbacks which are triggered only by events from the graphical user interface. They allow the implementation of inter/extrapolation methods for parameter and boundary conditions. Fig. 17 shows the input and output relationship of the regionalization task.

Inter-

polation method

Measure points (x, y, f)

Sample points (x, y)

Sample values (f)

Fig. 17: Input/output schema of regionalization methods

The graphical elements which are related to what callbacks are shown in Fig. 1.

Fig. 1: Callbacks of the Regionalization interface

OnStartRegio()

OnEditOptions()

Module name

OnImportData()

OnExportData()

OnEditData()

Dialog window created and managed by the module in OnEditOptions()

Page 16: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

void OnEditOptions (IfmRegio pReg, Widget hParent); Allows the implementation of an own graphical user interface (GUI) to edit module-specific options.

void OnImportData (IfmRegio pReg, Widget hParent); By the implementation of this callback you can override FEFLOW's internal loading facility for measure points. The callback should pop up a file selection dialog. The measure points loaded from the selected file can be stored into FEFLOW's internal store through the set of API functions such as IfmRegioSetMeasureValue() or functions completely managed inside the module. In the latter case the measure point handling callbacks OnExportData()as well as OnEditData() should be also implemented for having a consistent user interface.

void OnExportData (IfmRegio pReg, Widget hParent); Allows the implementation of an own graphical user interface to export measure points to an external file. Consider the notes above.

void OnEditData (IfmRegio pReg, Widget hParent); Use OnEditData() for providing a graphical editor to edit measure points.

IfmResult OnStartRegio (IfmRegio pReg); OnStartRegio() will be called to start the data regionalization. Data regionalization means the determination of values at given sample points by inter/extrapolation from user-defined measure points and values (cf. Fig. ). The measure points may be obtained from FEFLOW through the API calls IfmGetMeasure...() or from a module-internal store (see notes on OnImportData()). The sample point coordinates are given through IfmRegioGetSample[XYZ](); the result should be set through IfmRegioSetSampleValue().

void PostRegio (IfmRegio pReg); PostRegio() will be called after successful data regionalization. Error handling is performed via the exception handler.

Page 17: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

3 Programming Interface (IFM API) While the entry points into modules are realized by callback functions, the way to get information from FEFLOW and to transfer information to FEFLOW is processed by a set of functions starting with the prefix Ifm. These functions are summarized in the Interface Manager API (see Appendix B).

While the IFM maintains the module building and generates the stubs code of all selected callbacks the application of these API functions is part of the module programmer. Therefore, the understanding of the fundamentals of the API functions is of basic importance for writing stable and reusable modules with the FEFLOW IFM.

When beginning to write a module you can drop a number of API features such as Exception Handling, Serialization, Personal Profiles, Graphical User Interface, etc. You can add them later in more advanced revisions. Nevertheless, you should read the next sections for information and getting special hints in module programming.

3.1 Overview The design of the API has to meet a number of basic demands:

• The API must be independent of the current implementation in FEFLOW (this represents a very important request)

• The overhead produced in FEFLOW must be as low as possible.

• It should be possible to extend the FEM problem and accompanying result files with module-specific data.

• A suitable version management has to ensure that formerly written module data can also be loaded by future versions and vice versa.

• The user must be able to reach all module options and parameters within FEFLOW's graphical environment. Therefore, a module should be able to extend the graphical user interface of FEFLOW.

• The module source code should be widely platform-independent.

To fit the first demand the API is not realized as a library which would freeze the interface at compile or, at least, at link time. Instead, the API uses a number of hierarchical structures (also called contexts), containing the addresses of the current implementation functions of each API function.

For explanation look at the following simplified example. In a common header file we have the definition of the interface (e.g. ifm/modulep.h) for attaining x- and y-coordinates of a mesh:

typedef struct { float (*getX) (void*, int); float (*getY) (void*, int); ... } ModuleStruct; typedef struct ModuleStruct* IfmHandle; FEFLOW implements the defined API functions and passes the address of this structure to a module callback function (e.g. PreTimeStep):

static float MyGetX (void* data, int node) { return coordinates[0][node]; } static float MyGetY (void* data, int node) { return coordinates[1][node]; }

Page 18: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

static ModuleStruct modInfo = { MyGetX, MyGetY }; { ... PreTimeStep (&modInfo); ... } On the module's side we have two levels of referencing - firstly, in one of the files located in $FEFLOW_HOME/src/ifm/src which was created during the installation of the Interface Manager Development Kit. These files convert the function pointers (data) to real functions (code) so that you can use them in the usual way.

float IfmGetX (IfmHandle hdl, int node) { return (*hdl->getX) (hdl, node); } and, secondly, inside a callback function in your module:

void PreTimeStep (IfmHandle hdl) { float x = IfmGetX (hdl); ... } This approach has the advantage to hide all changes in the implementation functions in FEFLOW (e.g. MyGetX and MyGetY). Neither recompilation nor relinking of the module are necessary. This approach has to satisfy the following requirements:

i) The position of all members in a structure has to remain the same over all time.

ii) New API functions can be appended only at the end of the original structure.

iii) Modules should check the size of the structure before accessing a member.

iv) The handle passed to a callback function must be never corrupted.

Requirements i) and ii) are guaranteed by the Interface Manager, but the remaining requirements have to be ensured by the module programmer. Therefore, the API has been expanded by a number of macros and functions for debugging purposes. All API context structures contain special members storing a version number, the length of the function pointer array, an opaque run-time information and others.

3.2 Exception Handling The application the API should be widely intuitive. Compared with a return value-based approach, e.g.

float x; if (!IfmGetX (hdl, node, &x)) /* Perform any error handling ...*/ you will find the exception-based approach easier to use:

float x = IfmGetX (hdl, node); The advantage of the latter becomes more visible when using the API functions in complex expressions, e.g.

double distance = hypot (IfmGetX (hdl, node), IfmGetY (hdl, node)); compared with:

Page 19: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

float x, y; double distance; if (!IfmGetX (hdl, node, &x)) abort(); if (!IfmGetY (hdl, node, &y)) abort(); distance = hypot (x, y); Most of the API functions avoid the continuous check on successful completion. But what happens when invalid arguments are passed to an API function or when other errors occur?

In this case the Interface Manager calls a special callback function - the Exception Handler. This exception handler decides whether to continue the execution with a given value or to skip the remaining part of the exception-causing callback. The principle is very similar to the method which is used by the math library (libm.a) when passing, for instance, negative arguments to the log() function. See the manual page for matherr(2) for more information.

As long as the module does not install its own exception handler the IFM uses an internal handler. It sends a message to FEFLOW's log window and skips the execution for the exception-causing callback.

An exception handler has the following prototype:

void ExceptionHandler (IfmHandle, IfmExceptionContext*); where IfmExceptionContext is defined as

typedef struct { const char* proc; /* Name of the interface function */ const char* reason; /* Reason of exceptional condition */ enum { IfmEX_SKIP, /* Skip execution of the calling function */ IfmEX_CONTINUE, /* Continue with value from argUnion */ IfmEX_ABORT /* Abort the current execution */ } action; enum { IfmEX_VOID, IfmEX_LONG, IfmEX_ULONG, IfmEX_FLOAT, IfmEX_DOUBLE, IfmEX_STRING, IfmEX_CHAR_ARRAY, IfmEX_POINTER } argType; /* Argument type */ union { long aLong; /* Value for all signed integers */ unsigned long aULong; /* Value for all unsigned integers */ float aFloat; /* Value for floats */ double aDouble; /* Value for doubles */ char* aString; /* Value for allocated and static strings */ void* aPointer; /* Value for anonymous pointer */ } argUnion; } IfmExceptionContext; The members describe the context of the exception:

• proc: Contains the name of the API function that fails. The prefix Ifm is omitted. An example: If you pass a negative node number to IfmGetX you will get an exception with proc containing the string "GetX".

• reason: Describes the error reason in clear text in English language.

• action: Tells the Interface Manager what is to do after the exception handler has returned. The explanation for all possible values is given below.

Page 20: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

• argType: Defines the type of the return value of the API function that causes the exception. This member is only of interest if action was set to IfmEX_CONTINUE. The value must not be changed! Use the type together with proc and/or reason to set a value in argUnion.

• argUnion: When continuing after exception handling the value of this member is used as the return value of the failing API function. Since different API functions have different return types it is necessary to consult the argType member before setting a value.

The member variable action can be set to the following values:

• IfmEX_SKIP: This is the default setting and prevents the API function from returning. The rest of the callback function that calls the API function will be skipped. In the case of periodically invocation the callback function will be called next round again, possibly repeating the same error. Note! If memory has been allocated, file have been opened, etc. inside the callback function you should use IfmOnError() to perform a cleanup before leaving the callback. For demonstration see the example further below.

• IfmEX_CONTINUE: Setting action to this value the API function returns immediately with the function value found in argUnion. It depends on the API function which member of this union takes affect. Note that all integer return values (char, short, int, and long) are represented by the aLong member and all unsigned integer return values by the aULong member.

• IfmEX_ABORT: Behaves similar to IfmEX_SKIP, however, all future calls of any callback function of the same module will be suppressed.

Since the call of an API function might not return it is necessary to use a construct for the cleanup of temporary variables. The Interface Manager API provides a few functions to control the exception handling as follows:

int IfmOnError (IfmHandle pHdl); Returns True if passed on an error. If applied in a stack frame deeper than the callback itself, IfmResume() has to be called before this stack frame is left. Therefore, the best method is to call IfmOnError() as the first statement in a callback function. It makes the call of IfmResume() unnecessary. The example below explains the usage of both IfmOnError() and IfmResume().

void IfmResume (IfmHandle pHdl); Indicates the leaving of a stack frame with an IfmOnError() call.

void IfmTryProc (IfmHandle pHdl, void (*pfnProc)(IfmHandle, void*)); Executes a function under the exception control of the Interface Manager.

void IfmThrowException (IfmHandle pHdl, const char *pszProc, const char *pszReason); Throws an own-defined exception. You can pass any text to pszProc and pszReason.

This example demonstrates the usage of these API functions:

void PreTimeStep (IfmDocument pDoc) { double** work = NULL; /* Initialize with NULL! */ int i; if (IfmOnError (pDoc)) { IfmFreeMem2D (pDoc, &work); return; /* Forget never to return !!! */ } /* Since memory allocation exceptions are not disabled by default * (see IfmEnableMemoryExceptions()) the exception handler will * be called even on out-of-memory errors. */ IfmMallocMem2D (pDoc, &work, IfmGetNumberOfNodes (pDoc), 2, sizeof (double), False);

Page 21: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* * The next statement is incorrect because C indices are starting at 0! */ for (i = 1; i <= IfmGetNumberOfNodes (pDoc); i++) { work[i][0] = IfmGetOriginX (pDoc) + IfmGetX (pDoc, i); work[i][1] = IfmGetOriginY (pDoc) + IfmGetY (pDoc, i); } ... IfmFreeMem2D (pDoc, &work); } With C++ you can mix the standard exception handling with the IFM exception handling:

class CMyException { public: CMyException() {} }; void ExceptionHandler (IfmHandle pHdl, IfmExceptionContext* pEx) { IfmWarning (pHdl, "Caught exception in \"%s\": %s!\n", pEx->proc, pEx->reason); throw CMyException(); } void PreTimeStep (IfmDocument pDoc) { double** work = NULL; /* Initialize with NULL! */ try { // Can throw an exception IfmMallocMem2D (pDoc, &work, IfmGetNumberOfNodes (pDoc), 2, sizeof (double), False); // Will throw an exception! for (int i = 1; i <= IfmGetNumberOfNodes (pDoc); i++) { work[i][0] = IfmGetOriginX (pDoc) + IfmGetX (pDoc, i); work[i][1] = IfmGetOriginY (pDoc) + IfmGetY (pDoc, i); } ... IfmFreeMem2D (pDoc, &work); } catch (CMyException e) { IfmFreeMem2D (pDoc, &work); } }

Page 22: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

3.3 Serialization Since modules become an integral part of FEFLOW it is a fundamental demand to store private data of a module into the FEM and DAC files. For this purpose the Interface Manager provides a callback function named Serialize() and a set of archive API functions identified by the prefix Ifmio. The interface is neutral with respect to the supported file modes - Binary and ASCII. That means with the same code you can write to and read from both file types. The API functions called from Serialize()realize four tasks:

• Initialize local data before its first use (IfmIO_INIT).

• Load local data from FEM files (IfmIO_LOAD).

• Store local data into FEM files (IfmIO_STORE).

• Free the memory which was allocated during loading, editing, or processing (IfmIO_FREE).

Let's look at an example. The callback functions below have to maintain three document-related variables which are combined in the following structure:

typedef struct { int count; float speed; char* name; } LocalDocData; OnBeginDocument() is responsible for creating a new instance of the local data. OnBeginDocument() can have the following form:

IfmResult OnBeginDocument (IfmDocument pDoc) { LocalDocData * data = NULL; ... IfmAllocMem (pDoc, &data, 1, sizeof (LocalDocData), False); IfmDocumentSetUserData (pDoc, data); return True; } While OnBeginDocument() allocates memory OnEndDocument() has to free it.

void OnEndDocument (IfmDocument pDoc) { LocalDocData * pData = (LocalDocData *)IfmDocumentGetUserData (pDoc); IfmFreeMem (pDoc, &pData); IfmDocumentSetUserData (pDoc, pData); /* Set NULL */ } The following callback function is called on multiple events:

void Serialize (IfmDocument pDoc, IfmArchive pArc) { LocalDocData * pData = (LocalDocData *)IfmDocumentGetUserData (pDoc); IfmioInt (pArc, &pData->count); IfmioFloat (pArc, &pData->speed); IfmioString (pArc, &pData->name, 256); } These few lines are suitable to:

• initialize the LocalDocData structure with zero values immediately after OnBeginDocument(),

• load all values from ASCII and binary FEM files as well,

• save all values into ASCII and binary FEM files as well,

• free the name member immediately before OnEndDocument() is called.

Page 23: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Usually, programs under development need more data to serialize with each new version. If you append simply a further member to the LocalDocData structure and a corresponding line in Serialize, you would get a premature-end-of-file error when you try to load an older file. The reason is obvious: the older version has written only the first three members into the file that were known at this time.

The Serialize API provides four functions to build an effective version control:

int IfmGetLoadVersion (IfmDocument pDoc); Retrieve the module version found in FEM file.

int IfmGetSaveVersion (IfmDocument pDoc); Retrieve the module version to be used while next FEM file saving.

void IfmSetSaveVersion (IfmDocument pDoc, int nVersion); Set the module version to be used while next FEM file saving.

int IfmioGetVersion (IfmArchive pArc); Retrieve the effective archive version (see remarks below)

IfmGetLoadVersion() echoes the information of the module version that wrote the FEM file of this document. This information is read/only, while the saving version is read/write. That means you can tell your Serialize callback a lower version output to produce through IfmSetSaveVersion(). The maximum (and default) version number is limited to the current module version. For completeness there is the counterpart function - IfmGetSaveVersion().

The result of IfmioGetVersion() (Table 3) is more synthetic: it depends on the current serialization mode, the current module and the document.

Table 3: Dynamic serialization version

Serialization mode

Return value

IfmIO_INIT Current module version

IfmIO_LOAD Load version of current document

IfmIO_STORE Save version of current document, by default, the version of the current module

IfmIO_FREE Current module version

The application of the version management is very easy. For demonstration, we add the new data member heat to LocalDocData:

typedef struct { int count; float speed; double heat; char* name; } LocalDocData; void Serialize (IfmDocument pDoc, IfmArchive pArc) { LocalDocData * pData = (LocalDocData *)IfmDocumentGetUserData (pDoc); if (IfmioGetVersion (pArc) >= 0x1000) { IfmioInt (pArc, &pData->count); IfmioFloat (pArc, &pData->speed); IfmioString (pArc, &pData->name, 256); }

Page 24: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

if (IfmioGetVersion (pArc) >= 0x1100) { IfmioDouble (pArc, &pData->heat); } } This Serialize callback handles all possible cases: it initializes and frees all available variables during document creation and destroying, it loads only the variables which correspond to the module version that wrote the file, and it saves all variables which correspond to the module version set for output.

The proposed procedure of data serialization has an additional positive effect: you get always an overview which variables are related to a specific version.

3.4 API Functions The API distinguishes between different context levels. Each API function is assigned to a specific context part, which means that the function pointer belongs to a specific structure. Since context part structures are stacked (a higher level structure sits on the top of a lower level structure), you can call lower level API functions on a higher level context. The basic context is the module-level context. All functions defined in this level can be called from anywhere as soon as the module is loaded (inside a callback function this should be obvious). The next higher level is called document-level. This context becomes available when a module has been attached to a FEM problem. Higher context levels provide an interface to task-specific data, e.g. inside the data regionalization menu it gives access to the raw data of interpolation and so on. They become available only in special situations. To mark the different complexity of a context, different names are used for the handles: IfmModule, IfmDocument, IfmRegio, etc. In real, they are all of type IfmHandle.

You can find a complete description of all API functions in alphabetical order in Appendix B (see online help).

Page 25: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

4 Examples This Section demonstrates alternate methods of implementing a module. All examples are referred to the same task: the incorporation of the Inverse Distance Weighting (IDW) interpolation method for data regionalization purposes. The method is relative simple and easy to understand. The source code of the IDW method written in C and FORTRAN is given in Appendix A. The following prototype definition can be used:

void idw_interpolate ( int number_of_measure_points, /* Input */ double* measure_point_x_coordinates, /* Input */ double* measure_point_y_coordinates, /* Input */ double* measure_point_values, /* Input */ int number_of_sample_points, /* Input */ double* sample_point_x_coordinates, /* Input */ double* sample_point_y_coordinates, /* Input */ double* sample_point_values, /* Output */ int num_neighbors, /* Input */ double exponent); /* Input */ The task which has to be programmed is describes in the sketch of Fig. 2.

In the following sections we propose three alternate methods to realize an IFM module: direct implementation, execution of an other program, and client/server method. Each method has its own advantages and disadvantages which are listed in Table 1.

Table 1: Comparison of module implementation methods

Method Advantage Disadvantage

Direct implementation • No extra process necessary.

• No data duplication necessary.

• → Fastest method.

• → Lowest overhead.

• Direct access to FEFLOW's GUI. (alerts, progress bar, etc.)

• Working in FEFLOW's address space (memory violations affect both FEFLOW and the module).

• No graphical update while FEFLOW waits for task completion. (The global event loop must be maintained by FEFLOW)

• Requires availability of the method's source code.

Measure points (x, y, f)

Sample points (x, y)

Parameter (neighbors, exp.)

Sample values (f)

InverseDistance

Weighting

Inter-polation

Fig. 2: Input/output schema of idw_interpolation()

Page 26: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

• Widely operating system-independent.

• Naming conflicts with FEFLOW's internal variables are possible.

Executing another program

• No source code of the method's implementation required.

• Program runs in own address and naming space.

• Testing independent of FEFLOW.

• While waiting on completion, other tasks (e.g. graphical update) can be performed.

• Additional process required.

• Starting command mostly operating system-dependent.

• Passing data from FEFLOW to the program through files or pipes.

• No access to FEFLOW's GUI.

Client/server with RPC • Separate implementation of task formulation and realization with separate address and naming space

• Client and server can run on different hosts

• Opportunity to provide multiple event loops.

• High effort for network management

• Requires to handle network communication problems.

• No access to FEFLOW's GUI

You can create the framework for each example as described in Section 0 1. Programming Tool Kit. For the first we implement only the OnStartRegio() callback in different ways. Later you can add further improvements such as a parameter editor or special import facilities. Each of the following sections contains the complete source code of the callback OnStartRegio(). All other parts are generated automatically by the IFM. To avoid typing in you can use for all examples the code located in $FEFLOW_HOME/src/ifm/samples/regio.

4.1 Direct Implementation First, we implement the IDW interpolation method into the module in a direct manner (see example invdirect). This approach gives the best performance with the lowest overhead. But it requires also full source code availability. Since the IDW subroutine needs vectors as input and also requires a vector for output we have to allocate appropriate vectors and fill them with the desired information. Here you see the complete implementation of the callback:

/* ** Prototype of the IDW routine */ extern void idw_interpolation (int, double*, double*, double*, int, double*, double*, double*, int, double); static IfmResult OnStartRegio (IfmRegio pReg) { int i, sam_n, mea_n; /* Number of triplets resp. */ double *mea_x = NULL; /* Measure point triplets */ double *mea_y = NULL; double *mea_f = NULL; double *sam_x = NULL; /* Sample point triplets */ double *sam_y = NULL; double *sam_f = NULL; /* (as output) */ /* Inverse Distance Weighting parameters */ int idw_neighbors = (int)IfmProfileGetLong (pReg, profile, "Neighbors", INVDIST_NEIGHBORS); double idw_exponent = IfmProfileGetDouble (pReg, profile,

Page 27: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

"Exponent", INVDIST_EXPONENT); /* ** Retrieve the number of sample and measure points. */ sam_n = IfmRegioGetNumberOfSamplePoints (pReg); mea_n = IfmRegioGetNumberOfMeasurePoints (pReg); if (sam_n == 0 || mea_n == 0) return False; /* Nothing to do */ /* ** Allocate local arrays (no error check!) */ IfmAllocMem (pReg, &mea_x, mea_n, sizeof (double), False); IfmAllocMem (pReg, &mea_y, mea_n, sizeof (double), False); IfmAllocMem (pReg, &mea_f, mea_n, sizeof (double), False); IfmAllocMem (pReg, &sam_x, sam_n, sizeof (double), False); IfmAllocMem (pReg, &sam_y, sam_n, sizeof (double), False); IfmAllocMem (pReg, &sam_f, sam_n, sizeof (double), False); /* ** Read measure point coordinates and values */ for (i = 0; i < mea_n; i++) { mea_x[i] = IfmRegioGetMeasureX (pReg, i); mea_y[i] = IfmRegioGetMeasureY (pReg, i); mea_f[i] = IfmRegioGetMeasureValue (pReg, i); } /* ** Read sample point coordinates */ for (i = 0; i < sam_n; i++) { sam_x[i] = IfmRegioGetSampleX (pReg, i); sam_y[i] = IfmRegioGetSampleY (pReg, i); } /* ** Perform Inverse Distance Interpolation */ idw_interpolation (pReg, mea_n, mea_x, mea_y, mea_f, sam_n, sam_x, sam_y, sam_f, idw_neighbors, idw_exponent); /* ** Write results to the sample points */ for (i = 0; i < sam_n; i++) IfmRegioSetSampleValue (pReg, i, sam_f[i]); /* ** Free local arrays */ IfmFreeMem (pReg, &mea_f); IfmFreeMem (pReg, &mea_y); IfmFreeMem (pReg, &mea_x); IfmFreeMem (pReg, &sam_f); IfmFreeMem (pReg, &sam_y); IfmFreeMem (pReg, &sam_x); return True; } Let's explain this callback in more detail as follows:

static IfmResult OnStartRegio (IfmRegio pReg) {

Page 28: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

The header of the callback was created by the IFM. When you click on the Add Function button while the callback OnStartRegio is selected the minus marker turns to plus and an empty function body has been added to the primary source code file as well as the necessary registration call in the function RegisterModule.

double *mea_x = NULL; /* Measure point triplets */ ... IfmAllocMem (pReg, &mea_x, mea_n, sizeof (double), False); ... Since our predefined interpolation method requires vectors as input and output we have to collect the elemental results from the API calls to complete vectors. Here we could use fixed size arrays. But the number of measure points and especially the number of sample points might be very large and unknown at compile time. So it is better to allocate memory exactly of the necessary size from the operating system. For memory allocation (see Section Fehler! Verweisquelle konnte nicht gefunden werden. Fehler! Verweisquelle konnte nicht gefunden werden.) we use the IFM API routines for demonstration and comfort; malloc() or calloc() are here not preferred since we would have to provide an own error handling on insufficient memory. The routine IfmAllocMem() requires an initialized pointer as input. So we have to set mea_x as well as the other pointers to NULL. If there is not enough memory IfmAllocMem() calls the ExceptionHandler(). We have no handler implemented yet. But the default implementation is sufficient for the most cases. On error, the execution of the OnStartRegio callback would be suspended.

int idw_neighbors = (int)IfmProfileGetLong (pReg, profile, "Neighbors", INVDIST_NEIGHBORS); double idw_exponent = IfmProfileGetDouble (pReg, profile, "Exponent", INVDIST_EXPONENT); Since these parameters are more personal it is a good idea to hold them in the personal profile. See Section Fehler! Verweisquelle konnte nicht gefunden werden. Fehler! Verweisquelle konnte nicht gefunden werden. for how to use these API functions. Later we can implement a parameter editor which allows the modification of the profile entries. For the first the default values INVDIST_NEIGHBORS and INVDIST_EXPONENT, respectively, will take affect.

for (i = 0; i < mea_n; i++) { mea_x[i] = IfmRegioGetMeasureX (pReg, i); mea_y[i] = IfmRegioGetMeasureY (pReg, i); mea_f[i] = IfmRegioGetMeasureValue (pReg, i); } for (i = 0; i < sam_n; i++) { sam_x[i] = IfmRegioGetSampleX (pReg, i); sam_y[i] = IfmRegioGetSampleY (pReg, i); } In these loops we fill the allocated vectors with the appropriate measure and sample points from FEFLOW. Note, the index for the Get functions starts always with zero!

Remark: In principle, you can also rewrite the IDW routine for a direct use of the IfmRegio API functions. This would avoid the expensive memory allocation and copy operations. The overhead arising in resolving the function pointers from the jump tables (see Section 0 3.1 Overview) is very less. On the other hand, with the copy of FEFLOW's data you can do what you want without affecting neither FEFLOW nor the state of the current FEM problem. If the interpolation method does not succeed you can free the allocated working fields, returning False and that's all. The current parameter distribution remains kept.

idw_interpolation (pReg, mea_n, mea_x, mea_y, mea_f, sam_n, sam_x, sam_y, sam_f, idw_neighbors, idw_exponent); That is the call to the real interpolation method as described in Appendix A in C code (A.1) and FORTRAN (A.2). In interpolation routines based directly on the API functions the expression

Page 29: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

sam_f[i] = value would have to be replaced by the call IfmRegioSetSampleValue(pReg, i, value).

for (i = 0; i < sam_n; i++) IfmRegioSetSampleValue (pReg, i, sam_f[i]); This stores the interpolation results back to FEFLOW.

IfmFreeMem (pReg, &mea_f); ... Last, all allocated memory has to be freed. Since FEFLOW can call the module more than once an omission of the corresponding Free statements would cause memory leaks. Such leaks can lead to future memory allocation denies.

return True; } The return value of this callback indicates whether the operation was successful or not. On success, FEFLOW displays the new parameter distribution as it would do for its internal methods.

Note! Returning False it does not imply the restoration of cells which are possibly destroyed through calls of IfmRegioSetSampleValue(). This lies completely in the responsibility to the programmer of the module.

A slight modification allows the calling of an interpolation routine which was implemented in FORTRAN. Since Mixed Language Programming depends on the used compilers for FORTRAN and C we will demonstrate the principle on hand of the Microsoft compilers Visual C++ and FORTRAN Power Station.

To call the FORTRAN IDW routine (see Appendix A.2) we have to modify its prototype definition as follows:

/* ** External implementation as a FORTRAN subroutine */ extern void __stdcall IDW_INTERPOLATION (int*, double*, double*, double*, int*, double*, double*, double*, int*, double*); Accordingly, we have to replace the call of the interpolation routine idw_interpolation() by the following code:

IDW_INTERPOLATION (&mea_n, mea_x, mea_y, mea_f, &sam_n, sam_x, sam_y, sam_f, &idw_neighbors, &idw_exponent); The reserved keyword __stdcall has been used to declare the different calling convention. Also, it is important to reference the FORTRAN routine always by upper-case letters, even when it was written in lower-case letters in the FORTRAN source code. Besides, pay attention to pass all arguments by reference. In C, all arrays are passed automatically by reference, but all integral arguments have to precede an ampersand to pass their addresses.

4.2 Using an Executable as a Module Sometimes, the method you want to use is only available in binary form, as an executable. The example invexec provides such an executable (a main program around the IDW interpolation method). Mostly, you have to transfer the data as files and parameters via command line options. Besides of passing data files some programs might be able to read the data from standard input (stdin) and send their output to standard output (stdout). Error messages should arrive always on standard error output (stderr). Such programs can be invoked with statements as:

% program < input_file > output_file

Page 30: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

The big advantage of this I/O redirection becomes visible when two or more programs are concatenated with the '|' pipe operator:

% program1 < input_file | program2 | program3 > output_file

By convention, many programs also allow to specify "-" when standard input is meant for an input file or standard output for output files, e.g.:

% tar cvf - /usr | gzip > dump.tgz

The sample application invexe provides all features mentioned above. See the examples below for alternative kinds of invocation, where mea_file specifies the measure point triplet file, sam_file specifies the sample point coordinates file and val_file specifies the resulting sample value file:

• invexe -e 2.5 mea_file sam_file val_file

• invexe -n 8 mea_file < sam_file > val_file

• invexe mea_file - val_file < sam_file

• invexe - sam_file < mea_file > val_file

• cat sam_file mea_file | invexe - - val_file

• cat sam_file mea_file | invexe > val_file

The first two examples show the usage of command line parameters: in the first one the weighting exponent has been changed from default 2.0 to 2.5, in the second example the number of considered neighbor points has been increased from 4 to 8.

All three files have the same structure: The first line contains the number of records (lines) followed by the data records, each per line. A record consist of three columns (mea_file), two columns (sam_file), or one column (val_file). A column alignment is not necessary; but the columns must be separated by at least one white space character (blank or tab). For instance:

87 3407969.50010742 3411397.28990234 10.231 3407667.25010742 3410500.28990234 9.982 3407287.00010742 3409574.28990234 10.000 3407043.25010742 3409066.78990234 19.998 3406466.25010742 3407810.28990234 10.676 3406419.50010742 3407261.28990234 12.333 ... This is the complete source code of the OnStartRegio callback:

static IfmResult OnStartRegio (IfmRegio pReg) { int i, success = False; char cmd[1024]; double val; FILE* fp = NULL; char fn_mea[MAXPATHLEN]; char fn_sam[MAXPATHLEN]; char fn_val[MAXPATHLEN]; /* Inverse Distance Weighting parameters */ int idw_neighbors; double idw_exponent; char* idw_program; /* ** Get temporary filenames. */ if (!tmpnam (fn_mea) || !tmpnam (fn_sam) || !tmpnam (fn_val)) { IfmWarning (pReg, "tmpnam(): %s\n", strerror (errno)); return False; }

Page 31: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* ** Write the measure point file. */ fp = fopen (fn_mea, "wt"); if (!fp) { IfmWarning (pReg, "%s: %s\n", fn_mea, strerror (errno)); goto leave; } fprintf (fp, "%d\n", IfmRegioGetNumberOfMeasurePoints (pReg)); for (i = 0; i < IfmRegioGetNumberOfMeasurePoints(pReg); i++) fprintf (fp, "%.15lg %.15lg %.15lg\n", IfmRegioGetOriginX(pReg)+IfmRegioGetMeasureX (pReg, i), IfmRegioGetOriginY(pReg)+IfmRegioGetMeasureY (pReg, i), IfmRegioGetMeasureValue (pReg, i)); fclose (fp); /* ** Write the sample point file. */ fp = fopen (fn_sam, "wt"); if (!fp) { IfmWarning (pReg, "%s: %s\n", fn_sam, strerror (errno)); goto leave; } fprintf (fp, "%d\n", IfmRegioGetNumberOfSamplePoints (pReg)); for (i = 0; i < IfmRegioGetNumberOfSamplePoints(pReg); i++) fprintf (fp, "%.15lg %.15lg\n", IfmRegioGetOriginX(pReg)+IfmRegioGetSampleX (pReg, i), IfmRegioGetOriginY(pReg)+IfmRegioGetSampleY (pReg, i)); fclose (fp); fp = NULL; /* ** Obtain interpolation parameter. */ idw_neighbors = (int)IfmProfileGetLong (pReg, profile, "MinNeighbors", INVDIST_NEIGHBORS); idw_exponent = IfmProfileGetDouble (pReg, profile, "Exponent", INVDIST_EXPONENT); idw_program = IfmProfileGetString (pReg, profile, "Program", INVDIST_PROGRAM); /* ** Assume the executable in the module's directory. */ IfmGetComponentPath (pReg, cmd, sizeof (cmd), idw_program); sprintf (strchr (cmd, 0), " -n %d -e %g %s %s %s", idw_neighbors, idw_exponent, fn_mea, fn_sam, fn_val); /* ** Submit command string to the operating system. */ if (system (cmd) != 0) { IfmWarning (pReg, "Failed to execute \"%s\"!\n", idw_program); goto leave; }

Page 32: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* ** Read sample values. */ fp = fopen (fn_val, "rt"); if (!fp) { IfmWarning (pReg, "%s: %s\n", fn_val, strerror (errno)); goto leave; } fscanf (fp, "%d", &i); if (i != IfmRegioGetNumberOfSamplePoints (pReg)) { IfmWarning (pReg, "Bad number of values (%d) returned!\n", i); goto leave; } for (i = 0; i < IfmRegioGetNumberOfSamplePoints (pReg); i++) { if (fscanf (fp, "%lf", &val) == 1) IfmRegioSetSampleValue (pReg, i, val); } success = True; /* ** Cleanup. */ leave: if (fp) fclose (fp); (void)remove (fn_sam); (void)remove (fn_mea); (void)remove (fn_val); IfmProfileFreeString (pReg, idw_program); return success; } Some parts are very similar to the previous example. So we will address only the different features:

char fn_mea[MAXPATHLEN]; char fn_sam[MAXPATHLEN]; char fn_val[MAXPATHLEN]; if (!tmpnam (fn_mea) || !tmpnam (fn_sam) || !tmpnam (fn_val)) { IfmWarning (pReg, "tmpnam(): %s\n", strerror (errno)); return False; } For passing the data to the IDW program invexe we need two files and, for obtaining the result, another file (we can also use a pipe for output – see later). One way would be to use fixed file names, for instance invmea.tmp, invsam.tmp, or invval.tmp. But in a multi-user/multi-tasking environment it is possible that two users or the same user multiply invoke the program at the same time. In this case, the last invocation would overwrite the first and we would get wrong results. In regard to this problem the C runtime library contains some functions for creating temporary files. For our case we use the tmpnam() function which returns an unique file name in the default temporary directory (e.g. /var/tmp). The location can be changed by setting the environment variable TMPDIR. The name consists of a number of letters and numbers with no relationship to the later contents of the file, e.g. baac2919. If you wish more control over the generated file name use tempnam() instead of tmpnam().

If the requested file names cannot be obtained from the system, we print a warning message into FEFLOW's Log Window and give up. The function strerror() converts the global error code errno into a string.

Page 33: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

fp = fopen (fn_mea, "wt"); if (!fp) { IfmWarning (pReg, "%s: %s\n", fn_mea, strerror (errno)); goto leave; } fprintf (fp, "%d\n", IfmRegioGetNumberOfMeasurePoints (pReg)); for (i = 0; i < IfmRegioGetNumberOfMeasurePoints(pReg); i++) fprintf (fp, "%.15lg %.15lg %.15lg\n", IfmRegioGetOriginX(pReg)+IfmRegioGetMeasureX (pReg, i), IfmRegioGetOriginY(pReg)+IfmRegioGetMeasureY (pReg, i), IfmRegioGetMeasureValue (pReg, i)); fclose (fp); These few lines create the temporary measure points file. Here, the IFM API functions are used directly while writing the file. No special memory allocation is required. In difference to the first example of direct implementation all coordinates are passed in double precision for universality. In the fprintf()call we use the format "%.15lg". It forces to print up to 15 significant digits and changes automatically to the exponential format if necessary. This grants a maximum information on each coordinate with a minimum on occupied disk space.

In the same manner we create the sample points file.

idw_program = IfmProfileGetString (pReg, profile, "Program", INVDIST_PROGRAM); We obtain the name of the program to run also from the personal profile. We have to keep in mind that the obtained string has to be freed later by IfmProfileFreeString()!

IfmGetComponentPath (pReg, cmd, sizeof (cmd), idw_program); Through IfmGetComponentPath()we can get the complete path of a component based on the path from where the module was loaded. Since the module can be on any location it is more unlikely that the executable can be found on the search path. Thus, we invoke the executable with the full path specification.

sprintf (strchr (cmd, 0), " -n %d -e %g %s %s %s", idw_neighbors, idw_exponent, fn_mea, fn_sam, fn_val); The statement appends the parameter as command line options (-n and -e) as well as all three file names. The first argument of sprintf() presents normally the buffer pointer for printing. We use the result of the call strchr(cmd, 0) which returns a pointer to the end of the string cmd. The result will be always non-zero while using zero-terminated strings (ASCIIZ strings).

if (system (cmd) != 0) { IfmWarning (pReg, "Failed to execute \"%s\"!\n", idw_program); goto leave; } The statement system() executes the command we have built by using the default command interpreter. Consider system() as a kind of submitting commands as you would do in a common command or terminal window. The return value from this call corresponds to the exit value of the child process. If the child dies due to an error signal synthetic exit codes are generated (130 and above). When the program was successfully started and has terminated with the value 0 the return value of system() is also 0 which indicates normally that all is well.

Page 34: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

fp = fopen (fn_val, "rt"); if (!fp) { IfmWarning (pReg, "%s: %s\n", fn_val, strerror (errno)); goto leave; } fscanf (fp, "%d", &i); if (i != IfmRegioGetNumberOfSamplePoints (pReg)) { IfmWarning (pReg, "Bad number of values (%d) returned!\n", i); goto leave; } for (i = 0; i < IfmRegioGetNumberOfSamplePoints (pReg); i++) { if (fscanf (fp, "%lf", &val) == 1) IfmRegioSetSampleValue (pReg, i, val); } These lines read the generated output file, check again that the length of the resulting vector meets the expected value and store the interpolation results back to FEFLOW.

Besides the method of executing the program via system() and the explicit opening for reading through fopen() both steps can be combined to one call of popen() as long as the program can send its output to the standard output (by default screen/window). Since our example program allows this mode we can rewrite the last part as follows:

int i, success = False, is_pipe = False; ... sprintf (strchr (cmd, 0), " -n %d -e %g %s %s", idw_neighbors, idw_exponent, fn_mea, fn_sam); fp = popen (cmd, "rt"); is_pipe = True; ... leave: if (fp) is_pipe ? pclose (fp) : fclose (fp); ... Note, in the sprintf() call the fn_val argument has been removed. Once fp was opened by popen() it can be handled as before. But, pay attention to close the handle with pclose() instead of fclose().

4.3 Coupling an Application via RPC As you have seen in the above example where a module was coupled to a binary program it requires a higher effort and produces additional overhead.

The method shown below is a generalized version of Inter-Process Communication (IPC). It does not require to run the sub-process on the same host as FEFLOW is running. The method is called Remote Procedure Calls, or short RPC. Using RPC we get a new approach: the Client/Server Model. This example is based on the popular Open Network Computing (ONC)- or SUN-RPC.

First we summarize the code (located in invrpc):

static IfmResult OnStartRegio (IfmRegio pReg) { int i, success = False; /* Inverse Distance Weighting parameters */ int idw_neighbors = (int)IfmProfileGetLong (pReg, profile, "Neighbors", INVDIST_NEIGHBORS); double idw_exponent = IfmProfileGetDouble (pReg, profile, "Exponent", INVDIST_EXPONENT); int mea_n = IfmRegioGetNumberOfMeasurePoints (pReg); int sam_n = IfmRegioGetNumberOfSamplePoints (pReg); double xorg = IfmGetOriginX (pReg); double yorg = IfmGetOriginY (pReg);

Page 35: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

double *mea_x = NULL, *mea_y = NULL, *mea_f = NULL; double *sam_x = NULL, *sam_y = NULL, *sam_f = NULL; IfmAllocMem (pReg, &mea_x, mea_n, sizeof (double), False); IfmAllocMem (pReg, &mea_y, mea_n, sizeof (double), False); IfmAllocMem (pReg, &mea_f, mea_n, sizeof (double), False); for (i = 0; i < mea_n; i++) { mea_x[i] = xorg + IfmRegioGetMeasureX (pReg, i); mea_y[i] = yorg + IfmRegioGetMeasureY (pReg, i); mea_f[i] = IfmRegioGetMeasureValue (pReg, i); } IfmAllocMem (pReg, &sam_x, sam_n, sizeof (double), False); IfmAllocMem (pReg, &sam_y, sam_n, sizeof (double), False); IfmAllocMem (pReg, &sam_f, sam_n, sizeof (double), False); for (i = 0; i < sam_n; i++) { sam_x[i] = xorg + IfmRegioGetSampleX (pReg, i); sam_y[i] = yorg + IfmRegioGetSampleY (pReg, i); }

if (idw_interpolation (pReg, mea_n, mea_x, mea_y, mea_f, sam_n, sam_x, sam_y, sam_f, idw_neighbors, idw_exponent)) {

for (i = 0; i < sam_n; i++) IfmRegioSetSampleValue (pReg, i, sam_f[i]); success = True; } IfmFreeMem (pReg, &mea_x); IfmFreeMem (pReg, &mea_y); IfmFreeMem (pReg, &mea_f); IfmFreeMem (pReg, &sam_x); IfmFreeMem (pReg, &sam_y); IfmFreeMem (pReg, &sam_f); return success; } At a first glance it looks very simple, nearly the same as the direct implementation. However, since all network communication effort is realized in idw_interpolation() it is now more a communication method instead of an interpolation method. The real idw_interpolation() will be executed on the server site, out of FEFLOW and IFM.

For the transport of all arguments, measure point triplets, sample coordinates and parameters to the server (we call it invsvc) and getting the results back from the server we have to establish a network connection, sending and receiving data with time-outs, serializing the local data to a network transparent representation, and others. This would be a lot of work. Thus, the developers of the RPC package have been provided a special tool for creating client/server applications named RPCGEN. It generates the source code for all of the above tasks.

The input of RPCGEN is an interface definition language (but not the IDL!) similar to the C language. It defines both the data to be transferred as well as the interface functions. The interface definition file we use for this example (comm.x) has the following form:

struct idw_arg { int neighbors; double exponent; double mea_x<>; double mea_y<>; double mea_f<>; double sam_x<>; double sam_y<>; }; struct idw_result { double sam_f<>;

Page 36: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

}; program INVRPC_PROG { version INVRPC_VERSION { int SHUTDOWN_SERVICE(void) = 1; idw_result IDW_INTERPOLATION(idw_arg) = 2; } = 1; } = 0x20011160; where idw_arg defines the argument list to be passed to the server when calling the automatically generated interface function idw_interpolation_1(). Similarly, idw_result defines the return data type of the interface function. Next, program INVRPC_PROG defines all interface functions which are realized by a given version, here INVRPC_VERSION (=1). We intend to create a server with two functions: (1) a client-controlled server shutdown SHUTDOWN_SERVICE and (2) the real interpolation method IDW_INTERPOLATION. Over the network, each function is represented by a number, SHUTDOWN_SERVICE by 1 and IDW_INTERPOLATION by 2. The program number 0x20011160 is arbitrary and can be replaced by any other 32-bit number greater than 0x20000000 and less than 0x40000000. This range is reserved for user-written RPC services. All program numbers above are reserved for transient services. (Practically, our service is rather a transient then a user-written service!)

One comment to the syntax of variable declarations:

double mea_x<100>;

specifies a vector of double values with a variable length up to 100 elements whereas

double mea_x[100];

defines a fixed size array. The variable length array transfers only the actual length of the array whereas the fixed size array would transfer all 100 elements even if only 5 are actually used. RPCGEN will expand the first notification by:

struct mea_x { u_int mea_x_len; double* mea_x_val; }; For understanding all features of RPC you will need more information as we can give here. See corresponding literature and web pages [4][5] for more.

After applying RPCGEN to the interface definition file comm.x we will get four new files:

• comm.h The header file containing all information common to client and server.

• comm_clnt.c Client stubs code providing virtual representations of the server functions for the client.

• comm_svc.c Server stubs code containing the main program and the dispatch routines for the server.

• comm_xdr.c External data representation, data serializer.

Page 37: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Prepared by this short introduction into RPC programming look now to the internals of the idw_interpolation() function:

int idw_interpolation ( IfmHandle pHdl, int mea_n, double* mea_x, double* mea_y, double* mea_f, int sam_n, double* sam_x, double* sam_y, double* sam_f, int idw_neighbors, double idw_exponent) { idw_result* result = NULL; idw_arg arg; int success = False; arg.neighbors = idw_neighbors; arg.exponent = idw_exponent; arg.mea_x.mea_x_len = arg.mea_y.mea_y_len = arg.mea_f.mea_f_len = mea_n; arg.mea_x.mea_x_val = mea_x; arg.mea_y.mea_y_val = mea_y; arg.mea_f.mea_f_val = mea_f; arg.sam_x.sam_x_len = arg.sam_y.sam_y_len = sam_n; arg.sam_x.sam_x_val = sam_x; arg.sam_y.sam_y_val = sam_y; if (!client) create_rpc_client (pHdl); if (client) { do result = idw_interpolation_1 (&arg, client); while (retry (pHdl, (void*)result, "idw_interpolation")); } if (result) { if (result->sam_f.sam_f_len == sam_n) { memcpy (sam_f, result->sam_f.sam_f_val, sam_n*sizeof (double)); success = True; } xdr_free ((xdrproc_t)xdr_idw_result, (caddr_t)result); } return success; } As before let's explain the new features in this routine step by step:

arg.neighbors = idw_neighbors; arg.exponent = idw_exponent; arg.mea_x.mea_x_len = arg.mea_y.mea_y_len = arg.mea_f.mea_f_len = mea_n; arg.mea_x.mea_x_val = mea_x; ... Since the interface function idw_interpolate_1() generated by RPCGEN carries only one data argument we have to fill the structure idw_arg with all arguments passed to idw_interpolate(). The length members mea_x_len, etc. have to be set to the same value.

Page 38: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

if (!client) create_rpc_client (pHdl); Here we reference to a variable named client at first time. This is a global variable since this example handles only one service. If client is NULL we have to create firstly a connection to the server. This is part of create_rpc_client() which will be described below.

if (client) { do result = idw_interpolation_1 (&arg, client); while (retry (pHdl, (void*)result, "idw_interpolation")); } While realizing a function over a network you should be always aware that something can go wrong. Therefore, a good and sure error handling is of fundamental importance. The above construction does the most for you with appropriate dialogs through the IFM. You can copy it into each interface function you intend to handle. Change simply the name of the interface function (here idw_interpolation_1) and the last argument to retry().

Each interface function returns NULL when an error occurs. Retry() checks the result and , on error, raises an appropriate message box and asks the user to Retry or Abort the current operation. When the user selects Abort retry() return False and the loop will be left. On successful completion retry() returns False immediately to leave the loop.

if (result) { if (result->sam_f.sam_f_len == sam_n) { memcpy (sam_f, result->sam_f.sam_f_val, sam_n*sizeof (double)); success = True; } xdr_free ((xdrproc_t)xdr_idw_result, (caddr_t)result); } As mentioned, on Abort the loop is left with a NULL pointer in result. Thus, you have to check result before attempting to analyze the result. In our case we have a further condition at a successful call: the number of returned sample values has to be the same as the number of sample point coordinates transferred to the server. If they match we can copy the result to the sam_f argument and free the memory allocated by the interface function.

Finally, we have to show the rest of the implementation. This is widely module-independent and can be copied to other projects as well. The only work you have to do is specifying the macros SVC_NAME, RPC_PROG and RPC_VERS accordingly:

#define SVC_HOST "localhost" static char svc_host[] = SVC_HOST; static char svc_spec[] = SVC_NAME "@" SVC_HOST; static CLIENT* client; static struct timeval timeout = {15, 0}; /* 15 seconds */ Create a new RPC client. Currently, we handle only one client connection to the server:

static int create_rpc_client (IfmHandle pHdl) { destroy_rpc_client (pHdl);

Page 39: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* * Create client in a loop for "retry" */ do { client = clnt_create (svc_host, RPC_PROG, RPC_VERS, "tcp"); /* * Before alerting the user try to start SVC_NAME */ if (!client) { int i; char *t, cmd[MAXPATHLEN]; IfmGetComponentPath (pHdl, cmd, sizeof (cmd), SVC_NAME)); system (strcat (cmd, "&")); for (i = 0; i < 5 && !client; i++) { sleep (1); client = clnt_create (svc_host, RPC_PROG, RPC_VERS, "tcp"); } } if (!client) { switch (IfmAlert (pHdl, NULL, " Retry | Abort ", clnt_spcreateerror (svc_spec))) { case 1: break; case 2: return False; } } else clnt_control (client, CLSET_TIMEOUT, (char*)&timeout); } while (!client); return True; } In the following function we shut down the server and destroy the RPC client handle.

static void destroy_rpc_client (IfmHandle pHdl) { if (client) { shutdown_service (pHdl); clnt_destroy (client); client = NULL; } }

Page 40: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

In case of network errors the RPC client call will produce a NULL pointer. This function raises a message box with the error and asks the user to retry the current call.

static int retry (IfmHandle pHdl, void* pResult, const char* proc) { if (!pResult) { switch (IfmAlert (pHdl, NULL, " Retry | Abort ", clnt_sperror (client, proc))) { case 1: return create_rpc_client (pHdl); case 2: destroy_rpc_client (pHdl); return False; } } return False; } The function below forces the server's self-termination.

static int shutdown_service (IfmHandle pHdl) { int* result = NULL; if (client) { result = shutdown_service_1 (NULL, client); } return result ? *result : 0; }

Page 41: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

5 References [1] Adrian Nye, et. al.: The Definitive Guides to the X Window System (Part 1-6)

O'Reilly & Associates, Inc., 1996

[2] OSF/Motif™ Programmer's Reference Revision 1.2 Prentice Hall, Englewood Cliffs, New Jersey 07632, 1993

[3] Hummingbird Communications Ltd., X-Server for Windows NT and Windows 95 http://www.hcl.com

[4] RFC 1057: RPC: Remote Procedure Call Protocol Specification Version 2 http://www.globecom.net/(nobg,sv)/ietf/rfc/rfc1057.shtml

[5] RFC 1014: XDR: External Data Representation Standard http://www.globecom.net/(nobg,sv)/ietf/rfc/rfc1014.shtml

[6] OpenLook in Sun’s Online Documentation http://docs.sun.com

[7] D.F. Watson: Contouring - A Guide To The Analysis And Display Of Spatial Data Elsevier Science Ltd., 1992

Page 42: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

Appendix A – Inverse distance weighing method In our examples, we have considered a 2D-interpolation of irregular points based on the Inverse Distance Weighting method:

• considering only values at the measure points, • without considering gradients at the measure points, • without any trend analysis.

For every sample point (where the interpolated value will be computed) a number N of next-neighbored measure points will be searched. The value fi at the sample point will be computed from the values fd of the measure points using a weighted summation:

fi = w[1]*fd[1] + w[2]*fd[2] + ... + w[N]*fd[N]

with weighting factors w[i] computed from the distances s[i] between the sample point and neighbored measure point i and the exponent e according to:

-e s[i] w[i] = ______________________________ -e -e -e s[1] + s[2] + ... + s[N]

Page 43: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

A.1 Implementation in C

/************************ Copyright (C) WASY Ltd. 1997 ******************** * * FEFLOW * interactive graphics-based Finite Element simulation * system for subsurface FLOW and transport processes * *------------------------------------------------------------------------- */ #include <stdio.h> #include <math.h>

/*------------------------------------------------------------------------- ** idw_interpolation - 2D-Interpolation of irregular points ** based on the IDW method */ void idw_interpolation (num_measure, xd, yd, fd, num_sample, xi, yi, fi, idw_neighbors, idw_exponent) int num_measure; /* Number of measure points */ double *xd, *yd; /* Measure point coordinates */ double *fd; /* Measure point values */ int num_sample; /* Number of sample points */ double *xi, *yi; /* Sample point coordinates */ double *fi; /* Result: Interpolated sample point values */ int idw_neighbors; /* Number of neighbor points to be considered */ double idw_exponent; /* Exponent */ { int **nb_list = NULL; /* The indices of neighbored data points of interpolation point i (sorted by distance) [num_sample][idw_neighbors] */ double **nb_dist = NULL; /* Sorted distances from interpolation point i to the sorted data points in nb_list. [num_sample][idw_neighbors] */ double weight; /* The weighting factors for the idw_neighbors neighbored data points */ double sum_inv; /* The quotient of the weighting factor formula */ double xxx, yyy, distance; register int i, j, k, l; /* Allocate local arrays */ nb_list = alloc2d (num_sample, idw_neighbors, sizeof (int)); nb_dist = alloc2d (num_sample, idw_neighbors, sizeof (double)); /* Compute the sorted distances from interpolation points to data points. ** nb_dist contains the quadrate of the distances. */ /* Initialize arrays */ for (i = 0; i < num_sample; i++) { for (j = 0; j < idw_neighbors; j++) { nb_list[i][j] = INVALID_INDEX; nb_dist[i][j] = 1.e30; } } /* ** Find the next neighbored data points (xd, yd) around the current ** interpolation point (xi[i], yi[i]). */ /* Loop of the interpolation points */ for (i = 0; i < num_sample; i++) {

Page 44: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* Second loop of data points */ for (j = 0; j < num_measure; j++) { xxx = xi[i] - xd[j]; yyy = yi[i] - yd[j]; distance = xxx * xxx + yyy * yyy; if (distance < nb_dist[i][idw_neighbors-1]) { /* A new neighbor for interpolation point i. ** Sort the distances to the neighbors in nb_dist of node i ** from minimal to maximal */ for (k = 0; k < idw_neighbors; k++) { if (distance < nb_dist[i][k]) { /* Shift all distances and indices with greater distances ** to sort the distance vector nb_dist and the index array ** nb_list with increasing distances */ for (l = idw_neighbors-1; l > k; l--) { nb_dist[i][l] = nb_dist[i][l-1]; nb_list[i][l] = nb_list[i][l-1]; } /* Set the current neighbor and distance */ nb_dist[i][k] = distance; nb_list[i][k] = j; break; } } } } } /* ** If only one neighbor considered (idw_neighbors == 1) ** do not analyze distances */ if (idw_neighbors == 1) { for (i = 0; i < num_sample; i++) fi[i] = fd[nb_list[i][0]]; } else { for (i = 0; i < num_sample; i++) { sum_inv = 0.0; /* The divisor of the weighting coefficient formula */ fi[i] = 0.0; /* Reset the interpolation value */ for (j = 0; j < idw_neighbors; j++) { /* Exponential operation and inverse building of the distances. ** Because of the distances are computed as quadratic distances, ** the nb_dist's idw_exponent should by idw_exponent*0.5 to ** retrieve the correct distances. This is only useful for ** idw_exponent's != 2.0. */ if (idw_exponent != 2.0) nb_dist[i][j] = pow (nb_dist[i][j], idw_exponent * 0.5); /* Compute inverse distances and quotient */ if (nb_dist[i][j]) nb_dist[i][j] = 1.0 / nb_dist[i][j]; else nb_dist[i][j] = 1.0e32; sum_inv += nb_dist[i][j]; } if (!sum_inv) sum_inv = 1.0 / DEFAULT_DISTANCE;

Page 45: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

/* Perform interpolation by sum the weighted values of the ** data points */ for (j = 0; j < idw_neighbors; j++) { weight = nb_dist[i][j] / sum_inv; fi[i] += weight * fd[nb_list[i][j]]; } } } /* Free local arrays */ free2d (nb_list); free2d (nb_dist); } /* ** Two dimensional memory allocation ** - Access to elements: ptr[y][x] */ static void* alloc2d (int rows, int cols, int size) { char **p1, *p2; int n; if (rows <= 0 || cols <= 0 || size <= 0) return NULL; p1 = (char**)malloc (rows * sizeof(char*)); p2 = (char*)calloc (rows*cols, size); if (!p1 || !p2) { fprintf (stderr, "out of memory!\n"); exit (1); } size *= cols; /* size of a row */ for (n = 0; n < rows; n++, p2 += size) p1[n] = p2; return (void*)p1; } /* ** Free matrix allocated with alloc2d */ static void free2d (void* p) { if (p) { free (*(void**)p); free ((void*)p); } }

Page 46: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

A.2 Implementation in FORTRAN

SUBROUTINE IDW_INTERPOLATION (num_measure, xd, yd, fd, num_sample, * xi, yi, fi, idw_neighbors, idw_exponent) INTEGER*4 num_measure ! Number of measure points REAL*8 xd(*), yd(*) ! Measure point coordinates REAL*8 fd(*) ! Measure point values INTEGER*4 num_sample ! Number of sample points REAL*8 xi(*), yi(*) ! Sample point coordinates REAL*8 fi(*) ! Result: Interpolated sample point values INTEGER*4 idw_neighbors ! Number of neighbor points to be considered REAL*8 idw_exponent ! Exponent INTEGER*4,ALLOCATABLE::nb_list(:,:) ! The indices of neighbored data points of ! interpolation point i (sort by distance) ! [num_sample][idw_neighbors] REAL*8,ALLOCATABLE::nb_dist(:,:) ! Sorted distances from interpolation point i ! to the sorted data points in nb_list. ! [num_sample][idw_neighbors] REAL*8 weight ! The weighting factors for the idw_neighbors ! neighbored data points REAL*8 sum_inv ! Quotient of weighting factor formula REAL*8 xxx, yyy, distance INTEGER*4 i, j, k, l C Allocate local arrays ALLOCATE (nb_list(num_sample, idw_neighbors)) ALLOCATE (nb_dist(num_sample, idw_neighbors)) C Compute the sorted distances from interpolation points to data C points. nb_dist contains the quadrate of the distances. C Initialize arrays DO i=1,num_sample DO j=1,idw_neighbors nb_list(i,j) = -1; nb_dist(i,j) = 1.e30; END DO END DO C Find the next neighbored data points (xd, yd) around the current C interpolation point (xi[i], yi[i]). C Loop of the interpolation points DO i=1,num_sample C Second loop of data points DO j=1,num_measure xxx = xi(i) - xd(j) yyy = yi(i) - yd(j) distance = xxx * xxx + yyy * yyy IF (distance .LT. nb_dist(i,idw_neighbors)) THEN C A new neighbor for interpolation point i. C Sort the distances to the neighbors in nb_dist of node i C from minimal to maximal DO k=1,idw_neighbors IF (distance .LT. nb_dist(i,k)) THEN C Shift all distances and indices with greater distances C to sort the distance vector nb_dist and the index array C nb_list with increasing distances DO l=idw_neighbors,k+1,-1

Page 47: Interface Manager for Programmers 1. Programming Tool KitInterface Manager for Programmers 1. Programming Tool Kit Understandably, there is a natural caution before someone attempts

nb_dist(i,l) = nb_dist(i,l-1); nb_list(i,l) = nb_list(i,l-1); END DO C Set the current neighbor and distance nb_dist(i,k) = distance; nb_list(i,k) = j; GOTO 1 END IF END DO 1 CONTINUE END IF END DO END DO C If only one neighbor considered (idw_neighbors == 1) C do not analyze distances IF (idw_neighbors .EQ. 1) THEN DO i=1,num_sample fi(i) = fd(nb_list(i, 1)) END DO ELSE DO i=1,num_sample sum_inv = 0.0 ! Divisor of the weighting coefficient formula fi(i) = 0.0 ! Reset the interpolation value DO j=1,idw_neighbors C Exponential operation and inverse building of the distances. C Because of the distances are computed as quadratic distances, C the nb_dist's idw_exponent should by idw_exponent*0.5 to C retrieve the correct distances. This is only useful for C idw_exponent's != 2.0. IF (idw_exponent .NE. 2.0) THEN nb_dist(i,j) = nb_dist(i,j) ** (idw_exponent * 0.5) END IF C Compute inverse distances and quotient IF (nb_dist(i,j) .NE. 0.) THEN nb_dist(i,j) = 1. / nb_dist(i,j) ELSE nb_dist(i,j) = 1.e32 ENDIF sum_inv = sum_inv + nb_dist(i,j) END DO IF (sum_inv .EQ. 0.) THEN sum_inv = 1.e-30 END IF C Perform interpolation by sum the weighted values of C the data points DO j=1,idw_neighbors weight = nb_dist(i,j) / sum_inv; fi(i) = fi(i) + weight * fd(nb_list(i,j)) END DO END DO END IF C Free local arrays DEALLOCATE(nb_list, nb_dist) END SUBROUTINE