We have already learned how to iterate over cells in the simulation.
Quite often in the multi-cell simulations there is a need to visit
neighbors of a single cell. We define a neighbor as an adjacent cell
which has common surface area with the cell in mind. To enable neighbor
tracking you have to include NeighborTracker plugin in the XML or in
Python code which replaces XML. For details see
CompuCellPythonTutorial/NeighborTracker
example. Take a look at the
implementation of the step function where we visit cell neighbors:
from cc3d.core.PySteppables import *
class NeighborTrackerPrinterSteppable(SteppableBasePy):
def __init__(self, frequency=100):
SteppableBasePy.__init__(self, frequency)
def step(self, mcs):
for cell in self.cell_list:
for neighbor, common_surface_area in self.get_cell_neighbor_data_list(cell):
if neighbor:
print("neighbor.id", neighbor.id, " common_surface_area=", common_surface_area)
else:
print("Medium common_surface_area=", common_surface_area)
In the outer for loop we iterate over all cells. During each iteration this loop picks a single cell. For each such cell we construct the inner loop where we access a list of cell neighbors:
for neighbor, common_surface_area in self.get_cell_neighbor_data_list(cell):
Notice that during each iteration loop Python returns two objects:
neighbor and common surface area. neighbor points to a cell object that
has nonzero common surface area with the cell from the outer loop. It
can happen that the neighbor object returned by the inner loop is None
.
This means that this particular cell from the outer loop touches Medium.
Take a look at the if-else statement in the example code above. If you
want to paste neighbor iteration code template into your simulation go
to CC3D Python->Visit->Cell Neighbors
in Twedit++.
If you are puzzled why loop above has two variables after for
it is because self.get_cell_neighbor_data_list(cell)
object when iterated over will return tuples of two objects. Let's do an experiment:
for neighbor_tuple in self.get_cell_neighbor_data_list(cell):
print neighbor_tuple
if neighbor_tuple[0]:
print('Cell id = ', neighbor_tuple[0].id)
else:
print('Got Medium Cell ')
print('Common Surface Area = ', neighbor_tuple[1])
The output will be:
neighbor_tuple= (<CompuCell.CellG; proxy of <Swig Object of type 'std::vector< CompuCell3D::CellG * >::value_type' at 0x0000000007388EA0> >, 5)
Cell id = 11
Common Surface Area = 5
neighbor_tuple= (None, 4)
Got Medium Cell
Common Surface Area = 4
Now you can see neighbor_tuple
is indeed an object that has two components. First one neighbor_tuple[0]
points to
cell object, second one neighbor_tuple[1]
is a common surface area.
In general, when Python iterates over a list-like object that returns tuples you have two choices how to write the for
loop. You can either use
for neighbor_tuple in self.get_cell_neighbor_data_list(cell):
print(neighbor_tuple[0], neighbor_tuple[1])
and refer to to the elements of the returned tuple using indices or you can be more explicit and unpack
the tuple
directly into two variables and access them by different "names":
for neighbor, common_surface_area in self.get_cell_neighbor_data_list(cell):
print neighbor, common_surface_area
In addition to a plain-vanilla iteration over neighbors the CellNeighborDataList
object that you get using
self.get_cell_neighbor_data_list(cell)
has few useful tools that summarize properties of cell neighbors.
Sometimes we are interested in a common surface area of a given cell
with ALL neighbors that are of specific type.
CellNeighborDataList
has a convenience function common_surface_area_with_cell_types
that computes it. Here is an example
for cell in self.cell_list:
neighbor_list = self.get_cell_neighbor_data_list(cell)
common_area_with_types = neighbor_list.common_surface_area_with_cell_types(cell_type_list=[1, 2])
print 'Common surface of cell.id={} with cells of types [1,2] = {}'.format(cell.id, common_area_with_types)
The example output is:
Common surface of cell.id=10 with cells of types [1,2] = 24
Common surface of cell.id=11 with cells of types [1,2] = 22
As you can see common_surface_area_with_cell_types
returns a number that is a total common surface area of a given cell
with other cells of the type that you specify as argument to common_surface_area_with_cell_types
function as shown above
If you want to break the above common surface area by cell types. i.e. you want to know what was the common
surface area with cells of type 1, what was the common surface area with cells of type 2, etc..., you want to use
neighbor_list.common_surface_area_by_type()
call :
for cell in self.cellList:
neighbor_list = self.get_cell_neighbor_data_list(cell)
common_area_with_types = neighbor_list.common_surface_area_with_cell_types(cell_type_list=[1, 2])
print 'Common surface of cell.id={} with cells of types [1,2] = {}'.format(cell.id, common_area_with_types)
common_area_by_type_dict = neighbor_list.common_surface_area_by_type()
print 'Common surface of cell.id={} with neighbors \ndetails {}'.format(cell.id, common_area_by_type_dict)
The output may look as follows:
Common surface of cell.id=10 with cells of types [1,2] = 20
Common surface of cell.id=10 with neighbors
details defaultdict(<type 'int'>, {1L: 15, 2L: 5})
Common surface of cell.id=11 with cells of types [1,2] = 24
Common surface of cell.id=11 with neighbors
details defaultdict(<type 'int'>, {1L: 15, 2L: 9})
For cell with id=10
we have that the total common surface area with cell types 1 and 2 is 20 and if we "zoom-in"
we can see that cell with id=10
had common surface area of 15
with cell of types 1
and 5
with cells of type 2
The two contact areas by type ad up to 20
as expected because this particular cell is in contact only with cells of type
1
and 2
.
Similar thinking explains common surface areas for cell 11.
A more interesting thing is to look at cell with id==
. In this particular simulation this cell was in contact with Medium
and the output looks as follows:
Common surface of cell.id=1 with cells of types [1,2] = 12
Common surface of cell.id=1 with neighbors
details defaultdict(<type 'int'>, {0: 10, 1L: 1, 2L: 11})
Now you see that the overlap with cells of type 0
, 1
, 2
was 10
, 1
, 11
and this does not add up
to 12
- the total contact area between cell with id=1
and cells of type 1
and 2
.
However if we replaced
common_area_with_types = neighbor_ist.common_surface_area_with_cell_types(cell_type_list=[1, 2])
with
common_area_with_types = neighbor_list.common_surface_area_with_cell_types(cell_type_list=[0, 1, 2])
all the surfaced areas for cell with id=1
would add up as they did for cells with id=10
If you want to know how many neighbors of a given type a given cell has you can do "manual" iteration of all neighbors
and keep track of how many of them were of a particular type or you can use a convenience function neighbor_count_by_type
.
neighbor_count_by_type
will return a dicitonary where the key is a type io the neighbor and the value is
how many neighbors of this type are in contact with a given cell
Here is an example:
for cell in self.cell_list:
neighbor_list = self.get_cell_neighbor_data_list(cell)
neighbor_count_by_type_dict = neighbor_list.neighbor_count_by_type()
print 'Neighbor count for cell.id={} is {}'.format(cell.id, neighbor_count_by_type_dict)
and the output is:
Neighbor count for cell.id=1 is defaultdict(<type 'int'>, {0: 1, 1L: 1, 2L: 2})
Neighbor count for cell.id=2 is defaultdict(<type 'int'>, {0: 1, 1L: 2, 2L: 1})
...
Neighbor count for cell.id=11 is defaultdict(<type 'int'>, {1L: 4, 2L: 2})
Neighbor count for cell.id=12 is defaultdict(<type 'int'>, {1L: 2, 2L: 3})
Here is an explanation: cell with id==2
had one neighbor of type Medium (key 0
), two neighbor of type 1
(key 1
),
and one neighbor of type 2
(key 2
)
Cell with id=11
was in contact with six cells - 4 of them were of type 1
and two were of type 2