Active finite Voronoi model of confluent epithelial tissue; simulates self-propelled cells and returns trajectories, tissue statistics and a final snapshot.
pyafv simulates a 2D tissue with the active finite Voronoi (AFV) model. Each cell is a point. The cell shape comes from a Voronoi tessellation of the points, with a finite range. A cell has a maximal radius r, so a cell can have free boundaries against the medium. Standard vertex and Voronoi models cannot do this. Cells can detach, and the tissue can open or fragment.
Each cell has an area and a perimeter. Elastic terms pull the area toward a preferred area A0 and the perimeter toward P0. The parameter Lambda is the tension difference between non-contacting edges and contacting edges. Each cell also self-propels with speed v0 along a direction that diffuses at rate dr.
The example places 40 cells at random in a square of side 12.3 (that is sqrt(40 pi) * 1.1) and moves them for 300 steps of 0.05, so 15 time units. The key assumption is overdamped motion. Position changes by (force + v0 * direction) * dt, with no inertia.
When to use it
Use pyafv to ask how activity, adhesion and preferred shape set the state of a cell collective:
Does a tissue stay confluent, or do cells detach at a given speed?
How do the mean area and the number of contacts change with the preferred perimeter?
How big are the forces that hold neighbors together?
tyssue models a fixed polygon network, where cells keep their neighbors. Artistoo models deformable cells on a pixel grid. pyafv fits when cells move past each other and the tissue edge matters.
Install it yourself
The package needs Python 3.10 up to 3.14. A dry run micromamba create -n t --dry-run -c conda-forge -c bioconda pyafv=0.4.16 resolves pyafv 0.4.16 with Python 3.14.8 on linux-aarch64.
build() returns a dict with forces, areas, perimeters, arclens, coord_nums, vertices, edges_type, regions and connections. To move the cells, compute new positions and call sim.update_positions(pts). PhysicalParams has these fields and defaults: r=1.0, A0=pi, P0=4.8, KA=1.0, KP=1.0, Lambda=0.2, delta=None (0.45 * r) and seed=None.
Run the tested example
This recipe builds pyafv in a container and runs the example below. It needs Docker and nothing else. It last ran on 2026-10-12 in 1.3 s.
Ranges are the limits used for the example recipe; the program itself may accept more.
Reading the output
run_afv.py wraps the script above. At each step it calls sim.build(), records the means, moves the cells with (F + v0 * n) * dt, and turns each direction angle by a random amount of size sqrt(2 * dr * dt). It ends with sim.plot_2d().
The log has one line, done: 40 cells, 300 steps, backend cython. The Cython backend is the fast one. The package falls back to pure Python with a warning if it cannot load it.
summary.csv (21267 bytes) has 301 rows, one per step, with the means over cells. Step 0 shows a mean area of 2.292, a mean perimeter of 5.667, 2.35 contacts per cell and a mean force of 2.409. The force drops within 10 steps to 0.267 as the random start relaxes. Preferred area A0 is pi, 3.14, so the cells stay compressed below it.
trajectory.csv (19344 bytes) holds the cell positions at 11 snapshots, every 30 steps, for 440 rows. tissue.png is the final tessellation.
Your own runs
Copy the docker run line from the recipe and change values. The reference run is the default. At step 300 it has a mean area of 2.309, a mean perimeter of 5.652, 3.00 contacts and a mean force of 0.0834.
Faster cells. Set --v0 0.5. Contacts rose to 3.75 and the mean force to 0.442. The mean area was 2.264.
Smaller preferred perimeter. Set --p0 3.6. The mean area fell to 1.795 and the mean perimeter to 5.035. Contacts were 2.75.
Higher tension difference. Set --lam 1.0. The mean area fell to 2.128 and the perimeter to 5.483. Contacts rose to 3.35.
All runs used seed 42, so the start positions were the same.
Pitfalls
Short runs. The default covers 15 time units. The first rows of summary.csv show the start still relaxing, with the force at 2.409 at step 0. Read the last rows, and check that the means have stopped changing.
Step size. Cells move (force + v0 * direction) * dt per step. With --v0 0.5 the mean force rose from 0.083 to 0.442, so check the force column after raising speed or dt.
Seeds. One seed sets the start positions, the directions and the noise. Use several seeds for a statement about a parameter.
Only means are saved.summary.csv holds means over cells. Use trajectory.csv and tissue.png to look at single cells.
Parameter names. The recipe's helper --lam is the package's Lambda, and --p0 is P0. The helper fixes r=1, A0=pi, KA=1 and KP=1.