Example Application¶
In the following we provide a basic example showing the simulation flow using the C-API. The archive (capi_example.zip) contains all sources together with example input files.
When using qmake
you can use the capi_example.pro
for Makefile generation. Before starting please adapt the JCMROOT
path at the beginning of the capi_example.pro
file.
When you are not using qmake
please create a C++-project with the source main.cpp
, with $$JCMROOT/include
added to the include path and link against the two dlls jcm_fetools
and jcm_geo
. Further dependencies on third party libraries may require you to link against additional dlls listed in the capi_example.pro
file.
After successfully compiling the program, change to the sub-folder capi_example
and start the program with
>> ./run.sh testproject
The run.sh
is a convenient wrapper to adapt the path and load variables accordingly and call the executable capi_example
with appropriate arguments. Before its first start, please adapt the JCMROOT
path at the beginning of the run.sh
file.
The program reads JCM simulation input files from hard drive, creates the mesh, starts the simulation and prints some results to the console.
Note
You can use the C-API without any harddisk footprint and file reading. The input file reading in the example driver is only used for conveniences.
main routine
All files of the C-API start with JCM… Let us look at the main routine showing the main simulation flow:
int main(int argc, char* argv[])
{
if (argc==1)
{
std::cout<<"*** Please provide path to JCM project directory"<<std::endl;
return 1;
}
//initialization of JCMsuite
bool initializeMPI=false;
JCMInitialize(true,initializeMPI);
JCMGeoInitialize(initializeMPI);
JCMSetNumThreads(1);
//read input files
std::string path(argv[1]);
std::vector<std::string> inputFiles;
std::vector<std::string> inputContent;
if (ReadJCMInputFile(path,inputFiles,inputContent))
return 1;
//input files are loaded onto folder on pinboard
std::string pathPinboard=path+_SEP_+"virtual";
//create mesh
int interrupt=0;
const char* layoutString=inputContent[0].c_str();
std::string gridFileName=pathPinboard+_SEP_+"grid.jcm";
int gridHandle;
if (JCMCreateGridV2(&gridHandle,
gridFileName.c_str(),
layoutString,
&interrupt))
{
std::cerr<<"*** Mesh creation failed"<<std::endl;
return 1;
}
//upload JCMsuite input files to pinboard
std::vector<int> inputFileHandles;
for (size_t iF=1;iF<inputFiles.size();iF++)
{
int handleLoc;
std::string filePath=pathPinboard+_SEP_+inputFiles[iF];
if (JCMSetDataTreeHandle(&handleLoc,inputContent[iF].c_str(),filePath.c_str()))
{
std::cerr<<"*** File \""<<inputFiles[iF]<<"\" could not be created on pinboard"<<std::endl;
return 1;
}
inputFileHandles.push_back(handleLoc);
}
//solve project
std::vector<int> resultHandles_(3);
for (size_t iR=0;iR<resultHandles_.size();iR++)
resultHandles_[iR]=0;
int* resultHandles=&resultHandles_[0];
int nResultHandles;
int projectHandle=inputFileHandles[0];
if (JCMSolve(&resultHandles,&nResultHandles,projectHandle,&interrupt))
{
std::cerr<<"*** Solving failed"<<std::endl;
return 1;
}
std::cout<<GetPinboardStatus()<<std::endl;
//read results from pinboard
for (int iR=0;iR<nResultHandles;iR++)
{
const char* fileName_;
JCMSource(&fileName_,resultHandles_[iR]);
std::string fileName(fileName_);
if (fileName.compare(fileName.length()-6,6,"ft.jcm")==0)
std::cout<<GetTableContent(resultHandles_[iR])<<std::endl;
}
//clean up handles
JCMReleaseHandle(gridHandle);
for (int iR=0;iR<nResultHandles;iR++)
JCMReleaseHandle(resultHandles_[iR]);
for (size_t iR=0;iR<inputFileHandles.size();iR++)
JCMReleaseHandle(inputFileHandles[iR]);
JCMFinalize(false);
JCMGeoFinalize(false);
}
First, the C-API has to be initialized via JCMInitialize and JCMGeoInitialize, where also a license is checked and allocated. Next the input files are read from hard drive and their content and file name stored in C++ strings.
std::vector<std::string> inputFiles;
std::vector<std::string> inputContent;
if (ReadJCMInputFile(path,inputFiles,inputContent))
return 1;
Of course this content can be created directly within your own application without file reading. Its content has to follow the JCM input syntax.
First the mesh is created using the layout description in the layoutString:
int gridHandle;
if (JCMCreateGridV2(&gridHandle,
gridFileName.c_str(),
layoutString,
&interrupt))
{
std::cerr<<"*** Mesh creation failed"<<std::endl;
return 1;
}
Here, the gridHandle is an integer, which is used to refer to the grid on the pinboard when using corresponding functions of the C-API. The gridFileName defines, in which virtual folder the mesh should be created on the pinboard. The integer interrupt can be used from external to interrupt the mesh creation process. It is checked regularly, and mesh creation is stopped, when it is said to a value other than 0. All function of the C-API return a 1 if they fail which can be used checking their successful execution.
Next, we upload all other simulation files to the pinboard:
if (JCMSetDataTreeHandle(&handleLoc,inputContent[iF].c_str(),filePath.c_str()))
{
std::cerr<<"*** File \""<<inputFiles[iF]<<"\" could not be created on pinboard"<<std::endl;
return 1;
}
Again, we pass the file content and the file path and obtain an integer handle, referring to the file on the pinboard. The filePath should be a location as it would be specified on your hard drive.
After uploading all files, we solve the project:
if (JCMSolve(&resultHandles,&nResultHandles,projectHandle,&interrupt))
{
std::cerr<<"*** Solving failed"<<std::endl;
return 1;
}
Here, we pass the integer handle projectHandle referring to the project file, when we uploaded it to the pinboard. Handles to all result files, which are created on the pinboard are written to the resultHandles array, their multitude is returned to nResultHandles. Again, the interrupt integer is passed and can be set from the calling program to a non-zero value, to stop the simulation. Please note that for all post processes a Format can be specified. If this is set to Pinboard as shown here:
PostProcess {
FourierTransform {
OutputFileName = "$THIS_results/ft.jcm"
FieldBagPath = "$THIS_results/fieldbag.jcm"
NormalDirection = Y
Format = Pinboard
}
}
the output file is written to the pinboard and not to the hard drive. Note the $THIS tag in the file paths which automatically prepend the path of the simulation folder. In the project file
Project {
...
StorageFormat = Pinboard
...
the StorageFormat can be set to Pinboard which writes the result fieldbag file to the pinboard instead of the hard drive.
In our C-API example we finally call a routine GetTableContent which accesses the result files and prints them to the console. Before exiting, we release all input file and result file handles from the pinboard
for (int iR=0;iR<nResultHandles;iR++)
JCMReleaseHandle(resultHandles_[iR]);
for (size_t iR=0;iR<inputFileHandles.size();iR++)
JCMReleaseHandle(inputFileHandles[iR]);
and finalize the C-API, releasing all of its memory and internal objects:
JCMFinalize(false);
JCMGeoFinalize(false);
Additional functionality
The function ReadJCMInputFile has no C-API functionality and is just one opportunity to generate the needed simulation description input, by reading files from hard drive.
The C++ subroutines GetTableContent and GetPinboardStatus include additional functionality of the C-API. Let us have a look at them.
The function GetTableContent shows how to obtain information of the result files from the pinboard and write the values of a table into the console:
std::string GetTableContent(int handle)
{
std::stringstream pStream;
const char* title;
JCMTableGetTitle(&title,handle);
int nColumns;
JCMTableNColumns(&nColumns,handle);
pStream<<"Table \""<<title<<"\":"<<std::endl;
for (int iC=0;iC<nColumns;iC++)
{
const char* colName;
JCMTableGetColumnName(&colName,handle,iC);
pStream<<"Column "<<iC+1<<": "<<colName<<std::endl;
}
int nRows;
JCMTableNRows(&nRows,handle);
pStream<<"Content:"<<std::endl;
for (int iR=0;iR<nRows;iR++)
{
pStream<<"Row "<<iR<<": ";
for (int iC=0;iC<nColumns;iC++)
{
char type;
JCMTableGetColumnType(&type,handle,iC);
switch (type)
{
case 'I':
{
int entry;
JCMTableGetIntEntry(&entry,handle,iR,iC);
pStream<<entry;
break;
}
case 'R':
{
double entry;
JCMTableGetDoubleEntry(&entry,handle,iR,iC);
pStream<<entry;
break;
}
case 'C':
{
doublecomplex entry;
JCMTableGetDoubleComplexEntry(&entry,handle,iR,iC);
pStream<<entry.real<<"+i*"<<entry.imag;
break;
}
default:
break;
}
if (iC<nColumns-1)
pStream<<",";
pStream<<"\t";
}
pStream<<std::endl;
}
return pStream.str();
}
The values of the table are read with different functions JCMTableGetIntEntry, JCMTableGetDoubleEntry, JCMTableGetDoubleComplexEntry according to their type, which is obtained via JCMTableGetColumnType.
There are a number of functions in the C-API in order to get information about the objects on the pinboard. Some functionality is shown in the routine GetPinboardStatus
std::string GetPinboardStatus()
{
int nHandles;
JCMPinboardSize(&nHandles);
int* handles=new int[nHandles];
JCMHandleList(&handles);
std::stringstream pStream;
pStream<<"Pinboard status:"<<std::endl;
for (int iH=0;iH<nHandles;iH++)
{
int handle=handles[iH];
const char* type;
JCMWhatIs(&type,handle);
const char* source;
JCMSource(&source,handle);
int nRef;
JCMHandleReferenceCount(&nRef,handle);
pStream<<"\""<<type<<"\" object "<<"<"<<source<<"> (NReferences="<<(nRef-1)<<")\n";
JCMReleaseHandle(handle);
}
delete[] handles;
return pStream.str();
}
where we obtain the number of handles on the pinboard JCMPinboardSize and their respective integer handles by JCMHandleList. We write access their type by JCMWhatIs and their file path location via JCMSource. Each time we ask for a handle, as here in JCMHandleList the reference count of the handle is increased. Because of that, we call JCMReleaseHandle in order to reduce the reference count by one, before leaving the function. If the reference count goes to 0, the object is deleted from the pinboard.
There are further function to obtain information of the mesh, evaluate a fieldbag, etc. All these function are explained in the Function Reference.