
org.integratedmodelling.engine.modelling.TemporalCausalGraph Maven / Gradle / Ivy
/*******************************************************************************
* 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 Thinklab 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 - 2025 Weber Informatics LLC | Privacy Policy