All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.engine.modelling.TemporalCausalGraph Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *  Copyright (C) 2007, 2015:
 *  
 *    - Ferdinando Villa 
 *    - integratedmodelling.org
 *    - any other authors listed in @author annotations
 *
 *    All rights reserved. This file is part of the k.LAB software suite,
 *    meant to enable modular, collaborative, integrated 
 *    development of interoperable data and model components. For
 *    details, see http://integratedmodelling.org.
 *    
 *    This program is free software; you can redistribute it and/or
 *    modify it under the terms of the Affero General Public License 
 *    Version 3 or any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but without any warranty; without even the implied warranty of
 *    merchantability or fitness for a particular purpose.  See the
 *    Affero General Public License for more details.
 *  
 *     You should have received a copy of the Affero General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *     The license is also available at: https://www.gnu.org/licenses/agpl.html
 *******************************************************************************/
package org.integratedmodelling.engine.modelling;

import java.io.PrintStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import org.integratedmodelling.api.time.ITemporalExtent;
import org.integratedmodelling.api.time.ITemporalSeries;
import org.integratedmodelling.api.time.ITimeInstant;
import org.integratedmodelling.api.time.ITimePeriod;
import org.integratedmodelling.common.data.Edge;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabValidationException;
import org.jgrapht.graph.DefaultDirectedGraph;

public class TemporalCausalGraph {

    /*
     * fv - this declaration must stay like this, or a javac bug will prevent compilation with Sun java. The obvious one would be
     * to use RelationshipEdge in the declaration of course.
     */
    private final DefaultDirectedGraph  graph                  = new DefaultDirectedGraph(RelationshipEdge.class);
    private final Map> agentsTemporalSeries   = new HashMap>();
    private final Map                  stateAgentReverseIndex = new HashMap();
    private final Map            stateTimePeriodIndex   = new HashMap();

    public static class InvalidationResult {
        private final HashMap> invalidStates = new HashMap>();

        private void add(RelationshipType relationshipType, INNER_STATE state) {
            invalidStates.get(relationshipType).add(state);
        }

        private void addAll(InvalidationResult other) {
            for (RelationshipType relationshipType : RelationshipType.values()) {
                invalidStates.get(relationshipType).addAll(other.invalidStates.get(relationshipType));
            }
        }

        public Collection get(RelationshipType relationshipType) {
            return invalidStates.get(relationshipType);
        }

        public Collection getInfluential() {
            return invalidStates.get(RelationshipType.influential);
        }

        public Collection getCausal() {
            return invalidStates.get(RelationshipType.causal);
        }
    }

    public enum RelationshipType {
        influential,
        causal
    }

    @SuppressWarnings("serial")
    private abstract class RelationshipEdge extends Edge {
        public abstract RelationshipType getType();
    }

    @SuppressWarnings("serial")
    private class CausalRelationship extends RelationshipEdge {
        @Override
        public RelationshipType getType() {
            return RelationshipType.causal;
        }
    }

    @SuppressWarnings("serial")
    private class InfluentialRelationship extends RelationshipEdge {
        @Override
        public RelationshipType getType() {
            return RelationshipType.influential;
        }
    }

    public void createAgent(AGENT agent, STATE initialState, ITimePeriod initialTimePeriod) {
        createAgent(agent, initialState, initialTimePeriod, null);
    }

    /**
     * Add the agent to the causal graph so that its agent-states can be part of the simulation
     *
     * TODO check that agent doesn't exist yet? Or is that a performance drag?
     *
     * @param agent
     * @param initialState
     * @param initialTimePeriod
     * @param parentNode 
     *            the agent-state which caused this agent to come into existence (i.e. by message sender,
     *            genetic parent, etc)     */
    public void createAgent(AGENT agent, STATE initialState, ITimePeriod initialTimePeriod, STATE parentNode) {
        // start an agent-state series for the agent
        ITemporalSeries temporalSeries = new TemporalSeries();
        agentsTemporalSeries.put(agent, temporalSeries);

        // add the node to all local data structures
        addStateNode(agent, initialState, initialTimePeriod, temporalSeries, parentNode);
    }

    /**
     * More causal relationships can be added later if desired.
     */
    public void addStateNode(AGENT agent, STATE state, ITimePeriod timePeriod, STATE parentState)
            throws KlabValidationException {
        ITemporalSeries temporalSeries = agentsTemporalSeries.get(agent);
        addStateNode(agent, state, timePeriod, temporalSeries, parentState);
    }

    private void addStateNode(AGENT agent, STATE state, ITimePeriod timePeriod, ITemporalSeries temporalSeries, STATE parentState) {
        // add the state node to the agent's time series. will inherently check for illegal overlaps.
        temporalSeries.put(timePeriod, state);

        // register the state in the reverse index
        stateAgentReverseIndex.put(state, agent);
        stateTimePeriodIndex.put(state, timePeriod);

        // add the state node to the graph
        graph.addVertex(state);

        if (parentState != null) {
            // the parent has a CAUSAL relationship with the child
            graph.addEdge(parentState, state, new CausalRelationship());
        }
    }

    public void addInfluentialRelationship(STATE cause, STATE effect) {
        graph.addEdge(cause, effect, new InfluentialRelationship());
    }

