EmergentOrganization/cell-rpg

View on GitHub
core/src/io/github/emergentorganization/cellrpg/systems/CASystems/GeneticCells/DGRN4j/DGRN.java

Summary

Maintainability
C
1 day
Test Coverage
package io.github.emergentorganization.cellrpg.systems.CASystems.GeneticCells.DGRN4j;

import it.uniroma1.dis.wsngroup.gexf4j.core.*;
import it.uniroma1.dis.wsngroup.gexf4j.core.data.*;
import it.uniroma1.dis.wsngroup.gexf4j.core.impl.GexfImpl;
import it.uniroma1.dis.wsngroup.gexf4j.core.impl.StaxGraphWriter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.xml.crypto.KeySelectorException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;


public class DGRN {
    public static Attribute attr_ActivationValue;
    public static Attribute attr_AlleleCount;
    private final Logger logger = LogManager.getLogger(getClass());
    public final Random randomGenerator;
    public Graph graph;
    public final String ACTIVATION_VALUE_ID;
    private final String ALLELE_COUNT_ID = "# of alleles";
    private final OutflowNodeHandler handleOutputNodes;
    private final InflowNodeHandler inflowNodeHandle;

    public DGRN(String creator, String description, AttributeList attrList, Attribute attributeActivationValue,
                OutflowNodeHandler outflowNodeHandler, InflowNodeHandler inflowNodeHandler) {
        this(creator, description, attrList, attributeActivationValue, outflowNodeHandler, inflowNodeHandler,
                new Random());
    }

    private DGRN(String creator, String description, AttributeList attrList, Attribute attributeActivationValue,
                 OutflowNodeHandler outflowNodeHandler, InflowNodeHandler inflowNodeHandler, Random randomizer) {
        randomGenerator = randomizer;
        attr_AlleleCount = attrList.createAttribute(
                ALLELE_COUNT_ID,
                AttributeType.INTEGER,
                ALLELE_COUNT_ID
        );
        attr_ActivationValue = attributeActivationValue;
        ACTIVATION_VALUE_ID = attr_ActivationValue.getId();
        inflowNodeHandle = inflowNodeHandler;
        handleOutputNodes = outflowNodeHandler;
        initGraph(creator, description, attrList);
        addNodes(inflowNodeHandler.getListOfInflowNodes());
        addNodes(outflowNodeHandler.getListOfOutflowNodes());
        primeInflowNodes();
    }

    public static int getNodeAttributeIndex(Node node, String attributeId) throws KeySelectorException {
        // gets value from attribute with given Id from given node
        // throws KeySelectorException if node not found
        // TODO: this should should really be handled within the gexf4j library.
        AttributeValueList attributes = node.getAttributeValues();
        for (int i = 0; i < attributes.size(); i++) {
            if (attributes.get(i).getAttribute().getId().equals(attributeId)) {
                return i;
            }
        } // else
        throw new KeySelectorException("attribute '" + attributeId + "' not found!");
    }

    public static void setNodeAttributeValue(Node node, String attributeId, String newValue) throws KeySelectorException {
        // gets value from attribute with given Id from given node
        // throws KeySelectorException if node not found
        // TODO: this should should really be handled within the gexf4j library.
        for (AttributeValue attr : node.getAttributeValues()) {
            //System.out.println("s_attrId:" + attr.getAttribute().getId());
            if (attr.getAttribute().getId().equals(attributeId)) {
                attr.setValue(newValue);
                return;
            }
        } // else
        throw new KeySelectorException("attribute '" + attributeId + "' not found!");
    }

    public static String getNodeAttributeValue(Node node, String attributeId) throws KeySelectorException {
        // gets value from attribute with given Id from given node
        // throws KeySelectorException if node not found
        // TODO: this should should really be handled within the gexf4j library.
        for (AttributeValue attr : node.getAttributeValues()) {
            //System.out.println("g_attrId:" + attr.getAttribute().getId());
            if (attr.getAttribute().getId().equals(attributeId)) {
                return attr.getValue();
            }
        } // else
        throw new KeySelectorException("attribute '" + attributeId + "' not found!");
    }

    public List<Edge> getAllEdgesOf(Node node) {
        // returns list of all edges with given node as tgt or src
        List<Edge> edges = new ArrayList<Edge>();
        for (Edge edge : graph.getAllEdges()) {
            if (edge.getSource().getId().equals(node.getId())
                    || edge.getTarget().getId().equals(node.getId())) {
                edges.add(edge);
            }
        }
        return edges;
    }

    void primeInflowNodes() {
        // inserts appropriate values into inflow nodes
        for (String node : inflowNodeHandle.getListOfInflowNodes()) {
            try {
                int newValue = inflowNodeHandle.getInflowNodeValue(node);
                logger.trace("inflow " + node + " set to " + newValue);
                setNodeAttributeValue(getNode(node), ACTIVATION_VALUE_ID, Integer.toString(newValue));
            } catch (KeySelectorException err) {
                logger.error("inflow node '" + node + "' attr '" + ACTIVATION_VALUE_ID + "' not set; not found!", err);
            }
        }
    }

