2.7. Appendix: C++ code¶
This section walks you through the writting of a simple C++ code to answer the questions in this exercise. You can choose to write your own code using the following procedure or use the provided incomplete C++ code.
In a directory of your choosing, create a file called ’boltzmann.cpp’
and open it in a text-editor. Within this file, begin by adding the
following:
#include <iostream>
#include <fstream>
#include <math.h>
#include <cstdlib>
#include <string>
using namespace std;
double calculateStateOccupancy(double reducedTemperature, double stateEnergy);
void outputToFile(double *distribution, string outputFile);
int numberOfEnergyLevels = 0;
double reducedTemperature = 0.0;
double partitionFunction = 0.0;
double *distribution;
int main(int argc, char * argv []) {
return 0;
}
This program will calculate the Boltzmann distribution of the fictitious
system at a given temperature and degeneracy. The variables you require
to perform this calculation are numberOfEnergyLevels,
reducedTemperature, normalise and distribution.
2.7.1. Parsing Command-line Arguments¶
The number of energy levels you wish to calculate the distribution over
(numberOfEnergyLevels) as well as the reduced temperature of your
system (reducedTemperature) will need to be passed into your main
function. This can be achieved using the following code within the main
function:
numberOfEnergyLevels = atoi(argv[1]);
reducedTemperature = atof(argv[2]);
2.7.2. Storing and Accessing the Boltzmann Distribution¶
A structure to hold the distribution also needs to be created. Write the
following within your main function, once the value of
numberOfEnergyLevels has been set:
distribution = new double [numberOfEnergyLevels];
This code creates an array of rational numbers of size
numberOfEnergyLevels. The function new allocates a memory block
whose purpose is to hold the contents of this array. This function also
assigns the address of the begining of this memory block to the pointer
distribution. For example if the array contains the values
\([0.9, 0.05, 0.03, 0.02]\), then these individual values can be accessed
via distribution[j] or in pointer-arithmetic by *(distribution+j).
Pointers and arrays are discussed in more detail within the C++ glossary
in section [cppglossary].
2.7.3. Calculating the Boltzmann Distribution¶
You are now ready to design the Boltzmann distribution implementation
within your main function. Enter the following code in an appropriate
place, i.e. once the array distribution has been allocated:
for(int i = 0; i < numberOfEnergyLevels; i++)
{
// To modify. Compute distribution and partition function//
distribution[i]=1.0;
partitionFunction=1.0;
}
Recall that the calculation of the Boltzmann distribution requires the calculation of the partition function, \(Z\). Hence, within this loop you calculate the partition function.
2.7.4. Outputting the Results¶
To finish the flow of the program, you will need to output the results
of your calculation to a file and terminate the program. This can be
achieved by calling the outputToFile() function. Place the following
code after the for-loop:
outputToFile(distribution, "results.dat");
delete [] distribution;
It is worthwhile to note that since you dynamically allocated a new
(un-managed) memory block within your program earlier, you must also
delete it once you are finished. To free the memory associated with this
array you must call delete [] distribution.
You will also need to provide an implementation for the function
outputToFile(). Enter the following code below the main function:
void outputToFile(double *distribution, string outputFile) {
ofstream output;
output.open(outputFile);
for (int i = 0; i < numberOfEnergyLevels; i++) {
output << i << " " << distribution[i]/partitionFunction << "\n";
}
output.close();
}
The purpose of this function is to write the Boltzmann distribution
contained within the array distribution to the file specified by the
path outputFile. If this file does not exist, it will automatically
create it and otherwise will entirely overwrite all contents contained
therein. Writing to files is discussed further in the glossary (section
[cppglossary]). Also note that the normalisation of the Boltzmann
distribution with the partition function occurs within the writing step.
2.7.5. Calculating State Occupancy¶
Finally, place the following code somewhere below your main function. You must now modify this function to complete this Boltzmann distribution program. Hint: recall that you are using reduced temperature.
double calculateStateOccupancy(double reducedTemperature, int i) {
double stateOccupancy = 0.0;
//Calculate the occupancy for state i
return stateOccupancy;
}
Note: The directive #include <math.h> at the begining of your code
includes the math library. This library contains the function exp()
which you can use to calculate \(e^x\) by calling exp(x).
2.7.6. Compiling Your C++ Code¶
To compile the program, navigate your terminal focus to the directory
which contains boltzmann.cpp, and type the following:
g++ -std=c++11 boltzmann.cpp -o boltzmann.x
This will produce an executable file called boltzmann.x in your
current directory. You can execute this program using the following
command-line arguments:
./boltzmann.x numberOfEnergyLevels reducedTemperature
2.7.7. GNUPlot: Displaying Results¶
A convenient way to quickly graph the data contained within your results
file is to use the gnuplot program. Open a terminal and navigate the
terminal focus to the directory containing your results file. Enter the
command gnuplot to start the program.
2.7.7.1. Plotting¶
Your results file only contains 2 columns thus you can simply plot the graph directly using the following command:
plot 'results.dat'
If however you have multiple columns from which to plot, you can use the
using specifier as follows:
plot 'results.dat' using 2:3
which will plot the third column (y-axis) against the second (x-axis). Extending this, to plot multiple graphs on one image, you can type the following:
plot 'results1.dat' using 1:2 title 'graph1' with lines, \
'results2.dat' using 1:2 title 'graph2' with lines, \
'results3.dat' using 1:2 title 'graph3' with lines
In this example each data point is connected with a straight line using
the with lines command. Note that with lines may be abbreviated as
w l, using as u, and title as t.
2.7.7.2. Labelling Axes¶
Axis labels are added with the set command. The following commands
label the x-axis and y-axis respectively:
set xlabel 'Energy Level'
set ylabel 'Occupancy'
2.7.7.3. Output to File¶
To write the graph to file with PNG format, the following commands can be used:
set term png
set output 'results.png'
replot
set term x11
2.7.8. C++ Glossary¶
2.7.8.1. Pointers and Arrays¶
2.7.8.1.1. Pointers¶
Memory in a computer can be viewed simply as a succession of memory cells, each one byte in size, and each with a unique address. When a variable is declared, the memory block required to store its value is assigned at a specific location (its memory address). Pointers are simple structures which allow you to obtain the memory address of a particular variable, and are declared as follows:
type * name;
where type refers to the variable type this pointer is pointing to.
The memory address of a variable can then be accessed with the
address-of operator (&) as follows:
int bar = 1000;
int * foo = &bar;
which assigns to the pointer foo the address to the memory block
containing the contents of the variable bar. The value stored within
the memory block addressed by the pointer is accessed by using the
dereference operator (*):
int bar = 1000;
int * foo = &bar;
cout << foo << endl; //prints memory address of variable bar
cout << *foo << endl; //dereference foo to print 1000
Finally, when you create a dynamically allocated variable using the
new operator, you must declare this statement with a pointer as
follows:
Object * objectPointer = new Object;
Object * objectArray = new Object[3];
such that when its time to free the memory associated with this
variable, you can use the delete operator (or delete [] for arrays)
to act on its pointer:
delete objectPointer;
delete[] objectArray;
2.7.8.1.2. Arrays¶
The concept of an array is very much akin to that of a pointer. In C++ arrays are simply wrapped pointers which address the starting memory block of the contents contained within the array. This is illustrated in the following example:
int myarray [3];
int * arrayPointer = &myarray;
myarray[0] = 5;
myarray[1] = 6;
myarray[2] = 7; // array now contains {5, 6, 7}
cout << *(arrayPointer) << ", " << myarray[0] << endl; //prints 5, 5
cout << *(arrayPointer+1) << ", " << myarray[1] << endl; //prints 6, 6
2.7.8.2. Writing to Files¶
The classes ofstream and ifstream can be used for writing and
reading files respectively. To make use of these classes you simply
include the <iostream> and <fstream> libraries. To write to files,
the following commands can be used:
ofstream myfile;
myfile.open("example.txt");
myfile << "Writing this to file.\n";
myfile.close();
Here an object of class ofstream is created with the name myfile.
Next, the function open() is called to create a stream to the file
example.txt. Characters are pushed onto this stream using the stream
insertion operator << and finally the function close() is called to
flush the stream and release any allocated memory.