/* Copyright 2014 MITRE Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.mitre.provenance.simulate; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.mitre.provenance.PLUSException; import org.mitre.provenance.db.neo4j.Neo4JStorage; import org.mitre.provenance.plusobject.PLUSActor; import org.mitre.provenance.plusobject.PLUSEdge; import org.mitre.provenance.plusobject.PLUSInvocation; import org.mitre.provenance.plusobject.PLUSObject; import org.mitre.provenance.plusobject.PLUSString; import org.mitre.provenance.plusobject.PLUSWorkflow; /** * Generate a random DAG. This is the "DAGAholic" method of creating random graphs. Note this object is a * ProvenanceCollection, and so can be easily stored/serialized/reported as such. * * <p>One annoying implementation detail: we actually have to keep a second copy of nodes and edges; the superclass * tends to store nodes/edges in a map, but we need to be able to refer to them in an ordered list. The super-class doesn't * support that, so we need to implement that for this class. * * @author moxious */ public class DAGAholic extends SyntheticGraph { protected static Logger log = Logger.getLogger(DAGAholic.class.getName()); /** The nodes in the graph. */ protected List <PLUSObject> nodeList; /** The edges in the graph */ protected List <PLUSEdge> edgeList; protected Map<String,PLUSEdge>edgeHash = new HashMap<String,PLUSEdge> (); /** The workflow that links everything */ protected PLUSWorkflow wf = null; public static final int SET_SIZE = 100; public static final int SET_NODE_COUNT = 200; public DAGAholic() throws PLUSException { this(new SyntheticGraphProperties()); generate(); } /** * Create a new DAGAholic generator * @param wfName the name of the workflow to generate * @param nodes the number of nodes the final graph should contain * @param pctChance the percentage changes (between 0.0 and 1.0) that any two nodes will be connected. * @param pctData this percentage of nodes will be data; the remainder will be invocations. * @param sgf the SGF to associate with all of the objects in the graph. * @param protect the number of objects to protect in the graph (randomly chosen) * @throws Exception */ public DAGAholic(SyntheticGraphProperties props) throws PLUSException { super(props); nodeList = new ArrayList <PLUSObject>(); edgeList = new ArrayList <PLUSEdge> (); generate(); } // End DAGAholic /** * Actually generates the contents of the DAGAholic instance. * @throws PLUSException */ protected void generate() throws PLUSException { wf = new PLUSWorkflow(); wf.setName(props.getName()); wf.setWhenStart(new Date().toString()); wf.setWhenEnd(new Date().toString()); addNode(wf); // Create a number of actors according to the square root of the number of nodes. int aCount = (int)Math.sqrt(new Double(props.getComponents())); PLUSActor [] actors = new PLUSActor [aCount]; for(int x=0; x<aCount; x++) { actors[x] = new PLUSActor("Group Owner " + (x+1)); addActor(actors[x]); } // End for for(int x=0; x<props.getComponents(); x++) { PLUSObject node = null; // Create nodes as either data or invocation, depending on a random roll, // and a stated desired percentage of data/invocation. if(rand.nextDouble() <= props.getPercentageData()) { node = new PLUSString("Node " + (x+1));// + " with " + pctChance + " connectivity."); ((PLUSString)node).setContent("The quick brown fox jumps over the lazy dog"); } else { node = new PLUSInvocation("Node " + (x+1)); ((PLUSInvocation)node).setWorkflow(wf); } if(props.getSGF() != null) node.useSurrogateComputation(props.getSGF()); int r = Math.abs(rand.nextInt()); r = r % aCount; // Randomly assign the rth actor to this node. node.setOwner(actors[r]); // We need these nodes in an ordered list for later random access. nodeList.add(node); addNode(node); } // End for int curNode = 0; while(true) { int next = curNode + 1; // Don't refer to something past the end of the array. if(next >= props.getComponents()) break; PLUSEdge edge = new PLUSEdge(nodeList.get(curNode), nodeList.get(next), wf); edgeList.add(edge); addEdge(edge); edgeHash.put(edge.toString(), edge); double effectiveChance = props.getConnectivity(); // Effective chance needs to be scaled back for things earlier in the graph. float pctThrough = (float)curNode / (float)props.getComponents(); effectiveChance = (props.getConnectivity() * pctThrough); if(effectiveChance <= 0) effectiveChance=(float)0.01; int connectionsMade = 0; while(rand.nextDouble() <= effectiveChance) { next++; if(next >= props.getComponents()) break; //if(next > (curNode + (nodes * pctChance))) break; connectionsMade++; edge = new PLUSEdge(nodeList.get(curNode), nodeList.get(next), wf); edgeList.add(edge); edgeHash.put(edge.toString(), edge); addEdge(edge); } // End while // log.info("connectivity=" + props.getConnectivity() + ", effective=" + effectiveChance + " on node " + curNode + " => " + connectionsMade + " additional connections."); curNode = next; } // End while // Randomly mark protectN nodes for surrogate magic later on. HashSet<Integer> protectedIdxs = new HashSet<Integer>(); for(int x=0; x<props.getProtectN(); x++) { int randNodeIdx = 0; do { // Keep picking a random index until we're sure it wasn't one that // we already marked as protected. randNodeIdx = rand.nextInt(props.getComponents()); } while(protectedIdxs.contains(randNodeIdx)); nodeList.get(randNodeIdx).setPrivileges(props.getPrivilegeSet()); protectedIdxs.add(randNodeIdx); } // log.info("finished randomprotect"); for(int x=(props.getComponents()-1); x>0; x--) { int futilityIndex = 0; while(rand.nextDouble() <= props.getConnectivity()) { int tries = 0; boolean added = false; futilityIndex++; while(!added) { tries++; if(tries >= 10) { // log.info("Adding backwards edges isn't working. I quit."); break; } // Keep looking for a valid edge to add until it's there. int fromIdx = rand.nextInt(x); PLUSEdge edge = new PLUSEdge(nodeList.get(fromIdx), nodeList.get(x), wf); if(!edgeHash.containsKey(edge.toString())) { edgeList.add(edge); edgeHash.put(edge.toString(), edge); // System.out.println("Backwards: " + fromIdx + " to " + x); added = true; } // End if } // End while if(!added && futilityIndex > 2) { //System.out.println("Backwards futility limit reached."); break; } } // End while } // End for } // End generate public static void main(String [] args) throws Exception { DAGAholic d = new DAGAholic(); System.out.println(d); Neo4JStorage.store(d); } } // End DAGAholic