def test_draw_totally_out_of_bounds(self): p = _MockSTPlotter() h = geom.HalfRibbon(geom.Ray((0, 1), (2, 3)), geom.Ray((0, 1), (0, 2))) h.draw(p, tlim=(-2, 3), xlim=(0, 1)) self.assertEqual(len(p.points), 0) self.assertEqual(len(p.segments), 0) self.assertEqual(len(p.polygons), 0)
def test_draw_endpoints_one_endpoint_out_of_bounds(self): p = _MockSTPlotter() h = geom.HalfRibbon(geom.Ray((0, 1), (2, 0.5)), geom.Ray((0, 1), (0, -0.5)), draw_options={'edgecolor': 'None'}) h.draw(p, tlim=(-2, 3), xlim=(0, 1)) self.assertEqual(len(p.points), 0) self.assertEqual(len(p.segments), 0) self.assertEqual(len(p.polygons), 1) p.polygons_equal(self, p.polygons[0], ([(0, 0), (0, 1), (2, 1), (2, 0.5), (1, 0)], None, {}))
def test_draw_totally_out_of_bounds(self): ray = geom.Ray((1, 1), (2, 3)) p = _MockSTPlotter() ray.draw(p, tlim=(-1, 2), xlim=(0, 2)) self.assertEqual(len(p.points), 0) self.assertEqual(len(p.segments), 0) self.assertEqual(len(p.polygons), 0)
def test_boundary_intersections_exterior_endpoint(self): ray = geom.Ray((0, 1), (0.5, -1)) tlim = (0, 1) xlim = (0, 1) self.assertEqual(ray._boundary_intersections(tlim, xlim), [(0.5, -1), (0.5, 0), (0.5, 1)])
def test_intersect_equal_lines_diff_direction_diff_point(self): ray = geom.Ray((1, 1), (2, 3)) line = geom.Line((2, 2), (3, 4)) intersection = ray.intersect(line) self.assertIsInstance(intersection, geom.Ray) self.assertEqual(intersection.direction(), ray.direction()) self.assertEqual(intersection.point(), ray.point())
def test_draw_endpoint_in_bounds(self): ray = geom.Ray((1, 1), (0, 1)) p = _MockSTPlotter() ray.draw(p, tlim=(-1, 2), xlim=(0, 2)) self.assertEqual(len(p.points), 0) self.assertEqual(len(p.segments), 1) self.assertEqual(len(p.polygons), 0) p.segments_equal(self, p.segments[0], ((0, 1), (1, 2), None, {}))
def test_extrapolate_color(self): gradln = grad.gradient_line((1/3, 1/3), (2/3, 2/3), (2/3, 0, 1/3, 1), (1/3, 0, 2/3, 1), divisions=1) self.assertEqual(len(gradln), 5) self.assertEqual(gradln[0], geom.Ray((-1, -1), (0, 0))) _tuple_eq(self, gradln[0].draw_options['color'], (1, 0, 0, 1)) self.assertEqual(gradln[1][0], (0, 0)) self.assertEqual(gradln[1][1], (1/3, 1/3)) _tuple_eq(self, gradln[1].draw_options['color'], (5/6, 0, 1/6, 1)) self.assertEqual(gradln[2][0], (1/3, 1/3)) self.assertEqual(gradln[2][1], (2/3, 2/3)) _tuple_eq(self, gradln[2].draw_options['color'], (3/6, 0, 3/6, 1)) self.assertEqual(gradln[3][0], (2/3, 2/3)) self.assertEqual(gradln[3][1], (1, 1)) _tuple_eq(self, gradln[3].draw_options['color'], (1/6, 0, 5/6, 1)) self.assertEqual(gradln[4], geom.Ray((1, 1), (1, 1))) _tuple_eq(self, gradln[4].draw_options['color'], (0, 0, 1, 1))
def test_longitudinal_gradient_ribbon(self): gradribbon = grad.longitudinal_gradient_ribbon( ((0, 0), (1, 0)), ((0, 1), (1, 1)), (1, 0, 0, 1), (0, 0, 1, 1), divisions=1) self.assertEqual(len(gradribbon), 3) self.assertEqual(gradribbon[0][0], geom.Ray((-1, 0), (0.5, 0))) self.assertEqual(gradribbon[0][1], geom.Ray((-1, 0), (0.5, 1))) _tuple_eq(self, gradribbon[0].draw_options['facecolor'], (1, 0, 0, 1)) self.assertEqual(gradribbon[1][0], (0, 0)) self.assertEqual(gradribbon[1][1], (1.5, 0)) self.assertEqual(gradribbon[1][2], (1.5, 1)) self.assertEqual(gradribbon[1][3], (0, 1)) _tuple_eq(self, gradribbon[1].draw_options['facecolor'], (1/2, 0, 1/2, 1)) self.assertEqual(gradribbon[2][0], geom.Ray((1, 0), (1, 0))) self.assertEqual(gradribbon[2][1], geom.Ray((1, 0), (1, 1))) _tuple_eq(self, gradribbon[2].draw_options['facecolor'], (0, 0, 1, 1))
def test_auto_draw_lims(self): ray = geom.Ray((1, -1), (2, 3)) self.assertEqual(ray._auto_draw_lims(), ((2, 3), (2, 3)))
def test_init_error_on_antiparallel(self): self.assertRaises(ValueError, geom.HalfRibbon, geom.Ray((0, 1), (0, 0)), geom.Ray((0, -1), (2, 3)))
def test_ghost_intersection(self): ray = geom.Ray((1, 0), (2, 1)) line = geom.Line((0, 1), (1, 0)) self.assertIsNone(ray.intersect(line))
def test_actual_intersection_tangent(self): ray = geom.Ray((1, 0), (1, 1)) line = geom.Line((0, 1), (1, 0)) self.assertEqual(ray.intersect(line), (1, 1))
def test_actual_intersection_full_crossing(self): ray = geom.Ray((1, 0), (0, 1)) line = geom.Line((0, 1), (1, 0)) self.assertEqual(ray.intersect(line), (1, 1))
def test_str(self): self.assertEqual(str(geom.Ray((0, 1), (2, 3))), 'Ray( [t, x] = [2, 3] + k*[0, 1] where k >= 0 )')
def test_intersect_parallel(self): ray = geom.Ray((1, 1), (0, 0)) line = geom.Line((2, 2), (0, 1)) self.assertIsNone(ray.intersect(line))
def test_eq(self): self.assertEqual(geom.Ray((1, 1), (2, 3)), geom.Ray((1, 1), (2, 3)))
def gradient_line(point1, point2, color1, color2, divisions=100, extrapolate_color=True, draw_options=geom.geomrc['draw_options']): """A line with a color gradient. The gradient transition will happen over a finite range in spacetime, and be monochromatic at either end. Args: point1 (specrel.geom.STVector or iterable): Starting point of the gradient. point2 (specrel.geom.STVector or iterable): Ending point of the gradient. color1 (color): A Matplotlib color for the gradient starting color. color2 (color): A Matplotlib color for the gradient ending color. divisions (int, optional): The number of line segment divisions in the gradient. More divisions means a smoother gradient. extrapolate_color (bool, optional): Flag for whether or not to extrapolate the color gradient across the line past the specified endpoints so that the color change spans as far as possible across the line. draw_options (dict, optional): See `specrel.geom.LorentzTransformable`. Returns: specrel.geom.Collection: Collection containing the color gradient increments, in the order: 1. `specrel.geom.Ray` before `point1` with `color1`. 2. Line segments changing color from `point1` to `point2`. 3. `specrel.geom.Ray` after `point2` with `color2`. """ # Copy draw_options and remove color if it's there draw_options = dict(draw_options) draw_options.pop('color', None) # If extrapolating color, calculate the color gradient extremes if extrapolate_color: point1, point2, color1, color2, divisions = _colorgrad_extremes( point1, point2, color1, color2, divisions) # Color gradient calculator for the given points and colors def this_colorgrad(x): return _calc_colorgrad(x, point1, point2, color1, color2) # Line direction vector direc = geom.STVector(point2) - geom.STVector(point1) grad = geom.Collection() # Monochromatic ray at the tail end of the gradient line grad.append( geom.Ray(-direc, point1, draw_options={ 'color': color1, **draw_options })) # The line segments comprising the color gradient for i in range(divisions): start_point, _ = this_colorgrad(i / divisions) end_point, _ = this_colorgrad((i + 1) / divisions) _, grad_color = this_colorgrad((i + 1 / 2) / divisions) grad.append( geom.line_segment(start_point, end_point, draw_options={ 'color': grad_color, **draw_options })) # Monochromatic ray at the head end of the gradient line grad.append( geom.Ray(direc, point2, draw_options={ 'color': color2, **draw_options })) return grad
v_away = 1/2 v_message = 4 t_send = 1 person1 = phy.MovingObject(0, velocity=-v_away, draw_options={'color': 'red', 'label': 'Person 1', 'markersize': 15}) person2 = phy.MovingObject(0, velocity=v_away, draw_options={'color': 'blue', 'label': 'Person 2', 'markersize': 15}) # Extensions of the messages to all time, even though they don't exist for all # time. The lines are useful for intersection calculations joke_alltime = phy.MovingObject(person2.center_pos(t_send), start_time=t_send, velocity=-v_message) # Get the left edge of the MovingObject; since it's a point object it's just # the worldline joke_sent = joke_alltime.left().intersect(person1.left()) # The actual joke object to draw joke = geom.Ray((-1, v_message), joke_sent, tag='*Joke*', draw_options={'color': 'red', 'linestyle': '--', 'marker': '>'}) # Mark the point of receipt joke_received = joke.intersect(person2.left()) joke_received.draw_options = { 'color': 'limegreen', 'marker': '*', 'markersize': 10, 'label': 'Joke received' } # Repeat for the response stuff response_alltime = phy.MovingObject(person1.center_pos(t_send), start_time=t_send, velocity=v_message, tag='Hahaha!', draw_options={'color': 'blue', 'linestyle': '--', 'marker': '<'}) response_sent = response_alltime.left().intersect(person2.left()) response = geom.Ray((-1, -v_message), response_sent, tag='Hahaha!', draw_options={'color': 'blue', 'linestyle': '--', 'marker': '<'})
def test_neq_opp_dir(self): self.assertNotEqual(geom.Ray((1, 1), (2, 3)), geom.Ray((-1, -1), (2, 3)))
def test_neq_diff_point(self): self.assertNotEqual(geom.Ray((1, 1), (2, 3)), geom.Ray((1, 1), (1, 2)))
def test_eq_scaled_dir(self): self.assertEqual(geom.Ray((1, 1), (2, 3)), geom.Ray((2, 2), (2, 3)))
) # Add a few more frills to the drawing door_draw_options = {'linestyle': '--', 'marker': '|', 'markersize': 10} garage[0].draw_options = door_draw_options garage[1].draw_options = door_draw_options # Time range t_start = 0 # End when the ladder totally clears the garage t_end = ladder.time_for_left_pos(garage.right_pos(0)) # Time when the garage doors are opened/closd t_transition = ladder.time_for_left_pos(garage.left_pos(0)) # Whole period when each door is closed closed_draw_options = {'color': 'red', 'marker': '|', 'markersize': 10} left_closed = geom.Ray((1, 0), (t_transition, garage.left_pos(0)), draw_options=closed_draw_options) right_closed = geom.Ray((-1, 0), (t_transition, garage.right_pos(0)), draw_options=closed_draw_options) # Exact event of closing/opening each door close_event_draw_options = { 'color': 'red', 'marker': 'v', 'markersize': 10, 'label': 'Close door', } open_event_draw_options = { 'color': 'limegreen', 'marker': '^', 'markersize': 10, 'label': 'Open door',
def longitudinal_gradient_ribbon(line1_endpoints, line2_endpoints, color1, color2, divisions=100, extrapolate_color=True, draw_options=geom.geomrc['draw_options']): """A `specrel.geom.Ribbon`-esque object with a longitudinal color gradient (across the infinite direction). Args: line1_endpoints (list): list of two `specrel.geom.STVector`-convertible points defining the start and end positions of the gradient along the first edge of the ribbon. line2_endpoints (list): Same as `line1_endpoints`, but for the second edge of the ribbon. color1 (color): A Matplotlib color for the gradient starting color. color2 (color): A Matplotlib color for the gradient ending color. divisions (int, optional): The number of line segment divisions in the gradient. More divisions means a smoother gradient. extrapolate_color (bool, optional): Flag for whether or not to extrapolate the color gradient across the ribbon past the specified endpoints so that the color change spans as far as possible across the ribbon. draw_options (TYPE, optional): See `specrel.geom.LorentzTransformable`. Returns: specrel.geom.Collection: Collection containing the color gradient increments, in the order: 1. `specrel.geom.HalfRibbon` with `color1` before both line starting points. 2. Polygons changing color from starts of the lines to the ends. 3. `specrel.geom.HalfRibbon` with `color2` after both line ending points. """ # Copy draw_options and remove color, facecolor, and edgecolor if they're # there draw_options = dict(draw_options) draw_options.pop('color', None) draw_options.pop('facecolor', None) draw_options.pop('edgecolor', None) # If extrapolating color, calculate the color gradient extremes if extrapolate_color: point1, point2, color1, color2, divisions = _colorgrad_extremes( *line1_endpoints, color1, color2, divisions) line1_endpoints = (point1, point2) point1, point2, _, _, _ = _colorgrad_extremes(*line2_endpoints, color1, color2, divisions) line2_endpoints = (point1, point2) # Color gradient calculators for each line with the given colors def line1_colorgrad(x): return _calc_colorgrad(x, *line1_endpoints, color1, color2) def line2_colorgrad(x): return _calc_colorgrad(x, *line2_endpoints, color1, color2) # Direction vectors of each line direc1 = geom.STVector(line1_endpoints[1]) - geom.STVector( line1_endpoints[0]) direc2 = geom.STVector(line2_endpoints[1]) - geom.STVector( line2_endpoints[0]) grad = geom.Collection() # Form the monochromatic half ribbon at the tail end of the gradient # Overlap with the first polygon by half a division to mitigate any # boundary gaps start_point1, _ = line1_colorgrad(1 / (2 * divisions)) start_point2, _ = line2_colorgrad(1 / (2 * divisions)) grad.append( geom.HalfRibbon( geom.Ray(-direc1, start_point1), geom.Ray(-direc2, start_point2), # Explicitly turn off edge coloring draw_options={ 'facecolor': color1, 'edgecolor': 'None', **draw_options })) # Interior polygons comprising the color gradient for i in range(divisions): start_point1, _ = line1_colorgrad(i / divisions) # Overlap bands by half a division end_point1, _ = line1_colorgrad((i + 1 + 1 / 2) / divisions) start_point2, _ = line2_colorgrad(i / divisions) end_point2, _ = line2_colorgrad((i + 1 + 1 / 2) / divisions) _, grad_color = line1_colorgrad((i + 1 / 2) / divisions) grad.append( geom.polygon([start_point1, end_point1, end_point2, start_point2], draw_options={ 'facecolor': grad_color, **draw_options })) # Monochromatic half ribon at the head end of the gradient grad.append( geom.HalfRibbon(geom.Ray(direc1, line1_endpoints[1]), geom.Ray(direc2, line2_endpoints[1]), draw_options={ 'facecolor': color2, 'edgecolor': 'None', **draw_options })) return grad