C interface
This chapter gives an introduction to the C interface of Mosel. It shows how to execute models from C and how to access modeling objects from C. It is not possible to make changes to Mosel modeling objects from C using this interface, but the data and parameters used by a model may be modified via files or run time parameters.
Basic tasks
To work with a Mosel model, in the C language or with the command line interpreter, it always needs to be compiled, then loaded into Mosel and executed. In this section we show how to perform these basic tasks in C.
Compiling a model in C
The following example program shows how Mosel is initialized in C, and how a model file (extension .mos) is compiled into a binary model (BIM) file (extension .bim). To use the Mosel Model Compiler Library, we need to include the header file xprm_mc.h at the start of the C program.
For the sake of readability, in this program (file ugcomp.c), as for all others in this chapter, we only implement a rudimentary testing for errors.
#include <stdlib.h> #include "xprm_mc.h" int main() { if(XPRMinit()) /* Initialize Mosel */ return 1; if(XPRMcompmod(NULL, "burglar2.mos", NULL, "Knapsack example")) return 2; /* Compile the model burglar2.mos, output the file burglar2.bim */ return 0; }The model burglar2.mos used here is the same as model burglari.mos in Section The burglar problem revisited, but reading the data from file.
With version 1.4 of Mosel it becomes possible to redirect the BIM file that is generated by the compilation. Instead of writing it out to a physical file it may, for instance, be kept in memory or be written out in compressed format. The interested reader is refered to the whitepaper Generalized file handling in Mosel.
Executing a model in C
The example in this section shows how a Mosel binary model file (BIM) can be executed in C. The BIM file can of course be generated within the same program where it is executed, but here we leave out this step. A BIM file is an executable version of a model, but it does not include any data that is read in by the model from external files. It is portable, that is, it may be executed on a different type of architecture than the one it has been generated on. A BIM file produced by the Mosel compiler first needs to be loaded into Mosel (function XPRMloadmod) and can then be run by a call to function XPRMrunmod. To use these functions, we need to include the header file xprm_rt.h at the beginning of our program (named ugrun.c).
#include <stdio.h> #include "xprm_rt.h" int main() { XPRMmodel mod; int result; if(XPRMinit()) /* Initialize Mosel */ return 1; if((mod=XPRMloadmod("burglar2.bim", NULL))==NULL) /* Load a BIM file */ return 2; if(XPRMrunmod(mod,&result,NULL)) /* Run the model */ return 3; return 0; }The compile/load/run sequence may also be performed with a single function call to XPRMexecmod (in this case we need to include the header file xprm_mc.h):
#include <stdio.h> #include "xprm_mc.h" int main() { int result; if(XPRMinit()) /* Initialize Mosel */ return 1; /* Execute = compile/load/run a model */ if(XPRMexecmod(NULL, "burglar2.mos", NULL, &result, NULL)) return 2; return 0; }Parameters
In Part Using the Mosel language the concept of parameters in Mosel has been introduced: when a Mosel model is executed from the command line, it is possible to pass new values for its parameters into the model. The same is possible with a model run in C. If, for instance, we want to run model `Prime' from Section Working with sets to obtain all prime numbers up to 500 (instead of the default value 100 set for the parameter LIMIT in the model), we may start a program with the following lines:
XPRMmodel mod; int result; if(XPRMinit()) /* Initialize Mosel */ return 1; if((mod=XPRMloadmod("prime.bim",NULL))==NULL) /* Load a BIM file */ return 2; if(XPRMrunmod(mod,&result,"LIMIT=500")) /* Run the model */ return 3;To use function XPRMexecmod instead of the compile/load/run sequence we have:
int result; if(XPRMinit()) /* Initialize Mosel */ return 1; /* Execute with new parameter settings */ if(XPRMexecmod(NULL,"prime.mos","LIMIT=500",&result,NULL)) return 2;Accessing modeling objects and solution values
Using the Mosel libraries, it is not only possible to compile and run models, but also to access information on the different modeling objects.
Accessing sets
A complete version of a program (file ugparam1.c) for running the model `Prime' mentioned in the previous section may look as follows (we work with a model prime2 that corresponds to the one printed in Section Working with sets but with all output printing removed because we are doing this in C):
#include <stdio.h> #include "xprm_mc.h" int main() { XPRMmodel mod; XPRMalltypes rvalue, setitem; XPRMset set; int result, type, i, size, first, last; if(XPRMinit()) /* Initialize Mosel */ return 1; if(XPRMexecmod(NULL, "prime2.mos", "LIMIT=500", &result, &mod)) return 2; /* Execute the model */ type=XPRMfindident(mod, "SPrime", &rvalue); /* Get the object 'SPrime' */ if((XPRM_TYP(type)!=XPRM_TYP_INT)|| /* Check the type: */ (XPRM_STR(type)!=XPRM_STR_SET)) /* it must be a set of integers */ return 3; set = rvalue.set; size = XPRMgetsetsize(set); /* Get the size of the set */ if(size>0) { first = XPRMgetfirstsetndx(set); /* Get number of the first index */ last = XPRMgetlastsetndx(set); /* Get number of the last index */ printf("Prime numbers from 2 to %d:\n", LIM); for(i=first;i<=last;i++) /* Print all set elements */ printf(" %d,", XPRMgetelsetval(set,i,&setitem)->integer); printf("\n"); } return 0; }To print the contents of set SPrime that contains the desired result (prime numbers between 2 and 500), we first retrieve the Mosel reference to this object using function XPRMfindident. We are then able to enumerate the elements of the set (using functions XPRMgetfirstsetndx and XPRMgetlastsetndx) and obtain their respective values with XPRMgetelsetval.
Retrieving solution values
The following program ugsol1.c executes the model `Burglar3' (the same as model `Burglar2' from Chapter Some illustrative examples but with all output printing removed) and prints out its solution.
#include <stdio.h> #include "xprm_rt.h"</p> int main() { XPRMmodel mod; XPRMalltypes rvalue, itemname; XPRMarray varr, darr; XPRMmpvar x; XPRMset set; int indices[1], result, type; double val; if(XPRMinit()) /* Initialize Mosel */ return 1; if((mod=XPRMloadmod("burglar3.bim", NULL))==NULL) /* Load a BIM file */ return 2; if(XPRMrunmod(mod, &result, NULL)) /* Run the model (includes optimization) */ return 3; if((XPRMgetprobstat(mod)&XPRM_PBRES)!=XPRM_PBOPT) return 4; /* Test whether a solution is found */ printf("Objective value: %g\n", XPRMgetobjval(mod)); /* Print the obj. function value */ type=XPRMfindident(mod,"take",&rvalue); /* Get the model object 'take' */ if((XPRM_TYP(type)!=XPRM_TYP_MPVAR)|| /* Check the type: */ (XPRM_STR(type)!=XPRM_STR_ARR)) /* it must be an `mpvar' array */ return 5; varr = rvalue.array; type=XPRMfindident(mod,"VALUE",&rvalue); /* Get the model object 'VALUE' */ if((XPRM_TYP(type)!=XPRM_TYP_REAL)|| /* Check the type: */ (XPRM_STR(type)!=XPRM_STR_ARR)) /* it must be an array of reals */ return 6; darr = rvalue.array; type=XPRMfindident(mod,"ITEMS",&rvalue); /* Get the model object 'ITEMS' */ if((XPRM_TYP(type)!=XPRM_TYP_STRING)|| /* Check the type: */ (XPRM_STR(type)!=XPRM_STR_SET)) /* it must be a set of strings */ return 7; set = rvalue.set; XPRMgetfirstarrentry(varr, indices); /* Get the first entry of array varr (we know that the array is dense and has a single dimension) */ do { XPRMgetarrval(varr, indices, &x); /* Get a variable from varr */ XPRMgetarrval(darr, indices, &val); /* Get the corresponding value */ printf("take(%s): %g\t (item value: %g)\n", XPRMgetelsetval(set, indices[0], &itemname)->string, XPRMgetvsol(mod,x), val); /* Print the solution value */ } while(!XPRMgetnextarrentry(varr, indices)); /* Get the next index tuple */ return 0; }The array of variables varr is enumerated using the array functions XPRMgetfirstarrentry and XPRMgetnextarrentry. These functions may be applied to arrays of any type and dimension (for higher numbers of dimensions, merely the size of the array indices that is used to store the index-tuples has to be adapted). With these functions we run systematically through all possible combinations of index tuples, hence the hint at dense arrays in the example. In the case of sparse arrays it is preferrable to use different enumeration functions that only enumerate those entries that are defined (see next section).
Sparse arrays
In Chapter More advanced modeling features the problem `Transport' has been introduced. The objective of this problem is to calculate the flows flowpr from a set of plants to a set of sales regions that satisfy all demand and supply constraints and minimize the total cost. Not all plants may deliver goods to all regions. The flow variables flowpr are therefore defined as a sparse array. The following example (file ugarray1.c) prints out all existing entries of the array of variables.
#include <stdio.h> #include "xprm_rt.h" int main() { XPRMmodel mod; XPRMalltypes rvalue; XPRMarray varr; XPRMset *sets; int *indices, dim, result, type, i; if(XPRMinit()) /* Initialize Mosel */ return 1; if((mod=XPRMloadmod("transport.bim", NULL))==NULL) /* Load a BIM file */ return 2; if(XPRMrunmod(mod, &result, NULL)) /* Run the model */ return 3; type=XPRMfindident(mod,"flow",&rvalue); /* Get the model object named 'flow' */ if((XPRM_TYP(type)!=XPRM_TYP_MPVAR)|| /* Check the type: */ (XPRM_STR(type)!=XPRM_STR_ARR)) /* it must be an array of unknowns */ return 4; varr=rvalue.array; dim = XPRMgetarrdim(varr); /* Get the number of dimensions of the array */ indices = (int *)malloc(dim*sizeof(int)); sets = (XPRMset *)malloc(dim*sizeof(XPRMset)); XPRMgetarrsets(varr,sets); /* Get the indexing sets */ XPRMgetfirstarrtruentry(varr,indices); /* Get the first true index tuple */ do { printf("flow("); for(i=0;i<dim-1;i++) printf("%s,",XPRMgetelsetval(sets[i],indices[i],&rvalue)->string); printf("%s), ",XPRMgetelsetval(sets[dim-1],indices[dim-1],&rvalue)->string); } while(!XPRMgetnextarrtruentry(varr,indices)); /* Get next true index tuple*/ printf("\n"); free(sets); free(indices); XPRMresetmod(mod); return 0; }In this example, we first get the number of indices (dimensions) of the array of variables varr (using function XPRMgetarrdim). We use this information to allocate space for the arrays sets and indices that will be used to store the indexing sets and single index tuples for this array respectively. We then read the indexing sets of the array (function XPRMgetarrsets) to be able to produce a nice printout.
The enumeration starts with the first defined index tuple, obtained with function XPRMgetfirstarrtruentry, and continues with a series of calls to XPRMgetnextarrtruentry until all defined entries have been enumerated.
Termination
At the end of the previous program example we have reset the model (using function XPRMresetmod), thus freeing some resources allocated to it, in particular deleting temporary files that may have been created during its execution.
All program examples in this manual only serve to execute Mosel models. The corresponding model and Mosel itself are terminated (unloaded from memory) with the end of the C program. However, for embedding the execution of a Mosel model into some larger application it may be desirable to free the space used by the model or the execution of Mosel before the end of the application program. To this aim Mosel provides the two functions XPRMunloadmod and XPRMfinish.
Exchanging data between an application and a model
In the previous sections we have seen how to obtain solution information and other data from a Mosel model after its execution. For the integration of a model into an application a flow of information in the opposite sense, that is, from the host application to the model, will often also be required, in particular if data are generated by the application that serve as input to the model. It is possible to write out this data to a (text) file or a database and read this file in from the model, but it is clearly more efficient to communicate such data in memory directly from the application to the model. In this section we show two versions of our Burglar example where all input data is loaded from the application into the model, using dense and sparse data format respectively. The same communication mechanism, namely a combination of the two IO drivers (see Section Generalized file handling for further detail) raw and mem, is also used to write back the solution from the model to the calling application.
Dense arrays
In the first instance we are going to consider a version of the `Burglar' model that corresponds to the very first version we have seen in Section The burglar problem where all arrays are indexed by the range set ITEMS = 1..8. In our C program ugiodense.c below, this corresponds to storing data in standard C arrays that are communicated to the Mosel model at the start of its execution.
#include <stdio.h> #include "xprm_mc.h" double vdata[8]={15,100,90,60,40,15,10, 1}; /* Input data: VALUE */ double wdata[8]={ 2, 20,20,30,40,30,60,10}; /* Input data: WEIGHT */ double solution[8]; /* Array for solution values */ int main() { XPRMmodel mod; int i,result; char vdata_name[40]; /* File name of input data 'vdata' */ char wdata_name[40]; /* File name of input data 'wdata' */ char solution_name[40]; /* File name of solution values */ char params[96]; /* Parameter string for model execution */ if(XPRMinit()) /* Initialize Mosel */ return 1; /* Prepare file names for 'initializations' using the 'raw' driver */ sprintf(vdata_name, "noindex,mem:%#lx/%u", (unsigned long)vdata, sizeof(vdata)); sprintf(wdata_name, "noindex,mem:%#lx/%u", (unsigned long)wdata, sizeof(wdata)); sprintf(solution_name, "noindex,mem:%#lx/%u", (unsigned long)solution, sizeof(solution)); /* Pass file names as execution param.s */ sprintf(params, "VDATA='%s',WDATA='%s',SOL='%s'", vdata_name, wdata_name, solution_name); if(XPRMexecmod(NULL, "burglar6.mos", params, &result, &mod)) return 2; /* Execute a model file */ if((XPRMgetprobstat(mod)&XPRM_PBRES)!=XPRM_PBOPT) return 3; /* Test whether a solution is found */ /* Display solution values obtained from the model */ printf("Objective value: %g\n", XPRMgetobjval(mod)); for(i=0;i<8;i++) printf(" take(%d): %g\n", i+1, solution[i]); XPRMresetmod(mod); /* Reset the model */ return 0; }In this example we use the raw IO driver for communication between the application and the model it executes. Employing this driver means that data is saved in binary format. File names used with the raw driver have the form rawoption[,...],filename. The option noindex for this driver indicates that data is to be stored in dense format, that is, just the data entries without any information about the indices—this format supposes that the index set(s) is known in the Mosel model before data is read in. The filename uses the mem driver, this means that data is stored in memory. The actual location of the data is specified by giving the address of the corresponding memory block and its size.
The program above works with the following version of the `Burglar' model where the locations of input and output data are specified by the calling application through model parameters. Instead of printing out the solution in the model, we copy the solution values of the decision variables take into the array of reals soltake that is written to memory and will be processed by the host application.
model Burglar6 uses "mmxprs" parameters VDATA = ''; WDATA = '' ! Locations of input data SOL = '' ! Location for solution data output WTMAX = 102 ! Maximum weight allowed end-parameters declarations ITEMS = 1..8 ! Index range for items VALUE: array(ITEMS) of real ! Value of items WEIGHT: array(ITEMS) of real ! Weight of items take: array(ITEMS) of mpvar ! 1 if we take item i; 0 otherwise soltake: array(ITEMS) of real ! Solution values end-declarations initializations from 'raw:' VALUE as VDATA WEIGHT as WDATA end-initializations ! Objective: maximize total value MaxVal:= sum(i in ITEMS) VALUE(i)*take(i) ! Weight restriction sum(i in ITEMS) WEIGHT(i)*take(i) <= WTMAX ! All variables are 0/1 forall(i in ITEMS) take(i) is_binary maximize(MaxVal) ! Solve the MIP-problem ! Output solution to calling application forall(i in ITEMS) soltake(i):= getsol(take(i)) initializations to 'raw:' soltake as SOL end-initializations end-modelSparse arrays
Let us now take a look at the case where we use a set of strings instead of a simple range set to index the various arrays in our model. Storing the indices with the data values makes necessary slightly more complicated structures in our C program for the input and solution data. In the C program below (file ugiosparse.c), every input data entry defines both, the value and the weight coefficient for the corresponding index.
#include <stdio.h> #include "xprm_mc.h" const struct { /* Initial values for array 'data': */ const char *ind; /* index name */ double val,wght; /* value and weight data entries */ } data[]={{"camera",15,2}, {"necklace",100,20}, {"vase",90,20}, {"picture",60,30}, {"tv",40,40}, {"video",15,30}, {"chest",10,60}, {"brick",1,10}}; const struct { /* Array to receive solution values: */ const char *ind; /* index name */ double val; /* solution value */ } solution[8]; int main() { XPRMmodel mod; int i,result; char data_name[40]; /* File name of input data 'data' */ char solution_name[40]; /* File name of solution values */ char params[96]; /* Parameter string for model execution */ if(XPRMinit()) /* Initialize Mosel */ return 1; /* Prepare file names for 'initializations' using the 'raw' driver */ sprintf(data_name, "slength=0,mem:%#lx/%u", (unsigned long)data, sizeof(data)); sprintf(solution_name, "slength=0,mem:%#lx/%u", (unsigned long)solution, sizeof(solution)); /* Pass file names as execution param.s */ sprintf(params, "DATA='%s',SOL='%s'", data_name, solution_name); if(XPRMexecmod(NULL, "burglar7.mos", params, &result, &mod)) return 2; /* Execute a model file */ if((XPRMgetprobstat(mod)&XPRM_PBRES)!=XPRM_PBOPT) return 3; /* Test whether a solution is found */ /* Display solution values obtained from the model */ printf("Objective value: %g\n", XPRMgetobjval(mod)); for(i=0;i<8;i++) printf(" take(%s): %g\n", solution[i].ind, solution[i].val); XPRMresetmod(mod); return 0; }The use of the two IO drivers is quite similar to what we have seen before. We now pass on data in sparse format, this means that every data entry is saved together with its index (tuple). Option slength=0 of the raw driver indicates that strings are represented by pointers to null terminated arrays of characters (C-string) instead of fixed size arrays.
Similarly to the model of the previous section, the model burglar7.mos executed by the C program above reads and writes data from/to memory using the raw driver and the locations are specified by the calling application through the model parameters. Since the contents of the index set ITEMS is not defined in the model we have moved the declaration of the decision variables after the data input where the contents of the set is known, thus avoiding the creation of the array of decision variables as a dynamic array.
model Burglar7 uses "mmxprs" parameters DATA = '' ! Location of input data SOL = '' ! Location for solution data output WTMAX = 102 ! Maximum weight allowed end-parameters declarations ITEMS: set of string ! Index set for items VALUE: array(ITEMS) of real ! Value of items WEIGHT: array(ITEMS) of real ! Weight of items end-declarations initializations from 'raw:' [VALUE,WEIGHT] as DATA end-initializations declarations take: array(ITEMS) of mpvar ! 1 if we take item i; 0 otherwise end-declarations ! Objective: maximize total value MaxVal:= sum(i in ITEMS) VALUE(i)*take(i) ! Weight restriction sum(i in ITEMS) WEIGHT(i)*take(i) <= WTMAX ! All variables are 0/1 forall(i in ITEMS) take(i) is_binary maximize(MaxVal) ! Solve the MIP-problem ! Output solution to calling application forall(i in ITEMS) soltake(i):= getsol(take(i)) initializations to 'raw:' soltake as SOL end-initializations end-modelRedirecting the Mosel output
When integrating a Mosel model into an application it may be desirable to be able to redirect any output produced by the model to the application. This can be done by the means of a callback function. This function takes a predefined signature as shown in the following C program. If it is called from outside of the execution of any Mosel model, its parameter model will be NULL. In our example the callback function prefixes the printout of every line of Mosel output with Mosel:.
#include <stdio.h> #include "xprm_mc.h" /**** Callback function to handle output ****/ long XPRM_RTC cbmsg(XPRMmodel model, void *info, char *buf, unsigned long size) { printf("Mosel: %.*s", (int)size, buf); return 0; } int main() { int result; char outfile_name[40]; /* File name of output stream */ if(XPRMinit()) /* Initialize Mosel */ return 1; /* Prepare file name for output stream */ /* using 'cb' driver */ sprintf(outfile_name, "cb:%#lx", (unsigned long)cbmsg); /* Set default output stream to callback */ XPRMsetdefstream(NULL, XPRM_F_WRITE, outfile_name); /* Execute = compile/load/run a model */ if(XPRMexecmod(NULL, "burglar2.mos", NULL, &result, NULL)) return 2; return 0; }The same procedure that has been presented here for redirecting the Mosel output can also be applied to redirect any error messages produced by Mosel—the only required modification consists in replacing the constant XPRM_F_WRITE by XPRM_F_ERROR in the argument of function XPRMsetdefstream.
Problem solving in C with Xpress-Optimizer
In certain cases, for instance if the user wants to re-use parts of algorithms that he has written in C for the Xpress-Optimizer, it may be necessary to pass from a problem formulation with Mosel to solving the problem in C by direct calls to the Xpress-Optimizer. The following example shows how this may be done for the Burglar problem. We use a slightly modified version of the original Mosel model:
model Burglar4 uses "mmxprs" declarations WTMAX=102 ! Maximum weight allowed ITEMS={"camera", "necklace", "vase", "picture", "tv", "video", "chest", "brick"} ! Index set for items VALUE: array(ITEMS) of real ! Value of items WEIGHT: array(ITEMS) of real ! Weight of items take: array(ITEMS) of mpvar ! 1 if we take item i; 0 otherwise end-declarations initializations from 'burglar.dat' VALUE WEIGHT end-initializations ! Objective: maximize total value MaxVal:= sum(i in ITEMS) VALUE(i)*take(i) ! Weight restriction sum(i in ITEMS) WEIGHT(i)*take(i) <= WTMAX ! All variables are 0/1 forall(i in ITEMS) take(i) is_binary setparam("XPRS_LOADNAMES", true) ! Enable loading of object names loadprob(MaxVal) ! Load problem into the optimizer end-modelThe procedure maximize to solve the problem has been replaced by loadprob. This procedure loads the problem into the optimizer without solving it. We also enable the loading of names from Mosel into the optimizer so that we may obtain an easily readable output.
The following C program ugxprs1.c reads in the Mosel model and solves the problem by direct calls to Xpress-Optimizer. To be able to address the problem loaded into the optimizer, we need to retrieve the optimizer problem pointer from Mosel. Since this information is a parameter (XPRS_PROBLEM) that is provided by module mmxprs, we first need to obtain the reference of this library (by using function XPRMfinddso).
#include <stdio.h> #include "xprm_rt.h" #include "xprs.h" int main() { XPRMmodel mod; XPRMdsolib dso; XPRMalltypes rvalue; XPRSprob prob; int result, ncol, len, i; double *sol, val; char *names; if(XPRMinit()) /* Initialize Mosel */ return 1; if((mod=XPRMloadmod("burglar4.bim", NULL))==NULL) /* Load a BIM file */ return 2; if(XPRMrunmod(mod, &result, NULL)) /* Run the model (no optimization) */ return 3; /* Retrieve the pointer to the problem loaded in the Xpress-Optimizer */ if((dso=XPRMfinddso("mmxprs"))==NULL) return 4; if(XPRMgetdsoparam(mod, dso, "xprs_problem", &result, &rvalue)) return 5; if(XPRM_TYP(result)==XPRM_TYP_STRING) prob=(XPRSprob)strtol(rvalue.ref,NULL,0); else prob=rvalue.ref; if(XPRSmaxim(prob, "g")) /* Solve the problem */ return 6; if(XPRSgetintattrib(prob, XPRS_MIPSTATUS, &result)) return 7; /* Test whether a solution is found */ if((result==XPRS_MIP_SOLUTION) || (result==XPRS_MIP_OPTIMAL)) { if(XPRSgetdblattrib(prob, XPRS_MIPOBJVAL, &val)) return 8; printf("Objective value: %g\n", val); /* Print the objective function value */ if(XPRSgetintattrib(prob, XPRS_COLS, &ncol)) return 9; if((sol = (double *)malloc(ncol * sizeof(double)))==NULL) return 10; if(XPRSgetmipsol(prob, sol, NULL)) return 11; /* Get the primal solution values */ if(XPRSgetintattrib(prob, XPRS_NAMELENGTH, &len)) return 11; /* Get the maximum name length */ if((names = (char *)malloc((len*8+1)*ncol*sizeof(char)))==NULL) return 12; if(XPRSgetnames(prob, 2, names, 0, ncol-1)) return 13; /* Get the variable names */ for(i=0; i<ncol; i++) /* Print out the solution */ printf("%s: %g\n", names+((len*8+1)*i), sol[i]); free(names); free(sol); } return 0; }Since the Mosel language provides ample programming facilities, in most applications there will be no need to switch from the Mosel language to problem solving in C. If nevertheless this type of implementation is chosen, it should be noted that it is not possible to get back to Mosel, once the Xpress-Optimizer has been called directly from C: the solution information and any possible changes made to the problem directly in the optimizer are not communicated to Mosel.
If you have any comments or suggestions about these pages, please send mail to docs@dashoptimization.com.