Robust Guidance by Followers

Persistent Identifier

Use this permanent link to cite or share this Morpheus model:

Guidance-by-followers combined with one of several simple rule sets for integration of signals from multiple neighbors provides a robust mechanism for collective cell migration.

Introduction

Collective cell migration is an important process during biological development and tissue repair but may turn malignant during tumor invasion. Recently, guidance-by-followers was identified as an underlying mechanism of collective cell migration in the zebrafish embryo. When multiple cells interact simultaneously, this poses the question of how the guidance stimuli are integrated.

In this study, a recent individual-based model is extended by an integration step of the vectorial guidance stimuli and model predictions are compared for different variants of the mechanism:

  • neglecting guidance over steric interactions
  • arithmetic mean of stimuli,
  • dominance of stimulus with largest transmission interface,
  • and dominance of most head-on stimulus.

Description

During gastrulation in zebrafish, the elongating body axis is composed of distinct cell populations. The axis is headed by the mesendodermal polster followed by the posterior axial mesoderm. The movement of both these tissues relies on different mechanisms. Individual polster cells undergo active random run-and-tumble migration but, as a group, they coordinate their movement to exhibit guided collective migration. Meanwhile, the posterior axial mesoderm elongates by undergoing convergence and extension.

Cell-autonomous behavior of polster cells (without guidance-by-followers) and the implementation of the local mechanism described as ‘guidance-by-followers’ using MembraneProperties and the DirectedMotion component is described in model M0006 in full detail.
Figure 1: Collective cell migration due to guidance-by-followers during gastrulation in zebrafish. Schematic of the dorsal **(A)** and lateral **(B)** view of a zebrafish embryo during gastrulation, at 60% epiboly stage. The polster (green), the (posterior) axial mesoderm (yellow), and paraxial mesoderm (gray) are part of the inner layer of cells, and all move toward the animal pole of the embryo. Polster cells undergo a collective migration while axial mesoderm extends by the addition of new cells by internalization and by convergence and extension. These two tissues are made up of several layers of cells and constitute the body axis that elongates during gastrulation. The paraxial mesoderm is a flat monolayer of cells flanking the body axis, which also moves toward the animal pole. *A*, animal pole; *Veg*, vegetal pole; *R*, right; *L*, left, *D*, dorsal; *V*, ventral. **(C, D)** The schematic description of the cell migration model. In **(C)**, the direction of motion is transmitted from a moving cell to a polster cell (green) when it is hit by the moving cell (here, a yellow axial cell). When multiple cells interact simultaneously **(D)**, competing input signals need to be processed to determine the direction of motion of each cell. ([*CC BY 4.0*](https://creativecommons.org/licenses/by/4.0/): [**Müller _et al._**](#reference), [Fig. 1](https://www.frontiersin.org/articles/10.3389/fams.2023.1163583/full#F1))
Figure 1: Collective cell migration due to guidance-by-followers during gastrulation in zebrafish. Schematic of the dorsal (A) and lateral (B) view of a zebrafish embryo during gastrulation, at 60% epiboly stage. The polster (green), the (posterior) axial mesoderm (yellow), and paraxial mesoderm (gray) are part of the inner layer of cells, and all move toward the animal pole of the embryo. Polster cells undergo a collective migration while axial mesoderm extends by the addition of new cells by internalization and by convergence and extension. These two tissues are made up of several layers of cells and constitute the body axis that elongates during gastrulation. The paraxial mesoderm is a flat monolayer of cells flanking the body axis, which also moves toward the animal pole. A, animal pole; Veg, vegetal pole; R, right; L, left, D, dorsal; V, ventral. (C, D) The schematic description of the cell migration model. In (C), the direction of motion is transmitted from a moving cell to a polster cell (green) when it is hit by the moving cell (here, a yellow axial cell). When multiple cells interact simultaneously (D), competing input signals need to be processed to determine the direction of motion of each cell. (CC BY 4.0: Müller et al., Fig. 1)

For tissue-scale simulations, a confined Space in 2D is used, mimicking the presence of paraxial mesoderm on both sides. A Population of $400$ cells is initialized and is given $20\ \text{min}$ time to settle and adjust their shapes and packing. Once this initial phase is over, two Events are triggered by which the cells are assigned an identity based on their position along the anteroposterior (top-down) axis, and the appropriate motility characteristics are applied to them. Unless stated otherwise, we choose to split the population in a position such that one-third becomes polster and two-thirds become axial cells.

The initial split of the overall cell population into two cell types establishes a front between polster and axial cells. Polster cells (green in Fig. 1) exhibit random, run-and-tumble movement and are sensitive to guidance-by-followers. Axial cells (yellow in Fig. 1) display a DirectedMotion oriented toward the top of the simulation and are not responsive to guidance-by-followers. Both cell types can trigger guidance-by-followers in polster cells.

If there are multiple neighbor cells moving toward the considered cell, then an additional rule for integrating potentially multiple guidance-by-follower signals is needed. To investigate the impact of such signal integration, we here consider and compare four hypothetical signal processing models. In the model implementation, this can be chosen by setting the constant pushing_mode to $0\ \text{-}\ 3$. The four signal processing models are defined as follows and are illustrated in Fig. 2.

  • Model 0 ignores the guidance-by-followers interaction and serves as a quantifiable baseline where cells will collide and squeeze past each other due to steric interactions.
  • Model 1 sets the cell velocity to the arithmetic mean of neighbors' velocity vectors which fall within a max_angle sector, with the length of each cell-cell contact serving as the weighting factor.
  • Model 2 sets the cell velocity to that of the neighbor which has the largest contact length given that its velocity vector falls within the max_angle sector around the contact vector, ignoring other candidates. If the largest contact does not fulfill the max_angle sector criterion because it strikes more tangential, then the next smaller contact is evaluated.
  • Model 3 sets the cell velocity to that of the neighbor with the best aligned velocity vector, i.e., the smallest angle between velocity and contact vectors, ignoring other candidates.
**Figure 2:** A schematic representation of cell-cell interactions upon contact. **(A)** Three cells, here of different sizes, move as indicated by their velocity vectors (left). Upon contact (right), sectors up to the angle $\alpha_\text{max}$ around each velocity vector indicate whether guidance is exerted (when the direction to the cell’s center of mass falls within, yellow) or not (gray). **(B)** Four cell-cell interaction models and their resulting velocity vectors (black unchanged, red changed upon contact): Model 0 is considered as a baseline without velocity changes upon contact. Model 1 yields the mean of the impact velocities, weighted by cell-cell contact length. Model 2 yields the velocity vector of the impact with the largest cell-cell contact length. Model 3 yields the velocity vector of the impact that is oriented closest to the cell center. ([*CC BY 4.0*](https://creativecommons.org/licenses/by/4.0/): [**Müller _et al._**](#reference), [Fig. 2](https://www.frontiersin.org/articles/10.3389/fams.2023.1163583/full#F2))
Figure 2: A schematic representation of cell-cell interactions upon contact. (A) Three cells, here of different sizes, move as indicated by their velocity vectors (left). Upon contact (right), sectors up to the angle $\alpha_\text{max}$ around each velocity vector indicate whether guidance is exerted (when the direction to the cell’s center of mass falls within, yellow) or not (gray). (B) Four cell-cell interaction models and their resulting velocity vectors (black unchanged, red changed upon contact): Model 0 is considered as a baseline without velocity changes upon contact. Model 1 yields the mean of the impact velocities, weighted by cell-cell contact length. Model 2 yields the velocity vector of the impact with the largest cell-cell contact length. Model 3 yields the velocity vector of the impact that is oriented closest to the cell center. (CC BY 4.0: Müller et al., Fig. 2)

Results

The different rule-based models with specific modalities of neighbor information integration by polster cells can quasi-quantitatively predict polster behavior (Müller et al., 2023) and produce robust guidance of polster cells by the axial mesoderm (see Vid. 1 and Fig. 3 below), suggesting that the guidance-by-followers process is robust to variation in cellular mechanisms such as:

  • multiple conflicting stimuli,
  • variability in cell size,
  • random cell motility parameters,
  • variations in cell numbers.

For these results and comparison to experimental data please see the publication.

Video 1: Simulation video of M0008_robust-guidance-by-followers_model.xml (CC BY 4.0: Müller et al., Supplementary Video 2)
**Figure 3:** Snapshots of a simulation without the guidance-by-follower mechanism (model 0) and of three different realizations of the guidance-by-follower mechanism (models 1–3) at simulation times $20, 320, 620\ \mathrm{min}$. Cells are `color`-coded based on their identity, source of the velocity vector, and alignment. **Yellow:** Axial cells with motility as indicated by the yellow arrow on the left. Leading axial cells define the front speed. **Light green:** Polster cells guided by follower cells such that their orientation points to the same quadrant as the axial front velocity. **Orange:** Polster cells guided by follower cells away from the quadrant of the axial front velocity. **Dark green:** Polster cells with orientation into the same quadrant as the axial front velocity by chance due to run-and-tumble motion. **Red:** Polster cells with run-and-tumble motion away from the quadrant of the axial front velocity. ([*CC BY 4.0*](https://creativecommons.org/licenses/by/4.0/): [**Müller _et al._**](#reference), [Fig. 3](https://www.frontiersin.org/articles/10.3389/fams.2023.1163583/full#F3))
Figure 3: Snapshots of a simulation without the guidance-by-follower mechanism (model 0) and of three different realizations of the guidance-by-follower mechanism (models 1–3) at simulation times $20, 320, 620\ \mathrm{min}$. Cells are color-coded based on their identity, source of the velocity vector, and alignment. Yellow: Axial cells with motility as indicated by the yellow arrow on the left. Leading axial cells define the front speed. Light green: Polster cells guided by follower cells such that their orientation points to the same quadrant as the axial front velocity. Orange: Polster cells guided by follower cells away from the quadrant of the axial front velocity. Dark green: Polster cells with orientation into the same quadrant as the axial front velocity by chance due to run-and-tumble motion. Red: Polster cells with run-and-tumble motion away from the quadrant of the axial front velocity. (CC BY 4.0: Müller et al., Fig. 3)

Reference

This model is the original used in the publication, up to technical updates:

R. Müller, A. Boutillon, D. Jahn, J. Starruß, N. B. David, L. Brusch: Collective cell migration due to guidance-by-followers is robust to multiple stimuli. Front. Appl. Math. Stat. 9: 2297-4687, 2023.

Model

Get this model via:

  • Morpheus-Link or
  •  Download: M0008_robust-guidance-by-followers_model.xml
  • XML Preview

    <?xml version='1.0' encoding='UTF-8'?>
    <MorpheusModel version="4">
        <Description>
            <Details>Full title:		Collective cell migration due to guidance-by-followers is robust to multiple stimuli
    Authors:		R. Müller, D. Jahn, J. Starruß, A. Boutillon, N. B. David, L. Brusch
    Contributors:	D. Jahn
    Date:		10.02.2023
    Software:       	Morpheus (open-source). Download from https://morpheus.gitlab.io
    Model ID:		https://identifiers.org/morpheus/M0008
    Units: 		[time] = min, [space] = μm
    Reference:		This model is the original used in the publication, up to technical updates:
    		R. Müller, A. Boutillon, D. Jahn, J. Starruß, N. B. David, L. Brusch: Collective cell migration due to guidance-by-followers is robust to multiple stimuli. Front. Appl. Math. Stat. 9: 2297-4687, 2023.
    		https://doi.org/10.3389/fams.2023.1163583
    Comment:		Extension of the guidance-by-followers mechanism (https://identifiers.org/morpheus/M0006) comparing robustness of various integrations of multiple simultaneous cell-cell contact stimuli.</Details>
            <Title>Robust Guidance by Followers</Title>
        </Description>
        <Space>
            <Lattice class="hexagonal">
                <Neighborhood>
                    <Order>1</Order>
                </Neighborhood>
                <Size symbol="size" value="500, 2400, 0"/>
                <BoundaryConditions>
                    <Condition type="periodic" boundary="x"/>
                    <Condition type="periodic" boundary="y"/>
                </BoundaryConditions>
            </Lattice>
            <SpaceSymbol symbol="space"/>
            <MembraneLattice>
                <Resolution symbol="membrane_size" value="50"/>
                <SpaceSymbol symbol="membrane_pos"/>
            </MembraneLattice>
        </Space>
        <Time>
            <StartTime value="0"/>
            <StopTime value="620"/>
            <TimeSymbol symbol="time"/>
            <RandomSeed value="0"/>
        </Time>
        <Global>
            <Constant symbol="init_time" name="Time for cell initialization" value="20"/>
            <Constant symbol="directed_motion_strength_global" name="Speed of axial cells" value="0.5">
                <Annotation>Strength of the DirectedMotion plugin</Annotation>
            </Constant>
            <Constant symbol="run_duration_adjustment" name="Mean duration of run phases" tags="RunandTumble" value="0.76"/>
            <Constant symbol="RandT_or_Mech_motion_strength_global" name="Speed of polster cells" value="0.5">
                <Annotation>Strength of the DirectedMotion plugin</Annotation>
            </Constant>
            <Constant symbol="max_angle" name="Max. angle of influence" tags="guidance_by_followers" value="pi/6">
                <Annotation>Angle between cell movement and direction to the neighbor. Below max_angle, the cell is considered as moving towards the neighbouring cell and will influence its direction</Annotation>
            </Constant>
            <Constant symbol="min_push_velocity" name="Min. velocity of influence" tags="guidance_by_followers" value="0.1">
                <Annotation>Minimal velocity for a cell to influence its neighbors</Annotation>
            </Constant>
            <VariableVector symbol="velocity" value="0.0, 0.0, 0.0">
                <Annotation>Needed for NeighborhoodReporters neighbor_velocity_x and neighbor_velocity_y</Annotation>
            </VariableVector>
            <Variable symbol="axial_cell_front_y" name="Y-coordinate of axial cell front" value="-1"/>
            <Variable symbol="polster_cell_front_y" name="Y-coordinate of polster cell front" value="-1"/>
            <Variable symbol="axial_cell_front_y_init" name="Y-coordinate of axial cell front right after initialization at t = init_time" value="-1"/>
            <Variable symbol="polster_cell_front_y_init" name="Y-coordinate of polster cell front right after initialization at t = init_time" value="-1"/>
            <Event trigger="on-change" time-step="1.0" name="Save Y-coordinate of initial axial and polster cell fronts">
                <Condition>time == init_time</Condition>
                <Rule symbol-ref="axial_cell_front_y_init">
                    <Expression>axial_cell_front_y</Expression>
                </Rule>
                <Rule symbol-ref="polster_cell_front_y_init">
                    <Expression>polster_cell_front_y</Expression>
                </Rule>
            </Event>
            <Function symbol="axial_cell_front_speed" name="Speed of axial cell front">
                <Expression>(axial_cell_front_y - axial_cell_front_y_init) / (time - init_time)</Expression>
            </Function>
            <Function symbol="polster_cell_front_speed" name="Speed of polster cell front">
                <Expression>(polster_cell_front_y - polster_cell_front_y_init) / (time - init_time)</Expression>
            </Function>
            <Variable symbol="polster_cells_total" name="Total number of polster cells" value="-1"/>
            <Variable symbol="polster_cells_orientated_total" name="Total number of polster cells orientated ±45° " value="-1">
                <Annotation>Total number of polster cells orientated ±45°</Annotation>
            </Variable>
            <Function symbol="polster_cells_orientated_ratio" name="Fraction of polster cells orientated ±45°">
                <Expression>polster_cells_orientated_total/polster_cells_total</Expression>
            </Function>
            <Constant symbol="pushing_mode" name="Mode selector for evaluation of multi-neighbor collisions (default: 1)" tags="py" value="1">
                <Annotation>0 = none, 1 = contact-weighted mean, 2 = largest contact takes all, 3 = min angle takes all</Annotation>
            </Constant>
            <Function symbol="cell_count" name="Parameter for pushing mode 2" tags="py">
                <Expression>celltype.cell.size</Expression>
            </Function>
        </Global>
        <CellTypes>
            <CellType class="biological" name="cell">
                <Function symbol="color" name="Set cell colors">
                    <Annotation>Cell colors:
    
    - white: uninitialized cells (time >= init_time)
    - yellow: axial cells (RandT_or_Mech_motion_strength == 0)
    - red: RaT polster cells not orientated ±45° (mech_induced_dir.abs == 0 and !polster_cell_orientated)
    - orange: guided polster cells not orientated ±45°(mech_induced_dir.abs > 0 and !polster_cell_orientated)
    - bright green: guided polster cells orientated ±45°(mech_induced_dir.abs > 0 and polster_cell_orientated)
    - dark green: RaT polster cells randomly orientated ±45° (mech_induced_dir.abs == 0 and polster_cell_orientated)</Annotation>
                    <Expression>if(time >= init_time, if(RandT_or_Mech_motion_strength > 0, if(mech_induced_dir.abs > 0, if(polster_cell_orientated, 5, 3), if(polster_cell_orientated, 6, 2)), 4), 1)</Expression>
                </Function>
                <SurfaceConstraint target="1" strength="1" mode="aspherity"/>
                <Property symbol="target_volume" value="326.0"/>
                <VolumeConstraint target="target_volume" strength="1"/>
                <Property symbol="directed_motion_strength" name="Strength of the directed motion" value="0.0"/>
                <Event time-step="1.0" name="Axial cell initialization">
                    <Annotation>After init_time, set the properties of axial cells</Annotation>
                    <Condition>time == init_time and cell.center.y &lt;= 360</Condition>
                    <Rule symbol-ref="directed_motion_strength">
                        <Expression>directed_motion_strength_global</Expression>
                    </Rule>
                    <Rule symbol-ref="RandT_or_Mech_motion_strength">
                        <Expression>0</Expression>
                    </Rule>
                </Event>
                <PropertyVector symbol="directed_motion_dir" name="Direction of the directed motion (axial cells)." value="0.0, 1.0, 0.0"/>
                <DirectedMotion direction="directed_motion_dir" name="Directed movement of axial cells" strength="directed_motion_strength">
                    <Annotation>Set to strength = 0 for polster cells</Annotation>
                </DirectedMotion>
                <Property symbol="RandT_or_Mech_motion_strength" name="Strength of Run and Tumble or mechanically induced motion." tags="guidance_by_followers" value="0"/>
                <Event time-step="1.0" name="Polster cell initialization">
                    <Annotation>After init_time, set the properties of polster cells</Annotation>
                    <Condition>time == init_time and cell.center.y > 360</Condition>
                    <Rule symbol-ref="RandT_or_Mech_motion_strength">
                        <Expression>RandT_or_Mech_motion_strength_global</Expression>
                    </Rule>
                    <Rule symbol-ref="directed_motion_strength">
                        <Expression>0</Expression>
                    </Rule>
                </Event>
                <Property symbol="tumble.run_duration" name="run duration" tags="RunandTumble" value="0.0"/>
                <Property symbol="tumble.last" name="last tumble event" tags="RunandTumble" value="0"/>
                <PropertyVector symbol="RaT_dir" tags="RunandTumble" value="0.0, 0.0, 0.0"/>
                <Event trigger="when-true" time-step="5" name="Run and Tumble" tags="RunandTumble">
                    <Condition>(time >= tumble.last + tumble.run_duration)</Condition>
                    <Rule symbol-ref="tumble.last">
                        <Expression>time</Expression>
                    </Rule>
                    <Rule symbol-ref="tumble.run_duration" name="new update time">
                        <Expression>run_duration_adjustment * rand_gamma(0.5, 5)</Expression>
                    </Rule>
                    <Intermediate symbol="angle" value="rand_uni(0, 2 * pi)"/>
                    <VectorRule symbol-ref="RaT_dir" notation="φ,θ,r">
                        <Expression>angle, 0 , 1</Expression>
                    </VectorRule>
                </Event>
                <MembraneProperty symbol="cell_center_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <MembraneProperty symbol="cell_center_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <Mapper time-step="1.0" name="cell_center_x" tags="py">
                    <Input value="cell.center.x"/>
                    <Output symbol-ref="cell_center_x"/>
                </Mapper>
                <Mapper time-step="1.0" name="cell_center_y" tags="py">
                    <Input value="cell.center.y"/>
                    <Output symbol-ref="cell_center_y"/>
                </Mapper>
                <MembraneProperty symbol="neighbor_center_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <MembraneProperty symbol="neighbor_center_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <NeighborhoodReporter time-step="1.0" name="neighbor_center_x" tags="py">
                    <Input scaling="length" value="cell.center.x"/>
                    <Output symbol-ref="neighbor_center_x" mapping="discrete"/>
                </NeighborhoodReporter>
                <NeighborhoodReporter time-step="1.0" name="neighbor_center_y" tags="py">
                    <Input scaling="length" value="cell.center.y"/>
                    <Output symbol-ref="neighbor_center_y" mapping="discrete"/>
                </NeighborhoodReporter>
                <PropertyVector symbol="velocity" value="0.0, 0.0, 0.0"/>
                <MotilityReporter time-step="1" name="Get velocity for computing mechanics">
                    <Annotation>Needs time-step = 1</Annotation>
                    <Velocity symbol-ref="velocity"/>
                </MotilityReporter>
                <MembraneProperty symbol="neighbor_velocity_x" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <MembraneProperty symbol="neighbor_velocity_y" tags="py" value="0.0">
                    <Diffusion rate="0"/>
                </MembraneProperty>
                <NeighborhoodReporter time-step="1.0" name="neighbor_velocity_x" tags="py">
                    <Input scaling="length" value="velocity.x"/>
                    <Output symbol-ref="neighbor_velocity_x" mapping="discrete"/>
                </NeighborhoodReporter>
                <NeighborhoodReporter time-step="1.0" name="neighbor_velocity_y" tags="py">
                    <Input scaling="length" value="velocity.y"/>
                    <Output symbol-ref="neighbor_velocity_y" mapping="discrete"/>
                </NeighborhoodReporter>
                <Property symbol="py_angle" tags="py" value="0.0"/>
                <Property symbol="py_induced_dir_x" tags="py" value="0.0"/>
                <Property symbol="py_induced_dir_y" tags="py" value="0.0"/>
                <PyMapper time-step="1" name="Code for multi-neighbor collisions" tags="py">
                    <Annotation>Needs time-step = 1</Annotation>
                    <Input symbol-ref="pushing_mode"/>
                    <Input symbol-ref="cell_count"/>
                    <Input symbol-ref="cell_center_x"/>
                    <Input symbol-ref="cell_center_y"/>
                    <Input symbol-ref="neighbor_center_x"/>
                    <Input symbol-ref="neighbor_center_y"/>
                    <Input symbol-ref="neighbor_velocity_x"/>
                    <Input symbol-ref="neighbor_velocity_y"/>
                    <Input symbol-ref="max_angle"/>
                    <Input symbol-ref="min_push_velocity"/>
                    <Script>import numpy as np
    
    # merge all inputs into one big data frame df
    # with two multiindex-columns "MemX"=membrane-position and "Cell"=cell.id
    # and many rows for all membrane-positions and one cell after the other
    df = pandas.concat([cell_center_x, cell_center_y, neighbor_center_x, neighbor_center_y, neighbor_velocity_x, neighbor_velocity_y], axis=1, keys=['cell_center_x', 'cell_center_y', 'neighbor_center_x', 'neighbor_center_y', 'neighbor_velocity_x', 'neighbor_velocity_y'])
    
    # relative_position_to_neighbor = if(neighbor_center.abs>0, cell.center - neighbor_center, 0)
    df['neighbor_center_abs'] = (df['neighbor_center_x']**2+df['neighbor_center_y']**2)**0.5
    df['relative_position_to_neighbor_x'] = df['cell_center_x']-df['neighbor_center_x']
    df['relative_position_to_neighbor_y'] = df['cell_center_y']-df['neighbor_center_y']
    df.loc[df['neighbor_center_abs'] == 0, 'relative_position_to_neighbor_x'] = np.NaN
    df.loc[df['neighbor_center_abs'] == 0, 'relative_position_to_neighbor_y'] = np.NaN
    
    # angle = if(neighbor_center.abs>0, neighbor_velocity.phi - relative_position_to_neighbor.phi, pi)
    df['angle'] = np.arctan2(df['neighbor_velocity_y'],df['neighbor_velocity_x'])-np.arctan2(df['relative_position_to_neighbor_y'],df['relative_position_to_neighbor_x'])
    df.loc[df['neighbor_center_abs'] == 0, 'angle'] = np.pi
    df['angle_abs'] = abs((df['angle'] + np.pi) % (2 * np.pi) - np.pi)
    
    # induced_dir = if(cos(angle)>cos(max_angle) and neighbor_velocity.abs>min_push_velocity, neighbor_velocity, 0)
    df['induced_dir_x'] = df['neighbor_velocity_x']
    df['induced_dir_y'] = df['neighbor_velocity_y']
    df['induced_dir_abs'] = (df['neighbor_velocity_x']**2+df['neighbor_velocity_y']**2)**0.5
    df.loc[(np.cos(df['angle'])&lt;np.cos(max_angle)) | (df['induced_dir_abs']&lt;min_push_velocity), 'induced_dir_x'] = np.NaN
    df.loc[(np.cos(df['angle'])&lt;np.cos(max_angle)) | (df['induced_dir_abs']&lt;min_push_velocity), 'induced_dir_y'] = np.NaN
    #print(df.to_string())
    
    # to monitor, also return per-cell aggregated intermediate quantities
    py_angle = df.groupby('Cell')['angle'].mean()
    
    #aggregate information per cell and apply summary statistics as selected by pushing_mode
    if pushing_mode == 0:
    #    print("Pushing mode 0: nothing to be done.")
        pass
    
    elif pushing_mode == 1:
    #    print("Pushing mode 1: contact-length weighted mean of direction vectors.")
        py_induced_dir_x = df.groupby('Cell')['induced_dir_x'].mean()
        py_induced_dir_y = df.groupby('Cell')['induced_dir_y'].mean()
    
    elif pushing_mode == 2:
    #    print("Pushing mode 2: winner takes all, longest contact wins.")
        df2 = df.groupby('Cell')['induced_dir_x'].apply(lambda x: list(x.mode()))
        df2.loc[df2.apply(len) == 0] = 0.0
        df3 = df.groupby('Cell')['induced_dir_y'].apply(lambda x: list(x.mode()))
        for i in range(1,int(cell_count)+1):
            if(df2[i] == 0.0):
                py_induced_dir_x[i] = 0.0
                py_induced_dir_y[i] = 0.0
            else:
                py_induced_dir_x[i] = df2[i][0]
                py_induced_dir_y[i] = df3[i][0]
    
    elif pushing_mode == 3:
    #    print("Pushing mode 3: winner takes all, the angle closest to zero wins.")
        df2 = df.reset_index()
        py_induced_dir_x = df2.loc[df2.groupby('Cell')['angle_abs'].idxmin()]['induced_dir_x']
        py_induced_dir_y = df2.loc[df2.groupby('Cell')['angle_abs'].idxmin()]['induced_dir_y']
    else:
        print("This mode ",pushing_mode," does not match 0, 1, 2, 3 and still needs to be defined.")
    
    #print(py_induced_dir_x.to_string())
    #print(py_induced_dir_y.to_string())</Script>
                    <Output symbol-ref="py_angle"/>
                    <Output symbol-ref="py_induced_dir_x"/>
                    <Output symbol-ref="py_induced_dir_y"/>
                </PyMapper>
                <PropertyVector symbol="mech_induced_dir" name="Mechanically induced direction (computed in the PyMapper)." notation="x,y,z" tags="py" value="0.0, 0.0, 0.0"/>
                <VectorEquation symbol-ref="mech_induced_dir" name="Set induced direction of polster cells" notation="x,y,z" tags="py">
                    <Expression>py_induced_dir_x, py_induced_dir_y, 0.0</Expression>
                </VectorEquation>
                <PropertyVector symbol="dir" name="Direction of polster cells" value="0.0, 0.0, 0.0"/>
                <VectorEquation symbol-ref="dir" name="Set direction of polster cells">
                    <Expression>if(mech_induced_dir.abs > 0, mech_induced_dir, RaT_dir)</Expression>
                </VectorEquation>
                <DirectedMotion direction="dir" name="Directed movement polster cells" strength="RandT_or_Mech_motion_strength" tags="guidance_by_followers">
                    <Annotation>RandT or mechanically induced movement. Set to strength = 0 for axial cells</Annotation>
                </DirectedMotion>
                <Function symbol="axial_cell_y" name="Y-coordinate of axial cells">
                    <Expression>cell.center.y * (RandT_or_Mech_motion_strength == 0)</Expression>
                </Function>
                <Function symbol="polster_cell_y" name="Y-coordinate of polster cells">
                    <Expression>cell.center.y * (RandT_or_Mech_motion_strength > 0)</Expression>
                </Function>
                <Mapper time-step="1.0" name="Y-coordinate of axial cell front">
                    <Annotation>The axial cell furthest ahead determines the axial cell front</Annotation>
                    <Input value="axial_cell_y"/>
                    <Output symbol-ref="axial_cell_front_y" mapping="maximum"/>
                </Mapper>
                <Mapper time-step="1.0" name="Y-coordinate of polster cell front">
                    <Annotation>The polster cell furthest ahead determines the polster cell front</Annotation>
                    <Input value="polster_cell_y"/>
                    <Output symbol-ref="polster_cell_front_y" mapping="maximum"/>
                </Mapper>
                <Function symbol="polster_cell" name="Flag for polster cells">
                    <Annotation>Returns:
        - '1' if cell is a polster cell
        - '0' if cell is a polster cell</Annotation>
                    <Expression>RandT_or_Mech_motion_strength > 0 and time >= init_time</Expression>
                </Function>
                <Mapper time-step="orientation_logger_time_step" name="Sum of polster cells">
                    <Input value="polster_cell"/>
                    <Output symbol-ref="polster_cells_total" mapping="sum"/>
                </Mapper>
                <Function symbol="polster_cell_orientated" name="Flag for polster cells orientated ±45°">
                    <Annotation>Returns:
        - '1' for polster cells moving in y direction ±45°
        - '0' for polster cells moving in other directions and all axial cells</Annotation>
                    <Expression>RandT_or_Mech_motion_strength > 0 and velocity.phi >= 1/4 * pi and velocity.phi &lt;= 3/4 * pi</Expression>
                </Function>
                <Mapper name="Sum of polster cells orientated ±45°">
                    <Annotation>Sums up polster cells oriented ±45°</Annotation>
                    <Input value="polster_cell_orientated"/>
                    <Output symbol-ref="polster_cells_orientated_total" mapping="sum"/>
                </Mapper>
                <Function symbol="velocity.angle_rotated" name="Transform angle using 'arctan2'">
                    <Annotation>Transform the angle of the cell from Morpheus' 0° in x-axis direction to 0° when the cell moves in y-axis direction. The direction of movement of the cell is then given as an absolute difference in degrees from the ideal movement in the y-axis direction.</Annotation>
                    <Expression>atan2(-cos(velocity.phi), sin(velocity.phi)) / pi * 180</Expression>
                </Function>
                <!--    <Disabled>
            <Function symbol="velocity.angle" name="Convert angle to dregrees">
                <Annotation>Convert the angle from radians to degrees</Annotation>
                <Expression>velocity.phi / pi * 180</Expression>
            </Function>
        </Disabled>
    -->
                <!--    <Disabled>
            <Function symbol="velocity.angle_rotated_if" name="Transform angles using 'if'">
                <Annotation>Transform the angle using if-conditions and modulo operator</Annotation>
                <Expression>if(mod(velocity.angle + 270, 360) > 180, mod(velocity.angle + 270, 360) - 360, mod(velocity.angle + 270, 360))</Expression>
            </Function>
        </Disabled>
    -->
                <!--    <Disabled>
            <Function symbol="velocity.angle_rotated_mod" name="Transform angle using 'mod'">
                <Annotation>Transform the angle using only the modulo operator</Annotation>
                <Expression>mod(mod(velocity.angle + 270, 360) + 180, 360) - 180</Expression>
            </Function>
        </Disabled>
    -->
            </CellType>
            <CellType class="biological" name="confinement">
                <FreezeMotion>
                    <Condition>1</Condition>
                </FreezeMotion>
                <Property symbol="color" value="0"/>
            </CellType>
        </CellTypes>
        <CPM>
            <Interaction>
                <Contact type1="cell" type2="confinement" value="20.0"/>
            </Interaction>
            <ShapeSurface scaling="norm">
                <Neighborhood>
                    <Order>3</Order>
                </Neighborhood>
            </ShapeSurface>
            <MonteCarloSampler stepper="edgelist">
                <MCSDuration value="0.1"/>
                <MetropolisKinetics temperature="1"/>
                <Neighborhood>
                    <Order>1</Order>
                </Neighborhood>
            </MonteCarloSampler>
        </CPM>
        <CellPopulations>
            <Population type="cell" name="Axial and polster cells" size="1">
                <InitRectangle random-offset="5" number-of-cells="400" mode="regular">
                    <Dimensions origin="160,15.0, 0.0" size="180.0, 650.0, 1.0"/>
                </InitRectangle>
            </Population>
            <Population type="confinement" name="Lateral confinement" size="1">
                <InitCellObjects mode="order">
                    <Arrangement displacements="1, 1, 1" repetitions="1, 1, 1">
                        <Box origin="0.0, 0.0, 0.0" size="150.0, 2400.0*0.866, 0.0"/>
                    </Arrangement>
                </InitCellObjects>
                <InitCellObjects mode="distance">
                    <Arrangement displacements="1, 1, 1" repetitions="1, 1, 1">
                        <Box origin="size.x-150, 0.0, 0.0" size="150.0, 2400.0*0.866, 0.0"/>
                    </Arrangement>
                </InitCellObjects>
            </Population>
        </CellPopulations>
        <Analysis>
            <ModelGraph format="png" reduced="false" include-tags="#untagged,RunandTumble,guidance_by_followers,py"/>
            <Gnuplotter time-step="10">
                <Plot title=" ">
                    <Cells min="0" max="6" value="color">
                        <ColorMap>
                            <Color value="0" color="gray90"/>
                            <Color value="1" color="white"/>
                            <Color value="2" color="light-red"/>
                            <Color value="3" color="orange"/>
                            <Color value="4" color="yellow"/>
                            <Color value="5" color="green"/>
                            <Color value="6" color="forest-green"/>
                        </ColorMap>
                    </Cells>
                    <CellArrows orientation="dir * 0"/>
                </Plot>
                <Terminal name="png" size="1440, 1440, 0"/>
            </Gnuplotter>
            <Logger time-step="620" name="Orientation summary statistics">
                <Annotation>For plot 'Velocity Angle ±45°'</Annotation>
                <Input>
                    <Symbol symbol-ref="directed_motion_strength_global"/>
                    <Symbol symbol-ref="axial_cell_front_y_init"/>
                    <Symbol symbol-ref="axial_cell_front_y"/>
                    <Symbol symbol-ref="axial_cell_front_speed"/>
                    <Symbol symbol-ref="polster_cells_orientated_ratio"/>
                    <Symbol symbol-ref="polster_cell_front_y_init"/>
                    <Symbol symbol-ref="polster_cell_front_y"/>
                    <Symbol symbol-ref="polster_cell_front_speed"/>
                </Input>
                <Output>
                    <TextOutput separator="semicolon" file-name="stats_orientation_summary" file-format="csv"/>
                </Output>
            </Logger>
            <Logger time-step="620" name="Orientation per cell">
                <Annotation>For plot 'Velocity Angle Distribution'</Annotation>
                <Restriction condition="RandT_or_Mech_motion_strength > 0">
                    <Celltype celltype="cell"/>
                </Restriction>
                <Input>
                    <Symbol symbol-ref="velocity.phi"/>
                    <Symbol symbol-ref="velocity.angle_rotated"/>
                </Input>
                <Output>
                    <TextOutput separator="semicolon" file-name="stats_orientation_cells" file-format="csv"/>
                </Output>
            </Logger>
            <Logger time-step="1.0" name="Helper logger; do not turn off">
                <Annotation>Do not turn off. Helper logger ensuring that the VectorEquations for 'mech_induced_dir' and 'dir' are periodically evaluated by the TimeScheduler.</Annotation>
                <Restriction>
                    <Celltype celltype="cell"/>
                </Restriction>
                <Input>
                    <Symbol symbol-ref="dir.z"/>
                </Input>
                <Output>
                    <TextOutput file-name="helper_logger" file-format="matrix"/>
                </Output>
            </Logger>
        </Analysis>
    </MorpheusModel>
    
    

    This model requires a special Morpheus version that features the PyMapper capability. For this, please see the installation instructions below.
    Model Graph
    Model Graph

    Installation Instructions

    The model can be run with a prebuilt Morpheus PyMapper version, which requires Docker installed on a Linux system.

    Install the Docker Engine with the following commands:

    sudo apt update
    sudo apt install docker.io
    

    Now enter the directory containing the model M0008_robust-guidance-by-followers_model.xml and run it by typing:

    sudo docker run --rm -it -v${PWD}:/morpheus registry.gitlab.com/morpheus.lab/morpheus/morpheus-python M0008_robust-guidance-by-followers_model.xml
    

    When you call the model for the first time, a Docker image with the Morpheus PyMapper version is automatically downloaded before launching the simulation. You should now be able to see the Morpheus output in the console and observe how the simulation results are written to the same folder where the XML file is located.

    Downloads

    Files associated with this model:

    Next