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

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

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

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;

import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IActiveSubject;
import org.integratedmodelling.api.modelling.IDirectObservation;
import org.integratedmodelling.api.modelling.agents.IAgentState;
import org.integratedmodelling.api.modelling.agents.ICollision;
import org.integratedmodelling.api.modelling.agents.IObservationController;
import org.integratedmodelling.api.modelling.agents.IObservationGraphNode;
import org.integratedmodelling.api.modelling.agents.IObservationGraphNodeSubscriber;
import org.integratedmodelling.api.modelling.agents.IObservationTask;
import org.integratedmodelling.api.modelling.scheduling.ITransition;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.time.ITemporalSeries;
import org.integratedmodelling.api.time.ITimeInstant;
import org.integratedmodelling.api.time.ITimePeriod;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.engine.introspection.CallTracer;
import org.integratedmodelling.engine.modelling.TemporalCausalGraph.InvalidationResult;
import org.integratedmodelling.engine.time.literals.PeriodValueUnboundedEnd;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabResourceNotFoundException;
import org.integratedmodelling.exceptions.KlabValidationException;

public class ObservationController implements IObservationController {

    private final TemporalCausalGraph causalGraph;
    private final PriorityQueue                                      taskQueue           = new PriorityQueue();
    private final Set                                                currentlyProcessing = new HashSet();
    private final HashMap                             agentDetails        = new HashMap();

    private final IMonitor                                                             monitor;

    private final ITimeInstant                                                         simulationEndTime;

    /*
     * this disables/enables the generation of collision detection tasks. Re-enable in testing
     * when they actually do anything.
     */
    private boolean collisionDetectionEnabled = false;

    private class AgentDetail {
        IActiveDirectObservation                               subject;
        ITimeInstant                                           birth;
        ITimeInstant                                           death       = null;
        ITimeInstant                                           lastKnownAliveTime;
        HashMap subscribers = new HashMap();

        public AgentDetail(IActiveDirectObservation subject, ITimePeriod initialTimePeriod) {
            this.subject = subject;
            birth = initialTimePeriod.getStart();
            lastKnownAliveTime = initialTimePeriod.getEnd();
        }
    }

    /**
     * represents the agent's status as of a specific point in time: is it alive? dead?
     * has it been evaluated yet?
     */
    public enum AgentStatus {
        nonExistent,
        evaluated,
        notYetEvaluated,
        dead
    }

    public ObservationController(
            TemporalCausalGraph causalGraph,
            IMonitor monitor, ITimeInstant simulationEndTime) {
        this.causalGraph = causalGraph;
        this.monitor = monitor;
        // this.scenarios = scenarios;
        this.simulationEndTime = simulationEndTime;
    }

    /**
     * TODO the recursion should be here and ensure that 1. tasks respect the sequence in
     * the dataflow; 2. each task re-enqueues its next task in the same order.
     */
    @Override
    public IObservationGraphNode createAgent(IActiveDirectObservation subject, IAgentState initialState, ITimePeriod initialTimePeriod, IObservationGraphNode parentNode, IObservationTask creationTask, boolean enqueueSubsequentTask) {

        IObservationGraphNode node = new ObservationGraphNode(initialState, creationTask);

        causalGraph.createAgent(subject, node, initialTimePeriod, parentNode);
        agentDetails.put(subject, new AgentDetail(subject, initialTimePeriod));

        // will the agent remain alive for another observation? (this code assumes yes)
        if (enqueueSubsequentTask) {
            IObservationTask subsequentTaskForThisAgent = new ObservationTask(subject, initialTimePeriod
                    .getEnd(), node, this);
            enqueueTask(subsequentTaskForThisAgent);
        }

        return node;
    }

    private void enqueueTask(IObservationTask task) {
//        KLAB.info("TASKING " + task);
        taskQueue.add(task);
        task.getParentNode().addTaskCausedByThisNodeState(task);
    }

    @Override
    public IObservationTask getNext() {
        IObservationTask task = taskQueue.poll();
        while (task != null && !task.isValid()
                && task.getObservationTime().compareTo(simulationEndTime) >= 0) {
            // poll until we find a task which has not been invalidated by a collision
            // (exits if the queue is empty)
            task = taskQueue.poll();
        }

        if (task != null) {
            // we found a valid task before the queue was empty, so mark it as 'working'
            // and return it
            task.startedWorkAt(System.currentTimeMillis());
            currentlyProcessing.add(task);
        }

        monitor.debug("ObservationController is returning task " + CallTracer.detailedDescription(task));
        return task;
    }

