External libraries

From ASCEND
Jump to: navigation, search

ASCEND supports external libraries (you might call them 'plugins') of a number of different kinds. Specifically,

  • external solvers (for solving NLA and possibly other types of problems such as MILP or MINLP)
  • external integrators (for solving ODE/DAE problems)
  • external methods (that can be called from inside METHODS using the EXTERNAL command)
  • external relations (in other words, equations that are calculated using code outside ASCEND)
  • external import handlers (more on this later)

Currently all solvers and integrators for ASCEND are implemented as external libraries, as well as a number of external methods including a wrapper for QRSlv and a Brent solver for 'homing in' on a solution to a single variable in otherwise hard-to-solve cases. There are also external libraries for calculation of steam properties (see [1]), properties of other fluids (see FPROPS) and for modelling of heat transfer in long-pipe flow boiling (ask John Pye).

Some other external libraries are under development including one for interpolating time-series data for use in simulations with forcing data.

For large array-based simulations, external libraries may permit faster solution by eliminating unnecessary iteration by ASCEND and instead performing the iteration inside lower-level code.

External methods

External methods can be used to implement procedural-style changes to your model via the EXTERNAL statement. This technique is used internally by ASCEND to implement the ClearAll method (see models/basemodel.a4l. Here is the syntax to call an load and call an external method in a simple model:

IMPORT "mylibrary"; (* load libmylibrary.so or mylibrary.dll *)

MODEL mymodel;
        ...
METHODS
    METHOD dosomething;
        ...

        (* run a method from mylibrary... *)
        EXTERNAL myexternalmethod(SELF);
        RUN values; (* combine the EXTERNAL call with other statements as you wish *)

    END dosomething;
    METHOD values;
        ...
    END values;

END mymodel;

The IMPORT statement is used to specify how to load your external library; it is required that your external library contains a registration function that will tell ASCEND what external methods your library is providing.

External relations (aka 'black box' relations)

External relations are implemented as blocks of equations that are evaluated in external code. This means that you pass a set of 'input' variables, and the external code evaluates the resulting values of the 'output' variables. In addition, there is a provision for a 'data' object to be passed to the code. This data object should be constant-valued, so it can contain values that the external code won't be changing.

To make use of an external relation (for example myextrel ) as declared in your library mylibrary, you use syntax like this:

IMPORT "mylibrary"; (* ASCEND will try to load libmylibrary.so or mylibrary.dll depending your platform *)
...

x IS_A real;
A IS_A real; (* etc *)
eta IS_A real_constant;

data IS_A mymodel_data;

(* use a 'black box relation' from mylibrary... *)
my_external_relation: myextrel(
         x, y, z : INPUT;

         A, B : OUTPUT;
         eta : DATA
);

...

How external relations are used by ASCEND

External relations are solved to completion in the external code. When implementing your external relation, you should select your OUTPUT variables such that they can be calculated with certainty in terms of the INPUT and DATA variables. You can design your code to include its own internal iteration or solver procedure if necessary.

When a black box is added your model, it creates a 'relation' object for each of your OUTPUT variables, and makes this relation incident with its output variable as well as with all of the INPUT variables for the black box:

Incidence matrix for a model containing a single 'black box' external relation containing 3 outputs and 9 inputs.

When ASCEND reaches a block that contains external relations, it may find that all the INPUT variables are already known, in which case a single call the the external relation will be sufficient to determine the values of the OUTPUT variables. On the other hand, it may be that the values for some of the OUTPUT variables are already known (having been solved earlier in the solver process) and some of the INPUT variables are yet to be determined. In this case, ASCEND will use its standard Newton solver algorithm to home in on a solution for those variables. The external relation API provides the means for you to give a Jacobian matrix for the output variables relative to the input variables, which can then be used to improve the accuracy of the Newton solver. If you don't provide these derivatives, however, ASCEND will compute finite difference approximations by perturbing the values of the INPUT variables to estimate the Jacobian. Obviously this can result in many excess calls your your external relation evaluation function, so you will need to work out whether or not it is worth providing the derivative evaluation functions as well.

DATA instance

The DATA instance (see above example) is a mechanism whereby an arbitrary set of configuration data can be passed to the external relation when it is first set up.

DATA parameters are intended to convey fixed, constant values to the external relation. The best way to use these is to set up a MODEL containing various constant values:

MODEL myext_data;

    myvalue IS_A constant;
    myvalue :== 3.45 {m};
END myext_data;

These values can then be obtained in the external library code using (here C++) the following. The 'data' variable is passed in to the BBoxInitFn.

symchar *myvalue_sym = AddSymbol("myvalue");
struct Instance *myvalue_inst = ChildByChar(data,myvalue_sym);

if(!myvalue_inst)throw runtime_error("Instance 'myvalue' is required in DATA");
double myvalue = RealAtomValue(myvalue_inst);

Note that this is intended to be a read-only mechanism; there is no guarantee, if you attempt to 'hold on' to pointers into this DATA instance, that those instances will still be there later on.

See also a more detailed example about external relations.

External solvers

All ASCEND solvers are implemented as external libraries. This includes QRSlv as the default NLA solver, CONOPT as the standard optimisation solver, plus CMSlv as the conditional solver. See Solvers for more information.

Default solvers are loaded when ASCEND starts. To use other solvers, you must add an IMPORT statement at the start of your model file to trigger loading of the necessary shared library. Note the ASCENDSOLVERS environment variable can be used to located where these shared libraries are located, if necessary.

Somewhat related: we have implemented an experimental Goal seeking solver using the EXTERNAL method mechanism. This is suitable for implementing a 'shooting method' with an ASCEND model, for cases where a complicated model (for example one with some difficult feedback) doesn't converge when using the standard QRSlv solver.

See also External Solvers.

External integrators

Support for external integrators has also been implemented. This means that any new integrator your create can be loaded dynamically with the IMPORT statement. For example we provide DOPRI5:

IMPORT "dopri5";

For non-standard integrators, this additional IMPORT statement is required in order to for ASCEND to load the integrate. Standard integrators IDA and LSODE are always loaded, by default. The integrator will then be available in the list when you open the 'Integrate' dialog.

See also External Integrators.

External import handlers

In a pleasing coup de récursion, one can implement 'handlers' for importing other types of external libraries using the external library mechanism itself. For example, we have the ExtPy shared library, which, if IMPORTed, allows one to then subsequently IMPORT external libraries defined by Python scripts. This mechanism is still somewhat hackish but has nevertheless proven useful in automating some modelling tasks, particularly when using the PyGTK GUI.

See also Import Handlers.

External CALLs

This seems to be a fossil in the code. There is some provision for 'CALL' statements that do something with external code. Not sure still what that does. This might be the start of a system for performing calls to functions with arguments from ASCEND METHODs, which is currently not implemented in general.

Writing an external library

We will assume you are implementing a 'native' external library, in other words one that loads from a shared library (.so or DLL file depending on your platform).

You will create a source code files extfn.c that will be complied into a shared library libextfn.so or a DLL extfn.dll depending on your platform.

Your source code needs file to contain a registration function named with the stem of the library name (extfn) with the added text _register added at the end. This will enable ASCEND to automatically discover the registration function for your external library.

Your *_register function will in turn contain one or more calls to the ASCEND CreateUserFunction function. These calls will register your external methods and functions, allowing them to be called from your .a4c file. Here is an example of a registration function:

extern int
DLEXPORT mylibrary_register(struct Slv_Interp *dummy1, struct Instance *root
                ,struct gl_list_t *arglist, unsigned long dummy4

){
    const char *addone_help = "This function will return one-plus-its-input-argument";
    int result;

    result = CreateUserFunction("add_one"
            ,(ExtEvalFunc *)addone_prepare
            ,(ExtEvalFunc **)addone_calc
            ,(ExtEvalFunc **)NULL
            ,(ExtEvalFunc **)NULL
            ,1, 1, addone_help
    );

    return result;
}

Each call to CreateUserFunction can offer a 'preparation' function (which can be used to pre-calculate fixed data tables and so on), an 'evalation' function (which will return your function value, plus first and second derivative functions.

In order to compile your source code, you should use a SCons build file based on the example one given in models/johnpye/extfn. Note the useful ascend-config script that can help in determing your linker and compiler flags.

Your sourcecode must contain (one or more functions like) the mylibrary_register that you referenced in your IMPORT statement. This function must contain one or more calls to CreateUserFunction which will tell ASCEND about the functions you are offering in your shared library.

There are examples of such code in models/johnpye/extfn in our model library.

An important concept is the ImportHandler. This is a mechanism whereby you can define new types of external functionality to your model, to be accessed by the IMPORT statement. This means that the IMPORT statement is overloaded and can be set up to import anything you like, so long as ASCEND has been told how to deal with it.

See also Writing ASCEND external relations in C.

Freesteam

The open source steam properties library freesteam (http://freesteam.sf.net) includes bindings to ASCEND via the external 'black box' system. There is a windows installer. Once installed, you need to edit you .ascend.ini file so that the directory c:\Program Files\freesteam\ascend is included in your librarypath. Freesteam works equally well under Linux, although there are no binary packages at this stage.

A transient simulation of the equilibrium of two masses of water, showing phase change. The steam properties are calculated using freesteam. The integration was performed using IDA.