    private void initGraph(String creator, String description, AttributeList attrList) {
        Gexf gexf = new GexfImpl();
        gexf.getMetadata()
                .setLastModified(Calendar.getInstance().getTime())
                .setCreator(creator)
                .setDescription(description);
        gexf.setVisualization(false);

        graph = gexf.getGraph();
        graph.getAttributeLists().add(attrList);

        graph.setDefaultEdgeType(EdgeType.DIRECTED)
                .setMode(Mode.STATIC);
    }

    private void addNodes(String[] nodeNameList) {
        for (String nodeName : nodeNameList) {
            Node colorAdd1 = graph.createNode(nodeName);
            colorAdd1
                    .setLabel(nodeName)
                    .getAttributeValues()
                    .addValue(attr_ActivationValue, "0")
                    .addValue(attr_AlleleCount, "1");
        }
    }

    public void connect(String nodeId1, String nodeId2, int connectionWeight) throws KeySelectorException {
        // connects n1 -> n2 with given weight
        Node n1 = getNode(nodeId1);
        Node n2 = getNode(nodeId2);
        if (n1.hasEdgeTo(nodeId2)) {
            throw new IllegalStateException("node '" + nodeId1 + "' is already connected to '" + nodeId2 + "'");
        } else {
            n1.connectTo(n2).setWeight(connectionWeight);
        }
    }

    private void deductEdgeWeightFromSrc(Edge edge) throws KeySelectorException {
        // deduct the magnitude of the edge weight from the src potential
        Node src = edge.getSource();
        int oldVal = Integer.parseInt(getNodeAttributeValue(src, ACTIVATION_VALUE_ID));
        int newVal = oldVal - Math.abs((int) edge.getWeight());
        setNodeAttributeValue(src, ACTIVATION_VALUE_ID, Integer.toString(newVal));
    }

    boolean edgePropagatesSignal(Edge edge) throws KeySelectorException {
        // returns true if the given node has a signal that should be passed along the given edge
        // assuming single-step weight-threshold-gate drain
        // check if src node has enough potential to traverse edge
        try {
            int edgeMagnitude = Math.abs((int) edge.getWeight());
            // potential must be positive to traverse edge, even if edge weight is negative
            //    thus, no abs() on the src potential
            int srcPotential = Integer.parseInt(getNodeAttributeValue(edge.getSource(), ACTIVATION_VALUE_ID));
            // traversing negative weights requires positive potential...
            logger.trace("(" + srcPotential + ")-" + edgeMagnitude + "->?");
            return srcPotential >= edgeMagnitude;
        } catch (IllegalStateException ex) {
            logger.error(edge.getSource().getId() + "->" + edge.getTarget().getId() + " has no weight? : ", ex);
            return false; //throw ex;
        }
    }

    public void tick() {
        // computes one cycle through the DGRN
        // propagate signals through network
        HashMap<String, Integer> nodeUpdates = new HashMap<String, Integer>();
        for (Edge edge : graph.getAllEdges()) {
            try {
                if (edgePropagatesSignal(edge)) {
                    logger.trace("running " + edge.getSource().getId() + "->" + edge.getTarget().getId());
                    // must read all updates before applying additions, so they are stored in temp hashMap
                    if (nodeUpdates.containsKey(edge.getTarget().getId())) {
                        nodeUpdates.put(
                                edge.getTarget().getId(),
                                nodeUpdates.get(edge.getTarget().getId()) + (int) edge.getWeight()
                        );
                    } else {
                        nodeUpdates.put(
                                edge.getTarget().getId(),
                                (int) edge.getWeight()
                        );
                    }
                    deductEdgeWeightFromSrc(edge);
                }

            } catch (KeySelectorException err) {
                logger.error(edge.getSource().getId() +
                        " or " + edge.getTarget().getId() +
                        " node has no activation value attribute. attempting to add it.", err);
                edge.getSource().getAttributeValues().addValue(attr_ActivationValue, "0");
                edge.getTarget().getAttributeValues().addValue(attr_ActivationValue, "0");
                return;
            }
        }
        // now apply updates
        for (String key : nodeUpdates.keySet()) {
            try {
                // src nodes have already been reduced, but need to add weights to tgt nodes
                int oldVal = Integer.parseInt(getNodeAttributeValue(getNode(key), ACTIVATION_VALUE_ID));
                int delta = nodeUpdates.get(key);
                logger.trace("" + key + "=" + oldVal + "+" + delta);
                String newVal = Integer.toString(oldVal + delta);
                setNodeAttributeValue(
                        getNode(key),
                        ACTIVATION_VALUE_ID,
                        newVal
                );
                handleOutputNodes.handleOutputNode(key, nodeUpdates.get(key));
            } catch (KeySelectorException err) {
                logger.error("node not found for key:" + key, err);
            }
        }
        primeInflowNodes();
    }

    public void inheritGenes(DGRN parent, int maxAlleles) {
        // inherits genes from given parent assuming parent will represent 1/maxAlleles in genetic material
        //     example: maxAlleles=2 (haploid)
        //      parent_1: gene1(alleles:2), gene2(alleles:1)
        //      patent_2: gene1(alleles:1), gene2(alleles:1), gene3(alleles:1)
        //      child   : gene1(1 or 2),    gene2(0, 1, 2)  , gene3(0 or 1)

        //     example2: maxAlleles=2 (triploid)
        //      parent_1: gene1(alleles:2), gene2(alleles:1)
        //      parent_2: gene1(alleles:1), gene2(alleles:1), gene3(alleles:1)
        //      parent_3: gene1( 0       ), gene2(   3     ), gene3(   0     ), gene4( 3 )
        //      child   : gene1(1 or 2),    gene2(1, 2, 3)  , gene3(0 or 1)   , gene4( 1 )

        //     example3: maxAlleles=1 (mitosis)
        //      child == parent_1

        // for each gene pair
        logger.trace("inheriting from parent " + parent.toString());
        for (Node node : parent.graph.getNodes()) {
            if (isInflowNode(node.getId()) || isOutflowNode(node.getId())) {
                continue;  // don't inherit inflow/outflow nodes (these are in all by default)
            } else {
                try {
                    // choose if gene gets included based on node # of alleles attribute
                    int n_alleles = Integer.parseInt(getNodeAttributeValue(node, ALLELE_COUNT_ID));
                    // n_alleles/maxAlleles = chance of inheriting this gene
                    int diceRoll = randomGenerator.nextInt(maxAlleles + 1);
                    logger.trace("@ gene node " + node.getId() + " | " + n_alleles + "/" + maxAlleles + " alleles");
                    if (diceRoll <= n_alleles) {
                        // dice roll has determined that gene is inherited.
                        logger.trace("    inherited");
                        try {
                            // if gene already exists, # of alleles += 1
                            logger.trace("    adding to existing node");
                            Node childNode = getNode(node.getId());
                            int newAlleleCount = Integer.parseInt(getNodeAttributeValue(childNode, ALLELE_COUNT_ID));
                            newAlleleCount++;
                            setNodeAttributeValue(childNode, ALLELE_COUNT_ID, Integer.toString(newAlleleCount));
                        } catch (KeySelectorException err) {
                            // else add chosen gene w/ # of alleles = 1
                            logger.trace("    creating new gene node");
                            Node newNode = graph.createNode(node.getId());
                            newNode
                                    .setLabel(node.getLabel())
                                    .getAttributeValues()
                                    .addValue(attr_ActivationValue, "0")
                                    .addValue(attr_AlleleCount, "1");
                            // copy connections
                            List<Edge> edgesList = parent.getAllEdgesOf(node);
                            logger.trace("      found " + edgesList.size() + " edges.");  // TODO: this should != 0
                            for (Edge edge : edgesList) {
                                if (edge.getSource().equals(node)) {
                                    logger.trace("copying " + node.getId() + "->" + edge.getTarget().getId());
                                    // outgoing
                                    Node otherNode = getNode(edge.getTarget().getId());
                                    newNode.connectTo(otherNode).setWeight(edge.getWeight());
                                } else if (edge.getTarget().equals(node)) {
                                    // incoming
                                    logger.trace("copying " + edge.getSource().getId() + "->" + node.getId());
                                    Node otherNode = getNode(edge.getSource().getId());
                                    otherNode.connectTo(newNode).setWeight(edge.getWeight());
                                } else {
                                    throw new IllegalStateException("node is not src or tgt of edge?!?");
                                    // TODO: or maybe otherNode is not yet in grid!
                                }
                            }
                        }
                    } else { // dice roll determines gene not to be inherited
                        logger.trace("    not inherited");
                    }
                } catch (KeySelectorException err) {
                    logger.error("failed to inherit " + node.getId() + " gene: ", err);
                }
            }
        }
    }

    private boolean isOutflowNode(String id) {
        // returns true if given id is id of an outflow node
        for (String outNode : handleOutputNodes.getListOfOutflowNodes()) {
            if (outNode.equals(id)) {
                return true;
            }
        } // else
        return false;
    }

    private boolean isInflowNode(String id) {
        // returns true if given id is id of inflow node
        for (String inNode : inflowNodeHandle.getListOfInflowNodes()) {
            if (inNode.equals(id)) {
                return true;
            }
        } // else
        return false;
    }

    public void saveGraph(Gexf gexf) {
        StaxGraphWriter graphWriter = new StaxGraphWriter();
        File f = new File("static_graph_sample.gexf");
        Writer out;
        try {
            out = new FileWriter(f, false);
            graphWriter.writeToStream(gexf, out, "UTF-8");
            logger.info("saved graph to " + f.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Node getNode(String nodeId) throws KeySelectorException {
        // returns node found using given nodeId
        // throws KeySelectorException if node not found
        // TODO: this should should really be handled within the gexf4j library.
        List<Node> nodes = graph.getNodes();
        for (Node node : nodes) {
            if (node.getId().equals(nodeId)) {
                return node;
            }
        } // else
        throw new KeySelectorException("node w/ id '" + nodeId + "' not found!");
    }
}