swarm class

The swarm class acts as a collection of agents that share a similar motion model. All agents must be grouped into a swarm, and every swarm must also have an associated environment object. Internally, information about agent locations, velocities, and accelerations within the swarm are stored as masked NumPy arrays where each row is an agent and each column is a spatial dimension. The mask refers to whether or not the agent has left the spatial domain. Any swarm properties that do not vary from agent to agent are stored within the shared_props attribute, implemented as a Python dictionary. Properties that vary between agents are stored within the props attribute as a pandas DataFrame.

Created on Tues Jan 24 2017

Author: Christopher Strickland

Email: cstric12@utk.edu

class planktos.swarm(swarm_size=100, envir=None, init='random', seed=None, shared_props=None, props=None, name='organism', color='darkgreen', **kwargs)

Fundamental Planktos object describing a group of similar agents.

The swarm class (alongside the environment class) provides the main agent functionality of Planktos. Each swarm object should be thought of as a group of similar (though not necessarily identical) agents. Planktos implements agents in this way rather than as individual objects for speed purposes; it is easier to vectorize a swarm of agents than individual agent objects, and also much easier to plot them all, get data on them all, etc.

The swarm object contains all information on the agents’ positions, properties, and movement algorithm. It also handles plotting of the agents and saving of agent data to file for further analysis.

Initial agent velocities will be set as the local fluid velocity if present, otherwise zero. Assignment to the velocities attribute can be made directly for other initial conditions.

NOTE: To customize agent behavior, subclass this class and re-implement the method get_positions (do not change the call signature).

