def test_toi_contents(): bld = Billiard() # add a single ball, no collision possible here bld.add_ball((0, 0), (0, 0), 1) assert bld.toi_min[0] == (INF, -1) assert bld.toi_next == (INF, -1, 0) # add one more ball on collision course bld.add_ball((4, 0), (-1, 0), 1) assert bld.toi_table[1] == [2.0] assert bld.toi_min[1] == (2.0, 0) assert bld.toi_next == (2.0, 0, 1) # add a third ball that collides earlier with the first one and then with # the second one bld.add_ball((0, 4), (0, -2), 1) assert bld.toi_table[2] == [1.0, approx(2.0)] assert bld.toi_min[2] == (1.0, 0) assert bld.toi_next == (1.0, 0, 2) # test simulation.calc_toi assert bld.calc_toi(0, 1) == 2.0 assert bld.calc_toi(0, 2) == 1.0 assert bld.calc_toi(1, 2) == approx(2.0)
def test_toi_structure(): bld = Billiard() for i in range(10): bld.add_ball((i, 0), (0, 1)) assert len(bld.toi_table) == i + 1 assert len(bld.toi_table[i]) == i assert len(bld.toi_min) == i + 1
def test_time(): bld = Billiard() assert bld.time == 0.0 ret = bld.evolve(1.0) assert ret == [] assert bld.time == 1.0 ret = bld.evolve(42.0) assert ret == [] assert bld.time == 42.0
def test_index(): n = 10 bld = Billiard() # add balls and check index for i in range(n): idx = bld.add_ball((i, 0), (0, i)) assert idx == i assert bld.num == n # check that the indices are actually right for idx in range(n): assert tuple(bld.balls_position[idx]) == (idx, 0) assert tuple(bld.balls_velocity[idx]) == (0, idx)
def test_movement(): bld = Billiard() # add ten balls for i in range(10): bld.add_ball((i, 0), (0, 1)) # move time = 42.0 ret = bld.evolve(time) assert ret == [] for idx in range(10): # movement in y-direction assert tuple(bld.balls_position[idx]) == (idx, time) assert tuple(bld.balls_velocity[idx]) == (0, 1)
def test_newton_cradle(): bld = Billiard() # setup Newton's cradle with four balls bld.add_ball((-3, 0), (1, 0), 1) bld.add_ball((0, 0), (0, 0), 1) bld.add_ball((5, 0), (0, 0), 1) # this is the last ball (more coverage) bld.add_ball((3, 0), (0, 0), 1) # in direct contact with third ball assert bld.toi_next == (1.0, 0, 1) # first collision collisions = bld.evolve(1.0) assert len(collisions) == 1 assert collisions[0] == (1.0, 0, 1) assert tuple(bld.balls_position[0]) == (-2, 0) assert tuple(bld.balls_velocity[0]) == (0, 0) assert tuple(bld.balls_position[1]) == (0, 0) assert tuple(bld.balls_velocity[1]) == (1, 0) assert bld.toi_next == (2.0, 1, 3) # second and third collision and then some more time collisions = bld.evolve(11.0) assert len(collisions) == 2 assert collisions[0] == (2.0, 1, 3) assert collisions[1] == (2.0, 2, 3) assert tuple(bld.balls_position[1]) == (1, 0) assert tuple(bld.balls_velocity[1]) == (0, 0) assert tuple(bld.balls_position[2]) == (5 + (11 - 2) * 1, 0) assert tuple(bld.balls_velocity[2]) == (1, 0) assert tuple(bld.balls_position[3]) == (3, 0) assert tuple(bld.balls_velocity[3]) == (0, 0) # there are no other collisions assert table_tolist(bld.toi_table) == [[], [INF], [INF, INF], [INF, INF, INF]] assert bld.toi_min == [(INF, -1), (INF, 0), (INF, 0), (INF, 0)] assert bld.toi_next == (INF, -1, 0)
def test_simple_collision(): bld = Billiard() bld.add_ball((2, 0), (4, 0), radius=1) bld.evolve(10.0) assert tuple(bld.balls_position[0]) == (42.0, 0.0) assert tuple(bld.balls_velocity[0]) == (4.0, 0.0) # add another ball that will collide with the first one bld.add_ball((50, 18), (0, -9), radius=1, mass=2) assert bld.toi_next == (approx(11.79693), 0, 1) collisions = bld.evolve(14.0) assert bld.time == 14 assert len(collisions) == 1 assert collisions[0] == (approx(11.79693), 0, 1) assert tuple(bld.balls_position[0]) == (approx(46.2503), approx(-26.43683)) assert tuple(bld.balls_position[1]) == (approx(55.8748), approx(-4.78158)) assert tuple(bld.balls_velocity[0]) == (approx(-1.333333), approx(-12)) assert tuple(bld.balls_velocity[1]) == (approx(2.666667), approx(-3))
def test_obstacles(): disk = billiards.obstacles.Disk((0, 0), radius=1) bld = Billiard(obstacles=[disk]) assert len(bld.obstacles) == 1 assert bld.obstacles[0] == disk bld.add_ball((-10, 0), (1, 0), radius=1) assert len(bld.obstacles_toi) == 1 assert bld.obstacles_toi[0] == (8.0, disk) assert bld.obstacles_next == (8.0, 0, disk) ret = bld.bounce_ballobstacle() assert ret == (8.0, 0, disk) assert bld.obstacles_toi[0] == (INF, None) assert bld.obstacles_next == (INF, 0, None) assert tuple(bld.balls_velocity[0]) == (-1.0, 0.0) # wrong type with pytest.raises(TypeError): Billiard(obstacles=[42])
def test_add(): # missing required positional arguments bld = Billiard() with pytest.raises(TypeError): bld.add_ball((0, 0)) # wrong type send to numpy bld = Billiard() with pytest.raises(ValueError): bld.add_ball((0, 0), None) # wrong data send to numpy bld = Billiard() with pytest.raises(ValueError): bld.add_ball((0, 0), (0, 0, 0)) bld = Billiard() assert bld.add_ball((0, 0), (0, 0)) == 0 # wrong type for radius bld = Billiard() with pytest.raises(TypeError): bld.add_ball((0, 0), (0, 0), None) bld = Billiard() assert bld.add_ball((0, 0), (0, 0), 0) == 0 # wrong type for mass bld = Billiard() with pytest.raises(TypeError): bld.add_ball((0, 0), (0, 0), 1, None) bld = Billiard() assert bld.add_ball((0, 0), (0, 0), 0, 0) == 0
def test_exceptional_balls(): bld = Billiard() # setup two point particles bld.add_ball((-3, 0), (1, 0), 0, mass=0) # note: massless bld.add_ball((-2, 0), (0, 0), 0, mass=42) assert bld.toi_table == [[], [INF]] # no collision # setup two balls with infinite masses bld.add_ball((0, 0), (0, 0), 1, mass=INF) bld.add_ball((100, 0), (-20, 0), 1, mass=INF) assert bld.toi_table[2] == [2.0, INF] assert bld.toi_table[3] == [ approx(102 / 21), approx(101 / 20), approx(98 / 20), ] assert bld.toi_next == (2.0, 0, 2) bld.evolve(2.0) # massless <-> infinite mass collision assert tuple(bld.balls_position[0]) == (-1, 0) assert tuple(bld.balls_velocity[0]) == (-1, 0) assert tuple(bld.balls_position[2]) == (0, 0) assert tuple(bld.balls_velocity[2]) == (0, 0) assert bld.toi_table[:3] == [[], [INF], [INF, INF]] assert bld.toi_table[3] == [ approx(98 / 19), approx(101 / 20), approx(98 / 20), ] assert bld.toi_next == (approx(98 / 20), 2, 3) # toi == 4.9 bld.evolve(5.0) # infinite mass <-> infinite mass collision assert tuple(bld.balls_position[2]) == (0, 0) assert tuple(bld.balls_velocity[2]) == (0, 0) assert tuple(bld.balls_position[3]) == (approx(2 + 0.1 * 20), 0) assert tuple(bld.balls_velocity[3]) == (20, 0) assert bld.toi_table[3] == [INF, INF, INF] assert tuple(bld.balls_position[0]) == (-4, 0) assert tuple(bld.balls_velocity[0]) == (-1, 0) # add one more massless ball that collides with the first one bld.add_ball((-6, 0), (0, 0), 1, mass=0) assert bld.toi_table[4] == [approx(6.0), INF, INF, INF] assert bld.toi_next == (approx(6.0), 0, 4) # collisions of two massless balls do not make sense with pytest.raises(FloatingPointError): bld.evolve(7.0)
def test_masses(): bld = Billiard() # setup three balls bld.add_ball((-3, 0), (1, 0), 1, mass=0) # massless bld.add_ball((0, 0), (0, 0), 1, mass=42) # finite mass bld.add_ball((4, 0), (-1, 0), 1, mass=INF) # infinite mass assert bld.toi_table == [[], [1.0], [2.5, 2.0]] bld.evolve(1.0) # massless <-> finite mass collision assert tuple(bld.balls_position[0]) == (-2, 0) assert tuple(bld.balls_velocity[0]) == (-1, 0) assert tuple(bld.balls_position[1]) == (0, 0) assert tuple(bld.balls_velocity[1]) == (0, 0) assert bld.toi_table == [[], [INF], [INF, 2.0]] bld.evolve(2.0) # finite mass <-> infinite mass collision assert tuple(bld.balls_position[1]) == (0, 0) assert tuple(bld.balls_velocity[1]) == (-2, 0) assert tuple(bld.balls_position[2]) == (2, 0) assert tuple(bld.balls_velocity[2]) == (-1, 0) assert bld.toi_table == [[], [3.0], [INF, INF]] bld.evolve(3.0) # again massless <-> finite mass collision assert tuple(bld.balls_position[0]) == (-4, 0) assert tuple(bld.balls_velocity[0]) == (-3, 0) assert tuple(bld.balls_position[1]) == (-2, 0) assert tuple(bld.balls_velocity[1]) == (-2, 0) assert bld.toi_table == [[], [INF], [INF, INF]]