Parsers and Coding Tips
This page presents some sample code to be used as an example to help
you write your own solver. Note however that this code sometimes uses a
number of assumptions which may be invalid in your own implementation. It is your responsability to adapt the code
to your own implementation !
Sample parsers
Here are a few simple parsers for the
input format used in this evaluation. Note that these parsers have
minimal features, are not optimized and should not be used as such in
an industrial strength solver. Furthermore, they have not been
intensively tested yet. You may therefore expect some updated version
one day or another.
Simple Parser (C language) (version
2.9.4: has support for reading non linear
constraints and linearization inside the parser)
Simple Parser (C++ language)
(version 2.9.4: has support for reading non linear
constraints and linearization inside the parser)
Simple Parser (Java language)
(version 2.9.0: has preliminary support for reading non linear
constraints but doesn't perform linearization inside the parser)
A
sample Makefile
A tiny linear test file
A tiny non linear test file
A tiny factorization problem (non linear)
Reading the TMPDIR environment variable
/**
* Return the path to the only directory where solvers are allowed to read or write files
*
* will return "/tmp" if this information is not available (e.g. outside the evaluation environment)
*/
char *getTmpDir()
{
char *s=getenv("TMPDIR");
if (s==NULL)
s="/tmp";
return s;
}
Reading the TIMELIMIT or MEMLIMIT environment variables
/**
* Return the limit corresponding to name
* or 0 if no limit is imposed (e.g. outside the evaluation environment)
*
* To be used with name="TIMELIMIT" or name="MEMLIMIT"
*/
int getLimit(char *name)
{
char *value = getenv(name);
if (value == NULL)
return 0;
return atoi(value);
}
Storing the best model found so far
Whenever an optimizing solver finds a model with a better value of the
objective function, it must store that model in case no better model
would be found later. However, storing the model is a critical section
of the program because the solver might receive a SIGTERM while it is
recording the new solution. The C++ class below is an example of what
can be done. The critical section is limited to one single affectation
of pointers, which is a reasonable solution on most architectures (note
however that this may fail on architectures where a pointer assignment
is not atomic).
When a solver finds a better solution, it calls beginSolution()
once and then calls in a loop appendToSolution() to
record each literal of the model. Then, endSolution() is
called to actually validate that model. getSolution()
returns the last validated model.
/**
* a class to record a solution
*
* while a new solution is recorded, the previous solution is
preserved
* to be able to return it when we receive a signal
*
* It is assumed that a negated atom is represented by a negative
integer
* and that an atom is represented by a positive integer. 0 should
not be used.
*/
class SolutionList
{
private:
typedef vector<int> Solution; // a self expandable array
of integers
Solution *tmp,*preserved;
public:
SolutionList()
{
tmp=NULL;
preserved=NULL;
}
/**
* to be called when a new solution will be entered
*/
void beginSolution()
{
tmp=new Solution;
}
/**
* append a literal to the new solution being entered
*/
void appendToSolution(int lit)
{
assert(lit!=0);
tmp->push_back(lit); // append a literal to the
array
}
/**
* to be called once a solution is completely entered
*/
void endSolution()
{
Solution *del=preserved;
// begin critical section
preserved=tmp;
// end critical section
tmp=NULL;
if (del)
delete del;
}
/**
* return true is a solution was recorded
*/
bool hasSolution()
{
return preserved!=NULL;
}
/**
* return the latest stable solution
*
* even in case of an interrupt, the returned solution is
coherent as long
* as endSolution() is not called in between
*/
const vector<int> &getSolution() const
{
if (!preserved)
throw runtime_error("no solution
recorded");
return *preserved;
}
};
Outputing the solver answer
The functions below (C++ code) may be used as a guide to output the
solver answer.
/**
* To be used when the solver has found a model.
*
* The model is recorded in parameter sol. optimum must be set to
true
* if and only if this model gives the optimum value of the
objective function
*/
void outputSatisfiable(const SolutionList &sol, bool optimum)
{
if (optimum)
cout << "s OPTIMUM FOUND" << endl;
else
cout << "s SATISFIABLE" << endl;
const vector<int> &s=sol.getSolution();
cout << "v " << flush;
for(int i=0;i<s.size();++i)
{
if (s[i]<0)
cout << "-x" << -s[i]
<< ' ';
else
cout << 'x' << s[i] <<
' ';
}
cout << endl; // write '\n' and flush the output stream
exit(0);
}
/**
* To be used when the instance is unsatisfiable
*/
void outputUnsatisfiable()
{
cout << "s UNSATISFIABLE" << endl;
exit(0);
}
/**
* To be used when it is unknown if the instance is satisfiable or
not
*/
void outputUnknown()
{
cout << "s UNKNOWN" << endl;
exit(0);
}
When you output the model, don't forget to write a line feed character
('\n') and to flush your output
stream !
Note than the C++ endl automatically outputs a '\n' and
flushes the stream.
Intercepting the SIGTERM signal
Intercepting the SIGTERM signal is just a matter of calling signal().
When the solver is interrupted, it should answer "s SATISFIABLE" if it
found a solution or "s UNKNOWN" otherwise.
#include <signal.h>
SolutionList solution; // global variable that records the best model
found so far
/**
* give the best answer we can when we receive a signal
*/
void interrupt(int sig)
{
if (solution.hasSolution())
outputSatisfiable(solution,false);
else
outputUnknown();
}
int main()
{
signal(SIGTERM,interrupt);
...
}
From a strict point of view, using I/O operations in a signal handler
is generally discouraged because there's no guarantee that I/O
functions are reentrant. However, in this particular case, the signal
handler is called only once to end the program and there's probably
little risk to get into trouble.
Yet, you may prefer a cleaner way to handle the signal. The signal
handler (interrupt()) should simply set a boolean variable
abortASAP which is tested in your solver inner loop to
stop the program when it becomes true. The downside is that it adds a
test to your inner loop. Besides, you must be sure that little time
ellapses between two iterations of that loop since you have only one
second after the SIGTERM to output your solution before the solver
actually gets killed.
bool abortASAP=false; // global variable
void interrupt(int sig)
{
abortASAP=true;
}
int main()
{
signal(SIGTERM,interrupt);
...
while(...) // inner loop of your program
{
if (abortASAP)
{
if (solution.hasSolution())
outputSatisfiable(solution,false); // these functions never return
else
outputUnknown();
}
// your solver core...
....
}
}