Parameters:
  • swarm_size (int, default=100) – Number of agents in the swarm. ignored when using the ‘grid’ init method.

  • envir (environment object, optional) – Environment for the swarm to exist in. Defaults to a newly initialized environment with all of the defaults.

  • init ({'random', 'grid', ndarray}, default='random') –

    Method for initalizing agent positions.

    • ’random’: Uniform random distribution throughout the domain

    • ’grid’: Uniform grid on interior of the domain, including capability to leave out closed immersed structures. In this case, swarm_size is ignored since it is determined by the grid dimensions. Requires the additional keyword parameters grid_dim and testdir.

    • 1D array: All positions set to a single point given by the x,y,[z] coordinates of this array

    • 2D array: All positions as specified. Shape of array should be NxD, where N is the number of agents and D is spatial dimension. In this case, swarm_size is ignored.

  • seed (int, optional) – Seed for random number generator

  • shared_props (dictionary, optional) – dictionary of properties shared by all agents as name-value pairs. If none are provided, two default properties will be created, ‘mu’ and ‘cov’, corresponding to intrinsic mean drift and a covariance matrix for brownian motion respectively. ‘mu’ will be set to an array of zeros with length matching the spatial dimension, and ‘cov’ will be set to an identity matrix of appropriate size according to the spatial dimension. This allows the default agent behavior to be unbiased brownian motion. Examples: * diam: diameter of the particles * m: mass of the particles * Cd: drag coefficient of the particles * cross_sec: cross-sectional area of the particles * R: density ratio

  • props (Pandas dataframe of individual agent properties, optional) – Pandas dataframe of individual agent properties that vary between agents. This is the method by which individual variation among the agents should be specified. The number of rows in the dataframe should match the number of agents. If no dataframe is supplied, a default one is created which contains only the agent starting positions in a column entitled ‘start_pos’. This is to aid in creating more properties later, if desired, as it is only necessary to add columns to the existing dataframe.

  • name (string, optional) – Name of this swarm

  • color (matplotlib color format) – Plotting color (see https://matplotlib.org/stable/tutorials/colors/colors.html)

  • **kwargs (dict, optional) – keyword arguments to be used in the ‘grid’ initialization method or values to be set as a swarm object property. In the latter case, these values can be floats, ndarrays, or iterables, but keep in mind that problems will result with parsing if the number of agents is equal to the spatial dimension - this is to be avoided. This method of specifying agent properties is depreciated: use the shared_props dictionary instead.

  • grid_dim (tuple of int (x, y, [z])) – number of grid points in x, y, [and z] directions for ‘grid’ initialization

  • testdir ({'x0', 'x1', 'y0', 'y1', ['z0'], ['z1']}, optional) – two character string for testing if grid points are in the interior of an immersed structure and if so, masking them in the grid initialization. The first char is x,y, or z denoting the dimensional direction of the search ray, the second is either 0 or 1 denoting the direction (backward vs. forward) along that direction. See documentation of swarm.grid_init for more information.

Attributes

envirenvironment object

environment that this swarm belongs to

positionsmasked array, shape Nx2 (2D) or Nx3 (3D)

spatial location of all the agents in the swarm. the mask is False for any row corresponding to an agent that is within the spatial boundaries of the environment, otherwise the mask for the row is set to True and the position of that agent is no longer updated

pos_historylist of masked arrays

all previous position arrays are stored here. to get their corresponding times, check the time_history attribute of the swarm’s environment.

full_pos_historylist of masked arrays

same as pos_history, but also includes the positions attribute as the last entry in the list

velocitiesmasked array, shape Nx2 (2D) or Nx3 (3D)

velocity of all the agents in the swarm. same masking properties as positions

accelerationsmasked array, shape Nx2 (2D) or Nx3 (3D)

accelerations of all the agents in the swarm. same masking properties as positions

ib_collision1D array of bool with length equal to the swarm size

For each agent, True if the agent collided with an immersed boundary in the most recent time it moved. False otherwise.

propspandas DataFrame

Pandas dataframe of individual agent properties that vary between agents. This is the method by which individual variation among the agents should be specified.

shared_propsdictionary

dictionary of properties shared by all agents as name-value pairs

rndStatenumpy Generator object

random number generator for this swarm, seeded by the “seed” parameter

namestring

name of this swarm

colormatplotlib color format

Plotting color (see https://matplotlib.org/stable/tutorials/colors/colors.html)

Notes

If the agent behavior you are looking for is simply brownian motion with fluid advection, all you need to do is change the ‘cov’ entry in the shared_props dictionary to a covariance matrix that matches the amount of jitter you are looking for. You can also add fixed directional bias by editing the ‘mu’ shared_props entry. This default behavior is then accomplished by solving the relevant SDE using Euler steps, where the step size is the dt argument of the move method which you call in a loop, e.g.:

for ii in range(50):
    swm.move(0.1)

In order to accomodate general, user-defined behavior algorithms, all other agent behaviors should be explicitly specified by subclassing this swarm class and overriding the get_positions method. This is easy, and takes the following form:

class myagents(planktos.swarm):

    def get_positions(self, dt, params=None):
        #
        # Put any calculations here that are necessary to determine
        #   where the agents should end up after a time step of length
        #   dt assuming they don't run into a boundary of any sort.
        #   Boundary conditions, mesh crossings, etc. will be handled
        #   automatically by Planktos after this function returns. The
        #   new positions you return should be an ndarray of shape NxD
        #   where N is the number of agents in the swarm and D is the
        #   spatial dimension of the system. The params argument is
        #   there in case you want this method to take in any external
        #   info (e.g. time-varying forcing functions, user-controlled
        #   behavior switching, etc.). Note that this method has full
        #   access to all of the swarm attributes via the "self"
        #   argument. For example, self.positions will return an NxD
        #   masked array of current agent positions. The one thing this
        #   method SHOULD NOT do is set the positions, velocities, or
        #   accelerations attributes of the swarm. This will be handled
        #   automatically after this method returns, and after boundary
        #   conditions have been checked.

        return newpositions

Then, when you create a swarm object, create it using:

swrm = myagents() # add swarm parameters as necessary, as documented above

This will create a swarm object, but with your my_positions method instead of the default one!

Examples

Create a default swarm in an environment with some fluid data loaded and tiled.

>>> envir = planktos.environment()
>>> envir.read_IBAMR3d_vtk_dataset('../tests/IBAMR_test_data', start=5, finish=None)
>>> envir.tile_flow(3,3)
>>> swrm = swarm(envir=envir)
add_prop(prop_name, value, shared=False)[source]

Method that will automatically delete any conflicting properties when adding a new one.

Parameters:
  • prop_name (str) – name of the property to add

  • value (any) – value to set the property at

  • shared (bool) – if False, set as a property that applies to all agents in the swarm. if True, value should be an ndarray with a number of rows equal to the number of agents in the swarm, and the property will be set as a column in the swarm.props DataFrame.

apply_boundary_conditions(dt, ib_collisions='sliding')[source]

Apply boundary conditions to self.positions.

There should be no reason to call this method directly; it is automatically called by self.move after updating agent positions according to the algorithm found in self.get_positions.

This method compares current agent positions (self.positions) to the previous agent positions (last entry in self.pos_history) in order to first: determine if the agent collided with any immersed structures and if so, to update self.positions using a sliding collision algorithm based on vector projection and second: assess whether or not any agents exited the domain and if so, update their positions based on the boundary conditions as specified in the enviornment class (self.envir).

For noflux boundary conditions such sliding projections are really simple (since the domain is just a box), so we just do them directly/manually instead of folding them into the far more complex, recursive algorithm used for internal mesh structures. Periodic boundary conditions will recursively check for immersed boundary crossings after each crossing of the domain boundary.

Parameters:
  • dt (float) – length of current time step; for updating velocity and accel

  • ib_collisions ({None, 'sliding' (default), 'sticky'}) – Type of interaction with immersed boundaries. If None, turn off all interaction with immersed boundaries. In sliding collisions, conduct recursive vector projection until the length of the original vector is exhausted. In sticky collisions, just return the point of intersection.

calc_re(u, diam=None)[source]

Calculate and return the Reynolds number as experienced by a swarm with characteristic length ‘diam’ in a fluid moving with velocity u. All other parameters will be pulled from the environment’s attributes.

If diam is not specified, this method will look for it in the shared_props dictionary of this swarm.

Parameters:
  • u (float) – characteristic fluid speed, m/s

  • diam (float, optional) – characteristic length scale of a single agent, m

Returns:

float – Reynolds number

property full_pos_history

History of self.positions, including present time.

get_DuDt(time=None, positions=None)[source]

Return the material derivative with respect to time of the fluid velocity at all agent positions (or at provided positions) via linear interpolation of the material gradient.

Current swarm position is used unless alternative positions are explicitly passed in.

In the returned 2D ndarray, each row corresponds to an agent (in the same order as listed in self.positions) and each column is a dimension.

Parameters:
  • time (float, optional) – time at which to return the data. defaults to the current environment time

  • positions (ndarray, optional) – positions at which to return the data. defaults to the locations of the swarm agents, self.positions

Returns:

ndarray with shape NxD, where N is the number of agents and D the – spatial dimension

get_dudt(time=None, positions=None)[source]

Return fluid time derivative at given positions via interpolation.

Current swarm position is used unless alternative positions are explicitly passed in.

In the returned 2D ndarray, each row corresponds to an agent (in the same order as listed in self.positions) and each column is a dimension.

Parameters:
  • time (float, optional) – time at which to return the data. defaults to the current environment time

  • positions (ndarray, optional) – positions at which to return the data. defaults to the locations of the swarm agents, self.positions

Returns:

ndarray with shape NxD, where N is the number of agents and D the – spatial dimension

get_fluid_drift(time=None, positions=None)[source]

Return fluid-based drift for all agents via interpolation.

Current swarm position is used unless alternative positions are explicitly passed in. Any passed-in positions must be an NxD array where N is the number of points and D is the spatial dimension of the system.

In the returned 2D ndarray, each row corresponds to an agent (in the same order as listed in self.positions) and each column is a dimension.

Parameters:
  • time (float, optional) – time at which to return the fluid drift. defaults to the current environment time

  • positions (ndarray, optional) – positions at which to return the fluid drift. defaults to the locations of the swarm agents, self.positions

Returns:

ndarray with shape NxD, where N is the number of agents and D the – spatial dimension

get_fluid_mag_gradient(positions=None)[source]

Return the gradient of the magnitude of the fluid velocity at all agent positions (or at provided positions) via linear interpolation of the gradient.

The gradient is linearly interpolated from the fluid grid to the agent locations. The current environment time is always used, interpolated from data if necessary

Parameters:

positions (ndarray, optional) – positions at which to return the data. defaults to the locations of the swarm agents, self.positions

Returns:

ndarray with shape NxD, where N is the number of agents and D the – spatial dimension

get_positions(dt, params=None)[source]

Returns the new agent positions after a time step of dt.

THIS IS THE METHOD TO OVERRIDE IF YOU WANT DIFFERENT MOVEMENT! Do not change the call signature.

This method returns the new positions of all agents following a time step of length dt, whether due to behavior, drift, or anything else. It should not set the self.positions attribute. Similarly, self.velocities and self.accelerations will automatically be updated outside of this method using finite differences. The only attributes it should change is if there are any user-defined, time-varying agent properties that should be different after the time step (whether shared among all agents, and thus in self.shared_props, or individual to each agent, and thus in self.props). These can be altered directly or by using the add_prop method of this class.

In this default implementation, movement is a random walk with drift as given by an Euler step solver of the appropriate SDE for this process. Drift is the local fluid velocity plus self.get_prop(‘mu’) (‘mu’ is a shared_prop attribute), and the stochasticity is determined by the covariance matrix self.get_prop(‘cov’) (‘cov’ is also a shared_prop attribute).

Parameters:
  • dt (float) – length of time step

  • params (any, optional) – any other parameters necessary

Returns:

ndarray – NxD array of new agent positions after a time step of dt given that the agents started at self.positions. N is the number of agents and D is the spatial dimension of the system.

Notes

When writing code for this method, it can be helpful to make use of the ode generators and solvers in the planktos.motion module. Please see the documentation for the functions of this module for options.

To access the current positions of each agent, use self.positions. self.positions is a masked, NxD array of agent positions where the mask refers to whether or not the agent has exited the domain. You do not want to accidently edit self.positions directly, so make sure that you get a value copy of self.positions using self.positions.copy() whenever that copy will be modified. Direct assignment of self.positions is by reference.

Similarly,self.velocities and self.accelerations will provide initial velocities and accelerations for the time step for each agent respectively. Use .copy() as necessary and do not directly assign to these variables; they will be automatically updated later in the movement process.

The get_fluid_drift method will return the fluid velocity at each agent location using interpolation. Call it once outside of a loop for speed. Similarly, the get_dudt method will return the time derivative of the fluid velocity at the location of each agent. The get_fluid_mag_gradient method will return the gradient of the magnitude of the fluid velocity at the location of each agent.

See also

get_prop

given an agent/swarm property name, return the value(s). When accessing a property in swarm.props, this can be preferred over accessing the property directly through the because instead of returning a pandas Series object (for a column in the DataFrame), it automatically converts to a numpy array first.

add_prop

add a new agent/swarm property or overwrite an old one

get_fluid_drift

return the fluid velocity at each agent location

get_dudt

return time derivative of fluid velocity at each agent

get_fluid_mag_gradient

return the gradient of the magnitude of the fluid velocity at each agent

get_prop(prop_name)[source]

Return the property requested as either a scalar (if shared) or a numpy array, ready for use in vectorized operations (left-most index specifies the agent).

Parameters:

prop_name (str) – name of the property to return

Returns:

property (float or ndarray)

grid_init(x_num, y_num, z_num=None, testdir=None)[source]

Return a flattened array which describes a regular grid of locations, except potentially masking any grid points in the interior of a closed, immersed structure.

The full, unmasked grid will be x_num by y_num [by z_num] on the interior and boundaries of the domain. The output of this method is appropriate for finding FTLE, and that is its main purpose. It will automatically be called by the environment class’s calculate_FTLE method, and if you want to initialize a swarm with a grid this is possible by passing the init=’grid’ keyword argument when the swarm is created. So there is probably no reason to use this method directly.

Grid list moves in the [Z direction], Y direction, then X direction (due to C order of memory layout).

Parameters:
  • x_num (int) – number of grid points in each direction

  • y_num (int) – number of grid points in each direction

  • [z_num] (int) – number of grid points in each direction

  • testdir ({'x0', 'x1', 'y0', 'y1', ['z0'], ['z1']}, optional) – to check if a point is an interior to an immersed structure, a line will be drawn from the point to a domain boundary. If the number of immersed boundary intersections is odd, the point will be considered interior and masked. This check will not be run at all if testdir is None. Otherwise, specify a direction with one of the following: ‘x0’,’x1’,’y0’,’y1’,’z0’,’z1’ (the last two for 3D problems only) denoting the dimension (x,y, or z) and the direction (0 for negative, 1 for positive).

Notes

This algorithm is meant as a huristic only! It is not guaranteed to mask all interior grid points, and will mask non-interior points if there is not a clear line from the point to one of the boundaries of the domain. If this method fails for your geometry and better accuracy is needed, use this method as a starting point and mask/unmask as necessary.

move(dt=1.0, params=None, ib_collisions='sliding', update_time=True, silent=False)[source]

Move all organisms in the swarm over one time step of length dt. DO NOT override this method when subclassing; override get_positions instead!!!

Performs a lot of utility tasks such as updating the positions and pos_history attributes, checking boundary conditions, and recalculating the current velocities and accelerations attributes.

Parameters:
  • dt (float) – length of time step to move all agents

  • params (any, optional) – parameters to pass along to get_positions, if necessary

  • ib_collisions ({None, 'sliding' (default), 'sticky'}) – Type of interaction with immersed boundaries. If None, turn off all interaction with immersed boundaries. In sliding collisions, conduct recursive vector projection until the length of the original vector is exhausted. In sticky collisions, just return the point of intersection.

  • update_time (bool, default=True) – whether or not to update the environment’s time by dt. Probably The only reason to change this to False is if there are multiple swarm objects in the same environment - then you want to update each before incrementing the time in the environment.

  • silent (bool, default=False) – If True, suppress printing the updated time.

See also

get_positions

method that returns (but does not assign) the new positions of the swarm after the time step dt, which Planktos users override in order to specify their own, custom agent behavior.

plot(t=None, filename=None, blocking=True, dist='density', fluid=None, clip=None, figsize=None, save_kwargs=None, azim=None, elev=None)[source]

Plot the position of the swarm at time t, or at the current time if no time is supplied. The actual time plotted will depend on the history of movement steps; the closest entry in environment.time_history will be shown without interpolation.

Parameters:
  • t (float, optional) – time to plot. if None (default), the current time.

  • filename (str, optional) – file name to save image as. Image will not be shown, only saved.

  • blocking (bool, default True) – whether the plot should block execution or not

  • dist ({'density' (default), 'cov', float, 'hist'}) –

    whether to plot Gaussian kernel density estimation or histogram. Options are:

    • ’density’: plot Gaussian KDE using Scotts Factor from scipy.stats.gaussian_kde

    • ’cov’: use the variance in each direction from self.shared_props[‘cov’] to plot Gaussian KDE

    • float: plot Gaussian KDE using the given bandwidth factor to multiply the KDE variance by

    • ’hist’: plot histogram

  • fluid ({'vort', 'quiver'}, optional) –

    Plot info on the fluid in the background. 2D only! If None, don’t plot anything related to the fluid. Options are:

    • ’vort’: plot vorticity in the background

    • ’quiver’: quiver plot of fluid velocity in the background

  • clip (float, optional) – if plotting vorticity, specifies the clip value for pseudocolor. this value is used for both negative and positive vorticity.

  • figsize (tuple of length 2, optional) – figure size in inches, (width, height). default is a heurstic that works… most of the time?

  • save_kwargs (dict of keyword arguments, optional) – keys must be valid strings that match keyword arguments for the matplotlib savefig function. These arguments will be passed to savefig assuming that a filename has been specified.

  • azim (float, optional) – In 3D plots, the azimuthal viewing angle. Defaults to -60.

  • elev (float, optional) – In 3D plots, the elevation viewing angle. Defaults to 30.

plot_all(movie_filename=None, frames=None, downsamp=None, fps=10, dist='density', fluid=None, clip=None, figsize=None, save_kwargs=None, writer_kwargs=None, azim=None, elev=None)[source]

Plot the history of the swarm’s movement, incl. current time in successively updating plots or saved as a movie file. A movie file is created if movie_filename is specified.

Parameters:
  • movie_filename (string, optional) – file name to save movie as. file extension will determine the type of file saved.

  • frames (iterable of integers, optional.) – If None, plot the entire history of the swarm’s movement including the present time, with each step being a frame in the animation. If an iterable, plot only the time steps of the swarm as indexed by the iterable (note, this is an interable of the time step indices, not the time in seconds at those time steps!).

  • downsamp (iterable of int or int, optional) – If None, do not downsample the agents - plot them all. If an integer, plot only the first n agents (equivalent to range(downsamp)). If an iterable, plot only the agents specified. In all cases, statistics are reported for the TOTAL population, both shown and unshown. This includes the histograms/KDE plots.

  • fps (int, default=10) – Frames per second, only used if saving a movie to file. Make sure this is at least as big as 1/dt, where dt is the time interval between frames!

  • dist ({'density' (default), 'cov', float, 'hist'}) –

    whether to plot Gaussian kernel density estimation or histogram. Options are:

    • ’density’: plot Gaussian KDE using Scotts Factor from scipy.stats.gaussian_kde

    • ’cov’: use the variance in each direction from self.shared_props[‘cov’] to plot Gaussian KDE

    • float: plot Gaussian KDE using the given bandwidth factor to multiply the KDE variance by

    • ’hist’: plot histogram

  • fluid ({'vort', 'quiver'}, optional) –

    Plot info on the fluid in the background. 2D only! If None, don’t plot anything related to the fluid. Options are:

    • ’vort’: plot vorticity in the background

    • ’quiver’: quiver plot of fluid velocity in the background

  • clip (float, optional) – if plotting vorticity, specifies the clip value for pseudocolor. this value is used for both negative and positive vorticity.

  • figsize (tuple of length 2, optional) – figure size in inches, (width, height). default is a heurstic that works… most of the time?

  • writer_kwargs (dict of keyword arguments, optional) – keys must be valid strings that match keyword arguments for a matplotlib

  • save_kwargs (dict of keyword arguments, optional) – keys must be valid strings that match keyword arguments for the matplotlib animation.FFMpegWriter object. These arguments will be used in the writer object initiation save assuming that a movie_filename has been specified. Otherwise, defaults are the passed in fps and metadata=dict(artist=’Christopher Strickland’)).

  • azim (float, optional) – In 3D plots, the azimuthal viewing angle. Defaults to -60.

  • elev (float, optional) – In 3D plots, the elevation viewing angle. Defaults to 30.

save_data(path, name, pos_fmt='%.18e')[source]

Save the full position history (with mask and time stamps) along with current velocity and acceleration to csv files. Save shared_props to a npz file and save props to json.

The output format for the position csv is the same as for the save_pos_to_csv method.

shared_props is saved as an npz file since it is likely to contain some mixture of scalars and arrays, but does not vary between the agents so is less likely to be loaded outside of Python. props is saved to json since it is likely to contain a variety of types of data, may need to be loaded outside of Python, and json will be human readable.

Parameters:
  • path (str) – directory for storing data

  • name (str) – prefix name for data files

  • pos_fmt (str format, default='%.18e') – format and precision for storing position, vel, and accel data

save_pos_to_csv(filename, fmt='%.18e', sv_vel=False, sv_accel=False)[source]

Save the full position history including present time, with mask and time stamps, to a csv.

The output format for the position csv will be as follows:

  • The first row contains cycle and time information. The cycle is given, and then each time stamp is repeated D times, where D is the spatial dimension of the system.

  • Each subsequent row corresponds to a different agent in the swarm.

  • Reading across the columns of an agent row: first, a boolean is given showing the state of the mask for that time step. Agents are masked when they have exited the domain. Then, the position vector is given as a group of D columns for the x, y, (and z) direction. Each set of 1+D columns then corresponds to a different cycle/time, as labeled by the values in the first row.

The result is a csv that is N+1 by (1+D)*T, where N is the number of agents, D is the dimension of the system, and T is the number of times recorded.

Parameters:
  • filename (str) – path/name of the file to save the data to

  • fmt (str format, default='%.18e') – fmt argument to be passed to numpy.savetxt for format and precision of numerical data

  • sv_vel (bool, default=False) – whether or not to save the current time velocity data

  • sv_accel (book, default=False) – whether or not to save the current time acceleration data

save_pos_to_vtk(path, name, all=True)[source]

Save position data to vtk as point data (PolyData). A different file will be created for each time step in the history, or just one file of the current positions will be created if the all argument is False.

Parameters:
  • path (str) – location to save the data

  • name (str) – name of dataset

  • all (bool) – if True, save the entire history including the current time. If false, save only the current time.