/*
This program is free software: you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version, see .
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
the GNU General Public License for more details.
*/
//Name : DensityChecker.
//
//Author : Rahil Baber.
//
//Date : 10th November 2010.
//
//Compilation : This program has been successfully compiled under Visual C++ 2010, and
// gcc 4.4.1. E.g. in Linux type "g++ -O3 DensityChecker.cpp -o dcheck"
//
//Usage :
// E.g. in Linux "./dcheck 2-9-prf.txt"
//
//Description : Using Razborov's method it calculates a bound on the Turan density for a
// given family of 3-graphs. The program expects the relevant data (flags,
// basis, matrices, etc.) to be passed to it through the input file (specified
// through the command line). This program is designed only to verify that the
// input file gives the claimed bound, it does not create such input files.
//
//Notes : Currently the program can only handle 3-graphs of order at most 9. Users may
// wish/need to change the hard coded values 'LI_MAX' and 'noOfDecimalPlaces'
// specified below. Note that while some of our proofs can be verified in under
// 10 minutes others may take several hours to verify.
#include
#include
#include
using namespace std;
//*** Hard coded values that a user may wish to change. ***
//Used to allocate memory for CLargeInt objects. If an overflow error occurs it most likely
//means that LI_MAX was set too low. The larger LI_MAX is, the slower the program will run
//and the more memory it will require.
const long LI_MAX = 1000; //You may need to change (increase) this value.
//Used by DisplayDensity() to calculate the upper bound of the Turan density to
//'noOfDecimalPlaces' decimal places.
const long noOfDecimalPlaces = 6;
//*** Class definitions. ***
//Exception classes which are thrown when overflow or division by zero occurs.
class ErrorOverflow{};
class ErrorDivisionByZero{};
//Used to store and manipulate very large integers. Can store any integer of magnitude less
//than 2^(8*LI_MAX). The integer's magnitude is stored in a static array to avoid slow
//dynamic memory allocation. The amount of the array being used is also stored, to increase
//the speed of operations on small integers. Functions which use this class may throw an
//ErrorOverflow or ErrorDivisionByZero object.
class CLargeInt
{
public:
//The default constructor, sets the large integer to 0.
CLargeInt();
//The following constructors allow the fundamental integral data types to be
//automatically cast into CLargeInt objects. These constructors call Assign() and so may
//throw an ErrorOverflow object. No object will be thrown if LI_MAX >=
//sizeof(unsigned long), (sizeof(unsigned long) = 4 usually).
CLargeInt(unsigned int input);
CLargeInt(int input);
CLargeInt(unsigned long input);
CLargeInt(long input);
//The copy constructor.
CLargeInt(const CLargeInt& input);
//The assignment operator.
CLargeInt& operator=(const CLargeInt& input);
//The unary operators. Used to evaluate +A or -A, when A is a CLargeInt object.
const CLargeInt operator+() const;
const CLargeInt operator-() const;
//The basic arithmetic operators. The +,-,* operators may throw an ErrorOverflow object.
//The integer division operators / and % are wrappers for the Divide() function and so
//may throw an ErrorDivisionByZero object.
friend const CLargeInt operator+(const CLargeInt& A, const CLargeInt& B);
friend const CLargeInt operator-(const CLargeInt& A, const CLargeInt& B);
friend const CLargeInt operator*(const CLargeInt& A, const CLargeInt& B);
friend const CLargeInt operator/(const CLargeInt& A, const CLargeInt& B);
friend const CLargeInt operator%(const CLargeInt& A, const CLargeInt& B);
//The comparison operators. They are wrappers for the Compare() function.
friend const bool operator==(const CLargeInt& A, const CLargeInt& B);
friend const bool operator!=(const CLargeInt& A, const CLargeInt& B);
friend const bool operator< (const CLargeInt& A, const CLargeInt& B);
friend const bool operator<=(const CLargeInt& A, const CLargeInt& B);
friend const bool operator> (const CLargeInt& A, const CLargeInt& B);
friend const bool operator>=(const CLargeInt& A, const CLargeInt& B);
//Used to convert the CLargeInt object into a long. If the value stored in the object is
//larger than what can be stored in a long variable it throws an ErrorOverflow object.
long ConvertToLong() const;
private:
//Used by constructors to convert the fundamental integral data types to CLargeInt
//objects. It may throw an ErrorOverflow object but only if LI_MAX B.
static long Compare(const CLargeInt& A, const CLargeInt& B);
//Used by the integer division operators / and %. Define |input| to be the magnitude of
//input, and sign(input) to be +1 if input>=0 and -1 if input<0. The function calculates
//outputQ, and outputR as follows:
//outputQ = sign(inputN)*sign(inputD)*(|inputN|/|inputD|), where '/' is integer division,
//outputR = sign(inputN)*(|inputN|%|inputD|).
//The choice of signs for outputQ and outputR were chosen so that inputN = inputD *
//outputQ + outputR. If inputD = 0 an ErrorDivisionByZero class is thrown. The function
//assumes outputQ resides in a different address to inputN, inputD, and outputR. It also
//assumes outputR resides in a different address to inputN, inputD, and outputQ.
static void Divide(const CLargeInt& inputN, const CLargeInt& inputD,
CLargeInt& outputQ, CLargeInt& outputR);
//Data[], Size, and bPositive hold the data that represents the large integer. Data[] and
//Size hold the magnitude of the large integer, bPositive holds the sign. bPositive =
//true if the integer is greater than or equal to 0, and false otherwise. The magnitude
//of the integer is given by:
//Data[Size-1]*256^(Size-1)+Data[Size-2]*256^(Size-2)+...+Data[1]*256+Data[0].
//Size holds the number of bytes the integer takes up in Data[]. To avoid leading zeros,
//which can artificially make a small integer use up a lot of the Data[] array, we add
//the following restriction. If the integer is 0 then Size=0, otherwise Data[Size-1] must
//be non-zero.
unsigned char Data[LI_MAX];
long Size; //Number of bytes. Size=0 <=> integer=0. Size!=0 => Data[Size-1]!=0.
bool bPositive; //bPositive=true if integer>=0, false otherwise.
};
//Allows us to use << operator to output the CLargeInt object L to the screen or a file.
ostream& operator<<(ostream& out, const CLargeInt& L);
//Used to store and manipulate fractions with very large numerators and denominators. It uses
//two CLargeInt member variables, named N and D, which store the numerator and denominator of
//the fraction respectively. Functions which use this class may throw an ErrorOverflow or
//ErrorDivisionByZero object.
class CLargeFraction
{
public:
//The default constructor, sets the fraction to 0.
CLargeFraction();
//The following constructors allow the fundamental integral data types to be
//automatically cast into CLargeFraction objects. They may throw an ErrorOverflow object.
//No object will be thrown if LI_MAX >= sizeof(unsigned long), (sizeof(unsigned long) = 4
//usually).
CLargeFraction(unsigned int input);
CLargeFraction(int input);
CLargeFraction(unsigned long input);
CLargeFraction(long input);
//A constructor that allows CLargeInt objects to be automatically cast into
//CLargeFraction objects.
CLargeFraction(const CLargeInt& input);
//The unary operators. Used to evaluate +A or -A, when A is a CLargeFraction object.
//These functions may throw an ErrorDivisionByZero object.
const CLargeFraction operator+() const;
const CLargeFraction operator-() const;
//The basic arithmetic operators. The +,-,*,/ operators may throw an ErrorOverflow or
//ErrorDivisionByZero object.
friend const CLargeFraction operator+(const CLargeFraction& A, const CLargeFraction& B);
friend const CLargeFraction operator-(const CLargeFraction& A, const CLargeFraction& B);
friend const CLargeFraction operator*(const CLargeFraction& A, const CLargeFraction& B);
friend const CLargeFraction operator/(const CLargeFraction& A, const CLargeFraction& B);
//The comparison operators. They are wrappers for the Compare() function. They may throw
//an ErrorOverflow or ErrorDivisionByZero object.
friend const bool operator==(const CLargeFraction& A, const CLargeFraction& B);
friend const bool operator!=(const CLargeFraction& A, const CLargeFraction& B);
friend const bool operator< (const CLargeFraction& A, const CLargeFraction& B);
friend const bool operator<=(const CLargeFraction& A, const CLargeFraction& B);
friend const bool operator> (const CLargeFraction& A, const CLargeFraction& B);
friend const bool operator>=(const CLargeFraction& A, const CLargeFraction& B);
//Removes any common factors between the numerator and the denominator and makes the
//denominator positive. It may throw an ErrorDivisionByZero object.
void Simplify();
CLargeInt N; //Numerator.
CLargeInt D; //Denominator.
private:
//Used by the comparison operators to compare two fractions A, B.
//Returns -1 if A < B,
// 0 if A = B,
// 1 if A > B.
//It may throw an ErrorOverflow or ErrorDivisionByZero object.
static long Compare(const CLargeFraction& A, const CLargeFraction& B);
};
//Allows us to use << operator to output the CLargeFraction object F to the screen or a file.
ostream& operator<<(ostream& out, const CLargeFraction& F);
//A simple class to store 3-graphs that have at most nine vertices. Because C++ indexes
//arrays starting from 0, we take our vertex set to be {0,1,...,nV-1}, where nV is the number
//of vertices in our graph. We can represent a flag as (g, n) a CGraph g and an integer n, by
//taking the labelled vertices to be 0,1,...,n-1 and to avoid confusion we label them
//0,1,...,n-1 respectively (instead of 1,2,...,n).
class CGraph
{
public:
//Number of vertices.
long nV;
//The adjacency matrix.
//M[i][j][k] = true if edge ijk is in the 3-graph,
// false if edge ijk is not in the 3-graph.
bool M[9][9][9];
};
//Allows us to use << operator to output the CGraph object g to the screen or a file.
ostream& operator<<(ostream& out, const CGraph& g);
//Used by CLinkedList to create a dynamically allocated list of graphs.
class CListNode
{
public:
//The graph which is part of the list.
CGraph g;
//CLinkedList is primarily used to store the family $\mathcal{H}$ (the admissible graphs
//of order "orderOfH"). We will use coeff to keep track of the coefficient of $p(H;G)$.
CLargeFraction coeff;
//Pointer to the next CListNode in the list.
//If there is no next CListNode, pNext = NULL.
CListNode* pNext;
};
//A very basic linked list of graphs. CLinkedList is primarily used to store the family
//$\mathcal{H}$ (the admissible graphs of order "orderOfH"). It may throw a bad_alloc object.
class CLinkedList
{
public:
//The constructor. Sets pHead=NULL to indicate the list is empty.
CLinkedList();
//The destructor. Calls Free().
~CLinkedList();
//Used to destroy the list, and free any memory it has allocated.
void Free();
//Used to add a new graph to the start of the list. It will throw a bad_alloc object if
//there is not enough memory to add the new graph.
void Add(CGraph& g);
//Returns the number of nodes in the list.
long Size();
//Pointer to the first element in the list. If the list is empty pHead = NULL.
CListNode* pHead;
};
//A very basic class that holds data used to calculate the upper bound of the Turan density.
//The calculation involves summing positive semidefinite matrices multiplied by flags. Each
//CTermInfo object holds one such matrix, the basis it is written in, the flags which are
//used to describe the basis, and the common induced type of the flags. Memory is dynamically
//allocated for the matrix, basis, and flags by ReadDataFile(). Any dynamically allocated
//memory is automatically freed by the CTermInfo's destructor. The class uses CLargeFraction
//objects and so ErrorOverflow and ErrorDivisionByZero objects may be thrown.
class CTermInfo
{
public:
//The default constructor, initializes pointers to NULL.
CTermInfo();
//The destructor. A wrapper for Free().
~CTermInfo();
//Deallocates memory.
void Free();
//The member variables are all initialized (and where appropriate allocated) by the
//function ReadDataFile().
//The common induced type of the flags.
CGraph Type;
//FlagList[] holds a list of flags, and is of size noOfFlags.
long noOfFlags;
CGraph* FlagList;
//Matrix[][] holds the positive semidefinite matrix and is of size MatrixDim by
//MatrixDim.
long MatrixDim;
CLargeFraction** Matrix;
//Basis[][] represents a list of linear combinations of flags by storing the coeffcients
//of the flags. The n-th linear combination is:
//Basis[n-1][0](FlagList[0])+Basis[n-1][1](FlagList[1])+...
//There are MatrixDim linear combinations (hence the first index varies from 0 to
//MatrixDim-1). The second index varies from 0 to noOfFlags-1.
CLargeFraction** Basis;
};
//*** Global variables. ***
//The order of the graphs $H_i$. Denoted by the letter $l$ in the thesis. Due to the way the
//function GenerateH() and ReadGraphEdgeSet() has been implemented orderOfH can be at most 9.
//In order for the density calculation to be valid orderOfH must be at least 3. It is
//initialized by ReadDataFile().
long orderOfH;
//H is a linked list representing $\mathcal{H}$ the family of admissible graphs of order
//orderOfH. H is initialized by a call to GenerateH().
CLinkedList H;
//ForbiddenGraph[] holds the forbidden family $\mathcal{F}$. Its size is held in
//noOfForbiddenGraphs. noOfForbiddenGraphs is initialized in ReadDataFile(). ReadDataFile()
//also allocates the memory for ForbiddenGraph[] then initializes it. The memory is
//deallocated in Cleanup().
long noOfForbiddenGraphs;
CGraph* ForbiddenGraph = NULL;
//If bForbidOnlyInducedSubgraphs is set to false, the admissible graphs are those that do not
//contain a subgraph from our forbidden family $\mathcal{F}$. If set to true then the
//admissible graphs are those that do not contain an *induced* subgraph from $\mathcal{F}$.
//It is initialized by ReadDataFile().
bool bForbidOnlyInducedSubgraphs;
//Term[] is an array of size noOfTerms that holds data used to calculate the upper bound of
//the Turan density. noOfTerms is initialized in ReadDataFile(). ReadDataFile() also
//allocates the memory for Term[] then initializes it. The memory is deallocated in
//Cleanup().
long noOfTerms;
CTermInfo* Term = NULL;
//Factorial[n] = n!.
long Factorial[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
//Iso[][] (together with Factorial[]) will be useful in generating isomorphic copies of
//graphs. Iso[][] is an array (orderOfH! by orderOfH) of all permutations of
//{0,1,...,orderOfH-1}. Iso[p][n] is the image of n under permutation p. Iso[][] is ordered
//in such a way that {Iso[p][] : p<=r!} is the set of permutations in which only {0,1,..,r-1}
//are permuted and {r,...,orderOfH-1} are left in place. InitIso() allocates memory for
//Iso[][] and initializes it. Its memory is deallocated by Cleanup().
long** Iso = NULL;
//*** Function declarations. ***
//Deallocates the memory allocated to H, ForbiddenGraph[], Term[], and Iso[][]. To deallocate
//the memory associated with Iso[][] it requires orderOfH to be set.
void Cleanup();
//Allocates memory for Iso[][] and initializes it. The function assumes orderOfH has been set
//to a value between 3 and 9. Iso[][] is initialized to an orderOfH! by orderOfH array
//representing all permutations of {0,1,...,orderOfH-1}. Iso[p][n] will be the image of n
//under permutation p. Iso[][] will be ordered in such a way that {Iso[p][] : p<=r!} is the
//set of permutations in which only {0,1,..,r-1} are permuted and {r,...,orderOfH-1} are left
//in place. A bad_alloc object is thrown if memory allocation fails.
void InitIso();
//Calculates the number of characters needed to display 'n'. The function is used by
//ReadDataFile().
long NumberOfDigits(unsigned long n);
//This function is used in parsing the input file. It reads one character from the file. If
//the end of the file is reached it returns '\0'. If the character is '#' it interprets this
//as the start of a comment. It will then skip over all characters until '\n','\r', or '\0'
//is encountered and returns that instead.
char ReadChar(istream& in);
//This function is used in parsing the input file. It checks whether the character is a
//whitespace character (i.e. an end of line character, tab, or space).
bool IsWhitespace(char c);
//This function is used in parsing the input file. It discards whitespace characters until it
//reaches a non-whitespace character. The last character that was read (but not yet
//interpreted) should be passed to the function via 'c'. When the function returns 'c' will
//hold the last character read by the function.
void RemoveWhitespace(istream& in, char& c);
//This function is used in parsing the input file. It continually removes the character
//'repeated' until a different character is encountered. 'repeated' must appear at least once
//otherwise the function returns false. The last character that was read (but not yet
//interpreted) should be passed to the function via 'c'. When the function returns 'c' will
//hold the last character read by the function.
bool ReadRepeatingChar(istream& in, char repeated, char& c);
//This function is used in parsing the input file. Checks whether 'str[]' matches the
//characters in the istream. Ignores capitalization. Allows whitespaces in 'str[]' to be
//represented by one or more whitespaces in the istream. The last character that was read
//(but not yet interpreted) should be passed to the function via 'c'. When the function
//returns 'c' will hold the last character read by the function.
bool CheckString(istream& in, const char* str, char& c);
//This function is used in parsing the input file. It removes any whitespaces then reads in a
//boolean value and returns it via the variable 'b'. The function accepts "yes", "no", "y",
//"n", "true", or "false" as the boolean value (capitalization is ignored). If one of the
//listed boolean values is not encountered the function returns false, otherwise it returns
//true. The last character that was read (but not yet interpreted) should be passed to the
//function via 'c'. When the function returns 'c' will hold the last character read by the
//function.
bool ReadBool(istream& in, bool& b, char& c);
//This function is used in parsing the input file. It reads in a CLargeInt value and returns
//it via the variable 'L'. It may throw an ErrorOverflow object if the integer is too large.
//The first character must be +,-,0-9. If the first character is + or - the next character
//must be one of 0-9 or a series of whitespaces followed by 0-9. No whitespaces are allowed
//between the numeric digits. If the function reads in an integer of this form successfully
//it returns true, otherwise it returns false. The last character that was read (but not yet
//interpreted) should be passed to the function via 'c'. When the function returns 'c' will
//hold the last character read by the function.
bool ReadLargeInteger(istream& in, CLargeInt& L, char& c);
//This function is used in parsing the input file. It removes any whitespaces then tries to
//read in ":", e.g. "Dimension:3". The string 'str[]' is read in using
//CheckString(). The integer is read using ReadLargeInteger() and returned via 'output'. It
//allows optional whitespaces either side of the colon. It may throw an ErrorOverflow object
//if the integer is too large.
//The function returns 0 on success,
// 1 if the input is unexpected,
// 2 if the integer is too large to be stored in a long.
//The last character that was read (but not yet interpreted) should be passed to the function
//via 'c'. When the function returns 'c' will hold the last character read by the function.
long ReadLong(istream& in, const char* str, long& output, char& c);
//This function is used in parsing the input file. It removes any whitespaces then tries to
//read in the edge set of a graph. The function assumes g.nV has been set and uses this to
//verify the edges are valid. The edge set should take the form "{,,...,}",
//where is a three digit integer representing the edge. For example "{123, 325, 167}"
//or the empty set "{}". The three integers representing the edge should be distinct and be
//between 1 and g.nV (inclusive). Whitespaces either side of the sequences are
//ignored. The function returns true if it succeeds, and false otherwise. The last character
//that was read (but not yet interpreted) should be passed to the function via 'c'. When the
//function returns 'c' will hold the last character read by the function.
bool ReadGraphEdgeSet(istream& in, CGraph& g, char& c);
//This function is used in parsing the input file. It removes any whitespaces then tries to
//read in a linear combination of flags and store it in Term.Basis[basisIndex][]. The
//function assumes memory has already been allocated for Term.Basis[][]. It also assumes
//Term.noOfFlags has been set and uses this to verify the flags read in are valid. The linear
//combination of flags should take the form
//"()<+ or ->()<+ or ->...".
//For example "1/2(F1)-4/3(F2)+2/3(F5)+1/7(F6)". An ErrorOverflow object may be thrown if an
//integer that is read is too large to be stored.
//The function returns 0 if it succeeds,
// 1 if the input is incorrectly formatted,
// 2 if a flag index does not lie in [1,Term.noOfFlags],
// 3 if a coefficient causes a division by zero.
//The last character that was read (but not yet interpreted) should be passed to the function
//via 'c'. When the function returns 'c' will hold the last character read by the function.
long ReadBasis(istream& in, CTermInfo& Term, long basisIndex, char& c);
//This function parses the input file given by filename. It initializes, and where
//appropriate allocates memory, for the global variables orderOfH, noOfForbiddenGraphs,
//ForbiddenGraph[], bForbidOnlyInducedSubgraphs, noOfTerms, and Term[]. It returns true if it
//succeeds and false if an error occurred. It makes use of the functions NumberOfDigits(),
//ReadChar(), IsWhitespace(), RemoveWhitespace(), ReadRepeatingChar(), CheckString(),
//ReadBool(), ReadLargeInteger(), ReadLong(), ReadGraphEdgeSet(), and ReadBasis().
bool ReadDataFile(const char* filename);
//Returns true if all matrices in Term[] are positive semidefinite (psd). The function
//assumes Term[] and noOfTerms have been initialized. A bad_alloc or ErrorOverflow object may
//be thrown.
bool IsPSD();
//Returns the number of edges in the graph g.
long NumberOfEdges(CGraph& g);
//Returns true if g1 and g2 are of the same order and have the same edge set.
bool IsCopy(CGraph& g1, CGraph& g2);
//If bInduced = false, IsSubgraph() returns true if gSub is a subgraph of gSuper.
//If bInduced = true, IsSubgraph() returns true if gSub is an induced subgraph of gSuper.
//Assumes Iso[][] has been initialized.
bool IsSubgraph(CGraph& gSub, CGraph& gSuper, bool bInduced);
//Each edge set of a graph can be assigned a unique integer. FindMinIso() creates gOut a
//graph with the property that the flag (gOut, orderOfType) is isomorphic to the flag
//(gIn, orderOfType) and has the smallest integer associated with its edge set out of all
//such graphs. This function is used by GenerateH() to avoid storing isomorphic copies of
//graphs, and by CalculateDensity() to speed up searching through lists of flags. In order
//for GenerateH() to function correctly the way FindMinIso() constructs the integer
//representing the edge set is important. The function assumes Iso[][] has been initialized.
void FindMinIso(CGraph gIn, CGraph& gOut, long orderOfType);
//Generates 'H' a linked list representing $\mathcal{H}$ the family of admissible graphs of
//order orderOfH. The function assumes orderOfH, ForbiddenGraph[], noOfForbiddenGraphs, and
//bForbidOnlyInducedSubgraphs, have been initialized. A bad_alloc object may be thrown.
void GenerateH();
//Displays 'Density' as both a fraction and a decimal. This function is used by
//CalculateDensity() to display an upper bound of the Turan density. It may throw an
//ErrorOverflow object.
void DisplayDensity(CLargeFraction Density);
//Calculates then displays the upper bound of the Turan density. The function assumes
//Iso[][], Term[], noOfTerms, H, and orderOfH have been initialized. To save memory the
//function modifies Term[]. A bad_alloc or ErrorOverflow object may be thrown.
void CalculateDensity();
//The main function. Expects argc to be 2, argv[0] to be the name of the executable and
//argv[1] to be the name of the input file.
int main(int argc, char* argv[]);
//*** Function definitions. ***
//The default constructor, sets the large integer to 0.
CLargeInt::CLargeInt()
{
Size = 0;
bPositive = true;
return;
}
//Allows an unsigned int to be automatically cast into a CLargeInt object. It may throw an
//ErrorOverflow object.
CLargeInt::CLargeInt(unsigned int input)
{
Assign(input);
return;
}
//Allows an int to be automatically cast into a CLargeInt object. It may throw an
//ErrorOverflow object.
CLargeInt::CLargeInt(int input)
{
unsigned long temp;
if(input>=0)
Assign(input);
else
{
//Call Assign() on -input.
//-input may be 2147483648 which could cause an overflow. To avoid this situation we
//cast -(input+1) to an unsigned long then add one.
input++;
temp = -input;
temp++;
Assign(temp);
bPositive = false;
}
return;
}
//Allows an unsigned long to be automatically cast into a CLargeInt object. It may throw an
//ErrorOverflow object.
CLargeInt::CLargeInt(unsigned long input)
{
Assign(input);
return;
}
//Allows a long to be automatically cast into a CLargeInt object. It may throw an
//ErrorOverflow object.
CLargeInt::CLargeInt(long input)
{
unsigned long temp;
if(input>=0)
Assign(input);
else
{
//Call Assign() on -input.
//-input may be 2147483648 which could cause an overflow. To avoid this situation we
//cast -(input+1) to an unsigned long then add one.
input++;
temp = -input;
temp++;
Assign(temp);
bPositive = false;
}
return;
}
//The copy constructor.
CLargeInt::CLargeInt(const CLargeInt& input)
{
long i;
//Check if we are trying to copy the object to itself.
if(this==&input)
return;
Size = input.Size;
bPositive = input.bPositive;
//We do not need to copy all of Data[].
for(i=0;i=B.Size)
output.Size = A.Size;
else
output.Size = B.Size;
//Do the addition byte by byte.
carryOver = 0;
for(i=0;i=LI_MAX)
throw ErrorOverflow(); //Overflow occured.
output.Data[output.Size] = carryOver;
output.Size++;
}
return output;
}
else
{
//Find the difference between the magnitudes of A and B.
//If A.Size==B.Size assume |A|>=|B|.
if(A.Size>=B.Size)
{
output.bPositive = A.bPositive;
output.Size = A.Size;
//Do the subtraction of B from A, byte by byte.
carryOver = 0;
for(i=0;i=0)
{
output.Data[i] = sum;
carryOver = 0;
}
else
{
output.Data[i] = 256+sum;
carryOver = -1;
}
}
if(carryOver==0)
{
//Remove leading zeros.
while(true)
{
if(output.Size==0)
{
output.bPositive = true;
return output;
}
if(output.Data[output.Size-1]!=0)
return output;
output.Size--;
}
}
//A.Size==B.Size but |A|<|B|.
}
//A.Size |A|<|B|
output.bPositive = B.bPositive;
output.Size = B.Size;
//Do the subtraction of A from B, byte by byte.
carryOver = 0;
for(i=0;i=0)
{
output.Data[i] = sum;
carryOver = 0;
}
else
{
output.Data[i] = 256+sum;
carryOver = -1;
}
}
//Remove leading zeros.
while(true)
{
if(output.Size==0)
{
output.bPositive = true;
return output;
}
if(output.Data[output.Size-1]!=0)
return output;
output.Size--;
}
}
}
//The - arithmetic operator. Returns A-B. It may throw an ErrorOverflow object.
const CLargeInt operator-(const CLargeInt& A, const CLargeInt& B)
{
return A+(-B);
}
//The * arithmetic operator. Returns A*B. It may throw an ErrorOverflow object.
const CLargeInt operator*(const CLargeInt& A, const CLargeInt& B)
{
long i, j, k;
long sum, carryOver;
CLargeInt output;
//Deal with A=0 or B=0 as a special case.
if(A.Size==0)
return A;
if(B.Size==0)
return B;
//A!=0 and B!=0.
//Calculate the sign of A*B.
if(A.bPositive==B.bPositive)
output.bPositive = true;
else
output.bPositive = false;
//output will take up A.Size+B.Size-{1 or 0} bytes. Provisionally set the size.
if(A.Size+B.Size-1>LI_MAX)
throw ErrorOverflow(); //Overflow will occur.
output.Size = A.Size+B.Size-1;
//Initialize output to 0.
for(i=0;i=LI_MAX)
throw ErrorOverflow();
output.Data[output.Size] = carryOver;
output.Size++;
}
}
return output;
}
//The / arithmetic operator. Returns A/B where / is integer division (see Divide() for a more
//precise definition of /). If B=0 an ErrorDivisionByZero object is thrown.
const CLargeInt operator/(const CLargeInt& A, const CLargeInt& B)
{
CLargeInt output, Temp;
CLargeInt::Divide(A,B,output,Temp);
return output;
}
//The % arithmetic operator. Returns A%B (see Divide() for a more precise definition of %).
//If B=0 an ErrorDivisionByZero object is thrown.
const CLargeInt operator%(const CLargeInt& A, const CLargeInt& B)
{
CLargeInt output, Temp;
CLargeInt::Divide(A,B,Temp,output);
return output;
}
//Returns (A==B).
const bool operator==(const CLargeInt& A, const CLargeInt& B)
{
return (CLargeInt::Compare(A,B)==0);
}
//Returns (A!=B).
const bool operator!=(const CLargeInt& A, const CLargeInt& B)
{
return (CLargeInt::Compare(A,B)!=0);
}
//Returns (AB).
const bool operator>(const CLargeInt& A, const CLargeInt& B)
{
return (CLargeInt::Compare(A,B)==1);
}
//Returns (A>=B).
const bool operator>=(const CLargeInt& A, const CLargeInt& B)
{
return (CLargeInt::Compare(A,B)!=-1);
}
//Used to convert the CLargeInt object into a long. If the value stored in the object is
//larger than what can be stored in a long variable it throws an ErrorOverflow object.
long CLargeInt::ConvertToLong() const
{
long i;
long max, min;
long result;
//Check if overflow occurs.
if(Size>=sizeof(long))
{
//max = 2^{8*sizeof(long)-1} - 1,
//min = -2^{8*sizeof(long)-1}.
//To avoid overflow, we define them as follows:
max = (1L<<(8*sizeof(long)-2))-1;
max += 1L<<(8*sizeof(long)-2);
min = -max;
min -= 1;
if(*this=LI_MAX)
throw ErrorOverflow();
//Remove the least significant byte of input and store it in Data[i].
Data[i] = input%256;
input = input/256;
if(input==0)
{
Size = i+1;
return;
}
}
return;
}
//Used by the comparison operators to compare two large integers A, B.
//Returns -1 if A < B,
// 0 if A = B,
// 1 if A > B.
long CLargeInt::Compare(const CLargeInt& A, const CLargeInt& B)
{
long i;
if(A.bPositive!=B.bPositive)
{
if(A.bPositive==true)
return 1; //A >= 0 > B.
else
return -1; //A < 0 <= B.
}
//A and B have the same sign.
if(A.Size!=B.Size)
{
if((A.bPositive==true && A.Size>B.Size)
|| (A.bPositive==false && A.Size B.
else
return -1; //A < B.
}
//A.Size = B.Size and A.bPositive = B.bPositive.
//Check the magnitude of the integers, beginning with the most significant byte.
for(i=A.Size-1;i>=0;i--)
{
if(A.Data[i]>B.Data[i])
{
if(A.bPositive==true)
return 1; //A > B.
else
return -1; //A < B.
}
if(A.Data[i] B.
}
//A.Data[i] = B.Data[i]. Check the next most significant byte.
}
return 0; //A = B.
}
//Used by the integer division operators / and %. Define |input| to be the magnitude of
//input, and sign(input) to be +1 if input>=0 and -1 if input<0. The function calculates
//outputQ, and outputR as follows:
//outputQ = sign(inputN)*sign(inputD)*(|inputN|/|inputD|), where '/' is integer division,
//outputR = sign(inputN)*(|inputN|%|inputD|).
//The choice of signs for outputQ and outputR were chosen so that inputN = inputD *
//outputQ + outputR. If inputD = 0 an ErrorDivisionByZero class is thrown. The function
//assumes outputQ resides in a different address to inputN, inputD, and outputR. It also
//assumes outputR resides in a different address to inputN, inputD, and outputQ.
void CLargeInt::Divide(const CLargeInt& inputN, const CLargeInt& inputD,
CLargeInt& outputQ, CLargeInt& outputR)
{
long i, j;
long sum, carryOver;
bool bSubtractD;
long bitD, bitN;
long nBit, nByte;
long rBit, rByte;
long overflowByte;
//Deal with inputN=0 and inputD=0 as a special case.
if(inputD.Size==0)
throw ErrorDivisionByZero();
if(inputN.Size==0)
{
outputQ = 0;
outputR = 0;
return;
}
//We will do the division via the long division method in binary.
//Find the leading bit of inputD.
j = inputD.Data[inputD.Size-1];
for(i=7;i>=0;i--)
{
if((j>>i)%2==1)
break;
}
bitD = i;
//Find the leading bit of inputN.
j = inputN.Data[inputN.Size-1];
for(i=7;i>=0;i--)
{
if((j>>i)%2==1)
break;
}
bitN = i;
//Check if |inputD|>|inputN| obviously holds.
if(inputD.Size>inputN.Size || (inputD.Size==inputN.Size && bitD>bitN))
{
outputQ = 0;
outputR = inputN;
return;
}
//|inputN| is stored in at least as many bits as |inputD|.
//|inputD| is stored in "(inputD.Size-1)*8+bitD+1" bits. Fill outputR with the
//"(inputD.Size-1)*8+bitD+1" most significant bits of inputN.
outputR.Size = inputD.Size;
for(i=0;i>nBit)&1)<= |inputD|. If it is we set the bit in outputQ to 1, and
//subtract |inputD| from |outputR|.
if(overflowByte!=0)
bSubtractD = true; //|outputR| takes up more bytes than |inputD|.
else
{
for(i=outputR.Size-1;i>=0;i--)
{
if(outputR.Data[i]>inputD.Data[i])
{
bSubtractD = true;
break;
}
if(outputR.Data[i]=0)
{
outputR.Data[i] = sum;
carryOver = 0;
}
else
{
outputR.Data[i] = 256+sum;
carryOver = -1;
}
}
//Set the bit of outputQ to 1.
outputQ.Data[nByte] |= 1<>nBit)&1;
for(i=0;i=0;i--)
out << char('0'+digit[i]);
return out;
}
//The default constructor, sets the fraction to 0.
CLargeFraction::CLargeFraction()
{
N = 0;
D = 1;
return;
}
//Allows an unsigned int to be automatically cast into a CLargeFraction object. It may throw
//an ErrorOverflow object.
CLargeFraction::CLargeFraction(unsigned int input)
{
N = input;
D = 1;
return;
}
//Allows an int to be automatically cast into a CLargeFraction object. It may throw an
//ErrorOverflow object.
CLargeFraction::CLargeFraction(int input)
{
N = input;
D = 1;
return;
}
//Allows an unsigned long to be automatically cast into a CLargeFraction object. It may throw
//an ErrorOverflow object.
CLargeFraction::CLargeFraction(unsigned long input)
{
N = input;
D = 1;
return;
}
//Allows a long to be automatically cast into a CLargeFraction object. It may throw an
//ErrorOverflow object.
CLargeFraction::CLargeFraction(long input)
{
N = input;
D = 1;
return;
}
//Allows a CLargeInt object to be automatically cast into a CLargeFraction object.
CLargeFraction::CLargeFraction(const CLargeInt& input)
{
N = input;
D = 1;
return;
}
//The + unary operator. Used to evaluate +A, when A is a CLargeFraction object. It may throw
//an ErrorDivisionByZero object.
const CLargeFraction CLargeFraction::operator+() const
{
CLargeFraction output = *this;
output.Simplify();
return output;
}
//The - unary operator. Used to evaluate -A, when A is a CLargeFraction object. It may throw
//an ErrorDivisionByZero object.
const CLargeFraction CLargeFraction::operator-() const
{
CLargeFraction output;
output.N = -N;
output.D = D;
output.Simplify();
return output;
}
//The + arithmetic operator. Returns A+B. It may throw an ErrorOverflow or
//ErrorDivisionByZero object.
const CLargeFraction operator+(const CLargeFraction& A, const CLargeFraction& B)
{
CLargeFraction output;
output.N = A.N*B.D+B.N*A.D;
output.D = A.D*B.D;
output.Simplify();
return output;
}
//The - arithmetic operator. Returns A-B. It may throw an ErrorOverflow or
//ErrorDivisionByZero object.
const CLargeFraction operator-(const CLargeFraction& A, const CLargeFraction& B)
{
return A+(-B);
}
//The * arithmetic operator. Returns A*B. It may throw an ErrorOverflow or
//ErrorDivisionByZero object.
const CLargeFraction operator*(const CLargeFraction& A, const CLargeFraction& B)
{
CLargeFraction output;
output.N = A.N*B.N;
output.D = A.D*B.D;
output.Simplify();
return output;
}
//The / arithmetic operator. Returns A/B. It may throw an ErrorOverflow or
//ErrorDivisionByZero object.
const CLargeFraction operator/(const CLargeFraction& A, const CLargeFraction& B)
{
CLargeFraction output;
if(B.D==0)
throw ErrorDivisionByZero();
output.N = A.N*B.D;
output.D = B.N*A.D;
output.Simplify();
return output;
}
//Returns (A==B). It may throw an ErrorOverflow or ErrorDivisionByZero object.
const bool operator==(const CLargeFraction& A, const CLargeFraction& B)
{
return (CLargeFraction::Compare(A,B)==0);
}
//Returns (A!=B). It may throw an ErrorOverflow or ErrorDivisionByZero object.
const bool operator!=(const CLargeFraction& A, const CLargeFraction& B)
{
return (CLargeFraction::Compare(A,B)!=0);
}
//Returns (AB). It may throw an ErrorOverflow or ErrorDivisionByZero object.
const bool operator>(const CLargeFraction& A, const CLargeFraction& B)
{
return (CLargeFraction::Compare(A,B)==1);
}
//Returns (A>=B). It may throw an ErrorOverflow or ErrorDivisionByZero object.
const bool operator>=(const CLargeFraction& A, const CLargeFraction& B)
{
return (CLargeFraction::Compare(A,B)!=-1);
}
//Removes any common factors between the numerator and the denominator and makes the
//denominator positive. It may throw an ErrorDivisionByZero object.
void CLargeFraction::Simplify()
{
CLargeInt R1, R2, R3;
if(D==0)
throw ErrorDivisionByZero();
if(N==0)
{
D = 1;
return;
}
//Apply the Euclidean algorithm to determine the highest common factor.
R1 = N;
R2 = D;
while(true)
{
R3 = R1%R2;
if(R3==0)
break; //R2 is the highest common factor.
R1 = R2;
R2 = R3;
}
//Divide by the highest common factor.
N = N/R2;
D = D/R2;
//Make the denominator positive.
if(D<0)
{
N = -N;
D = -D;
}
return;
}
//Used by the comparison operators to compare two fractions A, B.
//Returns -1 if A < B,
// 0 if A = B,
// 1 if A > B.
//It may throw an ErrorOverflow or ErrorDivisionByZero object.
long CLargeFraction::Compare(const CLargeFraction& A, const CLargeFraction& B)
{
long result;
CLargeInt L;
if(A.D==0 || B.D==0)
throw ErrorDivisionByZero();
L = A.N*B.D-A.D*B.N;
if(L==0)
return 0; //A = B.
if(L>0)
result = 1; //A > B, if both A.D>0 and B.D>0.
else
result = -1;//A < B, if both A.D>0 and B.D>0.
//Take the signs of A.D and B.D into account.
if(A.D<0)
result = -result;
if(B.D<0)
result = -result;
return result;
}
//Allows us to use << operator to output the CLargeFraction object F to the screen or a file.
ostream& operator<<(ostream& out, const CLargeFraction& F)
{
out << F.N << "/" << F.D;
return out;
}
//Allows us to use << operator to output the CGraph object g to the screen or a file.
ostream& operator<<(ostream& out, const CGraph& g)
{
long i, j, k;
bool bFirst;
//Display the number of vertices.
out << "Order " << g.nV << " : ";
//Display the edges.
out << "Edges = {";
//Used to help us put the commas in the right place.
bFirst = true;
for(i=0;ipNext;
delete pHead;
pHead = pTemp;
}
return;
}
//Used to add a new graph to the start of the list. It will throw a bad_alloc object if there
//is not enough memory to add the new graph.
void CLinkedList::Add(CGraph& g)
{
CListNode* pNew;
pNew = new CListNode;
pNew->g = g;
pNew->coeff = 0;
pNew->pNext = pHead;
pHead = pNew;
return;
}
//Returns the number of nodes in the list.
long CLinkedList::Size()
{
long size;
CListNode* pNode;
size = 0;
pNode = pHead;
while(pNode!=NULL)
{
size++;
pNode = pNode->pNext;
}
return size;
}
//The default constructor, initializes pointers to NULL.
CTermInfo::CTermInfo()
{
FlagList = NULL;
Matrix = NULL;
Basis = NULL;
return;
}
//The destructor. A wrapper for Free().
CTermInfo::~CTermInfo()
{
Free();
return;
}
//Deallocates memory.
void CTermInfo::Free()
{
long i;
delete[] FlagList;
FlagList = NULL;
if(Matrix!=NULL)
{
for(i=0;i:", e.g. "Dimension:3". The string 'str[]' is read in using
//CheckString(). The integer is read using ReadLargeInteger() and returned via 'output'. It
//allows optional whitespaces either side of the colon. It may throw an ErrorOverflow object
//if the integer is too large.
//The function returns 0 on success,
// 1 if the input is unexpected,
// 2 if the integer is too large to be stored in a long.
//The last character that was read (but not yet interpreted) should be passed to the function
//via 'c'. When the function returns 'c' will hold the last character read by the function.
long ReadLong(istream& in, const char* str, long& output, char& c)
{
CLargeInt L;
output = 0;
RemoveWhitespace(in,c);
if(CheckString(in,str,c)==false)
return 1; //Unexpected input.
RemoveWhitespace(in,c);
if(c!=':')
return 1; //Unexpected input.
c = ReadChar(in);
RemoveWhitespace(in,c);
if(ReadLargeInteger(in,L,c)==false)
return 1; //Unexpected input.
try
{
output = L.ConvertToLong();
}
catch(ErrorOverflow& e)
{
return 2; //L is too large for 'output'.
}
return 0; //Success.
}
//This function is used in parsing the input file. It removes any whitespaces then tries to
//read in the edge set of a graph. The function assumes g.nV has been set and uses this to
//verify the edges are valid. The edge set should take the form "{,,...,}",
//where is a three digit integer representing the edge. For example "{123, 325, 167}"
//or the empty set "{}". The three integers representing the edge should be distinct and be
//between 1 and g.nV (inclusive). Whitespaces either side of the sequences are
//ignored. The function returns true if it succeeds, and false otherwise. The last character
//that was read (but not yet interpreted) should be passed to the function via 'c'. When the
//function returns 'c' will hold the last character read by the function.
bool ReadGraphEdgeSet(istream& in, CGraph& g, char& c)
{
long i, j, k;
//Initialize the adjacency matrix.
for(i=0;i<9;i++)
for(j=0;j<9;j++)
for(k=0;k<9;k++)
g.M[i][j][k] = false;
//The first non-whitespace character should be '{'.
RemoveWhitespace(in,c);
if(c!='{')
return false;
c = ReadChar(in);
RemoveWhitespace(in,c);
//Check for empty edge set
if(c=='}')
{
c = ReadChar(in);
return true;
}
while(true)
{
//Read edge.
if('1'<=c && c<='9')
i = c-'1';
else
return false;
c = ReadChar(in);
if('1'<=c && c<='9')
j = c-'1';
else
return false;
c = ReadChar(in);
if('1'<=c && c<='9')
k = c-'1';
else
return false;
c = ReadChar(in);
//Check ijk is a valid edge.
if(i==j || i==k || j==k)
return false;
if(i>=g.nV || j>=g.nV || k>=g.nV)
return false;
//Set the edge.
g.M[i][j][k] = true;
g.M[i][k][j] = true;
g.M[j][i][k] = true;
g.M[j][k][i] = true;
g.M[k][i][j] = true;
g.M[k][j][i] = true;
//The next non-whitespace character should be '}' or ','.
RemoveWhitespace(in,c);
if(c=='}')
{
c = ReadChar(in);
return true;
}
if(c!=',')
return false;
c = ReadChar(in);
RemoveWhitespace(in,c);
}
}
//This function is used in parsing the input file. It removes any whitespaces then tries to
//read in a linear combination of flags and store it in Term.Basis[basisIndex][]. The
//function assumes memory has already been allocated for Term.Basis[][]. It also assumes
//Term.noOfFlags has been set and uses this to verify the flags read in are valid. The linear
//combination of flags should take the form
//"()<+ or ->()<+ or ->...".
//For example "1/2(F1)-4/3(F2)+2/3(F5)+1/7(F6)". An ErrorOverflow object may be thrown if an
//integer that is read is too large to be stored.
//The function returns 0 if it succeeds,
// 1 if the input is incorrectly formatted,
// 2 if a flag index does not lie in [1,Term.noOfFlags],
// 3 if a coefficient causes a division by zero.
//The last character that was read (but not yet interpreted) should be passed to the function
//via 'c'. When the function returns 'c' will hold the last character read by the function.
long ReadBasis(istream& in, CTermInfo& Term, long basisIndex, char& c)
{
long i;
bool bFirstTerm;
CLargeInt L;
CLargeFraction Frac;
long index;
//Zero out basis.
for(i=0;i\".";
cout << "Order of subgraphs H : " << orderOfH << endl;
}
//Read noOfForbiddenGraphs.
{
r = ReadLong(FileIn,"Number of forbidden graphs",noOfForbiddenGraphs,c);
if(r==2)
throw "1:";
if(r!=0 || noOfForbiddenGraphs<1)
throw "0:Expected to read \"Number of forbidden graphs : "
"\".";
cout << "Number of forbidden graphs : " << noOfForbiddenGraphs << endl;
ForbiddenGraph = new CGraph[noOfForbiddenGraphs];
}
//Read ForbiddenGraph[].
{
//Read "Forbidden graphs :".
RemoveWhitespace(FileIn,c);
if(CheckString(FileIn,"Forbidden graphs",c)==false)
throw "0:Expected to read \"Forbidden graphs : \".";
RemoveWhitespace(FileIn,c);
if(c!=':')
throw "0:Expected to read \"Forbidden graphs : \".";
c = ReadChar(FileIn);
cout << "Forbidden graphs :" << endl;
//Read the graphs.
for(i=0;i9 || ForbiddenGraph[i].nV<0)
throw "2:";
RemoveWhitespace(FileIn,c);
if(c!=':')
throw "2:";
//Read the edge set.
c = ReadChar(FileIn);
if(ReadGraphEdgeSet(FileIn,ForbiddenGraph[i],c)==false)
throw "2:";
cout << ForbiddenGraph[i] << endl;
}
}
//Read bForbidOnlyInducedSubgraphs.
{
RemoveWhitespace(FileIn,c);
if(CheckString(FileIn,"Forbid only induced subgraphs",c)==false)
throw "0:Expected to read \"Forbid only induced subgraphs : \".";
RemoveWhitespace(FileIn,c);
if(c!=':')
throw "0:Expected to read \"Forbid only induced subgraphs : \".";
c = ReadChar(FileIn);
if(ReadBool(FileIn,bForbidOnlyInducedSubgraphs,c)==false)
throw "0:Expected to read \"Forbid only induced subgraphs : \".";
cout << "Forbid induced subgraphs : ";
cout << (bForbidOnlyInducedSubgraphs?"yes":"no") << endl;
}
//Read noOfTerms.
{
r = ReadLong(FileIn,"Number of terms",noOfTerms,c);
if((r==0 && noOfTerms<0) || r==1)
throw "0:Expected to read \"Number of terms : \".";
if(r==2)
throw "1:";
cout << "Number of terms : " << noOfTerms << endl;
if(noOfTerms==0)
{
cout << endl;
cout << "Finished reading the data file." << endl;
cout << endl;
FileIn.close();
return true;
}
//Allocate memory for the term data.
Term = new CTermInfo[noOfTerms];
}
cout << endl;
try
{
//Read the Term[] data.
for(nTerm=1;nTerm<=noOfTerms;nTerm++)
{
//Read the header e.g. "--- Term 1 ---".
{
cout << "\rReading term " << nTerm << " : ";
cout << "Reading the header." << flush;
RemoveWhitespace(FileIn,c);
if(ReadRepeatingChar(FileIn,'-',c)==false)
throw "3:";
RemoveWhitespace(FileIn,c);
if(CheckString(FileIn,"Term",c)==false)
throw "3:";
RemoveWhitespace(FileIn,c);
if(ReadLargeInteger(FileIn,L,c)==false)
throw "3:";
if(L!=nTerm)
throw "3:";
RemoveWhitespace(FileIn,c);
if(ReadRepeatingChar(FileIn,'-',c)==false)
throw "3:";
}
//Read Term[nTerm-1].Type.nV.
{
cout << "\rReading term " << nTerm << " : ";
cout << "Reading the order of the type." << flush;
r = ReadLong(FileIn,"Order of type",Term[nTerm-1].Type.nV,c);
if(r!=0 || Term[nTerm-1].Type.nV<0 || orderOfH]>\".";
//Initialize the edge set of the type.
for(i=0;i<9;i++)
for(j=0;j<9;j++)
for(k=0;k<9;k++)
Term[nTerm-1].Type.M[i][j][k] = false;
}
//Read the order of the flags.
{
cout << "\rReading term " << nTerm << " : ";
cout << "Reading the order of the flags." << flush;
//Temporarily store the order in 'n'.
r = ReadLong(FileIn,"Order of flags",n,c);
if(r!=0 || n,\n/2]>\".";
}
//Read Term[nTerm-1].noOfFlags.
{
cout << "\rReading term " << nTerm << " : ";
cout << "Reading the number of flags. " << flush;
r = ReadLong(FileIn,"Number of flags",Term[nTerm-1].noOfFlags,c);
if(r==2)
throw "1:";
if(r!=0 || Term[nTerm-1].noOfFlags<0)
throw "0:Expected to read \"Number of flags : "
"\".";
}
//Allocate memory for Term[nTerm-1].FlagList[].
{
cout << "\rReading term " << nTerm << " : ";
cout << "Allocating memory for the flags." << flush;
if(0\".";
if(Term[nTerm-1].noOfFlags==0 && Term[nTerm-1].MatrixDim>0)
throw "0:Expected the dimension to be 0, "
"as the number of flags is 0.";
}
//Allocate memory for Term[nTerm-1].Matrix[][] and Term[nTerm-1].Basis[][].
if(Term[nTerm-1].MatrixDim>0)
{
cout << "\rReading term " << nTerm << " : ";
cout << "Allocating memory for the matrix. " << flush;
Term[nTerm-1].Matrix = new CLargeFraction*[Term[nTerm-1].MatrixDim];
for(i=0;i.";
}
cout << "\rReading term " << nTerm << " : ";
cout << " ";
for(i=0;i]>\".";
if(r==3)
throw "0:A denominator of one of the fractions is zero.";
}
cout << "\rReading term " << nTerm << " : ";
cout << " ";
for(i=0;i : \"." << endl;
cout << "For example \"4 : {123, 124, 234}\"." << endl;
break;
case '3':
cout << "*** ERROR: Unexpected input. ***" << endl;
cout << "Expected to read \"--- Term " << nTerm << " ---\"." << endl;
break;
case '4':
cout << "*** ERROR: Unexpected input. ***" << endl;
cout << "Expected to read \"F" << nFlag << " : \"." << endl;
cout << "An example of an edge set for an order 4 graph is" << endl;
cout << "\"{123, 124, 234}\"." << endl;
break;
case '5':
cout << "*** ERROR: Unexpected input. ***" << endl;
cout << "Expected to read \"B" << nBasis;
cout << " : \"." << endl;
cout << "The linear combination of flags should be in the form" << endl;
cout << "\"()<+ or ->()<+ or ->...\"." << endl;
cout << "For example \"1/2(F1)-4/3(F2)+2/3(F5)+1/7(F6)\"." << endl;
break;
default:
cout << "*** ERROR: Unknown. ***" << endl;
cout << "Thrown data : \"" << str << "\"" << endl;
}
}
catch(ErrorOverflow& e)
{
cout << endl;
cout << "*** ERROR: Overflow. ***" << endl;
cout << "Integer read was too large to store." << endl;
cout << "Try increasing LI_MAX in the source code." << endl;
}
catch(bad_alloc& e)
{
cout << endl;
cout << "*** ERROR: Bad allocation. ***" << endl;
cout << "Probably caused by lack of memory." << endl;
cout << "Try decreasing LI_MAX in the source code." << endl;
}
catch(...)
{
cout << endl;
cout << "*** ERROR: Unknown. ***" << endl;
}
cout << endl;
FileIn.close();
return false;
}
//Returns true if all matrices in Term[] are positive semidefinite (PSD). The function
//assumes Term[] and noOfTerms have been initialized. A bad_alloc or ErrorOverflow object may
//be thrown.
bool IsPSD()
{
//Overview of the algorithm:
//
//This function checks if the matrices stored in Term[] are indeed positive semidefinite.
//
//It takes the matrix M and essentially tries to Cholesky decompose it, in particular to
//avoid square root operations we choose the variant form of U^T D U where D is diagonal
//(and U is upper triangle), however we don't really care about U or D just that the
//process is possible.
//
//The algorithm works by doing symmetric row and column operations on M. If C is the
//operation of adding lambda times column i to column j then, C^T M C is the operation of
//adding lambda times column i to column j, and lambda times row i to row j. Note that
//C^T M C is symmetric and the property of whether it is PSD or not is preserved
//(provided i does not equal j). Using these symmetric row and column operations we can
//try to diagonalise M, and in the process prove whether it is PSD or not.
//
//If M is a 1 by 1 matrix then it is easy to check if it is PSD or not. If it isn't a
//1 by 1 matrix then do the following:
//
//If M[0][0]<0 then the matrix is not PSD.
//
//If M[0][0]=0 then either there exists an i such that M[0][i] is not 0 in which case the
//matrix is not PSD, or all M[0][i]=M[i][0]=0, in which case it is equivalent to remove
//row 0 and column 0 to create the matrix M' and to check whether M' is PSD or not by
//restarting the process with M=M'.
//
//If M[0][0]>0 then subtract rows and columns to make M[i][0]=M[0][i]=0 for all i (except
//i=0). If we throw away row 0 and column 0 the resulting matrix M' is PSD if and only if
//M is. We can check M' by restarting the process with M=M'.
//
//It is costly to create M' each time we deal with row 0 and column 0 so instead we will
//continue to modify M and keep track of how many rows and columns we have discarded
//through the use of the variable n.
//
//Note that if we keep track of the operations we would get the U^T D U Cholesky
//decomposition, however the entries are likely ro be extremely large, which is why the
//data files do not store the matrices in this form.
long i, j, n;
long nTerm;
CLargeFraction** M;
long dim;
CLargeFraction F;
CTermInfo Temp;
for(nTerm=1;nTerm<=noOfTerms;nTerm++)
{
cout << "\rChecking matrix " << nTerm << " of " << noOfTerms << " : ";
cout << "Freeing memory. " << flush;
Temp.Free();
cout << "\rChecking matrix " << nTerm << " of " << noOfTerms << " : ";
cout << "Allocating memory." << flush;
if(Term[nTerm-1].MatrixDim<=0)
continue;
//Copy Term[nTerm-1].Matrix[][] to Temp.Matrix[][]. Use dim and M to simplify the
//expressions.
dim = Term[nTerm-1].MatrixDim;
Temp.MatrixDim = dim;
M = new CLargeFraction*[dim];
Temp.Matrix = M;
for(i=0;i=n+1. Furthermore row n
//and column n will end up consisting entirely of zeros, with the exception of
//M[n][n] which will be some positive value. Hence to save time we will skip
//these operations. It is enough to check the submatrix consisting of the entries
//M[i][j] where i,j>=n+1. The entire matrix is PSD if and only if the submatrix
//is PSD.
}
cout << "\rChecking matrix " << nTerm << " of " << noOfTerms << " : ";
cout << " ";
for(i=0;i<2*NumberOfDigits(dim);i++)
cout << " ";
}
cout << "\r ";
for(i=0;i<2*NumberOfDigits(noOfTerms);i++)
cout << " ";
cout << "\rAll matrices are positive semidefinite." << endl;
cout << endl;
return true;
}
//Returns the number of edges in the graph g.
long NumberOfEdges(CGraph& g)
{
long i, j, k;
long edges;
edges = 0;
for(i=0;ij>k).
for(i=0;i i > j > k >=0).
//Specifically ijk > rst if
// i>r,
//or i==r and j>s,
//or i==r and j==s and k>t.
//Our integer is a "gIn.nV choose 3" bit number where each bit is associated with a
//triple in our ordering. The largest triple is associated with the least significant
//bit, and the second largest triple with the second least significant bit, etc. The
//value of the bits in the integer are given by the associated triples, with a value
//of 1 if ijk is an edge in g and 0 if it isn't.
//We will not explicitly construct such integers, but by examining the edge set of
//gOut and gIn, under the permutation p, we will make a comparison of which
//associated integer would be smaller.
//Cycle through the triples from the smallest to the largest. Note that we can take
//i >= orderOfType because of the way we are permuting gIn.
for(i=orderOfType;ig with an extra vertex v.
g = pNode->g;
g.nV++;
//We need to determine which edges containing vertex v our new graph will
//contain.
noOfEdgesToSet = (g.nV-1)*(g.nV-2)/2;
//There are 2^noOfEdgesToSet possible choices for the set of edges containing v.
//We let b encode which of the edges we include in g via the value of its bits.
//Bits 0, 1, 2, 3, 4, 5, 6, 7, 8, ... of b tells us whether
//edges 01v,02v,12v,03v,13v,23v,04v,14v,24v, ... respectively are in graph g.
//Test all sets of edges containing v.
for(b=0;b<1<>p)%2==1)
e = true;
else
e = false;
//Update the adjacency matrix.
g.M[i][j][k] = e;
g.M[i][k][j] = e;
g.M[j][i][k] = e;
g.M[j][k][i] = e;
g.M[k][i][j] = e;
g.M[k][j][i] = e;
p++;
}
//Check if the graph is admissible.
for(i=0;ipNext;
}
//Make NewList the new OldList.
OldList.Free();
OldList.pHead = NewList.pHead;
NewList.pHead = NULL;
cout << "\rGenerating order " << n << " graphs : ";
for(i=0;i<2*NumberOfDigits(total);i++)
cout << " ";
}
//Move OldList to H.
H.Free();
H.pHead = OldList.pHead;
OldList.pHead = NULL;
//Display how large H is.
n = H.Size();
cout << "\r ";
if(n==1)
cout << "\rFound 1 graph." << endl;
else
cout << "\rFound " << n << " graphs."<< endl;
cout << endl;
return;
}
//Displays 'Density' as both a fraction and a decimal. This function is used by
//CalculateDensity() to display an upper bound of the Turan density. It may throw an
//ErrorOverflow object.
void DisplayDensity(CLargeFraction Density)
{
long i;
bool bNegative;
bool bRounded;
CLargeInt Digits, PowerOf10;
Density.Simplify();
cout << "\rUpper bound of the Turan density" << endl;
if(Density.D==1)
{
cout << " = " << Density.N << endl;
cout << endl;
return;
}
cout << " = " << Density;
//Display Density as a decimal to 'noOfDecimalPlaces' decimal places.
//Check if Density is negative.
if(Density<0)
{
Density = -Density;
bNegative = true;
}
else
bNegative = false;
//Calculate the digits that will make up the decimal.
//Calculate 10^noOfDecimalPlaces.
PowerOf10 = 1;
for(i=0;icoeff = CLargeFraction(NumberOfEdges(pNode->g)*6)
/(orderOfH*(orderOfH-1)*(orderOfH-2));
pNode = pNode->pNext;
}
cout << "\r ";
}
//Calculate the contribution of the terms.
for(nTerm=1;nTerm<=noOfTerms;nTerm++)
{
CTermInfo& T = Term[nTerm-1];
cout << "\rEvaluating term " << nTerm << " of " << noOfTerms << " : " << flush;
if(T.MatrixDim<=0 || T.noOfFlags<=0)
{
T.Free();
continue;
}
//Replace the flags with their "minimal" isomporhic copy.
{
cout << "\rEvaluating term " << nTerm << " of " << noOfTerms << " : ";
cout << "Initializing flags." << flush;
orderOfType = T.Type.nV;
orderOfFlag = T.FlagList[0].nV;
for(i=0;i/(orderOfH!). To avoid costly CLargeFraction operations we will
//pre-divide the matrix by (orderOfH!).
for(i=0;ig.M[mapping[r][i]][mapping[r][j]][mapping[r][k]];
}
//Check types match to T.Type, by temporarily reducing the vertex set of
//f[0].
f[0].nV = orderOfType;
if(IsCopy(f[0],T.Type)==false)
{
pNode = pNode->pNext;
continue;
}
f[0].nV = orderOfFlag;
//Find the isomorphic copy of each flag in T.FlagList[], and store the
//position at which it appears in index[].
for(r=0;r<2;r++)
FindMinIso(f[r],f[r],orderOfType);
for(index[0]=0;index[0]coeff, we
//will update pNode->coeff directly.
pNode->coeff = pNode->coeff+T.Matrix[index[0]][index[1]];
}
}
pNode = pNode->pNext;
}
}
cout << "\rEvaluating term " << nTerm << " of " << noOfTerms << " : ";
cout << " ";
for(i=0;i<2*NumberOfDigits(Factorial[orderOfH]);i++)
cout << " ";
T.Free();
}
cout << "\r ";
for(i=0;i<2*NumberOfDigits(noOfTerms);i++)
cout << " ";
//Find the upper bound of the Turan density.
{
cout << "\rFinding the maximum coefficient." << flush;
Density = 0;
pNode = H.pHead;
while(pNode!=NULL)
{
if(Densitycoeff)
Density = pNode->coeff;
pNode = pNode->pNext;
}
DisplayDensity(Density);
}
return;
}
//The main function. Expects argc to be 2, argv[0] to be the name of the executable and
//argv[1] to be the name of the input file.
int main(int argc, char* argv[])
{
long i;
bool bHasSpaces;
try
{
cout << endl;
//Check that a file was passed to the program via the command line.
if(argc!=2)
{
//Check if argv[0][] has spaces.
bHasSpaces = false;
i = 0;
while(argv[0][i]!='\0')
{
if(argv[0][i]==' ')
{
bHasSpaces = true;
break;
}
i++;
}
cout << "Usage : " << endl;
if(bHasSpaces==true)
cout << "\"" << argv[0] << "\"";
else
cout << argv[0];
cout << " " << endl;
cout << endl;
return 0;
}
//Read the input file.
if(ReadDataFile(argv[1])==false)
{
Cleanup();
return 0;
}
//Initialize Iso[][].
cout << "\rAllocating memory for the isomorphism mapping." << flush;
InitIso();
cout << "\rChecking the matrices are positive semidefinite....." << endl;
if(IsPSD()==false)
{
cout << endl;
cout << endl;
cout << "*** ERROR: Matrix is not positive semidefinite. ***" << endl;
cout << "For the calculation to return an upper bound of the" << endl;
cout << "Turan density all matrices must be positive semidefinite." << endl;
cout << endl;
Cleanup();
return 0;
}
cout << "\rGenerating admissible subgraphs of order " << orderOfH << "....." << endl;
GenerateH();
cout << "\rCalculating the upper bound of the Turan density....." << endl;
CalculateDensity();
Cleanup();
return 0; //Calculation completed successfully.
}
catch(bad_alloc& e)
{
cout << endl;
cout << endl;
cout << "*** ERROR: Bad allocation. ***" << endl;
cout << "Probably caused by lack of memory." << endl;
cout << "Try decreasing LI_MAX in the source code." << endl;
}
catch(ErrorOverflow& e)
{
cout << endl;
cout << endl;
cout << "*** ERROR: Overflow. ***" << endl;
cout << "The calculation encountered an integer that was too large to store.\n";
cout << "Try increasing LI_MAX in the source code." << endl;
}
catch(ErrorDivisionByZero& e)
{
cout << endl;
cout << endl;
cout << "*** ERROR: Division by zero. ***" << endl;
}
catch(...)
{
cout << endl;
cout << endl;
cout << "*** ERROR: Unknown. ***" << endl;
}
cout << endl;
Cleanup();
return 0;
}