    @Override
    public void setResult(IObservationTask task, ITransition result) throws KlabValidationException {

        // make sure we're dealing with the local task (so its state is accurate)
        task = getThreadlocalTaskObject(task);

        currentlyProcessing.remove(task);

        if (!task.isValid()) {
            // the task has been invalidated by a collision while it was processing, so
            // don't do anything with
            // its result
            return;
        }

        // Sanity check - have any of the dependencies become invalid during observation?
        IObservationGraphNode dependencyNode;
        Collection> dependencies = result
                .getObservationDependencies();
        for (Pair dependency : dependencies) {
            dependencyNode = dependency.getSecond();

            // make sure we're dealing with a local graph node (so its state is accurate)
            dependencyNode = getThreadlocalGraphNodeObject(dependencyNode);

            // has the dependency been removed or shortened by an invalidation?
            if (dependencyNode == null
                    || !dependencyNode.getAgentState().getTimePeriod().contains(dependency.getFirst())) {
                // the dependency node has been removed, or it is no longer valid for the
                // time which it was
                // originally queried.
                // re-evaluate the result which has been returned (without setting the
                // current result)
                enqueueTask(task);
                return;
            }
        }

        IActiveDirectObservation agent = task.getSubject();
        AgentDetail agentDetail = agentDetails.get(agent); // null if agent doesn't exist
                                                           // yet
        IObservationGraphNode node;
        ITimePeriod timePeriod;

        if (result.agentSurvives()) {
            // set in the causal graph (also sets parent causal link with the agent's
            // previous state)
            IAgentState agentState = result.getAgentState();
            timePeriod = agentState.getTimePeriod();
            ITimeInstant agentStateEndTime = timePeriod.getEnd();

            if (agentDetail == null) {
                // agent does not yet exist
                node = createAgent(agent, agentState, timePeriod, task.getParentNode(), task, false);
                agentDetail = agentDetails.get(agent);
            } else {
                // agent exists - add a new node in its agent-state series
                node = new ObservationGraphNode(agentState, task, result);
            }

            // queue up collision detection tasks for all agent-states which overlap the
            // one just created
            if (collisionDetectionEnabled) {
                
                /*
                 * FIXME - if these are enabled the queue priorities get messed up, so ensure
                 * that the comparator in ObservationTask is up to scratch before enabling.
                 */
                for (IObservationGraphNode overlappingNode : getOverlappingAgentStateNodes(node)) {
                    if (overlappingNode.canCollideWithAnything()) { // returns false for
                                                                    // agent-death nodes
                        IAgentState overlappingAgentState = overlappingNode.getAgentState();
                        // use getEnd() because collision detection happens at the END of
                        // the
                        // overlapping period
                        ITimeInstant endInstant = overlappingAgentState.getTimePeriod().getEnd();
                        if (agentStateEndTime.compareTo(endInstant) < 0) {
                            endInstant = agentStateEndTime;
                        }

                        ObservationTaskCollisionDetection collisionDetectionTask = new ObservationTaskCollisionDetection(endInstant, node, overlappingNode, node, this);
                        enqueueTask(collisionDetectionTask);
                    }
                }
            }
        } else {
 
            // agent goes
            ITimeInstant timeOfDeath = task.getObservationTime();
            timePeriod = new PeriodValueUnboundedEnd(timeOfDeath.getMillis());
            node = new ObservationGraphNodeAgentDeath(task, result);
            agentDetail.death = timeOfDeath;
        }
        causalGraph.addStateNode(agent, node, timePeriod, task.getParentNode());

        // set any other *influential* links (i.e. what did the observer observe during
        // observation?)
        for (Pair dependency : dependencies) {
            causalGraph.addInfluentialRelationship(dependency.getSecond(), node);
        }

        // queue up the next tasks which are a result of this observation (does not
        // include collision
        // detection).
        // possible outcomes of a task are:
        // next observation task for this agent
        // agent creation/destruction (messaging, childbirth, etc)
        // misc. observation tasks (we haven't limited the possible reasons)
        for (IObservationTask nextTask : result.getFurtherObservationTasks()) {
            nextTask.setParentNode(node);
            enqueueTask(nextTask);
        }

        // notify any listeners that this agent has changed
        notify(agent);

    }

    /**
     * TODO in multi-threaded environments, use this method to convert an arbitrary task
     * object into the equivalent task which is "owned" by the observation controller, so
     * that its state can be updated.
     * 
     * The most straightforward way to do this would be to implement task.hashCode() and
     * task.equals(other) and find the original in a hash index.
     * 
     * @param task
     * @return
     */
    protected IObservationTask getThreadlocalTaskObject(IObservationTask task) {
        return task;
    }

    /**
     * TODO this does the same as getThreadlocalTaskObject, but for graph nodes. TODO
     * return null if the node has been removed from the graph
     * 
     * @see ObservationController#getThreadlocalTaskObject()
     */
    private IObservationGraphNode getThreadlocalGraphNodeObject(IObservationGraphNode node) {
        return node;
    }

    private Collection getOverlappingAgentStateNodes(IObservationGraphNode node) {
        Collection overlaps = causalGraph.getOverlapping(node.getAgentState()
                .getTimePeriod());
        overlaps.remove(node); // above method will return the original agentState
        return overlaps;
    }

    @Override
    public void collide(IObservationGraphNode node1, IObservationGraphNode node2, ICollision collision)
            throws KlabException {
        collide(node1, collision);
        collide(node2, collision);
    }

