In this assignment we will write a simple point mass gravity simulator. You don't need to worry too much about the physics since I will provide all the relevant equations. There are basically two tasks we have to perform here: define a point mass data type and its utility functions, and define a way of managing a collection of point masses (that will also help our simulations). In previous assignments, we used arrays to manage collections of things, but in this case we would like to have an arbitrary number of particles. To support this we will be using a singly linked list.
Part 1: Write a module called "particle". The module should declare the following data type:
typedef struct _particle {
double position [3]; /* (x, y, z) coordinates */
double velocity [3]; /* dX, dY, dZ velocities */
double mass; /* Mass of point */
char identity [80]; /* Object name or handle */
} Particle;
The purpose of this module is to provide us with some functions to input, output and manipulate this point mass data type we have just defined. For the purpose of this assignment, you may assume that the numbers we are using for position, velocity and mass are metric (ohh, say kilometers and kilograms), but in part 2, we will ignore the gravity constants, and use a simplified equation to simulate.
Part 1.1: Add to the module the following function:
void initParticle (Particle * particlePtr);
The initParticle() function should set every numerical field in the particle data structure to 0, and set the identity to the empty string. Be sure to check for NULL pointers.
Part 1.2: Add to the module the following function:
void printParticle (const Particle * particlePtr);
If the input pointer is NULL, print "Invalid particle!\n", otherwise the output should resemble the following (note that all floating point numbers have a minimum of five significant digits):
Particle: ID001 Location: <6.0512, -9.0283, 1.2540> Mass: 1.0 dX: 0.0051, dY: -0.0200, dZ: 0.0000
This might be generated from some main() function as follows:
#include "particle.h"
int main (void)
{
Particle testParticle = { {6.0512, -9.0283, 1.254},
{0.0051, -0.02, 0.}, 1.0, "ID001" };
printParticle(&testParticle);
return 0;
}
Part 1.3: Write a function to read particle data from the console:
int readParticle (Particle * particle);
It should have as much error protection as you see fit, returning 1 if a particle was read successfully, or 0 if something went wrong. Prompts do not have to be exact matches to the following example:
Enter particle ID : ID001 Enter particle X coordinate : 6.0512 Enter particle Y coordinate : -9.0283 Enter particle Z coordinate : 1.254 Enter particle mass : 1.0 Enter particle X velocity : 0.0051 Enter particle Y velocity : -0.02 Enter particle Z veloctiy : 0
Part 1.4: Write a function to copy a particle:
void copyParticle (Particle * destParticle, const Particle * srcParticle);
This doesn't have to be anything fancy, and could in fact just use memcpy().
Part 1.5: Write a pair of functions to create/destroy Particle data instances:
Particle * newParticle (void); void freeParticle (Particle * particle);
The output of newParticle() can be created easily using malloc() and passing the address on to initParticle(). The freeParticle() function in turn should basically just call free(). Something to consider: How would this be different if the identity field was a char * instead of an array?
Part 1.6: Start a new C file called simulator.c. Have simulator.c include particle.h. Define a main() function in simulator.c, that displays a menu similar to the following:
************************************************************ 0 - Exit the simulator. 1 - Add a particle. 2 - Remove a particle. 3 - Display all particles. 4 - Run the simulator. ************************************************************ Your choice?
Write an input handler that gets a menu option. For now, just have a single particle (created using newParticle() -- don't forget to call freeParticle() when the program is exited).
Option 4 should prompt the user for a number of time steps to run, and ask if they want no output, brief output or detailed output. Example:
Number of time steps : 5 0 - No output (default) 1 - Brief output 2 - Detailed output Ouput level : -1 Invalid input, assuming no output.
You should then enter a counting loop where for each time step you do the following:
Part 2: Adding the simulation code.
I have provided two modules, list and physics, which are available here: list.h, list.c, physics.h, physics.c. The physics module uses some special functions from the standard C math library, which will require you to link in the corresponding library. This can be done by adding the "-lm" argument to GCC. Example:
% gcc -Wall -o simulator list.c particle.c physics.c simulator.c -lm
Part 2.1: Write some functions that will assist you in completing the simulator. You have full license to define any functions or modules you want for this program, and you do not need to duplicate my example functions. In my simulator, I just put the utility functions in the simulator.c file. Here are the prototypes of these functions:
static Particle * findParticle (List * list, const char * id);
static void printParticles (List * list);
static void runSimulator (List * list, int steps, double stepSize,
int verbosity);
static void updateParticles (List * list, double stepSize);
The findParticle() function iterates through a list of Particle structures. If it finds one with the specified identity, the data field of the corresponding list node is returned. Otherwise, NULL is returned. As an example of where this might be used is to ensure that a particle that is about to be inserted does not have the same name as a particle already in the list. It might also be useful to note that the removeNode() function in the list module uses the data field as a marker for the node to be removed.
The printParticles() function iterates through the passed list of Particle structures. It then just calls printParticle() on the data field of any list nodes it encounters.
The runSimulator() function implements a counting loop and based on the verbosity input prints out some time step information. For each time step, it simply calls updateParticles(), passing the particle list and the time step size (in seconds). Verbosity level 0 prints nothing out, verbosity level 1 prints a step count and simulation clock time (again in seconds), verbosity level 2 (and higher) prints a star bar for each timestep, the counter data from verbosity level 1, and all the particle data (using printParticles().
The updateParticles() is the trickiest function since it uses a nested loop to calculate particle interactions. Here is the pseudo-code of my implementation:
for each node, n0, in particle list:
p0 = n0->data
/* Update the position based on the velocity vector. */
for dimension from 0..2:
p0->position[dimension] += p0->velocity[dimension] * stepSize
for each node n1, in particle list:
if n1 != n0:
p1 = n1->data
/* calcAccel() is in the physics module, with full code. */
calcAccel(p0, p1, accel)
for dimension from 0 to 2:
p0->velocity[dimension] += accel[dimension] * stepSize
Part 2.2: Update the menu to perform the various tasks it advertises:
Don't forget to create and initialize an empty list in your main() function. Furthermore, the list should be deallocated before the program exits.
Option 0 still exits the program.
Option 1 reads a particle, then checks to see if a particle by the same name is already in the particle list. If it is not in the list, a new particle is allocated, the particle data is copied from the input structure to the new structure, and the new structure is added to the particle list (using addNode().) Otherwise, it displays the conflicting particle record found and does nothing with the input particle data. This is a poor design, if you are feeling ambitious, rework the system to reject particles as soon as the identity is enterred, thus saving the user the hassle of entering in the position, velocity and mass data.
Option 2 should read an identity string from the user, look for the corresponding particle in the particle list and remove it, if found. Otherwise, the user should be informed that no particle by the given identity was found.
Option 3 should switch from printing one particle to all particles in the particle list.
Option 4 should be modified to ask a floating point step size, in addition to an integer step count and verbosity level. After getting this data from the user, I simply pass it to runSimulator(), as described above.
Part 2.3: Enjoy! I've been experimenting with some real data, and finding that my acceleration vector is off by more than three orders of magnitude! (i.e. either the code is broken, or I'm using the wrong measurements in my test runs!) Try to get two particles to orbit each other, tweaking the simulation code if you are feeling brave. Here are some interesting numbers:
Earth's mass: 5.976e24 kg Earth's radius: 6378500 m Shuttle mass: About 110000 kg Shuttle state vector (STS-64): X = -6586893.6283200001 m Y = 99796.274879999997 m Z = 616986.37080000003 m dX = 542.85924366719996 m/s dY = -4252.3623933024001 m/s dZ = 6476.3651454911997 m/s
You can also calculate orbital velocities based on altitude at the Satellite Orbit Data Calculator (don't forget to add the Earth's radius to the altitude when constructing vectors). Of course, if all this is Greek (or rather, "geek") to you, don't worry! On the Linux boxes, you can play with my orbit.py program, or just compare your results to my reference program here. You can run a copy of the executable using the following commands on the Linux boxes:
% wget http://people.cs.uchicago.edu/~jriehl/cspp50101/simulator % chmod +x simulator % simulator
When you have completed your program, attach all the source files (including the ones I gave you, since you may modify them) to an email and send it to Jim with the following subject line: "CSPP50101 - Simulator Assignment Turnin". You have until 11:59PM, Wednesday September 8th to submit your program. Remember that it will be graded as being 300 out of 200 points, so even if you only have part of it working, you should make an A for the assignment. Good luck and happy simulating!