    /**
     * Invalidate the agent state as of the given time, by segmenting it into the pre-invalidation and
     * post-invalidation states. The pre-invalidation state will remain intact (with a reduced end time), and
     * the post-invalidation state will be removed, cascading all changes by deleting dependent states.
     *
     * Any states which are INFLUENTIALLY dependent (rather than CAUSALLY dependent) will be returned in the
     * result, so that the client can decide what to do with them. The prototypical use case in k.LAB is
     * that these agent-states will be re-evaluated given the changed agent-state(s) they depend on.
     *
     * @param state
     * @param invalidationTime
     * @return all INFLUENTIALLY dependent agent-states which were removed during the recursive step
     * @throws KlabException
     */
    @SuppressWarnings("unchecked")
    public InvalidationResult invalidate(STATE state, ITimeInstant invalidationTime)
            throws KlabException {
        InvalidationResult result = new InvalidationResult();

        for (Edge edge : graph.edgesOf(state)) {
            if (edge.source().equals(state)) {
                STATE affectedAgentState = (STATE) edge.target();
                ITimeInstant startInstant = stateTimePeriodIndex.get(affectedAgentState).getStart();
                if (startInstant.compareTo(invalidationTime) > 0) {
                    // the state which was caused or influenced by the invalid time period needs to be
                    // updated/deleted
                    result.addAll(invalidate(affectedAgentState, ((RelationshipEdge) edge).getType()));
                }
            }
        }

        // change the state end time in the agent's temporal series
        AGENT agent = stateAgentReverseIndex.get(state);
        ITemporalSeries temporalSeries = agentsTemporalSeries.get(agent);
        ITimePeriod newTimePeriod = temporalSeries.shorten(invalidationTime);

        // change the end time in the reverse index. Map.put() will replace the old time period with the new,
        // because they use the same index value.
        stateTimePeriodIndex.put(state, newTimePeriod);

        return result;
    }

    @SuppressWarnings("unchecked")
    public InvalidationResult invalidate(STATE state, RelationshipType relationshipType) {
        InvalidationResult result = new InvalidationResult();

        // cascade changes to children (causal dependencies) of this task
        for (Edge edge : graph.edgesOf(state)) {
            if (edge.source().equals(state)) {
                // this vertex is coming FROM the invalid task, so cascade the invalidation.
                result.addAll(invalidate((STATE) edge.target(), ((RelationshipEdge) edge).getType()));
            }
        }

        // find the state's location
        AGENT agent = stateAgentReverseIndex.get(state);
        ITemporalSeries temporalSeries = agentsTemporalSeries.get(agent);
        ITimePeriod timePeriod = stateTimePeriodIndex.get(state);

        if (relationshipType == RelationshipType.causal) {
            // for a causal relationship, the parent is invalid so this agent-state must be removed.
            // remove the state from all local data structures
            temporalSeries.remove(timePeriod.getEnd()); // use getEnd() because of exclusive-inclusive
                                                        // semantics
            graph.removeVertex(state);
            stateAgentReverseIndex.remove(state);
            stateTimePeriodIndex.remove(state);
        } else {
            // for an influential relationship, some dependent data changed, so this agent-state must be
            // re-computed.
            // add it to the result collection to let the client decide how to proceed.
            result.add(RelationshipType.influential, state);
        }
        return result;
    }

    public ITemporalSeries getAgentStateSeries(AGENT agent) {
        return agentsTemporalSeries.get(agent);
    }

    /**
     * Return a collection of STATE which overlaps the given time period.
     */
    public Collection getOverlapping(ITimePeriod timePeriod) {
        Collection result = new LinkedList();
        for (AGENT key : agentsTemporalSeries.keySet()) {
            ITemporalSeries timeSeries = agentsTemporalSeries.get(key);
            for (STATE overlapping : timeSeries.getOverlapping(timePeriod)) {
                result.add(overlapping);
            }
        }
        return result;
    }

    // TODO this should probably be a helper class, but then there's the public/private issue...
    public void dump(PrintStream out, ITimePeriod overallTimePeriod) {
        for (Entry> entry : agentsTemporalSeries.entrySet()) {
            ITemporalSeries temporalSeries = entry.getValue();
            String label = entry.getKey().toString();
            String agentTimeline = generateSingleTimeline(label, temporalSeries, overallTimePeriod);
            out.println(agentTimeline);
        }
    }

    // TODO be less hacky about these
    private static final int    TIME_SLOTS   = 100;
    private static final String LABEL_BUFFER = "    ";

    private String generateSingleTimeline(String label, ITemporalSeries temporalSeries, ITimePeriod overallTimePeriod) {
        long startMs = overallTimePeriod.getStart().getMillis();
        long msPerTimeSlot = (overallTimePeriod.getEnd().getMillis() - startMs) / TIME_SLOTS;
        char[] charSlots = new char[TIME_SLOTS + 1]; // +1 accommodates for the final observation
        // initialize to "--------"
        for (int i = 0; i < TIME_SLOTS; i++) {
            charSlots[i] = '-';
        }
        // set "|" characters for each observation
        long valueCount = temporalSeries.getValueCount();
        for (int i = 0; i < valueCount; i++) {
            ITemporalExtent extent = temporalSeries.getExtent(i);
            int position = getCharacterPositionForTime(extent.getStart().getMillis(), startMs, msPerTimeSlot);
            if (i == valueCount - 1) {
                // last item - agent dies
                charSlots[position] = 'X';
            } else {
                charSlots[position] = '|';
            }
        }

        // make the label a consistent width
        return new String(charSlots) + LABEL_BUFFER + label;
    }

    private int getCharacterPositionForTime(long timeMs, long startMs, long msPerTimeSlot) {
        return (int) ((timeMs - startMs) / msPerTimeSlot);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy