Calling C Code From MML

Introduction

This document describes the process of defining and using C procedural code within MML models. The MML mechanisms used are called functions and procedures (F&P). The functionality described here is somewhat problematic under Windows.

Prerequisites:

Overview

C code can be called from MML via either source or native F&P. In both cases, the C code must comform to The JSim C API .

Source F&P embed C source code within MML which is compiled at model build time. This method is the simplest to write and understand, is fairly portable between architectures and is appropriate for modest sized F&P. Using Source F&P requires a C compiler be configured in your JSim installation. See Customizing a JSim Installation for instructions on doing this, or pass the information along to your system administrator. Note that this customization is problematic under Windows.

Native F&P link to pre-compiled C native libraries. This method is somewhat more complicated than source F&P, but may be useful if:

  1. you wish to distribute a model to a collaborator who does not have access to a C compiler;
  2. your C code base is large enough that you don't wish to recompile /relink each time your model builds.

Native libraries required by a model are not saved in model or project files, and so must to separately distributed to collaborators. Note that native libraries are not portable between different computer architectures and operating systems, so what runs for you, may not run for him.

This document shows several examples of C source F&P. Afterward, the same examples are done using native F&P.

  • Example 1 - a scalar C source function;
  • Example 2 - a non-scalar C source function;
  • Example 3 - a C source procedure.
  • Example 4 - a native reworking of example1;
  • Example 5 - a native reworking of examples 2 & 3;
  • Comments or Questions?
    Give feedback

Example 1

In the following example, a C source function "gmean" is defined that accepts two scalar arguments and returns their geometric mean. The main program defines a variable v(t) as the geometric mean of t and t^3, which should thus be equal to t^2:

// C source function computing geometric mean
//   note: requires C compiler installed, will not run in applet
source real function gmean(a, b) {
  language="C";
  maincode={{
    double aval = a->realVal[0];
    double bval = b->realVal[0];
    JSIM_RETURN(sqrt(aval*bval));
  }};
}

math main {
  realDomain t;
  t.min=0; t.max=5; t.delta=1;
  real v(t) = gmean(t, t^3);
}

The first paragraph comprises the gmean function declaration. The keyword "source" indicates that the function will be defined in terms on source code within MML. The keyword "real" indicates that the function returns a real number. (Currently, real is the only supported function data type. Other data types may be supported in the future.) The name "gmean" follows the keyword "function", giving a unique name to this function. Function names must follow standard MML naming conventions. The argument list following the function name indicates this function takes exactly two scalar arguments. (Non-scalar arguments use the @ directive, described later in this document).

The "language" clause inside the function declaration indicates the procedural code will follow the JSim C Model API . The "maincode" clause contains the C procedural code. Conventions for this code are fully described in The JSim C API . In brief, the modeler specified maincode block is copied into a double returning C function, whose calling arguments are JSimArg structure pointers named a and b. JSimArg has a double array realVal, whose 0th element is the value for a scalar argument. The JSIM_RETURN statement is required when returning from a function in order to clean up temporary memory allocation. It's omission will cause memory leaks.

� The second paragraph comprises the main line of the MML model. In this case, gmean will be called once for each t. Although only a single equation using gmean is present in this example, multiple uses of gmean, including nested uses, are supported. It should be noted that incautious use of static variables within maincode can break this nesting ability.

Example 2

The first example is trivial because the desired functionality could more easily be obtained simply using MML. This is the case for most situations involving scalar arguments. Function and procedures become more useful when passing multi-dimensional arguments because the looping constructs within Java are more general than those available in MML.

In the following example, one-dimensional integration is implemented as a java source function:

// C function implementation for 1D integral
//   requires installed C compiler,  will not run in applet
source real function cintegral(a@t) {
  language = "C";
  maincode={{
    int ct, i, mult;
    double tot = 0;
    JSimGrid *t = a->grids;
    ct = t->ct;
    for (i=0; i<ct; i++) {
      mult = (i==0 || i==(ct-1)) ? 1 : 2;
      tot += a->realVal[i]*mult;
    }
    tot *= (t->max - t->min) / (2 * (t->ct - 1));
    JSIM_RETURN(tot);
  }};
}

math main {
  realDomain t;
  t.min=0; t.max=5; t.delta=1;
  real u(t) = t*t;
  real v = cintegral(u@t);
}

The MML @ construct is used here twice. In the function declaration, u@t indicates that the single function argument must be one-dimensional. When the function is called u@t, which should be read "u for all t", indicates that u values for all t should be passed to integral. If a function's declared argument dimensions do not match the arguments passed at call time, MML will alert the modeler at compile time.

JSimArg have a JSimGrid array grids, which provides access to the associated data grid(s) of a multidimensional variable. Since the function argument is not scalar, realVal array access is done at various grid points. Again, see the JSim C API for further information.

Multiple @ constructs can be used for higher dimensional function arguments.

Example 3

In the following example a procedure, rather than a function is demonstrated. The procedure "reverse" takes a one-dimensional input argument, reverses the order of the data, and places the result in a one-dimensional output argument:

// C function for reversal of time series
//   requires installed C compiler,  will not work in applet
source procedure reverse(u@t; v@t) {
  language="C";
  maincode={{
    int i;
    int ct = u->grids[0].ct;
    for (i=0; i<ct; i++)
      v->realVal[i] = u->realVal[ct-1-i];
    JSIM_RETURN();
  }};
}

math main {
  realDomain t;
  t.min=0; t.max=6; t.delta=2;
  real u(t) = t*t;
  real v(t);
  reverse(u@t, v@t);
}

