org.integratedmodelling.engine.modelling.resolver.Dataflow 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.resolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.integratedmodelling.api.knowledge.IObservation;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveEvent;
import org.integratedmodelling.api.modelling.IActiveProcess;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.ICoverage;
import org.integratedmodelling.api.modelling.IDerivedObserver;
import org.integratedmodelling.api.modelling.IDirectObservation;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.IModelBean;
import org.integratedmodelling.api.modelling.IObjectSource;
import org.integratedmodelling.api.modelling.IObservableSemantics;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.contextualization.IContextualizer;
import org.integratedmodelling.api.modelling.contextualization.IDirectInstantiator;
import org.integratedmodelling.api.modelling.contextualization.IEventInstantiator;
import org.integratedmodelling.api.modelling.contextualization.IProcessContextualizer;
import org.integratedmodelling.api.modelling.contextualization.IRelationshipInstantiator;
import org.integratedmodelling.api.modelling.contextualization.IStateContextualizer;
import org.integratedmodelling.api.modelling.contextualization.ISubjectContextualizer;
import org.integratedmodelling.api.modelling.contextualization.ISubjectInstantiator;
import org.integratedmodelling.api.modelling.resolution.IDataflow;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.modelling.runtime.IActiveObserver;
import org.integratedmodelling.api.modelling.scheduling.ITransition;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.monitoring.Messages;
import org.integratedmodelling.api.runtime.ITask.Status;
import org.integratedmodelling.base.HashableObject;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.beans.generic.Graph;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.data.Edge;
import org.integratedmodelling.common.interfaces.NetworkSerializable;
import org.integratedmodelling.common.interfaces.actuators.IActuator;
import org.integratedmodelling.common.interfaces.actuators.IDirectActuator;
import org.integratedmodelling.common.interfaces.actuators.IEventActuator;
import org.integratedmodelling.common.interfaces.actuators.IInstantiator;
import org.integratedmodelling.common.interfaces.actuators.IProcessActuator;
import org.integratedmodelling.common.interfaces.actuators.IStateActuator;
import org.integratedmodelling.common.interfaces.actuators.ISubjectActuator;
import org.integratedmodelling.common.kim.KIMObserver;
import org.integratedmodelling.common.model.actuators.DirectActuator;
import org.integratedmodelling.common.model.actuators.DirectInstantiator;
import org.integratedmodelling.common.model.actuators.EventInstantiatorActuator;
import org.integratedmodelling.common.model.actuators.ProcessActuator;
import org.integratedmodelling.common.model.actuators.RelationshipInstantiationActuator;
import org.integratedmodelling.common.model.actuators.StateActuator;
import org.integratedmodelling.common.model.actuators.SubjectContextualizerActuator;
import org.integratedmodelling.common.model.actuators.SubjectInstantiationActuator;
import org.integratedmodelling.common.model.runtime.AbstractStateContextualizer;
import org.integratedmodelling.common.states.States;
import org.integratedmodelling.common.utils.MapUtils;
import org.integratedmodelling.common.utils.NameGenerator;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.engine.modelling.resolver.Dataflow.DataPath;
import org.integratedmodelling.engine.modelling.resolver.Dataflow.ProcessingStep;
import org.integratedmodelling.engine.modelling.resolver.ResolutionGraph.DependencyEdge;
import org.integratedmodelling.engine.modelling.resolver.ResolutionGraph.DependencyEdge.Type;
import org.integratedmodelling.engine.modelling.resolver.ResolutionGraph.ProvenanceNode;
import org.integratedmodelling.engine.modelling.runtime.DirectObservation;
import org.integratedmodelling.engine.modelling.runtime.Process;
import org.integratedmodelling.engine.modelling.runtime.Subject;
import org.integratedmodelling.exceptions.KlabException;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
/**
* An acyclic dataflow built from the resolution graph of an observation.
*
* Iterates the processing steps in order of dependency.
*
* @author Ferd
*/
public class Dataflow extends DefaultDirectedGraph
implements Iterable, IDataflow, NetworkSerializable {
private static final long serialVersionUID = 2348908259284218130L;
IActiveDirectObservation subject;
ResolutionGraph resolutionGraph;
IScale scale;
IMonitor monitor;
ArrayList entryPoints = new ArrayList<>();
IResolutionScope scope;
IModel rootModel;
final ICoverage coverage;
HashMap nodeCatalog = new HashMap<>();
public Dataflow(IActiveDirectObservation subject, IResolutionScope scope,
IMonitor monitor)
throws KlabException {
super(DataPath.class);
this.resolutionGraph = (ResolutionGraph) scope.getResolutionGraph();
this.subject = subject;
this.scale = subject.getScale();
this.monitor = monitor;
this.coverage = scope.getCoverage();
// this.context = context;
this.scope = scope;
this.rootModel = scope.getModel();
((DirectObservation) subject).setDataflow(this);
compile();
}
public IActiveDirectObservation getSubject() {
return this.subject;
}
/**
* Return the number of direct observations that depend on the main subject.
*
* @return
*/
@Override
public int getDepth() {
int n = 0;
for (ProcessingStep ps : entryPoints) {
int cn = depth(ps, 0);
if (cn > n) {
n = cn;
}
}
return n;
}
private int depth(ProcessingStep step, int soFar) {
int n = 0;
for (DataPath dp : incomingEdgesOf(step)) {
int cn = depth(dp.getSourceStep(), step.isDirect ? soFar + 1 : soFar);
if (cn > n) {
n = cn;
}
}
return n + soFar;
}
/**
* Return how many observations need to be contextualized before the passed one, or -1
* if the passed observation is not in the dataflow.
*
* @param obs
* @return
*/
@Override
public int getDepth(IDirectObservation obs) {
int n = -1;
for (ProcessingStep ps : entryPoints) {
int cn = findDepth(ps, obs, 0);
if (cn > n) {
n = cn;
}
}
return n;
}
private int findDepth(ProcessingStep step, IDirectObservation obs, int soFar) {
int n = -1;
if (step.isDirect
&& ((DirectActuator) step.actuator).getObservation().equals(obs)) {
return soFar;
}
for (DataPath dp : incomingEdgesOf(step)) {
int cn = findDepth(dp.getSourceStep(), obs, soFar + 1);
if (cn > n) {
n = cn;
}
}
return n;
}
/**
* Return the priority order of evaluation for the passed observation, equal to the
* overall depth minus the depth of the observation. If the observation is not in the
* dataflow, return depth + 1, i.e. after everything.
*
* @param observation
* @return
*/
public int getPriority(IDirectObservation observation) {
return getDepth() - getDepth(observation);
}
private void compile() throws KlabException {
if (this.rootModel == null || this.resolutionGraph.isEmpty()) {
/*
* dataflow for a subject without explanatory model, fine as is.
*/
return;
}
List mediators = new ArrayList<>();
ProcessingStep root = compileNode(this.resolutionGraph
.get(this.rootModel), new ProcessingStep(this.rootModel), DependencyEdge.Type.NONE, mediators);
/*
* add any leftover mediators that didn't make it into a data path.
*/
if (root != null) {
for (IStateContextualizer cs : mediators) {
root.contextualizers.add(cs);
}
}
/*
* FIXME this should be simply checking for oe.isEmpty(), but somehow stray nodes
* are left in the graph.
*/
for (ProcessingStep s : this) {
Set oe = outgoingEdgesOf(s);
Set ie = incomingEdgesOf(s);
if (oe.size() == 0 && ie.size() > 0) {
this.entryPoints.add(s);
}
}
if (this.entryPoints.isEmpty() && root != null) {
this.entryPoints.add(root);
}
}
/**
* Compile a node recursively. This one costed me 3 days. Call with a ProcessingStep
* made with the topmost model.
*
* @param node The node to start at. Starts with a model node.
* @param step the node containing the model in the current scope. We only pass it
* around collecting the observer and all its contextualizer, switching to a child one
* when an incompatible model is encountered.
* @param edgeType the type of link between the current node and its parent.
* @return the topmost node.
* @throws KlabException
*/
private ProcessingStep compileNode(ProvenanceNode node, ProcessingStep step, DependencyEdge.Type edgeType, List mediators)
throws KlabException {
IStateContextualizer contextualizer = null;
IStateContextualizer processor = null;
/*
* Graph nodes only contain actuators.
*/
if (node.observer != null) {
if (edgeType == DependencyEdge.Type.DEPENDENCY
|| edgeType == DependencyEdge.Type.CONDITIONAL_DEPENDENCY
|| edgeType == DependencyEdge.Type.DEFINE_STATE) {
step.setObserver(step.model, node.observer);
}
/*
* mediators end up in the edges, contextualizers and processors in the nodes.
*/
if (edgeType == DependencyEdge.Type.MEDIATE_TO) {
IStateContextualizer mediator = node.observer
.getMediator(node.observer, monitor);
if (mediator != null) {
mediators.add(mediator);
}
} else {
contextualizer = node.observer
.getContextualizer(this.scope, this.monitor);
if (node.observer instanceof IDerivedObserver) {
/*
* nodes that may be producing their data in alternative ways can use
* this too.
*/
processor = node.observer.getDataProcessor(monitor);
}
}
} else if (node.datasource != null) {
contextualizer = node.datasource
.getContextualizer(this.scale, step.observer, monitor);
processor = step.observer.getDataProcessor(monitor);
}
/*
* process all incoming nodes, creating a new one when we encounter a new model;
* if we have matched actuators from two different models, draw a data path
*/
for (ResolutionGraph.DependencyEdge edge : this.resolutionGraph
.incomingEdgesOf(node)) {
ProvenanceNode childNode = edge.getSourceNode();
ProcessingStep childStep = childNode.model == null ? step
: new ProcessingStep(childNode.model);
ProcessingStep child = node.observer == null ? null
: nodeCatalog.get(node.observer.getObservable());
if (child == null) {
// renew mediator buffer
if (edge.type == Type.DEPENDENCY) {
mediators = new ArrayList<>();
}
// compile
child = compileNode(edge
.getSourceNode(), childStep, edge.type, mediators);
}
if (child.isDirect) {
DataPath link = new DataPath(edge, step.observer == null ? null
: step.observer.getObservable());
/*
* resolved through a direct observation: if we have an observer (we're
* looking for data) link to the observation using a key and, if needed,
* mediators in the edge. Otherwise just use a processing link. Initialize
* mediator now if created.
*/
if (step.observer != null) {
IStateContextualizer mediator = null;
String key = null;
for (String k : child.outputs.keySet()) {
if (child.outputs.get(k).equals(step.observer.getObservable())) {
key = k;
mediator = step.observer.getMediator(child.outputs.get(k)
.getObserver(), monitor);
if (mediator != null) {
/*
* FIXME initialize later when added to link and avoid
* ugly hacks.
*/
Map input = new HashMap<>();
Map output = new HashMap<>();
input.put(child.outputs.get(k).getObserver()
.getId(), child.outputs.get(k)
.getObserver().getObservable());
output.put(step.observer.getId(), step.observer
.getObservable());
mediator.define(step.observer
.getId(), step.observer, this.subject, this.scope, input, output, false, monitor);
}
break;
}
}
/*
* Make an edge with the key, the accessor for the state and the
* mediator if any
*/
link.stateKey = key;
if (mediator != null) {
link.mediators.add(mediator);
}
step.receiverKey = key;
step.contextualizers.add(new StateAccessor(step, key, monitor));
}
/*
* remember this for other links
*/
recordResolvedObservables(child);
addEdge(child, step, link);
} else if (step.observer != null && child.observer != null
&& !step.observer.equals(child.observer)) {
/*
* resolved through data
*/
if (edge.type == DependencyEdge.Type.RESOLVES) {
/*
* this is the model that resolves the current model's observables, so
* we can just use its observer and contextualizer to observe them,
* with the possible addition of a mediator.
*/
step.contextualizers.addAll(child.contextualizers);
/*
* see if an implicit mediation is required (one that was not defined
* in k.IM code). If so, mediator needs to be initialized.
*/
IStateContextualizer mediator = step.observer
.getMediator(child.observer, monitor);
if (mediator != null) {
Map input = new HashMap<>();
Map output = new HashMap<>();
input.put(child.observer.getId(), child.observer.getObservable());
output.put(step.observer.getId(), step.observer.getObservable());
/*
* FIXME initialize later when added to link and avoid ugly hacks.
*/
mediator.define(step.observer
.getId(), step.observer, this.subject, this.scope, input, output, false, monitor);
mediators.add(mediator);
}
/*
* tell the observer about the resolution, so it can inherit anything
* it feels like.
*/
step.observer = ((KIMObserver) step.observer)
.notifyResolution(child.observer);
/*
* graft the child's dependencies to this result; if it's a mediation,
* use the unmediated observer in the node.
*/
swapNode(child, step, edgeType == DependencyEdge.Type.MEDIATE_TO);
} else {
/*
* dependency: add edge with all the mediators gathered so far
*/
DataPath link = new DataPath(edge, child.observer.getObservable());
for (IStateContextualizer mediator : mediators) {
/*
* FIXME initialize here and avoid ugly hacks.
*/
((AbstractStateContextualizer) mediator)
.setStateName(link.getNameAtTarget());
link.mediators.add(mediator);
}
addEdge(child, step, link);
recordResolvedObservables(child);
}
/*
* TODO merge the next condition with the identical actions above when I
* can breathe.
*/
} else if (edge.type == DependencyEdge.Type.DEPENDENCY
|| edge.type == DependencyEdge.Type.CONDITIONAL_DEPENDENCY) {
/*
* dependency: add edge with all the mediators gathered so far
*/
DataPath link = new DataPath(edge, child.observer.getObservable());
for (IStateContextualizer mediator : mediators) {
/*
* FIXME initialize here and avoid ugly hacks.
*/
((AbstractStateContextualizer) mediator)
.setStateName(link.getNameAtTarget());
link.mediators.add(mediator);
}
addEdge(child, step, link);
recordResolvedObservables(child);
}
}
/*
* if we collected a contextualizer before, add it to the end so that the order of
* execution is first to last.
*/
if (contextualizer != null) {
step.contextualizers.add(contextualizer);
}
/*
* observers may need to re-process their data before anything is done.
*/
if (processor != null) {
step.contextualizers.add(processor);
}
return step;
}
private void recordResolvedObservables(ProcessingStep step) {
if (step.observer != null
&& !nodeCatalog.containsKey(step.observer.getObservable())) {
nodeCatalog.put(step.observer.getObservable(), step);
}
if (step.isDirect) {
for (String key : step.outputs.keySet()) {
if (!nodeCatalog.containsKey(step.outputs.get(key))) {
nodeCatalog.put(step.outputs.get(key), step);
}
}
}
}
/**
* Remove the first node passed and graft all its edges to the second.
*
* @param reject
* @param result
*/
private void swapNode(ProcessingStep reject, ProcessingStep result, boolean isMediation) {
for (DataPath incoming : incomingEdgesOf(reject)) {
DataPath newPath = new DataPath(incoming);
ProcessingStep source = incoming.getSourceStep();
addEdge(source, result, newPath);
}
for (DataPath outgoing : outgoingEdgesOf(reject)) {
DataPath newPath = new DataPath(outgoing);
ProcessingStep target = outgoing.getTargetStep();
addEdge(result, target, newPath);
}
if (result.actuator != null && reject.actuator != null) {
((StateActuator) result.actuator).copyActions(reject.actuator);
}
/*
* use the source observables - the result node had the result of the mediation
* but the actual node will contain the original data. We float the original
* observer to the final node and give it the name of the final output, so we can
* create the state properly.
*/
if (isMediation) {
result.observables = reject.observables;
IActiveObserver stateObserver = reject.originalObserver == null
? reject.observer
: reject.originalObserver;
result.originalObserver = stateObserver;
}
removeVertex(reject);
}
public class DataPath extends Edge implements IDataflow.Datapath {
private static final long serialVersionUID = 2366743581134478147L;
IObservableSemantics targetObservable = null;
IObservableSemantics sourceObservable = null;
boolean isConditional;
int conditionIndex = -1;
String dependencyName;
/*
* a state accessor that links to a state produced by a direct observation will
* need the key for it and any mediators needed to adapt it. In this case the edge
* links a state actuator to a processing step containing a direct actuator.
*/
List mediators = new ArrayList<>();
String stateKey = null;
DataPath(DataPath path) {
this.isConditional = path.isConditional;
this.targetObservable = path.targetObservable;
this.sourceObservable = path.sourceObservable;
this.conditionIndex = path.conditionIndex;
this.dependencyName = path.dependencyName;
this.mediators.addAll(path.mediators);
this.stateKey = path.stateKey;
}
String getNameAtSource() {
return getSourceStep().originalObserver == null
? getSourceStep().observer.getId()
: getSourceStep().originalObserver.getId();
}
String getNameAtTarget() {
return dependencyName;
}
/*
* each data path is for ONE observable and between two accessors. The same
* accessor may appear as the target of more than one path. Each accessor has a
* name which it is known to itself with, and is the ID of the model it comes from
* or it represents (in the case of datasource accessors). Each path has a formal
* name that the other accessor is known to it with. Paths act as name translators
* when connections are made. When an accessor reinterprets a datasource (which
* only happens with actions or mediations) the path has the same formal name as
* the target accessor's. Otherwise the dependency name is used as the formal
* name.
*/
DataPath(DependencyEdge edge, IObservableSemantics observable) {
// the observable being transmitted. Null in dependencies that
// terminate into a direct observer,
// for which we use the passed one.
this.targetObservable = edge.observable == null ? observable
: edge.observable;
// the observable being received; null if this links in a direct
// actuator
this.sourceObservable = observable;
// name of passed data within target model
this.dependencyName = this.targetObservable == null ? edge.formalName
: this.targetObservable.getFormalName();
// condition index (we don't really need the flag but the code after
// is
// cleaner)
if ((isConditional = (edge.conditionIndex >= 0))) {
conditionIndex = edge.conditionIndex;
}
}
@Override
public String toString() {
return getSource() + " -- "
+ (isConditional ? ("c/" + conditionIndex) + "/" : "")
+ dependencyName
+ " --> " + getTarget();
}
public IActuator getSourceAccessor() {
return ((ProcessingStep) (this.getSource())).actuator;
}
public IActuator getTargetAccessor() {
return ((ProcessingStep) (this.getTarget())).actuator;
}
@Override
public ProcessingStep getSourceStep() {
return (ProcessingStep) (this.getSource());
}
@Override
public ProcessingStep getTargetStep() {
return (ProcessingStep) (this.getTarget());
}
@Override
public boolean equals(Object edge) {
return edge instanceof DataPath
&& this.getSource().equals(((DataPath) edge).getSource())
&& this.getTarget().equals(((DataPath) edge).getTarget());
}
public String getLabel() {
String ret = null;
String mediation = "";
try {
boolean first = true;
for (int i = 0; i < mediators.size(); i++) {
String label = ((AbstractStateContextualizer) mediators.get(i))
.getLabel();
if (label == null) {
continue;
}
mediation += (first ? "" : "->") + label;
first = false;
}
} catch (Throwable t) {
}
if (targetObservable != null && sourceObservable != null) {
if (targetObservable.getType().equals(sourceObservable.getType())) {
ret = targetObservable.getType().toString()
+ (isConditional ? (" #" + conditionIndex) : "")
+ (mediation.isEmpty() ? "" : ("\n" + mediation));
} else {
ret = sourceObservable.getType().toString()
+ (mediation.isEmpty() ? "\n interpret as\n"
: ("\n " + mediation + "\n"))
+ targetObservable.getType().toString()
+ (isConditional ? (" #" + conditionIndex) : "");
}
} else {
ret = "" + (isConditional ? (" #" + conditionIndex) : "")
+ (mediation.isEmpty() ? "" : ("\n" + mediation));
}
return ret;
}
}
/*
* compilation element - the workflow is made of these. It holds the accessor (the
* processing step) and the observer that provides its observation semantics.
* Observable is only set in it when we want to create a state.
*/
public class ProcessingStep extends HashableObject
implements IDataflow.ProcessingStep {
/*
* we collect contextualizers as we move down the resolution graph, until we find
* a node with an observer; then all contextualizers are chained to its actuator,
* which will also apply actions and validate the result.
*/
List contextualizers = new ArrayList<>();
List observables = new ArrayList<>();
IActiveObserver observer;
// this holds the observer at the source point of any mediation so that
// we can create the states appropriately.
IActiveObserver originalObserver = null;
IActuator actuator;
boolean isDirect = false;
IModel model;
// used during computation
Map parameters = new HashMap<>();
Map states = new HashMap<>();
boolean initialized = false;
boolean computed = false;
// if this isn't null, it's the key to get to our state from a
// precomputed direct observation - state will be in states, which
// otherwise is indexed by formal name, and access happens through
// a preinstalled StateAccessor contextualizer.
String receiverKey = null;
/*
* this contains observers for states produced by a direct observer step, which
* are fully computed by the time they're used in a dataflow with states that
* depend on direct observations. In that case the dataflow edge will contain the
* contextualizers that adapt the state to the needs of the receiving observer. If
* that's the case, the states in {@link #states} will have the same keys.
*/
Map outputs = new HashMap<>();
void setObserver(IModel model, IActiveObserver observer) {
this.observer = observer;
this.actuator = new StateActuator(subject, model, observer, monitor);
}
ProcessingStep(IModel model) {
this.model = model;
this.observables.addAll(model.getObservables());
if (NS.isDirect(model.getObservable())) {
actuator = getDirectActuator(model, scope, monitor);
this.isDirect = true;
/*
* Produce the necessary key/catalog so that upstream states can use this
* model to resolve their dependencies.
*/
for (int i = 1; i < model.getObservables().size(); i++) {
String key = NameGenerator.shortUUID();
if (scope.isUsed(model.getObservables().get(i))) {
outputs.put(key, model.getObservables().get(i));
}
}
}
addVertex(this);
}
@Override
public String toString() {
String ret = "";
if (actuator != null && !(actuator instanceof IStateActuator)) {
ret += "[a " + actuator.toString() + "]";
}
if (observer != null) {
ret += (ret.isEmpty() ? "" : " ") + observer;
}
ret += " <" + contextualizers.size() + ">";
return ret;
}
public String getLabel() {
try {
String ret = observables.get(0).getType().getLocalName();
boolean first = true;
for (int i = 0; i < contextualizers.size(); i++) {
String label = ((AbstractStateContextualizer) contextualizers.get(i))
.getLabel();
if (label == null) {
continue;
}
ret += (first ? "\n" : "->") + label;
first = false;
}
return ret;
} catch (Throwable t) {
}
return "ERROR";
}
}
/*
* Not necessary any more given that we now compute recursively, but good to use (only
* in looking up the root nodes) so we get warned if the dataflow is not acyclic.
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator iterator() {
CycleDetector cycleDetector = new CycleDetector<>(this);
if (cycleDetector.detectCycles()) {
Iterator iterator;
Set cycleVertices;
Set subCycle;
ProcessingStep cycle;
// TODO leave but report an internal error and
// check what is happening. The point here is that we should
// distinguish cycles that are semantic contradiction from those
// that can be seen as representational artifacts once temporal
// dynamics is
// factored in.
monitor.warn("dataflow has circular dependencies: model may loop indefinitely");
// Get all vertices involved in cycles.
cycleVertices = cycleDetector.findCycles();
// Loop through vertices trying to find disjoint cycles.
while (!cycleVertices.isEmpty()) {
// Get a vertex involved in a cycle.
iterator = cycleVertices.iterator();
cycle = iterator.next();
// Get all vertices involved with this vertex.
subCycle = cycleDetector.findCyclesContainingVertex(cycle);
for (ProcessingStep sub : subCycle) {
// Remove vertex so that this cycle is not encountered
// again.
// cycleVertices.remove(sub);
}
}
}
return new TopologicalOrderIterator<>(this);
}
public boolean run(int contextIndex, ITransition transition) throws KlabException {
if (this.vertexSet().isEmpty())
return true;
HashSet computed = new HashSet<>();
for (ProcessingStep entry : this.entryPoints) {
compute(entry, contextIndex, transition, computed);
}
return true;
}
/*
* compute one step at one context state in an observer entry.
*/
private Map compute(ProcessingStep entry, int contextIndex, ITransition transition, HashSet computed)
throws KlabException {
entry.parameters.clear();
if (computed.contains(entry)) {
return entry.parameters;
}
computed.add(entry);
if (entry.actuator instanceof IStateActuator) {
Object rawValue = null;
if (!scale.isCovered(contextIndex)) {
entry.parameters.put(entry.actuator.getName(), rawValue);
return entry.parameters;
}
List conditionals = new ArrayList<>();
for (DataPath e : incomingEdgesOf(entry)) {
if (e.isConditional) {
conditionals.add(e);
} else if (!e.getSourceStep().isDirect) {
String nameAtSource = e.getNameAtSource();
String nameAtTarget = e.getNameAtTarget();
Map emap = compute(e
.getSourceStep(), contextIndex, transition, computed);
/*
* ensure the source observation is known with the name that's
* expected at the target and in the mediators
*/
rawValue = emap.get(nameAtSource);
emap.put(nameAtTarget, rawValue);
/*
* We get here with the raw values from the data sources, now we must
* mediate before we compute the data we use.
*/
for (IStateContextualizer mediator : e.mediators) {
Map ret = transition == ITransition.INITIALIZATION
? mediator.initialize(contextIndex, emap)
: mediator.compute(contextIndex, transition, emap);
if (ret != null) {
emap.putAll(ret);
}
}
entry.parameters.put(nameAtTarget, emap.get(nameAtTarget));
}
}
if (!conditionals.isEmpty()) {
Collections.sort(conditionals, new Comparator() {
@Override
public int compare(DataPath o1, DataPath o2) {
return Integer.compare(o1.conditionIndex, o2.conditionIndex);
}
});
for (DataPath e : conditionals) {
/*
* if condition, compute condition with current inputs; if true run
* source and break else, compute with current inputs; if not null or
* NaN, break
*/
}
}
/*
* compute TODO see if we really need to copy the data.
*/
entry.parameters
.putAll(((IStateActuator) entry.actuator)
.process(contextIndex, entry.parameters, transition));
/*
* store data before mediation.
*/
if (entry.receiverKey == null) {
for (String key : entry.states.keySet()) {
/*
* FIXME patch for one-node dataflows that have the observers name and
* not the model's when mediating. Should just be
* entry.parameters.get(key) but the key is not what's in there in
* that situation. FIXME also, states[key] may be there but null,
* while states[name] contains the state. states[key] should be
* enough.
*/
Object value = entry.parameters.get(key) != null
? entry.parameters.get(key)
: entry.parameters.get(entry.actuator.getName());
IState state = entry.states.get(key) != null ? entry.states.get(key)
: entry.states.get(entry.actuator.getName());
States.set(state, value, contextIndex);
}
}
}
if (entry.isDirect && entry.actuator != null) {
/*
* TODO actions etc
*/
}
return entry.parameters;
}
private void initialize(ProcessingStep entry) throws KlabException {
if (entry.actuator instanceof IStateActuator) {
if (entry.initialized) {
return;
}
entry.initialized = true;
IStateActuator stateActuator = (IStateActuator) entry.actuator;
/*
* initialize the actuator
*/
stateActuator.setContextualizers(entry.contextualizers);
Map inputs = new HashMap<>();
for (DataPath path : incomingEdgesOf(entry)) {
if (!path.getSourceStep().isDirect) {
inputs.put(path.dependencyName, path.getSourceStep().observer
.getObservable());
}
}
Map outputs = new HashMap<>();
for (IObservableSemantics observable : entry.observables) {
outputs.put(observable.getFormalName(), observable);
}
IActiveObserver observer = entry.originalObserver != null
? entry.originalObserver
: entry.observer;
stateActuator.define(observer
.getId(), observer, this.subject, this.scope, inputs, outputs, this.monitor);
/*
* create states TODO should be done by the actuator at define()?
*/
int i = 0;
for (IObservableSemantics o : entry.observables) {
/*
* FIXME hack alert - this should just properly attribute names and
* observers, there's more to the problem than this.
*/
if (this.vertexSet().size() == 1 && i == 0) {
o = entry.model.getObservable();
} else /* default strategy */ if (entry.originalObserver != null) {
o = entry.originalObserver.getObservable();
}
i++;
/*
* mediation may have altered the observer;, we will store the original
* data and mediations can be followed in the (future) dataflow debugger.
*/
/*
* TODO this needs to account for situations where no edges exist, in
* which the original observer should be ignored and we must make sure
* we're using the correct name at target to extract the info.
*/
/*
* If the observer is at the receiving end of a direct actuator, the state
* is provided by the initialized observation; otherwise we create it.
*/
if (entry.receiverKey == null) {
entry.states.put(o.getFormalName(), ((DirectObservation) this.subject)
.getStateFor(o, stateActuator));
}
}
}
}
/**
* Run the workflow for the passed transition, already determined by the scheduler to
* be relevant for it.
*
* If anything bad happens, use logging in the session/subject to communicate it and
* return false instead of throwing exceptions.
*
* @throws KlabException
*/
@Override
public boolean run(ITransition transition) throws KlabException {
if (this.vertexSet().isEmpty()) {
return true;
}
if (transition == null) {
IScale scale = this.scale.getSubscale(KLAB.c(NS.TIME_DOMAIN), 0);
if (scale.getMultiplicity() > 1) {
this.monitor.info("initializing " + scale.getMultiplicity()
+ " states", Messages.INFOCLASS_MODEL);
}
initialize(scale);
for (int i : this.scale.getIndex(transition)) {
if (this.monitor.getTask().getStatus() == Status.INTERRUPTED) {
this.monitor.warn("initialization interrupted by user");
return false;
}
run(i, transition);
}
} else {
for (int i : this.scale.getIndex(transition)) {
if (this.monitor.getTask().getStatus() == Status.INTERRUPTED) {
this.monitor.warn("initialization interrupted by user");
return false;
}
run(i, transition);
}
}
return true;
}
private boolean initialize(IScale scale) throws KlabException {
if (this.vertexSet().isEmpty()) {
return true;
}
HashSet computed = new HashSet<>();
for (ProcessingStep entry : this.entryPoints) {
initialize(entry, scale);
}
return true;
}
private boolean initialize(ProcessingStep entry, IScale scale) throws KlabException {
initialize(entry);
for (DataPath e : incomingEdgesOf(entry)) {
initialize(e.getSourceStep(), scale);
}
if (entry.isDirect && !entry.initialized) {
Map expectedInputs = new HashMap<>();
Map expectedOutputs = new HashMap<>();
for (DataPath e : incomingEdgesOf(entry)) {
if (!e.getSourceStep().isDirect) {
/*
* compute each input in its entirety unless it comes from a direct
* accessor itself. FIXME the source step gets the wrong name in the
* accessor (at least if it comes from direct).
*/
if (!e.getSourceStep().computed
&& e.getSourceStep().receiverKey == null) {
for (int i : this.scale.getIndex(ITransition.INITIALIZATION)) {
if (this.monitor.getTask()
.getStatus() == Status.INTERRUPTED) {
this.monitor.warn("initialization interrupted by user");
return false;
}
compute(e
.getSourceStep(), i, ITransition.INITIALIZATION, new HashSet<>());
}
e.getSourceStep().computed = true;
}
/*
* FIXME or maybe don't - not sure anymore of why this is necessary.
*/
IState inputState = e.getSourceStep().states
.containsKey(e.getSourceStep().receiverKey)
? e.getSourceStep().states
.get(e.getSourceStep().receiverKey)
: e.getSourceStep().states.get(e.getNameAtSource());
e.getSourceStep().states.get(e.getSourceStep().receiverKey);
/*
* FIXME don't know why this can be null sometimes.
*/
if (inputState != null) {
expectedInputs.put(e.getNameAtTarget(), inputState.getObservable()
.getSemantics());
}
}
}
/*
* determine output observables
*/
for (DataPath e : outgoingEdgesOf(entry)) {
if (!e.getTargetStep().isDirect)
expectedOutputs.put(e.getNameAtTarget(), e.getTargetStep().observer
.getObservable());
}
/*
* initialize direct actuator
*/
if (entry.actuator != null) {
Map outputs = initializeDirectActuator(entry, expectedInputs, expectedOutputs);
/*
* map output states to their key during contextualization
*/
Map forChild = new HashMap<>();
for (String key : entry.outputs.keySet()) {
IObservableSemantics observable = entry.outputs.get(key);
for (IObservation out : outputs.values()) {
if (out instanceof IState && out.getObservable().getSemantics()
.equals(observable)) {
forChild.put(key, (IState) out);
}
}
}
/*
* transfer output states into the states map at the target so that the
* target's StateAccessor below can find them.
*/
for (DataPath e : outgoingEdgesOf(entry)) {
if (e.stateKey != null) {
e.getTargetStep().states
.put(e.stateKey, forChild.get(e.stateKey));
}
}
}
entry.initialized = true;
}
return true;
}
private Map initializeDirectActuator(ProcessingStep step, Map expectedInputs, Map expectedOutputs)
throws KlabException {
IDirectActuator actuator = (IDirectActuator) step.actuator;
Map ret = null;
actuator.notifyModel(step.model);
for (String okey : expectedOutputs.keySet()) {
IObservableSemantics observable = expectedOutputs.get(okey);
actuator.notifyExpectedOutput(observable, observable.getObserver(), okey);
}
for (String ikey : expectedInputs.keySet()) {
actuator.notifyExpectedInput(ikey, expectedInputs.get(ikey));
}
/*
* if the model is a process model, create the process and insert it in the
* observation schedule.
*/
if (actuator instanceof IProcessActuator) {
IActiveProcess process = ((IProcessActuator) actuator).getProcess();
ret = ((IProcessActuator) actuator)
.initialize(process, this.subject, scope, monitor);
((Subject) this.subject).addProcess(process);
} else if (actuator instanceof IEventActuator) {
IActiveEvent event = ((IEventActuator) actuator).getEvent();
ret = ((IEventActuator) actuator)
.initialize(event, this.subject, scope, monitor);
((Subject) this.subject).addEvent(event);
} else if (actuator instanceof ISubjectActuator) {
ret = ((ISubjectActuator) actuator)
.initialize((IActiveSubject) this.subject, (IActiveDirectObservation) ((ResolutionScope) scope).contextSubject, scope, monitor);
}
/*
* if this was a subject model, the accessor may have created subjects, which we
* need to resolve in the main subject's context. TODO this must resolve and
* initialize both subjects and relationships. CHECK is the subject being inserted
* in the observation graph?
*/
if (NS.isThing(step.model.getObservable())) {
for (IObservation subj : ret.values()) {
if (subj instanceof IDirectObservation
&& !((DirectObservation) subj).isInitialized()) {
if (actuator instanceof IInstantiator) {
((DirectInstantiator) actuator)
.performPreResolutionActions((IActiveDirectObservation) subj);
}
if (((DirectObservation) subj)
.initialize(scope, /* FIXME! cause for provenance */ null, monitor)
.isEmpty()) {
monitor.warn("cannot resolve dependent subject "
+ ((IDirectObservation) subj).getName());
continue;
}
if (actuator instanceof IInstantiator) {
((DirectInstantiator) actuator)
.performPostResolutionActions((IActiveDirectObservation) subj);
}
}
}
}
return ret;
}
@SuppressWarnings("unchecked")
@Override
public T serialize(Class desiredClass) {
Graph ret = Graph.adapt(this, new Graph.Identifier() {
@Override
public IMetadata getMetadata(Object object) {
return null; // for now
}
@Override
public String getLabel(Object object) {
String label = null;
if (object instanceof ProcessingStep) {
label = ((ProcessingStep) object).getLabel();
} else if (object instanceof DataPath) {
label = ((DataPath) object).getLabel();
}
return label;
}
@Override
public String getType(Object object) {
return ((object instanceof ProcessingStep
&& ((ProcessingStep) object).isDirect)) ? "process"
: "processing-step";
}
@Override
public Pair getTopNode() {
return new Pair<>("Start", "start");
}
});
ret.setType(Messages.GRAPH_DATAFLOW);
return (T) ret;
}
class StateAccessor extends AbstractStateContextualizer {
String key;
ProcessingStep step;
public StateAccessor(ProcessingStep step, String key, IMonitor monitor) {
super(monitor);
this.step = step;
this.key = key;
}
@Override
public Map initialize(int index, Map inputs)
throws KlabException {
if (step.states.get(key) != null) {
/*
* FIXME this should NOT be necessary - the state here should just exist.
*/
return MapUtils.ofWithNull(getStateName(), States
.get(step.states.get(key), index));
}
return null;
}
@Override
public Map compute(int index, ITransition transition, Map inputs)
throws KlabException {
return MapUtils
.ofWithNull(getStateName(), step.states.get(key).getValue(index));
}
@Override
public boolean isProbabilistic() {
return false;
}
@Override
public String getLabel() {
return "unpack observation";
}
}
static IDirectActuator getDirectActuator(IModel model, IResolutionScope scope, IMonitor monitor) {
IObjectSource objectSource = null;
try {
IContextualizer contextualizer = model
.getContextualizer(scope, ((ResolutionScope) scope).monitor);
if (contextualizer instanceof ISubjectContextualizer
|| (!model.getActions().isEmpty() && NS.isThing(model.getObservable()))) {
return new SubjectContextualizerActuator((IActiveSubject) scope
.getSubject(), model, (ISubjectContextualizer) contextualizer, model
.getActions(), ((ResolutionScope) scope).monitor);
} else if (contextualizer instanceof IProcessContextualizer
|| (!model.getActions().isEmpty() && NS.isProcess(model.getObservable()))) {
// TODO use subject factory; run initialization actions.
Process process = new Process(model, scope.getSubject(), monitor);
IProcessActuator a = new ProcessActuator(process, model, (IProcessContextualizer) contextualizer, model
.getActions(), ((ResolutionScope) scope).monitor);
process.setActuator(a);
return a;
} else if (contextualizer instanceof IEventInstantiator) {
return new EventInstantiatorActuator((IActiveSubject) scope
.getSubject(), model, (IEventInstantiator) contextualizer, model
.getActions(), ((ResolutionScope) scope).monitor);
} else if (contextualizer instanceof ISubjectInstantiator) {
return new SubjectInstantiationActuator((IActiveSubject) scope
.getSubject(), model, (ISubjectInstantiator) contextualizer, model
.getActions(), ((ResolutionScope) scope).monitor);
} else if (contextualizer instanceof IRelationshipInstantiator) {
return new RelationshipInstantiationActuator((IActiveSubject) scope
.getSubject(), model, (IRelationshipInstantiator) contextualizer, model
.getActions(), ((ResolutionScope) scope).monitor);
} else if ((objectSource = model.getObjectSource(monitor)) != null) {
IDirectInstantiator instantiator = objectSource.getInstantiator();
if (instantiator != null) {
if (instantiator instanceof ISubjectInstantiator) {
return new SubjectInstantiationActuator((IActiveSubject) scope
.getSubject(), model, (ISubjectInstantiator) instantiator, model
.getActions(), ((ResolutionScope) scope).monitor);
} else if (instantiator != null) {
if (instantiator instanceof IEventInstantiator) {
return new EventInstantiatorActuator((IActiveSubject) scope
.getSubject(), model, (IEventInstantiator) instantiator, model
.getActions(), ((ResolutionScope) scope).monitor);
}
}
}
}
} catch (KlabException e) {
// stay null, but report.
((ResolutionScope) scope).monitor
.warn("error in " + model.getName()
+ ": cannot produce contextualizer");
}
return null;
}
}