:py:mod:`kwcoco.demo.boids` =========================== .. py:module:: kwcoco.demo.boids Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: kwcoco.demo.boids.Boids Functions ~~~~~~~~~ .. autoapisummary:: kwcoco.demo.boids.clamp_mag kwcoco.demo.boids.triu_condense_multi_index kwcoco.demo.boids._spatial_index_scratch kwcoco.demo.boids.closest_point_on_line_segment kwcoco.demo.boids._pygame_render_boids kwcoco.demo.boids._yeah_boid .. py:class:: Boids(num, dims=2, rng=None, **kwargs) Bases: :py:obj:`ubelt.NiceRepr` Efficient numpy based backend for generating boid positions. BOID = bird-oid object .. rubric:: References https://www.youtube.com/watch?v=mhjuuHl6qHM https://medium.com/better-programming/boids-simulating-birds-flock-behavior-in-python-9fff99375118 https://en.wikipedia.org/wiki/Boids .. rubric:: Example >>> from kwcoco.demo.boids import * # NOQA >>> num_frames = 10 >>> num_objects = 3 >>> rng = None >>> self = Boids(num=num_objects, rng=rng).initialize() >>> paths = self.paths(num_frames) >>> # >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> from mpl_toolkits.mplot3d import Axes3D # NOQA >>> ax = plt.gca(projection='3d') >>> ax.cla() >>> # >>> for path in paths: >>> time = np.arange(len(path)) >>> ax.plot(time, path.T[0] * 1, path.T[1] * 1, ',-') >>> ax.set_xlim(0, num_frames) >>> ax.set_ylim(-.01, 1.01) >>> ax.set_zlim(-.01, 1.01) >>> ax.set_xlabel('time') >>> ax.set_ylabel('u-pos') >>> ax.set_zlabel('v-pos') >>> kwplot.show_if_requested() import xdev _ = xdev.profile_now(self.compute_forces)() _ = xdev.profile_now(self.update_neighbors)() Ignore: self = Boids(num=5, rng=0).initialize() self.pos fig = kwplot.figure(fnum=10, do_clf=True) ax = fig.gca() verts = np.array([[0, 0], [1, 0], [0.5, 2]]) com = verts.mean(axis=0) verts = (verts - com) * 0.02 import kwimage poly = kwimage.Polygon(exterior=verts) def rotate(poly, theta): sin_ = np.sin(theta) cos_ = np.cos(theta) rot_ = np.array(((cos_, -sin_), (sin_, cos_),)) return poly.warp(rot_) for _ in xdev.InteractiveIter(list(range(10000))): self.step() ax.cla() import math for rx in range(len(self.pos)): x, y = self.pos[rx] dx, dy = (self.vel[rx] / np.linalg.norm(self.vel[rx], axis=0)) theta = (np.arctan2(dy, dx) - math.tau / 4) boid_poly = rotate(poly, theta).translate(self.pos[rx]) color = 'red' if rx == 0 else 'blue' boid_poly.draw(ax=ax, color=color) tip = boid_poly.data['exterior'].data[2] tx, ty = tip s = 100.0 vel = self.vel[rx] acc = self.acc[rx] com = self.acc[rx] spsteer = self.sep_steering[rx] cmsteer = self.com_steering[rx] alsteer = self.align_steering[rx] avsteer = self.avoid_steering[rx] # plt.arrow(tip[0], tip[1], s * vel[0], s * vel[1], color='green') plt.arrow(tip[0], tip[1], s * acc[1], s * acc[1], color='purple') plt.arrow(tip[0], tip[1], s * spsteer[0], s * spsteer[1], color='dodgerblue') plt.arrow(tip[0], tip[1], s * cmsteer[0], s * cmsteer[1], color='orange') plt.arrow(tip[0], tip[1], s * alsteer[0], s * alsteer[1], color='pink') plt.arrow(tip[0], tip[1], s * avsteer[0], s * avsteer[1], color='black') ax.set_xlim(0, 1) ax.set_ylim(0, 1) xdev.InteractiveIter.draw() rx = 0 .. py:method:: __nice__(self) .. py:method:: initialize(self) .. py:method:: update_neighbors(self) .. py:method:: compute_forces(self) .. py:method:: boundary_conditions(self) .. py:method:: step(self) Update positions, velocities, and accelerations .. py:method:: paths(self, num_steps) .. py:function:: clamp_mag(vec, mag, axis=None) vec = np.random.rand(10, 2) mag = 1.0 axis = 1 new_vec = clamp_mag(vec, mag, axis) np.linalg.norm(new_vec, axis=axis) .. py:function:: triu_condense_multi_index(multi_index, dims, symetric=False) Like np.ravel_multi_index but returns positions in an upper triangular condensed square matrix .. rubric:: Examples multi_index (Tuple[ArrayLike]): indexes for each dimension into the square matrix dims (Tuple[int]): shape of each dimension in the square matrix (should all be the same) symetric (bool): if True, converts lower triangular indices to their upper triangular location. This may cause a copy to occur. .. rubric:: References https://stackoverflow.com/a/36867493/887074 https://numpy.org/doc/stable/reference/generated/numpy.ravel_multi_index.html#numpy.ravel_multi_index .. rubric:: Examples >>> dims = (3, 3) >>> symetric = True >>> multi_index = (np.array([0, 0, 1]), np.array([1, 2, 2])) >>> condensed_idxs = triu_condense_multi_index(multi_index, dims, symetric=symetric) >>> assert condensed_idxs.tolist() == [0, 1, 2] >>> n = 7 >>> symetric = True >>> multi_index = np.triu_indices(n=n, k=1) >>> condensed_idxs = triu_condense_multi_index(multi_index, [n] * 2, symetric=symetric) >>> assert condensed_idxs.tolist() == list(range(n * (n - 1) // 2)) >>> from scipy.spatial.distance import pdist, squareform >>> square_mat = np.zeros((n, n)) >>> conden_mat = squareform(square_mat) >>> conden_mat[condensed_idxs] = np.arange(len(condensed_idxs)) + 1 >>> square_mat = squareform(conden_mat) >>> print('square_mat =\n{}'.format(ub.repr2(square_mat, nl=1))) >>> n = 7 >>> symetric = True >>> multi_index = np.tril_indices(n=n, k=-1) >>> condensed_idxs = triu_condense_multi_index(multi_index, [n] * 2, symetric=symetric) >>> assert sorted(condensed_idxs.tolist()) == list(range(n * (n - 1) // 2)) >>> from scipy.spatial.distance import pdist, squareform >>> square_mat = np.zeros((n, n)) >>> conden_mat = squareform(square_mat, checks=False) >>> conden_mat[condensed_idxs] = np.arange(len(condensed_idxs)) + 1 >>> square_mat = squareform(conden_mat) >>> print('square_mat =\n{}'.format(ub.repr2(square_mat, nl=1))) Ignore: >>> import xdev >>> n = 30 >>> symetric = True >>> multi_index = np.triu_indices(n=n, k=1) >>> condensed_idxs = xdev.profile_now(triu_condense_multi_index)(multi_index, [n] * 2) Ignore: # Numba helps here when ub.allsame is gone from numba import jit triu_condense_multi_index2 = jit(nopython=True)(triu_condense_multi_index) triu_condense_multi_index2 = jit()(triu_condense_multi_index) triu_condense_multi_index2(multi_index, [n] * 2) %timeit triu_condense_multi_index(multi_index, [n] * 2) %timeit triu_condense_multi_index2(multi_index, [n] * 2) .. py:function:: _spatial_index_scratch() Ignore: !pip install git+git://github.com/carsonfarmer/fastpair.git from fastpair import FastPair fp = FastPair() fp.build(list(map(tuple, self.pos.tolist()))) # !pip install pyqtree from pyqtree import Index spindex = Index(bbox=(0, 0, 1., 1.)) # this example assumes you have a list of items with bbox attribute for item in items: spindex.insert(item, item.bbox) https://stackoverflow.com/questions/41946007/efficient-and-well-explained-implementation-of-a-quadtree-for-2d-collision-det !pip install grispy import grispy index = grispy.GriSPy(data=self.pos) index.bubble_neighbors(self.pos, distance_upper_bound=0.1) .. py:function:: closest_point_on_line_segment(pts, e1, e2) Finds the closet point from p on line segment (e1, e2) :Parameters: * **pts** (*ndarray*) -- xy points [Nx2] * **e1** (*ndarray*) -- the first xy endpoint of the segment * **e2** (*ndarray*) -- the second xy endpoint of the segment :returns: pt_on_seg - the closest xy point on (e1, e2) from ptp :rtype: ndarray .. rubric:: References http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment .. rubric:: Example >>> # ENABLE_DOCTEST >>> from kwcoco.demo.boids import * # NOQA >>> verts = np.array([[ 21.83012702, 13.16987298], >>> [ 16.83012702, 21.83012702], >>> [ 8.16987298, 16.83012702], >>> [ 13.16987298, 8.16987298], >>> [ 21.83012702, 13.16987298]]) >>> rng = np.random.RandomState(0) >>> pts = rng.rand(64, 2) * 20 + 5 >>> e1, e2 = verts[0:2] >>> closest_point_on_line_segment(pts, e1, e2) Ignore: from numba import jit closest_point_on_line_segment2 = jit(closest_point_on_line_segment) closest_point_on_line_segment2(pts, e1, e2) %timeit closest_point_on_line_segment(pts, e1, e2) %timeit closest_point_on_line_segment2(pts, e1, e2) .. py:function:: _pygame_render_boids() Fast and responsive BOID rendering. This is an easter egg. Requirements: pip install pygame CommandLine: python -m kwcoco.demo.boids pip install pygame kwcoco -U && python -m kwcoco.demo.boids .. py:function:: _yeah_boid()