The keywords "C" and "procedure" indicate that the embedded code block(s) will follow the JSim C Model API for procedures. Most comments above regarding functions apply equally to procedures, with a few differences noted below.

The procedure argument list separates input arguments from output arguments with a semi-colon, with inputs listed first. This example has one input and one output argument.

The maincode block returns no value as is appropriate for a procedure, but the JSIM_RETURN() statement is still required for freeing allocated memory.

Example 4

The following is a reworking of source example 1 as a native C function. The model file looks like this:

// native function for geometric mean
//   requires C compiler, mylib library,  will not run in applet
native real function gmean(a, b) {
  language="C";
  library="mylib";
  name="mygmean";
}

math main {
  realDomain t;
  t.min=0; t.max=5; t.delta=1;
  real v(t) = gmean(t, t^3);
}

The function declaration specifies that the external library mylib will contain a double-returning function mygmean conforming to the JSim C Model API. The "language" clause indicates that JSim should link to the function using your operating system's conventions for the C language. The code for mylib.c is as follows:

// C library for use with nmean.mod
#include "jsimapi.h"

JSIM_REAL_FUNCTION(mylib,mygmean) {
  JSIM_INIT();
  JSimArg *a = JSIM_ARG(0);
  JSimArg *b = JSIM_ARG(1);
  double aval = a->realVal[0];
  double bval = b->realVal[0];
  JSIM_RETURN(sqrt(aval*bval));
}
Download Code261 bytes

jsimapi.h, distributed with JSim, defines the structures and macros you'll need. JSIM_REAL_FUNCTION() expands to a function header for mygmean. Due to Java's JNI mechanism for calling C, the name of the library in which the function will reside must be specified. JSIM_INIT performs certain initialization operations, and JSIM_ARG() returns pointers to the allocated arguments. The remaining three lines are the same code that was used in source example 1.

Before the model can be run, mylib.c must be compiled into a shared library appropriate for your system. The simplest way to do this is use the script installed when customizing JSim for C compilation (see Overview above). For example, on a Linux system with the C compile script installed in $JSIMHOME/local (one of several options), the command would be:

$JSIMHOME/local/jscompile_c $JSIMHOME $PWD mylib

This creates the library file libmylib.so under Linux. Other operating systems have other native library naming conventions. The library should be placed at an convenient location within JSIMPATH so that it can be found at model run time. See JSim User Reference Manual for information on JSIMPATH.

Building large multi-module native libraries requires great expertise in both C compiling and linking issues. Such details are beyond the scope of this document. The JSim C compile script cannot be used directly for such large projects, but experts may easily analyze the script to extract appropriate details.

Example 5

Native libraries may contain one or more entry points for JSim F&P. Below, we combine the code for examples 2 and 3 above into a single model and library (newlib). The model is as follows:

// native F&P example
//   requires installed C compiler, newlib library, will not run in applet
native real function cintegral(a@t) {
  language = "C";
  library = "newlib";
  name = "cintegral";
}

native procedure reverse(a@t; b@t) {
  language = "C";
  library = "newlib";
  name = "crev";
}

math main {
  realDomain t;
  t.min=0; t.max=4; t.delta=1;
  real u(t) = t^2;
  real v = cintegral(u@t);
  real w(t);
  reverse(u@t, w@t);
}

The code for newlib.c is as follows:

// C library for use with ncombo.mod
#include "jsimapi.h"

JSIM_REAL_FUNCTION(newlib,cintegral) {
  JSIM_INIT();
  JSimArg *u = JSIM_ARG(0);

  int ct, i, mult;
  double tot = 0;
  JSimGrid *t = u->grids;
  ct = t->ct;
  for (i=0; i<ct; i++) {
    mult = (i==0 || i==(ct-1)) ? 1 : 2;
    tot += u->realVal[i]*mult;
  }
  tot *= (t->max - t->min) / (2 * (t->ct - 1));
  JSIM_RETURN(tot);
}

JSIM_PROCEDURE(newlib,crev) {
  JSIM_INIT();
  JSimArg *u = JSIM_ARG(0);
  JSimArg *v = JSIM_ARG(1);
  int i;
  int ct = u->grids[0].ct;
  int ct1 = v->grids[0].ct;
  if (ct != ct1)
    JSIM_ERROR("argument grid lengths differ");
  for (i=0; i<ct; i++)
    v->realVal[i] = u->realVal[ct-1-i];
  JSIM_RETURN();
}
Download Code702 bytes

Note that JSIM_PROCEDURE is used instead of JSIM_REAL_FUNCTION for crev. Also note the use of JSIM_ERROR to alert the user to mis-matched array lengths.

Comments or Questions?

Give feedback

Model development and archiving support at https://www.imagwiki.nibib.nih.gov/physiome provided by the following grants: NIH U01HL122199 Analyzing the Cardiac Power Grid, 09/15/2015 - 05/31/2020, NIH/NIBIB BE08407 Software Integration, JSim and SBW 6/1/09-5/31/13; NIH/NHLBI T15 HL88516-01 Modeling for Heart, Lung and Blood: From Cell to Organ, 4/1/07-3/31/11; NSF BES-0506477 Adaptive Multi-Scale Model Simulation, 8/15/05-7/31/08; NIH/NHLBI R01 HL073598 Core 3: 3D Imaging and Computer Modeling of the Respiratory Tract, 9/1/04-8/31/09; as well as prior support from NIH/NCRR P41 RR01243 Simulation Resource in Circulatory Mass Transport and Exchange, 12/1/1980-11/30/01 and NIH/NIBIB R01 EB001973 JSim: A Simulation Analysis Platform, 3/1/02-2/28/07.