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

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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy