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

org.integratedmodelling.engine.modelling.ScaleMediator 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.HashMap;
import java.util.Map;

import org.integratedmodelling.api.knowledge.IProperty;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.IActiveDirectObservation;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.modelling.IState;
import org.integratedmodelling.api.modelling.agents.IObservationGraphNode;
import org.integratedmodelling.api.modelling.agents.IScaleMediator;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.api.time.ITemporalSeries;
import org.integratedmodelling.api.time.ITimeInstant;
import org.integratedmodelling.engine.modelling.ObservationController.AgentStatus;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabInternalErrorException;
import org.integratedmodelling.exceptions.KlabResourceNotFoundException;
import org.integratedmodelling.exceptions.KlabUnsupportedOperationException;

import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.RTree;

/**
 * Scale Mediators do the work of modifying a state expressed in one scale into another scale, and possibly
 * caching an intermediate representation for efficient re-modification into other arbitrary scales.
 *
 * @author luke
 *
 */
public abstract class ScaleMediator implements IScaleMediator {

    protected static final IMetadata metadata = new ScaleMediatorMetadata(); // override
                                                                             // in
                                                                             // subclasses

    protected final ObservationController               controller;
    protected final IActiveDirectObservation            subject;
    protected final ITemporalSeries timeOrderedStateObservations = new TemporalSeries();

    private static final long SOME_ACCEPTABLE_SLEEP_WAIT_INTERVAL = 500;

    /**
     * SubjectObservation: a node in the temporal series of observations of the observedSubject, as seen by
     * this ScaleMediator.
     *
     * Subclass this to fit the various interpolation strategies. The representation can be changed in any way
     * appropriate for the interpolation mechanism; for instance, if one of the state properties is useless,
     * feel free to delete it from the SubjectObservation nodes as they are created.
     *
     * Consider adding a member: "Object intermediateRepresentation" if the interpolation strategy cannot
     * provide temporal continuity in its representation; that is, if the intermediate representation must be
     * stored as snapshots, then it would make sense to store each snapshot with each instance of
     * SubjectObservation.
     *
     * Alternatively, if temporal continuity can be represented in one object (i.e. an n-dimensional vector
     * image), then it would be more fitting to store that as a member of ScaleMediator
     *
     * @author luke
     *
     */
    protected class SubjectObservation {
        final IObservationGraphNode               agentStateNode;
        final Map               spatialIndexes = new HashMap();
        final Map> outputStates   = new HashMap>(); // the
                                                                                                                  // output
                                                                                                                  // cache(s)

        SubjectObservation(IObservationGraphNode agentStateNode) {
            this.agentStateNode = agentStateNode;
        }

        Object getValue(IProperty property, int index) {
            return agentStateNode.getAgentState().getStates().get(property).getValue(index);
        }

        // TODO allow for arbitrary spatial dimensions (see notes on ISpatialExtent)
        public RTree getSpatialIndex(IProperty property) {
            RTree result = spatialIndexes.get(property);
            if (result == null) {
                result = new RTree();
                IState state = agentStateNode.getAgentState().getStates().get(property);
                ISpatialExtent spaceExtent = state.getSpace();

                for (int i = 0; i < state.getValueCount(); i++) {
                    ISpatialExtent extent = spaceExtent.getExtent(i);
                    Rectangle rectangle = getRectangleFromEnvelope(extent);
                    result.add(rectangle, i);
                }

                spatialIndexes.put(property, result);
            }
            return result;
        }
    }

    protected Rectangle getRectangleFromEnvelope(ISpatialExtent extent) {
        // float[] min = { (float) extent.getMinX(), (float) extent.getMinY() };
        // float[] max = { (float) extent.getMaxX(), (float) extent.getMaxY() };
        Rectangle rectangle = new Rectangle((float) extent.getMinX(), (float) extent.getMinY(), (float) extent
                .getMaxX(), (float) extent.getMaxY());
        return rectangle;
    }

    /**
     * TODO startTime is currently always null, because it's not possible to set through subclasses. If an
     * interpolator is created @ runtime for a pre-existing agent, then it might lead to wasted computation...
     * probably not a big deal at this point, but warrants re-thinking.
     *
     * @param subject
     * @param controller
     * @param startTime
     * @throws KlabResourceNotFoundException
     */
    public ScaleMediator(IActiveDirectObservation subject, ObservationController controller,
            ITimeInstant startTime)
                    throws KlabResourceNotFoundException {
        this.subject = subject;
        this.controller = controller;
        controller.subscribe(this, subject, startTime);
    }

    public ScaleMediator(IActiveDirectObservation subject, ObservationController controller)
            throws KlabResourceNotFoundException {
        this(subject, controller, null);
    }

    @Override
    public void notify(IObservationGraphNode node) {
        SubjectObservation observation = new SubjectObservation(node);
        timeOrderedStateObservations.put(node.getAgentState().getTimePeriod(), observation);
    }

    @Override
    public IState getTargetState(ITimeInstant time, IProperty property, IScale targetScale)
            throws KlabException {
        // TODO is this IConcept? -------------------^

        // TODO record dependencies for each observation made by a caller

        // TODO if the agent is reported "dead", then the caller will proceed without a causal link.
        // But if a collision then causes the agent to stay alive, the original caller will not re-evaluate.
        // Fix this by making "dead" be an official agent-state, with a null end time. (requires refactoring
        // PeriodValue)

        // see if the observation has already been made
        SubjectObservation observation = timeOrderedStateObservations.getAtTime(time);

        while (observation == null) {
            // there is no observation result for this time instant yet. is the agent dead?
            AgentStatus deadOrAlive = controller.getAgentStatus(subject, time);

            if (deadOrAlive == AgentStatus.dead) {
                return null;
            } else if (deadOrAlive == AgentStatus.evaluated || deadOrAlive == AgentStatus.notYetEvaluated
                    || deadOrAlive == AgentStatus.nonExistent) {
                // either the agent is evaluated and we don't have the result yet, or nothing has changed.
                // wait it out longer...
                try {
                    Thread.sleep(SOME_ACCEPTABLE_SLEEP_WAIT_INTERVAL);
                } catch (InterruptedException e) {
                    // probably not a big deal - just wake up and keep trying
                }

                if (deadOrAlive == AgentStatus.evaluated) {
                    observation = timeOrderedStateObservations.getAtTime(time);
                }
            } else {
                // either the result was null or the enum has changed out of sync with this code. either way
                // it's weird.
                throw new KlabInternalErrorException("controller.getAgentStatus() either returned null or the enum has changed out of sync with this code");
            }
        }

        // now we have a valid subject observation for the query time, so convert it to the target scale and
        // return it.
        IState result = getStateForTargetScale(observation, property, targetScale);
        return result;
    }

    protected IState getStateForTargetScale(SubjectObservation observation, IProperty property, IScale targetScale)
            throws KlabUnsupportedOperationException {
        // has the output scale been generated for this observation yet?
        Map result = observation.outputStates.get(targetScale);
        if (result == null) {
            // output scale does not yet exist. Generate it for the result, and also save the result in the
            // output cache.
            result = generateTargetScale(observation, targetScale);
            observation.outputStates.put(targetScale, result);
        }
        return result.get(property);
    }

    /**
     * This is the meat of the interpolator. It does the actual work of generating target scales from observed
     * scales. It will render an intermediate (continuous) representation into a target (non-continuous)
     * scale, or it will directly translate the subject's scale into the observer's scale, as appropriate for
     * the ScaleMediator class.
     *
     * @param observation
     * @param targetScale
     * @return
     * @throws KlabUnsupportedOperationException
     */
    protected abstract Map generateTargetScale(SubjectObservation observation, IScale targetScale)
            throws KlabUnsupportedOperationException;

    @Override
    public IMetadata getMetadata() {
        return metadata;
    }

    /**
     * the static version of this method is needed because the metadata represents *class*-level information;
     * i.e. what the algorithm is capable of, rather than the state of an instantiated object
     *
     * @return metadata
     */
    public static IMetadata getMetadataStatic() {
        return metadata;
    }

    @Override
    public void invalidate(ITimeInstant interruptTime) {
        SubjectObservation lastObservation = timeOrderedStateObservations.getLast();
        if (lastObservation == null) {
            return;
        }

        while (lastObservation.agentStateNode.getAgentState().getTimePeriod().getEnd()
                .compareTo(interruptTime) >= 0) {
            timeOrderedStateObservations.remove(lastObservation.agentStateNode.getAgentState()
                    .getTimePeriod().getStart());

            lastObservation = timeOrderedStateObservations.getLast();
            if (lastObservation == null) {
                return;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy