# Ryan Martin
# macid: abcd123
# student number: #######
# November 23, 2011
#
#
# This is a solution for Major Assignment 5 (Lab 11) on Friday.

class particle1D:

    def __init__(self, m, r0, v0, a0):
        self.m = float(m)
        self.r = float(r0)
        self.v = float(v0)
        self.a = float(a0)

    def getM(self):
        return self.m

    def getR(self):
        return self.r
    
    def getV(self):
        return self.v
    
    def getA(self):
        return self.a

    def update(self, delta_t):
        self.r = self.r + self.v * delta_t + 0.5 * self.a * delta_t**2
        self.v = self.v + self.a * delta_t

    def elasticCollision(self, p):
        self.v = (self.v * (self.m - p.getM()) + 2 * p.getV() * p.getM()) / (self.m + p.getM())

def runElasticCollisionSim(p1, p2, radius, delta_t):
    while abs(p1.getR() - p2.getR()) > radius:
        p1.update(delta_t)
        p2.update(delta_t)

    p3 = particle1D(p1.getM(), p1.getR(), p1.getV(), p1.getA())
    p1.elasticCollision(p2)
    p2.elasticCollision(p3)
    return [p1.getV(), p2.getV()]


'''
Design Question

When applying the collision to both p1 and p2, the following transaction
needs to occur:

	p1.elasticCollision(p2)
	p2.elasticCollision(p1)

However, when p1.elasticCollision(p2) is executed, p1 changes. This means
that the following call to p2.elasticCollision(p1) will result in
p2 being incorrectly mutated. An obvious, but incorrect, solution would be 
to use a temporary variable as follows:

	p3 = p1
	p1.elasticCollision(p2)
	p2.elasticCollision(p3)

The statement p3 = p1 does not make a copy of p1! Instead, p3 is aliased
to p1, and the statement p1.elasticCollision(p2) also changes p3, meaning
that p2.elasticCollision(p3) is equivalent to p2.elasticCollision(p1).

We can get around this by making an explicit copy, or deep copy, of p1 
and store it in p3. We can either do the straightforward method:

	p3 = particle1D(p1.getM(), p1.getR(), p1.getV(), p1.getA())

Or, we can add a copy() method to the particle1D class:

	def copy(self):
		return particle1D(self.m, self.r, self.v, self.a)
	
	...
	
	p3 = p1.copy()

Or, we can make use of Python's copy library:

	import copy
	
	...
	
	p3 = copy.deepcopy(p1)

Which essentially does the same as option 1.
'''
##Note: This format is used by our automated marking system.
##You will see that, for inputs, the class/function/method name is
##given with the corresponding inputs to that class/function/method
##listed right after it. The expected outputs are the state
##variables of the object after calling the function/method.
'''

Testing Report

FRI = {'test1': 
       {'inputs': ['particle1D', [1, 0, 2, 0], 'update', [0.1]],
       'outputs': [testingClass(1, 0.2, 2, 0)]},
       'test2':
       {'inputs': ['particle1D', [2.5, -1.5, -1.5, 2], 'update', [0.2]],
       'outputs': [testingClass(2.5, -1.76, -1.1, 2.0)]},
       'test3':
       {'inputs': ['particle1D', [1, 1, 3, 5], 'elasticCollision', [('particle1D', [5, 2, 1, 4])]],
       'outputs': [testingClass(1, 1, -0.3333333333333333, 5)]},
       'test4':
       {'inputs': ['runElasticCollisionSim', [('particle1D', [3, 3, -0.5, -1]), ('particle1D', [2, -3, 0, 3]), 0.1, 0.1]],
       'outputs': [[3.419999999999999, -3.48]]}}

'''