Introducing Adams Solver Libraries
Custom Adams Solver libraries let you use your user-written subroutines with Adams Solver. User-written subroutines let you tailor your template-based product to your needs and go beyond the functionality provided with Adams Solver and the specific solver functionality delivered with your product. For general information about subroutines, see:
For example, you want to create a force element that represents an aircraft landing-gear damper, then you could create a custom Adams Solver library to define the calculations used to produce that force. Inputs to the damper might include damper velocity, damper displacement, and operating temperature. Output could be force.
To use your custom Adams Solver subroutine, you first compile your C or FORTRAN code. You then link those files into an Adams Solver library (*.dll or *.so (on Linux)). The resulting file is stored in a location defined by either MDI_ACAR_SITE or MDI_ACAR_PRIVATE_DIR. When you invoke the site or private solver, the library is automatically loaded and the functions become available (dispatched using the auto-generated code in dispatch.f). A statement in the solver dataset, for the aircraft damper mentioned above, may look like this:
SFORCE/1
,TRANSLATIONAL
,I = 1
,J = 2
,FUNCTION = USER (2001, 1, 2, 3)
where:
■2001 is the branch ID of your sfo subroutine (your function in your subroutine would be sfo2001(par, npar)
■1 is the ID of the variable measuring damper_velocity
■2 is the ID of the variable measuring damper_displacement
■3 is the ID of the variable measuring temperature
This function would provide the force to the damper element. Assuming that a subroutine named sfo2001 is included in your custom solver code, the general dispatcher will call this subroutine, pass information to the subroutine, and return the force from the subroutine.
Learn more about Adams Solver libraries:
Requirements for Creating Custom Libraries
To be able to create Adams View or Adams Solver libraries, you must meet the following requirements:
■To link Adams Solver (run-time) routines, you must have a FORTRAN or C compiler.
■To link Adams View (design-time) routines, you must have a C compiler.
You can input C or FORTRAN source files to create Adams Solver user libraries and only C source files to create Adams View user libraries. You must compile these subroutines before linking them with an Adams product. See the hardware and software specifications in the Installation Guide that comes with your Adams product for the type of compiler you need (
https://simcompanion.hexagon.com/customers/s/article/Adams-Doc-2022-Release). Also refer to your compiler documentation for details.
Therefore, you can write an Adams Solver routine in FORTRAN or C, but keep in mind that:
■The solver subroutine is expected to be in FORTRAN.
■You must have the linkers from the FORTRAN compilers to create a library, even if the subroutine is in C.
Creating and Using Adams Solver Libraries
Before writing user subroutines, make sure you understand the concepts presented in
Introducing Adams Solver Libraries and
Requirements for Creating Custom Libraries, and more importantly the requirements for the Adams Solver dataset and the naming conventions for the subroutines and files containing subroutines. To review some of those concepts, assume that you have an Adams Solver statement similar to the following:
REQUEST/1, FUNCTION = USER(123, ..., ...)
Then, you would need to generate a file named REQ123.f, which would contain a subroutine named req123. For example:
C Request subroutine to calculate Yaw angle
subroutine req123 (par, npar)
If you follow this convention, generating your own custom libraries should be easy.
To generate a custom library:
1. Make a list of files that you want to include in your custom Adams Solver library.
2. Save this list in a text file named sub_list.lst. The following is an example list:
req123
sfo456
con789
3. Build your custom Adams Solver Library:
■On Windows:
■From the Start button, point to Programs, point to Adams 2024.1, point to your template-based product (for example, Adams Car), and then select Advanced.
■At the prompt, type cr-solverprivate, and then press Enter.
■Press Enter again to select the default, which tells the compiler not to build debug libraries.
■Type @sub_list.lst to provide the list of files you want to include in your custom Adams Solver library.
■Press Enter.
■On Linux:
■At the prompt, type adams2024_1 -c acar cr-solverprivate, and then press Enter.
■Press Enter again to select the default, which tells the compiler not to build debug libraries.
■Your template-based product displays the compiler options for the C and FORTRAN compilers. Make a note of the options.
■Type @sub_list.lst to provide the list of files you want to include in your custom Adams Solver library.
■Press Enter.
4. Alternatively, from the command line you can create your custom library by typing the following command:
adams2024_1 -c acar cr-solverpr n @sub_list.lst -n
Notes: | ■Adams Car writes the resulting acarsolver.dll (the name of the file is dependent on your template-based product) to the directory defined by the environment variable MDI_ACAR_PRIVATE_DIR. ■If you are having difficulty building your custom code, we recommend using a DOS prompt and the command adams2024_1 acar cr-solverprivate n @sub_list.lst. This allows for output to be readable. ■If you want to create a site solver library, you can replace the command cr-solverprivate with cr-solversite. Your template-based product stores the generated library in the directory defined by the environment variable MDI_ACAR_SITE. ■Your compiled files and sub_list.lst are stored in a working directory. Your template-based product does not necessarily start in that directory. If there is a mismatch between the two directories, your template-based product is unable to locate your files and will fail.To make the working directory default to the correct location, do the following: ■On Windows: ■From the Start button, point to Programs, point to Adams 2024.1, point to your template-based product (for example, Adams Car), and then right-click Advanced. ■Select Properties. ■In the Start in text box, enter the location of your files (for example, c:\temp). ■On Linux, move into the directory where sub_list.lst is located (for example, cd /usr/home/my_home/work). |
After you created the private Adams Solver library, modify an existing Adams Solver dataset to verify that your custom functions are working. You might find it helpful to write basic 'write' statements in your FORTRAN code to verify that the code is being executed.
Using the Dispatcher (GENDISP)
Adams Car has a unique utility for dealing with FORTRAN user subroutines called the generalized dispatcher or GENDISP. GENDISP automatically creates the necessary subroutines (REQSUB, SFOSUB, and so on) with all the branches to your routines as part of the linking of a library. The branching for each category of a subroutine (RESQUB, SFOSUB, and so on) are written into a single FORTRAN file named dispatch.f. In the topics that follow you will learn more about how this mechanism works.
Learn why you'll find it valuable to create subroutines and functions and how to create libraries that reference them:
Overview of GENDISP
When you write a standard REQSUB subroutine, you often need to process output in more than one way. You might have one subroutine that computes toe, camber, and caster angle, and another that computes lateral acceleration and body sideslip angle. In your dataset you may have the following requests, which reference two user subroutines to calculate toe, camber, and caster, and in the second subroutine lateral acceleration and body sideslip.
REQUEST/111 !toe, camber, caster
FUNCTION = USER(1, 1, 2, 3)
REQUEST/222 !lateral acceleration, body sideslip
FUNCTION = USER(2, 4, 5, 6)
The parameters of interest are USER(1, and USER(2, where the values 1 and 2 correspond to PAR(1) in the expression that follows. In your REQSUB, you must have logic to branch to the different subroutines as shown next:
IF (NINT( PAR(1)) .EQ. 1 ) THEN
CALL TCCREQ( ID, TIME, PAR, NPAR, IFLAG, RESULT )
ELSE IF (NINT( PAR(1)) .EQ. 2 ) THEN
CALL BSAREQ( ID, TIME, PAR, NPAR, IFLAG, RESULT )
.
.
.
In the above example, if PAR(1) is equal to 1 (as defined by REQUEST/111), then the subroutine TCCREQ is called. Likewise, if PAR(1) is equal to 2 (as defined by REQUEST/222), then the subroutine BSAREQ is called.
Each time you add a new subroutine, you must rewrite your REQSUB to call that subroutine. If your company has a standard REQSUB, you have to create a local version for yourself and then add your subroutines to it.
With the template-based products, however, if you follow a simple naming convention for your user-written subroutines, GENDISP automatically creates a REQSUB, SFOSUB, and so on, with all the branches to your routines as part of the linking of a library. GENDISP places these subroutines in an automatically generated file, named dispatch.f.
For example, if you rename the subroutine TCCREQ to REQ001 and the subroutine BSAREQ to REQ002, then GENDISP creates a REQSUB for you with branches to REQ001 and REQ002 as shown next:
IF (NINT( PAR(1)) .EQ. 1 ) THEN
CALL REQ001( ID, TIME, PAR, NPAR, IFLAG, RESULT )
ELSEIF (NINT( PAR(1)) .EQ. 2 ) THEN
CALL REQ002( ID, TIME, PAR, NPAR, IFLAG, RESULT )
.
.
.
Using GENDISP makes the generation of dispatcher routines a fully automated process, allowing you to concentrate primarily on the content of your solver subroutines.
Conventions for Using GENDISP
To use GENDISP correctly, you must do the following:
■Name your user subroutines using the convention that the first three characters must be the same as the calling user subroutine and the last three characters of the subroutine name must be numbers. For example, the subroutine called by a REQSUB, must be named REQ001, REQ002, and so on. Another example would be naming GFORCE user subroutines called from a GFOSUB names like GFO241, GFO534, and so on.
■Name your source code or object code files according to this convention. For GENDISP to work, the source code or object code files must have the same root name as the subroutine (for example, req001.f or req001.o). Note that GENDISP ignores case. Therefore, REQ001.f is the same as req001.f or ReQ001.f. GENDISP generates branches based on the root file name. GENDISP does not examine the contents of a file for names matching the convention.
■When invoking a user subroutine from your Adams model, make sure that the first user parameter matches the subroutine you want to call. For example, the following sensor statement generates a call to SEN743:
SENSOR/1, FUNC=USER(743, .......)\
Remember to always reserve the first parameter for branching when writing your user subroutines.
■Number all your routines in the 001 to 799 range (for example, REQ300 and SFO465). The range 900+ is reserved for standard Adams user subroutines.
User-Written Subroutines that GENDISP Supports
GENDISP supports the following Adams Solver user-written subroutines.
Learn about subroutines.
Adams Solver User-Written Subroutines that GENDISP Supports
Name: | Description: |
|---|
CONSUB | Control command |
COUSUB | Coupler subroutine and partial derivatives |
COUXX | Coupler first partial derivatives subroutine (CXX100) |
COUXX2 | Coupler second partial derivatives subroutine (CXD100) |
CURSUB | Curve statement |
DIFSUB | Differential equation statement |
DMPSUB | Modal damping subroutine |
FIESUB | Field statement |
GFOSUB | Gforce statement |
GSESUB | General state equation subroutine Adams Car provides generic GSEYU, GSEXU, GSEXX and GSEYX subroutines that numerically difference a GSE subroutine. The general dispatcher, however, does not provide support for the revised GSE implementation using the GSE_DERIV, GSE_UPDATE, GSE_OUTPUT, and GSE_SAMP subroutines. |
MFORCE | Modal force subroutine |
MOTSUB | Motion statement |
REQSUB | Request statement |
SENSUB | Sensor statement |
SFOSUB | Sforce statement |
TIRSUB | Tire statement subroutine (to be discontinued) |
UCOSUB | Ucon (user constraint) statement |
VARSUB | Variable statement |
VFOSUB | Vforce statement |
VTOSUB | Vtorque statement |
GENDISP supports the following Adams Car user-written subroutine types:
Adams Car Subroutine Types That GENDISP Supports
Name: | Description: |
|---|
BRASUB | Brake demand |
STRSUB | Steering demand |
THRSUB | Throttle demand |
Adams Tire now supports dynamic loading (dispatching) of tire (TYRSUB) and road contact (ARCSUB) subroutines. Support for standard driver interface (SDISUB) for lack of usage.
Adding GENDISP Support For New Subroutine Types
To add support for a new subroutine type, you modify the file dispatch.dat to add the new type. You can find a copy of dispatch.dat in the directory install_dir/product_name/dispatch.dat, where install_dir is the directory in which you installed your template-based product and product_name is acar, aengine, aircraft, or arail, according to the product you are using.
The file, dispatch.dat, is a TeimOrbit format file containing a block for each supported subroutine type and follows these rules:
■Block names are limited to three characters. For example, [MYU] is valid, but [MYUT] is not.
■Branching is always based on PAR(1) in the generated source code. So PAR must either be an array passed to the user subroutine as in REQSUB or PAR must be created using the (CODE_BEFORE_BRANCH) option as
Example of Using GENDISP.
■Code in the (CODE_BEFORE_BRANCH) sub-block is output literally to the generated source code, and must follow FORTRAN's strict formatting rules. For example, executable code on a line must be indented six spaces.
For example, the block for REQSUBs looks like:
$********************************************
[REQ]
(PARAMETERS)
{type name}
'INTEGER' 'ID'
'DOUBLE PRECISION' 'TIME'
'DOUBLE PRECISION' 'PAR(*)'
'INTEGER' 'NPAR'
'LOGICAL' 'IFLAG'
'DOUBLE PRECISION' 'RESULT(8)'
$********************************************
With this block in dispatch.dat and given a list of files names containing req900, req901, and req221, GENDISP would create the following source code in the file dispatch.f:
Cccccccccccccccccccccccccccccccccccccccccccccccccccc
subroutine reqsub( ID,
& TIME,
& PAR,
& NPAR,
& IFLAG,
& RESULT )
C This is a dispatcher routine written by gendisp
INTEGER ID
DOUBLE PRECISION TIME
DOUBLE PRECISION PAR(*)
INTEGER NPAR
LOGICAL IFLAG
DOUBLE PRECISION RESULT(8)
C Local variables
character*(80) errmsg
IF ( NINT(PAR(1)).EQ.900 ) THEN
CALL req900 ( ID,
& TIME,
& PAR,
& NPAR,
& IFLAG,
& RESULT )
ELSE IF ( NINT(PAR(1)).EQ.901 ) THEN
CALL req901 ( ID,
& TIME,
& PAR,
& NPAR,
& IFLAG,
& RESULT )
ELSE IF ( NINT(PAR(1)).EQ.221 ) THEN
CALL req221 ( ID,
& TIME,
& PAR,
& NPAR,
& IFLAG,
& RESULT )
ELSE
WRITE (ERRMSG,'(A,I4.4)')
& 'Error in dispatcher subroutine reqSUB: Invalid
PAR(1): ',
& NINT( PAR(1) )
CALL ERRMES( .TRUE., ERRMSG, 0, 'STOP')
ENDIF
RETURN
END
Each block for a subroutine in dispatch.dat must contain a (PARAMETERS) sub-block that defines the subroutine parameter types. Optionally, a block may contain a (CODE_BEFORE_BRANCH) sub-block as illustrated in the example MYUSUB shown in
Example of Using GENDISP.
Using GENDISP
The following procedure is only applicable if you want to extend the libraries that GENDISP dispatches. Here we provide an overview of how to test additions to the dispatch.dat file. The result of this procedure should be a fully populated dispatch.f file, which you can examine to verify that valid subroutines have been written. In normal circumstances, this procedure should not required because this process is automated by the acarcom and acarsolvercom scripts. We provide this procedure as a means to validate that you correctly modified the dispatch.dat file.
GENDISP is invoked when linking private or site libraries. A list of standard template-based product file names, plus file names you supply, are passed to GENDISP. GENDISP examines the list of files for names matching the supported subroutine types given in the file dispatch.dat. For each matching name, GENDISP creates a branch in the appropriate user subroutine and outputs the source code file dispatch.f to the working directory.
The file dispatch.f can contain multiple subroutines, but only one of each type (for example, one REQSUB or one CURSUB). The acarcom and acarsolvercom scripts include dispatch.f when linking. The files dispatch.f and dispatch.o are left in the working directory so you can look at them.
For testing or other purposes, you can execute GENDISP yourself. You can find GENDISP at the following location, where install_dir is the installation directory, and product_name is the name of your template-based product:
$install_dir/$product_name/$MDI_CPU/gendisp
For example, the location of GENDISP might be:
/usr/mdi12/acar/irix32/gendisp
You invoke GENDISP with the following arguments:
gendisp file.lst dispatch.f dispatch.dat
where:
■file.lst - File containing a list of user subroutine filenames for GENDISP processing.
■dispatch.f - Source code file name output by GENDISP.
■dispatch.dat - TeimOrbit format file defining the supported subroutine types and their parameter lists.
Example of Using GENDISP
The following example uses GENDISP to generate a MYUSUB that branches to subroutines named myu532, myu253, and so on. It also branches to these subroutines based on an integer argument named SWITCH.
SUBROUTINE MYUSUB( SWITCH, NDPAR, DPAR, VECTOR )
C
C GLOBAL VARIABLES
C
INTEGER SWITCH
INTEGER NDPAR
DOUBLE PRECISION DPAR(NDPAR)
DOUBLE PRECISION VECTOR(3)
C
C LOCAL VARIABLES
C
CHARACTER*(80) ERRMSG
INTEGER PAR(2)
C
PAR(1) = SWITCH
IF ( NINT(PAR(1)).EQ.532 ) THEN
CALL MYU532 ( SWITCH, NDPAR, DPAR, VECTOR )
ELSE IF ( NINT(PAR(1)).EQ.253 ) THEN
CALL MYU253 ( SWITCH, NDPAR, DPAR, VECTOR )
ELSE
WRITE (ERRMSG,'(A,I4.4)')
& 'ERROR in dispatcher subroutine MYSSUB: invalid switch:',
& NINT( PAR(1) )
CALL ERRMES( .TRUE., ERRMSG, 0, 'STOP')
ENDIF
RETURN
END
To dispatch.dat, you add a block for MYSSUB that looks like the following:
$********************************************
[MYU]
(PARAMETERS)
{type name}
'INTEGER' 'SWITCH'
'INTEGER' 'NDPAR'
'DOUBLE PRECISION' 'DPAR(NDPAR)'
'DOUBLE PRECISION' 'VECTOR(3)'
(CODE_BEFORE_BRANCH)
{code}
' INTEGER PAR(2)'
' PAR(1) = SWITCH'
$********************************************
The code GENDISP generates for MYUSUB looks a little different then the example above, but functions in the same way:
Ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc subroutine myusub( SWITCH,
& NDPAR,
& DPAR
, & VECTOR )
C This is a dispatcher routine written by gendisp
INTEGER SWITCH
INTEGER NDPAR
DOUBLE PRECISION DPAR(NDPAR)
DOUBLE PRECISION VECTOR(3)
C Local variables
character*(80) errmsg
INTEGER PAR(2)
PAR(1) = SWITCH
IF ( NINT(PAR(1)).EQ.532 ) THEN
CALL myu532 ( SWITCH,
& NDPAR,
& DPAR,
& VECTOR )
ELSE IF ( NINT(PAR(1)).EQ.253 ) THEN
CALL myu253 ( SWITCH,
& NDPAR,
& DPAR,
& VECTOR )
ELSE
WRITE (ERRMSG,'(A,I4.4)')
& 'Error in dispatcher subroutine myuSUB: Invalid
PAR(1): ',
& NINT( PAR(1) )
CALL ERRMES( .TRUE., ERRMSG, 0, 'STOP'
) ENDIF
RETURN
END