ImmuneCellRule.java

import ru.biosoft.physicell.biofvm.VectorUtil;
import ru.biosoft.physicell.core.Cell;
import ru.biosoft.physicell.core.CellFunctions.CustomCellRule;
import ru.biosoft.physicell.core.Model;
import ru.biosoft.physicell.core.Phenotype;
import ru.biosoft.physicell.core.RandomGenerator;

public class ImmuneCellRule extends CustomCellRule
{
    private RandomGenerator rng;

    public ImmuneCellRule(Model model)
    {
        rng = model.getRNG();
    }
    public void execute(Cell pCell, Phenotype phenotype, double dt)
    {
        int attach_lifetime_i = pCell.customData.findVariableIndex( "attachment_lifetime" );

        if( phenotype.death.dead == true )
        {
            // the cell death functions don't automatically turn off custom functions, since those are part of mechanics.
            // Let's just fully disable now.
            pCell.functions.customCellRule = null;
            return;
        }

        // if I'm docked
        if( pCell.state.numberAttachedCells() > 0 )
        {
            // attempt to kill my attached cell
            Cell attached = pCell.state.attachedCells.iterator().next();///[0];
            boolean detachMe = false;

            if( attemptApoptosis( pCell, attached, dt ) )
            {
                triggerApoptosis( pCell, attached );
                detachMe = true;
            }

            // decide whether to detach
            if( rng.checkRandom( dt / ( pCell.customData.get( attach_lifetime_i ) + 1e-15 ) ) )
                detachMe = true;

            // if I dettach, resume motile behavior
            if( detachMe )
            {
                Cell.detachCells( pCell, attached );
                phenotype.motility.isMotile = true;
            }
            return;
        }

        // I'm not docked, look for cells nearby and try to docked if this returns non-NULL, we're now attached to a cell
        if( checkNeighborsForAttachment( pCell, dt ) != null )
        {
            phenotype.motility.isMotile = false;
            return;
        }
        phenotype.motility.isMotile = true;
    }

    static boolean triggerApoptosis(Cell pAttacker, Cell pTarget)
    {
        if( pTarget.phenotype.death.dead )
            return false;
        pTarget.startDeath( pTarget.phenotype.death.findDeathModelIndex( "apoptosis" ) );
        return true;
    }

    boolean attemptApoptosis(Cell pAttacker, Cell pTarget, double dt)
    {
        int oncoproteinIndex = pTarget.customData.findVariableIndex( "oncoprotein" );
        int killRateIndex = pAttacker.customData.findVariableIndex( "kill_rate" );

        double oncoproteinSaturation = pAttacker.customData.get( "oncoprotein_saturation" ); // 2.0;
        double oncoproteinThreshold = pAttacker.customData.get( "oncoprotein_threshold" ); // 0.5; // 0.1;
        double oncoproteinDifference = oncoproteinSaturation - oncoproteinThreshold;

        double targetOconoprotein = pTarget.customData.get( oncoproteinIndex );
        if( targetOconoprotein < oncoproteinThreshold )
            return false;

        double scale = ( targetOconoprotein - oncoproteinThreshold ) / oncoproteinDifference;
        scale = Math.min( scale, 1.0 );

        if( rng.checkRandom( pAttacker.customData.get( killRateIndex ) * scale * dt ) )
            return true;
        return false;
    }

    public Cell checkNeighborsForAttachment(Cell pAttacker, double dt)
    {
        for( Cell nearbyCell : pAttacker.cells_in_my_container() )
        {
            if( nearbyCell != pAttacker )// don't try to kill yourself
            {
                if( attemptAttachment( pAttacker, nearbyCell, dt ) )
                    return nearbyCell;
            }
        }
        return null;
    }

    boolean attemptAttachment(Cell pAttacker, Cell pTarget, double dt)
    {
        double oncoprotein_saturation = pAttacker.customData.get( "oncoprotein_saturation" );
        double oncoprotein_threshold = pAttacker.customData.get( "oncoprotein_threshold" );
        double maxAttachmentDistance = pAttacker.customData.get( "max_attachment_distance" );
        double minAttachmentDistance = pAttacker.customData.get( "min_attachment_distance" );
        double targetOncoprotein = pTarget.customData.get( "oncoprotein" );
        if( targetOncoprotein > oncoprotein_threshold && !pTarget.phenotype.death.dead )
        {
            double distance = VectorUtil.dist( pTarget.position, pAttacker.position );
            if( distance > maxAttachmentDistance )
                return false;

            double attachRate = pAttacker.customData.get( "attachment_rate" );
            double scale = ( targetOncoprotein - oncoprotein_threshold ) / ( oncoprotein_saturation - oncoprotein_threshold );
            double distanceScale = ( maxAttachmentDistance - distance ) / ( maxAttachmentDistance - minAttachmentDistance );
            attachRate *= Math.min( scale, 1.0 ) * Math.min( distanceScale, 1.0 );
            if( rng.checkRandom( attachRate * dt ) )
                Cell.attachcCells( pAttacker, pTarget );
            return true;//TODO: should we return true only if attached successfully?
        }
        return false;
    }
}