lgca.lgca_hex.LGCA_Hex¶
- class lgca.lgca_hex.LGCA_Hex(nodes=None, dims=None, restchannels=0, density=0.1, bc='periodic', seed=None, propagation=True, **kwargs)¶
Bases:
LGCA_SquareClassical LGCA with volume exclusion on a 2D hexagonal lattice.
It holds all methods and attributes that are specific for a hexagonal geometry.
- Attributes:
- coord_pairslist of tuple
Indices of non-border nodes in the
lgca.nodesarray, linearized, each tuple is (x-index, y-index).- dyfloat
Scaling factor for the y axis. 0.87 for hexagonal geometry.
- lx, lyint
Lattice dimensions in x and y direction.
- orientationfloat
Attribute for drawing polygons that represent the nodes. Orientation of the polygon in rad. This is passed to
matplotlib.patches.RegularPolygon(). 0 for hexagonal geometry.- r_polyfloat
Attribute for drawing polygons that represent the nodes. Distance between polygon center and vertices.
- xcoords, ycoordsnp.ndarray
Logical coordinates of non-border nodes starting with 0. Dimensions:
(lgca.lx, lgca.ly).
Warning
Boundary conditions only work for hexagonal LGCA with an even number of rows. LGCA with an uneven number of rows should only be used for plotting.
See also
base.LGCA_baseBase class with geometry-independent methods and attributes.
- apply_abc()¶
Apply absorbing boundary conditions.
Update
self.nodes, using the shadow border nodes and respecting the geometry.
- apply_inflowbc()¶
Apply inflow boundary conditions.
Update
self.nodes, using the shadow border nodes and respecting the geometry.Boundary condition for an inflow from x=0, y=:, with reflecting boundary conditions along the y axis and periodic boundaries along the x axis. Nodes at (x=0, y) are set to a homogeneous state with a constant average density given by the attribute
0 <= self.inflow <= 1.If there is no such attribute, the nodes are filled with the maximum density.
- apply_pbc()¶
Apply periodic boundary conditions.
Update
self.nodes, using the shadow border nodes and respecting the geometry.
- apply_rbc()¶
Apply reflecting boundary conditions.
Update
self.nodes, using the shadow border nodes and respecting the geometry.
- c = array([[ 1.00000000e+00, 5.00000000e-01, -5.00000000e-01, -1.00000000e+00, -5.00000000e-01, 5.00000000e-01], [ 0.00000000e+00, 8.66025404e-01, 8.66025404e-01, 1.22464680e-16, -8.66025404e-01, -8.66025404e-01]])¶
- calc_flux(nodes)¶
Calculate the flux vector for all lattice sites in nodes.
The elements of the flux vectors are computed as the dot product between the LGCA’s neighborhood vectors and the velocity channel configuration in nodes.
- Parameters:
nodes (
numpy.ndarray) – Lattice configuration to compute the flux for. Must have more than or the same number of dimensions asself.nodesandnodes.shape[-1] >= self.velocitychannels. Is typicallyself.nodes.- Returns:
Array of flux vectors at each lattice site. Dimensions:
nodes.shape[:-1] + (len(self.c),).
- calc_permutations()¶
Initialize lazy computation structures for permutations. Only compute permutations when actually needed.
- calc_velocity_correlation(nodes=None)¶
Calculate the correlation between the node fluxes and the mean node flux in the neighborhood. Used to quantify correlated movement. Parameters ———- nodes :
numpy.ndarray- Returns:
Scalar field with the same shape as
self.cell_density.
- calc_vorticity(nodes=None)¶
Calculate the vorticity of the flow field corresponding to the lgca state ‘nodes’. The vorticity is used to characterize rotations in a flow field. For more, see https://en.wikipedia.org/wiki/Vorticity Parameters ———- nodes :
numpy.ndarray- Returns:
Scalar field with the same shape as
self.cell_density.
- channel_weight(qty)¶
Calculate weights for the velocity channels in interactions depending on a field qty.
The weight for the right/diagonal up right/diagonal up left/left/diagonal down left/diagonal down right velocity channel is given by the value of qty of the respective neighboring node.
- Parameters:
qty (
numpy.ndarray) – Scalar field with the same shape asself.cell_density.- Returns:
Weights for the velocity channels of shape
self.dims + (self.velocitychannels,).
- cix = array([ 1. , 0.5, -0.5, -1. , -0.5, 0.5])¶
- ciy = array([ 0.00000000e+00, 8.66025404e-01, 8.66025404e-01, 1.22464680e-16, -8.66025404e-01, -8.66025404e-01])¶
- dy = np.float64(0.8660254037844386)¶
- get_flux_permutations(n_particles)¶
Get flux permutations for
n_particles.
- get_permutations(n_particles)¶
Get permutations for
n_particles.- Parameters:
n_particles (int) – Number of occupied channels.
- Returns:
Array of permutations for
n_particles.
- gradient(qty)¶
Compute the gradient of qty along all axes.
- Parameters:
qty (
numpy.ndarray) – Quantity to take the gradient of. Needs to have the same number of dimensions asself.nodes. Ifqty.shape == self.nodes.shape[:-1]the result can be indexed with the LGCA coordinates (see example).- Returns:
Computed gradient. Dimensions:
qty.shape + (len(self.c),). Ifselfandqtyare 2D arrays,gradient(qty)[...,0]is the gradient in x direction andgradient(qty)[...,1]the gradient in y direction.
Notes
The gradient is calculated using
numpy.gradient()with stepwidth h=0.5 (s.t. no normalization takes place). It is computed as the central finite difference with equidistant support points and supports one-sided differences at the boundaries.In most cases this yields the simple difference between the two closest array elements in the given direction. For example, the gradient at position 1 of
np.array([1, 2, 4])would be (4 - 1)/(2 * 0.5) = 3.Examples
If the input quantity has the same x (and y) dimensions as the LGCA’s nodes, the gradient at each node position can be accessed the same way as the node itself.
>>> from lgca import get_lgca >>> import numpy as np >>> # define a square LGCA to illustrate dimensions >>> lgca = get_lgca(geometry='square', dims=(2,3)) >>> lgca.nodes.shape # (xdim, ydim, number of channels) (4, 5, 4) >>> my_qty = np.array([[0,0,0,0,0], >>> [1,1,1,1,1], >>> [2,2,2,3,2], >>> [3,3,3,3,3]]) >>> my_qty.shape # (xdim, ydim) (4, 5) >>> grad = lgca.gradient(my_qty) >>> grad.shape # (xdim, ydim, number of dimensions) (4, 5, 2) >>> # address like internal LGCA fields: first dimension is x (printed vertically), >>> # second dimension is y (printed horizontally), this can be a bit confusing >>> for coord in lgca.coord_pairs: >>> if np.any(grad[coord]>2): >>> print("Gradient at index", coord, "is ", grad[coord]) >>> print("Configuration at index ", coord, " is ", lgca.nodes[coord], >>> ", with cell density ", lgca.cell_density[coord]) Gradient at index (1, 3) is [3. 0.] Configuration at index (1, 3) is [False False False True] , with cell density 1
The first element of the gradient holds the gradient in x direction, the second element the gradient in y direction. Note that
(1, 3)is the index corresponding to a logical non-border coordinate(0, 2)if the interaction radius is 1. This is relevant for defining a custom field qty: Only the field values at non-border indices will be “felt” by the particles in the LGCA if the interaction is defined accordingly, but border nodes can be used to specify the field’s boundary conditions.The gradient in x direction is 3 = (3 - 0)/1. In y direction it is 0 = (1 - 1)/1.
- init_coords()¶
Initialize LGCA coordinates.
These are used to index the lattice nodes logically and programmatically (see below). Initializes
self.nonborder,self.xcoords,self.ycoordsandself.coord_pairs.See also
set_dimsSet LGCA dimensions.
init_nodesInitialize LGCA lattice configuration.
set_r_intChange the interaction radius.
Notes
self.xcoordsandself.ycoordshold the logical coordinates of non-border nodes in x- and y-direction starting with 0. Non-border nodes belong to the lattice in the mathematical definition of the LGCA, while border nodes (=shadow nodes) are only included in order to implement boundary conditions. The coordinate of every other row is shifted to the right by 0.5 in order to create a zig-zag boundary. Note that since the lattice is two-dimensional, so are the coordinates.>>> lgca = get_lgca(geometry='hex', dims=2) >>> lgca.xcoords array([[0.5, 0. ], [1.5, 1. ]]) >>> lgca.ycoords array([[0., 1.], [0., 1.]])
A column in the printout is a row in the LGCA lattice.
self.nonborderholds the programmatical coordinates of non-border nodes, i.e. the indices of theself.nodesarray where non-border nodes are stored. This is why it is a tuple: Because it is used to index a numpy array. All non-border lattice nodes can be called asself.nodes[self.nonborder].>>> lgca = get_lgca(geometry='hex', dims=2) # default: periodic boundary conditions >>> lgca.r_int 1 >>> lgca.nodes.sum(-1) # show contents of the lattice array([[1, 0, 1, 0], [0, 0, 0, 0], [1, 0, 1, 0], [0, 0, 0, 0]]) >>> lgca.nodes[lgca.nonborder].sum(-1) array([[0, 0], [0, 1]])
Summing along the last axis means summing over all channels of a node since we are interested in the geometry. The first and the last row and column in the output of
lgca.nodes.sum(-1)are the contents of the border (=shadow) nodes, which reflects the interaction radius of 1. The innermost four elements are the contents of the non-border nodes. Accordingly we find their indices to be:>>> lgca.nonborder (array([[1, 1], [2, 2]]), array([[1, 2], [1, 2]]))
The first element of the tuple is the index in x-direction, the second element the index in y-direction. Changing the interaction radius updates the shape of
self.nodesby including more border (=shadow) nodes. This also changes the coordinates. With an interaction radius of 2, there is 2 border nodes on each side enveloping the non-border nodes whose contents remain the same. Therefore the first non-border node has the index 2 in each direction.>>> lgca.set_r_int(2) # change the interaction radius >>> lgca.r_int 2 >>> lgca.nodes.sum(-1) # show contents of the lattice array([[0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1], [0, 0, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1]]) >>> lgca.nonborder (array([[2, 2], [3, 3]]), array([[2, 3], [2, 3]]))
self.coord_pairsis a list of programmatical (x,y) coordinate tuples for iterating through nodes one by one.>>> lgca.set_r_int(1) >>> lgca.coord_pairs [(1, 1), (1, 2), (2, 1), (2, 2)]
- init_nodes(density=0.1, nodes=None, **kwargs)¶
Initialize LGCA lattice configuration. Create the lattice and then assign particles to channels in the nodes.
Initializes
self.nodes. If nodes is not provided, the lattice is initialized randomly so that each node contains on averagedensityparticles. For the random initialization there is a choice between a fixed or random number of particles per node.- Parameters:
density (float, default=0.1) – If nodes is None, initialize lattice randomly with this average number of particles per node.
nodes (
numpy.ndarray) – Custom initial lattice configuration. Dimensions:(self.dims[0], self.dims[1], self.K).
See also
base.LGCA_base.random_resetInitialize lattice nodes with average density density.
set_dimsSet LGCA dimensions.
init_coordsInitialize LGCA coordinates.
- nb_sum(qty)¶
For each node, sum up the contents of qty for the 6 nodes in the von Neumann neughborhood, excluding the center.
qty is assumed to contain the value of a calculated quantity for each node in the lattice. nb_sum calculates the “neighborhood sum” of this quantity for each node, excluding the value for the node’s own position.
- Parameters:
qty (
numpy.ndarray) – Array holding some quantity of the LGCA, e.g. a flux. Of shapeself.dims + x, wherexis the shape of the quantity for one node, e.g.(2,)if it is a vector with 2 elements.self.dimsensures that lattice positions can be indexed the same way as inself.nodes.- Returns:
Sum of the content of qty in each node’s neighborhood, shape:
qty.shape. Lattice positions can be indexed the same way as inself.nodes.
Examples
>>> lgca = get_lgca(geometry='hex', density=0.3, dims=4) # periodic boundary conditions >>> lgca.cell_density[lgca.nonborder] array([[1, 2, 2, 1], [0, 2, 0, 0], [2, 2, 0, 1], [1, 2, 2, 3]]) >>> lgca.nb_sum(lgca.cell_density).astype(int)[lgca.nonborder] array([[ 6, 10, 7, 9], [ 8, 7, 7, 5], [ 9, 6, 10, 5], [11, 9, 10, 7]]) >>> # perform lgca.plot_density() to visualise for clarity
lgca.cell_densityis used as the argument qty. The value at each position in the resulting array is the sum of the values at the neighboring positions in the source array. Note that the reduction to the non-border nodes can only be done after the sum calculation in order to preserve boundary conditions. To interpret the cell density output, note that every other row of nodes is shifted to the right. Therefore node (1,1) has the neighborhood (0,0), (0,1), (0,2), (1,0), (1,2) and (2,1).
- plot_density(density=None, channels=slice(None, None, None), figindex=None, figsize=None, tight_layout=True, cmap='viridis', vmax=None, edgecolor='None', cbar=True, cbarlabel='Particle number $n$')¶
Plot particle density in the lattice. A color bar on the right side shows the color coding of density values. Empty nodes are white.
- Parameters:
cbar (bool, default=True) – Whether to draw a colorbar for the plot on an extra axis to the right.
cbarlabel (str, default='Particle number $n$') – Label of the colorbar.
channels (slice) – Indices of the velocity/resting channels that should be considered for the density calculation if density is None.
cmap (str or
matplotlib.colors.Colormap, default=’viridis’) – Color map for the density values. Used to construct a discretized version of the colormap.colorbarwidth (float) – Width of the additional axis for the color bar, passed to
mpl_toolkits.axes_grid1.axes_divider.AxesDivider.append_axes().density (
numpy.ndarray, optional) – Particle density values for a lattice to plot. If set to None and a simulation has been performed before, the result of the simulation is plotted. Dimensions:self.dims.edgecolor ({
matplotlibcolor, ‘None’, ‘auto’}, default ‘None’) – Color of the polygon edges for the lattice nodes.figindex (int or str, optional) – An identifier for the figure (passed to
matplotlib.pyplot.figure()). If it is a string, the figure label and the window title is set to this value.figsize (tuple of int or tuple of float with 2 elements, default=(8,8)) – Desired figure size in inches
(x, y).tight_layout (bool, default=True) – If
matplotlib.figure.Figure.tight_layout()is called for padding between and around subplots.vmax (int, optional) – Maximum density value for the color scaling. The minimum value is zero. All density values higher than vmax are drawn in the color at the end of the color bar. If None, vmax is set to the number of channels
self.K.**kwargs – Arguments to be passed on to
setup_figure().
- Returns:
Density plot over time.
See also
setup_figureManage basic layout.
- print_interactions()¶
Print the list of pre-implemented interactions for this LGCA type.
- print_nodes()¶
Print the full lattice configuration as integers.
- propagation()¶
Perform the transport step of the LGCA: Move particles through the lattice according to their velocity.
Updates
self.nodessuch that resting particles (the contents ofself.nodes[:, 6:]) stay in their position and particles in velocity channels (the contents ofself.nodes[:, :6]) are relocated according to the direction of the channel they reside in. Boundary conditions are enforced later byapply_boundaries().See also
base.LGCA_base.nodesState of the lattice showing the structure of the
lgca.nodesarray.
Notes
>>> # set up the node configuration >>> nodes = np.zeros((4,4,7)).astype(bool) >>> nodes[1,1,:] = True >>> lgca = get_lgca(geometry='hex', nodes=nodes) >>> lgca.cell_density[lgca.nonborder] array([[0, 0, 0, 0], [0, 7, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) >>> lgca.nodes[lgca.nonborder] # leftmost column of the lattice array([[[False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]], # column 1 [[False, False, False, False, False, False, False], [ True, True, True, True, True, True, True], # node (1,1): all channels are filled [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]], # column 2 [[False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]], # rightmost column [[False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]]])
Before propagation, seven particles occupy node (1,1). It lies one node away from the bottom left of the lattice. One particle resides in each velocity channel and one in the resting channel.
>>> lgca.propagation() >>> lgca.update_dynamic_fields() # to update lgca.cell_density >>> lgca.cell_density[lgca.nonborder] array([[1, 1, 1, 0], # left column of the lattice [1, 1, 1, 0], [0, 1, 0, 0], [0, 0, 0, 0]]) # right column of the lattice >>> lgca.nodes[lgca.nonborder] # leftmost column of the lattice array([[[False, False, False, False, True, False, False], # node (0,0): particle moving diagonally downwards left [False, False, False, True, False, False, False], # node (0,1): particle moving to the left [False, False, True, False, False, False, False], # node (0,2): particle moving diagonally upwards left [False, False, False, False, False, False, False]], # column 1 [[False, False, False, False, False, True, False], # node (1,0): particle moving diagonally downwards right [False, False, False, False, False, False, True], # node (1,1): resting particle [False, True, False, False, False, False, False], # node (1,2): particle moving diagonally upwards right [False, False, False, False, False, False, False]], # column 2 [[False, False, False, False, False, False, False], [ True, False, False, False, False, False, False], # node (2,1): particle moving to the right [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]], # rightmost column [[False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False], [False, False, False, False, False, False, False]]]) >>> # perform lgca.plot_density() to visualise for clarity
To interpret the cell density output, note that every other row of nodes is shifted to the right. Therefore node (1,2) is positioned diagonally upwards to the right from node (1,1). There is no node straight upwards from node (1,1). The particle with velocity to the right has moved to the right velocity channel in node (2,1) to the right of node (1,1) and the particles in the other velocity channels have also moved according to their direction (see output annotation). The resting particle stayed in its channel in node (1,1).
- r_poly = np.float64(0.5773502691896257)¶
- random_reset(density)¶
Randomly fill channels so that each node has on average
densityparticles.Each channel is independently occupied with probability
density / self.K.- Parameters:
density (float) – Desired average number of particles per node.
density = total_number_of_particles / number_of_nodes.
- set_bc(bc)¶
Set the boundary conditions.
Selects a method which is called every timestep to enforce boundary conditions. The methods to select from are implemented in the derived classes. The chosen one is assigned to
self.apply_boundaries().- Parameters:
bc ({'absorbing', 'reflecting', 'periodic', 'inflow'}) – Boundary conditions. Not all bc are supported in all geometries (yet).
- set_dims(dims=None, nodes=None, restchannels=0)¶
Set LGCA dimensions.
Initializes
self.K,self.restchannels,self.dims,self.lxandself.ly.- Parameters:
dims (int or tuple, default=(50,50)) – Lattice dimensions. Must match with specified geometry, an integer is interpreted as
(dims, dims).nodes (np.ndarray) – Custom initial lattice configuration.
restchannels (int, default=0) – Number of resting channels.
- set_interaction(**kwargs)¶
Set the interaction rule and respective needed parameters.
Set
self.interactionand possibly add entries inself.interaction_params. Do not use this to specify a custom interaction. In order to do this (as of now),self.interactionandself.interaction_paramsmust be manipulated directly from an external script.- Parameters:
kwargs['interaction'] (str, default='random_walk') – Name of the predefined interaction in
lgca.interactions.**kwargs – Interaction parameters.
- set_r_int(r)¶
Change the interaction radius. Update shadow border nodes accordingly.
This has effects on
self.nodes, the coordinates and the computed fields.- Parameters:
r (int) – New interaction radius.
- setup_figure(figindex=None, figsize=(8, 8), tight_layout=True)¶
Create a
matplotlibfigure and manage basic layout.Used by the class’ plotting functions.
- Parameters:
figindex (int or str, optional) – An identifier for the figure (passed to
matplotlib.pyplot.figure()). If it is a string, the figure label and the window title is set to this value.figsize (tuple of int or tuple of float with 2 elements, default=(8,8)) – Desired figure size in inches
(x, y).tight_layout (bool, default=True) – If
matplotlib.figure.Figure.tight_layout()is called for padding between and around subplots.
- Returns:
fig (
matplotlib.figure.Figure) – New customized figure.ax (
matplotlib.axes.Axes) – Drawing axis associated with fig.
See also
plot_densityPlot particle density over time.
plot_fluxPlot flux over time.
- timeevo(timesteps=100, record=False, recordN=False, recorddens=True, showprogress=True, recordpertype=False)¶
Perform a simulation of the LGCA for timesteps timesteps.
Different quantities can be recorded during the simulation, e.g. the total number of particles at each timestep. They are stored in LGCA attributes.
- Parameters:
timesteps (int, default=100) – How long the simulation should be performed.
record (bool, default=False) – Record the full lattice configuration for each timestep in
self.nodes_t.recorddens (bool, default=True) – Record the number of particles at each lattice site for each timestep in
self.dens_t.recordN (bool, default=False) – Record the total number of particles in the lattice for each timestep in
self.n_t.recordpertype (bool, default=False) – Record the number of particles in velocity channels/resting channels at each lattice site for each timestep in
self.velcells_tandself.restcells_t, respectively.showprogress (bool, default=True) – Show a simple progress bar with a percentage of performed timesteps in the standard output.
- timestep()¶
Update the state of the LGCA from time k to k+1. Includes the interaction and propagation steps.
- total_population()¶
Calculate the amount of particles in the lattice.
- Returns:
Total population size.
- update_dynamic_fields()¶
Update “fields” from the current LGCA state that store important variables to compute other dynamic steps.
Computes
self.cell_density, number of particles at each lattice node.