    @Override
    public void collide(IObservationGraphNode node, ICollision collision) throws KlabException {
        IActiveDirectObservation subject = node.getAgentState().getSubject();
        boolean stateMustChange = ((IActiveSubject) subject)
                .doesThisCollisionAffectYou(node.getAgentState(), collision);

        if (stateMustChange) {
            ITimeInstant collisionTime = collision.getCollisionTime();

            invalidateChildTasksAndSubscriberCaches(node, collisionTime);

            // terminate the agent-state at the collision time
            InvalidationResult statesToReEvaluate = causalGraph
                    .invalidate(node, collisionTime);

            // queue up the new observation which is now required.
            // All CAUSALLY dependent states and observation tasks which have been deleted
            // will be re-computed
            // by this task.
            ObservationTaskCollisionHandling postCollisionTask = new ObservationTaskCollisionHandling(agentDetails
                    .get(subject).subject, collision, node, this);
            enqueueTask(postCollisionTask);

            // re-queue the INFLUENTIALLY dependent observations which have become
            // necessary due to other
            // observations becoming invalid
            for (IObservationGraphNode state : statesToReEvaluate.getInfluential()) {
                invalidateChildTasksAndSubscriberCaches(state, collisionTime);
                enqueueTask(state.getTaskCreatingThisNodeState());
            }

            // CAUSALLY dependent observations should be deleted along with subsequent
            // tasks, without
            // re-queuing an observation.
            for (IObservationGraphNode state : statesToReEvaluate.getCausal()) {
                invalidateChildTasksAndSubscriberCaches(state, collisionTime);
            }
        }
    }

    /**
     * invalidate observation tasks which were a product of the post-collision time
     * period, and invalidate any IScaleMediator objects which have cached the state in
     * question.
     * 
     * @param agentState
     * @param collisionTime
     */
    private void invalidateChildTasksAndSubscriberCaches(IObservationGraphNode agentState, ITimeInstant collisionTime) {
        // NOTE: tasks can be in either the priority queue or the currently-processing
        // queue
        for (IObservationTask task : agentState.getTasksCausedByThisNodeState()) {
            if (task.getObservationTime().compareTo(collisionTime) > 0) {
                // the task was created as a result of the invalid partition of the
                // agent-state,
                // so invalidate it.
                // NOTE: don't remove it from any queues; this way is better for
                // performance.
                task.setInvalid();
            }
        }

        // invalidate the ScaleMediator listeners' caches
        // TODO it is essential that the cache invalidations are CONFIRMED before adding
        // any more tasks to the
        // queue
        // (this is trivially satisfied in single-threaded execution)
        for (IObservationGraphNodeSubscriber listener : agentState.getSubscribers()) {
            listener.invalidate(collisionTime);
        }
    }

    /**
     * 
     * @param agent
     * @param startTime
     *            set to null to start listening at the beginning.
     * @throws KlabResourceNotFoundException
     */
    @Override
    public void subscribe(IObservationGraphNodeSubscriber listener, IActiveDirectObservation agent, ITimeInstant startTime)
            throws KlabResourceNotFoundException {
        // register the subscriber (interpolator)
        AgentDetail agentDetail = agentDetails.get(agent);
        if (agentDetail == null) {
            throw new KlabResourceNotFoundException("Agent " + agent.toString());
        }
        agentDetail.subscribers.put(listener, startTime);

        // catch the listener up with what has been published since its start time
        notify(agent, listener);
    }

    private void notify(IActiveDirectObservation agent) {
        HashMap listeners = agentDetails
                .get(agent).subscribers;
        if (listeners == null) {
            return;
        }
        for (IObservationGraphNodeSubscriber listener : listeners.keySet()) {
            notify(agent, listener);
        }
    }

    private void notify(IActiveDirectObservation agent, IObservationGraphNodeSubscriber listener) {
        ITemporalSeries agentStateSeries = causalGraph.getAgentStateSeries(agent);
        HashMap agentListenerUpdateTimes = agentDetails
                .get(agent).subscribers;
        ITimeInstant lastNotified = agentListenerUpdateTimes.get(listener);
        IObservationGraphNode node;
        if (lastNotified == null) {
            node = agentStateSeries.getFirst();
        } else {
            node = agentStateSeries.getFollowing(lastNotified);
        }

        // iterate through the agent states which start AFTER the last-notified agent
        // state for this listener
        while (node != null) {
            listener.notify(node);
            lastNotified = node.getAgentState().getTimePeriod().getEnd();
            agentListenerUpdateTimes.put(listener, lastNotified);
            node = agentStateSeries.getFollowing(lastNotified);
        }
    }

    /**
     * is the agent alive? dead? non-existent?
     * 
     * @param agent
     * @param time
     * @return status
     */
    public AgentStatus getAgentStatus(IActiveDirectObservation agent, ITimeInstant time) {
        AgentDetail agentDetail = agentDetails.get(agent);
        if (agentDetail == null || agentDetail.birth.compareTo(time) >= 0) {
            // agent not created, or is born after the time given
            return AgentStatus.nonExistent;
        }

        // agent was born before the time given
        if (agentDetail.lastKnownAliveTime.compareTo(time) >= 0) {
            return AgentStatus.evaluated;
        }

        if (agentDetail.death != null && agentDetail.death.compareTo(time) < 0) {
            return AgentStatus.dead;
        }

        return AgentStatus.notYetEvaluated;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy